Skip to content

Commit 56fcbb9

Browse files
committed
Key encoding: in legacy mode use legacy encoding for a few more combinations
Some legacy terminal applications get confused seeing CSI u escape codes. Since it is relatively common to press ctrl or shift and space/enter/tab/backspace, emit the same bytes as traditional terminals do for these common keys.
1 parent de100ac commit 56fcbb9

File tree

3 files changed

+76
-47
lines changed

3 files changed

+76
-47
lines changed

docs/keyboard-protocol.rst

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -415,17 +415,18 @@ mode* (the ``smkx/rmkx`` terminfo capabilities). This form is used only in
415415
"F11", "kf11", "CSI 23 ~"
416416
"F12", "kf12", "CSI 24 ~"
417417

418-
There are a few more functional keys that have special cased legacy
419-
encodings:
418+
There are a few more functional keys that have special cased legacy encodings.
419+
These are present because they are commonly used and for the sake of legacy
420+
terminal applications that get confused when seeing CSI u escape codes:
420421

421422
.. csv-table:: C0 controls
422-
:header: "Key", "Encodings"
423+
:header: "Key", "No mods", "Ctrl", "Alt", "Shift", "Ctrl + Shift", "Alt + Shift", "Ctrl + Alt"
423424

424-
"Enter", "Plain - 0xd, alt+Enter - 0x1b 0x1d"
425-
"Escape", "Plain - 0x1b, alt+Esc - 0x1b 0x1b"
426-
"Backspace", "Plain - 0x7f, alt+Backspace - 0x1b 0x7f, ctrl+Backspace - 0x08"
427-
"Space", "Plain - 0x20, ctrl+Space - 0x0, alt+space - 0x1b 0x20"
428-
"Tab", "Plain - 0x09, shift+Tab - CSI Z"
425+
"Enter", "0xd", "0xd", "0x1b 0xd", "0xd", "0xd", "0x1b 0xd", "0x1b 0xd"
426+
"Escape", "0x1b", "0x1b", "0x1b 0x1b", "0x1b", "0x1b", "0x1b 0x1b", "0x1b 0x1b"
427+
"Backspace", "0x7f", "0x8", "0x1b 0x7f", "0x7f", "0x8", "0x1b 0x7f", "0x1b 0x8"
428+
"Tab", "0x9", "0x9", "0x1b 0x9", "CSI Z", "CSI Z", "0x1b CSI Z", "0x1b 0x9"
429+
"Space", "0x20", "0x0", "0x1b 0x20", "0x20", "0x0", "0x1b 0x20", "0x1b 0x0"
429430

430431
Note that :kbd:`Backspace` and :kbd:`ctrl+Backspace` are swapped in some
431432
terminals, this can be detected using the ``kbs`` terminfo property that

kitty/key_encoding.c

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,34 @@ convert_kp_key_to_normal_key(uint32_t key_number) {
126126
return key_number;
127127
}
128128

129+
static int
130+
legacy_functional_key_encoding_with_modifiers(uint32_t key_number, const KeyEvent *ev, char *output) {
131+
const char *prefix = ev->mods.value & ALT ? "\x1b" : "";
132+
const char *main = "";
133+
switch (key_number) {
134+
case GLFW_FKEY_ENTER:
135+
main = "\x0d";
136+
break;
137+
case GLFW_FKEY_ESCAPE:
138+
main = "\x1b";
139+
break;
140+
case GLFW_FKEY_BACKSPACE:
141+
main = ev->mods.value & CTRL ? "\x08" : "\x7f";
142+
break;
143+
case GLFW_FKEY_TAB:
144+
if (ev->mods.value & SHIFT) {
145+
prefix = ev->mods.value & ALT ? "\x1b\x1b" : "\x1b";
146+
main = "[Z";
147+
} else {
148+
main = "\t";
149+
}
150+
break;
151+
default:
152+
return -1;
153+
}
154+
return snprintf(output, KEY_BUFFER_SIZE, "%s%s", prefix, main);
155+
}
156+
129157
static int
130158
encode_function_key(const KeyEvent *ev, char *output) {
131159
#define SIMPLE(val) return snprintf(output, KEY_BUFFER_SIZE, "%s", val);
@@ -158,18 +186,11 @@ encode_function_key(const KeyEvent *ev, char *output) {
158186
default: break;
159187
}
160188
}
189+
} else if (legacy_mode) {
190+
int num = legacy_functional_key_encoding_with_modifiers(key_number, ev, output);
191+
if (num > -1) return num;
161192
}
162-
if (ev->mods.value == ALT && !ev->disambiguate) {
163-
switch(key_number) {
164-
case GLFW_FKEY_TAB: SIMPLE("\x1b\t");
165-
case GLFW_FKEY_ENTER: SIMPLE("\x1b\r");
166-
case GLFW_FKEY_BACKSPACE: SIMPLE("\x1b\x7f");
167-
}
168-
}
169-
if (ev->mods.value == SHIFT && key_number == GLFW_FKEY_TAB && !ev->disambiguate) { SIMPLE("\x1b[Z"); }
170-
if (ev->mods.value == CTRL && key_number == GLFW_FKEY_BACKSPACE && !ev->disambiguate) { SIMPLE("\x08"); }
171193
#undef SIMPLE
172-
173194
#define S(number, trailer) key_number = number; csi_trailer = trailer; break
174195
switch(key_number) {
175196
/* start special numbers (auto generated by gen-key-constants.py do not edit) */
@@ -288,6 +309,10 @@ encode_printable_ascii_key_legacy(const KeyEvent *ev, char *output) {
288309
return snprintf(output, KEY_BUFFER_SIZE, "%c", ctrled_key(key));
289310
if (mods == (CTRL | ALT))
290311
return snprintf(output, KEY_BUFFER_SIZE, "\x1b%c", ctrled_key(key));
312+
if (key == ' ') {
313+
if (mods == (CTRL | SHIFT)) return snprintf(output, KEY_BUFFER_SIZE, "%c", ctrled_key(key));
314+
if (mods == (ALT | SHIFT)) return snprintf(output, KEY_BUFFER_SIZE, "\x1b%c", key);
315+
}
291316
return 0;
292317
}
293318

kitty_tests/keys.py

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -60,29 +60,32 @@ def mods_test(key, plain=None, shift=None, ctrl=None, alt=None, calt=None, cshif
6060

6161
def a(a, b):
6262
ae(a, b, f"{a.encode('ascii')} != {b.encode('ascii')}")
63+
64+
def w(a, b):
65+
return c(b) if a is None else a
66+
6367
a(e(), plain or c())
64-
a(e(mods=defines.GLFW_MOD_SHIFT), shift or c(defines.GLFW_MOD_SHIFT))
65-
a(e(mods=defines.GLFW_MOD_CONTROL), ctrl or c(defines.GLFW_MOD_CONTROL))
66-
a(e(mods=defines.GLFW_MOD_ALT | defines.GLFW_MOD_CONTROL), calt or c(defines.GLFW_MOD_ALT | defines.GLFW_MOD_CONTROL))
67-
a(e(mods=defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_CONTROL), cshift or c(defines.GLFW_MOD_CONTROL | defines.GLFW_MOD_SHIFT))
68-
a(e(mods=defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_ALT), ashift or c(defines.GLFW_MOD_ALT | defines.GLFW_MOD_SHIFT))
68+
a(e(mods=defines.GLFW_MOD_SHIFT), w(shift, defines.GLFW_MOD_SHIFT))
69+
a(e(mods=defines.GLFW_MOD_CONTROL), w(ctrl, defines.GLFW_MOD_CONTROL))
70+
a(e(mods=defines.GLFW_MOD_ALT | defines.GLFW_MOD_CONTROL), w(calt, defines.GLFW_MOD_ALT | defines.GLFW_MOD_CONTROL))
71+
a(e(mods=defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_CONTROL), w(cshift, defines.GLFW_MOD_CONTROL | defines.GLFW_MOD_SHIFT))
72+
a(e(mods=defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_ALT), w(ashift, defines.GLFW_MOD_ALT | defines.GLFW_MOD_SHIFT))
73+
74+
def mkp(name, *a, **kw):
75+
for x in (f'GLFW_FKEY_{name}', f'GLFW_FKEY_KP_{name}'):
76+
k = getattr(defines, x)
77+
mods_test(k, *a, **kw)
6978

70-
mods_test(defines.GLFW_FKEY_ENTER, '\x0d', alt='\033\x0d', csi_num=ord('\r'))
71-
mods_test(defines.GLFW_FKEY_KP_ENTER, '\x0d', alt='\033\x0d', csi_num=ord('\r'))
72-
mods_test(defines.GLFW_FKEY_ESCAPE, '\x1b', alt='\033\033', csi_num=27)
73-
mods_test(defines.GLFW_FKEY_BACKSPACE, '\x7f', alt='\033\x7f', ctrl='\x08', csi_num=127)
74-
mods_test(defines.GLFW_FKEY_TAB, '\t', alt='\033\t', shift='\x1b[Z', csi_num=ord('\t'))
75-
mods_test(defines.GLFW_FKEY_INSERT, csi_num=2, trailer='~')
76-
mods_test(defines.GLFW_FKEY_KP_INSERT, csi_num=2, trailer='~')
77-
mods_test(defines.GLFW_FKEY_DELETE, csi_num=3, trailer='~')
78-
mods_test(defines.GLFW_FKEY_KP_DELETE, csi_num=3, trailer='~')
79-
mods_test(defines.GLFW_FKEY_PAGE_UP, csi_num=5, trailer='~')
80-
mods_test(defines.GLFW_FKEY_KP_PAGE_UP, csi_num=5, trailer='~')
81-
mods_test(defines.GLFW_FKEY_KP_PAGE_DOWN, csi_num=6, trailer='~')
82-
mods_test(defines.GLFW_FKEY_HOME, csi_num=1, trailer='H')
83-
mods_test(defines.GLFW_FKEY_KP_HOME, csi_num=1, trailer='H')
84-
mods_test(defines.GLFW_FKEY_END, csi_num=1, trailer='F')
85-
mods_test(defines.GLFW_FKEY_KP_END, csi_num=1, trailer='F')
79+
mkp('ENTER', '\x0d', alt='\033\x0d', ctrl='\x0d', shift='\x0d', ashift='\033\x0d', calt='\033\x0d', cshift='\x0d')
80+
mods_test(defines.GLFW_FKEY_ESCAPE, '\x1b', alt='\033\033', ctrl='\x1b', shift='\x1b', calt='\x1b\x1b', cshift='\x1b', ashift='\x1b\x1b')
81+
mods_test(defines.GLFW_FKEY_BACKSPACE, '\x7f', alt='\033\x7f', ctrl='\x08', shift='\x7f', ashift='\033\x7f', cshift='\x08', calt='\x1b\x08')
82+
mods_test(defines.GLFW_FKEY_TAB, '\t', alt='\033\t', shift='\x1b[Z', ctrl='\t', ashift='\x1b\x1b[Z', cshift='\x1b[Z', calt='\x1b\t')
83+
mkp('INSERT', csi_num=2, trailer='~')
84+
mkp('DELETE', csi_num=3, trailer='~')
85+
mkp('PAGE_UP', csi_num=5, trailer='~')
86+
mkp('PAGE_DOWN', csi_num=6, trailer='~')
87+
mkp('HOME', csi_num=1, trailer='H')
88+
mkp('END', csi_num=1, trailer='F')
8689
mods_test(defines.GLFW_FKEY_F1, csi_num=1, trailer='P')
8790
mods_test(defines.GLFW_FKEY_F2, csi_num=1, trailer='Q')
8891
mods_test(defines.GLFW_FKEY_F3, csi_num=1, trailer='R')
@@ -95,14 +98,10 @@ def a(a, b):
9598
mods_test(defines.GLFW_FKEY_F10, csi_num=21, trailer='~')
9699
mods_test(defines.GLFW_FKEY_F11, csi_num=23, trailer='~')
97100
mods_test(defines.GLFW_FKEY_F12, csi_num=24, trailer='~')
98-
mods_test(defines.GLFW_FKEY_UP, csi_num=1, trailer='A')
99-
mods_test(defines.GLFW_FKEY_KP_UP, csi_num=1, trailer='A')
100-
mods_test(defines.GLFW_FKEY_DOWN, csi_num=1, trailer='B')
101-
mods_test(defines.GLFW_FKEY_KP_DOWN, csi_num=1, trailer='B')
102-
mods_test(defines.GLFW_FKEY_RIGHT, csi_num=1, trailer='C')
103-
mods_test(defines.GLFW_FKEY_KP_RIGHT, csi_num=1, trailer='C')
104-
mods_test(defines.GLFW_FKEY_LEFT, csi_num=1, trailer='D')
105-
mods_test(defines.GLFW_FKEY_KP_LEFT, csi_num=1, trailer='D')
101+
mkp('UP', csi_num=1, trailer='A')
102+
mkp('DOWN', csi_num=1, trailer='B')
103+
mkp('RIGHT', csi_num=1, trailer='C')
104+
mkp('LEFT', csi_num=1, trailer='D')
106105

107106
# legacy key tests {{{
108107
# start legacy letter tests (auto generated by gen-key-constants.py do not edit)
@@ -397,6 +396,10 @@ def a(a, b):
397396
ae(enc(key=ord(' ')), ' ')
398397
ae(enc(key=ord(' '), mods=ctrl), '\0')
399398
ae(enc(key=ord(' '), mods=alt), '\x1b ')
399+
ae(enc(key=ord(' '), mods=shift), ' ')
400+
ae(enc(key=ord(' '), mods=ctrl | alt), '\x1b\0')
401+
ae(enc(key=ord(' '), mods=ctrl | shift), '\0')
402+
ae(enc(key=ord(' '), mods=alt | shift), '\x1b ')
400403
ae(enc(key=ord('i'), mods=ctrl | shift), csi(ctrl | shift, ord('i')))
401404
ae(enc(key=defines.GLFW_FKEY_LEFT_SHIFT), '')
402405
ae(enc(key=defines.GLFW_FKEY_CAPS_LOCK), '')

0 commit comments

Comments
 (0)