/* * Copyright 2005, Ingo Weinhold, bonefish@users.sf.net. * Distributed under the terms of the MIT License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for B_REQUEST_QUIT #include #include #include #include #include #include #ifdef __HAIKU__ #include #endif #include "AppInfoListMessagingTargetSet.h" #include "Debug.h" #include "EventQueue.h" #include "MessageDeliverer.h" #include "MessageEvent.h" #include "Registrar.h" #include "RosterAppInfo.h" #include "ShutdownProcess.h" #include "TRoster.h" using namespace BPrivate; // The time span a non-background application has after the quit message has // been delivered (more precisely: has been handed over to the // MessageDeliverer). static const bigtime_t kAppQuitTimeout = 3000000; // 3 s // The time span a background application has after the quit message has been // delivered (more precisely: has been handed over to the MessageDeliverer). static const bigtime_t kBackgroundAppQuitTimeout = 3000000; // 3 s // The time span non-app processes have after the HUP signal has been send // to them before they get a KILL signal. static const bigtime_t kNonAppQuitTimeout = 500000; // 0.5 s // The time span the app that has aborted the shutdown shall be displayed in // the shutdown window before closing it automatically. static const bigtime_t kDisplayAbortingAppTimeout = 3000000; // 3 s // message what fields enum { MSG_PHASE_TIMED_OUT = 'phto', MSG_DONE = 'done', MSG_KILL_APPLICATION = 'kill', MSG_CANCEL_SHUTDOWN = 'cncl', MSG_REBOOT_SYSTEM = 'rbot', }; // internal events enum { NO_EVENT, ABORT_EVENT, TIMEOUT_EVENT, APP_QUIT_EVENT, KILL_APP_EVENT, REBOOT_SYSTEM_EVENT, }; // phases enum { INVALID_PHASE = -1, USER_APP_TERMINATION_PHASE = 0, SYSTEM_APP_TERMINATION_PHASE = 1, BACKGROUND_APP_TERMINATION_PHASE = 2, OTHER_PROCESSES_TERMINATION_PHASE = 3, ABORTED_PHASE = 4, DONE_PHASE = 5, }; // inverse_compare_by_registration_time static int inverse_compare_by_registration_time(const RosterAppInfo *info1, const RosterAppInfo *info2) { bigtime_t cmp = info1->registration_time - info2->registration_time; if (cmp < 0) return 1; return (cmp > 0 ? -1 : 0); } // throw_error /*! \brief Used to avoid type matching problems when throwing a constant. */ static inline void throw_error(status_t error) { throw error; } // TimeoutEvent class ShutdownProcess::TimeoutEvent : public MessageEvent { public: TimeoutEvent(BHandler *target) : MessageEvent(0, target, MSG_PHASE_TIMED_OUT) { SetAutoDelete(false); fMessage.AddInt32("phase", INVALID_PHASE); fMessage.AddInt32("team", -1); } void SetPhase(int32 phase) { fMessage.ReplaceInt32("phase", phase); } void SetTeam(team_id team) { fMessage.ReplaceInt32("team", team); } static int32 GetMessagePhase(BMessage *message) { int32 phase; if (message->FindInt32("phase", &phase) != B_OK) phase = INVALID_PHASE; return phase; } static int32 GetMessageTeam(BMessage *message) { team_id team; if (message->FindInt32("team", &team) != B_OK) team = -1; return team; } }; // InternalEvent class ShutdownProcess::InternalEvent : public DoublyLinkedListLinkImpl { public: InternalEvent(uint32 type, team_id team, int32 phase) : fType(type), fTeam(team), fPhase(phase) { } uint32 Type() const { return fType; } team_id Team() const { return fTeam; } int32 Phase() const { return fPhase; } private: uint32 fType; int32 fTeam; int32 fPhase; }; // InternalEventList struct ShutdownProcess::InternalEventList : DoublyLinkedList { }; // QuitRequestReplyHandler class ShutdownProcess::QuitRequestReplyHandler : public BHandler { public: QuitRequestReplyHandler(ShutdownProcess *shutdownProcess) : BHandler("shutdown quit reply handler"), fShutdownProcess(shutdownProcess) { } virtual void MessageReceived(BMessage *message) { switch (message->what) { case B_REPLY: { bool result; thread_id thread; if (message->FindBool("result", &result) == B_OK && message->FindInt32("thread", &thread) == B_OK) { if (!result) fShutdownProcess->_NegativeQuitRequestReply(thread); } break; } default: BHandler::MessageReceived(message); break; } } private: ShutdownProcess *fShutdownProcess; }; // ShutdownWindow class ShutdownProcess::ShutdownWindow : public BWindow { public: ShutdownWindow() : BWindow(BRect(0, 0, 200, 100), "Shutdown Status", B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, B_ASYNCHRONOUS_CONTROLS | B_NOT_RESIZABLE | B_NOT_MINIMIZABLE | B_NOT_ZOOMABLE | B_NOT_CLOSABLE, B_ALL_WORKSPACES), fKillAppMessage(NULL), fCurrentApp(-1) { } ~ShutdownWindow() { for (int32 i = 0; AppInfo *info = (AppInfo*)fAppInfos.ItemAt(i); i++) delete info; } status_t Init(BMessenger target) { // create the views // root view BView *rootView = new(nothrow) BView(BRect(0, 0, 100, 15), "app icons", B_FOLLOW_NONE, 0); if (!rootView) return B_NO_MEMORY; rootView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); AddChild(rootView); // current app icon view fCurrentAppIconView = new(nothrow) CurrentAppIconView; if (!fCurrentAppIconView) return B_NO_MEMORY; rootView->AddChild(fCurrentAppIconView); // text view fTextView = new(nothrow) BTextView(BRect(0, 0, 10, 10), "text", BRect(0, 0, 10, 10), B_FOLLOW_NONE); if (!fTextView) return B_NO_MEMORY; fTextView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); fTextView->MakeEditable(false); fTextView->MakeSelectable(false); fTextView->SetWordWrap(true); rootView->AddChild(fTextView); // kill app button fKillAppButton = new(nothrow) BButton(BRect(0, 0, 10, 10), "kill app", "Kill Application", NULL, B_FOLLOW_NONE); if (!fKillAppButton) return B_NO_MEMORY; rootView->AddChild(fKillAppButton); BMessage *message = new BMessage(MSG_KILL_APPLICATION); if (!message) return B_NO_MEMORY; message->AddInt32("team", -1); fKillAppMessage = message; fKillAppButton->SetMessage(message); fKillAppButton->SetTarget(target); // cancel shutdown button fCancelShutdownButton = new(nothrow) BButton(BRect(0, 0, 10, 10), "cancel shutdown", "Cancel Shutdown", NULL, B_FOLLOW_NONE); if (!fCancelShutdownButton) return B_NO_MEMORY; rootView->AddChild(fCancelShutdownButton); message = new BMessage(MSG_CANCEL_SHUTDOWN); if (!message) return B_NO_MEMORY; fCancelShutdownButton->SetMessage(message); fCancelShutdownButton->SetTarget(target); // reboot system button fRebootSystemButton = new(nothrow) BButton(BRect(0, 0, 10, 10), "reboot", "Reboot System", NULL, B_FOLLOW_NONE); if (!fRebootSystemButton) return B_NO_MEMORY; fRebootSystemButton->Hide(); rootView->AddChild(fRebootSystemButton); message = new BMessage(MSG_REBOOT_SYSTEM); if (!message) return B_NO_MEMORY; fRebootSystemButton->SetMessage(message); fRebootSystemButton->SetTarget(target); // aborted OK button fAbortedOKButton = new(nothrow) BButton(BRect(0, 0, 10, 10), "ok", "OK", NULL, B_FOLLOW_NONE); if (!fAbortedOKButton) return B_NO_MEMORY; fAbortedOKButton->Hide(); rootView->AddChild(fAbortedOKButton); message = new BMessage(MSG_CANCEL_SHUTDOWN); if (!message) return B_NO_MEMORY; fAbortedOKButton->SetMessage(message); fAbortedOKButton->SetTarget(target); // compute the sizes static const int kHSpacing = 10; static const int kVSpacing = 10; static const int kInnerHSpacing = 5; static const int kInnerVSpacing = 8; // buttons fKillAppButton->ResizeToPreferred(); fCancelShutdownButton->ResizeToPreferred(); fRebootSystemButton->MakeDefault(true); fRebootSystemButton->ResizeToPreferred(); fAbortedOKButton->MakeDefault(true); fAbortedOKButton->ResizeToPreferred(); BRect rect(fKillAppButton->Frame()); int buttonWidth = rect.IntegerWidth() + 1; int buttonHeight = rect.IntegerHeight() + 1; rect = fCancelShutdownButton->Frame(); if (rect.IntegerWidth() >= buttonWidth) buttonWidth = rect.IntegerWidth() + 1; int defaultButtonHeight = fRebootSystemButton->Frame().IntegerHeight() + 1; // text view fTextView->SetText("two\nlines"); int textHeight = (int)fTextView->TextHeight(0, 1) + 1; // current app icon view int currentAppIconWidth = fCurrentAppIconView->Frame().IntegerWidth() + 1; int currentAppIconHeight = fCurrentAppIconView->Frame().IntegerHeight() + 1; int currentAppIconX = kHSpacing; int rightPartX = currentAppIconX + currentAppIconWidth; int textX = rightPartX + kInnerHSpacing; int textY = kVSpacing; int buttonsY = textY + textHeight + kInnerVSpacing; int nonDefaultButtonsY = buttonsY + (defaultButtonHeight - buttonHeight) / 2; int rightPartWidth = 2 * buttonWidth + kInnerHSpacing; int width = rightPartX + rightPartWidth + kHSpacing; int height = buttonsY + defaultButtonHeight + kVSpacing; // now layout the views // current app icon view fCurrentAppIconView->MoveTo(currentAppIconX, textY + (textHeight - currentAppIconHeight) / 2); // text view fTextView->MoveTo(textX, textY); fTextView->ResizeTo(rightPartWidth + rightPartX - textX - 1, textHeight - 1); fTextView->SetTextRect(fTextView->Bounds()); // buttons fKillAppButton->MoveTo(rightPartX, nonDefaultButtonsY); fKillAppButton->ResizeTo(buttonWidth - 1, buttonHeight - 1); fCancelShutdownButton->MoveTo( rightPartX + buttonWidth + kInnerVSpacing - 1, nonDefaultButtonsY); fCancelShutdownButton->ResizeTo(buttonWidth - 1, buttonHeight - 1); fRebootSystemButton->MoveTo( (width - fRebootSystemButton->Frame().IntegerWidth()) / 2, buttonsY); fAbortedOKButton->MoveTo( (width - fAbortedOKButton->Frame().IntegerWidth()) / 2, buttonsY); // set the root view and window size rootView->ResizeTo(width - 1, height - 1); ResizeTo(width - 1, height - 1); // center the window on screen BScreen screen; if (screen.IsValid()) { BRect screenFrame = screen.Frame(); int screenWidth = screenFrame.IntegerWidth() + 1; int screenHeight = screenFrame.IntegerHeight() + 1; MoveTo((screenWidth - width) / 2, (screenHeight - height) / 2); } else { MoveTo(20, 20); } return B_OK; } status_t AddApp(team_id team, BBitmap *miniIcon, BBitmap *largeIcon) { AppInfo *info = new(nothrow) AppInfo; if (!info) { delete miniIcon; delete largeIcon; return B_NO_MEMORY; } info->team = team; info->miniIcon = miniIcon; info->largeIcon = largeIcon; if (!fAppInfos.AddItem(info)) { delete info; return B_NO_MEMORY; } return B_OK; } void RemoveApp(team_id team) { int32 index = _AppInfoIndexOf(team); if (index < 0) return; if (team == fCurrentApp) SetCurrentApp(-1); AppInfo *info = (AppInfo*)fAppInfos.RemoveItem(index); delete info; } void SetCurrentApp(team_id team) { AppInfo *info = (team >= 0 ? _AppInfoFor(team) : NULL); fCurrentApp = team; fCurrentAppIconView->SetAppInfo(info); fKillAppMessage->ReplaceInt32("team", team); } void SetText(const char *text) { fTextView->SetText(text); } void SetCancelShutdownButtonEnabled(bool enable) { fCancelShutdownButton->SetEnabled(enable); } void SetKillAppButtonEnabled(bool enable) { if (enable != fKillAppButton->IsEnabled()) { fKillAppButton->SetEnabled(enable); if (enable) fKillAppButton->Show(); else fKillAppButton->Hide(); } } void SetWaitForShutdown() { fCurrentAppIconView->Hide(); fKillAppButton->Hide(); fCancelShutdownButton->Hide(); fRebootSystemButton->MakeDefault(true); fRebootSystemButton->Show(); // TODO: Temporary work-around for a Haiku bug. fRebootSystemButton->Invalidate(); SetTitle("System is Shut Down"); fTextView->SetText("It's now safe to turn off the computer."); } void SetWaitForAbortedOK() { fCurrentAppIconView->Hide(); fKillAppButton->Hide(); fCancelShutdownButton->Hide(); fAbortedOKButton->MakeDefault(true); fAbortedOKButton->Show(); // TODO: Temporary work-around for a Haiku bug. fAbortedOKButton->Invalidate(); SetTitle("Shutdown Aborted"); } private: struct AppInfo { team_id team; BBitmap *miniIcon; BBitmap *largeIcon; ~AppInfo() { delete miniIcon; delete largeIcon; } }; int32 _AppInfoIndexOf(team_id team) { if (team < 0) return -1; for (int32 i = 0; AppInfo *info = (AppInfo*)fAppInfos.ItemAt(i); i++) { if (info->team == team) return i; } return -1; } AppInfo *_AppInfoFor(team_id team) { int32 index = _AppInfoIndexOf(team); return (index >= 0 ? (AppInfo*)fAppInfos.ItemAt(index) : NULL); } class CurrentAppIconView : public BView { public: CurrentAppIconView() : BView(BRect(0, 0, 31, 31), "current app icon", B_FOLLOW_NONE, B_WILL_DRAW), fAppInfo(NULL) { SetViewColor(B_TRANSPARENT_32_BIT); fBackground = ui_color(B_PANEL_BACKGROUND_COLOR); } virtual void Draw(BRect updateRect) { SetDrawingMode(B_OP_COPY); SetLowColor(fBackground); FillRect(Bounds(), B_SOLID_LOW); if (fAppInfo && fAppInfo->largeIcon) { SetDrawingMode(B_OP_OVER); DrawBitmap(fAppInfo->largeIcon, BPoint(0, 0)); } } void SetAppInfo(AppInfo *info) { fAppInfo = info; Invalidate(); } private: const AppInfo *fAppInfo; rgb_color fBackground; }; private: BList fAppInfos; CurrentAppIconView *fCurrentAppIconView; BTextView *fTextView; BButton *fKillAppButton; BButton *fCancelShutdownButton; BButton *fRebootSystemButton; BButton *fAbortedOKButton; BMessage *fKillAppMessage; team_id fCurrentApp; }; // #pragma mark - // constructor ShutdownProcess::ShutdownProcess(TRoster *roster, EventQueue *eventQueue) : BLooper("shutdown process"), EventMaskWatcher(BMessenger(this), B_REQUEST_QUIT), fWorkerLock("worker lock"), fRequest(NULL), fRoster(roster), fEventQueue(eventQueue), fTimeoutEvent(NULL), fInternalEvents(NULL), fInternalEventSemaphore(-1), fQuitRequestReplyHandler(NULL), fWorker(-1), fCurrentPhase(INVALID_PHASE), fShutdownError(B_ERROR), fHasGUI(false), fReboot(false), fRequestReplySent(false), fWindow(NULL) { } // destructor ShutdownProcess::~ShutdownProcess() { // terminate the GUI if (fHasGUI && fWindow && fWindow->Lock()) fWindow->Quit(); // remove and delete the quit request reply handler if (fQuitRequestReplyHandler) { BAutolock _(this); RemoveHandler(fQuitRequestReplyHandler); delete fQuitRequestReplyHandler; } // remove and delete the timeout event if (fTimeoutEvent) { fEventQueue->RemoveEvent(fTimeoutEvent); delete fTimeoutEvent; } // remove the application quit watcher fRoster->RemoveWatcher(this); // delete the internal event semaphore if (fInternalEventSemaphore >= 0) delete_sem(fInternalEventSemaphore); // wait for the worker thread to terminate if (fWorker >= 0) { int32 result; wait_for_thread(fWorker, &result); } // delete all internal events and the queue if (fInternalEvents) { while (InternalEvent *event = fInternalEvents->First()) { fInternalEvents->Remove(event); delete event; } delete fInternalEvents; } // send a reply to the request and delete it _SendReply(fShutdownError); delete fRequest; } // Init status_t ShutdownProcess::Init(BMessage *request) { PRINT(("ShutdownProcess::Init()\n")); // create and add the quit request reply handler fQuitRequestReplyHandler = new(nothrow) QuitRequestReplyHandler(this); if (!fQuitRequestReplyHandler) RETURN_ERROR(B_NO_MEMORY); AddHandler(fQuitRequestReplyHandler); // create the timeout event fTimeoutEvent = new(nothrow) TimeoutEvent(this); if (!fTimeoutEvent) RETURN_ERROR(B_NO_MEMORY); // create the event list fInternalEvents = new(nothrow) InternalEventList; if (!fInternalEvents) RETURN_ERROR(B_NO_MEMORY); // create the event sempahore fInternalEventSemaphore = create_sem(0, "shutdown events"); if (fInternalEventSemaphore < 0) RETURN_ERROR(fInternalEventSemaphore); // init the app server connection fHasGUI = (Registrar::App()->InitGUIContext() == B_OK); // tell TRoster not to accept new applications anymore fRoster->SetShuttingDown(true); // start watching application quits status_t error = fRoster->AddWatcher(this); if (error != B_OK) { fRoster->SetShuttingDown(false); RETURN_ERROR(error); } // get a list of all applications to shut down and sort them error = fRoster->GetShutdownApps(fUserApps, fSystemApps, fBackgroundApps, fVitalSystemApps); if (error != B_OK) { fRoster->RemoveWatcher(this); fRoster->SetShuttingDown(false); RETURN_ERROR(error); } fUserApps.Sort(&inverse_compare_by_registration_time); fSystemApps.Sort(&inverse_compare_by_registration_time); // start the worker thread fWorker = spawn_thread(_WorkerEntry, "shutdown worker", B_NORMAL_PRIORITY, this); if (fWorker < 0) { fRoster->RemoveWatcher(this); fRoster->SetShuttingDown(false); RETURN_ERROR(fWorker); } // everything went fine: now we own the request fRequest = request; if (fRequest->FindBool("reboot", &fReboot) != B_OK) fReboot = false; resume_thread(fWorker); PRINT(("ShutdownProcess::Init() done\n")); return B_OK; } // MessageReceived void ShutdownProcess::MessageReceived(BMessage *message) { switch (message->what) { case B_SOME_APP_QUIT: { // get the team team_id team; if (message->FindInt32("be:team", &team) != B_OK) { // should not happen return; } PRINT(("ShutdownProcess::MessageReceived(): B_SOME_APP_QUIT: %ld\n", team)); // remove the app info from the respective list int32 phase; RosterAppInfo *info; { BAutolock _(fWorkerLock); info = fUserApps.InfoFor(team); if (info) fUserApps.RemoveInfo(info); else if ((info = fSystemApps.InfoFor(team))) fSystemApps.RemoveInfo(info); else if ((info = fBackgroundApps.InfoFor(team))) fBackgroundApps.RemoveInfo(info); else // not found return; phase = fCurrentPhase; } // post the event _PushEvent(APP_QUIT_EVENT, team, phase); delete info; break; } case MSG_PHASE_TIMED_OUT: { // get the phase the event is intended for int32 phase = TimeoutEvent::GetMessagePhase(message); team_id team = TimeoutEvent::GetMessageTeam(message);; PRINT(("MSG_PHASE_TIMED_OUT: phase: %ld, team: %ld\n", phase, team)); BAutolock _(fWorkerLock); if (phase == INVALID_PHASE || phase != fCurrentPhase) return; // post the event _PushEvent(TIMEOUT_EVENT, team, phase); break; } case MSG_KILL_APPLICATION: { team_id team; if (message->FindInt32("team", &team) != B_OK) break; BAutolock _(fWorkerLock); // post the event _PushEvent(KILL_APP_EVENT, team, fCurrentPhase); break; } case MSG_CANCEL_SHUTDOWN: { BAutolock _(fWorkerLock); // post the event _PushEvent(ABORT_EVENT, -1, fCurrentPhase); break; } case MSG_REBOOT_SYSTEM: { // post the event _PushEvent(REBOOT_SYSTEM_EVENT, -1, INVALID_PHASE); break; } case MSG_DONE: { // notify the registrar that we're done be_app->PostMessage(B_REG_SHUTDOWN_FINISHED, be_app); break; } default: BLooper::MessageReceived(message); break; } } // SendReply void ShutdownProcess::SendReply(BMessage *request, status_t error) { if (error == B_OK) { BMessage reply(B_REG_SUCCESS); request->SendReply(&reply); } else { BMessage reply(B_REG_ERROR); reply.AddInt32("error", error); request->SendReply(&reply); } } // _SendReply void ShutdownProcess::_SendReply(status_t error) { if (!fRequestReplySent) { SendReply(fRequest, error); fRequestReplySent = true; } } // _SetPhase void ShutdownProcess::_SetPhase(int32 phase) { BAutolock _(fWorkerLock); if (phase == fCurrentPhase) return; fCurrentPhase = phase; // remove the timeout event scheduled for the previous phase fEventQueue->RemoveEvent(fTimeoutEvent); } // _ScheduleTimeoutEvent void ShutdownProcess::_ScheduleTimeoutEvent(bigtime_t timeout, team_id team) { BAutolock _(fWorkerLock); // remove the timeout event fEventQueue->RemoveEvent(fTimeoutEvent); // set the event's phase, team and time fTimeoutEvent->SetPhase(fCurrentPhase); fTimeoutEvent->SetTeam(team); fTimeoutEvent->SetTime(system_time() + timeout); // add the event fEventQueue->AddEvent(fTimeoutEvent); } // _SetShowShutdownWindow void ShutdownProcess::_SetShowShutdownWindow(bool show) { if (fHasGUI) { BAutolock _(fWindow); if (show == fWindow->IsHidden()) { if (show) fWindow->Show(); else fWindow->Hide(); } } } // _InitShutdownWindow void ShutdownProcess::_InitShutdownWindow() { // prepare the window if (fHasGUI) { fWindow = new(nothrow) ShutdownWindow; if (fWindow != NULL) { status_t error = fWindow->Init(BMessenger(this)); if (error != B_OK) { delete fWindow; fWindow = NULL; } } // add the applications if (fWindow) { BAutolock _(fWorkerLock); _AddShutdownWindowApps(fUserApps); _AddShutdownWindowApps(fSystemApps); } else { WARNING(("ShutdownProcess::Init(): Failed to create or init " "shutdown window.")); fHasGUI = false; } } } // _AddShutdownWindowApps void ShutdownProcess::_AddShutdownWindowApps(AppInfoList &infos) { if (!fHasGUI) return; for (AppInfoList::Iterator it = infos.It(); it.IsValid(); ++it) { RosterAppInfo *info = *it; // init an app file info BFile file; status_t error = file.SetTo(&info->ref, B_READ_ONLY); if (error != B_OK) { WARNING(("ShutdownProcess::_AddShutdownWindowApps(): Failed to " "open file for app %s: %s\n", info->signature, strerror(error))); continue; } BAppFileInfo appFileInfo; error = appFileInfo.SetTo(&file); if (error != B_OK) { WARNING(("ShutdownProcess::_AddShutdownWindowApps(): Failed to " "init app file info for app %s: %s\n", info->signature, strerror(error))); } // get the application icons // mini icon BBitmap *miniIcon = new(nothrow) BBitmap(BRect(0, 0, 15, 15), B_CMAP8); if (miniIcon != NULL) { error = miniIcon->InitCheck(); if (error == B_OK) error = appFileInfo.GetTrackerIcon(miniIcon, B_MINI_ICON); if (error != B_OK) { delete miniIcon; miniIcon = NULL; } } // mini icon BBitmap *largeIcon = new(nothrow) BBitmap(BRect(0, 0, 31, 31), B_CMAP8); if (largeIcon != NULL) { error = largeIcon->InitCheck(); if (error == B_OK) error = appFileInfo.GetTrackerIcon(largeIcon, B_LARGE_ICON); if (error != B_OK) { delete largeIcon; largeIcon = NULL; } } // add the app error = fWindow->AddApp(info->team, miniIcon, largeIcon); if (error != B_OK) { WARNING(("ShutdownProcess::_AddShutdownWindowApps(): Failed to " "add app to the shutdown window: %s\n", strerror(error))); } } } // _RemoveShutdownWindowApp void ShutdownProcess::_RemoveShutdownWindowApp(team_id team) { if (fHasGUI) { BAutolock _(fWindow); fWindow->RemoveApp(team); } } // _SetShutdownWindowCurrentApp void ShutdownProcess::_SetShutdownWindowCurrentApp(team_id team) { if (fHasGUI) { BAutolock _(fWindow); fWindow->SetCurrentApp(team); } } // _SetShutdownWindowText void ShutdownProcess::_SetShutdownWindowText(const char *text) { if (fHasGUI) { BAutolock _(fWindow); fWindow->SetText(text); } } // _SetShutdownWindowCancelButtonEnabled void ShutdownProcess::_SetShutdownWindowCancelButtonEnabled(bool enabled) { if (fHasGUI) { BAutolock _(fWindow); fWindow->SetCancelShutdownButtonEnabled(enabled); } } // _SetShutdownWindowKillButtonEnabled void ShutdownProcess::_SetShutdownWindowKillButtonEnabled(bool enabled) { if (fHasGUI) { BAutolock _(fWindow); fWindow->SetKillAppButtonEnabled(enabled); } } // _SetShutdownWindowWaitForShutdown void ShutdownProcess::_SetShutdownWindowWaitForShutdown() { if (fHasGUI) { BAutolock _(fWindow); fWindow->SetWaitForShutdown(); } } // _SetShutdownWindowWaitForAbortedOK void ShutdownProcess::_SetShutdownWindowWaitForAbortedOK() { if (fHasGUI) { BAutolock _(fWindow); fWindow->SetWaitForAbortedOK(); } } // _NegativeQuitRequestReply void ShutdownProcess::_NegativeQuitRequestReply(thread_id thread) { BAutolock _(fWorkerLock); // Note: team ID == team main thread ID under Haiku. When testing under R5 // using the team ID in case of an ABORT_EVENT won't work correctly. But // this is done only for system apps. _PushEvent(ABORT_EVENT, thread, fCurrentPhase); } // _PrepareShutdownMessage void ShutdownProcess::_PrepareShutdownMessage(BMessage &message) const { message.what = B_QUIT_REQUESTED; message.AddBool("_shutdown_", true); BMessage::Private(message).SetReply(BMessenger(fQuitRequestReplyHandler)); } // _ShutDown status_t ShutdownProcess::_ShutDown() { PRINT(("Invoking _kern_shutdown(%d)\n", fReboot)); #ifdef __HAIKU__ RETURN_ERROR(_kern_shutdown(fReboot)); #else // we can't do anything on R5 return B_ERROR; #endif } // _PushEvent status_t ShutdownProcess::_PushEvent(uint32 eventType, team_id team, int32 phase) { InternalEvent *event = new(nothrow) InternalEvent(eventType, team, phase); if (!event) { ERROR(("ShutdownProcess::_PushEvent(): Failed to create event!\n")); return B_NO_MEMORY; } BAutolock _(fWorkerLock); fInternalEvents->Add(event); release_sem(fInternalEventSemaphore); return B_OK; } // _GetNextEvent status_t ShutdownProcess::_GetNextEvent(uint32 &eventType, thread_id &team, int32 &phase, bool block) { while (true) { // acquire the semaphore if (block) { status_t error; do { error = acquire_sem(fInternalEventSemaphore); } while (error == B_INTERRUPTED); if (error != B_OK) return error; } else { status_t error = acquire_sem_etc(fInternalEventSemaphore, 1, B_RELATIVE_TIMEOUT, 0); if (error != B_OK) { eventType = NO_EVENT; return B_OK; } } // get the event BAutolock _(fWorkerLock); InternalEvent *event = fInternalEvents->Head(); fInternalEvents->Remove(event); eventType = event->Type(); team = event->Team(); phase = event->Phase(); delete event; // if the event is an obsolete timeout event, we drop it right here if (eventType == TIMEOUT_EVENT && phase != fCurrentPhase) continue; break; } // notify the window, if an app has been removed if (eventType == APP_QUIT_EVENT) _RemoveShutdownWindowApp(team); return B_OK; } // _WorkerEntry status_t ShutdownProcess::_WorkerEntry(void *data) { return ((ShutdownProcess*)data)->_Worker(); } // _Worker status_t ShutdownProcess::_Worker() { try { _WorkerDoShutdown(); fShutdownError = B_OK; } catch (status_t error) { PRINT(("ShutdownProcess::_Worker(): error while shutting down: %s\n", strerror(error))); fShutdownError = error; } // this can happen only, if the shutdown process failed or was aborted: // notify the looper _SetPhase(DONE_PHASE); PostMessage(MSG_DONE); return B_OK; } // _WorkerDoShutdown void ShutdownProcess::_WorkerDoShutdown() { PRINT(("ShutdownProcess::_WorkerDoShutdown()\n")); // If we are here, the shutdown process has been initiated successfully, // that is, if an asynchronous BRoster::Shutdown() was requested, we // notify the caller at this point. bool synchronously; if (fRequest->FindBool("synchronously", &synchronously) == B_OK && !synchronously) { _SendReply(B_OK); } // ask the user to confirm the shutdown, if desired bool askUser; if (fHasGUI && fRequest->FindBool("confirm", &askUser) == B_OK && askUser) { const char *title = (fReboot ? "Reboot?" : "Shut Down?"); const char *text = (fReboot ? "Do you really want to reboot the system?" : "Do you really want to shut down the system?"); const char *buttonText = (fReboot ? "Reboot" : "Shut Down"); BAlert *alert = new BAlert(title, text, buttonText, "Cancel", NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); int32 result = alert->Go(); if (result != 0) throw_error(B_SHUTDOWN_CANCELLED); } // make the shutdown window ready and show it _InitShutdownWindow(); _SetShutdownWindowCurrentApp(-1); _SetShutdownWindowText("Tidying things up a bit."); _SetShutdownWindowCancelButtonEnabled(true); _SetShutdownWindowKillButtonEnabled(false); _SetShowShutdownWindow(true); // sync sync(); // phase 1: terminate the user apps _SetPhase(USER_APP_TERMINATION_PHASE); _QuitApps(fUserApps, false); // phase 2: terminate the system apps _SetPhase(SYSTEM_APP_TERMINATION_PHASE); _QuitApps(fSystemApps, true); // phase 3: terminate the background apps _SetPhase(BACKGROUND_APP_TERMINATION_PHASE); _QuitNonApps(); // phase 4: terminate the other processes _SetPhase(OTHER_PROCESSES_TERMINATION_PHASE); _QuitBackgroundApps(); _ScheduleTimeoutEvent(kBackgroundAppQuitTimeout, -1); _WaitForBackgroundApps(); _KillBackgroundApps(); // we're through: do the shutdown _SetPhase(DONE_PHASE); _SetShutdownWindowWaitForShutdown(); _ShutDown(); PRINT((" _kern_shutdown() failed\n")); // shutdown failed: This can happen for power off mode -- reboot should // always work. if (fHasGUI) { // wait for the reboot event uint32 event; do { team_id team; int32 phase; status_t error = _GetNextEvent(event, team, phase, true); if (error != B_OK) break; } while (event != REBOOT_SYSTEM_EVENT); #ifdef __HAIKU__ _kern_shutdown(true); #endif } // either there's no GUI or reboot failed: we enter the kernel debugger // instead #ifdef __HAIKU__ // TODO: Introduce the syscall. // while (true) { // _kern_kernel_debugger("The system is shut down. It's now safe to turn " // "off the computer."); // } #endif } // _QuitApps void ShutdownProcess::_QuitApps(AppInfoList &list, bool disableCancel) { PRINT(("ShutdownProcess::_QuitApps(%s)\n", (disableCancel ? "system" : "user"))); if (disableCancel) { _SetShutdownWindowCancelButtonEnabled(false); // check one last time for abort events uint32 event; do { team_id team; int32 phase; status_t error = _GetNextEvent(event, team, phase, false); if (error != B_OK) throw_error(error); if (event == ABORT_EVENT) { PRINT(("ShutdownProcess::_QuitApps(): shutdown cancelled by " "team %ld (-1 => user)\n", team)); _DisplayAbortingApp(team); throw_error(B_SHUTDOWN_CANCELLED); } } while (event != NO_EVENT); } // prepare the shutdown message BMessage message; _PrepareShutdownMessage(message); // now iterate through the list of apps while (true) { // eat events uint32 event; do { team_id team; int32 phase; status_t error = _GetNextEvent(event, team, phase, false); if (error != B_OK) throw_error(error); if (!disableCancel && event == ABORT_EVENT) { PRINT(("ShutdownProcess::_QuitApps(): shutdown cancelled by " "team %ld (-1 => user)\n", team)); _DisplayAbortingApp(team); throw_error(B_SHUTDOWN_CANCELLED); } } while (event != NO_EVENT); // get the first app to quit team_id team = -1; port_id port = -1; char appName[B_FILE_NAME_LENGTH]; { BAutolock _(fWorkerLock); if (!list.IsEmpty()) { RosterAppInfo *info = *list.It(); team = info->team; port = info->port; strcpy(appName, info->ref.name); } } if (team < 0) { PRINT(("ShutdownProcess::_QuitApps() done\n")); return; } // set window text char buffer[1024]; snprintf(buffer, sizeof(buffer), "Asking \"%s\" to quit.", appName); _SetShutdownWindowText(buffer); _SetShutdownWindowCurrentApp(team); // send the shutdown message to the app PRINT((" sending team %ld (port: %ld) a shutdown message\n", team, port)); SingleMessagingTargetSet target(port, B_PREFERRED_TOKEN); MessageDeliverer::Default()->DeliverMessage(&message, target); // schedule a timeout event _ScheduleTimeoutEvent(kAppQuitTimeout, team); // wait for the app to die or for the timeout to occur bool appGone = false; do { team_id eventTeam; int32 phase; status_t error = _GetNextEvent(event, eventTeam, phase, true); if (error != B_OK) throw_error(error); if ((event == APP_QUIT_EVENT) && eventTeam == team) { appGone = true; } if (event == TIMEOUT_EVENT && eventTeam == team) break; if (event == ABORT_EVENT) { if (disableCancel) { // If the app requests aborting the shutdown, we don't need // to wait any longer. It has processed the request and // won't quit by itself. We'll have to kill it. if (eventTeam == team) break; } else { PRINT(("ShutdownProcess::_QuitApps(): shutdown cancelled " "by team %ld (-1 => user)\n", eventTeam)); _DisplayAbortingApp(team); throw_error(B_SHUTDOWN_CANCELLED); } } BAutolock _(fWorkerLock); if (!list.InfoFor(team)) break; } while (event != NO_EVENT); if (appGone) { // fine: the app finished in an orderly manner } else { // the app is either blocking on a model alert or blocks for another // reason _QuitBlockingApp(list, team, appName, !disableCancel); } } } // _QuitBackgroundApps void ShutdownProcess::_QuitBackgroundApps() { PRINT(("ShutdownProcess::_QuitBackgroundApps()\n")); _SetShutdownWindowText("Asking background applications to quit."); // prepare the shutdown message BMessage message; _PrepareShutdownMessage(message); // send shutdown messages to user apps BAutolock _(fWorkerLock); AppInfoListMessagingTargetSet targetSet(fBackgroundApps); if (targetSet.HasNext()) { PRINT((" sending shutdown message to %ld apps\n", fBackgroundApps.CountInfos())); status_t error = MessageDeliverer::Default()->DeliverMessage( &message, targetSet); if (error != B_OK) { WARNING(("_QuitBackgroundApps::_Worker(): Failed to deliver " "shutdown message to all applications: %s\n", strerror(error))); } } PRINT(("ShutdownProcess::_QuitBackgroundApps() done\n")); } // _WaitForBackgroundApps void ShutdownProcess::_WaitForBackgroundApps() { PRINT(("ShutdownProcess::_WaitForBackgroundApps()\n")); // wait for user apps bool moreApps = true; while (moreApps) { { BAutolock _(fWorkerLock); moreApps = !fBackgroundApps.IsEmpty(); } if (moreApps) { uint32 event; team_id team; int32 phase; status_t error = _GetNextEvent(event, team, phase, true); if (error != B_OK) throw_error(error); if (event == ABORT_EVENT) { // ignore: it's too late to abort the shutdown } if (event == TIMEOUT_EVENT) return; } } PRINT(("ShutdownProcess::_WaitForBackgroundApps() done\n")); } // _KillBackgroundApps void ShutdownProcess::_KillBackgroundApps() { PRINT(("ShutdownProcess::_KillBackgroundApps()\n")); while (true) { // eat events (we need to be responsive for an abort event) uint32 event; do { team_id team; int32 phase; status_t error = _GetNextEvent(event, team, phase, false); if (error != B_OK) throw_error(error); } while (event != NO_EVENT); // get the first team to kill team_id team = -1; char appName[B_FILE_NAME_LENGTH]; AppInfoList &list = fBackgroundApps; { BAutolock _(fWorkerLock); if (!list.IsEmpty()) { RosterAppInfo *info = *list.It(); team = info->team; strcpy(appName, info->ref.name); } } if (team < 0) { PRINT(("ShutdownProcess::_KillBackgroundApps() done\n")); return; } // the app is either blocking on a model alert or blocks for another // reason _QuitBlockingApp(list, team, appName, false); } } // _QuitNonApps void ShutdownProcess::_QuitNonApps() { PRINT(("ShutdownProcess::_QuitNonApps()\n")); _SetShutdownWindowText("Asking other processes to quit."); // iterate through the remaining teams and send them the HUP signal int32 cookie = 0; team_info teamInfo; while (get_next_team_info(&cookie, &teamInfo) == B_OK) { if (fVitalSystemApps.find(teamInfo.team) == fVitalSystemApps.end()) { PRINT((" sending team %ld HUP signal\n", teamInfo.team)); #ifdef __HAIKU__ // Note: team ID == team main thread ID under Haiku send_signal(teamInfo.team, SIGHUP); #else // We don't want to do this when testing under R5, since it // would kill all teams besides our app server and registrar. #endif } } // give them a bit of time to terminate // TODO: Instead of just waiting we could periodically check whether the // processes are already gone to shorten the process. snooze(kNonAppQuitTimeout); // iterate through the remaining teams and kill them cookie = 0; while (get_next_team_info(&cookie, &teamInfo) == B_OK) { if (fVitalSystemApps.find(teamInfo.team) == fVitalSystemApps.end()) { PRINT((" killing team %ld\n", teamInfo.team)); #ifdef __HAIKU__ kill_team(teamInfo.team); #else // We don't want to do this when testing under R5, since it // would kill all teams besides our app server and registrar. #endif } } PRINT(("ShutdownProcess::_QuitNonApps() done\n")); } // _QuitBlockingApp void ShutdownProcess::_QuitBlockingApp(AppInfoList &list, team_id team, const char *appName, bool cancelAllowed) { if (BPrivate::is_app_showing_modal_window(team)) { // app blocks on a modal window char buffer[1024]; snprintf(buffer, sizeof(buffer), "The application \"%s\" might be " "blocked on a modal panel.", appName); _SetShutdownWindowText(buffer); _SetShutdownWindowCurrentApp(team); _SetShutdownWindowKillButtonEnabled(true); // wait for something to happen bool appGone = false; while (true) { uint32 event; team_id eventTeam; int32 phase; status_t error = _GetNextEvent(event, eventTeam, phase, true); if (error != B_OK) throw_error(error); if ((event == APP_QUIT_EVENT) && eventTeam == team) { appGone = true; break; } if (event == KILL_APP_EVENT && eventTeam == team) break; if (event == ABORT_EVENT) { if (cancelAllowed) { PRINT(("ShutdownProcess::_QuitBlockingApp(): shutdown " "cancelled by team %ld (-1 => user)\n", eventTeam)); throw_error(B_SHUTDOWN_CANCELLED); } // If the app requests aborting the shutdown, we don't need // to wait any longer. It has processed the request and // won't quit by itself. We'll have to kill it. if (eventTeam == team) break; } } _SetShutdownWindowKillButtonEnabled(false); if (appGone) return; } // kill the app PRINT((" killing team %ld\n", team)); kill_team(team); // remove the app (the roster will note eventually and send us // a notification, but we want to be sure) { BAutolock _(fWorkerLock); if (RosterAppInfo *info = list.InfoFor(team)) { list.RemoveInfo(info); delete info; } } } // _DisplayAbortingApp void ShutdownProcess::_DisplayAbortingApp(team_id team) { // find the app that cancelled the shutdown char appName[B_FILE_NAME_LENGTH]; bool foundApp = false; { BAutolock _(fWorkerLock); RosterAppInfo *info = fUserApps.InfoFor(team); if (!info) info = fSystemApps.InfoFor(team); if (!info) fBackgroundApps.InfoFor(team); if (info) { foundApp = true; strcpy(appName, info->ref.name); } } if (!foundApp) { PRINT(("ShutdownProcess::_DisplayAbortingApp(): Didn't find the app " "that has cancelled the shutdown.\n")); return; } // compose the text to be displayed char buffer[1024]; snprintf(buffer, sizeof(buffer), "Application \"%s\" has aborted the shutdown " "process.", appName); // set up the window _SetShutdownWindowCurrentApp(team); _SetShutdownWindowText(buffer); _SetShutdownWindowWaitForAbortedOK(); // schedule the timeout event _SetPhase(ABORTED_PHASE); _ScheduleTimeoutEvent(kDisplayAbortingAppTimeout); // wait for the timeout or the user to press the cancel button while (true) { uint32 event; team_id eventTeam; int32 phase; status_t error = _GetNextEvent(event, eventTeam, phase, true); if (error != B_OK) break; // stop waiting when the timeout occurs if (event == TIMEOUT_EVENT) break; // stop waiting when the user hit the cancel button if (event == ABORT_EVENT && phase == ABORTED_PHASE && eventTeam < 0) break; // also stop when the responsible app quit if ((event == APP_QUIT_EVENT) && eventTeam == team) break; } }