From 856ef1fc1bed143a84c88c32812758828520de7e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 9 Jan 2026 06:32:37 -0800 Subject: [PATCH] input: change the key_is_binding to return some information --- include/ghostty.h | 11 ++++++++++- src/Surface.zig | 41 +++++++++++++++++++++++++---------------- src/apprt/embedded.zig | 7 ++++++- src/input/Binding.zig | 21 +++++++++++++++++++++ 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/include/ghostty.h b/include/ghostty.h index ff28a0cff..b884ebc08 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -102,6 +102,13 @@ typedef enum { GHOSTTY_MODS_SUPER_RIGHT = 1 << 9, } ghostty_input_mods_e; +typedef enum { + GHOSTTY_BINDING_FLAGS_CONSUMED = 1 << 0, + GHOSTTY_BINDING_FLAGS_ALL = 1 << 1, + GHOSTTY_BINDING_FLAGS_GLOBAL = 1 << 2, + GHOSTTY_BINDING_FLAGS_PERFORMABLE = 1 << 3, +} ghostty_binding_flags_e; + typedef enum { GHOSTTY_ACTION_RELEASE, GHOSTTY_ACTION_PRESS, @@ -1058,7 +1065,9 @@ void ghostty_surface_set_color_scheme(ghostty_surface_t, ghostty_input_mods_e ghostty_surface_key_translation_mods(ghostty_surface_t, ghostty_input_mods_e); bool ghostty_surface_key(ghostty_surface_t, ghostty_input_key_s); -bool ghostty_surface_key_is_binding(ghostty_surface_t, ghostty_input_key_s); +bool ghostty_surface_key_is_binding(ghostty_surface_t, + ghostty_input_key_s, + ghostty_binding_flags_e*); void ghostty_surface_text(ghostty_surface_t, const char*, uintptr_t); void ghostty_surface_preedit(ghostty_surface_t, const char*, uintptr_t); bool ghostty_surface_mouse_captured(ghostty_surface_t); diff --git a/src/Surface.zig b/src/Surface.zig index cc727826f..9998922b9 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -2579,7 +2579,7 @@ pub fn preeditCallback(self: *Surface, preedit_: ?[]const u8) !void { pub fn keyEventIsBinding( self: *Surface, event_orig: input.KeyEvent, -) bool { +) ?input.Binding.Flags { // Apply key remappings for consistency with keyCallback var event = event_orig; if (self.config.key_remaps.isRemapped(event_orig.mods)) { @@ -2587,26 +2587,35 @@ pub fn keyEventIsBinding( } switch (event.action) { - .release => return false, + .release => return null, .press, .repeat => {}, } - // If we're in a sequence, check the sequence set - if (self.keyboard.sequence_set) |set| { - return set.getEvent(event) != null; - } - - // Check active key tables (inner-most to outer-most) - const table_items = self.keyboard.table_stack.items; - for (0..table_items.len) |i| { - const rev_i: usize = table_items.len - 1 - i; - if (table_items[rev_i].set.getEvent(event) != null) { - return true; + // Look up our entry + const entry: input.Binding.Set.Entry = entry: { + // If we're in a sequence, check the sequence set + if (self.keyboard.sequence_set) |set| { + break :entry set.getEvent(event) orelse return null; } - } - // Check the root set - return self.config.keybind.set.getEvent(event) != null; + // Check active key tables (inner-most to outer-most) + const table_items = self.keyboard.table_stack.items; + for (0..table_items.len) |i| { + const rev_i: usize = table_items.len - 1 - i; + if (table_items[rev_i].set.getEvent(event)) |entry| { + break :entry entry; + } + } + + // Check the root set + break :entry self.config.keybind.set.getEvent(event) orelse return null; + }; + + // Return flags based on the + return switch (entry.value_ptr.*) { + .leader => .{}, + inline .leaf, .leaf_chained => |v| v.flags, + }; } /// Called for any key events. This handles keybindings, encoding and diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 6c1d46722..364a1bec1 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -1751,13 +1751,18 @@ pub const CAPI = struct { export fn ghostty_surface_key_is_binding( surface: *Surface, event: KeyEvent, + c_flags: ?*input.Binding.Flags.C, ) bool { const core_event = event.keyEvent().core() orelse { log.warn("error processing key event", .{}); return false; }; - return surface.core_surface.keyEventIsBinding(core_event); + const flags = surface.core_surface.keyEventIsBinding( + core_event, + ) orelse return false; + if (c_flags) |ptr| ptr.* = flags.cval(); + return true; } /// Send raw text to the terminal. This is treated like a paste diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 3197bb7d1..08475c7e1 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -45,6 +45,27 @@ pub const Flags = packed struct { /// performed. If the action can't be performed then the binding acts as /// if it doesn't exist. performable: bool = false, + + /// C type + pub const C = u8; + + /// Converts this to a C-compatible value. + /// + /// Sync with ghostty.h for enums. + pub fn cval(self: Flags) C { + const Backing = @typeInfo(Flags).@"struct".backing_integer.?; + return @as(Backing, @bitCast(self)); + } + + test "cval" { + const testing = std.testing; + try testing.expectEqual(@as(u8, 0b0001), (Flags{}).cval()); + try testing.expectEqual(@as(u8, 0b0000), (Flags{ .consumed = false }).cval()); + try testing.expectEqual(@as(u8, 0b0011), (Flags{ .all = true }).cval()); + try testing.expectEqual(@as(u8, 0b0101), (Flags{ .global = true }).cval()); + try testing.expectEqual(@as(u8, 0b1001), (Flags{ .performable = true }).cval()); + try testing.expectEqual(@as(u8, 0b1111), (Flags{ .consumed = true, .all = true, .global = true, .performable = true }).cval()); + } }; /// Full binding parser. The binding parser is implemented as an iterator