aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/input/key.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/input/key.zig
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (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.zig878
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());
+}