Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, @preconcurrency UNUser
private var pendingConfiguredShortcutChord: PendingConfiguredShortcutChord?
private var activeConfiguredShortcutChordPrefixForCurrentEvent: ShortcutStroke?
private var configuredShortcutChordActions: [KeyboardShortcutSettings.Action] = []
/// Modifier combinations (raw `NSEvent.ModifierFlags` values) that any app-level shortcut
/// stroke requires, including the hardcoded Ctrl+Tab pair. `nil` until the defaults observer
/// has populated it, in which case no event is skipped.
private var configuredShortcutTriggerModifierMasks: Set<UInt>?
private var ghosttyConfigObserver: NSObjectProtocol?
var ghosttyGotoSplitLeftShortcut: StoredShortcut?
var ghosttyGotoSplitRightShortcut: StoredShortcut?
Expand Down Expand Up @@ -5840,6 +5844,17 @@ final class AppDelegate: NSObject, NSApplicationDelegate, @preconcurrency UNUser
configuredShortcutChordActions = KeyboardShortcutSettings.Action.allCases.filter {
KeyboardShortcutSettings.shortcut(for: $0).hasChord
}
// Every stroke matcher first requires the event's modifiers to equal the stroke's, so a
// modifier combination outside this set cannot match any first stroke. Legacy Ctrl+Tab
// and Ctrl+Shift+Tab are not actions, so they are added explicitly.
var masks: Set<UInt> = [
NSEvent.ModifierFlags.control.rawValue,
NSEvent.ModifierFlags([.control, .shift]).rawValue,
]
for action in KeyboardShortcutSettings.Action.allCases {
masks.insert(KeyboardShortcutSettings.shortcut(for: action).firstStroke.modifierFlags.rawValue)
}
configuredShortcutTriggerModifierMasks = masks
}

private func clearConfiguredShortcutChordState() {
Expand Down Expand Up @@ -6582,6 +6597,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate, @preconcurrency UNUser
commandPaletteTargetWindow: NSWindow?,
hasFocusedAddressBarInShortcutContext: Bool
) -> Bool {
if activeConfiguredShortcutChordPrefixForCurrentEvent == nil,
!eventModifiersCanTriggerAppShortcut(event) {
return false
}

if activeConfiguredShortcutChordPrefixForCurrentEvent == nil,
armConfiguredShortcutChordIfNeeded(event: event) {
return true
Expand Down Expand Up @@ -6623,6 +6643,22 @@ final class AppDelegate: NSObject, NSApplicationDelegate, @preconcurrency UNUser
return false
}

/// Cheap pre-filter for the precedence walk: false only when the event's modifiers equal no
/// configured first stroke and no ghostty goto_split stroke, so no matcher in the walk can
/// accept it. Callers must not use it while a chord prefix is pending (second strokes are
/// not part of the mask set).
private func eventModifiersCanTriggerAppShortcut(_ event: NSEvent) -> Bool {
guard let masks = configuredShortcutTriggerModifierMasks else { return true }
let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
.subtracting([.numericPad, .function, .capsLock])
if masks.contains(flags.rawValue) { return true }
func matches(_ shortcut: StoredShortcut?) -> Bool {
shortcut?.firstStroke.modifierFlags == flags
}
return matches(ghosttyGotoSplitLeftShortcut) || matches(ghosttyGotoSplitRightShortcut)
|| matches(ghosttyGotoSplitUpShortcut) || matches(ghosttyGotoSplitDownShortcut)
}

// Exhaustive dispatch for a single KeyboardShortcutSettings.Action: returns nil when this
// action's configured shortcut doesn't match the event (or, for .focusBrowserAddressBar,
// when it matches but none of its sub-branches handled it -- see that case below), in which
Expand Down
Loading