macos: use the new binding flags information value to trigger menu

This commit is contained in:
Mitchell Hashimoto
2026-01-09 06:58:48 -08:00
parent 856ef1fc1b
commit f34c69147a
3 changed files with 73 additions and 18 deletions

View File

@@ -100,6 +100,32 @@ extension Ghostty {
]
}
// MARK: Ghostty.Input.BindingFlags
extension Ghostty.Input {
/// `ghostty_binding_flags_e`
struct BindingFlags: OptionSet, Sendable {
let rawValue: UInt32
static let consumed = BindingFlags(rawValue: GHOSTTY_BINDING_FLAGS_CONSUMED.rawValue)
static let all = BindingFlags(rawValue: GHOSTTY_BINDING_FLAGS_ALL.rawValue)
static let global = BindingFlags(rawValue: GHOSTTY_BINDING_FLAGS_GLOBAL.rawValue)
static let performable = BindingFlags(rawValue: GHOSTTY_BINDING_FLAGS_PERFORMABLE.rawValue)
init(rawValue: UInt32) {
self.rawValue = rawValue
}
init(cFlags: ghostty_binding_flags_e) {
self.rawValue = cFlags.rawValue
}
var cFlags: ghostty_binding_flags_e {
ghostty_binding_flags_e(rawValue)
}
}
}
// MARK: Ghostty.Input.KeyEvent
extension Ghostty.Input {

View File

@@ -62,6 +62,26 @@ extension Ghostty {
}
}
/// Check if a key event matches a keybinding.
///
/// This checks whether the given key event would trigger a keybinding in the terminal.
/// If it matches, returns the binding flags indicating properties of the matched binding.
///
/// - Parameter event: The key event to check
/// - Returns: The binding flags if a binding matches, or nil if no binding matches
@MainActor
func keyIsBinding(_ event: ghostty_input_key_s) -> Input.BindingFlags? {
var flags = ghostty_binding_flags_e(0)
guard ghostty_surface_key_is_binding(surface, event, &flags) else { return nil }
return Input.BindingFlags(cFlags: flags)
}
/// See `keyIsBinding(_ event: ghostty_input_key_s)`.
@MainActor
func keyIsBinding(_ event: Input.KeyEvent) -> Input.BindingFlags? {
event.withCValue { keyIsBinding($0) }
}
/// Whether the terminal has captured mouse input.
///
/// When the mouse is captured, the terminal application is receiving mouse events

View File

@@ -1184,7 +1184,7 @@ extension Ghostty {
// We only care about key down events. It might not even be possible
// to receive any other event type here.
guard event.type == .keyDown else { return false }
// Only process events if we're focused. Some key events like C-/ macOS
// appears to send to the first view in the hierarchy rather than the
// the first responder (I don't know why). This prevents us from handling it.
@@ -1194,26 +1194,35 @@ extension Ghostty {
if (!focused) {
return false
}
// Let the menu system handle this event if we're not in a key sequence or key table.
// This allows the menu bar to flash for shortcuts like Command+V.
if keySequence.isEmpty && keyTables.isEmpty {
if let menu = NSApp.mainMenu, menu.performKeyEquivalent(with: event) {
return true
// Get information about if this is a binding.
let bindingFlags = surfaceModel.flatMap { surface in
var ghosttyEvent = event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS)
return (event.characters ?? "").withCString { ptr in
ghosttyEvent.text = ptr
return surface.keyIsBinding(ghosttyEvent)
}
}
// If the menu didn't handle it, check Ghostty bindings for custom shortcuts.
if let surface {
var ghosttyEvent = event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS)
let match = (event.characters ?? "").withCString { ptr in
ghosttyEvent.text = ptr
return ghostty_surface_key_is_binding(surface, ghosttyEvent)
}
if match {
self.keyDown(with: event)
return true
// If this is a binding then we want to perform it.
if let bindingFlags {
// Attempt to trigger a menu item for this key binding. We only do this if:
// - We're not in a key sequence or table (those are separate bindings)
// - The binding is NOT `all` (menu uses FirstResponder chain)
// - The binding is NOT `performable` (menu will always consume)
// - The binding is `consumed` (unconsumed bindings should pass through
// to the terminal, so we must not intercept them for the menu)
if keySequence.isEmpty,
keyTables.isEmpty,
bindingFlags.isDisjoint(with: [.all, .performable]),
bindingFlags.contains(.consumed) {
if let menu = NSApp.mainMenu, menu.performKeyEquivalent(with: event) {
return true
}
}
self.keyDown(with: event)
return true
}
let equivalent: String