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