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 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 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 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); 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 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 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 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 340 TeamMonitorWindow::Show() 341 { 342 fListView->MakeFocus(); 343 BWindow::Show(); 344 } 345 346 347 bool 348 TeamMonitorWindow::QuitRequested() 349 { 350 Disable(); 351 return fQuitting; 352 } 353 354 355 void 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 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 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 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 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 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 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 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 626 TeamDescriptionView::~TeamDescriptionView() 627 { 628 delete fRebootRunner; 629 } 630 631 632 void 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 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 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