Native Restart and Logout Dialogs with PyObjC

Published December 15, 2020 / 406 words / ~2 minutes to read

Recently I wanted to find a friendly way to prompt for logout or restart using the dialog prompts people were already used to. As part of a workflow users had to restart, but the only solutions I found to programmatically accomplish this were to force something like…

sudo shutdown -r now

That works decently, but can be easily interrupted by a blocking process, is abrupt, and isn’t what people are used to when they restart their Mac. When going to  > Restart… everyone’s used to seeing this dialog pop up…

Restart dialog

Luckily I found an old pudquick / frogor / Michael Lynn (thanks!) gist on generating native “polite” login window events using PyObjC.

Awesome. He took care of the deep dive API work for me and and I now have working code to generate a logout, restart, or shutdown dialog window on demand. The only problem being it’s written in Python 2. A while back I started shipping relocatable Python 3 interpeter to managed clients to get ready for Python 2 deprecation, and Apple eventually not including it by default with macOS. Running the gist unedited through Python 3.8 with PyObjC installed came back with a lot of errors. After messing around with encoding and a few other functions, I came up with a working Python 3 version. This has been tested with Python 3.8+ and PyObjC 6.1, but should work with most Python 3 versions.

The important bits here are the different AERegistry values.

# Defined in AERegistry.h
kAELogOut = OSType("logo")
kAEReallyLogOut = OSType("rlgo")
kAEShowRestartDialog = OSType("rrst")
kAEShowShutdownDialog = OSType("rsdn")

# Build a standalone application descriptor by bundle id
loginwindowDesc = NSAppleEventDescriptor.alloc().initWithDescriptorType_data_(
    typeApplicationBundleID, memoryview(b"com.apple.loginwindow")
)

# Build an event descriptor with our app descriptor as the target and the kAELogOut eventID
event = NSAppleEventDescriptor.appleEventWithEventClass_eventID_targetDescriptor_returnID_transactionID_(
    typeAppleEvent,
    kAELogOut,
    loginwindowDesc,
    kAutoGenerateReturnID,
    kAnyTransactionID,
)

On line 59 of my gist change the value kAELogOut to any of the others in that list to get a different dialog and corresponding function. Unsurprisingly, kAELogOut results in a logout dialog.

log dialog

kAEShowRestartDialog and kAEShowShutdownDialog are self explanatory. Give them a try to see the usual dialog windows you would see from going to  > Shutdown… and similar. Be careful when playing with kAEReallyLogOut. Apparently “really log out” means log out immediately with no dialog prompt. In a future post I plan to explain how I used the restart dialog to prompt users when their uptime was over a certain number of days.