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
44 TeamMonitorWindow* gTeamMonitorWindow = NULL;
45
46
47 struct TeamQuitter {
48 team_id team;
49 thread_id thread;
50 BLooper* window;
51 };
52
53
54 status_t
QuitTeamThreadFunction(void * data)55 QuitTeamThreadFunction(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
87 filter_result
FilterLocaleChanged(BMessage * message,BHandler ** target,BMessageFilter * filter)88 FilterLocaleChanged(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
98 filter_result
FilterKeyDown(BMessage * message,BHandler ** target,BMessageFilter * filter)99 FilterKeyDown(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
111 class TeamDescriptionView : public BView {
112 public:
113 TeamDescriptionView();
114 virtual ~TeamDescriptionView();
115
116 virtual void MessageReceived(BMessage* message);
117
118 void CtrlAltDelPressed(bool keyDown);
119
120 void SetItem(TeamListItem* item);
Item()121 TeamListItem* Item() { return fItem; }
122
123 private:
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
137 static const uint32 kMsgUpdate = 'TMup';
138 static const uint32 kMsgLaunchTerminal = 'TMlt';
139 const uint32 TM_CANCEL = 'TMca';
140 const uint32 TM_FORCE_REBOOT = 'TMfr';
141 const uint32 TM_KILL_APPLICATION = 'TMka';
142 const uint32 TM_QUIT_APPLICATION = 'TMqa';
143 const uint32 TM_RESTART_DESKTOP = 'TMrd';
144 const uint32 TM_SELECTED_TEAM = 'TMst';
145
146 static const uint32 kMsgRebootTick = 'TMrt';
147
148
TeamMonitorWindow()149 TeamMonitorWindow::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
~TeamMonitorWindow()239 TeamMonitorWindow::~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
253 void
MessageReceived(BMessage * msg)254 TeamMonitorWindow::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
339 void
Show()340 TeamMonitorWindow::Show()
341 {
342 fListView->MakeFocus();
343 BWindow::Show();
344 }
345
346
347 bool
QuitRequested()348 TeamMonitorWindow::QuitRequested()
349 {
350 Disable();
351 return fQuitting;
352 }
353
354
355 void
Enable()356 TeamMonitorWindow::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
376 void
Disable()377 TeamMonitorWindow::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
391 void
LocaleChanged()392 TeamMonitorWindow::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
407 void
QuitTeam(TeamListItem * item)408 TeamMonitorWindow::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
433 void
MarkUnquittableTeam(BMessage * message)434 TeamMonitorWindow::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
465 bool
HandleKeyDown(BMessage * msg)466 TeamMonitorWindow::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
507 void
_UpdateList()508 TeamMonitorWindow::_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
TeamDescriptionView()576 TeamDescriptionView::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
~TeamDescriptionView()626 TeamDescriptionView::~TeamDescriptionView()
627 {
628 delete fRebootRunner;
629 }
630
631
632 void
MessageReceived(BMessage * message)633 TeamDescriptionView::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
650 void
CtrlAltDelPressed(bool keyDown)651 TeamDescriptionView::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
670 void
SetItem(TeamListItem * item)671 TeamDescriptionView::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