mirror of
https://github.com/aphrody-labs/opentui.git
synced 2026-10-13 21:08:53 +00:00
core: encode embedded terminal Kitty keypad, lock, and modifier keys (#1648)
When the host terminal speaks the Kitty keyboard protocol, the embedded terminal sent no bytes for keypad Enter, the keypad navigation keys (keypad arrows, Home, End, Page Up and Down, Insert, Delete), Caps Lock, Scroll Lock, Num Lock, Print Screen, Pause, and the left and right Shift, Ctrl, Alt and Super keys. The printable keypad keys reached the child only as text, so a Kitty child could not tell them from the main keys, and Ctrl+keypad 0 arrived as a bare `0`. The parser reports these keys with Kitty-only names (`kpleft`, `capslock`, `leftshift`, ...), and `physicalKey` had no W3C code for them, so Ghostty's encoder got no key. It now maps the 41 names that Ghostty can encode. Ghostty then encodes each key for the child's modes: keypad Left is `CSI D` to a legacy child and `CSI 57417 u` to a Kitty child, keypad 0 is still `0` to a legacy child and `CSI 57399 ; ; 48 u` to a child that asks for key text, and a child in application keypad mode with mode 1035 reset gets `ESC O p`, as in xterm. Some keys still give no bytes because Ghostty has no key or no Kitty encoding for them: the media and volume keys, Hyper, Kitty's Meta, the ISO level shifts, F26 to F35, and Menu. A sweep sends all 111 Kitty functional keys through the parser into a Kitty child, parses the child's bytes back, and checks that only those 30 keys fail to come back with the same name. Rows in the `preserves` table give exact bytes for modified keypad keys, keypad Enter and navigation in legacy mode, and lock and modifier keys in legacy and Kitty modes. Deleting the keypad digit line, moving either end of its range, and each of about twenty tried deletions, swaps or wrong codes in the table makes at least one test fail. Fixes #1645
This commit is contained in:
1 parent
055e2093f1
commit
4eca5d5ada
2 files changed
+57
No files matched your search
@@ -275,6 +275,17 @@ describe("EmbeddedTerminalRenderable", () => {
|
||||
expect(terminal.encodeKey(keyEvent({ name, sequence: "" })).length > 0).toBe(name !== "menu")
|
||||
})
|
||||
|
||||
// Ghostty has no key or no Kitty encoding for 30 of the 111 Kitty functional keys: menu, F26-F35, the 10 media
|
||||
// keys, the 3 volume keys, hyper, Kitty's meta (Ghostty's meta is Kitty's super), and the ISO level shifts.
|
||||
test("round-trips every Kitty functional key that Ghostty can encode", () => {
|
||||
const terminal = new EmbeddedTerminalRenderable(setup.renderer, { width: 20, height: 4 })
|
||||
terminal.write("\x1b[>31u")
|
||||
const keys = Array.from({ length: 111 }, (_, i) => parseKeypress(`\x1b[${57344 + i}u`, { useKittyKeyboard: true })!)
|
||||
const sent = keys.map((key) => new TextDecoder().decode(terminal.encodeKey(new KeyEvent(key))))
|
||||
const lost = keys.filter((key, i) => parseKeypress(sent[i]!, { useKittyKeyboard: true })?.name !== key.name)
|
||||
expect(lost.map((key) => key.name)).toHaveLength(30)
|
||||
})
|
||||
|
||||
test.each([
|
||||
["Kitty Ctrl+c", 3, "\x1b[99;5u", "\x1b[99;5u"],
|
||||
["Kitty Escape", 3, "\x1b[27u", "\x1b[27u"],
|
||||
@@ -293,6 +304,20 @@ describe("EmbeddedTerminalRenderable", () => {
|
||||
["Kitty keypad Begin", 1, "\x1b[57427u", "\x1b[57427u"],
|
||||
["Kitty Ctrl+keypad Begin", 1, "\x1b[57427;5u", "\x1b[57427;5u"],
|
||||
["SS3 Up into a DECCKM child", "\x1b[?1h", "\x1bOA", "\x1bOA"],
|
||||
["Kitty keypad 9", 31, "\x1b[57408u", "\x1b[57408;;57u"],
|
||||
["Kitty Ctrl+keypad 0", 1, "\x1b[57399;5u", "\x1b[57399;5u"],
|
||||
["Kitty Ctrl+keypad 0 in legacy mode", 0, "\x1b[57399;5u", "0"],
|
||||
["Kitty keypad 0 into a DECKPAM child without mode 1035", "\x1b[?1035l\x1b=", "\x1b[57399u", "\x1bOp"],
|
||||
["Kitty Ctrl+keypad minus", 1, "\x1b[57412;5u", "\x1b[57412;5u"],
|
||||
["Kitty keypad plus in legacy mode", 0, "\x1b[57413u", "+"],
|
||||
["Kitty keypad Enter", 1, "\x1b[57414u", "\x1b[57414u"],
|
||||
["Kitty keypad Enter in legacy mode", 0, "\x1b[57414u", "\r"],
|
||||
["Kitty Ctrl+keypad Left", 1, "\x1b[57417;5u", "\x1b[57417;5u"],
|
||||
["Kitty Ctrl+keypad Left in legacy mode", 0, "\x1b[57417;5u", "\x1b[1;5D"],
|
||||
["Kitty keypad Page Down in legacy mode", 0, "\x1b[57422u", "\x1b[6~"],
|
||||
["Kitty Caps Lock in legacy mode", 0, "\x1b[57358u", ""],
|
||||
["Kitty Shift+Left Shift", 31, "\x1b[57441;2u", "\x1b[57441;2u"],
|
||||
["Kitty Left Shift in legacy mode", 0, "\x1b[57441u", ""],
|
||||
["plain Dvorak u", 1, "\x1b[117::102;1u", "u"],
|
||||
["plain Dvorak d", 1, "\x1b[100::104;1u", "d"],
|
||||
["Dvorak Ctrl+U", 1, "\x1b[117::102;5u", "\x1b[117;5u"],
|
||||
|
||||
@@ -432,10 +432,42 @@ function physicalKey(key: KeyEvent) {
|
||||
if (/^[a-z]$/i.test(name)) return `Key${name.toUpperCase()}`
|
||||
if (/^[0-9]$/.test(name)) return `Digit${name}`
|
||||
if (/^f([1-9]|1[0-9]|2[0-5])$/i.test(key.name)) return key.name.toUpperCase()
|
||||
if (/^kp[0-9]$/i.test(key.name)) return `Numpad${key.name.slice(2)}`
|
||||
return (
|
||||
{
|
||||
backspace: "Backspace",
|
||||
clear: "NumpadBegin",
|
||||
kpdecimal: "NumpadDecimal",
|
||||
kpdivide: "NumpadDivide",
|
||||
kpmultiply: "NumpadMultiply",
|
||||
kpminus: "NumpadSubtract",
|
||||
kpplus: "NumpadAdd",
|
||||
kpenter: "NumpadEnter",
|
||||
kpequal: "NumpadEqual",
|
||||
kpseparator: "NumpadSeparator",
|
||||
kpleft: "NumpadLeft",
|
||||
kpright: "NumpadRight",
|
||||
kpup: "NumpadUp",
|
||||
kpdown: "NumpadDown",
|
||||
kppageup: "NumpadPageUp",
|
||||
kppagedown: "NumpadPageDown",
|
||||
kphome: "NumpadHome",
|
||||
kpend: "NumpadEnd",
|
||||
kpinsert: "NumpadInsert",
|
||||
kpdelete: "NumpadDelete",
|
||||
capslock: "CapsLock",
|
||||
scrolllock: "ScrollLock",
|
||||
numlock: "NumLock",
|
||||
printscreen: "PrintScreen",
|
||||
pause: "Pause",
|
||||
leftshift: "ShiftLeft",
|
||||
leftctrl: "ControlLeft",
|
||||
leftalt: "AltLeft",
|
||||
leftsuper: "MetaLeft",
|
||||
rightshift: "ShiftRight",
|
||||
rightctrl: "ControlRight",
|
||||
rightalt: "AltRight",
|
||||
rightsuper: "MetaRight",
|
||||
enter: "Enter",
|
||||
return: "Enter",
|
||||
space: "Space",
|
||||
|
||||
Reference in new issue
Block a user