1//#define DEBUG 1
2#include <BeBuild.h>
3
4#include "AutoRaiseIcon.h"
5#include <stdlib.h>
6#include <Catalog.h>
7#include <DataIO.h>
8#include <Screen.h>
9#include <View.h>
10#include <Debug.h>
11
12
13#undef B_TRANSLATION_CONTEXT
14#define B_TRANSLATION_CONTEXT "AutoRaiseIcon"
15
16
17extern "C" _EXPORT BView *instantiate_deskbar_item(void)
18{
19	puts("Instanciating AutoRaise TrayView...");
20	return (new TrayView);
21}
22
23
24status_t removeFromDeskbar(void *)
25{
26	BDeskbar db;
27	if (db.RemoveItem(APP_NAME) != B_OK) {
28		printf("Unable to remove AutoRaise from BDeskbar\n");
29	}
30
31	return B_OK;
32}
33
34
35static status_t
36our_image(image_info& image)
37{
38	int32 cookie = 0;
39	while (get_next_image_info(B_CURRENT_TEAM, &cookie, &image) == B_OK) {
40		if ((char *)our_image >= (char *)image.text
41			&& (char *)our_image <= (char *)image.text + image.text_size)
42			return B_OK;
43	}
44
45	return B_ERROR;
46}
47
48
49//**************************************************
50
51ConfigMenu::ConfigMenu(TrayView *tv, bool useMag)
52	:BPopUpMenu("config_popup", false, false){
53
54	BMenu *tmpm;
55	BMenuItem *tmpi;
56	BMessage *msg;
57
58	AutoRaiseSettings *s = tv->Settings();
59
60	SetFont(be_plain_font);
61
62
63	BMenuItem *active = new BMenuItem(B_TRANSLATE("Active"),
64		new BMessage(MSG_TOGGLE_ACTIVE));
65	active->SetMarked(s->Active());
66	AddItem(active);
67
68	tmpm = new BMenu(B_TRANSLATE("Mode"));
69	tmpm->SetFont(be_plain_font);
70
71	msg = new BMessage(MSG_SET_MODE);
72	msg->AddInt32(AR_MODE, Mode_All);
73	tmpi = new BMenuItem(B_TRANSLATE("Default (all windows)"), msg);
74	tmpi->SetMarked(s->Mode() == Mode_All);
75	tmpm->AddItem(tmpi);
76
77	msg = new BMessage(MSG_SET_MODE);
78	msg->AddInt32(AR_MODE, Mode_DeskbarOver);
79	tmpi = new BMenuItem(B_TRANSLATE("Deskbar only (over its area)"), msg);
80	tmpi->SetMarked(s->Mode() == Mode_DeskbarOver);
81	tmpm->AddItem(tmpi);
82
83	msg = new BMessage(MSG_SET_MODE);
84	msg->AddInt32(AR_MODE, Mode_DeskbarTouch);
85	tmpi = new BMenuItem(B_TRANSLATE("Deskbar only (touch)"), msg);
86	tmpi->SetMarked(s->Mode() == Mode_DeskbarTouch);
87	tmpm->AddItem(tmpi);
88
89
90	tmpm->SetTargetForItems(tv);
91	BMenuItem *modem = new BMenuItem(tmpm);
92	modem->SetEnabled(s->Active());
93	AddItem(modem);
94
95	tmpm = new BMenu(B_TRANSLATE("Inactive behaviour"));
96	tmpm->SetFont(be_plain_font);
97
98	msg = new BMessage(MSG_SET_BEHAVIOUR);
99	msg->AddInt32(AR_BEHAVIOUR, B_NORMAL_MOUSE);
100	tmpi = new BMenuItem(B_TRANSLATE("Normal"), msg);
101	tmpi->SetMarked(tv->fNormalMM == B_NORMAL_MOUSE);
102	tmpm->AddItem(tmpi);
103
104	msg = new BMessage(MSG_SET_BEHAVIOUR);
105	msg->AddInt32(AR_BEHAVIOUR, B_FOCUS_FOLLOWS_MOUSE);
106	tmpi = new BMenuItem(B_TRANSLATE("Focus follows mouse"), msg);
107	tmpi->SetMarked(tv->fNormalMM == B_FOCUS_FOLLOWS_MOUSE);
108	tmpm->AddItem(tmpi);
109
110	msg = new BMessage(MSG_SET_BEHAVIOUR);
111	msg->AddInt32(AR_BEHAVIOUR, B_WARP_FOCUS_FOLLOWS_MOUSE);
112	tmpi = new BMenuItem(B_TRANSLATE("Warping (ffm)"), msg);
113	tmpi->SetMarked(tv->fNormalMM == (mode_mouse)B_WARP_FOCUS_FOLLOWS_MOUSE);
114	tmpm->AddItem(tmpi);
115
116	msg = new BMessage(MSG_SET_BEHAVIOUR);
117	msg->AddInt32(AR_BEHAVIOUR, B_INSTANT_WARP_FOCUS_FOLLOWS_MOUSE);
118	tmpi = new BMenuItem(B_TRANSLATE("Instant warping (ffm)"), msg);
119	tmpi->SetMarked(tv->fNormalMM == (mode_mouse)B_INSTANT_WARP_FOCUS_FOLLOWS_MOUSE);
120	tmpm->AddItem(tmpi);
121
122	tmpm->SetTargetForItems(tv);
123	BMenuItem *behavm = new BMenuItem(tmpm);
124	AddItem(behavm);
125
126
127	tmpm = new BMenu(B_TRANSLATE("Delay"));
128	tmpm->SetFont(be_plain_font);
129
130	msg = new BMessage(MSG_SET_DELAY);
131	msg->AddInt64(AR_DELAY, 100000LL);
132	tmpi = new BMenuItem(B_TRANSLATE("0.1 s"), msg);
133	tmpi->SetMarked(tv->raise_delay == 100000LL);
134	tmpm->AddItem(tmpi);
135
136	msg = new BMessage(MSG_SET_DELAY);
137	msg->AddInt64(AR_DELAY, 200000LL);
138	tmpi = new BMenuItem(B_TRANSLATE("0.2 s"), msg);
139	tmpi->SetMarked(tv->raise_delay == 200000LL);
140	tmpm->AddItem(tmpi);
141
142	msg = new BMessage(MSG_SET_DELAY);
143	msg->AddInt64(AR_DELAY, 500000LL);
144	tmpi = new BMenuItem(B_TRANSLATE("0.5 s"), msg);
145	tmpi->SetMarked(tv->raise_delay == 500000LL);
146	tmpm->AddItem(tmpi);
147
148	msg = new BMessage(MSG_SET_DELAY);
149	msg->AddInt64(AR_DELAY, 1000000LL);
150	tmpi = new BMenuItem(B_TRANSLATE("1 s"), msg);
151	tmpi->SetMarked(tv->raise_delay == 1000000LL);
152	tmpm->AddItem(tmpi);
153
154	msg = new BMessage(MSG_SET_DELAY);
155	msg->AddInt64(AR_DELAY, 2000000LL);
156	tmpi = new BMenuItem(B_TRANSLATE("2 s"), msg);
157	tmpi->SetMarked(tv->raise_delay == 2000000LL);
158	tmpm->AddItem(tmpi);
159
160	msg = new BMessage(MSG_SET_DELAY);
161	msg->AddInt64(AR_DELAY, 3000000LL);
162	tmpi = new BMenuItem(B_TRANSLATE("3 s"), msg);
163	tmpi->SetMarked(tv->raise_delay == 3000000LL);
164	tmpm->AddItem(tmpi);
165
166	msg = new BMessage(MSG_SET_DELAY);
167	msg->AddInt64(AR_DELAY, 4000000LL);
168	tmpi = new BMenuItem(B_TRANSLATE("4 s"), msg);
169	tmpi->SetMarked(tv->raise_delay == 4000000LL);
170	tmpm->AddItem(tmpi);
171
172	msg = new BMessage(MSG_SET_DELAY);
173	msg->AddInt64(AR_DELAY, 5000000LL);
174	tmpi = new BMenuItem(B_TRANSLATE("5 s"), msg);
175	tmpi->SetMarked(tv->raise_delay == 5000000LL);
176	tmpm->AddItem(tmpi);
177
178	tmpm->SetTargetForItems(tv);
179	BMenuItem *delaym = new BMenuItem(tmpm);
180	delaym->SetEnabled(s->Active());
181
182	AddItem(delaym);
183
184	AddSeparatorItem();
185//	AddItem(new BMenuItem("Settings...", new BMessage(OPEN_SETTINGS)));
186
187	AddItem(new BMenuItem(B_TRANSLATE("About " APP_NAME B_UTF8_ELLIPSIS),
188		new BMessage(B_ABOUT_REQUESTED)));
189	AddItem(new BMenuItem(B_TRANSLATE("Remove from tray"),
190		new BMessage(REMOVE_FROM_TRAY)));
191
192	SetTargetForItems(tv);
193	SetAsyncAutoDestruct(true);
194}
195
196ConfigMenu::~ConfigMenu() {}
197
198//************************************************
199
200TrayView::TrayView()
201	:BView(BRect(0, 0, B_MINI_ICON, B_MINI_ICON -1), "AutoRaise", B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW){
202	_init(); 	//Initialization common to both constructors
203}
204
205//Rehydratable constructor
206TrayView::TrayView(BMessage *mdArchive):BView(mdArchive){
207	_init();		//As above
208}
209
210void TrayView::GetPreferredSize(float *w, float *h)
211{
212	*w = B_MINI_ICON;
213	*h = B_MINI_ICON - 1;
214}
215
216void TrayView::_init()
217{
218	thread_info ti;
219
220	watching = false;
221	_settings = new AutoRaiseSettings;
222
223	raise_delay = _settings->Delay();
224	current_window = 0;
225	polling_delay = 100000;
226	fPollerSem = create_sem(0, "AutoRaise poller sync");
227	last_raiser_thread = 0;
228	fNormalMM = mouse_mode();
229
230	_activeIcon = NULL;
231	_inactiveIcon = NULL;
232
233	get_thread_info(find_thread(NULL), &ti);
234	fDeskbarTeam = ti.team;
235
236	resume_thread(poller_thread = spawn_thread(poller, "AutoRaise desktop "
237		"poller", B_NORMAL_PRIORITY, (void *)this));
238
239	image_info info;
240	{
241		status_t result = our_image(info);
242		if (result != B_OK) {
243			printf("Unable to lookup image_info for the AutoRaise image: %s\n",
244				strerror(result));
245			removeFromDeskbar(NULL);
246			return;
247		}
248	}
249
250	BFile file(info.name, B_READ_ONLY);
251	if (file.InitCheck() != B_OK) {
252		printf("Unable to access AutoRaise image file: %s\n",
253			strerror(file.InitCheck()));
254		removeFromDeskbar(NULL);
255		return;
256	}
257
258	BResources res(&file);
259	if (res.InitCheck() != B_OK) {
260		printf("Unable to load image resources: %s\n",
261			strerror(res.InitCheck()));
262		removeFromDeskbar(NULL);
263		return;
264	}
265
266	size_t bmsz;
267	char *p;
268
269	p = (char *)res.LoadResource('MICN', ACTIVE_ICON, &bmsz);
270	_activeIcon = new BBitmap(BRect(0, 0, B_MINI_ICON-1, B_MINI_ICON -1),
271		B_CMAP8);
272	if (p == NULL) {
273		puts("ERROR loading active icon");
274		removeFromDeskbar(NULL);
275		return;
276	}
277	_activeIcon->SetBits(p, B_MINI_ICON * B_MINI_ICON, 0, B_CMAP8);
278
279	p = (char *)res.LoadResource('MICN', INACTIVE_ICON, &bmsz);
280	_inactiveIcon = new BBitmap(BRect(0, 0, B_MINI_ICON-1, B_MINI_ICON -1),
281		B_CMAP8);
282	if (p == NULL) {
283		puts("ERROR loading inactive icon");
284		removeFromDeskbar(NULL);
285		return;
286	}
287	_inactiveIcon->SetBits(p, B_MINI_ICON * B_MINI_ICON, 0, B_CMAP8);
288
289	SetDrawingMode(B_OP_ALPHA);
290	SetFlags(Flags() | B_WILL_DRAW);
291
292	// begin watching if we want
293	// (doesn't work here, better do it in AttachedToWindow())
294}
295
296TrayView::~TrayView(){
297	status_t ret;
298
299	if (watching) {
300		set_mouse_mode(fNormalMM);
301		watching = false;
302	}
303	delete_sem(fPollerSem);
304	wait_for_thread(poller_thread, &ret);
305	if (_activeIcon) delete _activeIcon;
306	if (_inactiveIcon) delete _inactiveIcon;
307	if (_settings) delete _settings;
308
309	return;
310}
311
312//Dehydrate into a message (called by the DeskBar)
313status_t TrayView::Archive(BMessage *data, bool deep) const {
314	status_t error=BView::Archive(data, deep);
315	data->AddString("add_on", APP_SIG);
316
317	return error;
318}
319
320//Rehydrate the View from a given message (called by the DeskBar)
321TrayView *TrayView::Instantiate(BMessage *data) {
322
323	if (!validate_instantiation(data, "TrayView")) {
324		return NULL;
325	}
326
327	return (new TrayView(data));
328}
329
330void TrayView::AttachedToWindow() {
331	if(Parent())
332		SetViewColor(Parent()->ViewColor());
333	if (_settings->Active()) {
334		fNormalMM = mouse_mode();
335		set_mouse_mode(B_FOCUS_FOLLOWS_MOUSE);
336		release_sem(fPollerSem);
337		watching = true;
338	}
339}
340
341void TrayView::Draw(BRect updaterect) {
342	BRect bnds(Bounds());
343
344	if (Parent()) SetHighColor(Parent()->ViewColor());
345	else SetHighColor(189, 186, 189, 255);
346	FillRect(bnds);
347
348	if (_settings->Active())
349	{
350		if (_activeIcon) DrawBitmap(_activeIcon);
351	}
352	else
353	{
354		if (_inactiveIcon) DrawBitmap(_inactiveIcon);
355	}
356}
357
358void TrayView::MouseDown(BPoint where) {
359	BWindow *window = Window();	/*To handle the MouseDown message*/
360	if (!window)	/*Check for proper instantiation*/
361		return;
362
363	BMessage *mouseMsg = window->CurrentMessage();
364	if (!mouseMsg)	/*Check for existence*/
365		return;
366
367	if (mouseMsg->what == B_MOUSE_DOWN) {
368		/*Variables for storing the button pressed / modifying key*/
369		uint32 	buttons = 0;
370		uint32  modifiers = 0;
371
372		/*Get the button pressed*/
373		mouseMsg->FindInt32("buttons", (int32 *) &buttons);
374		/*Get modifier key (if any)*/
375		mouseMsg->FindInt32("modifiers", (int32 *) &modifiers);
376
377		/*Now perform action*/
378		switch(buttons) {
379			case B_PRIMARY_MOUSE_BUTTON:
380			{
381				SetActive(!_settings->Active());
382
383				break;
384			}
385			case B_SECONDARY_MOUSE_BUTTON:
386			{
387				ConvertToScreen(&where);
388
389				//menu will delete itself (see constructor of ConfigMenu),
390				//so all we're concerned about is calling Go() asynchronously
391				ConfigMenu *menu = new ConfigMenu(this, false);
392				menu->Go(where, true, true, ConvertToScreen(Bounds()), true);
393
394				break;
395			}
396		}
397	}
398}
399
400
401int32 fronter(void *arg)
402{
403	TrayView *tv = (TrayView *)arg;
404	int32 ws = current_workspace();
405	volatile int32 tok = tv->current_window;
406	sem_id sem = tv->fPollerSem;
407
408	snooze(tv->raise_delay);
409
410	if (acquire_sem(sem) != B_OK)
411		return B_OK; // this really needs a better locking model...
412	if (ws != current_workspace())
413		goto end; // don't touch windows if we changed workspace
414	if (tv->last_raiser_thread != find_thread(NULL))
415		goto end; // seems a newer one has been spawn, exit
416PRINT(("tok = %" B_PRId32 " cw = %" B_PRId32 "\n", tok, tv->current_window));
417	if (tok == tv->current_window) {
418		bool doZoom = false;
419		BRect zoomRect(0.0f, 0.0f, 10.0f, 10.0f);
420		do_window_action(tok, B_BRING_TO_FRONT, zoomRect, doZoom);
421	}
422
423	end:
424	release_sem(sem);
425	return B_OK;
426}
427
428
429int32 poller(void *arg)
430{
431	TrayView *tv = (TrayView *)arg;
432	volatile int32 tok = tv->current_window;
433	int32 *tl = NULL;
434	int32 i, tlc;
435	window_info *wi = NULL;
436
437	int pass=0;
438	BPoint mouse;
439	uint32 buttons;
440
441	while (acquire_sem(tv->fPollerSem) == B_OK) {
442		release_sem(tv->fPollerSem);
443		pass++;
444		BLooper *l = tv->Looper();
445		if (!l || l->LockWithTimeout(500000) != B_OK)
446			continue;
447		tv->GetMouse(&mouse, &buttons);
448		tv->ConvertToScreen(&mouse);
449		tv->Looper()->Unlock();
450		if (buttons) // we don't want to interfere when the user is moving a window or something...
451			goto zzz;
452
453		tl = get_token_list(-1, &tlc);
454		for (i=0; i<tlc; i++) {
455			free(wi);
456			wi = get_window_info(tl[i]);
457			if (wi) {
458PRINT(("wi [%" B_PRId32 "] = %p, %" B_PRId32 " %s\n", i, wi, wi->layer,
459	((struct client_window_info *)wi)->name));
460				if (wi->layer < 3) // we hit the desktop or a window not on this WS
461					continue;
462				if ((wi->window_left > wi->window_right) || (wi->window_top > wi->window_bottom))
463					continue; // invalid window ?
464				if (wi->is_mini)
465					continue;
466
467PRINT(("if (!%s && (%li, %li)isin(%" B_PRId32 ")(%" B_PRId32 ", %" B_PRId32
468	", %" B_PRId32 ", %" B_PRId32 ") && (%" B_PRId32 " != %" B_PRId32 ") \n",
469	wi->is_mini?"true":"false", (long)mouse.x, (long)mouse.y, i,
470	wi->window_left, wi->window_right, wi->window_top, wi->window_bottom,
471	wi->server_token, tok));
472
473
474
475				if ((((long)mouse.x) > wi->window_left) && (((long)mouse.x) < wi->window_right)
476					&& (((long)mouse.y) > wi->window_top) && (((long)mouse.y) < wi->window_bottom)) {
477//((tv->_settings->Mode() != Mode_DeskbarOver) || (wi->team == tv->fDeskbarTeam))
478
479					if ((tv->_settings->Mode() == Mode_All) &&
480					(wi->server_token == tv->current_window))
481						goto zzz; // already raised
482
483					if ((tv->_settings->Mode() == Mode_All) || (wi->team == tv->fDeskbarTeam)) {
484						tv->current_window = wi->server_token;
485						tok = wi->server_token;
486						resume_thread(tv->last_raiser_thread = spawn_thread(fronter, "fronter", B_NORMAL_PRIORITY, (void *)tv));
487						goto zzz;
488					} else if (tv->_settings->Mode() == Mode_DeskbarTouch) // give up, before we find Deskbar under it
489						goto zzz;
490				}
491				free(wi);
492				wi=NULL;
493			} else
494				goto zzz;
495		}
496	zzz:
497//		puts("");
498		if (wi) free(wi);
499		wi = NULL;
500		if (tl) free(tl);
501		tl = NULL;
502		snooze(tv->polling_delay);
503	}
504	return B_OK;
505}
506
507
508void TrayView::MessageReceived(BMessage* message)
509{
510	BMessenger msgr;
511
512	BAlert *alert;
513	bigtime_t delay;
514	int32 mode;
515
516	switch(message->what)
517	{
518		case MSG_TOGGLE_ACTIVE:
519			SetActive(!_settings->Active());
520			break;
521		case MSG_SET_ACTIVE:
522			SetActive(true);
523			break;
524		case MSG_SET_INACTIVE:
525			SetActive(false);
526			break;
527		case MSG_SET_DELAY:
528			delay = DEFAULT_DELAY;
529			message->FindInt64(AR_DELAY, &delay);
530			raise_delay = delay;
531			_settings->SetDelay(delay);
532			break;
533		case MSG_SET_MODE:
534			mode = Mode_All;
535			message->FindInt32(AR_MODE, &mode);
536			_settings->SetMode(mode);
537			break;
538		case MSG_SET_BEHAVIOUR:
539		{
540			message->FindInt32(AR_BEHAVIOUR, &mode);
541			bool wasactive = _settings->Active();
542			if (wasactive)
543				SetActive(false);
544			fNormalMM = (mode_mouse)mode;
545			set_mouse_mode(fNormalMM);
546			if (wasactive)
547				SetActive(true);
548			break;
549		}
550		case REMOVE_FROM_TRAY:
551		{
552			thread_id tid = spawn_thread(removeFromDeskbar, "RemoveFromDeskbar", B_NORMAL_PRIORITY, (void*)this);
553			if (tid != 0) resume_thread(tid);
554
555			break;
556		}
557		case B_ABOUT_REQUESTED:
558			alert = new BAlert("about box",
559				B_TRANSLATE("AutoRaise, (c) 2002, mmu_man\nEnjoy :-)"),
560				B_TRANSLATE("OK"), NULL, NULL,
561				B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_INFO_ALERT);
562			alert->SetShortcut(0, B_ENTER);
563			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
564			alert->Go(NULL); // use asynchronous version
565			break;
566		case OPEN_SETTINGS:
567
568			break;
569
570		default:
571			BView::MessageReceived(message);
572	}
573}
574
575AutoRaiseSettings *TrayView::Settings() const
576{
577	return _settings;
578}
579
580void TrayView::SetActive(bool st)
581{
582	_settings->SetActive(st);
583	if (_settings->Active())
584	{
585		if (!watching) {
586			fNormalMM = mouse_mode();
587			set_mouse_mode(B_FOCUS_FOLLOWS_MOUSE);
588			release_sem(fPollerSem);
589			watching = true;
590		}
591	}
592	else
593	{
594		if (watching) {
595			acquire_sem(fPollerSem);
596			set_mouse_mode(fNormalMM);
597			watching = false;
598		}
599	}
600	Invalidate();
601}
602
603