87 lines
2.5 KiB
Swift
87 lines
2.5 KiB
Swift
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<EventHotKeyID>.size,
|
|
nil,
|
|
&hotKeyID
|
|
)
|
|
let manager = Unmanaged<HotKeyManager>.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)
|
|
}
|
|
}
|