Skip to content

Instantly share code, notes, and snippets.

@ABridoux
Last active March 30, 2023 13:40
Show Gist options
  • Select an option

  • Save ABridoux/8efb76bdd4ab070d5ea4a18b33a34144 to your computer and use it in GitHub Desktop.

Select an option

Save ABridoux/8efb76bdd4ab070d5ea4a18b33a34144 to your computer and use it in GitHub Desktop.
A service to send events to restart, shutdown, put to sleep or logout the computer.
// Free to use
// Written by Alexis Bridoux - https://github.com/ABridoux
import AppKit
import Foundation
/// Service to shut down, restart, or put the computer to sleep. Also log out the user.
///
/// ### Resources
/// - [Apple doc](https://developer.apple.com/library/archive/qa/qa1134/_index.html)
/// - Already in use in [SplashBuddy](https://github.com/macadmins/SplashBuddy/blob/main/SplashBuddy/Tools/LoginWindow.swift)
enum EventService {}
// MARK: - Logic
extension EventService {
static func send(event eventType: AppleEventType) throws {
// target the login window process for the event
var loginWindowSerialNumber = ProcessSerialNumber(
highLongOfPSN: 0,
lowLongOfPSN: UInt32(kSystemProcess)
)
var targetDesc = AEAddressDesc()
var error = OSErr()
error = AECreateDesc(
keyProcessSerialNumber,
&loginWindowSerialNumber,
MemoryLayout<ProcessSerialNumber>.size,
&targetDesc
)
if error != noErr {
throw EventError(
errorDescription: "Unable to create the description of the app. Status: \(error)"
)
}
// create the Apple event
var event = AppleEvent()
error = AECreateAppleEvent(
kCoreEventClass,
eventType.eventId,
&targetDesc,
AEReturnID(kAutoGenerateReturnID),
AETransactionID(kAnyTransactionID),
&event
)
AEDisposeDesc(&targetDesc)
if error != noErr {
throw EventError(
errorDescription: "Unable to create an Apple Event for the app description. Status: \(error)"
)
}
// send the event
var reply = AppleEvent()
let status = AESendMessage(
&event,
&reply,
AESendMode(kAENoReply),
1000
)
if status != noErr {
throw EventError(
errorDescription: "Error while sending the event \(eventType). Status: \(status)"
)
}
AEDisposeDesc(&event)
AEDisposeDesc(&reply)
}
}
// MARK: - Models
extension EventService {
enum AppleEventType: String {
case shutdownComputer = "Shut down the computer"
case restartComputer = "Restart the computer"
case asleepComputer = "Asleep the computer"
case logoutUser = "Logout the user"
var eventId: OSType {
switch self {
case .shutdownComputer: return kAEShutDown
case .restartComputer: return kAERestart
case .putComputerToSleep: return kAESleep
case .logoutUser: return kAEReallyLogOut
}
}
}
}
extension EventService.AppleEventType: CaseIterable, Identifiable {
var id: String { rawValue }
}
extension EventService {
struct EventError: LocalizedError {
var errorDescription: String?
}
}
@ABridoux
Copy link
Copy Markdown
Author

ABridoux commented Aug 11, 2021

Explanations

Read the article

How to use it

First it's required to add the following key to the app entitlements:

<key>com.apple.security.temporary-exception.apple-events</key>
<array>
    <string>com.apple.loginwindow</string>
</array>

Then

EventService.send(event: .restartComputer)

EventService.send(event: .logoutUser)

@fabienconus
Copy link
Copy Markdown

Is this still valid as of macOS 13 ? I tried using it but XCode complains that it can't find 'kSystemProcess' in scope (line 21).

@ABridoux
Copy link
Copy Markdown
Author

Is this still valid as of macOS 13 ? I tried using it but XCode complains that it can't find 'kSystemProcess' in scope (line 21).

It seems it's not anymore in Foundation (or I was mistaken). Works fine when you import AppKit. I am updating the gist.

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