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