input: move Trigger key to a union, update C API

This commit is contained in:
Mitchell Hashimoto
2024-06-02 10:09:38 -07:00
parent f63927a7c4
commit eb05606213
6 changed files with 209 additions and 134 deletions

View File

@ -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 {

View File

@ -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,

View File

@ -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,

View File

@ -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 {

View File

@ -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 = {} },
);
}

View File

@ -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 } }));
}