From 2b12bcc07fc6ae5d6d60ac79c0cb69748c29aee1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 11 Jan 2021 21:58:54 +0530 Subject: [PATCH] Docs for legacy functional encoding --- docs/keyboard-protocol.rst | 308 ++++++++++++++++++++++++------------- gen-key-constants.py | 23 ++- kitty/key_encoding.c | 16 +- 3 files changed, 232 insertions(+), 115 deletions(-) diff --git a/docs/keyboard-protocol.rst b/docs/keyboard-protocol.rst index 44c61b2a7..3b2fc7950 100644 --- a/docs/keyboard-protocol.rst +++ b/docs/keyboard-protocol.rst @@ -23,6 +23,9 @@ issues in that proposal, namely: * No way to disambiguate :kbd:`Esc` keypresses, other than using 8-bit controls which are undesirable for other reasons + * Incorrectly claims special keys are sometimes encoded using ``CSI letter`` encodings when it + is actually ``ESC O letter``. + * Makes no mention of cursor key mode and how it changes encodings * Incorrectly encoding shifted keys when shift modifier is used * No way to have non-conflicting escape codes for :kbd:`alt+letter, ctrl+letter, ctrl+alt+letter` key presses @@ -96,6 +99,8 @@ sub-field for the shifted key, like this:: CSI unicode-key-code::base-layout-key +.. _modifiers: + Modifiers ~~~~~~~~~~~~~~ @@ -144,11 +149,13 @@ Non-Unicode keys There are many keys that don't correspond to letters from human languages, and thus aren't represented in Unicode. Think of functional keys, such as :kbd:`Escape, Play, Pause, F1, Home, etc`. These are encoded using Unicode code -points from the Private Use Area (``0xe000 - 0xf8ff``). The mapping of key +points from the Private Use Area (``57344 - 63743``). The mapping of key names to code points for these keys is in the :ref:`Functional key definition table below `. +.. _progressive_enhancement: + Progressive enhancement -------------------------- @@ -176,7 +183,7 @@ The value ``3`` means all set bits are reset, unset bits are left unchanged. "0b1 (1)", "Disambiguate escape codes" "0b10 (2)", "Report key event types" "0b100 (4)", "Report alternate keys" - "0b1000 (8)", "Report all keys as CSIu escape codes" + "0b1000 (8)", "Report all keys as ``CSI u`` escape codes" The program running in the terminal can query the terminal for the current values of the flags by sending:: @@ -208,7 +215,7 @@ the :kbd:`Esc` key generates the byte ``0x1b`` which also is used to indicate the start of an escape code. Similarly pressing the key :kbd:`alt+[` will generate the bytes used for CSI control codes. Turning on this flag will cause the terminal to report the :kbd:`Esc, alt+letter, ctrl+letter, ctrl+alt+letter` -keys using CSIu sequences instead of legacy ones. Here letter is any printable +keys using ``CSI u`` sequences instead of legacy ones. Here letter is any printable ASCII letter (from 32 (i.e. space) to 126 (i.e. ~)). Report event types @@ -227,121 +234,206 @@ This type of progressive enhancement causes the terminal to report alternate key values in addition to the main value, to aid in shortcut matching. See :ref:`key_codes` for details on how these are reported. +Legacy key event encoding +-------------------------------- + +In the default mode, the terminal uses a legacy encoding for key events. In +this encoding, only key press and repeat events are sent and there is no +way to distinguish between them. Text is sent directly as UTF-8 bytes. + +Any key events not described in this section are sent using the standard +``CSI u`` encoding. This includes keys that are not encodeable in the legacy +encoding, thereby increasing the space of useable key combinations even without +progressive enhancement. + +Legacy functional keys +~~~~~~~~~~~~~~~~~~~~~~~~ + +These keys are encoded using three schemes:: + + CSI number ; modifier ~ + CSI 1 ; modifier {ABCDFHPQRS} + ESC O {ABCDFHPQRS} + +In the above, if there are no modifiers, the modifier parameter is omitted. +The modifier value is encoded as described in the :ref:`modifiers` section, +above. When the second form is used, the number is always ``1`` and must be +omitted if the modifiers field is also absent. The third form becomes the +second form when modifiers are present. + +These sequences must match entries in the terminfo database for maximum +compatibility. The table below lists the key, its terminfo entry name and +the escape code used for it by kitty. A different terminal would use whatever +escape code is present in its terminfo database for the key. +Some keys have an alternate representation when the terminal is in *cursor key +mode* (the ``smkx/rmkx`` terminfo capabilities). This form is used only in +*cursor key mode* and only when no modifiers are present. + +.. csv-table:: Legacy functional encoding + :header: "Name", "Terminfo name", "Escape code" + + "INSERT", "kich1", "CSI 2 ~" + "DELETE", "kdch1", "CSI 3 ~" + "PAGE_UP", "kpp", "CSI 5 ~" + "PAGE_DOWN", "knp", "CSI 6 ~" + "UP", "cuu1,kcuu1", "CSI A, ESC O A" + "DOWN", "cud1,kcud1", "CSI B, ESC O B" + "RIGHT", "cuf1,kcuf1", "CSI C, ESC O C" + "LEFT", "cub1,kcub1", "CSI D, ESC O D" + "HOME", "home,khome", "CSI H, ESC O H" + "END", "-,kend", "CSI F, ESC O F" + "F1", "kf1", "ESC O P" + "F2", "kf2", "ESC O Q" + "F3", "kf3", "ESC O R" + "F4", "kf4", "ESC O S" + "F5", "kf5", "CSI 15 ~" + "F6", "kf6", "CSI 17 ~" + "F7", "kf7", "CSI 18 ~" + "F8", "kf8", "CSI 19 ~" + "F9", "kf9", "CSI 20 ~" + "F10", "kf10", "CSI 21 ~" + "F11", "kf11", "CSI 23 ~" + "F12", "kf12", "CSI 24 ~" + +Finally, there are a few more functional keys that have special cased legacy +encodings: + +.. csv-table:: C0 controls + :header: "Key", "Encodings" + + "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" + +Note that :kbd:`Backspace` and :kbd:`ctrl+backspace` are swapped in some +terminals. + +Legacy text keys +~~~~~~~~~~~~~~~~~~~ + + + .. _functional: Functional key definitions ---------------------------- +All numbers are in the Unicode Private Use Area (``57344 - 63743``) except +for a handful of keys that use numbers under 32 (C0 control codes) for legacy +compatibility reasons. + .. {{{ .. start functional key table (auto generated by gen-key-constants.py do not edit) .. csv-table:: Functional key codes - :header: "Name", "Codepoint (base-16)" + :header: "Name", "CSI sequence" - "ESCAPE", "E000" - "ENTER", "E001" - "TAB", "E002" - "BACKSPACE", "E003" - "INSERT", "E004" - "DELETE", "E005" - "LEFT", "E006" - "RIGHT", "E007" - "UP", "E008" - "DOWN", "E009" - "PAGE_UP", "E00A" - "PAGE_DOWN", "E00B" - "HOME", "E00C" - "END", "E00D" - "CAPS_LOCK", "E00E" - "SCROLL_LOCK", "E00F" - "NUM_LOCK", "E010" - "PRINT_SCREEN", "E011" - "PAUSE", "E012" - "MENU", "E013" - "F1", "E014" - "F2", "E015" - "F3", "E016" - "F4", "E017" - "F5", "E018" - "F6", "E019" - "F7", "E01A" - "F8", "E01B" - "F9", "E01C" - "F10", "E01D" - "F11", "E01E" - "F12", "E01F" - "F13", "E020" - "F14", "E021" - "F15", "E022" - "F16", "E023" - "F17", "E024" - "F18", "E025" - "F19", "E026" - "F20", "E027" - "F21", "E028" - "F22", "E029" - "F23", "E02A" - "F24", "E02B" - "F25", "E02C" - "F26", "E02D" - "F27", "E02E" - "F28", "E02F" - "F29", "E030" - "F30", "E031" - "F31", "E032" - "F32", "E033" - "F33", "E034" - "F34", "E035" - "F35", "E036" - "KP_0", "E037" - "KP_1", "E038" - "KP_2", "E039" - "KP_3", "E03A" - "KP_4", "E03B" - "KP_5", "E03C" - "KP_6", "E03D" - "KP_7", "E03E" - "KP_8", "E03F" - "KP_9", "E040" - "KP_DECIMAL", "E041" - "KP_DIVIDE", "E042" - "KP_MULTIPLY", "E043" - "KP_SUBTRACT", "E044" - "KP_ADD", "E045" - "KP_ENTER", "E046" - "KP_EQUAL", "E047" - "KP_SEPARATOR", "E048" - "KP_LEFT", "E049" - "KP_RIGHT", "E04A" - "KP_UP", "E04B" - "KP_DOWN", "E04C" - "KP_PAGE_UP", "E04D" - "KP_PAGE_DOWN", "E04E" - "KP_HOME", "E04F" - "KP_END", "E050" - "KP_INSERT", "E051" - "KP_DELETE", "E052" - "LEFT_SHIFT", "E053" - "LEFT_CONTROL", "E054" - "LEFT_ALT", "E055" - "LEFT_SUPER", "E056" - "RIGHT_SHIFT", "E057" - "RIGHT_CONTROL", "E058" - "RIGHT_ALT", "E059" - "RIGHT_SUPER", "E05A" - "MEDIA_PLAY", "E05B" - "MEDIA_PAUSE", "E05C" - "MEDIA_PLAY_PAUSE", "E05D" - "MEDIA_REVERSE", "E05E" - "MEDIA_STOP", "E05F" - "MEDIA_FAST_FORWARD", "E060" - "MEDIA_REWIND", "E061" - "MEDIA_TRACK_NEXT", "E062" - "MEDIA_TRACK_PREVIOUS", "E063" - "MEDIA_RECORD", "E064" - "LOWER_VOLUME", "E065" - "RAISE_VOLUME", "E066" - "MUTE_VOLUME", "E067" + "ESCAPE", "CSI 57344 ... u" + "ENTER", "CSI 57345 ... u" + "TAB", "CSI 57346 ... u" + "BACKSPACE", "CSI 57347 ... u" + "INSERT", "CSI 2 ... ~" + "DELETE", "CSI 3 ... ~" + "LEFT", "CSI 1 ... D" + "RIGHT", "CSI 1 ... C" + "UP", "CSI 1 ... A" + "DOWN", "CSI 1 ... B" + "PAGE_UP", "CSI 5 ... ~" + "PAGE_DOWN", "CSI 6 ... ~" + "HOME", "CSI 1 ... H or CSI 7 ... ~" + "END", "CSI 1 ... F or CSI 8 ... ~" + "CAPS_LOCK", "CSI 57358 ... u" + "SCROLL_LOCK", "CSI 57359 ... u" + "NUM_LOCK", "CSI 57360 ... u" + "PRINT_SCREEN", "CSI 57361 ... u" + "PAUSE", "CSI 57362 ... u" + "MENU", "CSI 57363 ... u" + "F1", "CSI 1 ... P or CSI 11 ... ~" + "F2", "CSI 1 ... Q or CSI 12 ... ~" + "F3", "CSI 1 ... R or CSI 13 ... ~" + "F4", "CSI 1 ... S or CSI 14 ... ~" + "F5", "CSI 15 ... ~" + "F6", "CSI 17 ... ~" + "F7", "CSI 18 ... ~" + "F8", "CSI 19 ... ~" + "F9", "CSI 20 ... ~" + "F10", "CSI 21 ... ~" + "F11", "CSI 23 ... ~" + "F12", "CSI 24 ... ~" + "F13", "CSI 57376 ... u" + "F14", "CSI 57377 ... u" + "F15", "CSI 57378 ... u" + "F16", "CSI 57379 ... u" + "F17", "CSI 57380 ... u" + "F18", "CSI 57381 ... u" + "F19", "CSI 57382 ... u" + "F20", "CSI 57383 ... u" + "F21", "CSI 57384 ... u" + "F22", "CSI 57385 ... u" + "F23", "CSI 57386 ... u" + "F24", "CSI 57387 ... u" + "F25", "CSI 57388 ... u" + "F26", "CSI 57389 ... u" + "F27", "CSI 57390 ... u" + "F28", "CSI 57391 ... u" + "F29", "CSI 57392 ... u" + "F30", "CSI 57393 ... u" + "F31", "CSI 57394 ... u" + "F32", "CSI 57395 ... u" + "F33", "CSI 57396 ... u" + "F34", "CSI 57397 ... u" + "F35", "CSI 57398 ... u" + "KP_0", "CSI 57399 ... u" + "KP_1", "CSI 57400 ... u" + "KP_2", "CSI 57401 ... u" + "KP_3", "CSI 57402 ... u" + "KP_4", "CSI 57403 ... u" + "KP_5", "CSI 57404 ... u" + "KP_6", "CSI 57405 ... u" + "KP_7", "CSI 57406 ... u" + "KP_8", "CSI 57407 ... u" + "KP_9", "CSI 57408 ... u" + "KP_DECIMAL", "CSI 57409 ... u" + "KP_DIVIDE", "CSI 57410 ... u" + "KP_MULTIPLY", "CSI 57411 ... u" + "KP_SUBTRACT", "CSI 57412 ... u" + "KP_ADD", "CSI 57413 ... u" + "KP_ENTER", "CSI 57414 ... u" + "KP_EQUAL", "CSI 57415 ... u" + "KP_SEPARATOR", "CSI 57416 ... u" + "KP_LEFT", "CSI 57417 ... u" + "KP_RIGHT", "CSI 57418 ... u" + "KP_UP", "CSI 57419 ... u" + "KP_DOWN", "CSI 57420 ... u" + "KP_PAGE_UP", "CSI 57421 ... u" + "KP_PAGE_DOWN", "CSI 57422 ... u" + "KP_HOME", "CSI 57423 ... u" + "KP_END", "CSI 57424 ... u" + "KP_INSERT", "CSI 57425 ... u" + "KP_DELETE", "CSI 57426 ... u" + "LEFT_SHIFT", "CSI 57427 ... u" + "LEFT_CONTROL", "CSI 57428 ... u" + "LEFT_ALT", "CSI 57429 ... u" + "LEFT_SUPER", "CSI 57430 ... u" + "RIGHT_SHIFT", "CSI 57431 ... u" + "RIGHT_CONTROL", "CSI 57432 ... u" + "RIGHT_ALT", "CSI 57433 ... u" + "RIGHT_SUPER", "CSI 57434 ... u" + "MEDIA_PLAY", "CSI 57435 ... u" + "MEDIA_PAUSE", "CSI 57436 ... u" + "MEDIA_PLAY_PAUSE", "CSI 57437 ... u" + "MEDIA_REVERSE", "CSI 57438 ... u" + "MEDIA_STOP", "CSI 57439 ... u" + "MEDIA_FAST_FORWARD", "CSI 57440 ... u" + "MEDIA_REWIND", "CSI 57441 ... u" + "MEDIA_TRACK_NEXT", "CSI 57442 ... u" + "MEDIA_TRACK_PREVIOUS", "CSI 57443 ... u" + "MEDIA_RECORD", "CSI 57444 ... u" + "LOWER_VOLUME", "CSI 57445 ... u" + "RAISE_VOLUME", "CSI 57446 ... u" + "MUTE_VOLUME", "CSI 57447 ... u" .. end functional key table .. }}} diff --git a/gen-key-constants.py b/gen-key-constants.py index ee36c043d..b943eff6e 100644 --- a/gen-key-constants.py +++ b/gen-key-constants.py @@ -113,6 +113,16 @@ raise_volume XF86AudioRaiseVolume - mute_volume XF86AudioMute - ''' # }}} +functional_encoding_overrides = { + 'insert': 2, 'delete': 3, 'page_up': 5, 'page_down': 6, + 'home': 7, 'end': 8, 'f1': 11, 'f2': 12, 'f3': 13, 'f4': 14, + 'f5': 15, 'f6': 17, 'f7': 18, 'f8': 19, 'f9': 20, 'f10': 21, + 'f11': 23, 'f12': 24 +} +different_trailer_functionals = { + 'up': 'A', 'down': 'B', 'right': 'C', 'left': 'D', 'end': 'F', 'home': 'H', + 'f1': 'P', 'f2': 'Q', 'f3': 'R', 'f4': 'S' +} functional_key_names: List[str] = [] name_to_code: Dict[str, int] = {} name_to_xkb: Dict[str, str] = {} @@ -175,11 +185,20 @@ def generate_functional_table() -> None: lines = [ '', '.. csv-table:: Functional key codes', - ' :header: "Name", "Codepoint (base-16)"', + ' :header: "Name", "CSI sequence"', '' ] for name, code in name_to_code.items(): - lines.append(f' "{name.upper()}", "{code:X}"') + if name in functional_encoding_overrides or name in different_trailer_functionals: + code = oc = functional_encoding_overrides.get(name, code) + trailer = different_trailer_functionals.get(name, '~') + code = code if trailer == '~' else 1 + if code == 1 and name not in ('up', 'down', 'left', 'right'): + trailer += f' or CSI {oc} ... ~' + else: + trailer = 'u' + name = f'"{name.upper()}",'.ljust(25) + lines.append(f' {name} "CSI {code} ... {trailer}"') lines.append('') patch_file('docs/keyboard-protocol.rst', 'functional key table', '\n'.join(lines), start_marker='.. ', end_marker='') diff --git a/kitty/key_encoding.c b/kitty/key_encoding.c index 0d0924f36..9d00e5b9c 100644 --- a/kitty/key_encoding.c +++ b/kitty/key_encoding.c @@ -61,8 +61,9 @@ static inline int serialize(const EncodingData *data, char *output, const char csi_trailer) { int pos = 0; #define P(fmt, ...) pos += snprintf(output + pos, KEY_BUFFER_SIZE - 2 - pos, fmt, __VA_ARGS__) - P("\x1b[%u", data->key); - if (data->add_alternates && (data->shifted_key || data->alternate_key)) { + P("\x1b%s", "["); + if (data->key != 1 || data->add_alternates || data->has_mods || data->add_actions) P("%u", data->key); + if (data->add_alternates) { P("%s", ":"); if (data->shifted_key) P("%u", data->shifted_key); if (data->alternate_key) P(":%u", data->alternate_key); @@ -92,10 +93,14 @@ encode_function_key(const KeyEvent *ev, char *output) { switch(key_number) { case GLFW_FKEY_UP: SIMPLE("\x1bOA"); case GLFW_FKEY_DOWN: SIMPLE("\x1bOB"); - case GLFW_FKEY_LEFT: SIMPLE("\x1bOD"); case GLFW_FKEY_RIGHT: SIMPLE("\x1bOC"); - case GLFW_FKEY_HOME: SIMPLE("\x1bOH"); + case GLFW_FKEY_LEFT: SIMPLE("\x1bOD"); case GLFW_FKEY_END: SIMPLE("\x1bOF"); + case GLFW_FKEY_HOME: SIMPLE("\x1bOH"); + case GLFW_FKEY_F1: SIMPLE("\x1bOP"); + case GLFW_FKEY_F2: SIMPLE("\x1bOQ"); + case GLFW_FKEY_F3: SIMPLE("\x1bOR"); + case GLFW_FKEY_F4: SIMPLE("\x1bOS"); default: break; } } @@ -103,7 +108,7 @@ encode_function_key(const KeyEvent *ev, char *output) { switch(key_number) { case GLFW_FKEY_ENTER: SIMPLE("\r"); case GLFW_FKEY_ESCAPE: { - if (ev->disambiguate) { return encode_csi_string('u', "27u", output); } + if (ev->disambiguate) { return encode_csi_string('u', "27", output); } SIMPLE("\x1b"); } case GLFW_FKEY_BACKSPACE: SIMPLE("\x7f"); @@ -154,6 +159,7 @@ encode_function_key(const KeyEvent *ev, char *output) { #undef S EncodingData ed = {0}; init_encoding_data(&ed, ev); + ed.key = key_number; ed.add_alternates = false; return serialize(&ed, output, csi_trailer); }