1 /* 2 * Copyright 2005-2008, Ingo Weinhold, bonefish@users.sf.net. 3 * Copyright 2006-2009, Axel Dörfler, axeld@pinc-software.de. 4 * Copyright 2006-2008, Stephan Aßmus. 5 * Copyright 2006, Ryan Leavengood. 6 * Copyright 2021, Jacob Secunda. 7 * 8 * Distributed under the terms of the MIT License. 9 */ 10 11 #include "ShutdownProcess.h" 12 13 #include <new> 14 #include <string.h> 15 16 #include <signal.h> 17 #include <unistd.h> 18 19 #include <Alert.h> 20 #include <AppFileInfo.h> 21 #include <AppMisc.h> 22 #include <Autolock.h> 23 #include <Bitmap.h> 24 #include <Button.h> 25 #include <Catalog.h> 26 #include <File.h> 27 #include <Message.h> 28 #include <MessagePrivate.h> 29 #include <RegistrarDefs.h> 30 #include <Roster.h> // for B_REQUEST_QUIT 31 #include <Screen.h> 32 #include <String.h> 33 #include <TextView.h> 34 #include <View.h> 35 #include <Window.h> 36 37 #include <TokenSpace.h> 38 #include <util/DoublyLinkedList.h> 39 40 #include <syscalls.h> 41 42 #include "AppInfoListMessagingTargetSet.h" 43 #include "Debug.h" 44 #include "EventQueue.h" 45 #include "MessageDeliverer.h" 46 #include "MessageEvent.h" 47 #include "Registrar.h" 48 #include "RosterAppInfo.h" 49 #include "TRoster.h" 50 51 52 #undef B_TRANSLATION_CONTEXT 53 #define B_TRANSLATION_CONTEXT "ShutdownProcess" 54 55 56 using std::nothrow; 57 using namespace BPrivate; 58 59 // The time span a non-background application has after the quit message has 60 // been delivered (more precisely: has been handed over to the 61 // MessageDeliverer). 62 static const bigtime_t kAppQuitTimeout = 3000000; // 3 s 63 64 // The time span a background application has after the quit message has been 65 // delivered (more precisely: has been handed over to the MessageDeliverer). 66 static const bigtime_t kBackgroundAppQuitTimeout = 3000000; // 3 s 67 68 // The time span non-app processes have after the TERM signal has been send 69 // to them before they get a KILL signal. 70 static const bigtime_t kNonAppQuitTimeout = 500000; // 0.5 s 71 72 // The time span the app that has aborted the shutdown shall be displayed in 73 // the shutdown window before closing it automatically. 74 static const bigtime_t kDisplayAbortingAppTimeout = 3000000; // 3 s 75 76 static const int kStripeWidth = 30; 77 static const int kIconVSpacing = 6; 78 static const int kIconSize = 32; 79 80 // The speed of the animation played when an application is blocked on a modal 81 // panel. 82 static const bigtime_t kIconAnimateInterval = 50000; // 0.05 s 83 84 // message what fields (must not clobber the registrar's message namespace) 85 enum { 86 MSG_PHASE_TIMED_OUT = 'phto', 87 MSG_DONE = 'done', 88 MSG_KILL_APPLICATION = 'kill', 89 MSG_CANCEL_SHUTDOWN = 'cncl', 90 MSG_REBOOT_SYSTEM = 'lbot', 91 }; 92 93 // internal events 94 enum { 95 NO_EVENT, 96 ABORT_EVENT, 97 TIMEOUT_EVENT, 98 APP_QUIT_EVENT, 99 KILL_APP_EVENT, 100 REBOOT_SYSTEM_EVENT, 101 DEBUG_EVENT 102 }; 103 104 // phases 105 enum { 106 INVALID_PHASE = -1, 107 USER_APP_TERMINATION_PHASE = 0, 108 SYSTEM_APP_TERMINATION_PHASE = 1, 109 BACKGROUND_APP_TERMINATION_PHASE = 2, 110 OTHER_PROCESSES_TERMINATION_PHASE = 3, 111 ABORTED_PHASE = 4, 112 DONE_PHASE = 5, 113 }; 114 115 116 static bool 117 inverse_compare_by_registration_time(const RosterAppInfo* info1, 118 const RosterAppInfo* info2) 119 { 120 return (info2->registration_time < info1->registration_time); 121 } 122 123 124 /*! \brief Used to avoid type matching problems when throwing a constant. 125 */ 126 static inline 127 void 128 throw_error(status_t error) 129 { 130 throw error; 131 } 132 133 134 class ShutdownProcess::TimeoutEvent : public MessageEvent { 135 public: 136 TimeoutEvent(BHandler* target) 137 : MessageEvent(0, target, MSG_PHASE_TIMED_OUT) 138 { 139 SetAutoDelete(false); 140 141 fMessage.AddInt32("phase", INVALID_PHASE); 142 fMessage.AddInt32("team", -1); 143 } 144 145 void SetPhase(int32 phase) 146 { 147 fMessage.ReplaceInt32("phase", phase); 148 } 149 150 void SetTeam(team_id team) 151 { 152 fMessage.ReplaceInt32("team", team); 153 } 154 155 static int32 GetMessagePhase(BMessage* message) 156 { 157 int32 phase; 158 if (message->FindInt32("phase", &phase) != B_OK) 159 phase = INVALID_PHASE; 160 161 return phase; 162 } 163 164 static int32 GetMessageTeam(BMessage* message) 165 { 166 team_id team; 167 if (message->FindInt32("team", &team) != B_OK) 168 team = -1; 169 170 return team; 171 } 172 }; 173 174 175 class ShutdownProcess::InternalEvent 176 : public DoublyLinkedListLinkImpl<InternalEvent> { 177 public: 178 InternalEvent(uint32 type, team_id team, int32 phase) 179 : 180 fType(type), 181 fTeam(team), 182 fPhase(phase) 183 { 184 } 185 186 uint32 Type() const { return fType; } 187 team_id Team() const { return fTeam; } 188 int32 Phase() const { return fPhase; } 189 190 private: 191 uint32 fType; 192 int32 fTeam; 193 int32 fPhase; 194 }; 195 196 197 struct ShutdownProcess::InternalEventList : DoublyLinkedList<InternalEvent> { 198 }; 199 200 201 class ShutdownProcess::QuitRequestReplyHandler : public BHandler { 202 public: 203 QuitRequestReplyHandler(ShutdownProcess* shutdownProcess) 204 : BHandler("shutdown quit reply handler"), 205 fShutdownProcess(shutdownProcess) 206 { 207 } 208 209 virtual void MessageReceived(BMessage* message) 210 { 211 switch (message->what) { 212 case B_REPLY: 213 { 214 bool result; 215 thread_id thread; 216 if (message->FindBool("result", &result) == B_OK 217 && message->FindInt32("thread", &thread) == B_OK) { 218 if (!result) 219 fShutdownProcess->_NegativeQuitRequestReply(thread); 220 } 221 222 break; 223 } 224 225 default: 226 BHandler::MessageReceived(message); 227 break; 228 } 229 } 230 231 private: 232 ShutdownProcess *fShutdownProcess; 233 }; 234 235 236 class ShutdownProcess::ShutdownWindow : public BWindow { 237 public: 238 ShutdownWindow() 239 : BWindow(BRect(0, 0, 200, 100), B_TRANSLATE("Shutdown status"), 240 B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 241 B_ASYNCHRONOUS_CONTROLS | B_NOT_RESIZABLE | B_NOT_MINIMIZABLE 242 | B_NOT_ZOOMABLE | B_NOT_CLOSABLE, B_ALL_WORKSPACES), 243 fKillAppMessage(NULL), 244 fCurrentApp(-1) 245 { 246 } 247 248 ~ShutdownWindow() 249 { 250 for (int32 i = 0; AppInfo* info = (AppInfo*)fAppInfos.ItemAt(i); i++) { 251 delete info; 252 } 253 } 254 255 virtual bool QuitRequested() 256 { 257 return false; 258 } 259 260 status_t Init(BMessenger target) 261 { 262 // create the views 263 264 // root view 265 fRootView = new(nothrow) TAlertView(BRect(0, 0, 10, 10), "app icons", 266 B_FOLLOW_NONE, 0); 267 if (!fRootView) 268 return B_NO_MEMORY; 269 fRootView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 270 AddChild(fRootView); 271 272 // text view 273 fTextView = new(nothrow) BTextView(BRect(0, 0, 10, 10), "text", 274 BRect(0, 0, 10, 10), B_FOLLOW_NONE); 275 if (!fTextView) 276 return B_NO_MEMORY; 277 fTextView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 278 rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR); 279 fTextView->SetFontAndColor(be_plain_font, B_FONT_ALL, &textColor); 280 fTextView->MakeEditable(false); 281 fTextView->MakeSelectable(false); 282 fTextView->SetWordWrap(false); 283 fRootView->AddChild(fTextView); 284 285 // kill app button 286 fKillAppButton = new(nothrow) BButton(BRect(0, 0, 10, 10), "kill app", 287 B_TRANSLATE("Kill application"), NULL, B_FOLLOW_NONE); 288 if (!fKillAppButton) 289 return B_NO_MEMORY; 290 fRootView->AddChild(fKillAppButton); 291 292 BMessage* message = new BMessage(MSG_KILL_APPLICATION); 293 if (!message) 294 return B_NO_MEMORY; 295 message->AddInt32("team", -1); 296 fKillAppMessage = message; 297 fKillAppButton->SetMessage(message); 298 fKillAppButton->SetTarget(target); 299 300 // cancel shutdown button 301 fCancelShutdownButton = new(nothrow) BButton(BRect(0, 0, 10, 10), 302 "cancel shutdown", B_TRANSLATE("Cancel shutdown"), NULL, 303 B_FOLLOW_NONE); 304 if (!fCancelShutdownButton) 305 return B_NO_MEMORY; 306 fRootView->AddChild(fCancelShutdownButton); 307 308 message = new BMessage(MSG_CANCEL_SHUTDOWN); 309 if (!message) 310 return B_NO_MEMORY; 311 fCancelShutdownButton->SetMessage(message); 312 fCancelShutdownButton->SetTarget(target); 313 314 // reboot system button 315 fRebootSystemButton = new(nothrow) BButton(BRect(0, 0, 10, 10), 316 "reboot", B_TRANSLATE("Restart system"), NULL, B_FOLLOW_NONE); 317 if (!fRebootSystemButton) 318 return B_NO_MEMORY; 319 fRebootSystemButton->Hide(); 320 fRootView->AddChild(fRebootSystemButton); 321 322 message = new BMessage(MSG_REBOOT_SYSTEM); 323 if (!message) 324 return B_NO_MEMORY; 325 fRebootSystemButton->SetMessage(message); 326 fRebootSystemButton->SetTarget(target); 327 328 // aborted OK button 329 fAbortedOKButton = new(nothrow) BButton(BRect(0, 0, 10, 10), 330 "ok", B_TRANSLATE("OK"), NULL, B_FOLLOW_NONE); 331 if (!fAbortedOKButton) 332 return B_NO_MEMORY; 333 fAbortedOKButton->Hide(); 334 fRootView->AddChild(fAbortedOKButton); 335 336 message = new BMessage(MSG_CANCEL_SHUTDOWN); 337 if (!message) 338 return B_NO_MEMORY; 339 fAbortedOKButton->SetMessage(message); 340 fAbortedOKButton->SetTarget(target); 341 342 // compute the sizes 343 static const int kHSpacing = 10; 344 static const int kVSpacing = 10; 345 static const int kInnerHSpacing = 5; 346 static const int kInnerVSpacing = 8; 347 348 // buttons 349 fKillAppButton->ResizeToPreferred(); 350 fCancelShutdownButton->ResizeToPreferred(); 351 fRebootSystemButton->MakeDefault(true); 352 fRebootSystemButton->ResizeToPreferred(); 353 fAbortedOKButton->MakeDefault(true); 354 fAbortedOKButton->ResizeToPreferred(); 355 356 BRect rect(fKillAppButton->Frame()); 357 int buttonWidth = rect.IntegerWidth() + 1; 358 int buttonHeight = rect.IntegerHeight() + 1; 359 360 rect = fCancelShutdownButton->Frame(); 361 if (rect.IntegerWidth() >= buttonWidth) 362 buttonWidth = rect.IntegerWidth() + 1; 363 364 int defaultButtonHeight 365 = fRebootSystemButton->Frame().IntegerHeight() + 1; 366 367 // text view 368 fTextView->SetText("two\nlines"); 369 int textHeight = (int)fTextView->TextHeight(0, 1) + 1; 370 371 int rightPartX = kStripeWidth + kIconSize / 2 + 1; 372 int textX = rightPartX + kInnerHSpacing; 373 int textY = kVSpacing; 374 int buttonsY = textY + textHeight + kInnerVSpacing; 375 int nonDefaultButtonsY = buttonsY 376 + (defaultButtonHeight - buttonHeight) / 2; 377 int rightPartWidth = 2 * buttonWidth + kInnerHSpacing; 378 int width = rightPartX + rightPartWidth + kHSpacing; 379 int height = buttonsY + defaultButtonHeight + kVSpacing; 380 381 // now layout the views 382 383 // text view 384 fTextView->MoveTo(textX, textY); 385 fTextView->ResizeTo(rightPartWidth + rightPartX - textX - 1, 386 textHeight - 1); 387 fTextView->SetTextRect(fTextView->Bounds()); 388 389 fTextView->SetWordWrap(true); 390 391 // buttons 392 fKillAppButton->MoveTo(rightPartX, nonDefaultButtonsY); 393 fKillAppButton->ResizeTo(buttonWidth - 1, buttonHeight - 1); 394 395 fCancelShutdownButton->MoveTo( 396 rightPartX + buttonWidth + kInnerVSpacing - 1, 397 nonDefaultButtonsY); 398 fCancelShutdownButton->ResizeTo(buttonWidth - 1, buttonHeight - 1); 399 400 fRebootSystemButton->MoveTo( 401 (width - fRebootSystemButton->Frame().IntegerWidth()) / 2, 402 buttonsY); 403 404 fAbortedOKButton->MoveTo( 405 (width - fAbortedOKButton->Frame().IntegerWidth()) / 2, 406 buttonsY); 407 408 // set the root view and window size 409 fRootView->ResizeTo(width - 1, height - 1); 410 ResizeTo(width - 1, height - 1); 411 412 // move the window to the same position as BAlerts 413 BScreen screen(this); 414 BRect screenFrame = screen.Frame(); 415 416 MoveTo(screenFrame.left + (screenFrame.Width() - width) / 2.0, 417 screenFrame.top + screenFrame.Height() / 4.0 - ceilf(height / 3.0)); 418 419 return B_OK; 420 } 421 422 status_t AddApp(team_id team, BBitmap* appIcon) 423 { 424 AppInfo* info = new(nothrow) AppInfo; 425 if (!info) { 426 delete appIcon; 427 return B_NO_MEMORY; 428 } 429 430 info->team = team; 431 info->appIcon = appIcon; 432 433 if (!fAppInfos.AddItem(info)) { 434 delete info; 435 return B_NO_MEMORY; 436 } 437 438 return B_OK; 439 } 440 441 void RemoveApp(team_id team) 442 { 443 int32 index = _AppInfoIndexOf(team); 444 if (index < 0) 445 return; 446 447 if (team == fCurrentApp) 448 SetCurrentApp(-1); 449 450 AppInfo* info = (AppInfo*)fAppInfos.RemoveItem(index); 451 delete info; 452 } 453 454 void SetCurrentApp(team_id team) 455 { 456 AppInfo* info = (team >= 0 ? _AppInfoFor(team) : NULL); 457 458 fCurrentApp = team; 459 fRootView->SetAppInfo(info); 460 461 fKillAppMessage->ReplaceInt32("team", team); 462 } 463 464 void SetText(const char* text) 465 { 466 fTextView->SetText(text); 467 } 468 469 void SetCancelShutdownButtonEnabled(bool enable) 470 { 471 fCancelShutdownButton->SetEnabled(enable); 472 } 473 474 void SetKillAppButtonEnabled(bool enable) 475 { 476 if (enable != fKillAppButton->IsEnabled()) { 477 fKillAppButton->SetEnabled(enable); 478 479 if (enable) 480 fKillAppButton->Show(); 481 else 482 fKillAppButton->Hide(); 483 } 484 } 485 486 void SetWaitAnimationEnabled(bool enable) 487 { 488 fRootView->IconWaitAnimationEnabled(enable); 489 } 490 491 void SetWaitForShutdown() 492 { 493 fKillAppButton->Hide(); 494 fCancelShutdownButton->Hide(); 495 fRebootSystemButton->MakeDefault(true); 496 fRebootSystemButton->Show(); 497 498 SetTitle(B_TRANSLATE("System is shut down")); 499 fTextView->SetText( 500 B_TRANSLATE("It's now safe to turn off the computer.")); 501 } 502 503 void SetWaitForAbortedOK() 504 { 505 fKillAppButton->Hide(); 506 fCancelShutdownButton->Hide(); 507 fAbortedOKButton->MakeDefault(true); 508 fAbortedOKButton->Show(); 509 // TODO: Temporary work-around for a Haiku bug. 510 fAbortedOKButton->Invalidate(); 511 512 SetTitle(B_TRANSLATE("Shutdown aborted")); 513 } 514 515 private: 516 struct AppInfo { 517 team_id team; 518 BBitmap* appIcon; 519 520 ~AppInfo() 521 { 522 delete appIcon; 523 } 524 }; 525 526 int32 _AppInfoIndexOf(team_id team) 527 { 528 if (team < 0) 529 return -1; 530 531 for (int32 i = 0; AppInfo* info = (AppInfo*)fAppInfos.ItemAt(i); i++) { 532 if (info->team == team) 533 return i; 534 } 535 536 return -1; 537 } 538 539 AppInfo* _AppInfoFor(team_id team) 540 { 541 int32 index = _AppInfoIndexOf(team); 542 return (index >= 0 ? (AppInfo*)fAppInfos.ItemAt(index) : NULL); 543 } 544 545 class TAlertView : public BView { 546 public: 547 TAlertView(BRect frame, const char* name, uint32 resizeMask, 548 uint32 flags) 549 : 550 BView(frame, name, resizeMask, flags | B_WILL_DRAW), 551 fCurrentIconBitmap(NULL), 552 fNormalIconBitmap(NULL), 553 fTintedIconBitmap(NULL), 554 fAnimationActive(false), 555 fAnimationWorker(-1), 556 fCurrentAnimationRow(-1), 557 fAnimationLightenPhase(true) 558 { 559 } 560 561 ~TAlertView() 562 { 563 atomic_set(&fAnimationActive, false); 564 wait_for_thread(fAnimationWorker, NULL); 565 566 delete fCurrentIconBitmap; 567 delete fNormalIconBitmap; 568 delete fTintedIconBitmap; 569 } 570 571 virtual void Draw(BRect updateRect) 572 { 573 BRect stripeRect = Bounds(); 574 stripeRect.right = kStripeWidth; 575 SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT)); 576 FillRect(stripeRect); 577 578 if (fCurrentIconBitmap != NULL && fCurrentIconBitmap->IsValid()) { 579 if (fCurrentIconBitmap->ColorSpace() == B_RGBA32) { 580 SetDrawingMode(B_OP_ALPHA); 581 SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 582 } else 583 SetDrawingMode(B_OP_OVER); 584 585 DrawBitmap(fCurrentIconBitmap, 586 BPoint(kStripeWidth - kIconSize / 2, kIconVSpacing)); 587 } 588 } 589 590 void SetAppInfo(AppInfo* info) 591 { 592 IconWaitAnimationEnabled(false); 593 594 BAutolock lock(Window()); 595 if (!lock.IsLocked()) 596 return; 597 598 delete fCurrentIconBitmap; 599 fCurrentIconBitmap = NULL; 600 601 delete fNormalIconBitmap; 602 fNormalIconBitmap = NULL; 603 604 delete fTintedIconBitmap; 605 fTintedIconBitmap = NULL; 606 607 if (info != NULL && info->appIcon != NULL 608 && info->appIcon->IsValid()) { 609 fCurrentIconBitmap = new BBitmap(BRect(0, 0, 31, 31), B_RGBA32); 610 611 if (fCurrentIconBitmap == NULL 612 || fCurrentIconBitmap->ImportBits(info->appIcon) != B_OK) { 613 delete fCurrentIconBitmap; 614 fCurrentIconBitmap = NULL; 615 } 616 } 617 618 Invalidate(); 619 } 620 621 void IconWaitAnimationEnabled(bool enable) 622 { 623 if (atomic_get(&fAnimationActive) == enable) 624 return; 625 626 BAutolock lock(Window()); 627 if (!lock.IsLocked()) 628 return; 629 630 if (enable) { 631 if (fCurrentIconBitmap == NULL 632 || !fCurrentIconBitmap->IsValid()) 633 return; 634 635 if (fNormalIconBitmap == NULL 636 || !fNormalIconBitmap->IsValid()) { 637 delete fNormalIconBitmap; 638 fNormalIconBitmap = NULL; 639 640 fNormalIconBitmap = new BBitmap(BRect(0, 0, 31, 31), 641 B_BITMAP_NO_SERVER_LINK, B_RGBA32); 642 643 if (fNormalIconBitmap == NULL 644 || fNormalIconBitmap->ImportBits(fCurrentIconBitmap) 645 != B_OK) { 646 delete fNormalIconBitmap; 647 fNormalIconBitmap = NULL; 648 649 return; 650 } 651 } 652 653 if (fTintedIconBitmap == NULL 654 || !fTintedIconBitmap->IsValid()) { 655 delete fTintedIconBitmap; 656 fTintedIconBitmap = NULL; 657 658 fTintedIconBitmap = new BBitmap(BRect(0, 0, 31, 31), 659 B_BITMAP_NO_SERVER_LINK, B_RGBA32); 660 661 if (fTintedIconBitmap == NULL 662 || fTintedIconBitmap->ImportBits(fNormalIconBitmap) 663 != B_OK) { 664 delete fTintedIconBitmap; 665 fTintedIconBitmap = NULL; 666 667 return; 668 } 669 670 int32 width = 671 fTintedIconBitmap->Bounds().IntegerWidth() + 1; 672 int32 height = 673 fTintedIconBitmap->Bounds().IntegerHeight() + 1; 674 int32 rowLength = fTintedIconBitmap->BytesPerRow(); 675 676 uint8* iconBits = (uint8*)fTintedIconBitmap->Bits(); 677 678 for (int32 y = 0; y < height; y++) { 679 for (int32 x = 0; x < width; x++) { 680 int32 offset = (y * rowLength) + (x * 4); 681 682 rgb_color pixelColor = make_color(iconBits[offset], 683 iconBits[offset + 1], iconBits[offset + 2], 684 iconBits[offset + 3]); 685 686 pixelColor = tint_color(pixelColor, 687 B_DARKEN_2_TINT); 688 689 iconBits[offset] = pixelColor.red; 690 iconBits[offset + 1] = pixelColor.green; 691 iconBits[offset + 2] = pixelColor.blue; 692 iconBits[offset + 3] = pixelColor.alpha; 693 } 694 } 695 } 696 697 fAnimationWorker = spawn_thread(&_AnimateWaitIconWorker, 698 "thumb twiddling", B_DISPLAY_PRIORITY, this); 699 700 if (fAnimationWorker < B_NO_ERROR) 701 return; 702 703 atomic_set(&fAnimationActive, true); 704 if (resume_thread(fAnimationWorker) != B_OK) 705 atomic_set(&fAnimationActive, false); 706 } else { 707 atomic_set(&fAnimationActive, false); 708 wait_for_thread(fAnimationWorker, NULL); 709 710 fCurrentAnimationRow = -1; 711 fAnimationLightenPhase = true; 712 713 if (fCurrentIconBitmap != NULL && fNormalIconBitmap != NULL) 714 fCurrentIconBitmap->ImportBits(fNormalIconBitmap); 715 } 716 } 717 718 private: 719 status_t _AnimateWaitIcon() 720 { 721 while (atomic_get(&fAnimationActive)) { 722 if (LockLooperWithTimeout(kIconAnimateInterval) != B_OK) 723 continue; 724 725 if (fCurrentAnimationRow < 0) { 726 fCurrentAnimationRow = 727 fCurrentIconBitmap->Bounds().IntegerHeight(); 728 fAnimationLightenPhase = !fAnimationLightenPhase; 729 } 730 731 BBitmap* sourceBitmap = fAnimationLightenPhase ? 732 fNormalIconBitmap : fTintedIconBitmap; 733 734 fCurrentIconBitmap->ImportBits(sourceBitmap, 735 BPoint(0, fCurrentAnimationRow), 736 BPoint(0, fCurrentAnimationRow), 737 BSize(sourceBitmap->Bounds().IntegerWidth() - 1, 0)); 738 739 fCurrentAnimationRow--; 740 741 Invalidate(); 742 743 UnlockLooper(); 744 745 snooze(kIconAnimateInterval); 746 } 747 748 return B_OK; 749 } 750 751 static status_t _AnimateWaitIconWorker(void* cookie) 752 { 753 TAlertView* ourView = (TAlertView*)cookie; 754 return ourView->_AnimateWaitIcon(); 755 } 756 757 private: 758 BBitmap* fCurrentIconBitmap; 759 BBitmap* fNormalIconBitmap; 760 BBitmap* fTintedIconBitmap; 761 int32 fAnimationActive; 762 thread_id fAnimationWorker; 763 int32 fCurrentAnimationRow; 764 bool fAnimationLightenPhase; 765 }; 766 767 private: 768 BList fAppInfos; 769 TAlertView* fRootView; 770 BTextView* fTextView; 771 BButton* fKillAppButton; 772 BButton* fCancelShutdownButton; 773 BButton* fRebootSystemButton; 774 BButton* fAbortedOKButton; 775 BMessage* fKillAppMessage; 776 team_id fCurrentApp; 777 }; 778 779 780 // #pragma mark - 781 782 783 ShutdownProcess::ShutdownProcess(TRoster* roster, EventQueue* eventQueue) 784 : 785 BLooper("shutdown process"), 786 EventMaskWatcher(BMessenger(this), B_REQUEST_QUIT | B_REQUEST_LAUNCHED), 787 fWorkerLock("worker lock"), 788 fRequest(NULL), 789 fRoster(roster), 790 fEventQueue(eventQueue), 791 fTimeoutEvent(NULL), 792 fInternalEvents(NULL), 793 fInternalEventSemaphore(-1), 794 fQuitRequestReplyHandler(NULL), 795 fWorker(-1), 796 fCurrentPhase(INVALID_PHASE), 797 fShutdownError(B_ERROR), 798 fHasGUI(false), 799 fReboot(false), 800 fRequestReplySent(false), 801 fWindow(NULL) 802 { 803 } 804 805 806 ShutdownProcess::~ShutdownProcess() 807 { 808 // terminate the GUI 809 if (fHasGUI && fWindow && fWindow->Lock()) 810 fWindow->Quit(); 811 812 // remove and delete the quit request reply handler 813 if (fQuitRequestReplyHandler) { 814 BAutolock _(this); 815 RemoveHandler(fQuitRequestReplyHandler); 816 delete fQuitRequestReplyHandler; 817 } 818 819 // remove and delete the timeout event 820 if (fTimeoutEvent) { 821 fEventQueue->RemoveEvent(fTimeoutEvent); 822 823 delete fTimeoutEvent; 824 } 825 826 // remove the application quit watcher 827 fRoster->RemoveWatcher(this); 828 829 // If an error occurred (e.g. the shutdown process was cancelled), the 830 // roster should accept applications again. 831 if (fShutdownError != B_OK) 832 fRoster->SetShuttingDown(false); 833 834 // delete the internal event semaphore 835 if (fInternalEventSemaphore >= 0) 836 delete_sem(fInternalEventSemaphore); 837 838 // wait for the worker thread to terminate 839 if (fWorker >= 0) { 840 int32 result; 841 wait_for_thread(fWorker, &result); 842 } 843 844 // delete all internal events and the queue 845 if (fInternalEvents) { 846 while (InternalEvent* event = fInternalEvents->First()) { 847 fInternalEvents->Remove(event); 848 delete event; 849 } 850 851 delete fInternalEvents; 852 } 853 854 // send a reply to the request and delete it 855 _SendReply(fShutdownError); 856 delete fRequest; 857 } 858 859 860 status_t 861 ShutdownProcess::Init(BMessage* request) 862 { 863 PRINT("ShutdownProcess::Init()\n"); 864 865 // create and add the quit request reply handler 866 fQuitRequestReplyHandler = new(nothrow) QuitRequestReplyHandler(this); 867 if (!fQuitRequestReplyHandler) 868 RETURN_ERROR(B_NO_MEMORY); 869 AddHandler(fQuitRequestReplyHandler); 870 871 // create the timeout event 872 fTimeoutEvent = new(nothrow) TimeoutEvent(this); 873 if (!fTimeoutEvent) 874 RETURN_ERROR(B_NO_MEMORY); 875 876 // create the event list 877 fInternalEvents = new(nothrow) InternalEventList; 878 if (!fInternalEvents) 879 RETURN_ERROR(B_NO_MEMORY); 880 881 // create the event sempahore 882 fInternalEventSemaphore = create_sem(0, "shutdown events"); 883 if (fInternalEventSemaphore < 0) 884 RETURN_ERROR(fInternalEventSemaphore); 885 886 // init the app server connection 887 fHasGUI = Registrar::App()->InitGUIContext() == B_OK; 888 889 // start watching application quits 890 status_t error = fRoster->AddWatcher(this); 891 if (error != B_OK) { 892 fRoster->SetShuttingDown(false); 893 RETURN_ERROR(error); 894 } 895 896 // start the worker thread 897 fWorker = spawn_thread(_WorkerEntry, "shutdown worker", 898 B_NORMAL_PRIORITY + 1, this); 899 if (fWorker < 0) { 900 fRoster->RemoveWatcher(this); 901 fRoster->SetShuttingDown(false); 902 RETURN_ERROR(fWorker); 903 } 904 905 // everything went fine: now we own the request 906 fRequest = request; 907 908 if (fRequest->FindBool("reboot", &fReboot) != B_OK) 909 fReboot = false; 910 911 resume_thread(fWorker); 912 913 PRINT("ShutdownProcess::Init() done\n"); 914 915 return B_OK; 916 } 917 918 919 void 920 ShutdownProcess::MessageReceived(BMessage* message) 921 { 922 switch (message->what) { 923 case B_SOME_APP_QUIT: 924 { 925 // get the team 926 team_id team; 927 if (message->FindInt32("be:team", &team) != B_OK) { 928 // should not happen 929 return; 930 } 931 932 PRINT("ShutdownProcess::MessageReceived(): B_SOME_APP_QUIT: %" 933 B_PRId32 "\n", team); 934 935 // remove the app info from the respective list 936 int32 phase; 937 RosterAppInfo* info; 938 { 939 BAutolock _(fWorkerLock); 940 941 info = fUserApps.InfoFor(team); 942 if (info) 943 fUserApps.RemoveInfo(info); 944 else if ((info = fSystemApps.InfoFor(team))) 945 fSystemApps.RemoveInfo(info); 946 else if ((info = fBackgroundApps.InfoFor(team))) 947 fBackgroundApps.RemoveInfo(info); 948 else // not found 949 return; 950 951 phase = fCurrentPhase; 952 } 953 954 // post the event 955 _PushEvent(APP_QUIT_EVENT, team, phase); 956 957 delete info; 958 959 break; 960 } 961 962 case B_SOME_APP_LAUNCHED: 963 { 964 // get the team 965 team_id team; 966 if (message->FindInt32("be:team", &team) != B_OK) { 967 // should not happen 968 return; 969 } 970 971 PRINT("ShutdownProcess::MessageReceived(): B_SOME_APP_LAUNCHED: %" 972 B_PRId32 "\n", team); 973 974 // add the user app info to the respective list 975 { 976 BAutolock _(fWorkerLock); 977 fRoster->AddAppInfo(fUserApps, team); 978 } 979 break; 980 } 981 982 case MSG_PHASE_TIMED_OUT: 983 { 984 // get the phase the event is intended for 985 int32 phase = TimeoutEvent::GetMessagePhase(message); 986 team_id team = TimeoutEvent::GetMessageTeam(message);; 987 PRINT("MSG_PHASE_TIMED_OUT: phase: %" B_PRId32 ", team: %" B_PRId32 988 "\n", phase, team); 989 990 BAutolock _(fWorkerLock); 991 992 if (phase == INVALID_PHASE || phase != fCurrentPhase) 993 return; 994 995 // post the event 996 _PushEvent(TIMEOUT_EVENT, team, phase); 997 998 break; 999 } 1000 1001 case MSG_KILL_APPLICATION: 1002 { 1003 team_id team; 1004 if (message->FindInt32("team", &team) != B_OK) 1005 break; 1006 1007 // post the event 1008 _PushEvent(KILL_APP_EVENT, team, fCurrentPhase); 1009 break; 1010 } 1011 1012 case MSG_CANCEL_SHUTDOWN: 1013 { 1014 // post the event 1015 _PushEvent(ABORT_EVENT, -1, fCurrentPhase); 1016 break; 1017 } 1018 1019 case MSG_REBOOT_SYSTEM: 1020 { 1021 // post the event 1022 _PushEvent(REBOOT_SYSTEM_EVENT, -1, INVALID_PHASE); 1023 break; 1024 } 1025 1026 case MSG_DONE: 1027 { 1028 // notify the registrar that we're done 1029 be_app->PostMessage(B_REG_SHUTDOWN_FINISHED, be_app); 1030 break; 1031 } 1032 1033 case B_REG_TEAM_DEBUGGER_ALERT: 1034 { 1035 bool stopShutdown; 1036 if (message->FindBool("stop shutdown", &stopShutdown) == B_OK 1037 && stopShutdown) { 1038 // post abort event to the worker 1039 _PushEvent(ABORT_EVENT, -1, fCurrentPhase); 1040 break; 1041 } 1042 1043 bool open; 1044 team_id team; 1045 if (message->FindInt32("team", &team) != B_OK 1046 || message->FindBool("open", &open) != B_OK) 1047 break; 1048 1049 BAutolock _(fWorkerLock); 1050 if (open) { 1051 PRINT("B_REG_TEAM_DEBUGGER_ALERT: insert %" B_PRId32 "\n", 1052 team); 1053 fDebuggedTeams.Add(team); 1054 } else { 1055 PRINT("B_REG_TEAM_DEBUGGER_ALERT: remove %" B_PRId32 "\n", 1056 team); 1057 fDebuggedTeams.Remove(team); 1058 _PushEvent(DEBUG_EVENT, -1, fCurrentPhase); 1059 } 1060 break; 1061 } 1062 1063 default: 1064 BLooper::MessageReceived(message); 1065 break; 1066 } 1067 } 1068 1069 1070 void 1071 ShutdownProcess::SendReply(BMessage* request, status_t error) 1072 { 1073 if (error == B_OK) { 1074 BMessage reply(B_REG_SUCCESS); 1075 request->SendReply(&reply); 1076 } else { 1077 BMessage reply(B_REG_ERROR); 1078 reply.AddInt32("error", error); 1079 request->SendReply(&reply); 1080 } 1081 } 1082 1083 1084 void 1085 ShutdownProcess::_SendReply(status_t error) 1086 { 1087 if (!fRequestReplySent) { 1088 SendReply(fRequest, error); 1089 fRequestReplySent = true; 1090 } 1091 } 1092 1093 1094 void 1095 ShutdownProcess::_SetPhase(int32 phase) 1096 { 1097 BAutolock _(fWorkerLock); 1098 1099 if (phase == fCurrentPhase) 1100 return; 1101 1102 fCurrentPhase = phase; 1103 1104 // remove the timeout event scheduled for the previous phase 1105 fEventQueue->RemoveEvent(fTimeoutEvent); 1106 } 1107 1108 1109 void 1110 ShutdownProcess::_ScheduleTimeoutEvent(bigtime_t timeout, team_id team) 1111 { 1112 BAutolock _(fWorkerLock); 1113 1114 // remove the timeout event 1115 fEventQueue->RemoveEvent(fTimeoutEvent); 1116 1117 // set the event's phase, team and time 1118 fTimeoutEvent->SetPhase(fCurrentPhase); 1119 fTimeoutEvent->SetTeam(team); 1120 fTimeoutEvent->SetTime(system_time() + timeout); 1121 1122 // add the event 1123 fEventQueue->AddEvent(fTimeoutEvent); 1124 } 1125 1126 1127 void 1128 ShutdownProcess::_SetShowShutdownWindow(bool show) 1129 { 1130 if (fHasGUI) { 1131 BAutolock _(fWindow); 1132 1133 if (show == fWindow->IsHidden()) { 1134 if (show) 1135 fWindow->Show(); 1136 else 1137 fWindow->Hide(); 1138 } 1139 } 1140 } 1141 1142 1143 void 1144 ShutdownProcess::_InitShutdownWindow() 1145 { 1146 // prepare the window 1147 if (fHasGUI) { 1148 fWindow = new(nothrow) ShutdownWindow; 1149 if (fWindow != NULL) { 1150 status_t error = fWindow->Init(BMessenger(this)); 1151 if (error != B_OK) { 1152 delete fWindow; 1153 fWindow = NULL; 1154 } 1155 } 1156 1157 // add the applications 1158 if (fWindow) { 1159 BAutolock _(fWorkerLock); 1160 _AddShutdownWindowApps(fUserApps); 1161 _AddShutdownWindowApps(fSystemApps); 1162 } else { 1163 WARNING("ShutdownProcess::Init(): Failed to create or init " 1164 "shutdown window."); 1165 1166 fHasGUI = false; 1167 } 1168 } 1169 } 1170 1171 1172 void 1173 ShutdownProcess::_AddShutdownWindowApps(AppInfoList& infos) 1174 { 1175 if (!fHasGUI) 1176 return; 1177 1178 for (AppInfoList::Iterator it = infos.It(); it.IsValid(); ++it) { 1179 RosterAppInfo* info = *it; 1180 1181 // init an app file info 1182 BFile file; 1183 status_t error = file.SetTo(&info->ref, B_READ_ONLY); 1184 if (error != B_OK) { 1185 WARNING("ShutdownProcess::_AddShutdownWindowApps(): Failed to " 1186 "open file for app %s: %s\n", info->signature, 1187 strerror(error)); 1188 continue; 1189 } 1190 1191 BAppFileInfo appFileInfo; 1192 error = appFileInfo.SetTo(&file); 1193 if (error != B_OK) { 1194 WARNING("ShutdownProcess::_AddShutdownWindowApps(): Failed to " 1195 "init app file info for app %s: %s\n", info->signature, 1196 strerror(error)); 1197 } 1198 1199 // get the application icon 1200 BBitmap* appIcon = new(nothrow) BBitmap(BRect(0, 0, 31, 31), 1201 B_BITMAP_NO_SERVER_LINK, B_RGBA32); 1202 if (appIcon != NULL) { 1203 error = appIcon->InitCheck(); 1204 if (error == B_OK) 1205 error = appFileInfo.GetTrackerIcon(appIcon, B_LARGE_ICON); 1206 if (error != B_OK) { 1207 delete appIcon; 1208 appIcon = NULL; 1209 } 1210 } 1211 1212 // add the app 1213 error = fWindow->AddApp(info->team, appIcon); 1214 if (error != B_OK) { 1215 WARNING("ShutdownProcess::_AddShutdownWindowApps(): Failed to " 1216 "add app to the shutdown window: %s\n", strerror(error)); 1217 } 1218 } 1219 } 1220 1221 1222 void 1223 ShutdownProcess::_RemoveShutdownWindowApp(team_id team) 1224 { 1225 if (fHasGUI) { 1226 BAutolock _(fWindow); 1227 1228 fWindow->RemoveApp(team); 1229 } 1230 } 1231 1232 1233 void 1234 ShutdownProcess::_SetShutdownWindowCurrentApp(team_id team) 1235 { 1236 if (fHasGUI) { 1237 BAutolock _(fWindow); 1238 1239 fWindow->SetCurrentApp(team); 1240 } 1241 } 1242 1243 1244 void 1245 ShutdownProcess::_SetShutdownWindowText(const char* text) 1246 { 1247 if (fHasGUI) { 1248 BAutolock _(fWindow); 1249 1250 fWindow->SetText(text); 1251 } 1252 } 1253 1254 1255 void 1256 ShutdownProcess::_SetShutdownWindowCancelButtonEnabled(bool enabled) 1257 { 1258 if (fHasGUI) { 1259 BAutolock _(fWindow); 1260 1261 fWindow->SetCancelShutdownButtonEnabled(enabled); 1262 } 1263 } 1264 1265 1266 void 1267 ShutdownProcess::_SetShutdownWindowKillButtonEnabled(bool enabled) 1268 { 1269 if (fHasGUI) { 1270 BAutolock _(fWindow); 1271 1272 fWindow->SetKillAppButtonEnabled(enabled); 1273 } 1274 } 1275 1276 1277 void 1278 ShutdownProcess::_SetShutdownWindowWaitAnimationEnabled(bool enabled) 1279 { 1280 if (fHasGUI) { 1281 BAutolock _(fWindow); 1282 1283 fWindow->SetWaitAnimationEnabled(enabled); 1284 } 1285 } 1286 1287 1288 void 1289 ShutdownProcess::_SetShutdownWindowWaitForShutdown() 1290 { 1291 if (fHasGUI) { 1292 BAutolock _(fWindow); 1293 1294 fWindow->SetWaitForShutdown(); 1295 } 1296 } 1297 1298 1299 void 1300 ShutdownProcess::_SetShutdownWindowWaitForAbortedOK() 1301 { 1302 if (fHasGUI) { 1303 BAutolock _(fWindow); 1304 1305 fWindow->SetWaitForAbortedOK(); 1306 } 1307 } 1308 1309 1310 void 1311 ShutdownProcess::_NegativeQuitRequestReply(thread_id thread) 1312 { 1313 BAutolock _(fWorkerLock); 1314 1315 // Note: team ID == team main thread ID under Haiku. When testing under R5 1316 // using the team ID in case of an ABORT_EVENT won't work correctly. But 1317 // this is done only for system apps. 1318 _PushEvent(ABORT_EVENT, thread, fCurrentPhase); 1319 } 1320 1321 1322 void 1323 ShutdownProcess::_PrepareShutdownMessage(BMessage& message) const 1324 { 1325 message.what = B_QUIT_REQUESTED; 1326 message.AddBool("_shutdown_", true); 1327 1328 BMessage::Private(message).SetReply(BMessenger(fQuitRequestReplyHandler)); 1329 } 1330 1331 1332 status_t 1333 ShutdownProcess::_ShutDown() 1334 { 1335 PRINT("Invoking _kern_shutdown(%d)\n", fReboot); 1336 RETURN_ERROR(_kern_shutdown(fReboot)); 1337 } 1338 1339 1340 status_t 1341 ShutdownProcess::_PushEvent(uint32 eventType, team_id team, int32 phase) 1342 { 1343 InternalEvent* event = new(nothrow) InternalEvent(eventType, team, phase); 1344 if (!event) { 1345 ERROR("ShutdownProcess::_PushEvent(): Failed to create event!\n"); 1346 1347 return B_NO_MEMORY; 1348 } 1349 1350 BAutolock _(fWorkerLock); 1351 1352 fInternalEvents->Add(event); 1353 release_sem(fInternalEventSemaphore); 1354 1355 return B_OK; 1356 } 1357 1358 1359 status_t 1360 ShutdownProcess::_GetNextEvent(uint32& eventType, thread_id& team, int32& phase, 1361 bool block) 1362 { 1363 while (true) { 1364 // acquire the semaphore 1365 if (block) { 1366 status_t error; 1367 do { 1368 error = acquire_sem(fInternalEventSemaphore); 1369 } while (error == B_INTERRUPTED); 1370 1371 if (error != B_OK) 1372 return error; 1373 } else { 1374 status_t error = acquire_sem_etc(fInternalEventSemaphore, 1, 1375 B_RELATIVE_TIMEOUT, 0); 1376 if (error != B_OK) { 1377 eventType = NO_EVENT; 1378 return B_OK; 1379 } 1380 } 1381 1382 // get the event 1383 BAutolock _(fWorkerLock); 1384 1385 InternalEvent* event = fInternalEvents->Head(); 1386 fInternalEvents->Remove(event); 1387 1388 eventType = event->Type(); 1389 team = event->Team(); 1390 phase = event->Phase(); 1391 1392 delete event; 1393 1394 // if the event is an obsolete timeout event, we drop it right here 1395 if (eventType == TIMEOUT_EVENT && phase != fCurrentPhase) 1396 continue; 1397 1398 break; 1399 } 1400 1401 // notify the window, if an app has been removed 1402 if (eventType == APP_QUIT_EVENT) 1403 _RemoveShutdownWindowApp(team); 1404 1405 return B_OK; 1406 } 1407 1408 1409 status_t 1410 ShutdownProcess::_WorkerEntry(void* data) 1411 { 1412 return ((ShutdownProcess*)data)->_Worker(); 1413 } 1414 1415 1416 status_t 1417 ShutdownProcess::_Worker() 1418 { 1419 try { 1420 _WorkerDoShutdown(); 1421 fShutdownError = B_OK; 1422 } catch (status_t error) { 1423 PRINT("ShutdownProcess::_Worker(): error while shutting down: %s\n", 1424 strerror(error)); 1425 1426 fShutdownError = error; 1427 } 1428 1429 // this can happen only, if the shutdown process failed or was aborted: 1430 // notify the looper 1431 _SetPhase(DONE_PHASE); 1432 PostMessage(MSG_DONE); 1433 1434 return B_OK; 1435 } 1436 1437 1438 void 1439 ShutdownProcess::_WorkerDoShutdown() 1440 { 1441 PRINT("ShutdownProcess::_WorkerDoShutdown()\n"); 1442 1443 // If we are here, the shutdown process has been initiated successfully, 1444 // that is, if an asynchronous BRoster::Shutdown() was requested, we 1445 // notify the caller at this point. 1446 bool synchronous; 1447 if (fRequest->FindBool("synchronous", &synchronous) == B_OK && !synchronous) 1448 _SendReply(B_OK); 1449 1450 // ask the user to confirm the shutdown, if desired 1451 bool askUser; 1452 if (fHasGUI && fRequest->FindBool("confirm", &askUser) == B_OK && askUser) { 1453 const char* restart = B_TRANSLATE("Restart"); 1454 const char* shutdown = B_TRANSLATE("Shut down"); 1455 BString title = B_TRANSLATE("%action%?"); 1456 title.ReplaceFirst("%action%", fReboot ? restart : shutdown); 1457 const char* text = fReboot 1458 ? B_TRANSLATE("Do you really want to restart the system?") 1459 : B_TRANSLATE("Do you really want to shut down the system?"); 1460 const char* defaultText = fReboot ? restart : shutdown; 1461 const char* otherText = fReboot ? shutdown : restart; 1462 BAlert* alert = new BAlert(title.String(), text, 1463 B_TRANSLATE("Cancel"), otherText, defaultText, 1464 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1465 // We want the alert to behave more like a regular window... 1466 alert->SetFeel(B_NORMAL_WINDOW_FEEL); 1467 // ...but not quit. Minimizing the alert would prevent the user from 1468 // finding it again, since registrar does not have an entry in the 1469 // Deskbar. 1470 alert->SetFlags(alert->Flags() | B_NOT_MINIMIZABLE | B_CLOSE_ON_ESCAPE); 1471 alert->SetWorkspaces(B_ALL_WORKSPACES); 1472 int32 result = alert->Go(); 1473 1474 if (result == 1) { 1475 // Toggle shutdown method 1476 fReboot = !fReboot; 1477 } else if (result < 1) 1478 throw_error(B_SHUTDOWN_CANCELLED); 1479 } 1480 1481 fWorkerLock.Lock(); 1482 // get a list of all applications to shut down and sort them 1483 status_t status = fRoster->GetShutdownApps(fUserApps, fSystemApps, 1484 fBackgroundApps, fVitalSystemApps); 1485 if (status != B_OK) { 1486 fWorkerLock.Unlock(); 1487 fRoster->RemoveWatcher(this); 1488 return; 1489 } 1490 1491 fUserApps.Sort(&inverse_compare_by_registration_time); 1492 fSystemApps.Sort(&inverse_compare_by_registration_time); 1493 1494 fWorkerLock.Unlock(); 1495 1496 // make the shutdown window ready and show it 1497 _InitShutdownWindow(); 1498 _SetShutdownWindowCurrentApp(-1); 1499 _SetShutdownWindowText(B_TRANSLATE("Tidying things up a bit.")); 1500 _SetShutdownWindowCancelButtonEnabled(true); 1501 _SetShutdownWindowKillButtonEnabled(false); 1502 _SetShowShutdownWindow(true); 1503 1504 // sync 1505 sync(); 1506 1507 // phase 1: terminate the user apps 1508 _SetPhase(USER_APP_TERMINATION_PHASE); 1509 1510 // since, new apps can still be launched, loop until all are gone 1511 if (!fUserApps.IsEmpty()) { 1512 _QuitApps(fUserApps, false); 1513 _WaitForDebuggedTeams(); 1514 } 1515 1516 // tell TRoster not to accept new applications anymore 1517 fRoster->SetShuttingDown(true); 1518 1519 // phase 2: terminate the system apps 1520 _SetPhase(SYSTEM_APP_TERMINATION_PHASE); 1521 _QuitApps(fSystemApps, true); 1522 _WaitForDebuggedTeams(); 1523 1524 // phase 3: terminate the background apps 1525 _SetPhase(BACKGROUND_APP_TERMINATION_PHASE); 1526 _QuitBackgroundApps(); 1527 _WaitForDebuggedTeams(); 1528 1529 // phase 4: terminate the other processes 1530 _SetPhase(OTHER_PROCESSES_TERMINATION_PHASE); 1531 _QuitNonApps(); 1532 _ScheduleTimeoutEvent(kBackgroundAppQuitTimeout, -1); 1533 _WaitForBackgroundApps(); 1534 _KillBackgroundApps(); 1535 _WaitForDebuggedTeams(); 1536 1537 // we're through: do the shutdown 1538 _SetPhase(DONE_PHASE); 1539 if (fReboot) 1540 _SetShutdownWindowText(B_TRANSLATE("Restarting" B_UTF8_ELLIPSIS)); 1541 else 1542 _SetShutdownWindowText(B_TRANSLATE("Shutting down" B_UTF8_ELLIPSIS)); 1543 _ShutDown(); 1544 _SetShutdownWindowWaitForShutdown(); 1545 1546 PRINT(" _kern_shutdown() failed\n"); 1547 1548 // shutdown failed: This can happen for power off mode -- reboot should 1549 // always work. 1550 if (fHasGUI) { 1551 // wait for the reboot event 1552 uint32 event; 1553 do { 1554 team_id team; 1555 int32 phase; 1556 status = _GetNextEvent(event, team, phase, true); 1557 if (status != B_OK) 1558 break; 1559 } while (event != REBOOT_SYSTEM_EVENT); 1560 1561 _kern_shutdown(true); 1562 } 1563 1564 // either there's no GUI or reboot failed: we enter the kernel debugger 1565 // instead 1566 while (true) { 1567 _kern_kernel_debugger("The system is shut down. It's now safe to turn " 1568 "off the computer."); 1569 } 1570 } 1571 1572 1573 bool 1574 ShutdownProcess::_WaitForApp(team_id team, AppInfoList* list, bool systemApps) 1575 { 1576 uint32 event; 1577 do { 1578 team_id eventTeam; 1579 int32 phase; 1580 status_t error = _GetNextEvent(event, eventTeam, phase, true); 1581 if (error != B_OK) 1582 throw_error(error); 1583 1584 if (event == APP_QUIT_EVENT && eventTeam == team) 1585 return true; 1586 1587 if (event == TIMEOUT_EVENT && eventTeam == team) 1588 return false; 1589 1590 if (event == ABORT_EVENT) { 1591 if (eventTeam == -1) { 1592 // The user canceled the shutdown process by pressing the 1593 // Cancel button. 1594 throw_error(B_SHUTDOWN_CANCELLED); 1595 } 1596 if (systemApps) { 1597 // If the app requests aborting the shutdown, we don't need 1598 // to wait any longer. It has processed the request and 1599 // won't quit by itself. We ignore this for system apps. 1600 if (eventTeam == team) 1601 return false; 1602 } else { 1603 // The app returned false in QuitRequested(). 1604 PRINT("ShutdownProcess::_WaitForApp(): shutdown cancelled " 1605 "by team %" B_PRId32 " (-1 => user)\n", eventTeam); 1606 1607 _DisplayAbortingApp(team); 1608 throw_error(B_SHUTDOWN_CANCELLED); 1609 } 1610 } 1611 1612 BAutolock _(fWorkerLock); 1613 if (list != NULL && !list->InfoFor(team)) 1614 return true; 1615 } while (event != NO_EVENT); 1616 1617 return false; 1618 } 1619 1620 1621 void 1622 ShutdownProcess::_QuitApps(AppInfoList& list, bool systemApps) 1623 { 1624 PRINT("ShutdownProcess::_QuitApps(%s)\n", 1625 (systemApps ? "system" : "user")); 1626 1627 if (systemApps) { 1628 _SetShutdownWindowCancelButtonEnabled(false); 1629 1630 // check one last time for abort events 1631 uint32 event; 1632 do { 1633 team_id team; 1634 int32 phase; 1635 status_t error = _GetNextEvent(event, team, phase, false); 1636 if (error != B_OK) 1637 throw_error(error); 1638 1639 if (event == ABORT_EVENT) { 1640 PRINT("ShutdownProcess::_QuitApps(): shutdown cancelled by " 1641 "team %" B_PRId32 " (-1 => user)\n", team); 1642 1643 _DisplayAbortingApp(team); 1644 throw_error(B_SHUTDOWN_CANCELLED); 1645 } 1646 1647 } while (event != NO_EVENT); 1648 } 1649 1650 // prepare the shutdown message 1651 BMessage message; 1652 _PrepareShutdownMessage(message); 1653 1654 // now iterate through the list of apps 1655 while (true) { 1656 // eat events 1657 uint32 event; 1658 do { 1659 team_id team; 1660 int32 phase; 1661 status_t error = _GetNextEvent(event, team, phase, false); 1662 if (error != B_OK) 1663 throw_error(error); 1664 1665 if (!systemApps && event == ABORT_EVENT) { 1666 PRINT("ShutdownProcess::_QuitApps(): shutdown cancelled by " 1667 "team %" B_PRId32 " (-1 => user)\n", team); 1668 1669 _DisplayAbortingApp(team); 1670 throw_error(B_SHUTDOWN_CANCELLED); 1671 } 1672 1673 } while (event != NO_EVENT); 1674 1675 // get the first app to quit 1676 team_id team = -1; 1677 port_id port = -1; 1678 char appName[B_FILE_NAME_LENGTH]; 1679 { 1680 BAutolock _(fWorkerLock); 1681 while (!list.IsEmpty()) { 1682 RosterAppInfo* info = *list.It(); 1683 team = info->team; 1684 port = info->port; 1685 strcpy(appName, info->ref.name); 1686 1687 if (info->IsRunning()) 1688 break; 1689 list.RemoveInfo(info); 1690 delete info; 1691 } 1692 } 1693 1694 if (team < 0) { 1695 PRINT("ShutdownProcess::_QuitApps() done\n"); 1696 return; 1697 } 1698 1699 // set window text 1700 BString buffer = B_TRANSLATE("Asking \"%appName%\" to quit."); 1701 buffer.ReplaceFirst("%appName%", appName); 1702 _SetShutdownWindowText(buffer.String()); 1703 _SetShutdownWindowCurrentApp(team); 1704 1705 // send the shutdown message to the app 1706 PRINT(" sending team %" B_PRId32 " (port: %" B_PRId32 ") a shutdown " 1707 "message\n", team, port); 1708 SingleMessagingTargetSet target(port, B_PREFERRED_TOKEN); 1709 MessageDeliverer::Default()->DeliverMessage(&message, target); 1710 1711 // schedule a timeout event 1712 _ScheduleTimeoutEvent(kAppQuitTimeout, team); 1713 1714 // wait for the app to die or for the timeout to occur 1715 bool appGone = _WaitForApp(team, &list, systemApps); 1716 if (appGone) { 1717 // fine: the app finished in an orderly manner 1718 } else { 1719 // the app is either blocking on a model alert or blocks for another 1720 // reason 1721 if (!systemApps) 1722 _QuitBlockingApp(list, team, appName, true); 1723 else { 1724 // This is a system app: remove it from the list 1725 BAutolock _(fWorkerLock); 1726 1727 if (RosterAppInfo* info = list.InfoFor(team)) { 1728 list.RemoveInfo(info); 1729 delete info; 1730 } 1731 } 1732 } 1733 } 1734 } 1735 1736 1737 void 1738 ShutdownProcess::_QuitBackgroundApps() 1739 { 1740 PRINT("ShutdownProcess::_QuitBackgroundApps()\n"); 1741 1742 _SetShutdownWindowText( 1743 B_TRANSLATE("Asking background applications to quit.")); 1744 1745 // prepare the shutdown message 1746 BMessage message; 1747 _PrepareShutdownMessage(message); 1748 1749 // send shutdown messages to user apps 1750 BAutolock _(fWorkerLock); 1751 1752 AppInfoListMessagingTargetSet targetSet(fBackgroundApps); 1753 1754 if (targetSet.HasNext()) { 1755 PRINT(" sending shutdown message to %" B_PRId32 " apps\n", 1756 fBackgroundApps.CountInfos()); 1757 1758 status_t error = MessageDeliverer::Default()->DeliverMessage( 1759 &message, targetSet); 1760 if (error != B_OK) { 1761 WARNING("_QuitBackgroundApps::_Worker(): Failed to deliver " 1762 "shutdown message to all applications: %s\n", 1763 strerror(error)); 1764 } 1765 } 1766 1767 PRINT("ShutdownProcess::_QuitBackgroundApps() done\n"); 1768 } 1769 1770 1771 void 1772 ShutdownProcess::_WaitForBackgroundApps() 1773 { 1774 PRINT("ShutdownProcess::_WaitForBackgroundApps()\n"); 1775 1776 // wait for user apps 1777 bool moreApps = true; 1778 while (moreApps) { 1779 { 1780 BAutolock _(fWorkerLock); 1781 moreApps = !fBackgroundApps.IsEmpty(); 1782 } 1783 1784 if (moreApps) { 1785 uint32 event; 1786 team_id team; 1787 int32 phase; 1788 status_t error = _GetNextEvent(event, team, phase, true); 1789 if (error != B_OK) 1790 throw_error(error); 1791 1792 if (event == ABORT_EVENT) { 1793 // ignore: it's too late to abort the shutdown 1794 } 1795 1796 if (event == TIMEOUT_EVENT) 1797 return; 1798 } 1799 } 1800 1801 PRINT("ShutdownProcess::_WaitForBackgroundApps() done\n"); 1802 } 1803 1804 1805 void 1806 ShutdownProcess::_KillBackgroundApps() 1807 { 1808 PRINT("ShutdownProcess::_KillBackgroundApps()\n"); 1809 1810 while (true) { 1811 // eat events (we need to be responsive for an abort event) 1812 uint32 event; 1813 do { 1814 team_id team; 1815 int32 phase; 1816 status_t error = _GetNextEvent(event, team, phase, false); 1817 if (error != B_OK) 1818 throw_error(error); 1819 1820 } while (event != NO_EVENT); 1821 1822 // get the first team to kill 1823 team_id team = -1; 1824 char appName[B_FILE_NAME_LENGTH]; 1825 AppInfoList& list = fBackgroundApps; 1826 { 1827 BAutolock _(fWorkerLock); 1828 1829 if (!list.IsEmpty()) { 1830 RosterAppInfo* info = *list.It(); 1831 team = info->team; 1832 strcpy(appName, info->ref.name); 1833 } 1834 } 1835 1836 1837 if (team < 0) { 1838 PRINT("ShutdownProcess::_KillBackgroundApps() done\n"); 1839 return; 1840 } 1841 1842 // the app is either blocking on a model alert or blocks for another 1843 // reason 1844 _QuitBlockingApp(list, team, appName, false); 1845 } 1846 } 1847 1848 1849 void 1850 ShutdownProcess::_QuitNonApps() 1851 { 1852 PRINT("ShutdownProcess::_QuitNonApps()\n"); 1853 1854 _SetShutdownWindowText(B_TRANSLATE("Asking other processes to quit.")); 1855 1856 // iterate through the remaining teams and send them the TERM signal 1857 int32 cookie = 0; 1858 team_info teamInfo; 1859 while (get_next_team_info(&cookie, &teamInfo) == B_OK) { 1860 if (!fVitalSystemApps.Contains(teamInfo.team)) { 1861 PRINT(" sending team %" B_PRId32 " TERM signal\n", teamInfo.team); 1862 1863 // Note: team ID == team main thread ID under Haiku 1864 send_signal(teamInfo.team, SIGTERM); 1865 } 1866 } 1867 1868 // give them a bit of time to terminate 1869 // TODO: Instead of just waiting we could periodically check whether the 1870 // processes are already gone to shorten the process. 1871 snooze(kNonAppQuitTimeout); 1872 1873 // iterate through the remaining teams and kill them 1874 cookie = 0; 1875 while (get_next_team_info(&cookie, &teamInfo) == B_OK) { 1876 if (!fVitalSystemApps.Contains(teamInfo.team)) { 1877 PRINT(" killing team %" B_PRId32 "\n", teamInfo.team); 1878 1879 kill_team(teamInfo.team); 1880 } 1881 } 1882 1883 PRINT("ShutdownProcess::_QuitNonApps() done\n"); 1884 } 1885 1886 1887 void 1888 ShutdownProcess::_QuitBlockingApp(AppInfoList& list, team_id team, 1889 const char* appName, bool cancelAllowed) 1890 { 1891 bool debugged = false; 1892 bool modal = false; 1893 { 1894 BAutolock _(fWorkerLock); 1895 if (fDebuggedTeams.Contains(team)) 1896 debugged = true; 1897 } 1898 if (!debugged) 1899 modal = BPrivate::is_app_showing_modal_window(team); 1900 1901 if (modal) { 1902 // app blocks on a modal window 1903 BString buffer = B_TRANSLATE("The application \"%appName%\" might be " 1904 "blocked on a modal panel."); 1905 buffer.ReplaceFirst("%appName%", appName); 1906 _SetShutdownWindowText(buffer.String()); 1907 _SetShutdownWindowCurrentApp(team); 1908 _SetShutdownWindowKillButtonEnabled(true); 1909 _SetShutdownWindowWaitAnimationEnabled(true); 1910 } 1911 1912 if (modal || debugged) { 1913 // wait for something to happen 1914 bool appGone = false; 1915 while (true) { 1916 uint32 event; 1917 team_id eventTeam; 1918 int32 phase; 1919 status_t error = _GetNextEvent(event, eventTeam, phase, true); 1920 if (error != B_OK) 1921 throw_error(error); 1922 1923 if ((event == APP_QUIT_EVENT) && eventTeam == team) { 1924 appGone = true; 1925 break; 1926 } 1927 1928 if (event == KILL_APP_EVENT && eventTeam == team) 1929 break; 1930 1931 if (event == ABORT_EVENT) { 1932 if (cancelAllowed || debugged) { 1933 PRINT("ShutdownProcess::_QuitBlockingApp(): shutdown " 1934 "cancelled by team %" B_PRId32 " (-1 => user)\n", 1935 eventTeam); 1936 1937 if (!debugged) 1938 _DisplayAbortingApp(eventTeam); 1939 throw_error(B_SHUTDOWN_CANCELLED); 1940 } 1941 1942 // If the app requests aborting the shutdown, we don't need 1943 // to wait any longer. It has processed the request and 1944 // won't quit by itself. We'll have to kill it. 1945 if (eventTeam == team) 1946 break; 1947 } 1948 } 1949 1950 _SetShutdownWindowKillButtonEnabled(false); 1951 _SetShutdownWindowWaitAnimationEnabled(false); 1952 1953 if (appGone) 1954 return; 1955 } 1956 1957 // kill the app 1958 PRINT(" killing team %" B_PRId32 "\n", team); 1959 1960 kill_team(team); 1961 1962 // remove the app (the roster will note eventually and send us 1963 // a notification, but we want to be sure) 1964 { 1965 BAutolock _(fWorkerLock); 1966 1967 if (RosterAppInfo* info = list.InfoFor(team)) { 1968 list.RemoveInfo(info); 1969 delete info; 1970 } 1971 } 1972 } 1973 1974 1975 void 1976 ShutdownProcess::_DisplayAbortingApp(team_id team) 1977 { 1978 // find the app that cancelled the shutdown 1979 char appName[B_FILE_NAME_LENGTH]; 1980 bool foundApp = false; 1981 { 1982 BAutolock _(fWorkerLock); 1983 1984 RosterAppInfo* info = fUserApps.InfoFor(team); 1985 if (!info) 1986 info = fSystemApps.InfoFor(team); 1987 if (!info) 1988 fBackgroundApps.InfoFor(team); 1989 1990 if (info) { 1991 foundApp = true; 1992 strcpy(appName, info->ref.name); 1993 } 1994 } 1995 1996 if (!foundApp) { 1997 PRINT("ShutdownProcess::_DisplayAbortingApp(): Didn't find the app " 1998 "that has cancelled the shutdown.\n"); 1999 return; 2000 } 2001 2002 // compose the text to be displayed 2003 BString buffer = B_TRANSLATE("Application \"%appName%\" has aborted the " 2004 "shutdown process."); 2005 buffer.ReplaceFirst("%appName%", appName); 2006 2007 // set up the window 2008 _SetShutdownWindowWaitAnimationEnabled(false); 2009 _SetShutdownWindowCurrentApp(team); 2010 _SetShutdownWindowText(buffer.String()); 2011 _SetShutdownWindowWaitForAbortedOK(); 2012 2013 // schedule the timeout event 2014 _SetPhase(ABORTED_PHASE); 2015 _ScheduleTimeoutEvent(kDisplayAbortingAppTimeout); 2016 2017 // wait for the timeout or the user to press the cancel button 2018 while (true) { 2019 uint32 event; 2020 team_id eventTeam; 2021 int32 phase; 2022 status_t error = _GetNextEvent(event, eventTeam, phase, true); 2023 if (error != B_OK) 2024 break; 2025 2026 // stop waiting when the timeout occurs 2027 if (event == TIMEOUT_EVENT) 2028 break; 2029 2030 // stop waiting when the user hit the cancel button 2031 if (event == ABORT_EVENT && phase == ABORTED_PHASE && eventTeam < 0) 2032 break; 2033 } 2034 } 2035 2036 2037 /*! Waits until the debugged team list is empty, ie. when there is no one 2038 left to debug. 2039 */ 2040 void 2041 ShutdownProcess::_WaitForDebuggedTeams() 2042 { 2043 PRINT("ShutdownProcess::_WaitForDebuggedTeams()\n"); 2044 { 2045 BAutolock _(fWorkerLock); 2046 if (fDebuggedTeams.Size() == 0) 2047 return; 2048 } 2049 2050 PRINT(" not empty!\n"); 2051 2052 // wait for something to happen 2053 while (true) { 2054 uint32 event; 2055 team_id eventTeam; 2056 int32 phase; 2057 status_t error = _GetNextEvent(event, eventTeam, phase, true); 2058 if (error != B_OK) 2059 throw_error(error); 2060 2061 if (event == ABORT_EVENT) 2062 throw_error(B_SHUTDOWN_CANCELLED); 2063 2064 BAutolock _(fWorkerLock); 2065 if (fDebuggedTeams.Size() == 0) { 2066 PRINT(" out empty"); 2067 return; 2068 } 2069 } 2070 } 2071 2072