Skip to content

Instantly share code, notes, and snippets.

@pudquick
Created April 7, 2016 08:15
Show Gist options
  • Save pudquick/9683c333e73a82379b8e377eb2e6fc41 to your computer and use it in GitHub Desktop.
Save pudquick/9683c333e73a82379b8e377eb2e6fc41 to your computer and use it in GitHub Desktop.
Send polite Logout / "really" Logout / Restart / Shutdown Apple Events to loginwindow via python and pyObjC
import struct, objc
from Foundation import NSBundle
from Cocoa import NSAppleEventDescriptor
def OSType(s):
# Convert 4 character code into 4 byte integer
return struct.unpack('>I', s)[0]
# Create an opaque pointer type to mask the raw AEDesc pointers we'll throw around
AEDescRef = objc.createOpaquePointerType('AEDescRef', '^{AEDesc=I^^{OpaqueAEDataStorageType}}')
# Load AESendMessage from AE.framework for sending the AppleEvent
AE_bundle = NSBundle.bundleWithIdentifier_('com.apple.AE')
functions = [("AESendMessage", b"i^{AEDesc=I^^{OpaqueAEDataStorageType}}^{AEDesc=I^^{OpaqueAEDataStorageType}}iq"),]
objc.loadBundleFunctions(AE_bundle, globals(), functions)
# defined in AEDataModel.h
kAENoReply = 1
kAENeverInteract = 16
kAEDefaultTimeout = -1
kAnyTransactionID = 0
kAutoGenerateReturnID = -1
# defined in AEDataModel.h
typeAppleEvent = OSType('aevt')
typeApplicationBundleID = OSType('bund')
# 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, buffer('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)
eventDesc = event.aeDesc()
# Send a polite logout (returns immediately)
err = AESendMessage(eventDesc, None, kAENoReply|kAENeverInteract, kAEDefaultTimeout)
@pudquick
Copy link
Author

pudquick commented Apr 7, 2016

And much less effective version, via SBApplication scripting bridge:

from ScriptingBridge import SBApplication
systemevents = SBApplication.applicationWithBundleIdentifier_('com.apple.systemevents')
systemevents.logOut()

Less effective because it triggers a bundle/GUI application conversion (triggering the Dock rocketship icon) plus doesn't work from root/anything other than the logged in user context

@pudquick
Copy link
Author

pudquick commented Apr 7, 2016

Round 3, via NSAppleScript:

from Foundation import NSAppleScript

script = u"""ignoring application responses
tell application "loginwindow"
\u300Aevent aevtlogo\u300B
end tell
end ignoring
"""

logout = NSAppleScript.alloc().initWithSource_(script)
result = logout.executeAndReturnError_(None)

Same issues as the SBApplication method, in terms of effectiveness/usability. Had to use the "asian encoding compatible" chevrons listed here: https://developer.apple.com/library/mac/documentation/AppleScript/Conceptual/AppleScriptLangGuide/conceptual/ASLR_lexical_conventions.html#//apple_ref/doc/uid/TP40000983-CH214-SW5

Likely because the original left/right chevrons were part of MacRoman encoding and NSString <-> NSAppleScript communication trips all over itself about how to interpret. Even if you build the NSString from data with the right encoding specified, NSAppleScript keeps receiving the UTF-8 encoded form. By moving to the alternate encoding, we're forcing pure Unicode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment