mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-25 00:21:46 +00:00
macos: simplify menu shortcut identity
Separate menu shortcut presentation from lookup identity. Store either a normalized key equivalent or a physical keycode in a private hashable enum, allowing Swift to synthesize equality and hashing instead of maintaining parallel optional-key logic. Assign display characters directly from KeyboardShortcut and remove unused NSMenuItem and SwiftUI conversion helpers.
This commit is contained in:
@@ -103,7 +103,7 @@ private extension Ghostty.MenuShortcutManager {
|
||||
return false
|
||||
}
|
||||
|
||||
menu.keyEquivalent = key.keyEquivalent
|
||||
menu.keyEquivalent = shortcut.key.character.description
|
||||
menu.keyEquivalentModifierMask = key.modifierFlags
|
||||
// The key equivalent was already localized from the physical keycode.
|
||||
menu.allowsAutomaticKeyEquivalentLocalization = !isPhysical
|
||||
@@ -118,10 +118,14 @@ private extension Ghostty.MenuShortcutManager {
|
||||
extension Ghostty.MenuShortcutManager {
|
||||
/// Hashable key for a menu shortcut match, normalized for quick lookup.
|
||||
struct MenuShortcutKey: Hashable {
|
||||
private enum Identity: Hashable {
|
||||
case keyEquivalent(String)
|
||||
case physicalKeyCode(UInt16)
|
||||
}
|
||||
|
||||
private static let shortcutModifiers: NSEvent.ModifierFlags = [.shift, .control, .option, .command]
|
||||
|
||||
let keyEquivalent: String
|
||||
private let physicalKeyCode: UInt16?
|
||||
private let identity: Identity
|
||||
private let modifiersRawValue: UInt
|
||||
|
||||
var modifierFlags: NSEvent.ModifierFlags {
|
||||
@@ -139,18 +143,12 @@ extension Ghostty.MenuShortcutManager {
|
||||
// it's originally uppercased, then we need to add `shift` to the modifiers
|
||||
mods.insert(.shift)
|
||||
}
|
||||
self.keyEquivalent = normalized
|
||||
self.physicalKeyCode = nil
|
||||
self.identity = .keyEquivalent(normalized)
|
||||
self.modifiersRawValue = mods.rawValue
|
||||
}
|
||||
|
||||
init(
|
||||
keyEquivalent: String = "",
|
||||
physicalKeyCode: UInt16,
|
||||
modifiers: NSEvent.ModifierFlags
|
||||
) {
|
||||
self.keyEquivalent = keyEquivalent
|
||||
self.physicalKeyCode = physicalKeyCode
|
||||
init(physicalKeyCode: UInt16, modifiers: NSEvent.ModifierFlags) {
|
||||
self.identity = .physicalKeyCode(physicalKeyCode)
|
||||
self.modifiersRawValue = modifiers.intersection(Self.shortcutModifiers).rawValue
|
||||
}
|
||||
|
||||
@@ -159,55 +157,16 @@ extension Ghostty.MenuShortcutManager {
|
||||
self.init(keyEquivalent: keyEquivalent, modifiers: event.modifierFlags)
|
||||
}
|
||||
|
||||
/// Create from a `NSMenuItem`
|
||||
///
|
||||
/// - Important: This will check whether the `keyEquivalent` is uppercased by `.shift` modifier.
|
||||
init?(_ menuItem: NSMenuItem) {
|
||||
self.init(
|
||||
keyEquivalent: menuItem.keyEquivalent,
|
||||
modifiers: menuItem.keyEquivalentModifierMask,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create from a swiftUI `KeyboardShortcut`
|
||||
/// Create from a SwiftUI `KeyboardShortcut`.
|
||||
init?(_ shortcut: KeyboardShortcut, physicalKeyCode: UInt16? = nil) {
|
||||
// Ghostty configured shortcuts are already normalized
|
||||
// in `Ghostty.keyboardShortcut(for:)`, see also gh-#12039
|
||||
let keyEquivalent = shortcut.key.character.description
|
||||
let modifierMask = NSEvent.ModifierFlags(swiftUIFlags: shortcut.modifiers)
|
||||
let modifiers = NSEvent.ModifierFlags(swiftUIFlags: shortcut.modifiers)
|
||||
if let physicalKeyCode {
|
||||
self.init(
|
||||
keyEquivalent: keyEquivalent,
|
||||
physicalKeyCode: physicalKeyCode,
|
||||
modifiers: modifierMask)
|
||||
self.init(physicalKeyCode: physicalKeyCode, modifiers: modifiers)
|
||||
} else {
|
||||
self.init(keyEquivalent: keyEquivalent, modifiers: modifierMask)
|
||||
self.init(
|
||||
keyEquivalent: shortcut.key.character.description,
|
||||
modifiers: modifiers)
|
||||
}
|
||||
}
|
||||
|
||||
static func == (lhs: Self, rhs: Self) -> Bool {
|
||||
guard lhs.modifiersRawValue == rhs.modifiersRawValue else { return false }
|
||||
return switch (lhs.physicalKeyCode, rhs.physicalKeyCode) {
|
||||
case let (.some(lhs), .some(rhs)): lhs == rhs
|
||||
case (nil, nil): lhs.keyEquivalent == rhs.keyEquivalent
|
||||
default: false
|
||||
}
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(modifiersRawValue)
|
||||
hasher.combine(physicalKeyCode)
|
||||
if physicalKeyCode == nil {
|
||||
hasher.combine(keyEquivalent)
|
||||
}
|
||||
}
|
||||
|
||||
var swiftUIShortcut: KeyboardShortcut? {
|
||||
guard let character = keyEquivalent.first else { return nil }
|
||||
return KeyboardShortcut(
|
||||
KeyEquivalent(character),
|
||||
modifiers: .init(nsFlags: modifierFlags)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,6 @@ struct NormalizedMenuShortcutKeyTests {
|
||||
#expect(key == nil)
|
||||
}
|
||||
|
||||
@Test func lowercasesKeyEquivalent() {
|
||||
let key = Key(keyEquivalent: "A", modifiers: .command)
|
||||
#expect(key?.keyEquivalent == "a")
|
||||
}
|
||||
|
||||
@Test func stripsNonShortcutModifiers() {
|
||||
// .capsLock and .function should be stripped
|
||||
let key = Key(keyEquivalent: "c", modifiers: [.command, .capsLock, .function])
|
||||
@@ -75,12 +70,14 @@ struct NormalizedMenuShortcutKeyTests {
|
||||
}
|
||||
|
||||
@Test func physicalKeysUseKeyCodeIdentity() {
|
||||
let configured = Key(keyEquivalent: "`", physicalKeyCode: 0x32, modifiers: .command)
|
||||
let event = Key(physicalKeyCode: 0x32, modifiers: .command)
|
||||
let physical = Key(physicalKeyCode: 0x32, modifiers: .command)
|
||||
let same = Key(physicalKeyCode: 0x32, modifiers: .command)
|
||||
let different = Key(physicalKeyCode: 0x31, modifiers: .command)
|
||||
let unicode = Key(keyEquivalent: "`", modifiers: .command)
|
||||
|
||||
#expect(configured == event)
|
||||
#expect(configured != unicode)
|
||||
#expect(physical == same)
|
||||
#expect(physical != different)
|
||||
#expect(physical != unicode)
|
||||
}
|
||||
|
||||
@Test func differentModifiersAreNotEqual() {
|
||||
|
||||
Reference in New Issue
Block a user