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.
7 * Distributed under the terms of the MIT License.
10 #include "ShutdownProcess.h"
19 #include <AppFileInfo.h>
27 #include <MessagePrivate.h>
28 #include <RegistrarDefs.h>
29 #include <Roster.h> // for B_REQUEST_QUIT
36 #include <TokenSpace.h>
37 #include <util/DoublyLinkedList.h>
41 #include "AppInfoListMessagingTargetSet.h"
43 #include "EventQueue.h"
44 #include "MessageDeliverer.h"
45 #include "MessageEvent.h"
46 #include "Registrar.h"
47 #include "RosterAppInfo.h"
51 #undef B_TRANSLATION_CONTEXT
52 #define B_TRANSLATION_CONTEXT "ShutdownProcess"
56 using namespace BPrivate
;
58 // The time span a non-background application has after the quit message has
59 // been delivered (more precisely: has been handed over to the
61 static const bigtime_t kAppQuitTimeout
= 3000000; // 3 s
63 // The time span a background application has after the quit message has been
64 // delivered (more precisely: has been handed over to the MessageDeliverer).
65 static const bigtime_t kBackgroundAppQuitTimeout
= 3000000; // 3 s
67 // The time span non-app processes have after the TERM signal has been send
68 // to them before they get a KILL signal.
69 static const bigtime_t kNonAppQuitTimeout
= 500000; // 0.5 s
71 // The time span the app that has aborted the shutdown shall be displayed in
72 // the shutdown window before closing it automatically.
73 static const bigtime_t kDisplayAbortingAppTimeout
= 3000000; // 3 s
75 static const int kStripeWidth
= 30;
76 static const int kIconVSpacing
= 6;
77 static const int kIconSize
= 32;
79 // message what fields (must not clobber the registrar's message namespace)
81 MSG_PHASE_TIMED_OUT
= 'phto',
83 MSG_KILL_APPLICATION
= 'kill',
84 MSG_CANCEL_SHUTDOWN
= 'cncl',
85 MSG_REBOOT_SYSTEM
= 'lbot',
102 USER_APP_TERMINATION_PHASE
= 0,
103 SYSTEM_APP_TERMINATION_PHASE
= 1,
104 BACKGROUND_APP_TERMINATION_PHASE
= 2,
105 OTHER_PROCESSES_TERMINATION_PHASE
= 3,
112 inverse_compare_by_registration_time(const RosterAppInfo
* info1
,
113 const RosterAppInfo
* info2
)
115 return (info2
->registration_time
< info1
->registration_time
);
119 /*! \brief Used to avoid type matching problems when throwing a constant.
123 throw_error(status_t error
)
129 class ShutdownProcess::TimeoutEvent
: public MessageEvent
{
131 TimeoutEvent(BHandler
* target
)
132 : MessageEvent(0, target
, MSG_PHASE_TIMED_OUT
)
134 SetAutoDelete(false);
136 fMessage
.AddInt32("phase", INVALID_PHASE
);
137 fMessage
.AddInt32("team", -1);
140 void SetPhase(int32 phase
)
142 fMessage
.ReplaceInt32("phase", phase
);
145 void SetTeam(team_id team
)
147 fMessage
.ReplaceInt32("team", team
);
150 static int32
GetMessagePhase(BMessage
* message
)
153 if (message
->FindInt32("phase", &phase
) != B_OK
)
154 phase
= INVALID_PHASE
;
159 static int32
GetMessageTeam(BMessage
* message
)
162 if (message
->FindInt32("team", &team
) != B_OK
)
170 class ShutdownProcess::InternalEvent
171 : public DoublyLinkedListLinkImpl
<InternalEvent
> {
173 InternalEvent(uint32 type
, team_id team
, int32 phase
)
181 uint32
Type() const { return fType
; }
182 team_id
Team() const { return fTeam
; }
183 int32
Phase() const { return fPhase
; }
192 struct ShutdownProcess::InternalEventList
: DoublyLinkedList
<InternalEvent
> {
196 class ShutdownProcess::QuitRequestReplyHandler
: public BHandler
{
198 QuitRequestReplyHandler(ShutdownProcess
* shutdownProcess
)
199 : BHandler("shutdown quit reply handler"),
200 fShutdownProcess(shutdownProcess
)
204 virtual void MessageReceived(BMessage
* message
)
206 switch (message
->what
) {
211 if (message
->FindBool("result", &result
) == B_OK
212 && message
->FindInt32("thread", &thread
) == B_OK
) {
214 fShutdownProcess
->_NegativeQuitRequestReply(thread
);
221 BHandler::MessageReceived(message
);
227 ShutdownProcess
*fShutdownProcess
;
231 class ShutdownProcess::ShutdownWindow
: public BWindow
{
234 : BWindow(BRect(0, 0, 200, 100), B_TRANSLATE("Shutdown status"),
235 B_TITLED_WINDOW_LOOK
, B_NORMAL_WINDOW_FEEL
,
236 B_ASYNCHRONOUS_CONTROLS
| B_NOT_RESIZABLE
| B_NOT_MINIMIZABLE
237 | B_NOT_ZOOMABLE
| B_NOT_CLOSABLE
, B_ALL_WORKSPACES
),
238 fKillAppMessage(NULL
),
245 for (int32 i
= 0; AppInfo
* info
= (AppInfo
*)fAppInfos
.ItemAt(i
); i
++) {
250 virtual bool QuitRequested()
255 status_t
Init(BMessenger target
)
260 fRootView
= new(nothrow
) TAlertView(BRect(0, 0, 10, 10), "app icons",
264 fRootView
->SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
268 fTextView
= new(nothrow
) BTextView(BRect(0, 0, 10, 10), "text",
269 BRect(0, 0, 10, 10), B_FOLLOW_NONE
);
272 fTextView
->SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
273 rgb_color textColor
= ui_color(B_PANEL_TEXT_COLOR
);
274 fTextView
->SetFontAndColor(be_plain_font
, B_FONT_ALL
, &textColor
);
275 fTextView
->MakeEditable(false);
276 fTextView
->MakeSelectable(false);
277 fTextView
->SetWordWrap(false);
278 fRootView
->AddChild(fTextView
);
281 fKillAppButton
= new(nothrow
) BButton(BRect(0, 0, 10, 10), "kill app",
282 B_TRANSLATE("Kill application"), NULL
, B_FOLLOW_NONE
);
285 fRootView
->AddChild(fKillAppButton
);
287 BMessage
* message
= new BMessage(MSG_KILL_APPLICATION
);
290 message
->AddInt32("team", -1);
291 fKillAppMessage
= message
;
292 fKillAppButton
->SetMessage(message
);
293 fKillAppButton
->SetTarget(target
);
295 // cancel shutdown button
296 fCancelShutdownButton
= new(nothrow
) BButton(BRect(0, 0, 10, 10),
297 "cancel shutdown", B_TRANSLATE("Cancel shutdown"), NULL
,
299 if (!fCancelShutdownButton
)
301 fRootView
->AddChild(fCancelShutdownButton
);
303 message
= new BMessage(MSG_CANCEL_SHUTDOWN
);
306 fCancelShutdownButton
->SetMessage(message
);
307 fCancelShutdownButton
->SetTarget(target
);
309 // reboot system button
310 fRebootSystemButton
= new(nothrow
) BButton(BRect(0, 0, 10, 10),
311 "reboot", B_TRANSLATE("Restart system"), NULL
, B_FOLLOW_NONE
);
312 if (!fRebootSystemButton
)
314 fRebootSystemButton
->Hide();
315 fRootView
->AddChild(fRebootSystemButton
);
317 message
= new BMessage(MSG_REBOOT_SYSTEM
);
320 fRebootSystemButton
->SetMessage(message
);
321 fRebootSystemButton
->SetTarget(target
);
324 fAbortedOKButton
= new(nothrow
) BButton(BRect(0, 0, 10, 10),
325 "ok", B_TRANSLATE("OK"), NULL
, B_FOLLOW_NONE
);
326 if (!fAbortedOKButton
)
328 fAbortedOKButton
->Hide();
329 fRootView
->AddChild(fAbortedOKButton
);
331 message
= new BMessage(MSG_CANCEL_SHUTDOWN
);
334 fAbortedOKButton
->SetMessage(message
);
335 fAbortedOKButton
->SetTarget(target
);
338 static const int kHSpacing
= 10;
339 static const int kVSpacing
= 10;
340 static const int kInnerHSpacing
= 5;
341 static const int kInnerVSpacing
= 8;
344 fKillAppButton
->ResizeToPreferred();
345 fCancelShutdownButton
->ResizeToPreferred();
346 fRebootSystemButton
->MakeDefault(true);
347 fRebootSystemButton
->ResizeToPreferred();
348 fAbortedOKButton
->MakeDefault(true);
349 fAbortedOKButton
->ResizeToPreferred();
351 BRect
rect(fKillAppButton
->Frame());
352 int buttonWidth
= rect
.IntegerWidth() + 1;
353 int buttonHeight
= rect
.IntegerHeight() + 1;
355 rect
= fCancelShutdownButton
->Frame();
356 if (rect
.IntegerWidth() >= buttonWidth
)
357 buttonWidth
= rect
.IntegerWidth() + 1;
359 int defaultButtonHeight
360 = fRebootSystemButton
->Frame().IntegerHeight() + 1;
363 fTextView
->SetText("two\nlines");
364 int textHeight
= (int)fTextView
->TextHeight(0, 1) + 1;
366 int rightPartX
= kStripeWidth
+ kIconSize
/ 2 + 1;
367 int textX
= rightPartX
+ kInnerHSpacing
;
368 int textY
= kVSpacing
;
369 int buttonsY
= textY
+ textHeight
+ kInnerVSpacing
;
370 int nonDefaultButtonsY
= buttonsY
371 + (defaultButtonHeight
- buttonHeight
) / 2;
372 int rightPartWidth
= 2 * buttonWidth
+ kInnerHSpacing
;
373 int width
= rightPartX
+ rightPartWidth
+ kHSpacing
;
374 int height
= buttonsY
+ defaultButtonHeight
+ kVSpacing
;
376 // now layout the views
379 fTextView
->MoveTo(textX
, textY
);
380 fTextView
->ResizeTo(rightPartWidth
+ rightPartX
- textX
- 1,
382 fTextView
->SetTextRect(fTextView
->Bounds());
384 fTextView
->SetWordWrap(true);
387 fKillAppButton
->MoveTo(rightPartX
, nonDefaultButtonsY
);
388 fKillAppButton
->ResizeTo(buttonWidth
- 1, buttonHeight
- 1);
390 fCancelShutdownButton
->MoveTo(
391 rightPartX
+ buttonWidth
+ kInnerVSpacing
- 1,
393 fCancelShutdownButton
->ResizeTo(buttonWidth
- 1, buttonHeight
- 1);
395 fRebootSystemButton
->MoveTo(
396 (width
- fRebootSystemButton
->Frame().IntegerWidth()) / 2,
399 fAbortedOKButton
->MoveTo(
400 (width
- fAbortedOKButton
->Frame().IntegerWidth()) / 2,
403 // set the root view and window size
404 fRootView
->ResizeTo(width
- 1, height
- 1);
405 ResizeTo(width
- 1, height
- 1);
407 // move the window to the same position as BAlerts
408 BScreen
screen(this);
409 BRect screenFrame
= screen
.Frame();
411 MoveTo(screenFrame
.left
+ (screenFrame
.Width() - width
) / 2.0,
412 screenFrame
.top
+ screenFrame
.Height() / 4.0 - ceilf(height
/ 3.0));
417 status_t
AddApp(team_id team
, BBitmap
* miniIcon
, BBitmap
* largeIcon
)
419 AppInfo
* info
= new(nothrow
) AppInfo
;
427 info
->miniIcon
= miniIcon
;
428 info
->largeIcon
= largeIcon
;
430 if (!fAppInfos
.AddItem(info
)) {
438 void RemoveApp(team_id team
)
440 int32 index
= _AppInfoIndexOf(team
);
444 if (team
== fCurrentApp
)
447 AppInfo
* info
= (AppInfo
*)fAppInfos
.RemoveItem(index
);
451 void SetCurrentApp(team_id team
)
453 AppInfo
* info
= (team
>= 0 ? _AppInfoFor(team
) : NULL
);
456 fRootView
->SetAppInfo(info
);
458 fKillAppMessage
->ReplaceInt32("team", team
);
461 void SetText(const char* text
)
463 fTextView
->SetText(text
);
466 void SetCancelShutdownButtonEnabled(bool enable
)
468 fCancelShutdownButton
->SetEnabled(enable
);
471 void SetKillAppButtonEnabled(bool enable
)
473 if (enable
!= fKillAppButton
->IsEnabled()) {
474 fKillAppButton
->SetEnabled(enable
);
477 fKillAppButton
->Show();
479 fKillAppButton
->Hide();
483 void SetWaitForShutdown()
485 fKillAppButton
->Hide();
486 fCancelShutdownButton
->Hide();
487 fRebootSystemButton
->MakeDefault(true);
488 fRebootSystemButton
->Show();
490 SetTitle(B_TRANSLATE("System is shut down"));
492 B_TRANSLATE("It's now safe to turn off the computer."));
495 void SetWaitForAbortedOK()
497 fKillAppButton
->Hide();
498 fCancelShutdownButton
->Hide();
499 fAbortedOKButton
->MakeDefault(true);
500 fAbortedOKButton
->Show();
501 // TODO: Temporary work-around for a Haiku bug.
502 fAbortedOKButton
->Invalidate();
504 SetTitle(B_TRANSLATE("Shutdown aborted"));
520 int32
_AppInfoIndexOf(team_id team
)
525 for (int32 i
= 0; AppInfo
* info
= (AppInfo
*)fAppInfos
.ItemAt(i
); i
++) {
526 if (info
->team
== team
)
533 AppInfo
* _AppInfoFor(team_id team
)
535 int32 index
= _AppInfoIndexOf(team
);
536 return (index
>= 0 ? (AppInfo
*)fAppInfos
.ItemAt(index
) : NULL
);
539 class TAlertView
: public BView
{
541 TAlertView(BRect frame
, const char* name
, uint32 resizeMask
,
543 : BView(frame
, name
, resizeMask
, flags
| B_WILL_DRAW
),
548 virtual void Draw(BRect updateRect
)
550 BRect stripeRect
= Bounds();
551 stripeRect
.right
= kStripeWidth
;
552 SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT
));
553 FillRect(stripeRect
);
555 if (fAppInfo
&& fAppInfo
->largeIcon
) {
556 if (fAppInfo
->largeIcon
->ColorSpace() == B_RGBA32
) {
557 SetDrawingMode(B_OP_ALPHA
);
558 SetBlendingMode(B_PIXEL_ALPHA
, B_ALPHA_OVERLAY
);
560 SetDrawingMode(B_OP_OVER
);
562 DrawBitmapAsync(fAppInfo
->largeIcon
,
563 BPoint(kStripeWidth
- kIconSize
/ 2, kIconVSpacing
));
567 void SetAppInfo(AppInfo
* info
)
574 const AppInfo
*fAppInfo
;
579 TAlertView
* fRootView
;
580 BTextView
* fTextView
;
581 BButton
* fKillAppButton
;
582 BButton
* fCancelShutdownButton
;
583 BButton
* fRebootSystemButton
;
584 BButton
* fAbortedOKButton
;
585 BMessage
* fKillAppMessage
;
593 ShutdownProcess::ShutdownProcess(TRoster
* roster
, EventQueue
* eventQueue
)
595 BLooper("shutdown process"),
596 EventMaskWatcher(BMessenger(this), B_REQUEST_QUIT
),
597 fWorkerLock("worker lock"),
600 fEventQueue(eventQueue
),
602 fInternalEvents(NULL
),
603 fInternalEventSemaphore(-1),
604 fQuitRequestReplyHandler(NULL
),
606 fCurrentPhase(INVALID_PHASE
),
607 fShutdownError(B_ERROR
),
610 fRequestReplySent(false),
616 ShutdownProcess::~ShutdownProcess()
619 if (fHasGUI
&& fWindow
&& fWindow
->Lock())
622 // remove and delete the quit request reply handler
623 if (fQuitRequestReplyHandler
) {
625 RemoveHandler(fQuitRequestReplyHandler
);
626 delete fQuitRequestReplyHandler
;
629 // remove and delete the timeout event
631 fEventQueue
->RemoveEvent(fTimeoutEvent
);
633 delete fTimeoutEvent
;
636 // remove the application quit watcher
637 fRoster
->RemoveWatcher(this);
639 // If an error occurred (e.g. the shutdown process was cancelled), the
640 // roster should accept applications again.
641 if (fShutdownError
!= B_OK
)
642 fRoster
->SetShuttingDown(false);
644 // delete the internal event semaphore
645 if (fInternalEventSemaphore
>= 0)
646 delete_sem(fInternalEventSemaphore
);
648 // wait for the worker thread to terminate
651 wait_for_thread(fWorker
, &result
);
654 // delete all internal events and the queue
655 if (fInternalEvents
) {
656 while (InternalEvent
* event
= fInternalEvents
->First()) {
657 fInternalEvents
->Remove(event
);
661 delete fInternalEvents
;
664 // send a reply to the request and delete it
665 _SendReply(fShutdownError
);
671 ShutdownProcess::Init(BMessage
* request
)
673 PRINT("ShutdownProcess::Init()\n");
675 // create and add the quit request reply handler
676 fQuitRequestReplyHandler
= new(nothrow
) QuitRequestReplyHandler(this);
677 if (!fQuitRequestReplyHandler
)
678 RETURN_ERROR(B_NO_MEMORY
);
679 AddHandler(fQuitRequestReplyHandler
);
681 // create the timeout event
682 fTimeoutEvent
= new(nothrow
) TimeoutEvent(this);
684 RETURN_ERROR(B_NO_MEMORY
);
686 // create the event list
687 fInternalEvents
= new(nothrow
) InternalEventList
;
688 if (!fInternalEvents
)
689 RETURN_ERROR(B_NO_MEMORY
);
691 // create the event sempahore
692 fInternalEventSemaphore
= create_sem(0, "shutdown events");
693 if (fInternalEventSemaphore
< 0)
694 RETURN_ERROR(fInternalEventSemaphore
);
696 // init the app server connection
697 fHasGUI
= Registrar::App()->InitGUIContext() == B_OK
;
699 // start watching application quits
700 status_t error
= fRoster
->AddWatcher(this);
702 fRoster
->SetShuttingDown(false);
706 // start the worker thread
707 fWorker
= spawn_thread(_WorkerEntry
, "shutdown worker",
708 B_NORMAL_PRIORITY
+ 1, this);
710 fRoster
->RemoveWatcher(this);
711 fRoster
->SetShuttingDown(false);
712 RETURN_ERROR(fWorker
);
715 // everything went fine: now we own the request
718 if (fRequest
->FindBool("reboot", &fReboot
) != B_OK
)
721 resume_thread(fWorker
);
723 PRINT("ShutdownProcess::Init() done\n");
730 ShutdownProcess::MessageReceived(BMessage
* message
)
732 switch (message
->what
) {
733 case B_SOME_APP_QUIT
:
737 if (message
->FindInt32("be:team", &team
) != B_OK
) {
742 PRINT("ShutdownProcess::MessageReceived(): B_SOME_APP_QUIT: %"
743 B_PRId32
"\n", team
);
745 // remove the app info from the respective list
749 BAutolock
_(fWorkerLock
);
751 info
= fUserApps
.InfoFor(team
);
753 fUserApps
.RemoveInfo(info
);
754 else if ((info
= fSystemApps
.InfoFor(team
)))
755 fSystemApps
.RemoveInfo(info
);
756 else if ((info
= fBackgroundApps
.InfoFor(team
)))
757 fBackgroundApps
.RemoveInfo(info
);
761 phase
= fCurrentPhase
;
765 _PushEvent(APP_QUIT_EVENT
, team
, phase
);
772 case MSG_PHASE_TIMED_OUT
:
774 // get the phase the event is intended for
775 int32 phase
= TimeoutEvent::GetMessagePhase(message
);
776 team_id team
= TimeoutEvent::GetMessageTeam(message
);;
777 PRINT("MSG_PHASE_TIMED_OUT: phase: %" B_PRId32
", team: %" B_PRId32
780 BAutolock
_(fWorkerLock
);
782 if (phase
== INVALID_PHASE
|| phase
!= fCurrentPhase
)
786 _PushEvent(TIMEOUT_EVENT
, team
, phase
);
791 case MSG_KILL_APPLICATION
:
794 if (message
->FindInt32("team", &team
) != B_OK
)
798 _PushEvent(KILL_APP_EVENT
, team
, fCurrentPhase
);
802 case MSG_CANCEL_SHUTDOWN
:
805 _PushEvent(ABORT_EVENT
, -1, fCurrentPhase
);
809 case MSG_REBOOT_SYSTEM
:
812 _PushEvent(REBOOT_SYSTEM_EVENT
, -1, INVALID_PHASE
);
818 // notify the registrar that we're done
819 be_app
->PostMessage(B_REG_SHUTDOWN_FINISHED
, be_app
);
823 case B_REG_TEAM_DEBUGGER_ALERT
:
826 if (message
->FindBool("stop shutdown", &stopShutdown
) == B_OK
828 // post abort event to the worker
829 _PushEvent(ABORT_EVENT
, -1, fCurrentPhase
);
835 if (message
->FindInt32("team", &team
) != B_OK
836 || message
->FindBool("open", &open
) != B_OK
)
839 BAutolock
_(fWorkerLock
);
841 PRINT("B_REG_TEAM_DEBUGGER_ALERT: insert %" B_PRId32
"\n",
843 fDebuggedTeams
.insert(team
);
845 PRINT("B_REG_TEAM_DEBUGGER_ALERT: remove %" B_PRId32
"\n",
847 fDebuggedTeams
.erase(team
);
848 _PushEvent(DEBUG_EVENT
, -1, fCurrentPhase
);
854 BLooper::MessageReceived(message
);
861 ShutdownProcess::SendReply(BMessage
* request
, status_t error
)
864 BMessage
reply(B_REG_SUCCESS
);
865 request
->SendReply(&reply
);
867 BMessage
reply(B_REG_ERROR
);
868 reply
.AddInt32("error", error
);
869 request
->SendReply(&reply
);
875 ShutdownProcess::_SendReply(status_t error
)
877 if (!fRequestReplySent
) {
878 SendReply(fRequest
, error
);
879 fRequestReplySent
= true;
885 ShutdownProcess::_SetPhase(int32 phase
)
887 BAutolock
_(fWorkerLock
);
889 if (phase
== fCurrentPhase
)
892 fCurrentPhase
= phase
;
894 // remove the timeout event scheduled for the previous phase
895 fEventQueue
->RemoveEvent(fTimeoutEvent
);
900 ShutdownProcess::_ScheduleTimeoutEvent(bigtime_t timeout
, team_id team
)
902 BAutolock
_(fWorkerLock
);
904 // remove the timeout event
905 fEventQueue
->RemoveEvent(fTimeoutEvent
);
907 // set the event's phase, team and time
908 fTimeoutEvent
->SetPhase(fCurrentPhase
);
909 fTimeoutEvent
->SetTeam(team
);
910 fTimeoutEvent
->SetTime(system_time() + timeout
);
913 fEventQueue
->AddEvent(fTimeoutEvent
);
918 ShutdownProcess::_SetShowShutdownWindow(bool show
)
921 BAutolock
_(fWindow
);
923 if (show
== fWindow
->IsHidden()) {
934 ShutdownProcess::_InitShutdownWindow()
936 // prepare the window
938 fWindow
= new(nothrow
) ShutdownWindow
;
939 if (fWindow
!= NULL
) {
940 status_t error
= fWindow
->Init(BMessenger(this));
947 // add the applications
949 BAutolock
_(fWorkerLock
);
950 _AddShutdownWindowApps(fUserApps
);
951 _AddShutdownWindowApps(fSystemApps
);
953 WARNING("ShutdownProcess::Init(): Failed to create or init "
963 ShutdownProcess::_AddShutdownWindowApps(AppInfoList
& infos
)
968 for (AppInfoList::Iterator it
= infos
.It(); it
.IsValid(); ++it
) {
969 RosterAppInfo
* info
= *it
;
971 // init an app file info
973 status_t error
= file
.SetTo(&info
->ref
, B_READ_ONLY
);
975 WARNING("ShutdownProcess::_AddShutdownWindowApps(): Failed to "
976 "open file for app %s: %s\n", info
->signature
,
981 BAppFileInfo appFileInfo
;
982 error
= appFileInfo
.SetTo(&file
);
984 WARNING("ShutdownProcess::_AddShutdownWindowApps(): Failed to "
985 "init app file info for app %s: %s\n", info
->signature
,
989 // get the application icons
991 color_space format
= B_RGBA32
;
993 color_space format
= B_CMAP8
;
997 BBitmap
* miniIcon
= new(nothrow
) BBitmap(BRect(0, 0, 15, 15), format
);
998 if (miniIcon
!= NULL
) {
999 error
= miniIcon
->InitCheck();
1001 error
= appFileInfo
.GetTrackerIcon(miniIcon
, B_MINI_ICON
);
1002 if (error
!= B_OK
) {
1009 BBitmap
* largeIcon
= new(nothrow
) BBitmap(BRect(0, 0, 31, 31), format
);
1010 if (largeIcon
!= NULL
) {
1011 error
= largeIcon
->InitCheck();
1013 error
= appFileInfo
.GetTrackerIcon(largeIcon
, B_LARGE_ICON
);
1014 if (error
!= B_OK
) {
1021 error
= fWindow
->AddApp(info
->team
, miniIcon
, largeIcon
);
1022 if (error
!= B_OK
) {
1023 WARNING("ShutdownProcess::_AddShutdownWindowApps(): Failed to "
1024 "add app to the shutdown window: %s\n", strerror(error
));
1031 ShutdownProcess::_RemoveShutdownWindowApp(team_id team
)
1034 BAutolock
_(fWindow
);
1036 fWindow
->RemoveApp(team
);
1042 ShutdownProcess::_SetShutdownWindowCurrentApp(team_id team
)
1045 BAutolock
_(fWindow
);
1047 fWindow
->SetCurrentApp(team
);
1053 ShutdownProcess::_SetShutdownWindowText(const char* text
)
1056 BAutolock
_(fWindow
);
1058 fWindow
->SetText(text
);
1064 ShutdownProcess::_SetShutdownWindowCancelButtonEnabled(bool enabled
)
1067 BAutolock
_(fWindow
);
1069 fWindow
->SetCancelShutdownButtonEnabled(enabled
);
1075 ShutdownProcess::_SetShutdownWindowKillButtonEnabled(bool enabled
)
1078 BAutolock
_(fWindow
);
1080 fWindow
->SetKillAppButtonEnabled(enabled
);
1086 ShutdownProcess::_SetShutdownWindowWaitForShutdown()
1089 BAutolock
_(fWindow
);
1091 fWindow
->SetWaitForShutdown();
1097 ShutdownProcess::_SetShutdownWindowWaitForAbortedOK()
1100 BAutolock
_(fWindow
);
1102 fWindow
->SetWaitForAbortedOK();
1108 ShutdownProcess::_NegativeQuitRequestReply(thread_id thread
)
1110 BAutolock
_(fWorkerLock
);
1112 // Note: team ID == team main thread ID under Haiku. When testing under R5
1113 // using the team ID in case of an ABORT_EVENT won't work correctly. But
1114 // this is done only for system apps.
1115 _PushEvent(ABORT_EVENT
, thread
, fCurrentPhase
);
1120 ShutdownProcess::_PrepareShutdownMessage(BMessage
& message
) const
1122 message
.what
= B_QUIT_REQUESTED
;
1123 message
.AddBool("_shutdown_", true);
1125 BMessage::Private(message
).SetReply(BMessenger(fQuitRequestReplyHandler
));
1130 ShutdownProcess::_ShutDown()
1132 PRINT("Invoking _kern_shutdown(%d)\n", fReboot
);
1133 RETURN_ERROR(_kern_shutdown(fReboot
));
1138 ShutdownProcess::_PushEvent(uint32 eventType
, team_id team
, int32 phase
)
1140 InternalEvent
* event
= new(nothrow
) InternalEvent(eventType
, team
, phase
);
1142 ERROR("ShutdownProcess::_PushEvent(): Failed to create event!\n");
1147 BAutolock
_(fWorkerLock
);
1149 fInternalEvents
->Add(event
);
1150 release_sem(fInternalEventSemaphore
);
1157 ShutdownProcess::_GetNextEvent(uint32
& eventType
, thread_id
& team
, int32
& phase
,
1161 // acquire the semaphore
1165 error
= acquire_sem(fInternalEventSemaphore
);
1166 } while (error
== B_INTERRUPTED
);
1171 status_t error
= acquire_sem_etc(fInternalEventSemaphore
, 1,
1172 B_RELATIVE_TIMEOUT
, 0);
1173 if (error
!= B_OK
) {
1174 eventType
= NO_EVENT
;
1180 BAutolock
_(fWorkerLock
);
1182 InternalEvent
* event
= fInternalEvents
->Head();
1183 fInternalEvents
->Remove(event
);
1185 eventType
= event
->Type();
1186 team
= event
->Team();
1187 phase
= event
->Phase();
1191 // if the event is an obsolete timeout event, we drop it right here
1192 if (eventType
== TIMEOUT_EVENT
&& phase
!= fCurrentPhase
)
1198 // notify the window, if an app has been removed
1199 if (eventType
== APP_QUIT_EVENT
)
1200 _RemoveShutdownWindowApp(team
);
1207 ShutdownProcess::_WorkerEntry(void* data
)
1209 return ((ShutdownProcess
*)data
)->_Worker();
1214 ShutdownProcess::_Worker()
1217 _WorkerDoShutdown();
1218 fShutdownError
= B_OK
;
1219 } catch (status_t error
) {
1220 PRINT("ShutdownProcess::_Worker(): error while shutting down: %s\n",
1223 fShutdownError
= error
;
1226 // this can happen only, if the shutdown process failed or was aborted:
1227 // notify the looper
1228 _SetPhase(DONE_PHASE
);
1229 PostMessage(MSG_DONE
);
1236 ShutdownProcess::_WorkerDoShutdown()
1238 PRINT("ShutdownProcess::_WorkerDoShutdown()\n");
1240 // If we are here, the shutdown process has been initiated successfully,
1241 // that is, if an asynchronous BRoster::Shutdown() was requested, we
1242 // notify the caller at this point.
1244 if (fRequest
->FindBool("synchronous", &synchronous
) == B_OK
&& !synchronous
)
1247 // ask the user to confirm the shutdown, if desired
1249 if (fHasGUI
&& fRequest
->FindBool("confirm", &askUser
) == B_OK
&& askUser
) {
1250 const char* restart
= B_TRANSLATE("Restart");
1251 const char* shutdown
= B_TRANSLATE("Shut down");
1252 BString title
= B_TRANSLATE("%action%?");
1253 title
.ReplaceFirst("%action%", fReboot
? restart
: shutdown
);
1254 const char* text
= fReboot
1255 ? B_TRANSLATE("Do you really want to restart the system?")
1256 : B_TRANSLATE("Do you really want to shut down the system?");
1257 const char* defaultText
= fReboot
? restart
: shutdown
;
1258 const char* otherText
= fReboot
? shutdown
: restart
;
1259 BAlert
* alert
= new BAlert(title
.String(), text
,
1260 B_TRANSLATE("Cancel"), otherText
, defaultText
,
1261 B_WIDTH_AS_USUAL
, B_WARNING_ALERT
);
1262 // We want the alert to behave more like a regular window...
1263 alert
->SetFeel(B_NORMAL_WINDOW_FEEL
);
1264 // ...but not quit. Minimizing the alert would prevent the user from
1265 // finding it again, since registrar does not have an entry in the
1267 alert
->SetFlags(alert
->Flags() | B_NOT_MINIMIZABLE
| B_CLOSE_ON_ESCAPE
);
1268 alert
->SetWorkspaces(B_ALL_WORKSPACES
);
1269 int32 result
= alert
->Go();
1272 // Toggle shutdown method
1274 } else if (result
< 1)
1275 throw_error(B_SHUTDOWN_CANCELLED
);
1278 // tell TRoster not to accept new applications anymore
1279 fRoster
->SetShuttingDown(true);
1283 // get a list of all applications to shut down and sort them
1284 status_t status
= fRoster
->GetShutdownApps(fUserApps
, fSystemApps
,
1285 fBackgroundApps
, fVitalSystemApps
);
1286 if (status
!= B_OK
) {
1287 fWorkerLock
.Unlock();
1288 fRoster
->RemoveWatcher(this);
1289 fRoster
->SetShuttingDown(false);
1293 fUserApps
.Sort(&inverse_compare_by_registration_time
);
1294 fSystemApps
.Sort(&inverse_compare_by_registration_time
);
1296 fWorkerLock
.Unlock();
1298 // make the shutdown window ready and show it
1299 _InitShutdownWindow();
1300 _SetShutdownWindowCurrentApp(-1);
1301 _SetShutdownWindowText(B_TRANSLATE("Tidying things up a bit."));
1302 _SetShutdownWindowCancelButtonEnabled(true);
1303 _SetShutdownWindowKillButtonEnabled(false);
1304 _SetShowShutdownWindow(true);
1309 // phase 1: terminate the user apps
1310 _SetPhase(USER_APP_TERMINATION_PHASE
);
1311 _QuitApps(fUserApps
, false);
1312 _WaitForDebuggedTeams();
1314 // phase 2: terminate the system apps
1315 _SetPhase(SYSTEM_APP_TERMINATION_PHASE
);
1316 _QuitApps(fSystemApps
, true);
1317 _WaitForDebuggedTeams();
1319 // phase 3: terminate the background apps
1320 _SetPhase(BACKGROUND_APP_TERMINATION_PHASE
);
1321 _QuitBackgroundApps();
1322 _WaitForDebuggedTeams();
1324 // phase 4: terminate the other processes
1325 _SetPhase(OTHER_PROCESSES_TERMINATION_PHASE
);
1327 _ScheduleTimeoutEvent(kBackgroundAppQuitTimeout
, -1);
1328 _WaitForBackgroundApps();
1329 _KillBackgroundApps();
1330 _WaitForDebuggedTeams();
1332 // we're through: do the shutdown
1333 _SetPhase(DONE_PHASE
);
1335 _SetShutdownWindowText(B_TRANSLATE("Restarting" B_UTF8_ELLIPSIS
));
1337 _SetShutdownWindowText(B_TRANSLATE("Shutting down" B_UTF8_ELLIPSIS
));
1339 _SetShutdownWindowWaitForShutdown();
1341 PRINT(" _kern_shutdown() failed\n");
1343 // shutdown failed: This can happen for power off mode -- reboot should
1346 // wait for the reboot event
1351 status
= _GetNextEvent(event
, team
, phase
, true);
1354 } while (event
!= REBOOT_SYSTEM_EVENT
);
1356 _kern_shutdown(true);
1359 // either there's no GUI or reboot failed: we enter the kernel debugger
1362 // TODO: Introduce the syscall.
1364 // _kern_kernel_debugger("The system is shut down. It's now safe to turn "
1365 // "off the computer.");
1372 ShutdownProcess::_WaitForApp(team_id team
, AppInfoList
* list
, bool systemApps
)
1378 status_t error
= _GetNextEvent(event
, eventTeam
, phase
, true);
1382 if (event
== APP_QUIT_EVENT
&& eventTeam
== team
)
1385 if (event
== TIMEOUT_EVENT
&& eventTeam
== team
)
1388 if (event
== ABORT_EVENT
) {
1389 if (eventTeam
== -1) {
1390 // The user canceled the shutdown process by pressing the
1392 throw_error(B_SHUTDOWN_CANCELLED
);
1395 // If the app requests aborting the shutdown, we don't need
1396 // to wait any longer. It has processed the request and
1397 // won't quit by itself. We ignore this for system apps.
1398 if (eventTeam
== team
)
1401 // The app returned false in QuitRequested().
1402 PRINT("ShutdownProcess::_WaitForApp(): shutdown cancelled "
1403 "by team %" B_PRId32
" (-1 => user)\n", eventTeam
);
1405 _DisplayAbortingApp(team
);
1406 throw_error(B_SHUTDOWN_CANCELLED
);
1410 BAutolock
_(fWorkerLock
);
1411 if (list
!= NULL
&& !list
->InfoFor(team
))
1413 } while (event
!= NO_EVENT
);
1420 ShutdownProcess::_QuitApps(AppInfoList
& list
, bool systemApps
)
1422 PRINT("ShutdownProcess::_QuitApps(%s)\n",
1423 (systemApps
? "system" : "user"));
1426 _SetShutdownWindowCancelButtonEnabled(false);
1428 // check one last time for abort events
1433 status_t error
= _GetNextEvent(event
, team
, phase
, false);
1437 if (event
== ABORT_EVENT
) {
1438 PRINT("ShutdownProcess::_QuitApps(): shutdown cancelled by "
1439 "team %" B_PRId32
" (-1 => user)\n", team
);
1441 _DisplayAbortingApp(team
);
1442 throw_error(B_SHUTDOWN_CANCELLED
);
1445 } while (event
!= NO_EVENT
);
1448 // prepare the shutdown message
1450 _PrepareShutdownMessage(message
);
1452 // now iterate through the list of apps
1459 status_t error
= _GetNextEvent(event
, team
, phase
, false);
1463 if (!systemApps
&& event
== ABORT_EVENT
) {
1464 PRINT("ShutdownProcess::_QuitApps(): shutdown cancelled by "
1465 "team %" B_PRId32
" (-1 => user)\n", team
);
1467 _DisplayAbortingApp(team
);
1468 throw_error(B_SHUTDOWN_CANCELLED
);
1471 } while (event
!= NO_EVENT
);
1473 // get the first app to quit
1476 char appName
[B_FILE_NAME_LENGTH
];
1478 BAutolock
_(fWorkerLock
);
1479 while (!list
.IsEmpty()) {
1480 RosterAppInfo
* info
= *list
.It();
1483 strcpy(appName
, info
->ref
.name
);
1485 if (info
->IsRunning())
1487 list
.RemoveInfo(info
);
1493 PRINT("ShutdownProcess::_QuitApps() done\n");
1498 BString buffer
= B_TRANSLATE("Asking \"%appName%\" to quit.");
1499 buffer
.ReplaceFirst("%appName%", appName
);
1500 _SetShutdownWindowText(buffer
.String());
1501 _SetShutdownWindowCurrentApp(team
);
1503 // send the shutdown message to the app
1504 PRINT(" sending team %" B_PRId32
" (port: %" B_PRId32
") a shutdown "
1505 "message\n", team
, port
);
1506 SingleMessagingTargetSet
target(port
, B_PREFERRED_TOKEN
);
1507 MessageDeliverer::Default()->DeliverMessage(&message
, target
);
1509 // schedule a timeout event
1510 _ScheduleTimeoutEvent(kAppQuitTimeout
, team
);
1512 // wait for the app to die or for the timeout to occur
1513 bool appGone
= _WaitForApp(team
, &list
, systemApps
);
1515 // fine: the app finished in an orderly manner
1517 // the app is either blocking on a model alert or blocks for another
1520 _QuitBlockingApp(list
, team
, appName
, true);
1522 // This is a system app: remove it from the list
1523 BAutolock
_(fWorkerLock
);
1525 if (RosterAppInfo
* info
= list
.InfoFor(team
)) {
1526 list
.RemoveInfo(info
);
1536 ShutdownProcess::_QuitBackgroundApps()
1538 PRINT("ShutdownProcess::_QuitBackgroundApps()\n");
1540 _SetShutdownWindowText(
1541 B_TRANSLATE("Asking background applications to quit."));
1543 // prepare the shutdown message
1545 _PrepareShutdownMessage(message
);
1547 // send shutdown messages to user apps
1548 BAutolock
_(fWorkerLock
);
1550 AppInfoListMessagingTargetSet
targetSet(fBackgroundApps
);
1552 if (targetSet
.HasNext()) {
1553 PRINT(" sending shutdown message to %" B_PRId32
" apps\n",
1554 fBackgroundApps
.CountInfos());
1556 status_t error
= MessageDeliverer::Default()->DeliverMessage(
1557 &message
, targetSet
);
1558 if (error
!= B_OK
) {
1559 WARNING("_QuitBackgroundApps::_Worker(): Failed to deliver "
1560 "shutdown message to all applications: %s\n",
1565 PRINT("ShutdownProcess::_QuitBackgroundApps() done\n");
1570 ShutdownProcess::_WaitForBackgroundApps()
1572 PRINT("ShutdownProcess::_WaitForBackgroundApps()\n");
1574 // wait for user apps
1575 bool moreApps
= true;
1578 BAutolock
_(fWorkerLock
);
1579 moreApps
= !fBackgroundApps
.IsEmpty();
1586 status_t error
= _GetNextEvent(event
, team
, phase
, true);
1590 if (event
== ABORT_EVENT
) {
1591 // ignore: it's too late to abort the shutdown
1594 if (event
== TIMEOUT_EVENT
)
1599 PRINT("ShutdownProcess::_WaitForBackgroundApps() done\n");
1604 ShutdownProcess::_KillBackgroundApps()
1606 PRINT("ShutdownProcess::_KillBackgroundApps()\n");
1609 // eat events (we need to be responsive for an abort event)
1614 status_t error
= _GetNextEvent(event
, team
, phase
, false);
1618 } while (event
!= NO_EVENT
);
1620 // get the first team to kill
1622 char appName
[B_FILE_NAME_LENGTH
];
1623 AppInfoList
& list
= fBackgroundApps
;
1625 BAutolock
_(fWorkerLock
);
1627 if (!list
.IsEmpty()) {
1628 RosterAppInfo
* info
= *list
.It();
1630 strcpy(appName
, info
->ref
.name
);
1636 PRINT("ShutdownProcess::_KillBackgroundApps() done\n");
1640 // the app is either blocking on a model alert or blocks for another
1642 _QuitBlockingApp(list
, team
, appName
, false);
1648 ShutdownProcess::_QuitNonApps()
1650 PRINT("ShutdownProcess::_QuitNonApps()\n");
1652 _SetShutdownWindowText(B_TRANSLATE("Asking other processes to quit."));
1654 // iterate through the remaining teams and send them the TERM signal
1657 while (get_next_team_info(&cookie
, &teamInfo
) == B_OK
) {
1658 if (fVitalSystemApps
.find(teamInfo
.team
) == fVitalSystemApps
.end()) {
1659 PRINT(" sending team %" B_PRId32
" TERM signal\n", teamInfo
.team
);
1662 // Note: team ID == team main thread ID under Haiku
1663 send_signal(teamInfo
.team
, SIGTERM
);
1665 // We don't want to do this when testing under R5, since it
1666 // would kill all teams besides our app server and registrar.
1671 // give them a bit of time to terminate
1672 // TODO: Instead of just waiting we could periodically check whether the
1673 // processes are already gone to shorten the process.
1674 snooze(kNonAppQuitTimeout
);
1676 // iterate through the remaining teams and kill them
1678 while (get_next_team_info(&cookie
, &teamInfo
) == B_OK
) {
1679 if (fVitalSystemApps
.find(teamInfo
.team
) == fVitalSystemApps
.end()) {
1680 PRINT(" killing team %" B_PRId32
"\n", teamInfo
.team
);
1683 kill_team(teamInfo
.team
);
1685 // We don't want to do this when testing under R5, since it
1686 // would kill all teams besides our app server and registrar.
1691 PRINT("ShutdownProcess::_QuitNonApps() done\n");
1696 ShutdownProcess::_QuitBlockingApp(AppInfoList
& list
, team_id team
,
1697 const char* appName
, bool cancelAllowed
)
1699 bool debugged
= false;
1702 BAutolock
_(fWorkerLock
);
1703 if (fDebuggedTeams
.find(team
) != fDebuggedTeams
.end())
1707 modal
= BPrivate::is_app_showing_modal_window(team
);
1710 // app blocks on a modal window
1711 BString buffer
= B_TRANSLATE("The application \"%appName%\" might be "
1712 "blocked on a modal panel.");
1713 buffer
.ReplaceFirst("%appName%", appName
);
1714 _SetShutdownWindowText(buffer
.String());
1715 _SetShutdownWindowCurrentApp(team
);
1716 _SetShutdownWindowKillButtonEnabled(true);
1719 if (modal
|| debugged
) {
1720 // wait for something to happen
1721 bool appGone
= false;
1726 status_t error
= _GetNextEvent(event
, eventTeam
, phase
, true);
1730 if ((event
== APP_QUIT_EVENT
) && eventTeam
== team
) {
1735 if (event
== KILL_APP_EVENT
&& eventTeam
== team
)
1738 if (event
== ABORT_EVENT
) {
1739 if (cancelAllowed
|| debugged
) {
1740 PRINT("ShutdownProcess::_QuitBlockingApp(): shutdown "
1741 "cancelled by team %" B_PRId32
" (-1 => user)\n",
1745 _DisplayAbortingApp(eventTeam
);
1746 throw_error(B_SHUTDOWN_CANCELLED
);
1749 // If the app requests aborting the shutdown, we don't need
1750 // to wait any longer. It has processed the request and
1751 // won't quit by itself. We'll have to kill it.
1752 if (eventTeam
== team
)
1757 _SetShutdownWindowKillButtonEnabled(false);
1764 PRINT(" killing team %" B_PRId32
"\n", team
);
1768 // remove the app (the roster will note eventually and send us
1769 // a notification, but we want to be sure)
1771 BAutolock
_(fWorkerLock
);
1773 if (RosterAppInfo
* info
= list
.InfoFor(team
)) {
1774 list
.RemoveInfo(info
);
1782 ShutdownProcess::_DisplayAbortingApp(team_id team
)
1784 // find the app that cancelled the shutdown
1785 char appName
[B_FILE_NAME_LENGTH
];
1786 bool foundApp
= false;
1788 BAutolock
_(fWorkerLock
);
1790 RosterAppInfo
* info
= fUserApps
.InfoFor(team
);
1792 info
= fSystemApps
.InfoFor(team
);
1794 fBackgroundApps
.InfoFor(team
);
1798 strcpy(appName
, info
->ref
.name
);
1803 PRINT("ShutdownProcess::_DisplayAbortingApp(): Didn't find the app "
1804 "that has cancelled the shutdown.\n");
1808 // compose the text to be displayed
1809 BString buffer
= B_TRANSLATE("Application \"%appName%\" has aborted the "
1810 "shutdown process.");
1811 buffer
.ReplaceFirst("%appName%", appName
);
1813 // set up the window
1814 _SetShutdownWindowCurrentApp(team
);
1815 _SetShutdownWindowText(buffer
.String());
1816 _SetShutdownWindowWaitForAbortedOK();
1818 // schedule the timeout event
1819 _SetPhase(ABORTED_PHASE
);
1820 _ScheduleTimeoutEvent(kDisplayAbortingAppTimeout
);
1822 // wait for the timeout or the user to press the cancel button
1827 status_t error
= _GetNextEvent(event
, eventTeam
, phase
, true);
1831 // stop waiting when the timeout occurs
1832 if (event
== TIMEOUT_EVENT
)
1835 // stop waiting when the user hit the cancel button
1836 if (event
== ABORT_EVENT
&& phase
== ABORTED_PHASE
&& eventTeam
< 0)
1842 /*! Waits until the debugged team list is empty, ie. when there is no one
1846 ShutdownProcess::_WaitForDebuggedTeams()
1848 PRINT("ShutdownProcess::_WaitForDebuggedTeams()\n");
1850 BAutolock
_(fWorkerLock
);
1851 if (fDebuggedTeams
.empty())
1855 PRINT(" not empty!\n");
1857 // wait for something to happen
1862 status_t error
= _GetNextEvent(event
, eventTeam
, phase
, true);
1866 if (event
== ABORT_EVENT
)
1867 throw_error(B_SHUTDOWN_CANCELLED
);
1869 BAutolock
_(fWorkerLock
);
1870 if (fDebuggedTeams
.empty()) {
1871 PRINT(" out empty");