mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
input: move Trigger key to a union, update C API
This commit is contained in:
@ -330,10 +330,20 @@ typedef struct {
|
||||
bool composing;
|
||||
} ghostty_input_key_s;
|
||||
|
||||
typedef enum {
|
||||
GHOSTTY_TRIGGER_TRANSLATED,
|
||||
GHOSTTY_TRIGGER_PHYSICAL,
|
||||
} ghostty_input_trigger_tag_e;
|
||||
|
||||
typedef union {
|
||||
ghostty_input_key_e translated;
|
||||
ghostty_input_key_e physical;
|
||||
} ghostty_input_trigger_key_u;
|
||||
|
||||
typedef struct {
|
||||
ghostty_input_key_e key;
|
||||
ghostty_input_trigger_tag_e tag;
|
||||
ghostty_input_trigger_key_u key;
|
||||
ghostty_input_mods_e mods;
|
||||
bool physical;
|
||||
} ghostty_input_trigger_s;
|
||||
|
||||
typedef enum {
|
||||
|
@ -115,7 +115,25 @@ extension Ghostty {
|
||||
guard let cfg = self.config else { return nil }
|
||||
|
||||
let trigger = ghostty_config_trigger(cfg, action, UInt(action.count))
|
||||
guard let equiv = Ghostty.keyEquivalent(key: trigger.key) else { return nil }
|
||||
let equiv: String
|
||||
switch (trigger.tag) {
|
||||
case GHOSTTY_TRIGGER_TRANSLATED:
|
||||
if let v = Ghostty.keyEquivalent(key: trigger.key.translated) {
|
||||
equiv = v
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
case GHOSTTY_TRIGGER_PHYSICAL:
|
||||
if let v = Ghostty.keyEquivalent(key: trigger.key.physical) {
|
||||
equiv = v
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
return KeyEquivalent(
|
||||
key: equiv,
|
||||
|
@ -1188,7 +1188,7 @@ pub fn keyCallback(
|
||||
const binding_mods = event.mods.binding();
|
||||
var trigger: input.Binding.Trigger = .{
|
||||
.mods = binding_mods,
|
||||
.key = event.key,
|
||||
.key = .{ .translated = event.key },
|
||||
};
|
||||
|
||||
const set = self.config.keybind.set;
|
||||
@ -1198,8 +1198,7 @@ pub fn keyCallback(
|
||||
set.getConsumed(trigger),
|
||||
};
|
||||
|
||||
trigger.key = event.physical_key;
|
||||
trigger.physical = true;
|
||||
trigger.key = .{ .physical = event.physical_key };
|
||||
if (set.get(trigger)) |v| break :action .{
|
||||
v,
|
||||
trigger,
|
||||
|
@ -96,7 +96,7 @@ export fn ghostty_config_trigger(
|
||||
self: *Config,
|
||||
str: [*]const u8,
|
||||
len: usize,
|
||||
) inputpkg.Binding.Trigger {
|
||||
) inputpkg.Binding.Trigger.C {
|
||||
return config_trigger_(self, str[0..len]) catch |err| err: {
|
||||
log.err("error finding trigger err={}", .{err});
|
||||
break :err .{};
|
||||
@ -106,9 +106,10 @@ export fn ghostty_config_trigger(
|
||||
fn config_trigger_(
|
||||
self: *Config,
|
||||
str: []const u8,
|
||||
) !inputpkg.Binding.Trigger {
|
||||
) !inputpkg.Binding.Trigger.C {
|
||||
const action = try inputpkg.Binding.Action.parse(str);
|
||||
return self.keybind.set.getTrigger(action) orelse .{};
|
||||
const trigger: inputpkg.Binding.Trigger = self.keybind.set.getTrigger(action) orelse .{};
|
||||
return trigger.cval();
|
||||
}
|
||||
|
||||
export fn ghostty_config_errors_count(self: *Config) u32 {
|
||||
|
@ -1100,12 +1100,12 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
||||
// keybinds for opening and reloading config
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .comma, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) },
|
||||
.{ .key = .{ .translated = .comma }, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) },
|
||||
.{ .reload_config = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .comma, .mods = inputpkg.ctrlOrSuper(.{}) },
|
||||
.{ .key = .{ .translated = .comma }, .mods = inputpkg.ctrlOrSuper(.{}) },
|
||||
.{ .open_config = {} },
|
||||
);
|
||||
|
||||
@ -1119,12 +1119,12 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
||||
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .c, .mods = mods },
|
||||
.{ .key = .{ .translated = .c }, .mods = mods },
|
||||
.{ .copy_to_clipboard = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .v, .mods = mods },
|
||||
.{ .key = .{ .translated = .v }, .mods = mods },
|
||||
.{ .paste_from_clipboard = {} },
|
||||
);
|
||||
}
|
||||
@ -1132,29 +1132,29 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
||||
// Fonts
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .equal, .mods = inputpkg.ctrlOrSuper(.{}) },
|
||||
.{ .key = .{ .translated = .equal }, .mods = inputpkg.ctrlOrSuper(.{}) },
|
||||
.{ .increase_font_size = 1 },
|
||||
);
|
||||
// Increase font size mapping for keyboards with dedicated plus keys (like german)
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .plus, .mods = inputpkg.ctrlOrSuper(.{}) },
|
||||
.{ .key = .{ .translated = .plus }, .mods = inputpkg.ctrlOrSuper(.{}) },
|
||||
.{ .increase_font_size = 1 },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .minus, .mods = inputpkg.ctrlOrSuper(.{}) },
|
||||
.{ .key = .{ .translated = .minus }, .mods = inputpkg.ctrlOrSuper(.{}) },
|
||||
.{ .decrease_font_size = 1 },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .zero, .mods = inputpkg.ctrlOrSuper(.{}) },
|
||||
.{ .key = .{ .translated = .zero }, .mods = inputpkg.ctrlOrSuper(.{}) },
|
||||
.{ .reset_font_size = {} },
|
||||
);
|
||||
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .j, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) },
|
||||
.{ .key = .{ .translated = .j }, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) },
|
||||
.{ .write_scrollback_file = {} },
|
||||
);
|
||||
|
||||
@ -1162,169 +1162,169 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
||||
if (comptime !builtin.target.isDarwin()) {
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .n, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .n }, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .new_window = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .w, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .w }, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .close_surface = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .q, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .q }, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .quit = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .f4, .mods = .{ .alt = true } },
|
||||
.{ .key = .{ .translated = .f4 }, .mods = .{ .alt = true } },
|
||||
.{ .close_window = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .t, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .t }, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .new_tab = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .left, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .left }, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .previous_tab = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .right, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .right }, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .next_tab = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .page_up, .mods = .{ .ctrl = true } },
|
||||
.{ .key = .{ .translated = .page_up }, .mods = .{ .ctrl = true } },
|
||||
.{ .previous_tab = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .page_down, .mods = .{ .ctrl = true } },
|
||||
.{ .key = .{ .translated = .page_down }, .mods = .{ .ctrl = true } },
|
||||
.{ .next_tab = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .o, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .o }, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .new_split = .right },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .e, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .e }, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .new_split = .down },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .left_bracket, .mods = .{ .ctrl = true, .super = true } },
|
||||
.{ .key = .{ .translated = .left_bracket }, .mods = .{ .ctrl = true, .super = true } },
|
||||
.{ .goto_split = .previous },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .right_bracket, .mods = .{ .ctrl = true, .super = true } },
|
||||
.{ .key = .{ .translated = .right_bracket }, .mods = .{ .ctrl = true, .super = true } },
|
||||
.{ .goto_split = .next },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .up, .mods = .{ .ctrl = true, .alt = true } },
|
||||
.{ .key = .{ .translated = .up }, .mods = .{ .ctrl = true, .alt = true } },
|
||||
.{ .goto_split = .top },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .down, .mods = .{ .ctrl = true, .alt = true } },
|
||||
.{ .key = .{ .translated = .down }, .mods = .{ .ctrl = true, .alt = true } },
|
||||
.{ .goto_split = .bottom },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .left, .mods = .{ .ctrl = true, .alt = true } },
|
||||
.{ .key = .{ .translated = .left }, .mods = .{ .ctrl = true, .alt = true } },
|
||||
.{ .goto_split = .left },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .right, .mods = .{ .ctrl = true, .alt = true } },
|
||||
.{ .key = .{ .translated = .right }, .mods = .{ .ctrl = true, .alt = true } },
|
||||
.{ .goto_split = .right },
|
||||
);
|
||||
|
||||
// Resizing splits
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .up, .mods = .{ .super = true, .ctrl = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .up }, .mods = .{ .super = true, .ctrl = true, .shift = true } },
|
||||
.{ .resize_split = .{ .up, 10 } },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .down, .mods = .{ .super = true, .ctrl = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .down }, .mods = .{ .super = true, .ctrl = true, .shift = true } },
|
||||
.{ .resize_split = .{ .down, 10 } },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .left, .mods = .{ .super = true, .ctrl = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .left }, .mods = .{ .super = true, .ctrl = true, .shift = true } },
|
||||
.{ .resize_split = .{ .left, 10 } },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .right, .mods = .{ .super = true, .ctrl = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .right }, .mods = .{ .super = true, .ctrl = true, .shift = true } },
|
||||
.{ .resize_split = .{ .right, 10 } },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .equal, .mods = .{ .super = true, .ctrl = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .equal }, .mods = .{ .super = true, .ctrl = true, .shift = true } },
|
||||
.{ .equalize_splits = {} },
|
||||
);
|
||||
|
||||
// Viewport scrolling
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .home, .mods = .{ .shift = true } },
|
||||
.{ .key = .{ .translated = .home }, .mods = .{ .shift = true } },
|
||||
.{ .scroll_to_top = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .end, .mods = .{ .shift = true } },
|
||||
.{ .key = .{ .translated = .end }, .mods = .{ .shift = true } },
|
||||
.{ .scroll_to_bottom = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .page_up, .mods = .{ .shift = true } },
|
||||
.{ .key = .{ .translated = .page_up }, .mods = .{ .shift = true } },
|
||||
.{ .scroll_page_up = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .page_down, .mods = .{ .shift = true } },
|
||||
.{ .key = .{ .translated = .page_down }, .mods = .{ .shift = true } },
|
||||
.{ .scroll_page_down = {} },
|
||||
);
|
||||
|
||||
// Semantic prompts
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .page_up, .mods = .{ .shift = true, .ctrl = true } },
|
||||
.{ .key = .{ .translated = .page_up }, .mods = .{ .shift = true, .ctrl = true } },
|
||||
.{ .jump_to_prompt = -1 },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .page_down, .mods = .{ .shift = true, .ctrl = true } },
|
||||
.{ .key = .{ .translated = .page_down }, .mods = .{ .shift = true, .ctrl = true } },
|
||||
.{ .jump_to_prompt = 1 },
|
||||
);
|
||||
|
||||
// Inspector, matching Chromium
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .i, .mods = .{ .shift = true, .ctrl = true } },
|
||||
.{ .key = .{ .translated = .i }, .mods = .{ .shift = true, .ctrl = true } },
|
||||
.{ .inspector = .toggle },
|
||||
);
|
||||
|
||||
// Terminal
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .a, .mods = .{ .shift = true, .ctrl = true } },
|
||||
.{ .key = .{ .translated = .a }, .mods = .{ .shift = true, .ctrl = true } },
|
||||
.{ .select_all = {} },
|
||||
);
|
||||
|
||||
// Selection clipboard paste
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .insert, .mods = .{ .shift = true } },
|
||||
.{ .key = .{ .translated = .insert }, .mods = .{ .shift = true } },
|
||||
.{ .paste_from_selection = {} },
|
||||
);
|
||||
}
|
||||
@ -1344,15 +1344,17 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{
|
||||
.key = @enumFromInt(i),
|
||||
.mods = mods,
|
||||
|
||||
// On macOS, we use the physical key for tab changing so
|
||||
// that this works across all keyboard layouts. This may
|
||||
// want to be true on other platforms as well but this
|
||||
// is definitely true on macOS so we just do it here for
|
||||
// now (#817)
|
||||
.physical = builtin.target.isDarwin(),
|
||||
.key = if (comptime builtin.target.isDarwin())
|
||||
.{ .physical = @enumFromInt(i) }
|
||||
else
|
||||
.{ .translated = @enumFromInt(i) },
|
||||
|
||||
.mods = mods,
|
||||
},
|
||||
.{ .goto_tab = (i - start) + 1 },
|
||||
);
|
||||
@ -1362,14 +1364,14 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
||||
// Toggle fullscreen
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .enter, .mods = inputpkg.ctrlOrSuper(.{}) },
|
||||
.{ .key = .{ .translated = .enter }, .mods = inputpkg.ctrlOrSuper(.{}) },
|
||||
.{ .toggle_fullscreen = {} },
|
||||
);
|
||||
|
||||
// Toggle zoom a split
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .enter, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) },
|
||||
.{ .key = .{ .translated = .enter }, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) },
|
||||
.{ .toggle_split_zoom = {} },
|
||||
);
|
||||
|
||||
@ -1377,167 +1379,167 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
||||
if (comptime builtin.target.isDarwin()) {
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .q, .mods = .{ .super = true } },
|
||||
.{ .key = .{ .translated = .q }, .mods = .{ .super = true } },
|
||||
.{ .quit = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .k, .mods = .{ .super = true } },
|
||||
.{ .key = .{ .translated = .k }, .mods = .{ .super = true } },
|
||||
.{ .clear_screen = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .a, .mods = .{ .super = true } },
|
||||
.{ .key = .{ .translated = .a }, .mods = .{ .super = true } },
|
||||
.{ .select_all = {} },
|
||||
);
|
||||
|
||||
// Viewport scrolling
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .home, .mods = .{ .super = true } },
|
||||
.{ .key = .{ .translated = .home }, .mods = .{ .super = true } },
|
||||
.{ .scroll_to_top = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .end, .mods = .{ .super = true } },
|
||||
.{ .key = .{ .translated = .end }, .mods = .{ .super = true } },
|
||||
.{ .scroll_to_bottom = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .page_up, .mods = .{ .super = true } },
|
||||
.{ .key = .{ .translated = .page_up }, .mods = .{ .super = true } },
|
||||
.{ .scroll_page_up = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .page_down, .mods = .{ .super = true } },
|
||||
.{ .key = .{ .translated = .page_down }, .mods = .{ .super = true } },
|
||||
.{ .scroll_page_down = {} },
|
||||
);
|
||||
|
||||
// Semantic prompts
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .up, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .up }, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .jump_to_prompt = -1 },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .down, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .down }, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .jump_to_prompt = 1 },
|
||||
);
|
||||
|
||||
// Mac windowing
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .n, .mods = .{ .super = true } },
|
||||
.{ .key = .{ .translated = .n }, .mods = .{ .super = true } },
|
||||
.{ .new_window = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .w, .mods = .{ .super = true } },
|
||||
.{ .key = .{ .translated = .w }, .mods = .{ .super = true } },
|
||||
.{ .close_surface = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .w, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .w }, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .close_window = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .w, .mods = .{ .super = true, .shift = true, .alt = true } },
|
||||
.{ .key = .{ .translated = .w }, .mods = .{ .super = true, .shift = true, .alt = true } },
|
||||
.{ .close_all_windows = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .t, .mods = .{ .super = true } },
|
||||
.{ .key = .{ .translated = .t }, .mods = .{ .super = true } },
|
||||
.{ .new_tab = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .left_bracket, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .left_bracket }, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .previous_tab = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .right_bracket, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .right_bracket }, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .next_tab = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .d, .mods = .{ .super = true } },
|
||||
.{ .key = .{ .translated = .d }, .mods = .{ .super = true } },
|
||||
.{ .new_split = .right },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .d, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .key = .{ .translated = .d }, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .new_split = .down },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .left_bracket, .mods = .{ .super = true } },
|
||||
.{ .key = .{ .translated = .left_bracket }, .mods = .{ .super = true } },
|
||||
.{ .goto_split = .previous },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .right_bracket, .mods = .{ .super = true } },
|
||||
.{ .key = .{ .translated = .right_bracket }, .mods = .{ .super = true } },
|
||||
.{ .goto_split = .next },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .up, .mods = .{ .super = true, .alt = true } },
|
||||
.{ .key = .{ .translated = .up }, .mods = .{ .super = true, .alt = true } },
|
||||
.{ .goto_split = .top },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .down, .mods = .{ .super = true, .alt = true } },
|
||||
.{ .key = .{ .translated = .down }, .mods = .{ .super = true, .alt = true } },
|
||||
.{ .goto_split = .bottom },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .left, .mods = .{ .super = true, .alt = true } },
|
||||
.{ .key = .{ .translated = .left }, .mods = .{ .super = true, .alt = true } },
|
||||
.{ .goto_split = .left },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .right, .mods = .{ .super = true, .alt = true } },
|
||||
.{ .key = .{ .translated = .right }, .mods = .{ .super = true, .alt = true } },
|
||||
.{ .goto_split = .right },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .up, .mods = .{ .super = true, .ctrl = true } },
|
||||
.{ .key = .{ .translated = .up }, .mods = .{ .super = true, .ctrl = true } },
|
||||
.{ .resize_split = .{ .up, 10 } },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .down, .mods = .{ .super = true, .ctrl = true } },
|
||||
.{ .key = .{ .translated = .down }, .mods = .{ .super = true, .ctrl = true } },
|
||||
.{ .resize_split = .{ .down, 10 } },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .left, .mods = .{ .super = true, .ctrl = true } },
|
||||
.{ .key = .{ .translated = .left }, .mods = .{ .super = true, .ctrl = true } },
|
||||
.{ .resize_split = .{ .left, 10 } },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .right, .mods = .{ .super = true, .ctrl = true } },
|
||||
.{ .key = .{ .translated = .right }, .mods = .{ .super = true, .ctrl = true } },
|
||||
.{ .resize_split = .{ .right, 10 } },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .equal, .mods = .{ .shift = true, .alt = true } },
|
||||
.{ .key = .{ .translated = .equal }, .mods = .{ .shift = true, .alt = true } },
|
||||
.{ .equalize_splits = {} },
|
||||
);
|
||||
|
||||
// Inspector, matching Chromium
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .i, .mods = .{ .alt = true, .super = true } },
|
||||
.{ .key = .{ .translated = .i }, .mods = .{ .alt = true, .super = true } },
|
||||
.{ .inspector = .toggle },
|
||||
);
|
||||
|
||||
// Alternate keybind, common to Mac programs
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .f, .mods = .{ .super = true, .ctrl = true } },
|
||||
.{ .key = .{ .translated = .f }, .mods = .{ .super = true, .ctrl = true } },
|
||||
.{ .toggle_fullscreen = {} },
|
||||
);
|
||||
}
|
||||
|
@ -79,11 +79,9 @@ pub fn parse(raw_input: []const u8) !Binding {
|
||||
}
|
||||
|
||||
// If the key starts with "physical" then this is an physical key.
|
||||
const physical = "physical:";
|
||||
const key_part = if (std.mem.startsWith(u8, part, physical)) key_part: {
|
||||
result.physical = true;
|
||||
break :key_part part[physical.len..];
|
||||
} else part;
|
||||
const physical_prefix = "physical:";
|
||||
const physical = std.mem.startsWith(u8, part, physical_prefix);
|
||||
const key_part = if (physical) part[physical_prefix.len..] else part;
|
||||
|
||||
// Check if its a key
|
||||
const keysInfo = @typeInfo(key.Key).Enum;
|
||||
@ -91,9 +89,17 @@ pub fn parse(raw_input: []const u8) !Binding {
|
||||
if (!std.mem.eql(u8, field.name, "invalid")) {
|
||||
if (std.mem.eql(u8, key_part, field.name)) {
|
||||
// Repeat not allowed
|
||||
if (result.key != .invalid) return Error.InvalidFormat;
|
||||
if (result.key != .translated or
|
||||
result.key.translated != .invalid)
|
||||
{
|
||||
return Error.InvalidFormat;
|
||||
}
|
||||
|
||||
result.key = @field(key.Key, field.name);
|
||||
const keyval = @field(key.Key, field.name);
|
||||
result.key = if (physical)
|
||||
.{ .physical = keyval }
|
||||
else
|
||||
.{ .translated = keyval };
|
||||
continue :loop;
|
||||
}
|
||||
}
|
||||
@ -499,28 +505,64 @@ pub const Key = enum(c_int) {
|
||||
/// This is an extern struct because this is also used in the C API.
|
||||
///
|
||||
/// This must be kept in sync with include/ghostty.h ghostty_input_trigger_s
|
||||
pub const Trigger = extern struct {
|
||||
pub const Trigger = struct {
|
||||
/// The key that has to be pressed for a binding to take action.
|
||||
key: key.Key = .invalid,
|
||||
key: Trigger.Key = .{ .translated = .invalid },
|
||||
|
||||
/// The key modifiers that must be active for this to match.
|
||||
mods: key.Mods = .{},
|
||||
|
||||
/// key is the "physical" version. This is the same as mapped for
|
||||
/// standard US keyboard layouts. For non-US keyboard layouts, this
|
||||
/// is used to bind to a physical key location rather than a translated
|
||||
/// key.
|
||||
physical: bool = false,
|
||||
pub const Key = union(C.Tag) {
|
||||
/// key is the translated version of a key. This is the key that
|
||||
/// a logical keyboard layout at the OS level would translate the
|
||||
/// physical key to. For example if you use a US hardware keyboard
|
||||
/// but have a Dvorak layout, the key would be the Dvorak key.
|
||||
translated: key.Key,
|
||||
|
||||
/// key is the "physical" version. This is the same as mapped for
|
||||
/// standard US keyboard layouts. For non-US keyboard layouts, this
|
||||
/// is used to bind to a physical key location rather than a translated
|
||||
/// key.
|
||||
physical: key.Key,
|
||||
};
|
||||
|
||||
/// The extern struct used for triggers in the C API.
|
||||
pub const C = extern struct {
|
||||
tag: Tag = .translated,
|
||||
key: C.Key = .{ .translated = .invalid },
|
||||
mods: key.Mods = .{},
|
||||
|
||||
pub const Tag = enum(c_int) {
|
||||
translated,
|
||||
physical,
|
||||
};
|
||||
|
||||
pub const Key = extern union {
|
||||
translated: key.Key,
|
||||
physical: key.Key,
|
||||
};
|
||||
};
|
||||
|
||||
/// Returns a hash code that can be used to uniquely identify this trigger.
|
||||
pub fn hash(self: Trigger) u64 {
|
||||
var hasher = std.hash.Wyhash.init(0);
|
||||
std.hash.autoHash(&hasher, self.key);
|
||||
std.hash.autoHash(&hasher, self.mods.binding());
|
||||
std.hash.autoHash(&hasher, self.physical);
|
||||
return hasher.final();
|
||||
}
|
||||
|
||||
/// Convert the trigger to a C API compatible trigger.
|
||||
pub fn cval(self: Trigger) C {
|
||||
return .{
|
||||
.tag = self.key,
|
||||
.key = switch (self.key) {
|
||||
.translated => |v| .{ .translated = v },
|
||||
.physical => |v| .{ .physical = v },
|
||||
},
|
||||
.mods = self.mods,
|
||||
};
|
||||
}
|
||||
|
||||
/// Format implementation for fmt package.
|
||||
pub fn format(
|
||||
self: Trigger,
|
||||
@ -538,8 +580,10 @@ pub const Trigger = extern struct {
|
||||
if (self.mods.shift) try writer.writeAll("shift+");
|
||||
|
||||
// Key
|
||||
if (self.physical) try writer.writeAll("physical:");
|
||||
try writer.print("{s}", .{@tagName(self.key)});
|
||||
switch (self.key) {
|
||||
.translated => |k| try writer.print("{s}", .{@tagName(k)}),
|
||||
.physical => |k| try writer.print("physical:{s}", .{@tagName(k)}),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -720,7 +764,7 @@ test "parse: triggers" {
|
||||
// single character
|
||||
try testing.expectEqual(
|
||||
Binding{
|
||||
.trigger = .{ .key = .a },
|
||||
.trigger = .{ .key = .{ .translated = .a } },
|
||||
.action = .{ .ignore = {} },
|
||||
},
|
||||
try parse("a=ignore"),
|
||||
@ -730,14 +774,14 @@ test "parse: triggers" {
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .shift = true },
|
||||
.key = .a,
|
||||
.key = .{ .translated = .a },
|
||||
},
|
||||
.action = .{ .ignore = {} },
|
||||
}, try parse("shift+a=ignore"));
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .ctrl = true },
|
||||
.key = .a,
|
||||
.key = .{ .translated = .a },
|
||||
},
|
||||
.action = .{ .ignore = {} },
|
||||
}, try parse("ctrl+a=ignore"));
|
||||
@ -746,7 +790,7 @@ test "parse: triggers" {
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .shift = true, .ctrl = true },
|
||||
.key = .a,
|
||||
.key = .{ .translated = .a },
|
||||
},
|
||||
.action = .{ .ignore = {} },
|
||||
}, try parse("shift+ctrl+a=ignore"));
|
||||
@ -755,7 +799,7 @@ test "parse: triggers" {
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .shift = true },
|
||||
.key = .a,
|
||||
.key = .{ .translated = .a },
|
||||
},
|
||||
.action = .{ .ignore = {} },
|
||||
}, try parse("a+shift=ignore"));
|
||||
@ -764,8 +808,7 @@ test "parse: triggers" {
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .shift = true },
|
||||
.key = .a,
|
||||
.physical = true,
|
||||
.key = .{ .physical = .a },
|
||||
},
|
||||
.action = .{ .ignore = {} },
|
||||
}, try parse("shift+physical:a=ignore"));
|
||||
@ -774,7 +817,7 @@ test "parse: triggers" {
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .shift = true },
|
||||
.key = .a,
|
||||
.key = .{ .translated = .a },
|
||||
},
|
||||
.action = .{ .ignore = {} },
|
||||
.consumed = false,
|
||||
@ -784,8 +827,7 @@ test "parse: triggers" {
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .shift = true },
|
||||
.key = .a,
|
||||
.physical = true,
|
||||
.key = .{ .physical = .a },
|
||||
},
|
||||
.action = .{ .ignore = {} },
|
||||
.consumed = false,
|
||||
@ -807,14 +849,14 @@ test "parse: modifier aliases" {
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .super = true },
|
||||
.key = .a,
|
||||
.key = .{ .translated = .a },
|
||||
},
|
||||
.action = .{ .ignore = {} },
|
||||
}, try parse("cmd+a=ignore"));
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .super = true },
|
||||
.key = .a,
|
||||
.key = .{ .translated = .a },
|
||||
},
|
||||
.action = .{ .ignore = {} },
|
||||
}, try parse("command+a=ignore"));
|
||||
@ -822,14 +864,14 @@ test "parse: modifier aliases" {
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .alt = true },
|
||||
.key = .a,
|
||||
.key = .{ .translated = .a },
|
||||
},
|
||||
.action = .{ .ignore = {} },
|
||||
}, try parse("opt+a=ignore"));
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .alt = true },
|
||||
.key = .a,
|
||||
.key = .{ .translated = .a },
|
||||
},
|
||||
.action = .{ .ignore = {} },
|
||||
}, try parse("option+a=ignore"));
|
||||
@ -837,7 +879,7 @@ test "parse: modifier aliases" {
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .ctrl = true },
|
||||
.key = .a,
|
||||
.key = .{ .translated = .a },
|
||||
},
|
||||
.action = .{ .ignore = {} },
|
||||
}, try parse("control+a=ignore"));
|
||||
@ -855,7 +897,10 @@ test "parse: action no parameters" {
|
||||
|
||||
// no parameters
|
||||
try testing.expectEqual(
|
||||
Binding{ .trigger = .{ .key = .a }, .action = .{ .ignore = {} } },
|
||||
Binding{
|
||||
.trigger = .{ .key = .{ .translated = .a } },
|
||||
.action = .{ .ignore = {} },
|
||||
},
|
||||
try parse("a=ignore"),
|
||||
);
|
||||
try testing.expectError(Error.InvalidFormat, parse("a=ignore:A"));
|
||||
@ -949,24 +994,24 @@ test "set: maintains reverse mapping" {
|
||||
var s: Set = .{};
|
||||
defer s.deinit(alloc);
|
||||
|
||||
try s.put(alloc, .{ .key = .a }, .{ .new_window = {} });
|
||||
try s.put(alloc, .{ .key = .{ .translated = .a } }, .{ .new_window = {} });
|
||||
{
|
||||
const trigger = s.getTrigger(.{ .new_window = {} }).?;
|
||||
try testing.expect(trigger.key == .a);
|
||||
try testing.expect(trigger.key.translated == .a);
|
||||
}
|
||||
|
||||
// should be most recent
|
||||
try s.put(alloc, .{ .key = .b }, .{ .new_window = {} });
|
||||
try s.put(alloc, .{ .key = .{ .translated = .b } }, .{ .new_window = {} });
|
||||
{
|
||||
const trigger = s.getTrigger(.{ .new_window = {} }).?;
|
||||
try testing.expect(trigger.key == .b);
|
||||
try testing.expect(trigger.key.translated == .b);
|
||||
}
|
||||
|
||||
// removal should replace
|
||||
s.remove(.{ .key = .b });
|
||||
s.remove(.{ .key = .{ .translated = .b } });
|
||||
{
|
||||
const trigger = s.getTrigger(.{ .new_window = {} }).?;
|
||||
try testing.expect(trigger.key == .a);
|
||||
try testing.expect(trigger.key.translated == .a);
|
||||
}
|
||||
}
|
||||
|
||||
@ -977,14 +1022,14 @@ test "set: overriding a mapping updates reverse" {
|
||||
var s: Set = .{};
|
||||
defer s.deinit(alloc);
|
||||
|
||||
try s.put(alloc, .{ .key = .a }, .{ .new_window = {} });
|
||||
try s.put(alloc, .{ .key = .{ .translated = .a } }, .{ .new_window = {} });
|
||||
{
|
||||
const trigger = s.getTrigger(.{ .new_window = {} }).?;
|
||||
try testing.expect(trigger.key == .a);
|
||||
try testing.expect(trigger.key.translated == .a);
|
||||
}
|
||||
|
||||
// should be most recent
|
||||
try s.put(alloc, .{ .key = .a }, .{ .new_tab = {} });
|
||||
try s.put(alloc, .{ .key = .{ .translated = .a } }, .{ .new_tab = {} });
|
||||
{
|
||||
const trigger = s.getTrigger(.{ .new_window = {} });
|
||||
try testing.expect(trigger == null);
|
||||
@ -998,12 +1043,12 @@ test "set: consumed state" {
|
||||
var s: Set = .{};
|
||||
defer s.deinit(alloc);
|
||||
|
||||
try s.put(alloc, .{ .key = .a }, .{ .new_window = {} });
|
||||
try testing.expect(s.getConsumed(.{ .key = .a }));
|
||||
try s.put(alloc, .{ .key = .{ .translated = .a } }, .{ .new_window = {} });
|
||||
try testing.expect(s.getConsumed(.{ .key = .{ .translated = .a } }));
|
||||
|
||||
try s.putUnconsumed(alloc, .{ .key = .a }, .{ .new_window = {} });
|
||||
try testing.expect(!s.getConsumed(.{ .key = .a }));
|
||||
try s.putUnconsumed(alloc, .{ .key = .{ .translated = .a } }, .{ .new_window = {} });
|
||||
try testing.expect(!s.getConsumed(.{ .key = .{ .translated = .a } }));
|
||||
|
||||
try s.put(alloc, .{ .key = .a }, .{ .new_window = {} });
|
||||
try testing.expect(s.getConsumed(.{ .key = .a }));
|
||||
try s.put(alloc, .{ .key = .{ .translated = .a } }, .{ .new_window = {} });
|
||||
try testing.expect(s.getConsumed(.{ .key = .{ .translated = .a } }));
|
||||
}
|
||||
|
Reference in New Issue
Block a user