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