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