diff options
Diffstat (limited to 'Sources/DDHotKey/DDHotKeyCenter.swift')
| -rw-r--r-- | Sources/DDHotKey/DDHotKeyCenter.swift | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/Sources/DDHotKey/DDHotKeyCenter.swift b/Sources/DDHotKey/DDHotKeyCenter.swift new file mode 100644 index 0000000..84a456d --- /dev/null +++ b/Sources/DDHotKey/DDHotKeyCenter.swift @@ -0,0 +1,154 @@ +// +// DDHotKeyCenter.swift +// DDHotKey +// +// Created by Dave DeLong on 8/28/19. +// + +import Cocoa +import Carbon + +public class DDHotKeyCenter { + + public enum RegistrationError: Error { + case alreadyRegistered + case tooManyHotKeys + case conflictsWithExistingHotKey(DDHotKey) + case unableToRegisterHotKey(OSStatus) + } + + public enum UnregistrationError: Error { + case notRegistered + case unknownHotKey + case unableToUnregisterHotKey(OSStatus) + } + + public static let shared = DDHotKeyCenter() + + private var registered = Dictionary<UUID, DDHotKey>() + private var nextID: UInt32 = 1 + + private init() { + + var spec = EventTypeSpec() + spec.eventClass = OSType(kEventClassKeyboard) + spec.eventKind = UInt32(kEventHotKeyReleased) + InstallEventHandler(GetApplicationEventTarget(), + handler, + 1, + &spec, + nil, + nil) + } + + internal func hotKeysMatching(_ filter: (DDHotKey) -> Bool) -> Array<DDHotKey> { + return registered.values.filter(filter) + } + + public func registeredHotKeys() -> Array<DDHotKey> { + return Array(registered.values) + } + + public func register(hotKey: DDHotKey) throws /* RegistrationError */ { + // cannot register a hot key that is already registered + if hotKey.hotKeyID != nil { + throw RegistrationError.alreadyRegistered + } + + guard nextID < UInt32.max else { + throw RegistrationError.tooManyHotKeys + } + + // cannot register a hot key that has the same invocation as an existing hotkey + let matching = registered.values.filter { $0.keyCode == hotKey.keyCode && $0.modifiers == hotKey.modifiers } + if matching.isEmpty == false { + throw RegistrationError.conflictsWithExistingHotKey(matching[0]) + } + + let hotKeyID = EventHotKeyID(signature: OSType(fourCharCode: "htk1"), id: nextID) + let flags = carbonModifiers(from: hotKey.modifiers) + + var hotKeyRef: EventHotKeyRef? + let error = RegisterEventHotKey(UInt32(hotKey.keyCode), flags, hotKeyID, GetEventDispatcherTarget(), 0, &hotKeyRef) + + if error != noErr { + throw RegistrationError.unableToRegisterHotKey(error) + } + + hotKey.hotKeyRef = hotKeyRef + hotKey.hotKeyID = nextID + registered[hotKey.uuid] = hotKey + + nextID += 1 + } + + public func unregister(hotKey: DDHotKey) throws /* UnregistrationError */ { + guard let ref = hotKey.hotKeyRef, hotKey.hotKeyID != nil else { + throw UnregistrationError.notRegistered + } + + guard let existing = registered[hotKey.uuid], existing === hotKey else { + throw UnregistrationError.unknownHotKey + } + + let status = UnregisterEventHotKey(ref) + + guard status == noErr else { + throw UnregistrationError.unableToUnregisterHotKey(status) + } + + registered.removeValue(forKey: hotKey.uuid) + } +} + + +fileprivate var handler: EventHandlerProcPtr = { (callRef, eventRef, context) -> OSStatus in + return autoreleasepool { () -> OSStatus in + var hotKeyID = EventHotKeyID() + GetEventParameter(eventRef, + EventParamName(kEventParamDirectObject), + EventParamType(typeEventHotKeyID), + nil, + MemoryLayout<EventHotKeyID>.size, + nil, + &hotKeyID); + + let keyID = hotKeyID.id + let matches = DDHotKeyCenter.shared.hotKeysMatching { $0.hotKeyID == keyID } + if matches.count != 1 { + print("Unable to find a single hotkey with id \(keyID)") + return OSStatus(errAborted) + } + let matching = matches[matches.startIndex] + + guard let ref = eventRef else { + print("Missing EventRef to handle hotkey with id \(keyID)") + return OSStatus(errAborted) + } + + guard let event = NSEvent(eventRef: UnsafeRawPointer(ref)) else { + print("Unable to create NSEvent from EventRef \(ref) for the hotkey with id \(keyID)") + return OSStatus(errAborted) + } + + let keyEvent = NSEvent.keyEvent(with: .keyUp, + location: event.locationInWindow, + modifierFlags: event.modifierFlags, + timestamp: event.timestamp, + windowNumber: -1, + context: nil, + characters: "", + charactersIgnoringModifiers: "", + isARepeat: false, + keyCode: matching.keyCode) + + guard let key = keyEvent else { + print("Unable to create key event from NSEvent \(event) for the hotkey with id \(keyID)") + return OSStatus(errAborted) + } + + matching.invoke(with: key) + return noErr + } + +} |
