import Carbon.HIToolbox import Foundation /// Thin wrapper around the Carbon RegisterEventHotKey API to register /// system-wide keyboard shortcuts without requiring Accessibility/Input /// Monitoring permission. final class HotKeyManager { static let shared = HotKeyManager() private var hotKeyRefs: [UInt32: EventHotKeyRef] = [:] private var handlers: [UInt32: () -> Void] = [:] private var eventHandlerRef: EventHandlerRef? private static let signature: FourCharCode = { var result: FourCharCode = 0 for byte in "SNAP".utf8 { result = (result << 8) + FourCharCode(byte) } return result }() private init() { installEventHandler() } private func installEventHandler() { var eventType = EventTypeSpec( eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed) ) let callback: EventHandlerUPP = { _, event, userData in guard let event, let userData else { return noErr } var hotKeyID = EventHotKeyID() GetEventParameter( event, EventParamName(kEventParamDirectObject), EventParamType(typeEventHotKeyID), nil, MemoryLayout.size, nil, &hotKeyID ) let manager = Unmanaged.fromOpaque(userData).takeUnretainedValue() manager.handlers[hotKeyID.id]?() return noErr } InstallEventHandler( GetEventDispatcherTarget(), callback, 1, &eventType, Unmanaged.passUnretained(self).toOpaque(), &eventHandlerRef ) } @discardableResult func register(id: UInt32, keyCode: UInt32, modifiers: UInt32, handler: @escaping () -> Void) -> Bool { unregister(id: id) var hotKeyRef: EventHotKeyRef? let hotKeyID = EventHotKeyID(signature: Self.signature, id: id) let status = RegisterEventHotKey( keyCode, modifiers, hotKeyID, GetEventDispatcherTarget(), 0, &hotKeyRef ) guard status == noErr, let hotKeyRef else { return false } hotKeyRefs[id] = hotKeyRef handlers[id] = handler return true } func unregister(id: UInt32) { if let ref = hotKeyRefs.removeValue(forKey: id) { UnregisterEventHotKey(ref) } handlers.removeValue(forKey: id) } }