Ariver
2026-06-20 1d351c209684ed785a840c5d041d017ed12ba64f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import AppKit
import ApplicationServices
@preconcurrency import CoreGraphics
import Foundation
import AlignerCore
 
@MainActor
final class CGEventTapTriggerService: TriggerServiceProtocol {
    enum ServiceError: Error, CustomStringConvertible {
        case eventTapUnavailable
 
        var description: String {
            switch self {
            case .eventTapUnavailable:
                return "CGEventTapCreate returned nil. Accessibility may be missing, or macOS may require a different trigger route."
            }
        }
    }
 
    var onQuickSwitch: (() -> Void)?
    var onStartFailure: ((ServiceError) -> Void)?
    private(set) var isRunning = false
 
    private let shortcut: KeyboardShortcut
    private let overlayVisibleProvider: () -> Bool
    private let accessibilityTrustedProvider: () -> Bool
    private let consumeInitialQuickSwitch: Bool
    private var eventTap: CFMachPort?
    private var runLoopSource: CFRunLoopSource?
 
    init(
        shortcut: KeyboardShortcut = TriggerDefaults.quickSwitchShortcut,
        overlayVisibleProvider: @escaping () -> Bool,
        accessibilityTrustedProvider: @escaping () -> Bool = {
            AXIsProcessTrusted()
        },
        consumeInitialQuickSwitch: Bool = true
    ) {
        self.shortcut = shortcut
        self.overlayVisibleProvider = overlayVisibleProvider
        self.accessibilityTrustedProvider = accessibilityTrustedProvider
        self.consumeInitialQuickSwitch = consumeInitialQuickSwitch
    }
 
    func start() {
        do {
            try startEventTap()
        } catch let error as ServiceError {
            onStartFailure?(error)
        } catch {
            onStartFailure?(.eventTapUnavailable)
        }
    }
 
    func startEventTap() throws {
        guard eventTap == nil else { return }
 
        let eventMask = CGEventMask(1 << CGEventType.keyDown.rawValue)
        guard let tap = CGEvent.tapCreate(
            tap: .cgSessionEventTap,
            place: .headInsertEventTap,
            options: .defaultTap,
            eventsOfInterest: eventMask,
            callback: Self.eventCallback,
            userInfo: Unmanaged.passUnretained(self).toOpaque()
        ) else {
            throw ServiceError.eventTapUnavailable
        }
 
        let source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
        CFRunLoopAddSource(CFRunLoopGetMain(), source, .commonModes)
        CGEvent.tapEnable(tap: tap, enable: true)
 
        eventTap = tap
        runLoopSource = source
        isRunning = true
    }
 
    func stop() {
        if let eventTap {
            CGEvent.tapEnable(tap: eventTap, enable: false)
        }
        if let runLoopSource {
            CFRunLoopRemoveSource(CFRunLoopGetMain(), runLoopSource, .commonModes)
        }
 
        runLoopSource = nil
        eventTap = nil
        isRunning = false
    }
 
    deinit {
        MainActor.assumeIsolated {
            stop()
        }
    }
 
    private func handle(event: CGEvent, type: CGEventType) -> Unmanaged<CGEvent>? {
        switch type {
        case .tapDisabledByTimeout, .tapDisabledByUserInput:
            guard accessibilityTrustedProvider() else {
                Task { @MainActor [weak self] in
                    self?.stop()
                }
                return Unmanaged.passUnretained(event)
            }
            if let eventTap {
                CGEvent.tapEnable(tap: eventTap, enable: true)
            }
            return Unmanaged.passUnretained(event)
        case .keyDown:
            break
        default:
            return Unmanaged.passUnretained(event)
        }
 
        guard accessibilityTrustedProvider() else {
            Task { @MainActor [weak self] in
                self?.stop()
            }
            return Unmanaged.passUnretained(event)
        }
 
        let receivedShortcut = KeyboardShortcut(
            keyCode: UInt16(event.getIntegerValueField(.keyboardEventKeycode)),
            modifiers: Self.shortcutModifiers(from: event.flags)
        )
        let decision = TriggerEventPolicy.decision(
            for: receivedShortcut,
            configuredQuickSwitch: shortcut,
            overlayVisible: overlayVisibleProvider(),
            consumeInitialQuickSwitch: consumeInitialQuickSwitch
        )
 
        switch decision {
        case .quickSwitch(let consumeEvent):
            Task { @MainActor [weak self] in
                self?.onQuickSwitch?()
            }
            return consumeEvent ? nil : Unmanaged.passUnretained(event)
        case .passThrough:
            return Unmanaged.passUnretained(event)
        }
    }
 
    private static let eventCallback: CGEventTapCallBack = { _, type, event, userInfo in
        guard let userInfo else {
            return Unmanaged.passUnretained(event)
        }
 
        let service = Unmanaged<CGEventTapTriggerService>.fromOpaque(userInfo).takeUnretainedValue()
        return MainActor.assumeIsolated {
            return service.handle(event: event, type: type)
        }
    }
 
    private static func shortcutModifiers(from flags: CGEventFlags) -> ShortcutModifiers {
        var modifiers = ShortcutModifiers()
 
        if flags.contains(.maskAlternate) {
            modifiers.insert(.option)
        }
        if flags.contains(.maskCommand) {
            modifiers.insert(.command)
        }
        if flags.contains(.maskShift) {
            modifiers.insert(.shift)
        }
        if flags.contains(.maskControl) {
            modifiers.insert(.control)
        }
 
        return modifiers
    }
}