diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/input/key.zig | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (diff) | |
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/input/key.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/input/key.zig | 878 |
1 files changed, 878 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/input/key.zig b/vendor/libghostty-vt/src/input/key.zig new file mode 100644 index 00000000..9c04f01e --- /dev/null +++ b/vendor/libghostty-vt/src/input/key.zig @@ -0,0 +1,878 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const Allocator = std.mem.Allocator; +const cimgui = @import("dcimgui"); + +pub const Mods = @import("key_mods.zig").Mods; + +/// A generic key input event. This is the information that is necessary +/// regardless of apprt in order to generate the proper terminal +/// control sequences for a given key press. +/// +/// Some apprts may not be able to provide all of this information, such +/// as GLFW. In this case, the apprt should provide as much information +/// as it can and it should be expected that the terminal behavior +/// will not be totally correct. +pub const KeyEvent = struct { + /// The action: press, release, etc. + action: Action = .press, + + /// The keycode of the physical key that was pressed. This is agnostic + /// to the layout. Layout-dependent matching can only be done via the + /// UTF-8 or unshifted codepoint. + key: Key = .unidentified, + + /// Mods are the modifiers that are pressed. + mods: Mods = .{}, + + /// The mods that were consumed in order to generate the text + /// in utf8. This has the mods set that were consumed, so to + /// get the set of mods that are effective you must negate + /// mods with this. + /// + /// This field is meaningless if utf8 is empty. + consumed_mods: Mods = .{}, + + /// Composing is true when this key event is part of a dead key + /// composition sequence and we're in the middle of it. + composing: bool = false, + + /// The utf8 sequence that was generated by this key event. + /// This will be an empty string if there is no text generated. + /// If composing is true and this is non-empty, this is preedit + /// text. + utf8: []const u8 = "", + + /// The codepoint for this key when it is unshifted. For example, + /// shift+a is "A" in UTF-8 but unshifted would provide 'a'. + unshifted_codepoint: u21 = 0, + + /// Returns the effective modifiers for this event. The effective + /// modifiers are the mods that should be considered for keybindings. + pub fn effectiveMods(self: KeyEvent) Mods { + if (self.utf8.len == 0) return self.mods; + return self.mods.unset(self.consumed_mods); + } + + /// Returns a unique hash for this key event to be used for tracking + /// uniquess specifically with bindings. This omits fields that are + /// irrelevant for bindings. + pub fn bindingHash(self: KeyEvent) u64 { + var hasher = std.hash.Wyhash.init(0); + + // These are all the fields that are explicitly part of Trigger. + std.hash.autoHash(&hasher, self.key); + std.hash.autoHash(&hasher, self.unshifted_codepoint); + std.hash.autoHash(&hasher, self.mods.binding()); + + // Notes on unmapped things and why: + // + // - action: we don't have action-specific bindings right now + // AND we want to know if a key resulted in a binding regardless + // of action because a press should also ignore a release and so on. + // + // We can add to this if there is other confusion. + + return hasher.final(); + } +}; + +/// The action associated with an input event. This is backed by a c_int +/// so that we can use the enum as-is for our embedding API. +/// +/// IMPORTANT: Any changes here update include/ghostty.h +pub const Action = enum(c_int) { + release, + press, + repeat, +}; + +/// The set of key codes that Ghostty is aware of. These represent +/// physical keys on the keyboard. The logical key (or key string) +/// is the string that is generated by the key event and that is up +/// to the apprt to provide. +/// +/// Note that these are layout-independent. For example, the "a" +/// key on a US keyboard is the same as the "ф" key on a Russian +/// keyboard, but both will report the "a" enum value in the key +/// event. These values are based on the W3C standard. See: +/// https://www.w3.org/TR/uievents-code +/// +/// Layout-dependent strings are provided in the KeyEvent struct as +/// UTF-8 and are produced by the associated apprt. Ghostty core has +/// no mechanism to map input events to strings without the apprt. +/// +/// IMPORTANT: Any changes here update include/ghostty.h ghostty_input_key_e +pub const Key = enum(c_int) { + unidentified, + + // "Writing System Keys" § 3.1.1 + backquote, + backslash, + bracket_left, + bracket_right, + comma, + digit_0, + digit_1, + digit_2, + digit_3, + digit_4, + digit_5, + digit_6, + digit_7, + digit_8, + digit_9, + equal, + intl_backslash, + intl_ro, + intl_yen, + key_a, + key_b, + key_c, + key_d, + key_e, + key_f, + key_g, + key_h, + key_i, + key_j, + key_k, + key_l, + key_m, + key_n, + key_o, + key_p, + key_q, + key_r, + key_s, + key_t, + key_u, + key_v, + key_w, + key_x, + key_y, + key_z, + minus, + period, + quote, + semicolon, + slash, + + // "Functional Keys" § 3.1.2 + alt_left, + alt_right, + backspace, + caps_lock, + context_menu, + control_left, + control_right, + enter, + meta_left, + meta_right, + shift_left, + shift_right, + space, + tab, + convert, + kana_mode, + non_convert, + + // "Control Pad Section" § 3.2 + delete, + end, + help, + home, + insert, + page_down, + page_up, + + // "Arrow Pad Section" § 3.3 + arrow_down, + arrow_left, + arrow_right, + arrow_up, + + // "Numpad Section" § 3.4 + num_lock, + numpad_0, + numpad_1, + numpad_2, + numpad_3, + numpad_4, + numpad_5, + numpad_6, + numpad_7, + numpad_8, + numpad_9, + numpad_add, + numpad_backspace, + numpad_clear, + numpad_clear_entry, + numpad_comma, + numpad_decimal, + numpad_divide, + numpad_enter, + numpad_equal, + numpad_memory_add, + numpad_memory_clear, + numpad_memory_recall, + numpad_memory_store, + numpad_memory_subtract, + numpad_multiply, + numpad_paren_left, + numpad_paren_right, + numpad_subtract, + + // > For numpads that provide keys not listed here, a code value string + // > should be created by starting with "Numpad" and appending an + // > appropriate description of the key. + // + // These numpad entries are distinguished by various encoding protocols + // (legacy and Kitty) so we support them here in case the apprt can + // produce them. + numpad_separator, + numpad_up, + numpad_down, + numpad_right, + numpad_left, + numpad_begin, + numpad_home, + numpad_end, + numpad_insert, + numpad_delete, + numpad_page_up, + numpad_page_down, + + // "Function Section" § 3.5 + escape, + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + f11, + f12, + f13, + f14, + f15, + f16, + f17, + f18, + f19, + f20, + f21, + f22, + f23, + f24, + f25, + @"fn", + fn_lock, + print_screen, + scroll_lock, + pause, + + // "Media Keys" § 3.6 + browser_back, + browser_favorites, + browser_forward, + browser_home, + browser_refresh, + browser_search, + browser_stop, + eject, + launch_app_1, + launch_app_2, + launch_mail, + media_play_pause, + media_select, + media_stop, + media_track_next, + media_track_previous, + power, + sleep, + audio_volume_down, + audio_volume_mute, + audio_volume_up, + wake_up, + + // "Legacy, Non-standard, and Special Keys" § 3.7 + copy, + cut, + paste, + + /// Converts an ASCII character to a key, if possible. This returns + /// null if the character is unknown. + /// + /// Note that this can't distinguish between physical keys, i.e. '0' + /// may be from the number row or the keypad, but it always maps + /// to '.zero'. + /// + /// This is what we want, we want people to create keybindings that + /// are independent of the physical key. + pub fn fromASCII(ch: u8) ?Key { + return switch (ch) { + inline else => |comptime_ch| { + return comptime result: { + @setEvalBranchQuota(100_000); + for (codepoint_map) |entry| { + // No ASCII characters should ever map to a keypad key + if (entry[1].keypad()) continue; + + if (entry[0] == @as(u21, @intCast(comptime_ch))) { + break :result entry[1]; + } + } + + break :result null; + }; + }, + }; + } + + /// Converts a W3C key code to a Ghostty key enum value. + /// + /// All required W3C key codes are supported, but there are a number of + /// non-standard key codes that are not supported. In the case the value is + /// invalid or unsupported, this function will return null. + pub fn fromW3C(code: []const u8) ?Key { + var result: [128]u8 = undefined; + + // If the code is bigger than our buffer it can't possibly match. + if (code.len > result.len) return null; + + // First just check the whole thing lowercased, this is the simple case + if (std.meta.stringToEnum( + Key, + std.ascii.lowerString(&result, code), + )) |key| return key; + + // We need to convert FooBar to foo_bar + var fbs = std.io.fixedBufferStream(&result); + const w = fbs.writer(); + for (code, 0..) |ch, i| switch (ch) { + 'a'...'z' => w.writeByte(ch) catch return null, + + // Caps and numbers trigger underscores + 'A'...'Z', '0'...'9' => { + if (i > 0) w.writeByte('_') catch return null; + w.writeByte(std.ascii.toLower(ch)) catch return null; + }, + + // We don't know of any key codes that aren't alphanumeric. + else => return null, + }; + + return std.meta.stringToEnum(Key, fbs.getWritten()); + } + + /// Converts a Ghostty key enum value to a W3C key code. + pub fn w3c(self: Key) []const u8 { + return switch (self) { + inline else => |tag| comptime w3c: { + @setEvalBranchQuota(50_000); + + const name = @tagName(tag); + + var buf: [128]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buf); + const w = fbs.writer(); + var i: usize = 0; + while (i < name.len) { + if (i == 0) { + w.writeByte(std.ascii.toUpper(name[i])) catch unreachable; + } else if (name[i] == '_') { + i += 1; + w.writeByte(std.ascii.toUpper(name[i])) catch unreachable; + } else { + w.writeByte(name[i]) catch unreachable; + } + + i += 1; + } + + const written = buf; + const result = written[0..fbs.getWritten().len]; + break :w3c result; + }, + }; + } + + /// True if this key represents a printable character. + pub fn printable(self: Key) bool { + return switch (self) { + inline else => |tag| { + return comptime result: { + @setEvalBranchQuota(10_000); + for (codepoint_map) |entry| { + if (entry[1] == tag) break :result true; + } + + break :result false; + }; + }, + }; + } + + /// True if this key is a modifier. + pub fn modifier(self: Key) bool { + return switch (self) { + .shift_left, + .control_left, + .alt_left, + .meta_left, + .shift_right, + .control_right, + .alt_right, + .meta_right, + => true, + + else => false, + }; + } + + /// Whether this key should be remappable by the operating system. + /// + /// On certain OSes (namely Linux and the BSDs) certain keys like the + /// functional keys are expected to be remappable by the user, such as + /// in the very common use case of swapping the Caps Lock key with the + /// Escape key with the XKB option `caps:swapescape`. + /// + /// However, the way XKB implements this is by essentially acting as a + /// software key remapper that destroys all information about the original + /// physical key, leading to very annoying bugs like #7309 where the + /// physical key `XKB_KEY_c` gets remapped into `XKB_KEY_Cyrillic_tse`, + /// which causes all of our physical key handling to completely break down. + /// _Very naughty._ + /// + /// As a compromise, given that writing system keys (§3.1.1) comprise the + /// majority of keys that "change meaning [...] based on the current locale + /// and keyboard layout", we allow all other keys to be remapped by default + /// since they should be fairly harmless. We might consider making this + /// configurable, but for now this should at least placate most people. + pub fn shouldBeRemappable(self: Key) bool { + return switch (self) { + // "Writing System Keys" § 3.1.1 + .backquote, + .backslash, + .bracket_left, + .bracket_right, + .comma, + .digit_0, + .digit_1, + .digit_2, + .digit_3, + .digit_4, + .digit_5, + .digit_6, + .digit_7, + .digit_8, + .digit_9, + .equal, + .intl_backslash, + .intl_ro, + .intl_yen, + .key_a, + .key_b, + .key_c, + .key_d, + .key_e, + .key_f, + .key_g, + .key_h, + .key_i, + .key_j, + .key_k, + .key_l, + .key_m, + .key_n, + .key_o, + .key_p, + .key_q, + .key_r, + .key_s, + .key_t, + .key_u, + .key_v, + .key_w, + .key_x, + .key_y, + .key_z, + .minus, + .period, + .quote, + .semicolon, + .slash, + => false, + + else => true, + }; + } + + /// Returns true if this is a keypad key. + pub fn keypad(self: Key) bool { + return switch (self) { + inline else => |tag| { + const name = @tagName(tag); + const result = comptime std.mem.startsWith(u8, name, "numpad_"); + return result; + }, + }; + } + + // Returns the codepoint representing this key, or null if the key is not + // printable + pub fn codepoint(self: Key) ?u21 { + return switch (self) { + inline else => |tag| { + return comptime result: { + @setEvalBranchQuota(10_000); + for (codepoint_map) |entry| { + if (entry[1] == tag) break :result entry[0]; + } + + break :result null; + }; + }, + }; + } + + /// Returns the cimgui key constant for this key. + pub fn imguiKey(self: Key) ?c_int { + return switch (self) { + .key_a => cimgui.c.ImGuiKey_A, + .key_b => cimgui.c.ImGuiKey_B, + .key_c => cimgui.c.ImGuiKey_C, + .key_d => cimgui.c.ImGuiKey_D, + .key_e => cimgui.c.ImGuiKey_E, + .key_f => cimgui.c.ImGuiKey_F, + .key_g => cimgui.c.ImGuiKey_G, + .key_h => cimgui.c.ImGuiKey_H, + .key_i => cimgui.c.ImGuiKey_I, + .key_j => cimgui.c.ImGuiKey_J, + .key_k => cimgui.c.ImGuiKey_K, + .key_l => cimgui.c.ImGuiKey_L, + .key_m => cimgui.c.ImGuiKey_M, + .key_n => cimgui.c.ImGuiKey_N, + .key_o => cimgui.c.ImGuiKey_O, + .key_p => cimgui.c.ImGuiKey_P, + .key_q => cimgui.c.ImGuiKey_Q, + .key_r => cimgui.c.ImGuiKey_R, + .key_s => cimgui.c.ImGuiKey_S, + .key_t => cimgui.c.ImGuiKey_T, + .key_u => cimgui.c.ImGuiKey_U, + .key_v => cimgui.c.ImGuiKey_V, + .key_w => cimgui.c.ImGuiKey_W, + .key_x => cimgui.c.ImGuiKey_X, + .key_y => cimgui.c.ImGuiKey_Y, + .key_z => cimgui.c.ImGuiKey_Z, + + .digit_0 => cimgui.c.ImGuiKey_0, + .digit_1 => cimgui.c.ImGuiKey_1, + .digit_2 => cimgui.c.ImGuiKey_2, + .digit_3 => cimgui.c.ImGuiKey_3, + .digit_4 => cimgui.c.ImGuiKey_4, + .digit_5 => cimgui.c.ImGuiKey_5, + .digit_6 => cimgui.c.ImGuiKey_6, + .digit_7 => cimgui.c.ImGuiKey_7, + .digit_8 => cimgui.c.ImGuiKey_8, + .digit_9 => cimgui.c.ImGuiKey_9, + + .semicolon => cimgui.c.ImGuiKey_Semicolon, + .space => cimgui.c.ImGuiKey_Space, + .quote => cimgui.c.ImGuiKey_Apostrophe, + .comma => cimgui.c.ImGuiKey_Comma, + .backquote => cimgui.c.ImGuiKey_GraveAccent, + .period => cimgui.c.ImGuiKey_Period, + .slash => cimgui.c.ImGuiKey_Slash, + .minus => cimgui.c.ImGuiKey_Minus, + .equal => cimgui.c.ImGuiKey_Equal, + .bracket_left => cimgui.c.ImGuiKey_LeftBracket, + .bracket_right => cimgui.c.ImGuiKey_RightBracket, + .backslash => cimgui.c.ImGuiKey_Backslash, + + .arrow_up => cimgui.c.ImGuiKey_UpArrow, + .arrow_down => cimgui.c.ImGuiKey_DownArrow, + .arrow_left => cimgui.c.ImGuiKey_LeftArrow, + .arrow_right => cimgui.c.ImGuiKey_RightArrow, + .home => cimgui.c.ImGuiKey_Home, + .end => cimgui.c.ImGuiKey_End, + .insert => cimgui.c.ImGuiKey_Insert, + .delete => cimgui.c.ImGuiKey_Delete, + .caps_lock => cimgui.c.ImGuiKey_CapsLock, + .scroll_lock => cimgui.c.ImGuiKey_ScrollLock, + .num_lock => cimgui.c.ImGuiKey_NumLock, + .page_up => cimgui.c.ImGuiKey_PageUp, + .page_down => cimgui.c.ImGuiKey_PageDown, + .escape => cimgui.c.ImGuiKey_Escape, + .enter => cimgui.c.ImGuiKey_Enter, + .tab => cimgui.c.ImGuiKey_Tab, + .backspace => cimgui.c.ImGuiKey_Backspace, + .print_screen => cimgui.c.ImGuiKey_PrintScreen, + .pause => cimgui.c.ImGuiKey_Pause, + .context_menu => cimgui.c.ImGuiKey_Menu, + + .f1 => cimgui.c.ImGuiKey_F1, + .f2 => cimgui.c.ImGuiKey_F2, + .f3 => cimgui.c.ImGuiKey_F3, + .f4 => cimgui.c.ImGuiKey_F4, + .f5 => cimgui.c.ImGuiKey_F5, + .f6 => cimgui.c.ImGuiKey_F6, + .f7 => cimgui.c.ImGuiKey_F7, + .f8 => cimgui.c.ImGuiKey_F8, + .f9 => cimgui.c.ImGuiKey_F9, + .f10 => cimgui.c.ImGuiKey_F10, + .f11 => cimgui.c.ImGuiKey_F11, + .f12 => cimgui.c.ImGuiKey_F12, + + .numpad_0 => cimgui.c.ImGuiKey_Keypad0, + .numpad_1 => cimgui.c.ImGuiKey_Keypad1, + .numpad_2 => cimgui.c.ImGuiKey_Keypad2, + .numpad_3 => cimgui.c.ImGuiKey_Keypad3, + .numpad_4 => cimgui.c.ImGuiKey_Keypad4, + .numpad_5 => cimgui.c.ImGuiKey_Keypad5, + .numpad_6 => cimgui.c.ImGuiKey_Keypad6, + .numpad_7 => cimgui.c.ImGuiKey_Keypad7, + .numpad_8 => cimgui.c.ImGuiKey_Keypad8, + .numpad_9 => cimgui.c.ImGuiKey_Keypad9, + .numpad_decimal => cimgui.c.ImGuiKey_KeypadDecimal, + .numpad_divide => cimgui.c.ImGuiKey_KeypadDivide, + .numpad_multiply => cimgui.c.ImGuiKey_KeypadMultiply, + .numpad_subtract => cimgui.c.ImGuiKey_KeypadSubtract, + .numpad_add => cimgui.c.ImGuiKey_KeypadAdd, + .numpad_enter => cimgui.c.ImGuiKey_KeypadEnter, + .numpad_equal => cimgui.c.ImGuiKey_KeypadEqual, + // We map KP_SEPARATOR to Comma because traditionally a numpad would + // have a numeric separator key. Most modern numpads do not + .numpad_left => cimgui.c.ImGuiKey_LeftArrow, + .numpad_right => cimgui.c.ImGuiKey_RightArrow, + .numpad_up => cimgui.c.ImGuiKey_UpArrow, + .numpad_down => cimgui.c.ImGuiKey_DownArrow, + .numpad_page_up => cimgui.c.ImGuiKey_PageUp, + .numpad_page_down => cimgui.c.ImGuiKey_PageUp, + .numpad_home => cimgui.c.ImGuiKey_Home, + .numpad_end => cimgui.c.ImGuiKey_End, + .numpad_insert => cimgui.c.ImGuiKey_Insert, + .numpad_delete => cimgui.c.ImGuiKey_Delete, + .numpad_begin => cimgui.c.ImGuiKey_NamedKey_BEGIN, + + .shift_left => cimgui.c.ImGuiKey_LeftShift, + .control_left => cimgui.c.ImGuiKey_LeftCtrl, + .alt_left => cimgui.c.ImGuiKey_LeftAlt, + .meta_left => cimgui.c.ImGuiKey_LeftSuper, + .shift_right => cimgui.c.ImGuiKey_RightShift, + .control_right => cimgui.c.ImGuiKey_RightCtrl, + .alt_right => cimgui.c.ImGuiKey_RightAlt, + .meta_right => cimgui.c.ImGuiKey_RightSuper, + + // These keys aren't represented in cimgui + .f13, + .f14, + .f15, + .f16, + .f17, + .f18, + .f19, + .f20, + .f21, + .f22, + .f23, + .f24, + .f25, + .intl_backslash, + .intl_ro, + .intl_yen, + .convert, + .kana_mode, + .non_convert, + .numpad_separator, + .numpad_backspace, + .numpad_clear, + .numpad_clear_entry, + .numpad_comma, + .numpad_memory_add, + .numpad_memory_clear, + .numpad_memory_recall, + .numpad_memory_store, + .numpad_memory_subtract, + .numpad_paren_left, + .numpad_paren_right, + .@"fn", + .fn_lock, + .browser_back, + .browser_favorites, + .browser_forward, + .browser_home, + .browser_refresh, + .browser_search, + .browser_stop, + .eject, + .launch_app_1, + .launch_app_2, + .launch_mail, + .media_play_pause, + .media_select, + .media_stop, + .media_track_next, + .media_track_previous, + .power, + .sleep, + .audio_volume_down, + .audio_volume_mute, + .audio_volume_up, + .wake_up, + .help, + .copy, + .cut, + .paste, + => null, + + .unidentified, + => null, + }; + } + + /// true if this key is one of the left or right versions of super (MacOS) + /// or ctrl. + pub fn ctrlOrSuper(self: Key) bool { + if (comptime builtin.target.os.tag.isDarwin()) { + return self == .meta_left or self == .meta_right; + } + return self == .control_left or self == .control_right; + } + + /// true if this key is either left or right shift. + pub fn leftOrRightShift(self: Key) bool { + return self == .shift_left or self == .shift_right; + } + + /// true if this key is either left or right alt. + pub fn leftOrRightAlt(self: Key) bool { + return self == .alt_left or self == .alt_right; + } + + test "fromASCII should not return keypad keys" { + const testing = std.testing; + try testing.expect(Key.fromASCII('0').? == .digit_0); + try testing.expect(Key.fromASCII('*') == null); + } + + test "keypad keys" { + const testing = std.testing; + try testing.expect(Key.numpad_0.keypad()); + try testing.expect(!Key.digit_1.keypad()); + } + + test "w3c" { + // All our keys should convert to and from the W3C format. + // We don't support every key in the W3C spec, so we only + // check the enum fields. + const testing = std.testing; + inline for (@typeInfo(Key).@"enum".fields) |field| { + const key = @field(Key, field.name); + const w3c_name = key.w3c(); + try testing.expectEqual(key, Key.fromW3C(w3c_name).?); + } + } + + const codepoint_map: []const struct { u21, Key } = &.{ + .{ 'a', .key_a }, + .{ 'b', .key_b }, + .{ 'c', .key_c }, + .{ 'd', .key_d }, + .{ 'e', .key_e }, + .{ 'f', .key_f }, + .{ 'g', .key_g }, + .{ 'h', .key_h }, + .{ 'i', .key_i }, + .{ 'j', .key_j }, + .{ 'k', .key_k }, + .{ 'l', .key_l }, + .{ 'm', .key_m }, + .{ 'n', .key_n }, + .{ 'o', .key_o }, + .{ 'p', .key_p }, + .{ 'q', .key_q }, + .{ 'r', .key_r }, + .{ 's', .key_s }, + .{ 't', .key_t }, + .{ 'u', .key_u }, + .{ 'v', .key_v }, + .{ 'w', .key_w }, + .{ 'x', .key_x }, + .{ 'y', .key_y }, + .{ 'z', .key_z }, + .{ '0', .digit_0 }, + .{ '1', .digit_1 }, + .{ '2', .digit_2 }, + .{ '3', .digit_3 }, + .{ '4', .digit_4 }, + .{ '5', .digit_5 }, + .{ '6', .digit_6 }, + .{ '7', .digit_7 }, + .{ '8', .digit_8 }, + .{ '9', .digit_9 }, + .{ ';', .semicolon }, + .{ ' ', .space }, + .{ '\'', .quote }, + .{ ',', .comma }, + .{ '`', .backquote }, + .{ '.', .period }, + .{ '/', .slash }, + .{ '-', .minus }, + .{ '=', .equal }, + .{ '[', .bracket_left }, + .{ ']', .bracket_right }, + .{ '\\', .backslash }, + + // Control characters + .{ '\t', .tab }, + + // Keypad entries. We just assume keypad with the numpad_ prefix + // so that has some special meaning. These must also always be last, + // so that our `fromASCII` function doesn't accidentally map them + // over normal numerics and other keys. + .{ '0', .numpad_0 }, + .{ '1', .numpad_1 }, + .{ '2', .numpad_2 }, + .{ '3', .numpad_3 }, + .{ '4', .numpad_4 }, + .{ '5', .numpad_5 }, + .{ '6', .numpad_6 }, + .{ '7', .numpad_7 }, + .{ '8', .numpad_8 }, + .{ '9', .numpad_9 }, + .{ '.', .numpad_decimal }, + .{ '/', .numpad_divide }, + .{ '*', .numpad_multiply }, + .{ '-', .numpad_subtract }, + .{ '+', .numpad_add }, + .{ '=', .numpad_equal }, + }; +}; + +/// This sets either "ctrl" or "super" to true (but not both) +/// on mods depending on if the build target is Mac or not. On +/// Mac, we default to super (i.e. super+c for copy) and on +/// non-Mac we default to ctrl (i.e. ctrl+c for copy). +pub fn ctrlOrSuper(mods: Mods) Mods { + var copy = mods; + if (comptime builtin.target.os.tag.isDarwin()) { + copy.super = true; + } else { + copy.ctrl = true; + } + + return copy; +} + +test "ctrlOrSuper" { + const testing = std.testing; + var m: Mods = ctrlOrSuper(.{}); + + try testing.expect(m.ctrlOrSuper()); +} |
