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