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