xref: /haiku/src/apps/autoraise/AutoRaiseIcon.cpp (revision 072d3935c2497638e9c2502f574c133caeba9d3d)
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 
17 extern "C" _EXPORT BView *instantiate_deskbar_item(void)
18 {
19 	puts("Instanciating AutoRaise TrayView...");
20 	return (new TrayView);
21 }
22 
23 
24 status_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 static status_t
36 our_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 
51 ConfigMenu::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 
196 ConfigMenu::~ConfigMenu() {}
197 
198 //************************************************
199 
200 TrayView::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
206 TrayView::TrayView(BMessage *mdArchive):BView(mdArchive){
207 	_init();		//As above
208 }
209 
210 void TrayView::GetPreferredSize(float *w, float *h)
211 {
212 	*w = B_MINI_ICON;
213 	*h = B_MINI_ICON - 1;
214 }
215 
216 void 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 
296 TrayView::~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)
313 status_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)
321 TrayView *TrayView::Instantiate(BMessage *data) {
322 
323 	if (!validate_instantiation(data, "TrayView")) {
324 		return NULL;
325 	}
326 
327 	return (new TrayView(data));
328 }
329 
330 void 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 
341 void 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 
358 void 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 
401 int32 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
416 PRINT(("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 
429 int32 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) {
458 PRINT(("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 
467 PRINT(("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 
508 void 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 
575 AutoRaiseSettings *TrayView::Settings() const
576 {
577 	return _settings;
578 }
579 
580 void 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