Last active
March 30, 2023 13:40
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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? | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It seems it's not anymore in
Foundation
(or I was mistaken). Works fine when you importAppKit
. I am updating the gist.