diff --git a/src/App.swift b/src/App.swift index 12a0b99d..772e3e7b 100644 --- a/src/App.swift +++ b/src/App.swift @@ -307,13 +307,6 @@ class App: AppCenterApplication { static func showUiOrCycleSelection(_ shortcutIndex: Int, _ forceDoNothingOnRelease_: Bool) { let session = SwitcherSession.current ?? { let new = SwitcherSession() - // Read the cached value rather than querying NSWorkspace (which is a blocking IPC) - // on this hot path. AX activation notifications can be in-flight when the user invokes - // the switcher right after activating an app, so this can be momentarily stale. In that - // race, the modifier release is redelivered to the previously-foreground app instead of - // the real initial app — a spurious but benign modifier-state update for that app, and - // the destination is still shielded from receiving the release. - new.initialPid = Applications.frontmostPid SwitcherSession.current = new return new }() @@ -328,7 +321,6 @@ class App: AppCenterApplication { } session.isFirstSummon = false session.shortcutIndex = shortcutIndex - session.holdMask = ControlsTab.shortcuts[Preferences.indexToName("holdShortcut", shortcutIndex)]?.shortcut.modifierFlags ?? [] // Hide instantly so the rebuild for a different shortcut (Appearance change, layout // recalc) is invisible. `TilesPanel.show()` flips alpha back to 1 once everything is // in its final state. No-op on first summon (panel was orderOut'd with alpha=0). diff --git a/src/events/KeyboardEvents.swift b/src/events/KeyboardEvents.swift index c7d023dc..fbdb8d43 100644 --- a/src/events/KeyboardEvents.swift +++ b/src/events/KeyboardEvents.swift @@ -22,23 +22,13 @@ class KeyboardEvents { case .flagsChanged: // TODO: it would be great to shortcut matching and trigger on the background thread // it would enable us to set App.shared.isBeingUsed here, and could stop tasks on main when they check the flag - let modifiers = NSEvent.ModifierFlags(rawValue: UInt(cgEvent.flags.rawValue)) - // When the hold-shortcut mask transitions out of fully held during a session, - // redeliver the real event to the initial app's PID and absorb the system-routed - // copy so the destination app doesn't receive the release. Posting first (before - // the main-thread async that drives focus switching) gives A's run loop a head - // start over the focus change. - var absorb = false - if let session = SwitcherSession.current, let pid = session.initialPid, - !session.holdMask.isEmpty, !modifiers.isSuperset(of: session.holdMask), - let copy = cgEvent.copy() { - copy.postToPid(pid) - absorb = true - } DispatchQueue.main.async { + let modifiers = NSEvent.ModifierFlags(rawValue: UInt(cgEvent.flags.rawValue)) + // TODO: ideally, we want to absorb all modifier keys except holdShortcut + // it was pressed down before AltTab was triggered, so we should let the up event through handleKeyboardEvent(nil, nil, nil, modifiers, false) } - return absorb ? nil : Unmanaged.passUnretained(cgEvent) + return Unmanaged.passUnretained(cgEvent) case .keyDown: // Issue #5585. Esc only — absorb when AltTab is using it and a shortcut binds it. cghid is // the earliest tap point; absorbing here preempts macOS 26 Game Overlay's hook on `⌘⎋`. diff --git a/src/switcher/SwitcherSession.swift b/src/switcher/SwitcherSession.swift index 1aa02f97..9d409c90 100644 --- a/src/switcher/SwitcherSession.swift +++ b/src/switcher/SwitcherSession.swift @@ -21,13 +21,4 @@ final class SwitcherSession { var hoveredIndex: Int? var selectedTarget: String? var searchQuery: String = "" - - /// PID of the app that was frontmost when this session started. The `cgEventHandler` - /// in `KeyboardEvents` redelivers the hold-modifier release event to this PID via - /// `CGEventPostToPid`, so the initial app sees a release matching the press it saw - /// when the user invoked the switcher. - var initialPid: pid_t? - /// Modifier mask of the active hold shortcut. The trigger event is the `flagsChanged` - /// where the current modifiers no longer fully satisfy this mask. - var holdMask: NSEvent.ModifierFlags = [] }