From 56fcbb95abb8409ca7ad2bcc185fa374d8f4bbc0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 21 Feb 2021 11:14:25 +0530 Subject: [PATCH] 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. --- docs/keyboard-protocol.rst | 17 +++++----- kitty/key_encoding.c | 45 +++++++++++++++++++++------ kitty_tests/keys.py | 63 ++++++++++++++++++++------------------ 3 files changed, 77 insertions(+), 48 deletions(-) diff --git a/docs/keyboard-protocol.rst b/docs/keyboard-protocol.rst index 3daa969b5..d613a6295 100644 --- a/docs/keyboard-protocol.rst +++ b/docs/keyboard-protocol.rst @@ -415,17 +415,18 @@ mode* (the ``smkx/rmkx`` terminfo capabilities). This form is used only in "F11", "kf11", "CSI 23 ~" "F12", "kf12", "CSI 24 ~" -There are a few more functional keys that have special cased legacy -encodings: +There are a few more functional keys that have special cased legacy encodings. +These are present because they are commonly used and for the sake of legacy +terminal applications that get confused when seeing CSI u escape codes: .. csv-table:: C0 controls - :header: "Key", "Encodings" + :header: "Key", "No mods", "Ctrl", "Alt", "Shift", "Ctrl + Shift", "Alt + Shift", "Ctrl + Alt" - "Enter", "Plain - 0xd, alt+Enter - 0x1b 0x1d" - "Escape", "Plain - 0x1b, alt+Esc - 0x1b 0x1b" - "Backspace", "Plain - 0x7f, alt+Backspace - 0x1b 0x7f, ctrl+Backspace - 0x08" - "Space", "Plain - 0x20, ctrl+Space - 0x0, alt+space - 0x1b 0x20" - "Tab", "Plain - 0x09, shift+Tab - CSI Z" + "Enter", "0xd", "0xd", "0x1b 0xd", "0xd", "0xd", "0x1b 0xd", "0x1b 0xd" + "Escape", "0x1b", "0x1b", "0x1b 0x1b", "0x1b", "0x1b", "0x1b 0x1b", "0x1b 0x1b" + "Backspace", "0x7f", "0x8", "0x1b 0x7f", "0x7f", "0x8", "0x1b 0x7f", "0x1b 0x8" + "Tab", "0x9", "0x9", "0x1b 0x9", "CSI Z", "CSI Z", "0x1b CSI Z", "0x1b 0x9" + "Space", "0x20", "0x0", "0x1b 0x20", "0x20", "0x0", "0x1b 0x20", "0x1b 0x0" Note that :kbd:`Backspace` and :kbd:`ctrl+Backspace` are swapped in some terminals, this can be detected using the ``kbs`` terminfo property that diff --git a/kitty/key_encoding.c b/kitty/key_encoding.c index b013c6567..a688f8ff7 100644 --- a/kitty/key_encoding.c +++ b/kitty/key_encoding.c @@ -126,6 +126,34 @@ convert_kp_key_to_normal_key(uint32_t key_number) { return key_number; } +static int +legacy_functional_key_encoding_with_modifiers(uint32_t key_number, const KeyEvent *ev, char *output) { + const char *prefix = ev->mods.value & ALT ? "\x1b" : ""; + const char *main = ""; + switch (key_number) { + case GLFW_FKEY_ENTER: + main = "\x0d"; + break; + case GLFW_FKEY_ESCAPE: + main = "\x1b"; + break; + case GLFW_FKEY_BACKSPACE: + main = ev->mods.value & CTRL ? "\x08" : "\x7f"; + break; + case GLFW_FKEY_TAB: + if (ev->mods.value & SHIFT) { + prefix = ev->mods.value & ALT ? "\x1b\x1b" : "\x1b"; + main = "[Z"; + } else { + main = "\t"; + } + break; + default: + return -1; + } + return snprintf(output, KEY_BUFFER_SIZE, "%s%s", prefix, main); +} + static int encode_function_key(const KeyEvent *ev, char *output) { #define SIMPLE(val) return snprintf(output, KEY_BUFFER_SIZE, "%s", val); @@ -158,18 +186,11 @@ encode_function_key(const KeyEvent *ev, char *output) { default: break; } } + } else if (legacy_mode) { + int num = legacy_functional_key_encoding_with_modifiers(key_number, ev, output); + if (num > -1) return num; } - if (ev->mods.value == ALT && !ev->disambiguate) { - switch(key_number) { - case GLFW_FKEY_TAB: SIMPLE("\x1b\t"); - case GLFW_FKEY_ENTER: SIMPLE("\x1b\r"); - case GLFW_FKEY_BACKSPACE: SIMPLE("\x1b\x7f"); - } - } - if (ev->mods.value == SHIFT && key_number == GLFW_FKEY_TAB && !ev->disambiguate) { SIMPLE("\x1b[Z"); } - if (ev->mods.value == CTRL && key_number == GLFW_FKEY_BACKSPACE && !ev->disambiguate) { SIMPLE("\x08"); } #undef SIMPLE - #define S(number, trailer) key_number = number; csi_trailer = trailer; break switch(key_number) { /* 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) { return snprintf(output, KEY_BUFFER_SIZE, "%c", ctrled_key(key)); if (mods == (CTRL | ALT)) return snprintf(output, KEY_BUFFER_SIZE, "\x1b%c", ctrled_key(key)); + if (key == ' ') { + if (mods == (CTRL | SHIFT)) return snprintf(output, KEY_BUFFER_SIZE, "%c", ctrled_key(key)); + if (mods == (ALT | SHIFT)) return snprintf(output, KEY_BUFFER_SIZE, "\x1b%c", key); + } return 0; } diff --git a/kitty_tests/keys.py b/kitty_tests/keys.py index 23f06c951..6a6592962 100644 --- a/kitty_tests/keys.py +++ b/kitty_tests/keys.py @@ -60,29 +60,32 @@ class TestKeys(BaseTest): def a(a, b): ae(a, b, f"{a.encode('ascii')} != {b.encode('ascii')}") - a(e(), plain or c()) - a(e(mods=defines.GLFW_MOD_SHIFT), shift or c(defines.GLFW_MOD_SHIFT)) - a(e(mods=defines.GLFW_MOD_CONTROL), ctrl or c(defines.GLFW_MOD_CONTROL)) - a(e(mods=defines.GLFW_MOD_ALT | defines.GLFW_MOD_CONTROL), calt or c(defines.GLFW_MOD_ALT | defines.GLFW_MOD_CONTROL)) - a(e(mods=defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_CONTROL), cshift or c(defines.GLFW_MOD_CONTROL | defines.GLFW_MOD_SHIFT)) - a(e(mods=defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_ALT), ashift or c(defines.GLFW_MOD_ALT | defines.GLFW_MOD_SHIFT)) - mods_test(defines.GLFW_FKEY_ENTER, '\x0d', alt='\033\x0d', csi_num=ord('\r')) - mods_test(defines.GLFW_FKEY_KP_ENTER, '\x0d', alt='\033\x0d', csi_num=ord('\r')) - mods_test(defines.GLFW_FKEY_ESCAPE, '\x1b', alt='\033\033', csi_num=27) - mods_test(defines.GLFW_FKEY_BACKSPACE, '\x7f', alt='\033\x7f', ctrl='\x08', csi_num=127) - mods_test(defines.GLFW_FKEY_TAB, '\t', alt='\033\t', shift='\x1b[Z', csi_num=ord('\t')) - mods_test(defines.GLFW_FKEY_INSERT, csi_num=2, trailer='~') - mods_test(defines.GLFW_FKEY_KP_INSERT, csi_num=2, trailer='~') - mods_test(defines.GLFW_FKEY_DELETE, csi_num=3, trailer='~') - mods_test(defines.GLFW_FKEY_KP_DELETE, csi_num=3, trailer='~') - mods_test(defines.GLFW_FKEY_PAGE_UP, csi_num=5, trailer='~') - mods_test(defines.GLFW_FKEY_KP_PAGE_UP, csi_num=5, trailer='~') - mods_test(defines.GLFW_FKEY_KP_PAGE_DOWN, csi_num=6, trailer='~') - mods_test(defines.GLFW_FKEY_HOME, csi_num=1, trailer='H') - mods_test(defines.GLFW_FKEY_KP_HOME, csi_num=1, trailer='H') - mods_test(defines.GLFW_FKEY_END, csi_num=1, trailer='F') - mods_test(defines.GLFW_FKEY_KP_END, csi_num=1, trailer='F') + def w(a, b): + return c(b) if a is None else a + + a(e(), plain or c()) + a(e(mods=defines.GLFW_MOD_SHIFT), w(shift, defines.GLFW_MOD_SHIFT)) + a(e(mods=defines.GLFW_MOD_CONTROL), w(ctrl, defines.GLFW_MOD_CONTROL)) + a(e(mods=defines.GLFW_MOD_ALT | defines.GLFW_MOD_CONTROL), w(calt, defines.GLFW_MOD_ALT | defines.GLFW_MOD_CONTROL)) + a(e(mods=defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_CONTROL), w(cshift, defines.GLFW_MOD_CONTROL | defines.GLFW_MOD_SHIFT)) + a(e(mods=defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_ALT), w(ashift, defines.GLFW_MOD_ALT | defines.GLFW_MOD_SHIFT)) + + def mkp(name, *a, **kw): + for x in (f'GLFW_FKEY_{name}', f'GLFW_FKEY_KP_{name}'): + k = getattr(defines, x) + mods_test(k, *a, **kw) + + mkp('ENTER', '\x0d', alt='\033\x0d', ctrl='\x0d', shift='\x0d', ashift='\033\x0d', calt='\033\x0d', cshift='\x0d') + mods_test(defines.GLFW_FKEY_ESCAPE, '\x1b', alt='\033\033', ctrl='\x1b', shift='\x1b', calt='\x1b\x1b', cshift='\x1b', ashift='\x1b\x1b') + mods_test(defines.GLFW_FKEY_BACKSPACE, '\x7f', alt='\033\x7f', ctrl='\x08', shift='\x7f', ashift='\033\x7f', cshift='\x08', calt='\x1b\x08') + 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') + mkp('INSERT', csi_num=2, trailer='~') + mkp('DELETE', csi_num=3, trailer='~') + mkp('PAGE_UP', csi_num=5, trailer='~') + mkp('PAGE_DOWN', csi_num=6, trailer='~') + mkp('HOME', csi_num=1, trailer='H') + mkp('END', csi_num=1, trailer='F') mods_test(defines.GLFW_FKEY_F1, csi_num=1, trailer='P') mods_test(defines.GLFW_FKEY_F2, csi_num=1, trailer='Q') mods_test(defines.GLFW_FKEY_F3, csi_num=1, trailer='R') @@ -95,14 +98,10 @@ class TestKeys(BaseTest): mods_test(defines.GLFW_FKEY_F10, csi_num=21, trailer='~') mods_test(defines.GLFW_FKEY_F11, csi_num=23, trailer='~') mods_test(defines.GLFW_FKEY_F12, csi_num=24, trailer='~') - mods_test(defines.GLFW_FKEY_UP, csi_num=1, trailer='A') - mods_test(defines.GLFW_FKEY_KP_UP, csi_num=1, trailer='A') - mods_test(defines.GLFW_FKEY_DOWN, csi_num=1, trailer='B') - mods_test(defines.GLFW_FKEY_KP_DOWN, csi_num=1, trailer='B') - mods_test(defines.GLFW_FKEY_RIGHT, csi_num=1, trailer='C') - mods_test(defines.GLFW_FKEY_KP_RIGHT, csi_num=1, trailer='C') - mods_test(defines.GLFW_FKEY_LEFT, csi_num=1, trailer='D') - mods_test(defines.GLFW_FKEY_KP_LEFT, csi_num=1, trailer='D') + mkp('UP', csi_num=1, trailer='A') + mkp('DOWN', csi_num=1, trailer='B') + mkp('RIGHT', csi_num=1, trailer='C') + mkp('LEFT', csi_num=1, trailer='D') # legacy key tests {{{ # start legacy letter tests (auto generated by gen-key-constants.py do not edit) @@ -397,6 +396,10 @@ class TestKeys(BaseTest): ae(enc(key=ord(' ')), ' ') ae(enc(key=ord(' '), mods=ctrl), '\0') ae(enc(key=ord(' '), mods=alt), '\x1b ') + ae(enc(key=ord(' '), mods=shift), ' ') + ae(enc(key=ord(' '), mods=ctrl | alt), '\x1b\0') + ae(enc(key=ord(' '), mods=ctrl | shift), '\0') + ae(enc(key=ord(' '), mods=alt | shift), '\x1b ') ae(enc(key=ord('i'), mods=ctrl | shift), csi(ctrl | shift, ord('i'))) ae(enc(key=defines.GLFW_FKEY_LEFT_SHIFT), '') ae(enc(key=defines.GLFW_FKEY_CAPS_LOCK), '')