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