Mission Control Turbo: macOS multitasking turbocharged
1import Carbon
2import AppKit
3
4/// Manages global hotkey registration using Carbon RegisterEventHotKey.
5final class HotKeyManager {
6 typealias Handler = () -> Void
7
8 private var hotKeyRef: EventHotKeyRef?
9 private var eventHandler: EventHandlerRef?
10 private let handler: Handler
11
12 /// The signature used to identify our hotkey events.
13 private static let hotKeySignature: FourCharCode = {
14 let chars: [UInt8] = [0x4D, 0x43, 0x54, 0x48] // "MCTH"
15 return FourCharCode(chars[0]) << 24 | FourCharCode(chars[1]) << 16 | FourCharCode(chars[2]) << 8 | FourCharCode(chars[3])
16 }()
17
18 private static let hotKeyID = EventHotKeyID(signature: hotKeySignature, id: 1)
19
20 init(handler: @escaping Handler) {
21 self.handler = handler
22 }
23
24 deinit {
25 unregister()
26 }
27
28 /// Register the global hotkey. Default: Ctrl+Up Arrow.
29 func register(keyCode: UInt32 = UInt32(kVK_UpArrow), modifiers: UInt32 = UInt32(controlKey)) {
30 unregister()
31
32 // Install Carbon event handler
33 var eventType = EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed))
34 let selfPtr = Unmanaged.passUnretained(self).toOpaque()
35
36 InstallEventHandler(
37 GetApplicationEventTarget(),
38 { (_, event, userData) -> OSStatus in
39 guard let userData = userData else { return OSStatus(eventNotHandledErr) }
40 let manager = Unmanaged<HotKeyManager>.fromOpaque(userData).takeUnretainedValue()
41 var hotKeyID = EventHotKeyID()
42 GetEventParameter(
43 event,
44 EventParamName(kEventParamDirectObject),
45 EventParamType(typeEventHotKeyID),
46 nil,
47 MemoryLayout<EventHotKeyID>.size,
48 nil,
49 &hotKeyID
50 )
51 if hotKeyID.signature == HotKeyManager.hotKeySignature {
52 DispatchQueue.main.async {
53 manager.handler()
54 }
55 }
56 return noErr
57 },
58 1,
59 &eventType,
60 selfPtr,
61 &eventHandler
62 )
63
64 // Register the hotkey
65 let hotKeyIDVar = Self.hotKeyID
66 RegisterEventHotKey(
67 keyCode,
68 modifiers,
69 hotKeyIDVar,
70 GetApplicationEventTarget(),
71 0,
72 &hotKeyRef
73 )
74 }
75
76 func unregister() {
77 if let ref = hotKeyRef {
78 UnregisterEventHotKey(ref)
79 hotKeyRef = nil
80 }
81 if let handler = eventHandler {
82 RemoveEventHandler(handler)
83 eventHandler = nil
84 }
85 }
86}