mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-01-01 03:02:15 +00:00
This allows bindings with `tab` to work properly on Linux. The issue is that in the key translation, we weren't mapping this and thought it was invalid IME input so we were ignoring it.
745 lines
21 KiB
Zig
745 lines
21 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const Allocator = std.mem.Allocator;
|
|
const cimgui = @import("cimgui");
|
|
const config = @import("../config.zig");
|
|
|
|
/// 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,
|
|
|
|
/// "key" is the logical key that was pressed. For example, if
|
|
/// a Dvorak keyboard layout is being used on a US keyboard,
|
|
/// the "i" physical key will be reported as "c". The physical
|
|
/// key is the key that was physically pressed on the keyboard.
|
|
key: Key,
|
|
physical_key: Key = .invalid,
|
|
|
|
/// 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);
|
|
}
|
|
};
|
|
|
|
/// A bitmask for all key modifiers.
|
|
///
|
|
/// IMPORTANT: Any changes here update include/ghostty.h
|
|
pub const Mods = packed struct(Mods.Backing) {
|
|
pub const Backing = u16;
|
|
|
|
shift: bool = false,
|
|
ctrl: bool = false,
|
|
alt: bool = false,
|
|
super: bool = false,
|
|
caps_lock: bool = false,
|
|
num_lock: bool = false,
|
|
sides: side = .{},
|
|
_padding: u6 = 0,
|
|
|
|
/// Tracks the side that is active for any given modifier. Note
|
|
/// that this doesn't confirm a modifier is pressed; you must check
|
|
/// the bool for that in addition to this.
|
|
///
|
|
/// Not all platforms support this, check apprt for more info.
|
|
pub const side = packed struct(u4) {
|
|
shift: Side = .left,
|
|
ctrl: Side = .left,
|
|
alt: Side = .left,
|
|
super: Side = .left,
|
|
};
|
|
|
|
pub const Side = enum { left, right };
|
|
|
|
/// Integer value of this struct.
|
|
pub fn int(self: Mods) Backing {
|
|
return @bitCast(self);
|
|
}
|
|
|
|
/// Returns true if no modifiers are set.
|
|
pub fn empty(self: Mods) bool {
|
|
return self.int() == 0;
|
|
}
|
|
|
|
/// Returns true if two mods are equal.
|
|
pub fn equal(self: Mods, other: Mods) bool {
|
|
return self.int() == other.int();
|
|
}
|
|
|
|
/// Return mods that are only relevant for bindings.
|
|
pub fn binding(self: Mods) Mods {
|
|
return .{
|
|
.shift = self.shift,
|
|
.ctrl = self.ctrl,
|
|
.alt = self.alt,
|
|
.super = self.super,
|
|
};
|
|
}
|
|
|
|
/// Perform `self &~ other` to remove the other mods from self.
|
|
pub fn unset(self: Mods, other: Mods) Mods {
|
|
return @bitCast(self.int() & ~other.int());
|
|
}
|
|
|
|
/// Returns the mods without locks set.
|
|
pub fn withoutLocks(self: Mods) Mods {
|
|
var copy = self;
|
|
copy.caps_lock = false;
|
|
copy.num_lock = false;
|
|
return copy;
|
|
}
|
|
|
|
/// Return the mods to use for key translation. This handles settings
|
|
/// like macos-option-as-alt. The translation mods should be used for
|
|
/// translation but never sent back in for the key callback.
|
|
pub fn translation(self: Mods, option_as_alt: config.OptionAsAlt) Mods {
|
|
// We currently only process macos-option-as-alt so other
|
|
// platforms don't need to do anything.
|
|
if (comptime !builtin.target.isDarwin()) return self;
|
|
|
|
// Alt has to be set only on the correct side
|
|
switch (option_as_alt) {
|
|
.false => return self,
|
|
.true => {},
|
|
.left => if (self.sides.alt == .right) return self,
|
|
.right => if (self.sides.alt == .left) return self,
|
|
}
|
|
|
|
// Unset alt
|
|
var result = self;
|
|
result.alt = false;
|
|
return result;
|
|
}
|
|
|
|
/// Checks to see if super is on (MacOS) or ctrl.
|
|
pub fn ctrlOrSuper(self: Mods) bool {
|
|
if (comptime builtin.target.isDarwin()) {
|
|
return self.super;
|
|
}
|
|
return self.ctrl;
|
|
}
|
|
|
|
// For our own understanding
|
|
test {
|
|
const testing = std.testing;
|
|
try testing.expectEqual(@as(Backing, @bitCast(Mods{})), @as(Backing, 0b0));
|
|
try testing.expectEqual(
|
|
@as(Backing, @bitCast(Mods{ .shift = true })),
|
|
@as(Backing, 0b0000_0001),
|
|
);
|
|
}
|
|
|
|
test "translation macos-option-as-alt" {
|
|
if (comptime !builtin.target.isDarwin()) return error.SkipZigTest;
|
|
|
|
const testing = std.testing;
|
|
|
|
// Unset
|
|
{
|
|
const mods: Mods = .{};
|
|
const result = mods.translation(.true);
|
|
try testing.expectEqual(result, mods);
|
|
}
|
|
|
|
// Set
|
|
{
|
|
const mods: Mods = .{ .alt = true };
|
|
const result = mods.translation(.true);
|
|
try testing.expectEqual(Mods{}, result);
|
|
}
|
|
|
|
// Set but disabled
|
|
{
|
|
const mods: Mods = .{ .alt = true };
|
|
const result = mods.translation(.false);
|
|
try testing.expectEqual(result, mods);
|
|
}
|
|
|
|
// Set wrong side
|
|
{
|
|
const mods: Mods = .{ .alt = true, .sides = .{ .alt = .right } };
|
|
const result = mods.translation(.left);
|
|
try testing.expectEqual(result, mods);
|
|
}
|
|
{
|
|
const mods: Mods = .{ .alt = true, .sides = .{ .alt = .left } };
|
|
const result = mods.translation(.right);
|
|
try testing.expectEqual(result, mods);
|
|
}
|
|
|
|
// Set with other mods
|
|
{
|
|
const mods: Mods = .{ .alt = true, .shift = true };
|
|
const result = mods.translation(.true);
|
|
try testing.expectEqual(Mods{ .shift = true }, result);
|
|
}
|
|
}
|
|
};
|
|
|
|
/// 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 keys that can map to keybindings. These have no fixed enum
|
|
/// values because we map platform-specific keys to this set. Note that
|
|
/// this only needs to accommodate what maps to a key. If a key is not bound
|
|
/// to anything and the key can be mapped to a printable character, then that
|
|
/// unicode character is sent directly to the pty.
|
|
///
|
|
/// This is backed by a c_int so we can use this as-is for our embedding API.
|
|
///
|
|
/// IMPORTANT: Any changes here update include/ghostty.h
|
|
pub const Key = enum(c_int) {
|
|
invalid,
|
|
|
|
// a-z
|
|
a,
|
|
b,
|
|
c,
|
|
d,
|
|
e,
|
|
f,
|
|
g,
|
|
h,
|
|
i,
|
|
j,
|
|
k,
|
|
l,
|
|
m,
|
|
n,
|
|
o,
|
|
p,
|
|
q,
|
|
r,
|
|
s,
|
|
t,
|
|
u,
|
|
v,
|
|
w,
|
|
x,
|
|
y,
|
|
z,
|
|
|
|
// numbers
|
|
zero,
|
|
one,
|
|
two,
|
|
three,
|
|
four,
|
|
five,
|
|
six,
|
|
seven,
|
|
eight,
|
|
nine,
|
|
|
|
// puncuation
|
|
semicolon,
|
|
space,
|
|
apostrophe,
|
|
comma,
|
|
grave_accent, // `
|
|
period,
|
|
slash,
|
|
minus,
|
|
equal,
|
|
left_bracket, // [
|
|
right_bracket, // ]
|
|
backslash, // /
|
|
|
|
// control
|
|
up,
|
|
down,
|
|
right,
|
|
left,
|
|
home,
|
|
end,
|
|
insert,
|
|
delete,
|
|
caps_lock,
|
|
scroll_lock,
|
|
num_lock,
|
|
page_up,
|
|
page_down,
|
|
escape,
|
|
enter,
|
|
tab,
|
|
backspace,
|
|
print_screen,
|
|
pause,
|
|
|
|
// function keys
|
|
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,
|
|
|
|
// keypad
|
|
kp_0,
|
|
kp_1,
|
|
kp_2,
|
|
kp_3,
|
|
kp_4,
|
|
kp_5,
|
|
kp_6,
|
|
kp_7,
|
|
kp_8,
|
|
kp_9,
|
|
kp_decimal,
|
|
kp_divide,
|
|
kp_multiply,
|
|
kp_subtract,
|
|
kp_add,
|
|
kp_enter,
|
|
kp_equal,
|
|
kp_separator,
|
|
kp_left,
|
|
kp_right,
|
|
kp_up,
|
|
kp_down,
|
|
kp_page_up,
|
|
kp_page_down,
|
|
kp_home,
|
|
kp_end,
|
|
kp_insert,
|
|
kp_delete,
|
|
kp_begin,
|
|
|
|
// TODO: media keys
|
|
|
|
// modifiers
|
|
left_shift,
|
|
left_control,
|
|
left_alt,
|
|
left_super,
|
|
right_shift,
|
|
right_control,
|
|
right_alt,
|
|
right_super,
|
|
|
|
// To support more keys (there are obviously more!) add them here
|
|
// and ensure the mapping is up to date in the Window key handler.
|
|
|
|
/// 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 awnt 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;
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
/// 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) {
|
|
.left_shift,
|
|
.left_control,
|
|
.left_alt,
|
|
.left_super,
|
|
.right_shift,
|
|
.right_control,
|
|
.right_alt,
|
|
.right_super,
|
|
=> true,
|
|
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
/// 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, "kp_");
|
|
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_uint {
|
|
return switch (self) {
|
|
.a => cimgui.c.ImGuiKey_A,
|
|
.b => cimgui.c.ImGuiKey_B,
|
|
.c => cimgui.c.ImGuiKey_C,
|
|
.d => cimgui.c.ImGuiKey_D,
|
|
.e => cimgui.c.ImGuiKey_E,
|
|
.f => cimgui.c.ImGuiKey_F,
|
|
.g => cimgui.c.ImGuiKey_G,
|
|
.h => cimgui.c.ImGuiKey_H,
|
|
.i => cimgui.c.ImGuiKey_I,
|
|
.j => cimgui.c.ImGuiKey_J,
|
|
.k => cimgui.c.ImGuiKey_K,
|
|
.l => cimgui.c.ImGuiKey_L,
|
|
.m => cimgui.c.ImGuiKey_M,
|
|
.n => cimgui.c.ImGuiKey_N,
|
|
.o => cimgui.c.ImGuiKey_O,
|
|
.p => cimgui.c.ImGuiKey_P,
|
|
.q => cimgui.c.ImGuiKey_Q,
|
|
.r => cimgui.c.ImGuiKey_R,
|
|
.s => cimgui.c.ImGuiKey_S,
|
|
.t => cimgui.c.ImGuiKey_T,
|
|
.u => cimgui.c.ImGuiKey_U,
|
|
.v => cimgui.c.ImGuiKey_V,
|
|
.w => cimgui.c.ImGuiKey_W,
|
|
.x => cimgui.c.ImGuiKey_X,
|
|
.y => cimgui.c.ImGuiKey_Y,
|
|
.z => cimgui.c.ImGuiKey_Z,
|
|
|
|
.zero => cimgui.c.ImGuiKey_0,
|
|
.one => cimgui.c.ImGuiKey_1,
|
|
.two => cimgui.c.ImGuiKey_2,
|
|
.three => cimgui.c.ImGuiKey_3,
|
|
.four => cimgui.c.ImGuiKey_4,
|
|
.five => cimgui.c.ImGuiKey_5,
|
|
.six => cimgui.c.ImGuiKey_6,
|
|
.seven => cimgui.c.ImGuiKey_7,
|
|
.eight => cimgui.c.ImGuiKey_8,
|
|
.nine => cimgui.c.ImGuiKey_9,
|
|
|
|
.semicolon => cimgui.c.ImGuiKey_Semicolon,
|
|
.space => cimgui.c.ImGuiKey_Space,
|
|
.apostrophe => cimgui.c.ImGuiKey_Apostrophe,
|
|
.comma => cimgui.c.ImGuiKey_Comma,
|
|
.grave_accent => cimgui.c.ImGuiKey_GraveAccent,
|
|
.period => cimgui.c.ImGuiKey_Period,
|
|
.slash => cimgui.c.ImGuiKey_Slash,
|
|
.minus => cimgui.c.ImGuiKey_Minus,
|
|
.equal => cimgui.c.ImGuiKey_Equal,
|
|
.left_bracket => cimgui.c.ImGuiKey_LeftBracket,
|
|
.right_bracket => cimgui.c.ImGuiKey_RightBracket,
|
|
.backslash => cimgui.c.ImGuiKey_Backslash,
|
|
|
|
.up => cimgui.c.ImGuiKey_UpArrow,
|
|
.down => cimgui.c.ImGuiKey_DownArrow,
|
|
.left => cimgui.c.ImGuiKey_LeftArrow,
|
|
.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,
|
|
|
|
.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,
|
|
|
|
.kp_0 => cimgui.c.ImGuiKey_Keypad0,
|
|
.kp_1 => cimgui.c.ImGuiKey_Keypad1,
|
|
.kp_2 => cimgui.c.ImGuiKey_Keypad2,
|
|
.kp_3 => cimgui.c.ImGuiKey_Keypad3,
|
|
.kp_4 => cimgui.c.ImGuiKey_Keypad4,
|
|
.kp_5 => cimgui.c.ImGuiKey_Keypad5,
|
|
.kp_6 => cimgui.c.ImGuiKey_Keypad6,
|
|
.kp_7 => cimgui.c.ImGuiKey_Keypad7,
|
|
.kp_8 => cimgui.c.ImGuiKey_Keypad8,
|
|
.kp_9 => cimgui.c.ImGuiKey_Keypad9,
|
|
.kp_decimal => cimgui.c.ImGuiKey_KeypadDecimal,
|
|
.kp_divide => cimgui.c.ImGuiKey_KeypadDivide,
|
|
.kp_multiply => cimgui.c.ImGuiKey_KeypadMultiply,
|
|
.kp_subtract => cimgui.c.ImGuiKey_KeypadSubtract,
|
|
.kp_add => cimgui.c.ImGuiKey_KeypadAdd,
|
|
.kp_enter => cimgui.c.ImGuiKey_KeypadEnter,
|
|
.kp_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
|
|
.kp_separator => cimgui.c.ImGuiKey_Comma,
|
|
.kp_left => cimgui.c.ImGuiKey_LeftArrow,
|
|
.kp_right => cimgui.c.ImGuiKey_RightArrow,
|
|
.kp_up => cimgui.c.ImGuiKey_UpArrow,
|
|
.kp_down => cimgui.c.ImGuiKey_DownArrow,
|
|
.kp_page_up => cimgui.c.ImGuiKey_PageUp,
|
|
.kp_page_down => cimgui.c.ImGuiKey_PageUp,
|
|
.kp_home => cimgui.c.ImGuiKey_Home,
|
|
.kp_end => cimgui.c.ImGuiKey_End,
|
|
.kp_insert => cimgui.c.ImGuiKey_Insert,
|
|
.kp_delete => cimgui.c.ImGuiKey_Delete,
|
|
.kp_begin => cimgui.c.ImGuiKey_NamedKey_BEGIN,
|
|
|
|
.left_shift => cimgui.c.ImGuiKey_LeftShift,
|
|
.left_control => cimgui.c.ImGuiKey_LeftCtrl,
|
|
.left_alt => cimgui.c.ImGuiKey_LeftAlt,
|
|
.left_super => cimgui.c.ImGuiKey_LeftSuper,
|
|
.right_shift => cimgui.c.ImGuiKey_RightShift,
|
|
.right_control => cimgui.c.ImGuiKey_RightCtrl,
|
|
.right_alt => cimgui.c.ImGuiKey_RightAlt,
|
|
.right_super => cimgui.c.ImGuiKey_RightSuper,
|
|
|
|
.invalid,
|
|
.f13,
|
|
.f14,
|
|
.f15,
|
|
.f16,
|
|
.f17,
|
|
.f18,
|
|
.f19,
|
|
.f20,
|
|
.f21,
|
|
.f22,
|
|
.f23,
|
|
.f24,
|
|
.f25,
|
|
=> 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.isDarwin()) {
|
|
return self == .left_super or self == .right_super;
|
|
}
|
|
return self == .left_control or self == .right_control;
|
|
}
|
|
|
|
/// true if this key is either left or right shift.
|
|
pub fn leftOrRightShift(self: Key) bool {
|
|
return self == .left_shift or self == .right_shift;
|
|
}
|
|
|
|
/// true if this key is either left or right alt.
|
|
pub fn leftOrRightAlt(self: Key) bool {
|
|
return self == .left_alt or self == .right_alt;
|
|
}
|
|
|
|
test "fromASCII should not return keypad keys" {
|
|
const testing = std.testing;
|
|
try testing.expect(Key.fromASCII('0').? == .zero);
|
|
try testing.expect(Key.fromASCII('*') == null);
|
|
}
|
|
|
|
test "keypad keys" {
|
|
const testing = std.testing;
|
|
try testing.expect(Key.kp_0.keypad());
|
|
try testing.expect(!Key.one.keypad());
|
|
}
|
|
|
|
const codepoint_map: []const struct { u21, Key } = &.{
|
|
.{ 'a', .a },
|
|
.{ 'b', .b },
|
|
.{ 'c', .c },
|
|
.{ 'd', .d },
|
|
.{ 'e', .e },
|
|
.{ 'f', .f },
|
|
.{ 'g', .g },
|
|
.{ 'h', .h },
|
|
.{ 'i', .i },
|
|
.{ 'j', .j },
|
|
.{ 'k', .k },
|
|
.{ 'l', .l },
|
|
.{ 'm', .m },
|
|
.{ 'n', .n },
|
|
.{ 'o', .o },
|
|
.{ 'p', .p },
|
|
.{ 'q', .q },
|
|
.{ 'r', .r },
|
|
.{ 's', .s },
|
|
.{ 't', .t },
|
|
.{ 'u', .u },
|
|
.{ 'v', .v },
|
|
.{ 'w', .w },
|
|
.{ 'x', .x },
|
|
.{ 'y', .y },
|
|
.{ 'z', .z },
|
|
.{ '0', .zero },
|
|
.{ '1', .one },
|
|
.{ '2', .two },
|
|
.{ '3', .three },
|
|
.{ '4', .four },
|
|
.{ '5', .five },
|
|
.{ '6', .six },
|
|
.{ '7', .seven },
|
|
.{ '8', .eight },
|
|
.{ '9', .nine },
|
|
.{ ';', .semicolon },
|
|
.{ ' ', .space },
|
|
.{ '\'', .apostrophe },
|
|
.{ ',', .comma },
|
|
.{ '`', .grave_accent },
|
|
.{ '.', .period },
|
|
.{ '/', .slash },
|
|
.{ '-', .minus },
|
|
.{ '=', .equal },
|
|
.{ '[', .left_bracket },
|
|
.{ ']', .right_bracket },
|
|
.{ '\\', .backslash },
|
|
|
|
// Control characters
|
|
.{ '\t', .tab },
|
|
|
|
// Keypad entries. We just assume keypad with the kp_ prefix
|
|
// so that has some special meaning. These must also always be last.
|
|
.{ '0', .kp_0 },
|
|
.{ '1', .kp_1 },
|
|
.{ '2', .kp_2 },
|
|
.{ '3', .kp_3 },
|
|
.{ '4', .kp_4 },
|
|
.{ '5', .kp_5 },
|
|
.{ '6', .kp_6 },
|
|
.{ '7', .kp_7 },
|
|
.{ '8', .kp_8 },
|
|
.{ '9', .kp_9 },
|
|
.{ '.', .kp_decimal },
|
|
.{ '/', .kp_divide },
|
|
.{ '*', .kp_multiply },
|
|
.{ '-', .kp_subtract },
|
|
.{ '+', .kp_add },
|
|
.{ '=', .kp_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.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());
|
|
}
|