Use W3C key code specification for keybindings (Breaking Change) (#7320)

Fixes #7315
Fixes #7314 
Fixes #7310 
Fixes #7309

**This PR has breaking changes, noted later.**

This changes our internal key events to match the [W3C key event code
specification](https://www.w3.org/TR/uievents-code/). With these
changes, apprts are expected to produce key codes within that spec and
everything follows from there. This gives us a standard cross-platform
keycode behavior.

Previously, due to our history, we based our key codes on GLFW and Linux
more generally (since Ghostty was on Linux first). This served us
surprisingly well through today, but has been riddled with various
issues (many fixed, but note how many bugs this PR is fixing). From a
user experience perspective, it also caused confusion about how
Ghostty's keybinds interact with keyboard layouts which led to various
complicated features like `physical:` (which is removed in this PR).

## Overview of Changes

* `physical:` is now gone. Physical keys are now specified by the W3C
key codes. Example: `ctrl+KeyA` will always match the "a" key on a US
physical layout (the name `KeyA` lining up with US keyboards is mandated
by the spec, not us). Note when we say "physical" here we mean the
keycode sent by the OS or GUI framework; these can often be overridden
using programs to remap keys at the "hardware" level but software
layouts do not do this.

* All single codepoint characters match the character produced by the
keyboard layout (i.e. are layout-dependent). So `ctrl+c` matches the
physical "c" key on a US standard keyboard with a US layout, but matches
the "i" key on a Dvorak layout. This also works for international
characters. Codepoints are case-insensitive and match via Unicode case
folding (this is how both Windows and macOS treat keyboard shortcuts).

* W3C key names replace all previous non-character names and always
match _physical keys_. For example, `ctrl+grave_accent` (Ghostty 1.1x)
is now `ctrl+backquote` to match the W3C spec. This PR maintains
backwards compatibility so the old values are aliased to the new values.

* W3C key names can be both snake case and match the spec exactly. e.g.
`KeyA` and `key_a` are both valid. This is an aesthetic choice, because
I think the capitalization is really ugly. We have tests to verify this
mapping so its officially supported.

* Because we support W3C keys, we now support significantly more media
keys such as `context_menu`, `browser_home` and so many more. These work
as long as your OS and keyboard can produce the valid key codes.

## Breaking Changes

* Key names have changed, e.g. `grave_accent` is now `backquote`. The
new names are all W3C standard. There are backwards compatible aliases,
so **old configurations will still work.** But the `+list-keybinds` CLI
will always output the new versions.

* `physical:` is gone, all W3C names are always physical and all code
points are always logical. There are backwards compatible aliases for
unambiguous keys, so **most old configurations will still work.** If
there is ambiguity, old configurations will error, but I don't expect
this to be common at all.

* Physical keybinds always take priority over logical keybinds. This was
_inconsistent_ before. Physical keybinds are relatively rare so I don't
think this will impact people but just noting this is not an explicit,
tested, documented behavior.
pull/7334/head
Mitchell Hashimoto 2025-05-12 09:18:28 -07:00 committed by GitHub
commit 3f54601df0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 1743 additions and 1393 deletions

View File

@ -103,10 +103,30 @@ typedef enum {
GHOSTTY_ACTION_REPEAT,
} ghostty_input_action_e;
// Based on: https://www.w3.org/TR/uievents-code/
typedef enum {
GHOSTTY_KEY_INVALID,
GHOSTTY_KEY_UNIDENTIFIED,
// a-z
// "Writing System Keys" § 3.1.1
GHOSTTY_KEY_BACKQUOTE,
GHOSTTY_KEY_BACKSLASH,
GHOSTTY_KEY_BRACKET_LEFT,
GHOSTTY_KEY_BRACKET_RIGHT,
GHOSTTY_KEY_COMMA,
GHOSTTY_KEY_DIGIT_0,
GHOSTTY_KEY_DIGIT_1,
GHOSTTY_KEY_DIGIT_2,
GHOSTTY_KEY_DIGIT_3,
GHOSTTY_KEY_DIGIT_4,
GHOSTTY_KEY_DIGIT_5,
GHOSTTY_KEY_DIGIT_6,
GHOSTTY_KEY_DIGIT_7,
GHOSTTY_KEY_DIGIT_8,
GHOSTTY_KEY_DIGIT_9,
GHOSTTY_KEY_EQUAL,
GHOSTTY_KEY_INTL_BACKSLASH,
GHOSTTY_KEY_INTL_RO,
GHOSTTY_KEY_INTL_YEN,
GHOSTTY_KEY_A,
GHOSTTY_KEY_B,
GHOSTTY_KEY_C,
@ -133,56 +153,91 @@ typedef enum {
GHOSTTY_KEY_X,
GHOSTTY_KEY_Y,
GHOSTTY_KEY_Z,
// numbers
GHOSTTY_KEY_ZERO,
GHOSTTY_KEY_ONE,
GHOSTTY_KEY_TWO,
GHOSTTY_KEY_THREE,
GHOSTTY_KEY_FOUR,
GHOSTTY_KEY_FIVE,
GHOSTTY_KEY_SIX,
GHOSTTY_KEY_SEVEN,
GHOSTTY_KEY_EIGHT,
GHOSTTY_KEY_NINE,
// puncuation
GHOSTTY_KEY_SEMICOLON,
GHOSTTY_KEY_SPACE,
GHOSTTY_KEY_APOSTROPHE,
GHOSTTY_KEY_COMMA,
GHOSTTY_KEY_GRAVE_ACCENT, // `
GHOSTTY_KEY_PERIOD,
GHOSTTY_KEY_SLASH,
GHOSTTY_KEY_MINUS,
GHOSTTY_KEY_PLUS,
GHOSTTY_KEY_EQUAL,
GHOSTTY_KEY_LEFT_BRACKET, // [
GHOSTTY_KEY_RIGHT_BRACKET, // ]
GHOSTTY_KEY_BACKSLASH, // \
GHOSTTY_KEY_PERIOD,
GHOSTTY_KEY_QUOTE,
GHOSTTY_KEY_SEMICOLON,
GHOSTTY_KEY_SLASH,
// control
GHOSTTY_KEY_UP,
GHOSTTY_KEY_DOWN,
GHOSTTY_KEY_RIGHT,
GHOSTTY_KEY_LEFT,
GHOSTTY_KEY_HOME,
GHOSTTY_KEY_END,
GHOSTTY_KEY_INSERT,
GHOSTTY_KEY_DELETE,
GHOSTTY_KEY_CAPS_LOCK,
GHOSTTY_KEY_SCROLL_LOCK,
GHOSTTY_KEY_NUM_LOCK,
GHOSTTY_KEY_PAGE_UP,
GHOSTTY_KEY_PAGE_DOWN,
GHOSTTY_KEY_ESCAPE,
GHOSTTY_KEY_ENTER,
GHOSTTY_KEY_TAB,
// "Functional Keys" § 3.1.2
GHOSTTY_KEY_ALT_LEFT,
GHOSTTY_KEY_ALT_RIGHT,
GHOSTTY_KEY_BACKSPACE,
GHOSTTY_KEY_PRINT_SCREEN,
GHOSTTY_KEY_PAUSE,
GHOSTTY_KEY_CAPS_LOCK,
GHOSTTY_KEY_CONTEXT_MENU,
GHOSTTY_KEY_CONTROL_LEFT,
GHOSTTY_KEY_CONTROL_RIGHT,
GHOSTTY_KEY_ENTER,
GHOSTTY_KEY_META_LEFT,
GHOSTTY_KEY_META_RIGHT,
GHOSTTY_KEY_SHIFT_LEFT,
GHOSTTY_KEY_SHIFT_RIGHT,
GHOSTTY_KEY_SPACE,
GHOSTTY_KEY_TAB,
GHOSTTY_KEY_CONVERT,
GHOSTTY_KEY_KANA_MODE,
GHOSTTY_KEY_NON_CONVERT,
// function keys
// "Control Pad Section" § 3.2
GHOSTTY_KEY_DELETE,
GHOSTTY_KEY_END,
GHOSTTY_KEY_HELP,
GHOSTTY_KEY_HOME,
GHOSTTY_KEY_INSERT,
GHOSTTY_KEY_PAGE_DOWN,
GHOSTTY_KEY_PAGE_UP,
// "Arrow Pad Section" § 3.3
GHOSTTY_KEY_ARROW_DOWN,
GHOSTTY_KEY_ARROW_LEFT,
GHOSTTY_KEY_ARROW_RIGHT,
GHOSTTY_KEY_ARROW_UP,
// "Numpad Section" § 3.4
GHOSTTY_KEY_NUM_LOCK,
GHOSTTY_KEY_NUMPAD_0,
GHOSTTY_KEY_NUMPAD_1,
GHOSTTY_KEY_NUMPAD_2,
GHOSTTY_KEY_NUMPAD_3,
GHOSTTY_KEY_NUMPAD_4,
GHOSTTY_KEY_NUMPAD_5,
GHOSTTY_KEY_NUMPAD_6,
GHOSTTY_KEY_NUMPAD_7,
GHOSTTY_KEY_NUMPAD_8,
GHOSTTY_KEY_NUMPAD_9,
GHOSTTY_KEY_NUMPAD_ADD,
GHOSTTY_KEY_NUMPAD_BACKSPACE,
GHOSTTY_KEY_NUMPAD_CLEAR,
GHOSTTY_KEY_NUMPAD_CLEAR_ENTRY,
GHOSTTY_KEY_NUMPAD_COMMA,
GHOSTTY_KEY_NUMPAD_DECIMAL,
GHOSTTY_KEY_NUMPAD_DIVIDE,
GHOSTTY_KEY_NUMPAD_ENTER,
GHOSTTY_KEY_NUMPAD_EQUAL,
GHOSTTY_KEY_NUMPAD_MEMORY_ADD,
GHOSTTY_KEY_NUMPAD_MEMORY_CLEAR,
GHOSTTY_KEY_NUMPAD_MEMORY_RECALL,
GHOSTTY_KEY_NUMPAD_MEMORY_STORE,
GHOSTTY_KEY_NUMPAD_MEMORY_SUBTRACT,
GHOSTTY_KEY_NUMPAD_MULTIPLY,
GHOSTTY_KEY_NUMPAD_PAREN_LEFT,
GHOSTTY_KEY_NUMPAD_PAREN_RIGHT,
GHOSTTY_KEY_NUMPAD_SUBTRACT,
GHOSTTY_KEY_NUMPAD_SEPARATOR,
GHOSTTY_KEY_NUMPAD_UP,
GHOSTTY_KEY_NUMPAD_DOWN,
GHOSTTY_KEY_NUMPAD_RIGHT,
GHOSTTY_KEY_NUMPAD_LEFT,
GHOSTTY_KEY_NUMPAD_BEGIN,
GHOSTTY_KEY_NUMPAD_HOME,
GHOSTTY_KEY_NUMPAD_END,
GHOSTTY_KEY_NUMPAD_INSERT,
GHOSTTY_KEY_NUMPAD_DELETE,
GHOSTTY_KEY_NUMPAD_PAGE_UP,
GHOSTTY_KEY_NUMPAD_PAGE_DOWN,
// "Function Section" § 3.5
GHOSTTY_KEY_ESCAPE,
GHOSTTY_KEY_F1,
GHOSTTY_KEY_F2,
GHOSTTY_KEY_F3,
@ -208,50 +263,35 @@ typedef enum {
GHOSTTY_KEY_F23,
GHOSTTY_KEY_F24,
GHOSTTY_KEY_F25,
GHOSTTY_KEY_FN,
GHOSTTY_KEY_FN_LOCK,
GHOSTTY_KEY_PRINT_SCREEN,
GHOSTTY_KEY_SCROLL_LOCK,
GHOSTTY_KEY_PAUSE,
// keypad
GHOSTTY_KEY_KP_0,
GHOSTTY_KEY_KP_1,
GHOSTTY_KEY_KP_2,
GHOSTTY_KEY_KP_3,
GHOSTTY_KEY_KP_4,
GHOSTTY_KEY_KP_5,
GHOSTTY_KEY_KP_6,
GHOSTTY_KEY_KP_7,
GHOSTTY_KEY_KP_8,
GHOSTTY_KEY_KP_9,
GHOSTTY_KEY_KP_DECIMAL,
GHOSTTY_KEY_KP_DIVIDE,
GHOSTTY_KEY_KP_MULTIPLY,
GHOSTTY_KEY_KP_SUBTRACT,
GHOSTTY_KEY_KP_ADD,
GHOSTTY_KEY_KP_ENTER,
GHOSTTY_KEY_KP_EQUAL,
GHOSTTY_KEY_KP_SEPARATOR,
GHOSTTY_KEY_KP_LEFT,
GHOSTTY_KEY_KP_RIGHT,
GHOSTTY_KEY_KP_UP,
GHOSTTY_KEY_KP_DOWN,
GHOSTTY_KEY_KP_PAGE_UP,
GHOSTTY_KEY_KP_PAGE_DOWN,
GHOSTTY_KEY_KP_HOME,
GHOSTTY_KEY_KP_END,
GHOSTTY_KEY_KP_INSERT,
GHOSTTY_KEY_KP_DELETE,
GHOSTTY_KEY_KP_BEGIN,
// special keys
GHOSTTY_KEY_CONTEXT_MENU,
// modifiers
GHOSTTY_KEY_LEFT_SHIFT,
GHOSTTY_KEY_LEFT_CONTROL,
GHOSTTY_KEY_LEFT_ALT,
GHOSTTY_KEY_LEFT_SUPER,
GHOSTTY_KEY_RIGHT_SHIFT,
GHOSTTY_KEY_RIGHT_CONTROL,
GHOSTTY_KEY_RIGHT_ALT,
GHOSTTY_KEY_RIGHT_SUPER,
// "Media Keys" § 3.6
GHOSTTY_KEY_BROWSER_BACK,
GHOSTTY_KEY_BROWSER_FAVORITES,
GHOSTTY_KEY_BROWSER_FORWARD,
GHOSTTY_KEY_BROWSER_HOME,
GHOSTTY_KEY_BROWSER_REFRESH,
GHOSTTY_KEY_BROWSER_SEARCH,
GHOSTTY_KEY_BROWSER_STOP,
GHOSTTY_KEY_EJECT,
GHOSTTY_KEY_LAUNCH_APP_1,
GHOSTTY_KEY_LAUNCH_APP_2,
GHOSTTY_KEY_LAUNCH_MAIL,
GHOSTTY_KEY_MEDIA_PLAY_PAUSE,
GHOSTTY_KEY_MEDIA_SELECT,
GHOSTTY_KEY_MEDIA_STOP,
GHOSTTY_KEY_MEDIA_TRACK_NEXT,
GHOSTTY_KEY_MEDIA_TRACK_PREVIOUS,
GHOSTTY_KEY_POWER,
GHOSTTY_KEY_SLEEP,
GHOSTTY_KEY_AUDIO_VOLUME_DOWN,
GHOSTTY_KEY_AUDIO_VOLUME_MUTE,
GHOSTTY_KEY_AUDIO_VOLUME_UP,
GHOSTTY_KEY_WAKE_UP,
} ghostty_input_key_e;
typedef struct {
@ -265,7 +305,6 @@ typedef struct {
} ghostty_input_key_s;
typedef enum {
GHOSTTY_TRIGGER_TRANSLATED,
GHOSTTY_TRIGGER_PHYSICAL,
GHOSTTY_TRIGGER_UNICODE,
} ghostty_input_trigger_tag_e;

View File

@ -469,17 +469,22 @@ class AppDelegate: NSObject,
guard NSApp.mainWindow == nil else { return event }
// If this event as-is would result in a key binding then we send it.
if let app = ghostty.app,
ghostty_app_key_is_binding(
app,
event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS)) {
if let app = ghostty.app {
var ghosttyEvent = event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS)
let match = (event.characters ?? "").withCString { ptr in
ghosttyEvent.text = ptr
if !ghostty_app_key_is_binding(app, ghosttyEvent) {
return false
}
return ghostty_app_key(app, ghosttyEvent)
}
// If the key was handled by Ghostty we stop the event chain. If
// the key wasn't handled then we let it fall through and continue
// processing. This is important because some bindings may have no
// affect at this scope.
if (ghostty_app_key(
app,
event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS))) {
if match {
return nil
}
}

View File

@ -5,12 +5,6 @@ import GhosttyKit
extension Ghostty {
// MARK: Keyboard Shortcuts
/// Returns the SwiftUI KeyEquivalent for a given key. Note that not all keys known by
/// Ghostty have a macOS equivalent since macOS doesn't allow all keys as equivalents.
static func keyEquivalent(key: ghostty_input_key_e) -> KeyEquivalent? {
return Self.keyToEquivalent[key]
}
/// Return the key equivalent for the given trigger.
///
/// Returns nil if the trigger doesn't have an equivalent KeyboardShortcut. This is possible
@ -22,16 +16,11 @@ extension Ghostty {
static func keyboardShortcut(for trigger: ghostty_input_trigger_s) -> KeyboardShortcut? {
let key: KeyEquivalent
switch (trigger.tag) {
case GHOSTTY_TRIGGER_TRANSLATED:
if let v = Ghostty.keyEquivalent(key: trigger.key.translated) {
key = v
} else {
return nil
}
case GHOSTTY_TRIGGER_PHYSICAL:
if let v = Ghostty.keyEquivalent(key: trigger.key.physical) {
key = v
// Only functional keys can be converted to a KeyboardShortcut. Other physical
// mappings cannot because KeyboardShortcut in Swift is inherently layout-dependent.
if let equiv = Self.keyToEquivalent[trigger.key.physical] {
key = equiv
} else {
return nil
}
@ -86,64 +75,11 @@ extension Ghostty {
/// not all ghostty key enum values are represented here because not all of them can be
/// mapped to a KeyEquivalent.
static let keyToEquivalent: [ghostty_input_key_e : KeyEquivalent] = [
// 0-9
GHOSTTY_KEY_ZERO: "0",
GHOSTTY_KEY_ONE: "1",
GHOSTTY_KEY_TWO: "2",
GHOSTTY_KEY_THREE: "3",
GHOSTTY_KEY_FOUR: "4",
GHOSTTY_KEY_FIVE: "5",
GHOSTTY_KEY_SIX: "6",
GHOSTTY_KEY_SEVEN: "7",
GHOSTTY_KEY_EIGHT: "8",
GHOSTTY_KEY_NINE: "9",
// a-z
GHOSTTY_KEY_A: "a",
GHOSTTY_KEY_B: "b",
GHOSTTY_KEY_C: "c",
GHOSTTY_KEY_D: "d",
GHOSTTY_KEY_E: "e",
GHOSTTY_KEY_F: "f",
GHOSTTY_KEY_G: "g",
GHOSTTY_KEY_H: "h",
GHOSTTY_KEY_I: "i",
GHOSTTY_KEY_J: "j",
GHOSTTY_KEY_K: "k",
GHOSTTY_KEY_L: "l",
GHOSTTY_KEY_M: "m",
GHOSTTY_KEY_N: "n",
GHOSTTY_KEY_O: "o",
GHOSTTY_KEY_P: "p",
GHOSTTY_KEY_Q: "q",
GHOSTTY_KEY_R: "r",
GHOSTTY_KEY_S: "s",
GHOSTTY_KEY_T: "t",
GHOSTTY_KEY_U: "u",
GHOSTTY_KEY_V: "v",
GHOSTTY_KEY_W: "w",
GHOSTTY_KEY_X: "x",
GHOSTTY_KEY_Y: "y",
GHOSTTY_KEY_Z: "z",
// Symbols
GHOSTTY_KEY_APOSTROPHE: "'",
GHOSTTY_KEY_BACKSLASH: "\\",
GHOSTTY_KEY_COMMA: ",",
GHOSTTY_KEY_EQUAL: "=",
GHOSTTY_KEY_GRAVE_ACCENT: "`",
GHOSTTY_KEY_LEFT_BRACKET: "[",
GHOSTTY_KEY_MINUS: "-",
GHOSTTY_KEY_PERIOD: ".",
GHOSTTY_KEY_RIGHT_BRACKET: "]",
GHOSTTY_KEY_SEMICOLON: ";",
GHOSTTY_KEY_SLASH: "/",
// Function keys
GHOSTTY_KEY_UP: .upArrow,
GHOSTTY_KEY_DOWN: .downArrow,
GHOSTTY_KEY_LEFT: .leftArrow,
GHOSTTY_KEY_RIGHT: .rightArrow,
GHOSTTY_KEY_ARROW_UP: .upArrow,
GHOSTTY_KEY_ARROW_DOWN: .downArrow,
GHOSTTY_KEY_ARROW_LEFT: .leftArrow,
GHOSTTY_KEY_ARROW_RIGHT: .rightArrow,
GHOSTTY_KEY_HOME: .home,
GHOSTTY_KEY_END: .end,
GHOSTTY_KEY_DELETE: .delete,
@ -153,104 +89,22 @@ extension Ghostty {
GHOSTTY_KEY_ENTER: .return,
GHOSTTY_KEY_TAB: .tab,
GHOSTTY_KEY_BACKSPACE: .delete,
]
static let asciiToKey: [UInt8 : ghostty_input_key_e] = [
// 0-9
0x30: GHOSTTY_KEY_ZERO,
0x31: GHOSTTY_KEY_ONE,
0x32: GHOSTTY_KEY_TWO,
0x33: GHOSTTY_KEY_THREE,
0x34: GHOSTTY_KEY_FOUR,
0x35: GHOSTTY_KEY_FIVE,
0x36: GHOSTTY_KEY_SIX,
0x37: GHOSTTY_KEY_SEVEN,
0x38: GHOSTTY_KEY_EIGHT,
0x39: GHOSTTY_KEY_NINE,
// A-Z
0x41: GHOSTTY_KEY_A,
0x42: GHOSTTY_KEY_B,
0x43: GHOSTTY_KEY_C,
0x44: GHOSTTY_KEY_D,
0x45: GHOSTTY_KEY_E,
0x46: GHOSTTY_KEY_F,
0x47: GHOSTTY_KEY_G,
0x48: GHOSTTY_KEY_H,
0x49: GHOSTTY_KEY_I,
0x4A: GHOSTTY_KEY_J,
0x4B: GHOSTTY_KEY_K,
0x4C: GHOSTTY_KEY_L,
0x4D: GHOSTTY_KEY_M,
0x4E: GHOSTTY_KEY_N,
0x4F: GHOSTTY_KEY_O,
0x50: GHOSTTY_KEY_P,
0x51: GHOSTTY_KEY_Q,
0x52: GHOSTTY_KEY_R,
0x53: GHOSTTY_KEY_S,
0x54: GHOSTTY_KEY_T,
0x55: GHOSTTY_KEY_U,
0x56: GHOSTTY_KEY_V,
0x57: GHOSTTY_KEY_W,
0x58: GHOSTTY_KEY_X,
0x59: GHOSTTY_KEY_Y,
0x5A: GHOSTTY_KEY_Z,
// a-z
0x61: GHOSTTY_KEY_A,
0x62: GHOSTTY_KEY_B,
0x63: GHOSTTY_KEY_C,
0x64: GHOSTTY_KEY_D,
0x65: GHOSTTY_KEY_E,
0x66: GHOSTTY_KEY_F,
0x67: GHOSTTY_KEY_G,
0x68: GHOSTTY_KEY_H,
0x69: GHOSTTY_KEY_I,
0x6A: GHOSTTY_KEY_J,
0x6B: GHOSTTY_KEY_K,
0x6C: GHOSTTY_KEY_L,
0x6D: GHOSTTY_KEY_M,
0x6E: GHOSTTY_KEY_N,
0x6F: GHOSTTY_KEY_O,
0x70: GHOSTTY_KEY_P,
0x71: GHOSTTY_KEY_Q,
0x72: GHOSTTY_KEY_R,
0x73: GHOSTTY_KEY_S,
0x74: GHOSTTY_KEY_T,
0x75: GHOSTTY_KEY_U,
0x76: GHOSTTY_KEY_V,
0x77: GHOSTTY_KEY_W,
0x78: GHOSTTY_KEY_X,
0x79: GHOSTTY_KEY_Y,
0x7A: GHOSTTY_KEY_Z,
// Symbols
0x27: GHOSTTY_KEY_APOSTROPHE,
0x5C: GHOSTTY_KEY_BACKSLASH,
0x2C: GHOSTTY_KEY_COMMA,
0x3D: GHOSTTY_KEY_EQUAL,
0x60: GHOSTTY_KEY_GRAVE_ACCENT,
0x5B: GHOSTTY_KEY_LEFT_BRACKET,
0x2D: GHOSTTY_KEY_MINUS,
0x2E: GHOSTTY_KEY_PERIOD,
0x5D: GHOSTTY_KEY_RIGHT_BRACKET,
0x3B: GHOSTTY_KEY_SEMICOLON,
0x2F: GHOSTTY_KEY_SLASH,
GHOSTTY_KEY_SPACE: .space,
]
// Mapping of event keyCode to ghostty input key values. This is cribbed from
// glfw mostly since we started as a glfw-based app way back in the day!
static let keycodeToKey: [UInt16 : ghostty_input_key_e] = [
0x1D: GHOSTTY_KEY_ZERO,
0x12: GHOSTTY_KEY_ONE,
0x13: GHOSTTY_KEY_TWO,
0x14: GHOSTTY_KEY_THREE,
0x15: GHOSTTY_KEY_FOUR,
0x17: GHOSTTY_KEY_FIVE,
0x16: GHOSTTY_KEY_SIX,
0x1A: GHOSTTY_KEY_SEVEN,
0x1C: GHOSTTY_KEY_EIGHT,
0x19: GHOSTTY_KEY_NINE,
0x1D: GHOSTTY_KEY_DIGIT_0,
0x12: GHOSTTY_KEY_DIGIT_1,
0x13: GHOSTTY_KEY_DIGIT_2,
0x14: GHOSTTY_KEY_DIGIT_3,
0x15: GHOSTTY_KEY_DIGIT_4,
0x17: GHOSTTY_KEY_DIGIT_5,
0x16: GHOSTTY_KEY_DIGIT_6,
0x1A: GHOSTTY_KEY_DIGIT_7,
0x1C: GHOSTTY_KEY_DIGIT_8,
0x19: GHOSTTY_KEY_DIGIT_9,
0x00: GHOSTTY_KEY_A,
0x0B: GHOSTTY_KEY_B,
0x08: GHOSTTY_KEY_C,
@ -278,22 +132,22 @@ extension Ghostty {
0x10: GHOSTTY_KEY_Y,
0x06: GHOSTTY_KEY_Z,
0x27: GHOSTTY_KEY_APOSTROPHE,
0x27: GHOSTTY_KEY_QUOTE,
0x2A: GHOSTTY_KEY_BACKSLASH,
0x2B: GHOSTTY_KEY_COMMA,
0x18: GHOSTTY_KEY_EQUAL,
0x32: GHOSTTY_KEY_GRAVE_ACCENT,
0x21: GHOSTTY_KEY_LEFT_BRACKET,
0x32: GHOSTTY_KEY_BACKQUOTE,
0x21: GHOSTTY_KEY_BRACKET_LEFT,
0x1B: GHOSTTY_KEY_MINUS,
0x2F: GHOSTTY_KEY_PERIOD,
0x1E: GHOSTTY_KEY_RIGHT_BRACKET,
0x1E: GHOSTTY_KEY_BRACKET_RIGHT,
0x29: GHOSTTY_KEY_SEMICOLON,
0x2C: GHOSTTY_KEY_SLASH,
0x33: GHOSTTY_KEY_BACKSPACE,
0x39: GHOSTTY_KEY_CAPS_LOCK,
0x75: GHOSTTY_KEY_DELETE,
0x7D: GHOSTTY_KEY_DOWN,
0x7D: GHOSTTY_KEY_ARROW_DOWN,
0x77: GHOSTTY_KEY_END,
0x24: GHOSTTY_KEY_ENTER,
0x35: GHOSTTY_KEY_ESCAPE,
@ -319,39 +173,39 @@ extension Ghostty {
0x5A: GHOSTTY_KEY_F20,
0x73: GHOSTTY_KEY_HOME,
0x72: GHOSTTY_KEY_INSERT,
0x7B: GHOSTTY_KEY_LEFT,
0x3A: GHOSTTY_KEY_LEFT_ALT,
0x3B: GHOSTTY_KEY_LEFT_CONTROL,
0x38: GHOSTTY_KEY_LEFT_SHIFT,
0x37: GHOSTTY_KEY_LEFT_SUPER,
0x7B: GHOSTTY_KEY_ARROW_LEFT,
0x3A: GHOSTTY_KEY_ALT_LEFT,
0x3B: GHOSTTY_KEY_CONTROL_LEFT,
0x38: GHOSTTY_KEY_SHIFT_LEFT,
0x37: GHOSTTY_KEY_META_LEFT,
0x47: GHOSTTY_KEY_NUM_LOCK,
0x79: GHOSTTY_KEY_PAGE_DOWN,
0x74: GHOSTTY_KEY_PAGE_UP,
0x7C: GHOSTTY_KEY_RIGHT,
0x3D: GHOSTTY_KEY_RIGHT_ALT,
0x3E: GHOSTTY_KEY_RIGHT_CONTROL,
0x3C: GHOSTTY_KEY_RIGHT_SHIFT,
0x36: GHOSTTY_KEY_RIGHT_SUPER,
0x7C: GHOSTTY_KEY_ARROW_RIGHT,
0x3D: GHOSTTY_KEY_ALT_RIGHT,
0x3E: GHOSTTY_KEY_CONTROL_RIGHT,
0x3C: GHOSTTY_KEY_SHIFT_RIGHT,
0x36: GHOSTTY_KEY_META_RIGHT,
0x31: GHOSTTY_KEY_SPACE,
0x30: GHOSTTY_KEY_TAB,
0x7E: GHOSTTY_KEY_UP,
0x7E: GHOSTTY_KEY_ARROW_UP,
0x52: GHOSTTY_KEY_KP_0,
0x53: GHOSTTY_KEY_KP_1,
0x54: GHOSTTY_KEY_KP_2,
0x55: GHOSTTY_KEY_KP_3,
0x56: GHOSTTY_KEY_KP_4,
0x57: GHOSTTY_KEY_KP_5,
0x58: GHOSTTY_KEY_KP_6,
0x59: GHOSTTY_KEY_KP_7,
0x5B: GHOSTTY_KEY_KP_8,
0x5C: GHOSTTY_KEY_KP_9,
0x45: GHOSTTY_KEY_KP_ADD,
0x41: GHOSTTY_KEY_KP_DECIMAL,
0x4B: GHOSTTY_KEY_KP_DIVIDE,
0x4C: GHOSTTY_KEY_KP_ENTER,
0x51: GHOSTTY_KEY_KP_EQUAL,
0x43: GHOSTTY_KEY_KP_MULTIPLY,
0x4E: GHOSTTY_KEY_KP_SUBTRACT,
0x52: GHOSTTY_KEY_NUMPAD_0,
0x53: GHOSTTY_KEY_NUMPAD_1,
0x54: GHOSTTY_KEY_NUMPAD_2,
0x55: GHOSTTY_KEY_NUMPAD_3,
0x56: GHOSTTY_KEY_NUMPAD_4,
0x57: GHOSTTY_KEY_NUMPAD_5,
0x58: GHOSTTY_KEY_NUMPAD_6,
0x59: GHOSTTY_KEY_NUMPAD_7,
0x5B: GHOSTTY_KEY_NUMPAD_8,
0x5C: GHOSTTY_KEY_NUMPAD_9,
0x45: GHOSTTY_KEY_NUMPAD_ADD,
0x41: GHOSTTY_KEY_NUMPAD_DECIMAL,
0x4B: GHOSTTY_KEY_NUMPAD_DIVIDE,
0x4C: GHOSTTY_KEY_NUMPAD_ENTER,
0x51: GHOSTTY_KEY_NUMPAD_EQUAL,
0x43: GHOSTTY_KEY_NUMPAD_MULTIPLY,
0x4E: GHOSTTY_KEY_NUMPAD_SUBTRACT,
];
}

View File

@ -65,6 +65,6 @@ extension NSEvent {
return self.characters(byApplyingModifiers: modifierFlags.subtracting(.control))
}
return nil
return characters
}
}

View File

@ -1043,12 +1043,16 @@ extension Ghostty {
}
// If this event as-is would result in a key binding then we send it.
if let surface,
ghostty_surface_key_is_binding(
surface,
event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS)) {
self.keyDown(with: event)
return true
if let surface {
var ghosttyEvent = event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS)
let match = (event.characters ?? "").withCString { ptr in
ghosttyEvent.text = ptr
return ghostty_surface_key_is_binding(surface, ghosttyEvent)
}
if match {
self.keyDown(with: event)
return true
}
}
let equivalent: String
@ -1062,6 +1066,16 @@ extension Ghostty {
equivalent = "\r"
case "/":
// Treat C-/ as C-_. We do this because C-/ makes macOS make a beep
// sound and we don't like the beep sound.
if (!event.modifierFlags.contains(.control) ||
!event.modifierFlags.isDisjoint(with: [.shift, .command, .option])) {
return false
}
equivalent = "_"
default:
// It looks like some part of AppKit sometimes generates synthetic NSEvents
// with a zero timestamp. We never process these at this point. Concretely,

View File

@ -1761,6 +1761,8 @@ pub fn keyEventIsBinding(
// sequences) or the root set.
const set = self.keyboard.bindings orelse &self.config.keybind.set;
// log.warn("text keyEventIsBinding event={} match={}", .{ event, set.getEvent(event) != null });
// If we have a keybinding for this event then we return true.
return set.getEvent(event) != null;
}
@ -1870,7 +1872,7 @@ pub fn keyCallback(
// Process the cursor state logic. This will update the cursor shape if
// needed, depending on the key state.
if ((SurfaceMouse{
.physical_key = event.physical_key,
.physical_key = event.key,
.mouse_event = self.io.terminal.flags.mouse_event,
.mouse_shape = self.io.terminal.mouse_shape,
.mods = self.mouse.mods,
@ -1895,12 +1897,12 @@ pub fn keyCallback(
// if we didn't have a previous event and this is a release
// event then we just want to set it to null.
const prev = self.pressed_key orelse break :event null;
if (prev.key == copy.key) copy.key = .invalid;
if (prev.key == copy.key) copy.key = .unidentified;
}
// If our key is invalid and we have no mods, then we're done!
// This helps catch the state that we naturally released all keys.
if (copy.key == .invalid and copy.mods.empty()) break :event null;
if (copy.key == .unidentified and copy.mods.empty()) break :event null;
break :event copy;
};
@ -2295,7 +2297,7 @@ pub fn focusCallback(self: *Surface, focused: bool) !void {
pressed_key.action = .release;
// Release the full key first
if (pressed_key.key != .invalid) {
if (pressed_key.key != .unidentified) {
assert(self.keyCallback(pressed_key) catch |err| err: {
log.warn("error releasing key on focus loss err={}", .{err});
break :err .ignored;
@ -2315,8 +2317,15 @@ pub fn focusCallback(self: *Surface, focused: bool) !void {
if (@field(pressed_key.mods, key)) {
@field(pressed_key.mods, key) = false;
inline for (&.{ "right", "left" }) |side| {
const keyname = if (comptime std.mem.eql(u8, key, "ctrl")) "control" else key;
pressed_key.key = @field(input.Key, side ++ "_" ++ keyname);
const keyname = comptime keyname: {
break :keyname if (std.mem.eql(u8, key, "ctrl"))
"control"
else if (std.mem.eql(u8, key, "super"))
"meta"
else
key;
};
pressed_key.key = @field(input.Key, keyname ++ "_" ++ side);
if (pressed_key.key != original_key) {
assert(self.keyCallback(pressed_key) catch |err| err: {
log.warn("error releasing key on focus loss err={}", .{err});

View File

@ -92,42 +92,12 @@ pub const App = struct {
// We want to get the physical unmapped key to process keybinds.
const physical_key = keycode: for (input.keycodes.entries) |entry| {
if (entry.native == self.keycode) break :keycode entry.key;
} else .invalid;
// If the resulting text has length 1 then we can take its key
// and attempt to translate it to a key enum and call the key callback.
// If the length is greater than 1 then we're going to call the
// charCallback.
//
// We also only do key translation if this is not a dead key.
const key = if (!self.composing) key: {
// If our physical key is a keypad key, we use that.
if (physical_key.keypad()) break :key physical_key;
// A completed key. If the length of the key is one then we can
// attempt to translate it to a key enum and call the key
// callback. First try plain ASCII.
if (text.len > 0) {
if (input.Key.fromASCII(text[0])) |key| {
break :key key;
}
}
// If the above doesn't work, we use the unmodified value.
if (std.math.cast(u8, unshifted_codepoint)) |ascii| {
if (input.Key.fromASCII(ascii)) |key| {
break :key key;
}
}
break :key physical_key;
} else .invalid;
} else .unidentified;
// Build our final key event
return .{
.action = self.action,
.key = key,
.physical_key = physical_key,
.key = physical_key,
.mods = self.mods,
.consumed_mods = self.consumed_mods,
.composing = self.composing,

View File

@ -966,46 +966,46 @@ pub const Surface = struct {
.repeat => .repeat,
};
const key: input.Key = switch (glfw_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,
.zero => .zero,
.one => .one,
.two => .two,
.three => .three,
.four => .four,
.five => .five,
.six => .six,
.seven => .seven,
.eight => .eight,
.nine => .nine,
.up => .up,
.down => .down,
.right => .right,
.left => .left,
.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,
.zero => .digit_0,
.one => .digit_1,
.two => .digit_2,
.three => .digit_3,
.four => .digit_4,
.five => .digit_5,
.six => .digit_6,
.seven => .digit_7,
.eight => .digit_8,
.nine => .digit_9,
.up => .arrow_up,
.down => .arrow_down,
.right => .arrow_right,
.left => .arrow_left,
.home => .home,
.end => .end,
.page_up => .page_up,
@ -1036,34 +1036,34 @@ pub const Surface = struct {
.F23 => .f23,
.F24 => .f24,
.F25 => .f25,
.kp_0 => .kp_0,
.kp_1 => .kp_1,
.kp_2 => .kp_2,
.kp_3 => .kp_3,
.kp_4 => .kp_4,
.kp_5 => .kp_5,
.kp_6 => .kp_6,
.kp_7 => .kp_7,
.kp_8 => .kp_8,
.kp_9 => .kp_9,
.kp_decimal => .kp_decimal,
.kp_divide => .kp_divide,
.kp_multiply => .kp_multiply,
.kp_subtract => .kp_subtract,
.kp_add => .kp_add,
.kp_enter => .kp_enter,
.kp_equal => .kp_equal,
.grave_accent => .grave_accent,
.kp_0 => .numpad_0,
.kp_1 => .numpad_1,
.kp_2 => .numpad_2,
.kp_3 => .numpad_3,
.kp_4 => .numpad_4,
.kp_5 => .numpad_5,
.kp_6 => .numpad_6,
.kp_7 => .numpad_7,
.kp_8 => .numpad_8,
.kp_9 => .numpad_9,
.kp_decimal => .numpad_decimal,
.kp_divide => .numpad_divide,
.kp_multiply => .numpad_multiply,
.kp_subtract => .numpad_subtract,
.kp_add => .numpad_add,
.kp_enter => .numpad_enter,
.kp_equal => .numpad_equal,
.grave_accent => .backquote,
.minus => .minus,
.equal => .equal,
.space => .space,
.semicolon => .semicolon,
.apostrophe => .apostrophe,
.apostrophe => .quote,
.comma => .comma,
.period => .period,
.slash => .slash,
.left_bracket => .left_bracket,
.right_bracket => .right_bracket,
.left_bracket => .bracket_left,
.right_bracket => .bracket_right,
.backslash => .backslash,
.enter => .enter,
.tab => .tab,
@ -1075,20 +1075,20 @@ pub const Surface = struct {
.num_lock => .num_lock,
.print_screen => .print_screen,
.pause => .pause,
.left_shift => .left_shift,
.left_control => .left_control,
.left_alt => .left_alt,
.left_super => .left_super,
.right_shift => .right_shift,
.right_control => .right_control,
.right_alt => .right_alt,
.right_super => .right_super,
.left_shift => .shift_left,
.left_control => .control_left,
.left_alt => .alt_left,
.left_super => .meta_left,
.right_shift => .shift_right,
.right_control => .control_right,
.right_alt => .alt_right,
.right_super => .meta_right,
.menu => .context_menu,
.menu,
.world_1,
.world_2,
.unknown,
=> .invalid,
=> .unidentified,
};
// This is a hack for GLFW. We require our apprts to send both
@ -1108,7 +1108,6 @@ pub const Surface = struct {
const key_event: input.KeyEvent = .{
.action = action,
.key = key,
.physical_key = key,
.mods = mods,
.consumed_mods = .{},
.composing = false,

View File

@ -1840,7 +1840,7 @@ pub fn keyEvent(
// (These are keybinds explicitly marked as requesting physical mapping).
const physical_key = keycode: for (input.keycodes.entries) |entry| {
if (entry.native == keycode) break :keycode entry.key;
} else .invalid;
} else .unidentified;
// Get our modifier for the event
const mods: input.Mods = gtk_key.eventMods(
@ -1861,52 +1861,6 @@ pub fn keyEvent(
break :consumed gtk_key.translateMods(@bitCast(masked));
};
// If we're not in a dead key state, we want to translate our text
// to some input.Key.
const key = if (!self.im_composing) key: {
// First, try to convert the keyval directly to a key. This allows the
// use of key remapping and identification of keypad numerics (as
// opposed to their ASCII counterparts)
if (gtk_key.keyFromKeyval(keyval)) |key| {
break :key key;
}
// A completed key. If the length of the key is one then we can
// attempt to translate it to a key enum and call the key
// callback. First try plain ASCII.
if (self.im_len > 0) {
if (input.Key.fromASCII(self.im_buf[0])) |key| {
break :key key;
}
}
// If that doesn't work then we try to translate the kevval..
if (keyval_unicode != 0) {
if (std.math.cast(u8, keyval_unicode)) |byte| {
if (input.Key.fromASCII(byte)) |key| {
break :key key;
}
}
}
// If that doesn't work we use the unshifted value...
if (std.math.cast(u8, keyval_unicode_unshifted)) |ascii| {
if (input.Key.fromASCII(ascii)) |key| {
break :key key;
}
}
// If we have im text then this is invalid. This means that
// the keypress generated some character that we don't know about
// in our key enum. We don't want to use the physical key because
// it can be simply wrong. For example on "Turkish Q" the "i" key
// on a US layout results in "ı" which is not the same as "i" so
// we shouldn't use the physical key.
if (self.im_len > 0 or keyval_unicode_unshifted != 0) break :key .invalid;
break :key physical_key;
} else .invalid;
// log.debug("key pressed key={} keyval={x} physical_key={} composing={} text_len={} mods={}", .{
// key,
// keyval,
@ -1936,8 +1890,7 @@ pub fn keyEvent(
// Invoke the core Ghostty logic to handle this input.
const effect = self.core_surface.keyCallback(.{
.action = action,
.key = key,
.physical_key = physical_key,
.key = physical_key,
.mods = mods,
.consumed_mods = consumed_mods,
.composing = self.im_composing,
@ -2088,8 +2041,7 @@ fn gtkInputCommit(
// invalid key, which should produce no PTY encoding).
_ = self.core_surface.keyCallback(.{
.action = .press,
.key = .invalid,
.physical_key = .invalid,
.key = .unidentified,
.mods = .{},
.consumed_mods = .{},
.composing = false,

View File

@ -21,7 +21,7 @@ pub fn accelFromTrigger(buf: []u8, trigger: input.Binding.Trigger) !?[:0]const u
// Write our key
switch (trigger.key) {
.physical, .translated => |k| {
.physical => |k| {
const keyval = keyvalFromKey(k) orelse return null;
try writer.writeAll(std.mem.span(gdk.keyvalName(keyval) orelse return null));
},
@ -122,42 +122,42 @@ pub fn eventMods(
// if only the modifier key is pressed, but our core logic
// relies on it.
switch (physical_key) {
.left_shift => {
.shift_left => {
mods.shift = action != .release;
mods.sides.shift = .left;
},
.right_shift => {
.shift_right => {
mods.shift = action != .release;
mods.sides.shift = .right;
},
.left_control => {
.control_left => {
mods.ctrl = action != .release;
mods.sides.ctrl = .left;
},
.right_control => {
.control_right => {
mods.ctrl = action != .release;
mods.sides.ctrl = .right;
},
.left_alt => {
.alt_left => {
mods.alt = action != .release;
mods.sides.alt = .left;
},
.right_alt => {
.alt_right => {
mods.alt = action != .release;
mods.sides.alt = .right;
},
.left_super => {
.meta_left => {
mods.super = action != .release;
mods.sides.super = .left;
},
.right_super => {
.meta_right => {
mods.super = action != .release;
mods.sides.super = .right;
},
@ -182,7 +182,7 @@ pub fn keyvalFromKey(key: input.Key) ?c_uint {
switch (key) {
inline else => |key_comptime| {
return comptime value: {
@setEvalBranchQuota(10_000);
@setEvalBranchQuota(50_000);
for (keymap) |entry| {
if (entry[1] == key_comptime) break :value entry[0];
}
@ -199,7 +199,7 @@ test "accelFromTrigger" {
try testing.expectEqualStrings("<Super>q", (try accelFromTrigger(&buf, .{
.mods = .{ .super = true },
.key = .{ .translated = .q },
.key = .{ .unicode = 'q' },
})).?);
try testing.expectEqualStrings("<Shift><Ctrl><Alt><Super>backslash", (try accelFromTrigger(&buf, .{
@ -213,61 +213,61 @@ test "accelFromTrigger" {
const RawEntry = struct { c_uint, input.Key };
const keymap: []const RawEntry = &.{
.{ gdk.KEY_a, .a },
.{ gdk.KEY_b, .b },
.{ gdk.KEY_c, .c },
.{ gdk.KEY_d, .d },
.{ gdk.KEY_e, .e },
.{ gdk.KEY_f, .f },
.{ gdk.KEY_g, .g },
.{ gdk.KEY_h, .h },
.{ gdk.KEY_i, .i },
.{ gdk.KEY_j, .j },
.{ gdk.KEY_k, .k },
.{ gdk.KEY_l, .l },
.{ gdk.KEY_m, .m },
.{ gdk.KEY_n, .n },
.{ gdk.KEY_o, .o },
.{ gdk.KEY_p, .p },
.{ gdk.KEY_q, .q },
.{ gdk.KEY_r, .r },
.{ gdk.KEY_s, .s },
.{ gdk.KEY_t, .t },
.{ gdk.KEY_u, .u },
.{ gdk.KEY_v, .v },
.{ gdk.KEY_w, .w },
.{ gdk.KEY_x, .x },
.{ gdk.KEY_y, .y },
.{ gdk.KEY_z, .z },
.{ gdk.KEY_a, .key_a },
.{ gdk.KEY_b, .key_b },
.{ gdk.KEY_c, .key_c },
.{ gdk.KEY_d, .key_d },
.{ gdk.KEY_e, .key_e },
.{ gdk.KEY_f, .key_f },
.{ gdk.KEY_g, .key_g },
.{ gdk.KEY_h, .key_h },
.{ gdk.KEY_i, .key_i },
.{ gdk.KEY_j, .key_j },
.{ gdk.KEY_k, .key_k },
.{ gdk.KEY_l, .key_l },
.{ gdk.KEY_m, .key_m },
.{ gdk.KEY_n, .key_n },
.{ gdk.KEY_o, .key_o },
.{ gdk.KEY_p, .key_p },
.{ gdk.KEY_q, .key_q },
.{ gdk.KEY_r, .key_r },
.{ gdk.KEY_s, .key_s },
.{ gdk.KEY_t, .key_t },
.{ gdk.KEY_u, .key_u },
.{ gdk.KEY_v, .key_v },
.{ gdk.KEY_w, .key_w },
.{ gdk.KEY_x, .key_x },
.{ gdk.KEY_y, .key_y },
.{ gdk.KEY_z, .key_z },
.{ gdk.KEY_0, .zero },
.{ gdk.KEY_1, .one },
.{ gdk.KEY_2, .two },
.{ gdk.KEY_3, .three },
.{ gdk.KEY_4, .four },
.{ gdk.KEY_5, .five },
.{ gdk.KEY_6, .six },
.{ gdk.KEY_7, .seven },
.{ gdk.KEY_8, .eight },
.{ gdk.KEY_9, .nine },
.{ gdk.KEY_0, .digit_0 },
.{ gdk.KEY_1, .digit_1 },
.{ gdk.KEY_2, .digit_2 },
.{ gdk.KEY_3, .digit_3 },
.{ gdk.KEY_4, .digit_4 },
.{ gdk.KEY_5, .digit_5 },
.{ gdk.KEY_6, .digit_6 },
.{ gdk.KEY_7, .digit_7 },
.{ gdk.KEY_8, .digit_8 },
.{ gdk.KEY_9, .digit_9 },
.{ gdk.KEY_semicolon, .semicolon },
.{ gdk.KEY_space, .space },
.{ gdk.KEY_apostrophe, .apostrophe },
.{ gdk.KEY_apostrophe, .quote },
.{ gdk.KEY_comma, .comma },
.{ gdk.KEY_grave, .grave_accent },
.{ gdk.KEY_grave, .backquote },
.{ gdk.KEY_period, .period },
.{ gdk.KEY_slash, .slash },
.{ gdk.KEY_minus, .minus },
.{ gdk.KEY_equal, .equal },
.{ gdk.KEY_bracketleft, .left_bracket },
.{ gdk.KEY_bracketright, .right_bracket },
.{ gdk.KEY_bracketleft, .bracket_left },
.{ gdk.KEY_bracketright, .bracket_right },
.{ gdk.KEY_backslash, .backslash },
.{ gdk.KEY_Up, .up },
.{ gdk.KEY_Down, .down },
.{ gdk.KEY_Right, .right },
.{ gdk.KEY_Left, .left },
.{ gdk.KEY_Up, .arrow_up },
.{ gdk.KEY_Down, .arrow_down },
.{ gdk.KEY_Right, .arrow_right },
.{ gdk.KEY_Left, .arrow_left },
.{ gdk.KEY_Home, .home },
.{ gdk.KEY_End, .end },
.{ gdk.KEY_Insert, .insert },
@ -310,45 +310,45 @@ const keymap: []const RawEntry = &.{
.{ gdk.KEY_F24, .f24 },
.{ gdk.KEY_F25, .f25 },
.{ gdk.KEY_KP_0, .kp_0 },
.{ gdk.KEY_KP_1, .kp_1 },
.{ gdk.KEY_KP_2, .kp_2 },
.{ gdk.KEY_KP_3, .kp_3 },
.{ gdk.KEY_KP_4, .kp_4 },
.{ gdk.KEY_KP_5, .kp_5 },
.{ gdk.KEY_KP_6, .kp_6 },
.{ gdk.KEY_KP_7, .kp_7 },
.{ gdk.KEY_KP_8, .kp_8 },
.{ gdk.KEY_KP_9, .kp_9 },
.{ gdk.KEY_KP_Decimal, .kp_decimal },
.{ gdk.KEY_KP_Divide, .kp_divide },
.{ gdk.KEY_KP_Multiply, .kp_multiply },
.{ gdk.KEY_KP_Subtract, .kp_subtract },
.{ gdk.KEY_KP_Add, .kp_add },
.{ gdk.KEY_KP_Enter, .kp_enter },
.{ gdk.KEY_KP_Equal, .kp_equal },
.{ gdk.KEY_KP_0, .numpad_0 },
.{ gdk.KEY_KP_1, .numpad_1 },
.{ gdk.KEY_KP_2, .numpad_2 },
.{ gdk.KEY_KP_3, .numpad_3 },
.{ gdk.KEY_KP_4, .numpad_4 },
.{ gdk.KEY_KP_5, .numpad_5 },
.{ gdk.KEY_KP_6, .numpad_6 },
.{ gdk.KEY_KP_7, .numpad_7 },
.{ gdk.KEY_KP_8, .numpad_8 },
.{ gdk.KEY_KP_9, .numpad_9 },
.{ gdk.KEY_KP_Decimal, .numpad_decimal },
.{ gdk.KEY_KP_Divide, .numpad_divide },
.{ gdk.KEY_KP_Multiply, .numpad_multiply },
.{ gdk.KEY_KP_Subtract, .numpad_subtract },
.{ gdk.KEY_KP_Add, .numpad_add },
.{ gdk.KEY_KP_Enter, .numpad_enter },
.{ gdk.KEY_KP_Equal, .numpad_equal },
.{ gdk.KEY_KP_Separator, .kp_separator },
.{ gdk.KEY_KP_Left, .kp_left },
.{ gdk.KEY_KP_Right, .kp_right },
.{ gdk.KEY_KP_Up, .kp_up },
.{ gdk.KEY_KP_Down, .kp_down },
.{ gdk.KEY_KP_Page_Up, .kp_page_up },
.{ gdk.KEY_KP_Page_Down, .kp_page_down },
.{ gdk.KEY_KP_Home, .kp_home },
.{ gdk.KEY_KP_End, .kp_end },
.{ gdk.KEY_KP_Insert, .kp_insert },
.{ gdk.KEY_KP_Delete, .kp_delete },
.{ gdk.KEY_KP_Begin, .kp_begin },
.{ gdk.KEY_KP_Separator, .numpad_separator },
.{ gdk.KEY_KP_Left, .numpad_left },
.{ gdk.KEY_KP_Right, .numpad_right },
.{ gdk.KEY_KP_Up, .numpad_up },
.{ gdk.KEY_KP_Down, .numpad_down },
.{ gdk.KEY_KP_Page_Up, .numpad_page_up },
.{ gdk.KEY_KP_Page_Down, .numpad_page_down },
.{ gdk.KEY_KP_Home, .numpad_home },
.{ gdk.KEY_KP_End, .numpad_end },
.{ gdk.KEY_KP_Insert, .numpad_insert },
.{ gdk.KEY_KP_Delete, .numpad_delete },
.{ gdk.KEY_KP_Begin, .numpad_begin },
.{ gdk.KEY_Shift_L, .left_shift },
.{ gdk.KEY_Control_L, .left_control },
.{ gdk.KEY_Alt_L, .left_alt },
.{ gdk.KEY_Super_L, .left_super },
.{ gdk.KEY_Shift_R, .right_shift },
.{ gdk.KEY_Control_R, .right_control },
.{ gdk.KEY_Alt_R, .right_alt },
.{ gdk.KEY_Super_R, .right_super },
.{ gdk.KEY_Shift_L, .shift_left },
.{ gdk.KEY_Control_L, .control_left },
.{ gdk.KEY_Alt_L, .alt_left },
.{ gdk.KEY_Super_L, .meta_left },
.{ gdk.KEY_Shift_R, .shift_right },
.{ gdk.KEY_Control_R, .control_right },
.{ gdk.KEY_Alt_R, .alt_right },
.{ gdk.KEY_Super_R, .meta_right },
// TODO: media keys
};

View File

@ -155,14 +155,12 @@ const ChordBinding = struct {
while (l_trigger != null and r_trigger != null) {
const lhs_key: c_int = blk: {
switch (l_trigger.?.data.key) {
.translated => |key| break :blk @intFromEnum(key),
.physical => |key| break :blk @intFromEnum(key),
.unicode => |key| break :blk @intCast(key),
}
};
const rhs_key: c_int = blk: {
switch (r_trigger.?.data.key) {
.translated => |key| break :blk @intFromEnum(key),
.physical => |key| break :blk @intFromEnum(key),
.unicode => |key| break :blk @intCast(key),
}
@ -254,8 +252,7 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 {
result = win.printSegment(.{ .text = " + " }, .{ .col_offset = result.col });
}
const key = switch (trigger.data.key) {
.translated => |k| try std.fmt.allocPrint(alloc, "{s}", .{@tagName(k)}),
.physical => |k| try std.fmt.allocPrint(alloc, "physical:{s}", .{@tagName(k)}),
.physical => |k| try std.fmt.allocPrint(alloc, "{s}", .{@tagName(k)}),
.unicode => |c| try std.fmt.allocPrint(alloc, "{u}", .{c}),
};
result = win.printSegment(.{ .text = key }, .{ .col_offset = result.col });
@ -297,8 +294,7 @@ fn iterateBindings(alloc: Allocator, iter: anytype, win: *const vaxis.Window) !s
if (t.mods.shift) try std.fmt.format(buf.writer(), "shift + ", .{});
switch (t.key) {
.translated => |k| try std.fmt.format(buf.writer(), "{s}", .{@tagName(k)}),
.physical => |k| try std.fmt.format(buf.writer(), "physical:{s}", .{@tagName(k)}),
.physical => |k| try std.fmt.format(buf.writer(), "{s}", .{@tagName(k)}),
.unicode => |c| try std.fmt.format(buf.writer(), "{u}", .{c}),
}

View File

@ -929,12 +929,46 @@ class: ?[:0]const u8 = null,
/// Trigger: `+`-separated list of keys and modifiers. Example: `ctrl+a`,
/// `ctrl+shift+b`, `up`.
///
/// Valid keys are currently only listed in the
/// [Ghostty source code](https://github.com/ghostty-org/ghostty/blob/d6e76858164d52cff460fedc61ddf2e560912d71/src/input/key.zig#L255).
/// This is a documentation limitation and we will improve this in the future.
/// A common gotcha is that numeric keys are written as words: e.g. `one`,
/// `two`, `three`, etc. and not `1`, `2`, `3`. This will also be improved in
/// the future.
/// If the key is a single Unicode codepoint, the trigger will match
/// any presses that produce that codepoint. These are impacted by
/// keyboard layouts. For example, `a` will match the `a` key on a
/// QWERTY keyboard, but will match the `q` key on a AZERTY keyboard
/// (assuming US physical layout).
///
/// For Unicode codepoints, matching is done by comparing the set of
/// modifiers with the unmodified codepoint. The unmodified codepoint is
/// sometimes called an "unshifted character" in other software, but all
/// modifiers are considered, not only shift. For example, `ctrl+a` will match
/// `a` but not `ctrl+shift+a` (which is `A` on a US keyboard).
///
/// Further, codepoint matching is case-insensitive and the unmodified
/// codepoint is always case folded for comparison. As a result,
/// `ctrl+A` configured will match when `ctrl+a` is pressed. Note that
/// this means some key combinations are impossible depending on keyboard
/// layout. For example, `ctrl+_` is impossible on a US keyboard because
/// `_` is `shift+-` and `ctrl+shift+-` is not equal to `ctrl+_` (because
/// the modifiers don't match!). More details on impossible key combinations
/// can be found at this excellent source written by Qt developers:
/// https://doc.qt.io/qt-6/qkeysequence.html#keyboard-layout-issues
///
/// Physical key codes can be specified by using any of the key codes
/// as specified by the [W3C specification](https://www.w3.org/TR/uievents-code/).
/// For example, `KeyA` will match the physical `a` key on a US standard
/// keyboard regardless of the keyboard layout. These are case-sensitive.
///
/// For aesthetic reasons, the w3c codes also support snake case. For
/// example, `key_a` is equivalent to `KeyA`. The only exceptions are
/// function keys, e.g. `F1` is `f1` (no underscore). This is a consequence
/// of our internal code using snake case but is purposely supported
/// and tested so it is safe to use. It allows an all-lowercase binding
/// which I find more aesthetically pleasing.
///
/// Function keys such as `insert`, `up`, `f5`, etc. are also specified
/// using the keys as specified by the previously linked W3C specification.
///
/// Physical keys always match with a higher priority than Unicode codepoints,
/// so if you specify both `a` and `KeyA`, the physical key will always be used
/// regardless of what order they are configured.
///
/// Valid modifiers are `shift`, `ctrl` (alias: `control`), `alt` (alias: `opt`,
/// `option`), and `super` (alias: `cmd`, `command`). You may use the modifier
@ -954,11 +988,6 @@ class: ?[:0]const u8 = null,
///
/// * only a single key input is allowed, `ctrl+a+b` is invalid.
///
/// * the key input can be prefixed with `physical:` to specify a
/// physical key mapping rather than a logical one. A physical key
/// mapping responds to the hardware keycode and not the keycode
/// translated by any system keyboard layouts. Example: "ctrl+physical:a"
///
/// You may also specify multiple triggers separated by `>` to require a
/// sequence of triggers to activate the action. For example,
/// `ctrl+a>n=new_window` will only trigger the `new_window` action if the
@ -4343,12 +4372,12 @@ pub const Keybinds = struct {
// keybinds for opening and reloading config
try self.set.put(
alloc,
.{ .key = .{ .translated = .comma }, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) },
.{ .key = .{ .unicode = ',' }, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) },
.{ .reload_config = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .comma }, .mods = inputpkg.ctrlOrSuper(.{}) },
.{ .key = .{ .unicode = ',' }, .mods = inputpkg.ctrlOrSuper(.{}) },
.{ .open_config = {} },
);
@ -4362,12 +4391,12 @@ pub const Keybinds = struct {
if (!builtin.target.os.tag.isDarwin()) {
try self.set.put(
alloc,
.{ .key = .{ .translated = .insert }, .mods = .{ .ctrl = true } },
.{ .key = .{ .physical = .insert }, .mods = .{ .ctrl = true } },
.{ .copy_to_clipboard = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .insert }, .mods = .{ .shift = true } },
.{ .key = .{ .physical = .insert }, .mods = .{ .shift = true } },
.{ .paste_from_clipboard = {} },
);
}
@ -4381,12 +4410,12 @@ pub const Keybinds = struct {
try self.set.put(
alloc,
.{ .key = .{ .translated = .c }, .mods = mods },
.{ .key = .{ .unicode = 'c' }, .mods = mods },
.{ .copy_to_clipboard = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .v }, .mods = mods },
.{ .key = .{ .unicode = 'v' }, .mods = mods },
.{ .paste_from_clipboard = {} },
);
}
@ -4397,84 +4426,84 @@ pub const Keybinds = struct {
// set the expected keybind for the menu.
try self.set.put(
alloc,
.{ .key = .{ .translated = .plus }, .mods = inputpkg.ctrlOrSuper(.{}) },
.{ .key = .{ .physical = .equal }, .mods = inputpkg.ctrlOrSuper(.{}) },
.{ .increase_font_size = 1 },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .equal }, .mods = inputpkg.ctrlOrSuper(.{}) },
.{ .key = .{ .unicode = '+' }, .mods = inputpkg.ctrlOrSuper(.{}) },
.{ .increase_font_size = 1 },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .minus }, .mods = inputpkg.ctrlOrSuper(.{}) },
.{ .key = .{ .unicode = '-' }, .mods = inputpkg.ctrlOrSuper(.{}) },
.{ .decrease_font_size = 1 },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .zero }, .mods = inputpkg.ctrlOrSuper(.{}) },
.{ .key = .{ .unicode = '0' }, .mods = inputpkg.ctrlOrSuper(.{}) },
.{ .reset_font_size = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .j }, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) },
.{ .key = .{ .unicode = 'j' }, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) },
.{ .write_screen_file = .paste },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .j }, .mods = inputpkg.ctrlOrSuper(.{ .shift = true, .alt = true }) },
.{ .key = .{ .unicode = 'j' }, .mods = inputpkg.ctrlOrSuper(.{ .shift = true, .alt = true }) },
.{ .write_screen_file = .open },
);
// Expand Selection
try self.set.putFlags(
alloc,
.{ .key = .{ .translated = .left }, .mods = .{ .shift = true } },
.{ .key = .{ .physical = .arrow_left }, .mods = .{ .shift = true } },
.{ .adjust_selection = .left },
.{ .performable = true },
);
try self.set.putFlags(
alloc,
.{ .key = .{ .translated = .right }, .mods = .{ .shift = true } },
.{ .key = .{ .physical = .arrow_right }, .mods = .{ .shift = true } },
.{ .adjust_selection = .right },
.{ .performable = true },
);
try self.set.putFlags(
alloc,
.{ .key = .{ .translated = .up }, .mods = .{ .shift = true } },
.{ .key = .{ .physical = .arrow_up }, .mods = .{ .shift = true } },
.{ .adjust_selection = .up },
.{ .performable = true },
);
try self.set.putFlags(
alloc,
.{ .key = .{ .translated = .down }, .mods = .{ .shift = true } },
.{ .key = .{ .physical = .arrow_down }, .mods = .{ .shift = true } },
.{ .adjust_selection = .down },
.{ .performable = true },
);
try self.set.putFlags(
alloc,
.{ .key = .{ .translated = .page_up }, .mods = .{ .shift = true } },
.{ .key = .{ .physical = .page_up }, .mods = .{ .shift = true } },
.{ .adjust_selection = .page_up },
.{ .performable = true },
);
try self.set.putFlags(
alloc,
.{ .key = .{ .translated = .page_down }, .mods = .{ .shift = true } },
.{ .key = .{ .physical = .page_down }, .mods = .{ .shift = true } },
.{ .adjust_selection = .page_down },
.{ .performable = true },
);
try self.set.putFlags(
alloc,
.{ .key = .{ .translated = .home }, .mods = .{ .shift = true } },
.{ .key = .{ .physical = .home }, .mods = .{ .shift = true } },
.{ .adjust_selection = .home },
.{ .performable = true },
);
try self.set.putFlags(
alloc,
.{ .key = .{ .translated = .end }, .mods = .{ .shift = true } },
.{ .key = .{ .physical = .end }, .mods = .{ .shift = true } },
.{ .adjust_selection = .end },
.{ .performable = true },
);
@ -4482,12 +4511,12 @@ pub const Keybinds = struct {
// Tabs common to all platforms
try self.set.put(
alloc,
.{ .key = .{ .translated = .tab }, .mods = .{ .ctrl = true, .shift = true } },
.{ .key = .{ .physical = .tab }, .mods = .{ .ctrl = true, .shift = true } },
.{ .previous_tab = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .tab }, .mods = .{ .ctrl = true } },
.{ .key = .{ .physical = .tab }, .mods = .{ .ctrl = true } },
.{ .next_tab = {} },
);
@ -4495,174 +4524,169 @@ pub const Keybinds = struct {
if (comptime !builtin.target.os.tag.isDarwin()) {
try self.set.put(
alloc,
.{ .key = .{ .translated = .n }, .mods = .{ .ctrl = true, .shift = true } },
.{ .key = .{ .unicode = 'n' }, .mods = .{ .ctrl = true, .shift = true } },
.{ .new_window = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .w }, .mods = .{ .ctrl = true, .shift = true } },
.{ .key = .{ .unicode = 'w' }, .mods = .{ .ctrl = true, .shift = true } },
.{ .close_surface = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .q }, .mods = .{ .ctrl = true, .shift = true } },
.{ .key = .{ .unicode = 'q' }, .mods = .{ .ctrl = true, .shift = true } },
.{ .quit = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .f4 }, .mods = .{ .alt = true } },
.{ .key = .{ .physical = .f4 }, .mods = .{ .alt = true } },
.{ .close_window = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .t }, .mods = .{ .ctrl = true, .shift = true } },
.{ .key = .{ .unicode = 't' }, .mods = .{ .ctrl = true, .shift = true } },
.{ .new_tab = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .w }, .mods = .{ .ctrl = true, .shift = true } },
.{ .key = .{ .unicode = 'w' }, .mods = .{ .ctrl = true, .shift = true } },
.{ .close_tab = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .left }, .mods = .{ .ctrl = true, .shift = true } },
.{ .key = .{ .physical = .arrow_left }, .mods = .{ .ctrl = true, .shift = true } },
.{ .previous_tab = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .right }, .mods = .{ .ctrl = true, .shift = true } },
.{ .key = .{ .physical = .arrow_right }, .mods = .{ .ctrl = true, .shift = true } },
.{ .next_tab = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .page_up }, .mods = .{ .ctrl = true } },
.{ .key = .{ .physical = .page_up }, .mods = .{ .ctrl = true } },
.{ .previous_tab = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .page_down }, .mods = .{ .ctrl = true } },
.{ .key = .{ .physical = .page_down }, .mods = .{ .ctrl = true } },
.{ .next_tab = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .o }, .mods = .{ .ctrl = true, .shift = true } },
.{ .key = .{ .unicode = 'o' }, .mods = .{ .ctrl = true, .shift = true } },
.{ .new_split = .right },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .e }, .mods = .{ .ctrl = true, .shift = true } },
.{ .key = .{ .unicode = 'e' }, .mods = .{ .ctrl = true, .shift = true } },
.{ .new_split = .down },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .left_bracket }, .mods = .{ .ctrl = true, .super = true } },
.{ .key = .{ .physical = .bracket_left }, .mods = .{ .ctrl = true, .super = true } },
.{ .goto_split = .previous },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .right_bracket }, .mods = .{ .ctrl = true, .super = true } },
.{ .key = .{ .physical = .bracket_right }, .mods = .{ .ctrl = true, .super = true } },
.{ .goto_split = .next },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .up }, .mods = .{ .ctrl = true, .alt = true } },
.{ .key = .{ .physical = .arrow_up }, .mods = .{ .ctrl = true, .alt = true } },
.{ .goto_split = .up },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .down }, .mods = .{ .ctrl = true, .alt = true } },
.{ .key = .{ .physical = .arrow_down }, .mods = .{ .ctrl = true, .alt = true } },
.{ .goto_split = .down },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .left }, .mods = .{ .ctrl = true, .alt = true } },
.{ .key = .{ .physical = .arrow_left }, .mods = .{ .ctrl = true, .alt = true } },
.{ .goto_split = .left },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .right }, .mods = .{ .ctrl = true, .alt = true } },
.{ .key = .{ .physical = .arrow_right }, .mods = .{ .ctrl = true, .alt = true } },
.{ .goto_split = .right },
);
// Resizing splits
try self.set.put(
alloc,
.{ .key = .{ .translated = .up }, .mods = .{ .super = true, .ctrl = true, .shift = true } },
.{ .key = .{ .physical = .arrow_up }, .mods = .{ .super = true, .ctrl = true, .shift = true } },
.{ .resize_split = .{ .up, 10 } },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .down }, .mods = .{ .super = true, .ctrl = true, .shift = true } },
.{ .key = .{ .physical = .arrow_down }, .mods = .{ .super = true, .ctrl = true, .shift = true } },
.{ .resize_split = .{ .down, 10 } },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .left }, .mods = .{ .super = true, .ctrl = true, .shift = true } },
.{ .key = .{ .physical = .arrow_left }, .mods = .{ .super = true, .ctrl = true, .shift = true } },
.{ .resize_split = .{ .left, 10 } },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .right }, .mods = .{ .super = true, .ctrl = true, .shift = true } },
.{ .key = .{ .physical = .arrow_right }, .mods = .{ .super = true, .ctrl = true, .shift = true } },
.{ .resize_split = .{ .right, 10 } },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .plus }, .mods = .{ .super = true, .ctrl = true, .shift = true } },
.{ .equalize_splits = {} },
);
// Viewport scrolling
try self.set.put(
alloc,
.{ .key = .{ .translated = .home }, .mods = .{ .shift = true } },
.{ .key = .{ .physical = .home }, .mods = .{ .shift = true } },
.{ .scroll_to_top = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .end }, .mods = .{ .shift = true } },
.{ .key = .{ .physical = .end }, .mods = .{ .shift = true } },
.{ .scroll_to_bottom = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .page_up }, .mods = .{ .shift = true } },
.{ .key = .{ .physical = .page_up }, .mods = .{ .shift = true } },
.{ .scroll_page_up = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .page_down }, .mods = .{ .shift = true } },
.{ .key = .{ .physical = .page_down }, .mods = .{ .shift = true } },
.{ .scroll_page_down = {} },
);
// Semantic prompts
try self.set.put(
alloc,
.{ .key = .{ .translated = .page_up }, .mods = .{ .shift = true, .ctrl = true } },
.{ .key = .{ .physical = .page_up }, .mods = .{ .shift = true, .ctrl = true } },
.{ .jump_to_prompt = -1 },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .page_down }, .mods = .{ .shift = true, .ctrl = true } },
.{ .key = .{ .physical = .page_down }, .mods = .{ .shift = true, .ctrl = true } },
.{ .jump_to_prompt = 1 },
);
// Inspector, matching Chromium
try self.set.put(
alloc,
.{ .key = .{ .translated = .i }, .mods = .{ .shift = true, .ctrl = true } },
.{ .key = .{ .unicode = 'i' }, .mods = .{ .shift = true, .ctrl = true } },
.{ .inspector = .toggle },
);
// Terminal
try self.set.put(
alloc,
.{ .key = .{ .translated = .a }, .mods = .{ .shift = true, .ctrl = true } },
.{ .key = .{ .unicode = 'a' }, .mods = .{ .shift = true, .ctrl = true } },
.{ .select_all = {} },
);
// Selection clipboard paste
try self.set.put(
alloc,
.{ .key = .{ .translated = .insert }, .mods = .{ .shift = true } },
.{ .key = .{ .physical = .insert }, .mods = .{ .shift = true } },
.{ .paste_from_selection = {} },
);
}
@ -4675,23 +4699,14 @@ pub const Keybinds = struct {
.{ .alt = true };
// Cmd+N for goto tab N
const start = @intFromEnum(inputpkg.Key.one);
const end = @intFromEnum(inputpkg.Key.eight);
var i: usize = start;
const start: u21 = '1';
const end: u21 = '8';
var i: u21 = start;
while (i <= end) : (i += 1) {
try self.set.put(
alloc,
.{
// 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)
.key = if (comptime builtin.target.os.tag.isDarwin())
.{ .physical = @enumFromInt(i) }
else
.{ .translated = @enumFromInt(i) },
.key = .{ .unicode = i },
.mods = mods,
},
.{ .goto_tab = (i - start) + 1 },
@ -4700,10 +4715,7 @@ pub const Keybinds = struct {
try self.set.put(
alloc,
.{
.key = if (comptime builtin.target.os.tag.isDarwin())
.{ .physical = .nine }
else
.{ .translated = .nine },
.key = .{ .unicode = '9' },
.mods = mods,
},
.{ .last_tab = {} },
@ -4713,14 +4725,14 @@ pub const Keybinds = struct {
// Toggle fullscreen
try self.set.put(
alloc,
.{ .key = .{ .translated = .enter }, .mods = inputpkg.ctrlOrSuper(.{}) },
.{ .key = .{ .physical = .enter }, .mods = inputpkg.ctrlOrSuper(.{}) },
.{ .toggle_fullscreen = {} },
);
// Toggle zoom a split
try self.set.put(
alloc,
.{ .key = .{ .translated = .enter }, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) },
.{ .key = .{ .physical = .enter }, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) },
.{ .toggle_split_zoom = {} },
);
@ -4728,199 +4740,199 @@ pub const Keybinds = struct {
if (comptime builtin.target.os.tag.isDarwin()) {
try self.set.put(
alloc,
.{ .key = .{ .translated = .q }, .mods = .{ .super = true } },
.{ .key = .{ .unicode = 'q' }, .mods = .{ .super = true } },
.{ .quit = {} },
);
try self.set.putFlags(
alloc,
.{ .key = .{ .translated = .k }, .mods = .{ .super = true } },
.{ .key = .{ .unicode = 'k' }, .mods = .{ .super = true } },
.{ .clear_screen = {} },
.{ .performable = true },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .a }, .mods = .{ .super = true } },
.{ .key = .{ .unicode = 'a' }, .mods = .{ .super = true } },
.{ .select_all = {} },
);
// Viewport scrolling
try self.set.put(
alloc,
.{ .key = .{ .translated = .home }, .mods = .{ .super = true } },
.{ .key = .{ .physical = .home }, .mods = .{ .super = true } },
.{ .scroll_to_top = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .end }, .mods = .{ .super = true } },
.{ .key = .{ .physical = .end }, .mods = .{ .super = true } },
.{ .scroll_to_bottom = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .page_up }, .mods = .{ .super = true } },
.{ .key = .{ .physical = .page_up }, .mods = .{ .super = true } },
.{ .scroll_page_up = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .page_down }, .mods = .{ .super = true } },
.{ .key = .{ .physical = .page_down }, .mods = .{ .super = true } },
.{ .scroll_page_down = {} },
);
// Semantic prompts
try self.set.put(
alloc,
.{ .key = .{ .translated = .up }, .mods = .{ .super = true, .shift = true } },
.{ .key = .{ .physical = .arrow_up }, .mods = .{ .super = true, .shift = true } },
.{ .jump_to_prompt = -1 },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .down }, .mods = .{ .super = true, .shift = true } },
.{ .key = .{ .physical = .arrow_down }, .mods = .{ .super = true, .shift = true } },
.{ .jump_to_prompt = 1 },
);
// Mac windowing
try self.set.put(
alloc,
.{ .key = .{ .translated = .n }, .mods = .{ .super = true } },
.{ .key = .{ .unicode = 'n' }, .mods = .{ .super = true } },
.{ .new_window = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .w }, .mods = .{ .super = true } },
.{ .key = .{ .unicode = 'w' }, .mods = .{ .super = true } },
.{ .close_surface = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .w }, .mods = .{ .super = true, .alt = true } },
.{ .key = .{ .unicode = 'w' }, .mods = .{ .super = true, .alt = true } },
.{ .close_tab = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .w }, .mods = .{ .super = true, .shift = true } },
.{ .key = .{ .unicode = 'w' }, .mods = .{ .super = true, .shift = true } },
.{ .close_window = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .w }, .mods = .{ .super = true, .shift = true, .alt = true } },
.{ .key = .{ .unicode = 'w' }, .mods = .{ .super = true, .shift = true, .alt = true } },
.{ .close_all_windows = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .t }, .mods = .{ .super = true } },
.{ .key = .{ .unicode = 't' }, .mods = .{ .super = true } },
.{ .new_tab = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .left_bracket }, .mods = .{ .super = true, .shift = true } },
.{ .key = .{ .physical = .bracket_left }, .mods = .{ .super = true, .shift = true } },
.{ .previous_tab = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .right_bracket }, .mods = .{ .super = true, .shift = true } },
.{ .key = .{ .physical = .bracket_right }, .mods = .{ .super = true, .shift = true } },
.{ .next_tab = {} },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .d }, .mods = .{ .super = true } },
.{ .key = .{ .unicode = 'd' }, .mods = .{ .super = true } },
.{ .new_split = .right },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .d }, .mods = .{ .super = true, .shift = true } },
.{ .key = .{ .unicode = 'd' }, .mods = .{ .super = true, .shift = true } },
.{ .new_split = .down },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .left_bracket }, .mods = .{ .super = true } },
.{ .key = .{ .physical = .bracket_left }, .mods = .{ .super = true } },
.{ .goto_split = .previous },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .right_bracket }, .mods = .{ .super = true } },
.{ .key = .{ .physical = .bracket_right }, .mods = .{ .super = true } },
.{ .goto_split = .next },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .up }, .mods = .{ .super = true, .alt = true } },
.{ .key = .{ .physical = .arrow_up }, .mods = .{ .super = true, .alt = true } },
.{ .goto_split = .up },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .down }, .mods = .{ .super = true, .alt = true } },
.{ .key = .{ .physical = .arrow_down }, .mods = .{ .super = true, .alt = true } },
.{ .goto_split = .down },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .left }, .mods = .{ .super = true, .alt = true } },
.{ .key = .{ .physical = .arrow_left }, .mods = .{ .super = true, .alt = true } },
.{ .goto_split = .left },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .right }, .mods = .{ .super = true, .alt = true } },
.{ .key = .{ .physical = .arrow_right }, .mods = .{ .super = true, .alt = true } },
.{ .goto_split = .right },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .up }, .mods = .{ .super = true, .ctrl = true } },
.{ .key = .{ .physical = .arrow_up }, .mods = .{ .super = true, .ctrl = true } },
.{ .resize_split = .{ .up, 10 } },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .down }, .mods = .{ .super = true, .ctrl = true } },
.{ .key = .{ .physical = .arrow_down }, .mods = .{ .super = true, .ctrl = true } },
.{ .resize_split = .{ .down, 10 } },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .left }, .mods = .{ .super = true, .ctrl = true } },
.{ .key = .{ .physical = .arrow_left }, .mods = .{ .super = true, .ctrl = true } },
.{ .resize_split = .{ .left, 10 } },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .right }, .mods = .{ .super = true, .ctrl = true } },
.{ .key = .{ .physical = .arrow_right }, .mods = .{ .super = true, .ctrl = true } },
.{ .resize_split = .{ .right, 10 } },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .equal }, .mods = .{ .super = true, .ctrl = true } },
.{ .key = .{ .physical = .equal }, .mods = .{ .super = true, .ctrl = true } },
.{ .equalize_splits = {} },
);
// Jump to prompt, matches Terminal.app
try self.set.put(
alloc,
.{ .key = .{ .translated = .up }, .mods = .{ .super = true } },
.{ .key = .{ .physical = .arrow_up }, .mods = .{ .super = true } },
.{ .jump_to_prompt = -1 },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .down }, .mods = .{ .super = true } },
.{ .key = .{ .physical = .arrow_down }, .mods = .{ .super = true } },
.{ .jump_to_prompt = 1 },
);
// Toggle command palette, matches VSCode
try self.set.put(
alloc,
.{ .key = .{ .translated = .p }, .mods = .{ .super = true, .shift = true } },
.{ .key = .{ .unicode = 'p' }, .mods = .{ .super = true, .shift = true } },
.{ .toggle_command_palette = {} },
);
// Inspector, matching Chromium
try self.set.put(
alloc,
.{ .key = .{ .translated = .i }, .mods = .{ .alt = true, .super = true } },
.{ .key = .{ .unicode = 'i' }, .mods = .{ .alt = true, .super = true } },
.{ .inspector = .toggle },
);
// Alternate keybind, common to Mac programs
try self.set.put(
alloc,
.{ .key = .{ .translated = .f }, .mods = .{ .super = true, .ctrl = true } },
.{ .key = .{ .unicode = 'f' }, .mods = .{ .super = true, .ctrl = true } },
.{ .toggle_fullscreen = {} },
);
// Selection clipboard paste, matches Terminal.app
try self.set.put(
alloc,
.{ .key = .{ .translated = .v }, .mods = .{ .super = true, .shift = true } },
.{ .key = .{ .unicode = 'v' }, .mods = .{ .super = true, .shift = true } },
.{ .paste_from_selection = {} },
);
@ -4931,27 +4943,27 @@ pub const Keybinds = struct {
// the keybinds to `unbind`.
try self.set.put(
alloc,
.{ .key = .{ .translated = .right }, .mods = .{ .super = true } },
.{ .key = .{ .physical = .arrow_right }, .mods = .{ .super = true } },
.{ .text = "\\x05" },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .left }, .mods = .{ .super = true } },
.{ .key = .{ .physical = .arrow_left }, .mods = .{ .super = true } },
.{ .text = "\\x01" },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .backspace }, .mods = .{ .super = true } },
.{ .key = .{ .physical = .backspace }, .mods = .{ .super = true } },
.{ .text = "\\x15" },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .left }, .mods = .{ .alt = true } },
.{ .key = .{ .physical = .arrow_left }, .mods = .{ .alt = true } },
.{ .esc = "b" },
);
try self.set.put(
alloc,
.{ .key = .{ .translated = .right }, .mods = .{ .alt = true } },
.{ .key = .{ .physical = .arrow_right }, .mods = .{ .alt = true } },
.{ .esc = "f" },
);
}
@ -5138,8 +5150,8 @@ pub const Keybinds = struct {
// Note they turn into translated keys because they match
// their ASCII mapping.
const want =
\\keybind = ctrl+z>two=goto_tab:2
\\keybind = ctrl+z>one=goto_tab:1
\\keybind = ctrl+z>2=goto_tab:2
\\keybind = ctrl+z>1=goto_tab:1
\\
;
try std.testing.expectEqualStrings(want, buf.items);
@ -5163,9 +5175,9 @@ pub const Keybinds = struct {
// NB: This does not currently retain the order of the keybinds.
const want =
\\a = ctrl+a>ctrl+c>t=new_tab
\\a = ctrl+a>ctrl+b>w=close_window
\\a = ctrl+a>ctrl+b>n=new_window
\\a = ctrl+a>ctrl+c>t=new_tab
\\a = ctrl+b>ctrl+d>a=previous_tab
\\
;

File diff suppressed because it is too large Load Diff

View File

@ -103,11 +103,15 @@ fn kitty(
// and UTF8 text we just send it directly since we assume that is
// whats happening. See legacy()'s similar logic for more details
// on how to verify this.
if (self.event.utf8.len > 0) {
if (self.event.utf8.len > 0) utf8: {
switch (self.event.key) {
.enter => return try copyToBuf(buf, self.event.utf8),
.backspace => return "",
else => {},
inline .enter, .backspace => |tag| {
// See legacy for why we handle this this way.
if (isControlUtf8(self.event.utf8)) break :utf8;
if (comptime tag == .backspace) return "";
return try copyToBuf(buf, self.event.utf8);
},
}
}
@ -272,11 +276,21 @@ fn legacy(
// - Korean: escape commits the dead key state
// - Korean: backspace should delete a single preedit char
//
if (self.event.utf8.len > 0) {
if (self.event.utf8.len > 0) utf8: {
switch (self.event.key) {
else => {},
.backspace => return "",
.enter, .escape => break :pc_style,
inline .backspace, .enter, .escape => |tag| {
// We want to ignore control characters. This is because
// some apprts (macOS) will send control characters as
// UTF-8 encodings and we handle that manually.
if (isControlUtf8(self.event.utf8)) break :utf8;
// Backspace encodes nothing because we modified IME.
// Enter/escape don't encode the PC-style encoding
// because we want to encode committed text.
if (comptime tag == .backspace) return "";
break :pc_style;
},
}
}
@ -571,7 +585,9 @@ fn ctrlSeq(
if (!mods.ctrl) return null;
const char, const unset_mods = unset_mods: {
var unset_mods = mods;
// We need to only get binding modifiers so we strip lock
// keys, sides, etc.
var unset_mods = mods.binding();
// Remove alt from our modifiers because it does not impact whether
// we are generating a ctrl sequence and we handle the ESC-prefix
@ -640,7 +656,7 @@ fn ctrlSeq(
// only matches Kitty in behavior. But I believe this is a
// justified divergence because it's a useful distinction.
break :unset_mods .{ char, unset_mods.binding() };
break :unset_mods .{ char, unset_mods };
};
// After unsetting, we only continue if we have ONLY control set.
@ -710,6 +726,12 @@ fn isControl(cp: u21) bool {
return cp < 0x20 or cp == 0x7F;
}
/// Returns true if this string is comprised of a single
/// control character. This returns false for multi-byte strings.
fn isControlUtf8(str: []const u8) bool {
return str.len == 1 and isControl(@intCast(str[0]));
}
/// This is the bitmask for fixterm CSI u modifiers.
const CsiUMods = packed struct(u3) {
shift: bool = false,
@ -1082,7 +1104,7 @@ test "kitty: plain text" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .a,
.key = .key_a,
.mods = .{},
.utf8 = "abcd",
},
@ -1098,7 +1120,7 @@ test "kitty: repeat with just disambiguate" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .a,
.key = .key_a,
.action = .repeat,
.mods = .{},
.utf8 = "a",
@ -1222,7 +1244,7 @@ test "kitty: enter with all flags" {
test "kitty: ctrl with all flags" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{ .key = .left_control, .mods = .{ .ctrl = true }, .utf8 = "" },
.event = .{ .key = .control_left, .mods = .{ .ctrl = true }, .utf8 = "" },
.kitty_flags = .{
.disambiguate = true,
.report_events = true,
@ -1240,7 +1262,7 @@ test "kitty: ctrl release with ctrl mod set" {
var enc: KeyEncoder = .{
.event = .{
.action = .release,
.key = .left_control,
.key = .control_left,
.mods = .{ .ctrl = true },
.utf8 = "",
},
@ -1272,7 +1294,7 @@ test "kitty: composing with no modifier" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .a,
.key = .key_a,
.mods = .{ .shift = true },
.composing = true,
},
@ -1287,7 +1309,7 @@ test "kitty: composing with modifier" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .left_shift,
.key = .shift_left,
.mods = .{ .shift = true },
.composing = true,
},
@ -1302,7 +1324,7 @@ test "kitty: shift+a on US keyboard" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .a,
.key = .key_a,
.mods = .{ .shift = true },
.utf8 = "A",
.unshifted_codepoint = 97, // lowercase A
@ -1321,7 +1343,7 @@ test "kitty: matching unshifted codepoint" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .a,
.key = .key_a,
.mods = .{ .shift = true },
.utf8 = "A",
.unshifted_codepoint = 65,
@ -1344,7 +1366,7 @@ test "kitty: report alternates with caps" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .j,
.key = .key_j,
.mods = .{ .caps_lock = true },
.utf8 = "J",
.unshifted_codepoint = 106,
@ -1450,7 +1472,7 @@ test "kitty: report alternates with hu layout release" {
var enc: KeyEncoder = .{
.event = .{
.action = .release,
.key = .left_bracket,
.key = .bracket_left,
.mods = .{ .ctrl = true },
.utf8 = "",
.unshifted_codepoint = 337,
@ -1473,7 +1495,7 @@ test "kitty: up arrow with utf8" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .up,
.key = .arrow_up,
.mods = .{},
.utf8 = &.{30},
},
@ -1505,7 +1527,7 @@ test "kitty: left shift" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .left_shift,
.key = .shift_left,
.mods = .{},
.utf8 = "",
},
@ -1521,7 +1543,7 @@ test "kitty: left shift with report all" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .left_shift,
.key = .shift_left,
.mods = .{},
.utf8 = "",
},
@ -1539,7 +1561,7 @@ test "kitty: report associated with alt text on macOS with option" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .w,
.key = .key_w,
.mods = .{ .alt = true },
.utf8 = "",
.unshifted_codepoint = 119,
@ -1565,7 +1587,7 @@ test "kitty: report associated with alt text on macOS with alt" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .w,
.key = .key_w,
.mods = .{ .alt = true },
.utf8 = "",
.unshifted_codepoint = 119,
@ -1588,7 +1610,7 @@ test "kitty: report associated with alt text on macOS with alt" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .w,
.key = .key_w,
.mods = .{},
.utf8 = "",
.unshifted_codepoint = 119,
@ -1611,7 +1633,7 @@ test "kitty: report associated with modifiers" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .j,
.key = .key_j,
.mods = .{ .ctrl = true },
.utf8 = "j",
.unshifted_codepoint = 106,
@ -1632,7 +1654,7 @@ test "kitty: report associated" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .j,
.key = .key_j,
.mods = .{ .shift = true },
.utf8 = "J",
.unshifted_codepoint = 106,
@ -1654,7 +1676,7 @@ test "kitty: report associated on release" {
var enc: KeyEncoder = .{
.event = .{
.action = .release,
.key = .j,
.key = .key_j,
.mods = .{ .shift = true },
.utf8 = "J",
.unshifted_codepoint = 106,
@ -1713,7 +1735,7 @@ test "kitty: enter with utf8 (dead key state)" {
test "kitty: keypad number" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{ .key = .kp_1, .mods = .{}, .utf8 = "1" },
.event = .{ .key = .numpad_1, .mods = .{}, .utf8 = "1" },
.kitty_flags = .{
.disambiguate = true,
.report_events = true,
@ -1807,7 +1829,7 @@ test "legacy: ctrl+alt+c" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .c,
.key = .key_c,
.mods = .{ .ctrl = true, .alt = true },
.utf8 = "c",
},
@ -1821,7 +1843,7 @@ test "legacy: alt+c" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .c,
.key = .key_c,
.utf8 = "c",
.mods = .{ .alt = true },
},
@ -1837,7 +1859,7 @@ test "legacy: alt+e only unshifted" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .e,
.key = .key_e,
.unshifted_codepoint = 'e',
.mods = .{ .alt = true },
},
@ -1855,7 +1877,7 @@ test "legacy: alt+x macos" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .c,
.key = .key_c,
.utf8 = "",
.unshifted_codepoint = 'c',
.mods = .{ .alt = true },
@ -1891,7 +1913,7 @@ test "legacy: alt+ф" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .f,
.key = .key_f,
.utf8 = "ф",
.mods = .{ .alt = true },
},
@ -1906,7 +1928,7 @@ test "legacy: ctrl+c" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .c,
.key = .key_c,
.mods = .{ .ctrl = true },
.utf8 = "c",
},
@ -1947,7 +1969,7 @@ test "legacy: ctrl+shift+char with modify other state 2" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .h,
.key = .key_h,
.mods = .{ .ctrl = true, .shift = true },
.utf8 = "H",
},
@ -1962,7 +1984,7 @@ test "legacy: fixterm awkward letters" {
var buf: [128]u8 = undefined;
{
var enc: KeyEncoder = .{ .event = .{
.key = .i,
.key = .key_i,
.mods = .{ .ctrl = true },
.utf8 = "i",
} };
@ -1971,7 +1993,7 @@ test "legacy: fixterm awkward letters" {
}
{
var enc: KeyEncoder = .{ .event = .{
.key = .m,
.key = .key_m,
.mods = .{ .ctrl = true },
.utf8 = "m",
} };
@ -1980,7 +2002,7 @@ test "legacy: fixterm awkward letters" {
}
{
var enc: KeyEncoder = .{ .event = .{
.key = .left_bracket,
.key = .bracket_left,
.mods = .{ .ctrl = true },
.utf8 = "[",
} };
@ -1989,7 +2011,7 @@ test "legacy: fixterm awkward letters" {
}
{
var enc: KeyEncoder = .{ .event = .{
.key = .two,
.key = .digit_2,
.mods = .{ .ctrl = true, .shift = true },
.utf8 = "@",
.unshifted_codepoint = '2',
@ -2005,7 +2027,7 @@ test "legacy: ctrl+shift+letter ascii" {
var buf: [128]u8 = undefined;
{
var enc: KeyEncoder = .{ .event = .{
.key = .m,
.key = .key_m,
.mods = .{ .ctrl = true, .shift = true },
.utf8 = "M",
.unshifted_codepoint = 'm',
@ -2019,7 +2041,7 @@ test "legacy: shift+function key should use all mods" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .up,
.key = .arrow_up,
.mods = .{ .shift = true },
.consumed_mods = .{ .shift = true },
},
@ -2033,7 +2055,7 @@ test "legacy: keypad enter" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .kp_enter,
.key = .numpad_enter,
.mods = .{},
.consumed_mods = .{},
},
@ -2047,7 +2069,7 @@ test "legacy: keypad 1" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .kp_1,
.key = .numpad_1,
.mods = .{},
.consumed_mods = .{},
.utf8 = "1",
@ -2062,7 +2084,7 @@ test "legacy: keypad 1 with application keypad" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .kp_1,
.key = .numpad_1,
.mods = .{},
.consumed_mods = .{},
.utf8 = "1",
@ -2078,7 +2100,7 @@ test "legacy: keypad 1 with application keypad and numlock" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .kp_1,
.key = .numpad_1,
.mods = .{ .num_lock = true },
.consumed_mods = .{},
.utf8 = "1",
@ -2094,7 +2116,7 @@ test "legacy: keypad 1 with application keypad and numlock ignore" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .kp_1,
.key = .numpad_1,
.mods = .{ .num_lock = false },
.consumed_mods = .{},
.utf8 = "1",
@ -2189,8 +2211,7 @@ test "legacy: hu layout ctrl+ő sends proper codepoint" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .left_bracket,
.physical_key = .left_bracket,
.key = .bracket_left,
.mods = .{ .ctrl = true },
.utf8 = "ő",
.unshifted_codepoint = 337,
@ -2207,7 +2228,7 @@ test "legacy: super-only on macOS with text" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .b,
.key = .key_b,
.utf8 = "b",
.mods = .{ .super = true },
},
@ -2223,7 +2244,7 @@ test "legacy: super and other mods on macOS with text" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .b,
.key = .key_b,
.utf8 = "B",
.mods = .{ .super = true, .shift = true },
},
@ -2233,51 +2254,73 @@ test "legacy: super and other mods on macOS with text" {
try testing.expectEqualStrings("", actual);
}
test "legacy: backspace with DEL utf8" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .backspace,
.utf8 = &.{0x7F},
.unshifted_codepoint = 0x08,
},
};
const actual = try enc.legacy(&buf);
try testing.expectEqualStrings("\x7F", actual);
}
test "ctrlseq: normal ctrl c" {
const seq = ctrlSeq(.invalid, "c", 'c', .{ .ctrl = true });
const seq = ctrlSeq(.unidentified, "c", 'c', .{ .ctrl = true });
try testing.expectEqual(@as(u8, 0x03), seq.?);
}
test "ctrlseq: normal ctrl c, right control" {
const seq = ctrlSeq(.invalid, "c", 'c', .{ .ctrl = true, .sides = .{ .ctrl = .right } });
const seq = ctrlSeq(.unidentified, "c", 'c', .{ .ctrl = true, .sides = .{ .ctrl = .right } });
try testing.expectEqual(@as(u8, 0x03), seq.?);
}
test "ctrlseq: alt should be allowed" {
const seq = ctrlSeq(.invalid, "c", 'c', .{ .alt = true, .ctrl = true });
const seq = ctrlSeq(.unidentified, "c", 'c', .{ .alt = true, .ctrl = true });
try testing.expectEqual(@as(u8, 0x03), seq.?);
}
test "ctrlseq: no ctrl does nothing" {
try testing.expect(ctrlSeq(.invalid, "c", 'c', .{}) == null);
try testing.expect(ctrlSeq(.unidentified, "c", 'c', .{}) == null);
}
test "ctrlseq: shifted non-character" {
const seq = ctrlSeq(.invalid, "_", '-', .{ .ctrl = true, .shift = true });
const seq = ctrlSeq(.unidentified, "_", '-', .{ .ctrl = true, .shift = true });
try testing.expectEqual(@as(u8, 0x1F), seq.?);
}
test "ctrlseq: caps ascii letter" {
const seq = ctrlSeq(.invalid, "C", 'c', .{ .ctrl = true, .caps_lock = true });
const seq = ctrlSeq(.unidentified, "C", 'c', .{ .ctrl = true, .caps_lock = true });
try testing.expectEqual(@as(u8, 0x03), seq.?);
}
test "ctrlseq: shift does not generate ctrl seq" {
try testing.expect(ctrlSeq(.invalid, "C", 'c', .{ .shift = true }) == null);
try testing.expect(ctrlSeq(.invalid, "C", 'c', .{ .shift = true, .ctrl = true }) == null);
try testing.expect(ctrlSeq(.unidentified, "C", 'c', .{ .shift = true }) == null);
try testing.expect(ctrlSeq(.unidentified, "C", 'c', .{ .shift = true, .ctrl = true }) == null);
}
test "ctrlseq: russian ctrl c" {
const seq = ctrlSeq(.c, "с", 0x0441, .{ .ctrl = true });
const seq = ctrlSeq(.key_c, "с", 0x0441, .{ .ctrl = true });
try testing.expectEqual(@as(u8, 0x03), seq.?);
}
test "ctrlseq: russian shifted ctrl c" {
const seq = ctrlSeq(.c, "с", 0x0441, .{ .ctrl = true, .shift = true });
const seq = ctrlSeq(.key_c, "с", 0x0441, .{ .ctrl = true, .shift = true });
try testing.expect(seq == null);
}
test "ctrlseq: russian alt ctrl c" {
const seq = ctrlSeq(.c, "с", 0x0441, .{ .ctrl = true, .alt = true });
const seq = ctrlSeq(.key_c, "с", 0x0441, .{ .ctrl = true, .alt = true });
try testing.expectEqual(@as(u8, 0x03), seq.?);
}
test "ctrlseq: right ctrl c" {
const seq = ctrlSeq(.key_c, "с", 'c', .{
.ctrl = true,
.sides = .{ .ctrl = .right },
});
try testing.expectEqual(@as(u8, 0x03), seq.?);
}

View File

@ -75,10 +75,10 @@ pub const KeyEntryArray = std.EnumArray(key.Key, []const Entry);
pub const keys = keys: {
var result = KeyEntryArray.initFill(&.{});
result.set(.up, pcStyle("\x1b[1;{}A") ++ cursorKey("\x1b[A", "\x1bOA"));
result.set(.down, pcStyle("\x1b[1;{}B") ++ cursorKey("\x1b[B", "\x1bOB"));
result.set(.right, pcStyle("\x1b[1;{}C") ++ cursorKey("\x1b[C", "\x1bOC"));
result.set(.left, pcStyle("\x1b[1;{}D") ++ cursorKey("\x1b[D", "\x1bOD"));
result.set(.arrow_up, pcStyle("\x1b[1;{}A") ++ cursorKey("\x1b[A", "\x1bOA"));
result.set(.arrow_down, pcStyle("\x1b[1;{}B") ++ cursorKey("\x1b[B", "\x1bOB"));
result.set(.arrow_right, pcStyle("\x1b[1;{}C") ++ cursorKey("\x1b[C", "\x1bOC"));
result.set(.arrow_left, pcStyle("\x1b[1;{}D") ++ cursorKey("\x1b[D", "\x1bOD"));
result.set(.home, pcStyle("\x1b[1;{}H") ++ cursorKey("\x1b[H", "\x1bOH"));
result.set(.end, pcStyle("\x1b[1;{}F") ++ cursorKey("\x1b[F", "\x1bOF"));
result.set(.insert, pcStyle("\x1b[2;{}~") ++ .{Entry{ .sequence = "\x1B[2~" }});
@ -101,33 +101,33 @@ pub const keys = keys: {
result.set(.f12, pcStyle("\x1b[24;{}~") ++ .{Entry{ .sequence = "\x1B[24~" }});
// Keypad keys
result.set(.kp_0, kpKeys("p"));
result.set(.kp_1, kpKeys("q"));
result.set(.kp_2, kpKeys("r"));
result.set(.kp_3, kpKeys("s"));
result.set(.kp_4, kpKeys("t"));
result.set(.kp_5, kpKeys("u"));
result.set(.kp_6, kpKeys("v"));
result.set(.kp_7, kpKeys("w"));
result.set(.kp_8, kpKeys("x"));
result.set(.kp_9, kpKeys("y"));
result.set(.kp_decimal, kpKeys("n"));
result.set(.kp_divide, kpKeys("o"));
result.set(.kp_multiply, kpKeys("j"));
result.set(.kp_subtract, kpKeys("m"));
result.set(.kp_add, kpKeys("k"));
result.set(.kp_enter, kpKeys("M") ++ .{Entry{ .sequence = "\r" }});
result.set(.kp_up, pcStyle("\x1b[1;{}A") ++ cursorKey("\x1b[A", "\x1bOA"));
result.set(.kp_down, pcStyle("\x1b[1;{}B") ++ cursorKey("\x1b[B", "\x1bOB"));
result.set(.kp_right, pcStyle("\x1b[1;{}C") ++ cursorKey("\x1b[C", "\x1bOC"));
result.set(.kp_left, pcStyle("\x1b[1;{}D") ++ cursorKey("\x1b[D", "\x1bOD"));
result.set(.kp_begin, pcStyle("\x1b[1;{}E") ++ cursorKey("\x1b[E", "\x1bOE"));
result.set(.kp_home, pcStyle("\x1b[1;{}H") ++ cursorKey("\x1b[H", "\x1bOH"));
result.set(.kp_end, pcStyle("\x1b[1;{}F") ++ cursorKey("\x1b[F", "\x1bOF"));
result.set(.kp_insert, pcStyle("\x1b[2;{}~") ++ .{Entry{ .sequence = "\x1B[2~" }});
result.set(.kp_delete, pcStyle("\x1b[3;{}~") ++ .{Entry{ .sequence = "\x1B[3~" }});
result.set(.kp_page_up, pcStyle("\x1b[5;{}~") ++ .{Entry{ .sequence = "\x1B[5~" }});
result.set(.kp_page_down, pcStyle("\x1b[6;{}~") ++ .{Entry{ .sequence = "\x1B[6~" }});
result.set(.numpad_0, kpKeys("p"));
result.set(.numpad_1, kpKeys("q"));
result.set(.numpad_2, kpKeys("r"));
result.set(.numpad_3, kpKeys("s"));
result.set(.numpad_4, kpKeys("t"));
result.set(.numpad_5, kpKeys("u"));
result.set(.numpad_6, kpKeys("v"));
result.set(.numpad_7, kpKeys("w"));
result.set(.numpad_8, kpKeys("x"));
result.set(.numpad_9, kpKeys("y"));
result.set(.numpad_decimal, kpKeys("n"));
result.set(.numpad_divide, kpKeys("o"));
result.set(.numpad_multiply, kpKeys("j"));
result.set(.numpad_subtract, kpKeys("m"));
result.set(.numpad_add, kpKeys("k"));
result.set(.numpad_enter, kpKeys("M") ++ .{Entry{ .sequence = "\r" }});
result.set(.numpad_up, pcStyle("\x1b[1;{}A") ++ cursorKey("\x1b[A", "\x1bOA"));
result.set(.numpad_down, pcStyle("\x1b[1;{}B") ++ cursorKey("\x1b[B", "\x1bOB"));
result.set(.numpad_right, pcStyle("\x1b[1;{}C") ++ cursorKey("\x1b[C", "\x1bOC"));
result.set(.numpad_left, pcStyle("\x1b[1;{}D") ++ cursorKey("\x1b[D", "\x1bOD"));
result.set(.numpad_begin, pcStyle("\x1b[1;{}E") ++ cursorKey("\x1b[E", "\x1bOE"));
result.set(.numpad_home, pcStyle("\x1b[1;{}H") ++ cursorKey("\x1b[H", "\x1bOH"));
result.set(.numpad_end, pcStyle("\x1b[1;{}F") ++ cursorKey("\x1b[F", "\x1bOF"));
result.set(.numpad_insert, pcStyle("\x1b[2;{}~") ++ .{Entry{ .sequence = "\x1B[2~" }});
result.set(.numpad_delete, pcStyle("\x1b[3;{}~") ++ .{Entry{ .sequence = "\x1B[3~" }});
result.set(.numpad_page_up, pcStyle("\x1b[5;{}~") ++ .{Entry{ .sequence = "\x1B[5~" }});
result.set(.numpad_page_down, pcStyle("\x1b[6;{}~") ++ .{Entry{ .sequence = "\x1B[6~" }});
result.set(.backspace, &.{
// Modify Keys Normal

View File

@ -16,12 +16,10 @@ 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,
/// 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 = .{},
@ -63,7 +61,6 @@ pub const KeyEvent = struct {
// These are all the fields that are explicitly part of Trigger.
std.hash.autoHash(&hasher, self.key);
std.hash.autoHash(&hasher, self.physical_key);
std.hash.autoHash(&hasher, self.unshifted_codepoint);
std.hash.autoHash(&hasher, self.mods.binding());
@ -152,9 +149,6 @@ pub const Mods = packed struct(Mods.Backing) {
pub fn translation(self: Mods, option_as_alt: config.OptionAsAlt) Mods {
var result = self;
// Control is never used for translation.
result.ctrl = false;
// macos-option-as-alt for darwin
if (comptime builtin.target.os.tag.isDarwin()) alt: {
// Alt has to be set only on the correct side
@ -190,14 +184,6 @@ pub const Mods = packed struct(Mods.Backing) {
);
}
test "translation removes control" {
const testing = std.testing;
const mods: Mods = .{ .ctrl = true };
const result = mods.translation(.true);
try testing.expectEqual(Mods{}, result);
}
test "translation macos-option-as-alt" {
if (comptime !builtin.target.os.tag.isDarwin()) return error.SkipZigTest;
@ -255,95 +241,164 @@ pub const Action = enum(c_int) {
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.
/// 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.
///
/// This is backed by a c_int so we can use this as-is for our embedding API.
/// 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
///
/// IMPORTANT: Any changes here update include/ghostty.h
/// 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) {
invalid,
unidentified,
// 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,
// punctuation
semicolon,
space,
apostrophe,
// "Writing System Keys" § 3.1.1
backquote,
backslash,
bracket_left,
bracket_right,
comma,
grave_accent, // `
period,
slash,
minus,
plus,
digit_0,
digit_1,
digit_2,
digit_3,
digit_4,
digit_5,
digit_6,
digit_7,
digit_8,
digit_9,
equal,
left_bracket, // [
right_bracket, // ]
backslash, // \
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,
// control
up,
down,
right,
left,
home,
end,
insert,
delete,
caps_lock,
scroll_lock,
num_lock,
page_up,
page_down,
escape,
enter,
tab,
// "Functional Keys" § 3.1.2
alt_left,
alt_right,
backspace,
print_screen,
pause,
caps_lock,
context_menu,
control_left,
control_right,
enter,
meta_left,
meta_right,
shift_left,
shift_right,
space,
tab,
convert,
kana_mode,
non_convert,
// function keys
// "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,
@ -369,53 +424,35 @@ pub const Key = enum(c_int) {
f23,
f24,
f25,
@"fn",
fn_lock,
print_screen,
scroll_lock,
pause,
// 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,
// special keys
context_menu,
// 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.
// "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,
/// Converts an ASCII character to a key, if possible. This returns
/// null if the character is unknown.
@ -446,6 +483,74 @@ pub const Key = enum(c_int) {
};
}
/// 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) {
@ -465,14 +570,14 @@ pub const Key = enum(c_int) {
/// 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,
.shift_left,
.control_left,
.alt_left,
.meta_left,
.shift_right,
.control_right,
.alt_right,
.meta_right,
=> true,
else => false,
@ -484,7 +589,7 @@ pub const Key = enum(c_int) {
return switch (self) {
inline else => |tag| {
const name = @tagName(tag);
const result = comptime std.mem.startsWith(u8, name, "kp_");
const result = comptime std.mem.startsWith(u8, name, "numpad_");
return result;
},
};
@ -510,61 +615,61 @@ pub const Key = enum(c_int) {
/// 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,
.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,
.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,
.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,
.apostrophe => cimgui.c.ImGuiKey_Apostrophe,
.quote => cimgui.c.ImGuiKey_Apostrophe,
.comma => cimgui.c.ImGuiKey_Comma,
.grave_accent => cimgui.c.ImGuiKey_GraveAccent,
.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,
.left_bracket => cimgui.c.ImGuiKey_LeftBracket,
.right_bracket => cimgui.c.ImGuiKey_RightBracket,
.bracket_left => cimgui.c.ImGuiKey_LeftBracket,
.bracket_right => 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,
.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,
@ -595,48 +700,47 @@ pub const Key = enum(c_int) {
.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,
.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
.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,
.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,
.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,
.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,
.invalid,
// These keys aren't represented in cimgui
.f13,
.f14,
.f15,
@ -650,9 +754,52 @@ pub const Key = enum(c_int) {
.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,
=> null,
// These keys aren't represented in cimgui
.plus,
.unidentified,
=> null,
};
}
@ -661,107 +808,118 @@ pub const Key = enum(c_int) {
/// or ctrl.
pub fn ctrlOrSuper(self: Key) bool {
if (comptime builtin.target.os.tag.isDarwin()) {
return self == .left_super or self == .right_super;
return self == .meta_left or self == .meta_right;
}
return self == .left_control or self == .right_control;
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 == .left_shift or self == .right_shift;
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 == .left_alt or self == .right_alt;
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').? == .zero);
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.kp_0.keypad());
try testing.expect(!Key.one.keypad());
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', .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 },
.{ '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 },
.{ '\'', .apostrophe },
.{ '\'', .quote },
.{ ',', .comma },
.{ '`', .grave_accent },
.{ '`', .backquote },
.{ '.', .period },
.{ '/', .slash },
.{ '-', .minus },
.{ '+', .plus },
.{ '=', .equal },
.{ '[', .left_bracket },
.{ ']', .right_bracket },
.{ '[', .bracket_left },
.{ ']', .bracket_right },
.{ '\\', .backslash },
// Control characters
.{ '\t', .tab },
// Keypad entries. We just assume keypad with the kp_ prefix
// 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', .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 },
.{ '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 },
};
};

View File

@ -19,7 +19,7 @@ pub const entries: []const Entry = entries: {
for (raw_entries, 0..) |raw, i| {
@setEvalBranchQuota(10000);
result[i] = .{
.key = code_to_key.get(raw[5]) orelse .invalid,
.key = code_to_key.get(raw[5]) orelse .unidentified,
.usb = raw[0],
.code = raw[5],
.native = raw[native_idx],
@ -45,42 +45,42 @@ pub const Entry = struct {
const code_to_key = code_to_key: {
@setEvalBranchQuota(5000);
break :code_to_key std.StaticStringMap(Key).initComptime(.{
.{ "KeyA", .a },
.{ "KeyB", .b },
.{ "KeyC", .c },
.{ "KeyD", .d },
.{ "KeyE", .e },
.{ "KeyF", .f },
.{ "KeyG", .g },
.{ "KeyH", .h },
.{ "KeyI", .i },
.{ "KeyJ", .j },
.{ "KeyK", .k },
.{ "KeyL", .l },
.{ "KeyM", .m },
.{ "KeyN", .n },
.{ "KeyO", .o },
.{ "KeyP", .p },
.{ "KeyQ", .q },
.{ "KeyR", .r },
.{ "KeyS", .s },
.{ "KeyT", .t },
.{ "KeyU", .u },
.{ "KeyV", .v },
.{ "KeyW", .w },
.{ "KeyX", .x },
.{ "KeyY", .y },
.{ "KeyZ", .z },
.{ "Digit1", .one },
.{ "Digit2", .two },
.{ "Digit3", .three },
.{ "Digit4", .four },
.{ "Digit5", .five },
.{ "Digit6", .six },
.{ "Digit7", .seven },
.{ "Digit8", .eight },
.{ "Digit9", .nine },
.{ "Digit0", .zero },
.{ "KeyA", .key_a },
.{ "KeyB", .key_b },
.{ "KeyC", .key_c },
.{ "KeyD", .key_d },
.{ "KeyE", .key_e },
.{ "KeyF", .key_f },
.{ "KeyG", .key_g },
.{ "KeyH", .key_h },
.{ "KeyI", .key_i },
.{ "KeyJ", .key_j },
.{ "KeyK", .key_k },
.{ "KeyL", .key_l },
.{ "KeyM", .key_m },
.{ "KeyN", .key_n },
.{ "KeyO", .key_o },
.{ "KeyP", .key_p },
.{ "KeyQ", .key_q },
.{ "KeyR", .key_r },
.{ "KeyS", .key_s },
.{ "KeyT", .key_t },
.{ "KeyU", .key_u },
.{ "KeyV", .key_v },
.{ "KeyW", .key_w },
.{ "KeyX", .key_x },
.{ "KeyY", .key_y },
.{ "KeyZ", .key_z },
.{ "Digit1", .digit_1 },
.{ "Digit2", .digit_2 },
.{ "Digit3", .digit_3 },
.{ "Digit4", .digit_4 },
.{ "Digit5", .digit_5 },
.{ "Digit6", .digit_6 },
.{ "Digit7", .digit_7 },
.{ "Digit8", .digit_8 },
.{ "Digit9", .digit_9 },
.{ "Digit0", .digit_0 },
.{ "Enter", .enter },
.{ "Escape", .escape },
.{ "Backspace", .backspace },
@ -88,12 +88,12 @@ const code_to_key = code_to_key: {
.{ "Space", .space },
.{ "Minus", .minus },
.{ "Equal", .equal },
.{ "BracketLeft", .left_bracket },
.{ "BracketRight", .right_bracket },
.{ "BracketLeft", .bracket_left },
.{ "BracketRight", .bracket_left },
.{ "Backslash", .backslash },
.{ "Semicolon", .semicolon },
.{ "Quote", .apostrophe },
.{ "Backquote", .grave_accent },
.{ "Quote", .quote },
.{ "Backquote", .backquote },
.{ "Comma", .comma },
.{ "Period", .period },
.{ "Slash", .slash },
@ -131,37 +131,37 @@ const code_to_key = code_to_key: {
.{ "Delete", .delete },
.{ "End", .end },
.{ "PageDown", .page_down },
.{ "ArrowRight", .right },
.{ "ArrowLeft", .left },
.{ "ArrowDown", .down },
.{ "ArrowUp", .up },
.{ "ArrowRight", .arrow_right },
.{ "ArrowLeft", .arrow_left },
.{ "ArrowDown", .arrow_down },
.{ "ArrowUp", .arrow_up },
.{ "NumLock", .num_lock },
.{ "NumpadDivide", .kp_divide },
.{ "NumpadMultiply", .kp_multiply },
.{ "NumpadSubtract", .kp_subtract },
.{ "NumpadAdd", .kp_add },
.{ "NumpadEnter", .kp_enter },
.{ "Numpad1", .kp_1 },
.{ "Numpad2", .kp_2 },
.{ "Numpad3", .kp_3 },
.{ "Numpad4", .kp_4 },
.{ "Numpad5", .kp_5 },
.{ "Numpad6", .kp_6 },
.{ "Numpad7", .kp_7 },
.{ "Numpad8", .kp_8 },
.{ "Numpad9", .kp_9 },
.{ "Numpad0", .kp_0 },
.{ "NumpadDecimal", .kp_decimal },
.{ "NumpadEqual", .kp_equal },
.{ "NumpadDivide", .numpad_divide },
.{ "NumpadMultiply", .numpad_multiply },
.{ "NumpadSubtract", .numpad_subtract },
.{ "NumpadAdd", .numpad_add },
.{ "NumpadEnter", .numpad_enter },
.{ "Numpad1", .numpad_1 },
.{ "Numpad2", .numpad_2 },
.{ "Numpad3", .numpad_3 },
.{ "Numpad4", .numpad_4 },
.{ "Numpad5", .numpad_5 },
.{ "Numpad6", .numpad_6 },
.{ "Numpad7", .numpad_7 },
.{ "Numpad8", .numpad_8 },
.{ "Numpad9", .numpad_9 },
.{ "Numpad0", .numpad_0 },
.{ "NumpadDecimal", .numpad_decimal },
.{ "NumpadEqual", .numpad_equal },
.{ "ContextMenu", .context_menu },
.{ "ControlLeft", .left_control },
.{ "ShiftLeft", .left_shift },
.{ "AltLeft", .left_alt },
.{ "MetaLeft", .left_super },
.{ "ControlRight", .right_control },
.{ "ShiftRight", .right_shift },
.{ "AltRight", .right_alt },
.{ "MetaRight", .right_super },
.{ "ControlLeft", .control_left },
.{ "ShiftLeft", .shift_left },
.{ "AltLeft", .alt_left },
.{ "MetaLeft", .meta_left },
.{ "ControlRight", .control_right },
.{ "ShiftRight", .shift_right },
.{ "AltRight", .alt_right },
.{ "MetaRight", .meta_right },
});
};

View File

@ -49,10 +49,10 @@ const raw_entries: []const RawEntry = &.{
.{ .backspace, 127, 'u', false },
.{ .insert, 2, '~', false },
.{ .delete, 3, '~', false },
.{ .left, 1, 'D', false },
.{ .right, 1, 'C', false },
.{ .up, 1, 'A', false },
.{ .down, 1, 'B', false },
.{ .arrow_left, 1, 'D', false },
.{ .arrow_right, 1, 'C', false },
.{ .arrow_up, 1, 'A', false },
.{ .arrow_down, 1, 'B', false },
.{ .page_up, 5, '~', false },
.{ .page_down, 6, '~', false },
.{ .home, 1, 'H', false },
@ -89,46 +89,44 @@ const raw_entries: []const RawEntry = &.{
.{ .f24, 57387, 'u', false },
.{ .f25, 57388, 'u', false },
.{ .kp_0, 57399, 'u', false },
.{ .kp_1, 57400, 'u', false },
.{ .kp_2, 57401, 'u', false },
.{ .kp_3, 57402, 'u', false },
.{ .kp_4, 57403, 'u', false },
.{ .kp_5, 57404, 'u', false },
.{ .kp_6, 57405, 'u', false },
.{ .kp_7, 57406, 'u', false },
.{ .kp_8, 57407, 'u', false },
.{ .kp_9, 57408, 'u', false },
.{ .kp_decimal, 57409, 'u', false },
.{ .kp_divide, 57410, 'u', false },
.{ .kp_multiply, 57411, 'u', false },
.{ .kp_subtract, 57412, 'u', false },
.{ .kp_add, 57413, 'u', false },
.{ .kp_enter, 57414, 'u', false },
.{ .kp_equal, 57415, 'u', false },
.{ .kp_separator, 57416, 'u', false },
.{ .kp_left, 57417, 'u', false },
.{ .kp_right, 57418, 'u', false },
.{ .kp_up, 57419, 'u', false },
.{ .kp_down, 57420, 'u', false },
.{ .kp_page_up, 57421, 'u', false },
.{ .kp_page_down, 57422, 'u', false },
.{ .kp_home, 57423, 'u', false },
.{ .kp_end, 57424, 'u', false },
.{ .kp_insert, 57425, 'u', false },
.{ .kp_delete, 57426, 'u', false },
.{ .kp_begin, 57427, 'u', false },
.{ .numpad_0, 57399, 'u', false },
.{ .numpad_1, 57400, 'u', false },
.{ .numpad_2, 57401, 'u', false },
.{ .numpad_3, 57402, 'u', false },
.{ .numpad_4, 57403, 'u', false },
.{ .numpad_5, 57404, 'u', false },
.{ .numpad_6, 57405, 'u', false },
.{ .numpad_7, 57406, 'u', false },
.{ .numpad_8, 57407, 'u', false },
.{ .numpad_9, 57408, 'u', false },
.{ .numpad_decimal, 57409, 'u', false },
.{ .numpad_divide, 57410, 'u', false },
.{ .numpad_multiply, 57411, 'u', false },
.{ .numpad_subtract, 57412, 'u', false },
.{ .numpad_add, 57413, 'u', false },
.{ .numpad_enter, 57414, 'u', false },
.{ .numpad_equal, 57415, 'u', false },
.{ .numpad_separator, 57416, 'u', false },
.{ .numpad_left, 57417, 'u', false },
.{ .numpad_right, 57418, 'u', false },
.{ .numpad_up, 57419, 'u', false },
.{ .numpad_down, 57420, 'u', false },
.{ .numpad_page_up, 57421, 'u', false },
.{ .numpad_page_down, 57422, 'u', false },
.{ .numpad_home, 57423, 'u', false },
.{ .numpad_end, 57424, 'u', false },
.{ .numpad_insert, 57425, 'u', false },
.{ .numpad_delete, 57426, 'u', false },
.{ .numpad_begin, 57427, 'u', false },
// TODO: media keys
.{ .left_shift, 57441, 'u', true },
.{ .right_shift, 57447, 'u', true },
.{ .left_control, 57442, 'u', true },
.{ .right_control, 57448, 'u', true },
.{ .left_super, 57444, 'u', true },
.{ .right_super, 57450, 'u', true },
.{ .left_alt, 57443, 'u', true },
.{ .right_alt, 57449, 'u', true },
.{ .shift_left, 57441, 'u', true },
.{ .shift_right, 57447, 'u', true },
.{ .control_left, 57442, 'u', true },
.{ .control_right, 57448, 'u', true },
.{ .meta_left, 57444, 'u', true },
.{ .meta_right, 57450, 'u', true },
.{ .alt_left, 57443, 'u', true },
.{ .alt_right, 57449, 'u', true },
};
test {

View File

@ -56,7 +56,7 @@ pub const Event = struct {
// Write our key. If we have an invalid key we attempt to write
// the utf8 associated with it if we have it to handle non-ascii.
try writer.writeAll(switch (self.event.key) {
.invalid => if (self.event.utf8.len > 0) self.event.utf8 else @tagName(.invalid),
.unidentified => if (self.event.utf8.len > 0) self.event.utf8 else @tagName(self.event.key),
else => @tagName(self.event.key),
});
@ -117,13 +117,6 @@ pub const Event = struct {
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%s", @tagName(self.event.key).ptr);
}
if (self.event.physical_key != self.event.key) {
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Physical Key");
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%s", @tagName(self.event.physical_key).ptr);
}
if (!self.event.mods.empty()) {
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
@ -227,9 +220,9 @@ test "event string" {
const testing = std.testing;
const alloc = testing.allocator;
var event = try Event.init(alloc, .{ .key = .a });
var event = try Event.init(alloc, .{ .key = .key_a });
defer event.deinit(alloc);
var buf: [1024]u8 = undefined;
try testing.expectEqualStrings("Press: a", try event.label(&buf));
try testing.expectEqualStrings("Press: key_a", try event.label(&buf));
}

View File

@ -132,7 +132,7 @@ test "keyToMouseShape" {
{
// No specific key pressed
const m: SurfaceMouse = .{
.physical_key = .invalid,
.physical_key = .unidentified,
.mouse_event = .none,
.mouse_shape = .progress,
.mods = .{},
@ -148,7 +148,7 @@ test "keyToMouseShape" {
// Over a link. NOTE: This tests that we don't touch the inbound state,
// not necessarily if we're over a link.
const m: SurfaceMouse = .{
.physical_key = .left_shift,
.physical_key = .shift_left,
.mouse_event = .none,
.mouse_shape = .progress,
.mods = .{},
@ -163,7 +163,7 @@ test "keyToMouseShape" {
{
// Mouse is currently hidden
const m: SurfaceMouse = .{
.physical_key = .left_shift,
.physical_key = .shift_left,
.mouse_event = .none,
.mouse_shape = .progress,
.mods = .{},
@ -178,7 +178,7 @@ test "keyToMouseShape" {
{
// default, no mods (mouse tracking)
const m: SurfaceMouse = .{
.physical_key = .left_shift,
.physical_key = .shift_left,
.mouse_event = .x10,
.mouse_shape = .default,
.mods = .{},
@ -194,7 +194,7 @@ test "keyToMouseShape" {
{
// default -> crosshair (mouse tracking)
const m: SurfaceMouse = .{
.physical_key = .left_alt,
.physical_key = .alt_left,
.mouse_event = .x10,
.mouse_shape = .default,
.mods = .{ .ctrl = true, .super = true, .alt = true, .shift = true },
@ -210,7 +210,7 @@ test "keyToMouseShape" {
{
// default -> text (mouse tracking)
const m: SurfaceMouse = .{
.physical_key = .left_shift,
.physical_key = .shift_left,
.mouse_event = .x10,
.mouse_shape = .default,
.mods = .{ .shift = true },
@ -226,7 +226,7 @@ test "keyToMouseShape" {
{
// crosshair -> text (mouse tracking)
const m: SurfaceMouse = .{
.physical_key = .left_alt,
.physical_key = .alt_left,
.mouse_event = .x10,
.mouse_shape = .crosshair,
.mods = .{ .shift = true },
@ -242,7 +242,7 @@ test "keyToMouseShape" {
{
// crosshair -> default (mouse tracking)
const m: SurfaceMouse = .{
.physical_key = .left_alt,
.physical_key = .alt_left,
.mouse_event = .x10,
.mouse_shape = .crosshair,
.mods = .{},
@ -258,7 +258,7 @@ test "keyToMouseShape" {
{
// text -> crosshair (mouse tracking)
const m: SurfaceMouse = .{
.physical_key = .left_alt,
.physical_key = .alt_left,
.mouse_event = .x10,
.mouse_shape = .text,
.mods = .{ .ctrl = true, .super = true, .alt = true, .shift = true },
@ -274,7 +274,7 @@ test "keyToMouseShape" {
{
// text -> default (mouse tracking)
const m: SurfaceMouse = .{
.physical_key = .left_shift,
.physical_key = .shift_left,
.mouse_event = .x10,
.mouse_shape = .text,
.mods = .{},
@ -290,7 +290,7 @@ test "keyToMouseShape" {
{
// text, no mods (no mouse tracking)
const m: SurfaceMouse = .{
.physical_key = .left_shift,
.physical_key = .shift_left,
.mouse_event = .none,
.mouse_shape = .text,
.mods = .{},
@ -306,7 +306,7 @@ test "keyToMouseShape" {
{
// text -> crosshair (no mouse tracking)
const m: SurfaceMouse = .{
.physical_key = .left_alt,
.physical_key = .alt_left,
.mouse_event = .none,
.mouse_shape = .text,
.mods = .{ .ctrl = true, .super = true, .alt = true },
@ -322,7 +322,7 @@ test "keyToMouseShape" {
{
// crosshair -> text (no mouse tracking)
const m: SurfaceMouse = .{
.physical_key = .left_alt,
.physical_key = .alt_left,
.mouse_event = .none,
.mouse_shape = .crosshair,
.mods = .{},