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