···1111/// The current global-tap mode (Notepat / Ableton in the popover) survives
1212/// any focus change, but requires Accessibility and isn't App-Store-eligible.
1313final class LocalKeyCapture {
1414+ enum EndReason {
1515+ case cancelled
1616+ case resignedKey
1717+ }
1818+1419 /// Called on every keyDown the panel observes. Return `true` to consume
1520 /// (so cmd-q etc. can still fall through if false). Receives the same
1621 /// (keyCode, isDown, isRepeat, flags) shape as the global tap so the
···1823 var onKey: ((UInt16, Bool, Bool, NSEvent.ModifierFlags) -> Bool)?
1924 /// Called when the panel resigns key — capture has ended naturally
2025 /// because the user clicked another app.
2121- var onCaptureEnd: (() -> Void)?
2626+ var onCaptureEnd: ((EndReason) -> Void)?
2727+ var cancelShortcut: MenuBandShortcut?
22282329 private var panel: NSPanel?
2430 private var monitor: Any?
···5056 monitor = NSEvent.addLocalMonitorForEvents(matching: [.keyDown, .keyUp]) { [weak self] event in
5157 guard let self = self else { return event }
5258 let isDown = (event.type == .keyDown)
5959+ if isDown, self.cancelShortcut?.matches(event: event) == true {
6060+ self.disarm(reason: .cancelled)
6161+ return nil
6262+ }
5363 let consumed = self.onKey?(event.keyCode, isDown, event.isARepeat, event.modifierFlags) ?? false
5464 return consumed ? nil : event
5565 }
···6676 ) { [weak self] _ in
6777 guard let self = self else { return }
6878 let stillKeyInApp = NSApp.windows.contains { $0.isKeyWindow }
6969- if !stillKeyInApp { self.disarm() }
7979+ if !stillKeyInApp { self.disarm(reason: .resignedKey) }
7080 }
7181 }
7282 isArmed = true
···74847585 /// Tear down the panel + monitor. Called when the user clicks another
7686 /// app (panel resigns key) or explicitly when we want to drop capture.
7777- func disarm() {
8787+ func disarm(reason: EndReason = .cancelled) {
7888 guard isArmed else { return }
7989 isArmed = false
8090 if let m = monitor {
···8696 resignKeyObserver = nil
8797 }
8898 panel?.orderOut(nil)
8989- onCaptureEnd?()
9999+ onCaptureEnd?(reason)
90100 }
9110192102 private func buildPanel() {
···111121 panel = p
112122 }
113123114114- deinit { disarm() }
124124+ deinit { disarm(reason: .cancelled) }
115125}
116126117127/// `NSPanel` subclass that overrides `canBecomeKey` to return true. Without