xref: /haiku/src/add-ons/input_server/devices/keyboard/TeamMonitorWindow.cpp (revision 541ff51a6ef4c47f8ab105ba6ff895cdbba83aca)
1 /*
2  * Copyright 2004-2008, Haiku.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Jérôme Duval
7  *		Axel Doerfler, axeld@pinc-software.de
8  */
9 
10 //!	Keyboard input server addon
11 
12 #include "TeamMonitorWindow.h"
13 
14 #include <stdio.h>
15 
16 #include <Application.h>
17 #include <CardLayout.h>
18 #include <Catalog.h>
19 #include <GroupLayoutBuilder.h>
20 #include <IconView.h>
21 #include <LocaleRoster.h>
22 #include <Message.h>
23 #include <MessageRunner.h>
24 #include <Roster.h>
25 #include <ScrollView.h>
26 #include <Screen.h>
27 #include <SpaceLayoutItem.h>
28 #include <String.h>
29 #include <TextView.h>
30 
31 #include <syscalls.h>
32 #include <tracker_private.h>
33 
34 #include "KeyboardInputDevice.h"
35 #include "TeamListItem.h"
36 
37 
38 #undef B_TRANSLATE_CONTEXT
39 #define B_TRANSLATE_CONTEXT "Team monitor"
40 
41 
42 TeamMonitorWindow* gTeamMonitorWindow = NULL;
43 
44 
45 struct TeamQuitter {
46 	team_id team;
47 	thread_id thread;
48 	BLooper* window;
49 };
50 
51 
52 status_t
53 QuitTeamThreadFunction(void* data)
54 {
55 	TeamQuitter* teamQuitter = reinterpret_cast<TeamQuitter*>(data);
56 	if (teamQuitter == NULL)
57 		return B_ERROR;
58 
59 	status_t status;
60 	BMessenger messenger(NULL, teamQuitter->team, &status);
61 	if (status != B_OK)
62 		return status;
63 
64 	BMessage message(B_QUIT_REQUESTED);
65 	BMessage reply;
66 
67 	messenger.SendMessage(&message, &reply, 3000000, 3000000);
68 
69 	bool result;
70 	if (reply.what != B_REPLY
71 		|| reply.FindBool("result", &result) != B_OK
72 		|| result == false) {
73 		message.what = kMsgQuitFailed;
74 		message.AddPointer("TeamQuitter", teamQuitter);
75 		message.AddInt32("error", reply.what);
76 		if (teamQuitter->window != NULL)
77 			teamQuitter->window->PostMessage(&message);
78 		return reply.what;
79 	}
80 
81 	return B_OK;
82 }
83 
84 
85 filter_result
86 FilterLocaleChanged(BMessage* message, BHandler** target,
87 	BMessageFilter *filter)
88 {
89 	if (message->what == B_LOCALE_CHANGED && gTeamMonitorWindow != NULL)
90 		gTeamMonitorWindow->LocaleChanged();
91 
92 	return B_DISPATCH_MESSAGE;
93 }
94 
95 
96 class AllShowingTextView : public BTextView {
97 public:
98 							AllShowingTextView(const char* name);
99 	virtual void			FrameResized(float width, float height);
100 };
101 
102 
103 class TeamDescriptionView : public BView {
104 public:
105 							TeamDescriptionView();
106 	virtual					~TeamDescriptionView();
107 
108 	virtual void			MessageReceived(BMessage* message);
109 
110 			void			CtrlAltDelPressed(bool keyDown);
111 
112 			void			SetItem(TeamListItem* item);
113 			TeamListItem*	Item() { return fItem; }
114 
115 private:
116 			TeamListItem*	fItem;
117 			int32			fSeconds;
118 			BMessageRunner*	fRebootRunner;
119 			IconView*		fIconView;
120 	const	char*			fInfoString;
121 	const	char*			fSysComponentString;
122 	const	char*			fQuitOverdueString;
123 			BCardLayout*	fLayout;
124 			AllShowingTextView*	fIconTextView;
125 			AllShowingTextView*	fInfoTextView;
126 };
127 
128 
129 static const uint32 kMsgUpdate = 'TMup';
130 static const uint32 kMsgLaunchTerminal = 'TMlt';
131 const uint32 TM_CANCEL = 'TMca';
132 const uint32 TM_FORCE_REBOOT = 'TMfr';
133 const uint32 TM_KILL_APPLICATION = 'TMka';
134 const uint32 TM_QUIT_APPLICATION = 'TMqa';
135 const uint32 TM_RESTART_DESKTOP = 'TMrd';
136 const uint32 TM_SELECTED_TEAM = 'TMst';
137 
138 static const uint32 kMsgRebootTick = 'TMrt';
139 
140 
141 TeamMonitorWindow::TeamMonitorWindow()
142 	:
143 	BWindow(BRect(0, 0, 350, 400), B_TRANSLATE("Team monitor"),
144 		B_TITLED_WINDOW_LOOK, B_MODAL_ALL_WINDOW_FEEL,
145 		B_NOT_MINIMIZABLE | B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS
146 			| B_CLOSE_ON_ESCAPE | B_AUTO_UPDATE_SIZE_LIMITS,
147 		B_ALL_WORKSPACES),
148 	fQuitting(false),
149 	fUpdateRunner(NULL)
150 {
151 	BGroupLayout* layout = new BGroupLayout(B_VERTICAL);
152 	float inset = 10;
153 	layout->SetInsets(inset, inset, inset, inset);
154 	layout->SetSpacing(inset);
155 	SetLayout(layout);
156 
157 	layout->View()->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
158 
159 	fListView = new BListView("teams");
160 	fListView->SetSelectionMessage(new BMessage(TM_SELECTED_TEAM));
161 
162 	BScrollView* scrollView = new BScrollView("scroll_teams", fListView,
163 		0, B_SUPPORTS_LAYOUT, false, true, B_FANCY_BORDER);
164 	layout->AddView(scrollView);
165 
166 	BGroupView* groupView = new BGroupView(B_HORIZONTAL);
167 	layout->AddView(groupView);
168 
169 	fKillButton = new BButton("kill", B_TRANSLATE("Kill application"),
170 		new BMessage(TM_KILL_APPLICATION));
171 	groupView->AddChild(fKillButton);
172 	fKillButton->SetEnabled(false);
173 
174 	fQuitButton = new BButton("quit", B_TRANSLATE("Quit application"),
175 		new BMessage(TM_QUIT_APPLICATION));
176 	groupView->AddChild(fQuitButton);
177 	fQuitButton->SetEnabled(false);
178 
179 	groupView->GroupLayout()->AddItem(BSpaceLayoutItem::CreateGlue());
180 
181 	fDescriptionView = new TeamDescriptionView;
182 	layout->AddView(fDescriptionView);
183 
184 	groupView = new BGroupView(B_HORIZONTAL);
185 	layout->AddView(groupView);
186 
187 	BButton* forceReboot = new BButton("force", B_TRANSLATE("Force reboot"),
188 		new BMessage(TM_FORCE_REBOOT));
189 	groupView->GroupLayout()->AddView(forceReboot);
190 
191 	BSpaceLayoutItem* glue = BSpaceLayoutItem::CreateGlue();
192 	glue->SetExplicitMinSize(BSize(inset, -1));
193 	groupView->GroupLayout()->AddItem(glue);
194 
195 	fRestartButton = new BButton("restart", B_TRANSLATE("Restart the desktop"),
196 		new BMessage(TM_RESTART_DESKTOP));
197 	SetDefaultButton(fRestartButton);
198 	groupView->GroupLayout()->AddView(fRestartButton);
199 
200 	glue = BSpaceLayoutItem::CreateGlue();
201 	glue->SetExplicitMinSize(BSize(inset, -1));
202 	groupView->GroupLayout()->AddItem(glue);
203 
204 	fCancelButton = new BButton("cancel", B_TRANSLATE("Cancel"),
205 		new BMessage(TM_CANCEL));
206 	SetDefaultButton(fCancelButton);
207 	groupView->GroupLayout()->AddView(fCancelButton);
208 
209 	BSize preferredSize = layout->View()->PreferredSize();
210 	if (preferredSize.width > Bounds().Width())
211 		ResizeTo(preferredSize.width, Bounds().Height());
212 	if (preferredSize.height > Bounds().Height())
213 		ResizeTo(Bounds().Width(), preferredSize.height);
214 
215 	BRect screenFrame = BScreen(this).Frame();
216 	BPoint point;
217 	point.x = (screenFrame.Width() - Bounds().Width()) / 2;
218 	point.y = (screenFrame.Height() - Bounds().Height()) / 2;
219 
220 	if (screenFrame.Contains(point))
221 		MoveTo(point);
222 
223 	SetSizeLimits(Bounds().Width(), Bounds().Width() * 2,
224 		Bounds().Height(), screenFrame.Height());
225 
226 	fRestartButton->Hide();
227 
228 	AddShortcut('T', B_COMMAND_KEY | B_OPTION_KEY,
229 		new BMessage(kMsgLaunchTerminal));
230 	AddShortcut('W', B_COMMAND_KEY, new BMessage(B_QUIT_REQUESTED));
231 
232 	gLocalizedNamePreferred
233 		= BLocaleRoster::Default()->IsFilesystemTranslationPreferred();
234 
235 	gTeamMonitorWindow = this;
236 
237 	if (be_app->Lock()) {
238 		be_app->AddCommonFilter(new BMessageFilter(B_ANY_DELIVERY,
239 			B_ANY_SOURCE, B_LOCALE_CHANGED, FilterLocaleChanged));
240 		be_app->Unlock();
241 	}
242 }
243 
244 
245 TeamMonitorWindow::~TeamMonitorWindow()
246 {
247 	while (fTeamQuitterList.ItemAt(0) != NULL) {
248 		TeamQuitter* teamQuitter = reinterpret_cast<TeamQuitter*>
249 			(fTeamQuitterList.RemoveItem((int32) 0));
250 		if (teamQuitter != NULL) {
251 			status_t status;
252 			wait_for_thread(teamQuitter->thread, &status);
253 			delete teamQuitter;
254 		}
255 	}
256 }
257 
258 
259 void
260 TeamMonitorWindow::MessageReceived(BMessage* msg)
261 {
262 	switch (msg->what) {
263 		case SYSTEM_SHUTTING_DOWN:
264 			fQuitting = true;
265 			break;
266 
267 		case kMsgUpdate:
268 			UpdateList();
269 			break;
270 
271 		case kMsgCtrlAltDelPressed:
272 			bool keyDown;
273 			if (msg->FindBool("key down", &keyDown) != B_OK)
274 				break;
275 
276 			fDescriptionView->CtrlAltDelPressed(keyDown);
277 			break;
278 
279 		case kMsgDeselectAll:
280 			fListView->DeselectAll();
281 			break;
282 
283 		case kMsgLaunchTerminal:
284 			be_roster->Launch("application/x-vnd.Haiku-Terminal");
285 			break;
286 
287 		case TM_FORCE_REBOOT:
288 			_kern_shutdown(true);
289 			break;
290 
291 		case TM_KILL_APPLICATION:
292 		{
293 			TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(
294 				fListView->CurrentSelection()));
295 			if (item != NULL) {
296 				kill_team(item->GetInfo()->team);
297 				UpdateList();
298 			}
299 			break;
300 		}
301 		case TM_QUIT_APPLICATION:
302 		{
303 			TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(
304 				fListView->CurrentSelection()));
305 			if (item != NULL) {
306 				QuitTeam(item);
307 			}
308 			break;
309 		}
310 		case kMsgQuitFailed:
311 			MarkUnquittableTeam(msg);
312 			break;
313 
314 		case TM_RESTART_DESKTOP:
315 		{
316 			if (!be_roster->IsRunning(kTrackerSignature))
317 				be_roster->Launch(kTrackerSignature);
318 			if (!be_roster->IsRunning(kDeskbarSignature))
319 				be_roster->Launch(kDeskbarSignature);
320 			fRestartButton->Hide();
321 			SetDefaultButton(fCancelButton);
322 			break;
323 		}
324 		case TM_SELECTED_TEAM:
325 		{
326 			fKillButton->SetEnabled(fListView->CurrentSelection() >= 0);
327 			TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(
328 				fListView->CurrentSelection()));
329 			fDescriptionView->SetItem(item);
330 			fQuitButton->SetEnabled(item != NULL && item->IsApplication());
331 			break;
332 		}
333 		case TM_CANCEL:
334 			PostMessage(B_QUIT_REQUESTED);
335 			break;
336 
337 		default:
338 			BWindow::MessageReceived(msg);
339 			break;
340 	}
341 }
342 
343 
344 bool
345 TeamMonitorWindow::QuitRequested()
346 {
347 	Disable();
348 	return fQuitting;
349 }
350 
351 
352 void
353 TeamMonitorWindow::UpdateList()
354 {
355 	bool changed = false;
356 
357 	for (int32 i = 0; i < fListView->CountItems(); i++) {
358 		TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
359 		if (item != NULL)
360 			item->SetFound(false);
361 	}
362 
363 	int32 cookie = 0;
364 	team_info info;
365 	while (get_next_team_info(&cookie, &info) == B_OK) {
366 		if (info.team <=16)
367 			continue;
368 
369 		bool found = false;
370 		for (int32 i = 0; i < fListView->CountItems(); i++) {
371 			TeamListItem* item
372 				= dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
373 			if (item != NULL && item->GetInfo()->team == info.team) {
374 				item->SetFound(true);
375 				found = true;
376 			}
377 		}
378 
379 		if (!found) {
380 			TeamListItem* item = new TeamListItem(info);
381 
382 			fListView->AddItem(item,
383 				item->IsSystemServer() ? fListView->CountItems() : 0);
384 			item->SetFound(true);
385 			changed = true;
386 		}
387 	}
388 
389 	for (int32 i = fListView->CountItems() - 1; i >= 0; i--) {
390 		TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
391 		if (item != NULL && !item->Found()) {
392 			if (item == fDescriptionView->Item()) {
393 				fDescriptionView->SetItem(NULL);
394 				fKillButton->SetEnabled(false);
395 				fQuitButton->SetEnabled(false);
396 			}
397 
398 			delete fListView->RemoveItem(i);
399 			changed = true;
400 		}
401 	}
402 
403 	if (changed)
404 		fListView->Invalidate();
405 
406 	bool desktopRunning = be_roster->IsRunning(kTrackerSignature)
407 		&& be_roster->IsRunning(kDeskbarSignature);
408 	if (!desktopRunning && fRestartButton->IsHidden()) {
409 		fRestartButton->Show();
410 		SetDefaultButton(fRestartButton);
411 		fRestartButton->Parent()->Layout(true);
412 	}
413 
414 	fRestartButton->SetEnabled(!desktopRunning);
415 }
416 
417 
418 void
419 TeamMonitorWindow::Enable()
420 {
421 	if (Lock()) {
422 		if (IsHidden()) {
423 			BMessage message(kMsgUpdate);
424 			fUpdateRunner = new BMessageRunner(this, &message, 1000000LL);
425 
426 			UpdateList();
427 			Show();
428 		}
429 		Unlock();
430 	}
431 }
432 
433 
434 void
435 TeamMonitorWindow::Disable()
436 {
437 	delete fUpdateRunner;
438 	fUpdateRunner = NULL;
439 	Hide();
440 	fListView->DeselectAll();
441 	for (int32 i = 0; i < fListView->CountItems(); i++) {
442 		TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
443 		if (item != NULL)
444 			item->SetRefusingToQuit(false);
445 	}
446 }
447 
448 
449 void
450 TeamMonitorWindow::LocaleChanged()
451 {
452 	BLocaleRoster::Default()->Refresh();
453 	gLocalizedNamePreferred
454 		= BLocaleRoster::Default()->IsFilesystemTranslationPreferred();
455 
456 	for (int32 i = 0; i < fListView->CountItems(); i++) {
457 		TeamListItem* item
458 			= dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
459 		if (item != NULL)
460 			item->CacheLocalizedName();
461 	}
462 }
463 
464 
465 void
466 TeamMonitorWindow::QuitTeam(TeamListItem* item)
467 {
468 	if (item == NULL)
469 		return;
470 
471 	TeamQuitter* teamQuitter = new TeamQuitter;
472 	teamQuitter->team = item->GetInfo()->team;
473 	teamQuitter->window = this;
474 	teamQuitter->thread = spawn_thread(QuitTeamThreadFunction,
475 		"team quitter", B_DISPLAY_PRIORITY, teamQuitter);
476 
477 	if (teamQuitter->thread < 0) {
478 		delete teamQuitter;
479 		return;
480 	}
481 
482 	fTeamQuitterList.AddItem(teamQuitter);
483 
484 	if (resume_thread(teamQuitter->thread) != B_OK) {
485 		fTeamQuitterList.RemoveItem(teamQuitter);
486 		delete teamQuitter;
487 	}
488 }
489 
490 
491 void
492 TeamMonitorWindow::MarkUnquittableTeam(BMessage* message)
493 {
494 	if (message == NULL)
495 		return;
496 
497 	int32 reply;
498 	if (message->FindInt32("error", &reply) != B_OK)
499 		return;
500 
501 	TeamQuitter* teamQuitter;
502 	if (message->FindPointer("TeamQuitter",
503 		reinterpret_cast<void**>(&teamQuitter)) != B_OK)
504 		return;
505 
506 	for (int32 i = 0; i < fListView->CountItems(); i++) {
507 		TeamListItem* item
508 			= dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
509 		if (item != NULL && item->GetInfo()->team == teamQuitter->team) {
510 			item->SetRefusingToQuit(true);
511 			fListView->Select(i);
512 			fListView->InvalidateItem(i);
513 			fDescriptionView->SetItem(item);
514 			break;
515 		}
516 	}
517 
518 	fTeamQuitterList.RemoveItem(teamQuitter);
519 	delete teamQuitter;
520 }
521 
522 
523 //	#pragma mark -
524 
525 
526 TeamDescriptionView::TeamDescriptionView()
527 	:
528 	BView("description view", B_WILL_DRAW),
529 	fItem(NULL),
530 	fSeconds(4),
531 	fRebootRunner(NULL)
532 {
533 	fInfoString = B_TRANSLATE(
534 		"Select an application from the list above and click one of "
535 		"the buttons 'Kill application' and 'Quit application' "
536 		"in order to close it.\n\n"
537 		"Hold CONTROL+ALT+DELETE for %ld seconds to reboot.");
538 
539 	fSysComponentString = B_TRANSLATE("(This team is a system component)");
540 	fQuitOverdueString = B_TRANSLATE("If the application will not quit "
541 		"you may have to kill it.");
542 
543 	fInfoTextView = new AllShowingTextView("info text");
544 	fIconTextView = new AllShowingTextView("icon text");
545 
546 	fIconView = new IconView();
547 	fIconView->SetExplicitAlignment(
548 		BAlignment(B_ALIGN_HORIZONTAL_UNSET, B_ALIGN_TOP));
549 
550 	BView* teamPropertiesView = new BView("team properties", B_WILL_DRAW);
551 	teamPropertiesView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
552 	BGroupLayout* layout = new BGroupLayout(B_HORIZONTAL);
553 	teamPropertiesView->SetLayout(layout);
554 	BGroupLayoutBuilder(layout)
555 		.Add(fIconView)
556 		.Add(fIconTextView);
557 
558 	fLayout = new BCardLayout();
559 	SetLayout(fLayout);
560 	fLayout->AddView(fInfoTextView);
561 	fLayout->AddView(teamPropertiesView);
562 
563 	SetItem(NULL);
564 }
565 
566 
567 TeamDescriptionView::~TeamDescriptionView()
568 {
569 	delete fRebootRunner;
570 }
571 
572 
573 void
574 TeamDescriptionView::MessageReceived(BMessage* message)
575 {
576 	switch (message->what) {
577 		case kMsgRebootTick:
578 			fSeconds--;
579 			if (fSeconds == 0)
580 				Window()->PostMessage(TM_FORCE_REBOOT);
581 			else
582 				SetItem(fItem);
583 			break;
584 
585 		default:
586 			BView::MessageReceived(message);
587 	}
588 }
589 
590 
591 void
592 TeamDescriptionView::CtrlAltDelPressed(bool keyDown)
593 {
594 	if (!(keyDown ^ (fRebootRunner != NULL)))
595 		return;
596 
597 	delete fRebootRunner;
598 	fRebootRunner = NULL;
599 	fSeconds = 4;
600 
601 	if (keyDown) {
602 		Window()->PostMessage(kMsgDeselectAll);
603 		BMessage tick(kMsgRebootTick);
604 		fRebootRunner = new BMessageRunner(this, &tick, 1000000LL);
605 	}
606 
607 	SetItem(NULL);
608 }
609 
610 
611 void
612 TeamDescriptionView::SetItem(TeamListItem* item)
613 {
614 	fItem = item;
615 	int32 styleStart = 0;
616 	int32 styleEnd = 0;
617 	BTextView* view = NULL;
618 
619 	if (item == NULL) {
620 		BString text;
621 		text.SetToFormat(fInfoString, fSeconds);
622 		fInfoTextView->SetText(text);
623 		if (fRebootRunner != NULL && fSeconds < 4) {
624 			styleStart = text.FindLast('\n');
625 			styleEnd = text.Length();
626 		}
627 		view = fInfoTextView;
628 	} else {
629 		BString text = item->Path()->Path();
630 		if (item->IsSystemServer())
631 			text << "\n" << fSysComponentString;
632 		if (item->IsRefusingToQuit()) {
633 			text << "\n\n" << fQuitOverdueString;
634 			styleStart = text.FindLast('\n');
635 			styleEnd = text.Length();
636 		}
637 		view = fIconTextView;
638 		fIconTextView->SetText(text);
639 		fIconView->SetIcon(item->Path()->Path());
640 	}
641 
642 	if (styleStart != styleEnd && view != NULL) {
643 		BFont font;
644 		view->GetFont(&font);
645 		font.SetFace(B_BOLD_FACE);
646 		view->SetStylable(true);
647 		view->SetFontAndColor(styleStart, styleEnd, &font);
648 	}
649 
650 	if (fLayout == NULL)
651 		return;
652 
653 	if (item == NULL)
654 		fLayout->SetVisibleItem((int32)0);
655 	else
656 		fLayout->SetVisibleItem((int32)1);
657 
658 	Invalidate();
659 }
660 
661 
662 //	#pragma mark -
663 
664 
665 AllShowingTextView::AllShowingTextView(const char* name)
666 	:
667 	BTextView(name, B_WILL_DRAW)
668 {
669 	MakeEditable(false);
670 	MakeSelectable(false);
671 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
672 }
673 
674 
675 void
676 AllShowingTextView::FrameResized(float width, float height)
677 {
678 	float minHeight = TextHeight(0, CountLines() - 1);
679 	SetExplicitMinSize(BSize(B_SIZE_UNSET, minHeight));
680 	BTextView::FrameResized(width, minHeight);
681 }
682