diff --git a/docs/keyboard-protocol.rst b/docs/keyboard-protocol.rst new file mode 100644 index 000000000..db4accb75 --- /dev/null +++ b/docs/keyboard-protocol.rst @@ -0,0 +1,263 @@ +A protocol for comprehensive keyboard handling in terminals +================================================================= + +There are various problems with the current state of keyboard handling. They +include: + +* No way to use modifiers other than ``Ctrl`` and ``Alt`` + +* No way to reliably use multiple modifier keys, other than, ``Shift+Alt``. + +* No way to handle different types of keyboard events, such as press, release or repeat + +* No reliable way to distinguish single ``Esc`` keypresses from the start of a + escape sequence. Currently, client programs use fragile timing related hacks + for this, leading to bugs, for example: + `neovim #2035 `_. + +To solve these issues and others, kitty has created a new keyboard protocol, +that is backward compatible but allows applications to opt-in to support more +advanced usages. The protocol is based on initial work in `fixterms +`_, however, it corrects various +issues in that proposal, namely: + + * No way to disambiguate Esc keypresses, other than using 8-bit controls + which are undesirable for other reasons + * Incorrectly encoding shifted keys when shift modifier is used + * No way to not have :kbd:`Alt+letter` key presses generate escape codes that + conflict with other escape codes + * No way to specify both shifted and unshifted keys for robust shortcut + matching (think matching :kbd:`ctrl+shift+equal` and :kbd:`ctrl+plus`) + * No way to specify alternate layout key. This is useful for keyboard layouts + such as Cyrillic where you want the shortcut :kbd:`ctrl+c` to work when + pressing the :kbd:`ctrl+ц` on the keyboard. + * No way to report repeat and release key events, only key press events + * No way to report key events without text, useful for gaming. Think of using + the :kbd:`WASD` keys to control movement. + * A very small subset of all possible functional keys are specified. + + +A basic overview +------------------ + +Key events are divided into two types, those that produce text and those that +do not. When a key event produces text, the text is sent directly as UTF-8 +encoded bytes. This is safe as UTF-8 contains no C0 control codes. +When the key event does not have text, the key event is encoded as an escape code. In +legacy compatibility mode (the default) this uses legacy escape codes, so old terminal +applications continue to work. Key events that could not be represented in +legacy mode are encoded using a ``CSI u`` escape code, that most terminal +programs should just ignore. For more advanced features, such as release/repeat +reporting etc., applications can tell the terminal they want this information by +sending an escape code to toggle the mode. + +The central escape code used to encode key events is:: + + CSI unicode-key-code:alternate-key-codes ; modifiers:event-type u + +Spaces in the above definition are present for clarity and should be ignored. +``CSI`` is the bytes ``0x1b 0x5b``. All parameters are decimal numbers. Fields +are separated by the semi-colon and sub-fields by the colon. Only the +``unicode-key-code`` field is mandatory, everything else is optional. The +escape code is terminated by the ``u`` character (the byte ``0x75``). + + +Key codes +~~~~~~~~~~~~~~ + +The ``unicode-key-code`` above is the Unicode codepoint representing the key, as a +decimal number. For example, the :kbd:`A` key is represented as ``97`` which is +the unicode code for lowercase ``a``. Note that the codepoint used is *always* +the lower-case (or more technically, un-shifted) version of the key. If the +user presses, for example, :kbd:`ctrl+shift+a` the escape code would be ``CSI +97;modifiers u``. It *must not* by ``CSI 65; modifiers u``. + +If *alternate key reporting* is requested by the program running in the +terminal, the terminal can send two additional Unicode codepoints, the +*shifted key* and *base layout key*, separated by colons. +The shifted key is simply the upper-case version of ``unicode-codepoint``, or +more technically, the shifted version. So `a` becomes `A` and so on, based on +the current keyboard layout. This is needed to be able to match against a +shortcut such as :kbd:`ctrl+plus` which depending on the type of keyboard could +be either :kbd:`ctrl+shift+equal` or :kbd:`ctrl+plus`. + +The *base layout key* is the key corresponding to the physical key in the +standard PC-101 key layout. So for example, if the user is using a Cyrillic +keyboard with a Cyrillic keyboard layout pressing the :kbd:`ctrl+ц` key will +be :kbd:`ctrl+c` in the standard layout. So the terminal should send the *base +layout key* as ``99`` corresponding to the ``c`` key. + +If only one alternate key is present, it is the *shifted key* if the terminal +wants to send only a base layout key but no shifted key, it must use an empty +sub-field for the shifted key, like this:: + + CSI unicode-key-code::base-layout-key + + +Modifiers +~~~~~~~~~~~~~~ + +This protocol supports four modifier keys, :kbd:`shift, alt, ctrl and super`. +Here super is either the *Windows/Linux* key or the *Cmd* key on mac keyboards. +Modifiers are encoded as a bit field with:: + + shift 0b1 (1) + alt 0b10 (2) + ctrl 0b100 (4) + super 0b1000 (8) + +In the escape code, the modifier value is encoded as a decimal number which is +``1 + actual modifiers``. So to represent :kbd:`shift` only, the value would be ``1 + +1 = 2``, to represent :kbd:`ctrl+shift` the value would be ``1 + 0b101 = 5`` +and so on. If the modifier field is not present in the escape code, its default +value is ``1`` which means no modifiers. + + +Event types +~~~~~~~~~~~~~~~~ + +There are three key event types: ``press, repeat and release``. They are +reported (if requested) as a sub-field of the modifiers field (separated by a +colon). If no modifiers are present, the modifiers field must have the value +``1`` and the event type sub-field the type of event. The ``press`` event type +has value ``1`` and is the default if no event type sub field is present. The +``repeat`` type is ``2`` and the ``release`` type is ``3``. So for example:: + + CSI key-code;1 # this is a press event + CSI key-code;1:1 # this is a press event + CSI key-code;1:2 # this is a repeat event + CSI key-code:1:3 # this is a release event + + +.. note:: Key events that result in text are reported as plain UTF-8 text, so + events are not supported for them, unless the application requests key + report mode, see below. + + +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 +names to code points for these keys is in the +:ref:`Functional key definition table below `. + + +.. _functional: + +Functional key definitions +---------------------------- + +.. start functional key table (auto generated by gen-key-constants.py do not edit) + +.. csv-table:: Functional key codes + :header: "Name", "Codepoint (base-16)" + + "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" + +.. end functional key table diff --git a/docs/protocol-extensions.rst b/docs/protocol-extensions.rst index e1ea0646b..2949833a9 100644 --- a/docs/protocol-extensions.rst +++ b/docs/protocol-extensions.rst @@ -69,91 +69,9 @@ of this protocol to enable drawing of arbitrary raster images in the terminal. Keyboard handling ------------------- -There are various problems with the current state of keyboard handling. They -include: - -* No way to use modifiers other than ``Ctrl`` and ``Alt`` - -* No way to reliably use multiple modifier keys, other than, ``Shift+Alt``. - -* No way to handle different types of keyboard events, such as press, release or repeat - -* No reliable way to distinguish single ``Esc`` keypresses from the start of a - escape sequence. Currently, client programs use fragile timing related hacks - for this, leading to bugs, for example: - `neovim #2035 `_. - -There are already two distinct keyboard handling modes, *normal mode* and -*application mode*. These modes generate different escape sequences for the -various special keys (arrow keys, function keys, home/end etc.) Most terminals -start out in normal mode, however, most shell programs like ``bash`` switch them to -application mode. We propose adding a third mode, named *full mode* that addresses -the shortcomings listed above. - -Switching to the new *full mode* is accomplished using the standard private -mode DECSET escape sequence:: - - [?2017h - -and to leave *full mode*, use DECRST:: - - [?2017l - -The number ``2017`` above is not used for any existing modes, as far as I know. -Client programs can query if the terminal emulator is in *full mode* by using -the standard `DECRQM `_ escape sequence. - -The new mode works as follows: - - * All printable key presses without modifier keys are sent just as in the - *normal mode*. This means all printable ASCII characters and in addition, - ``Enter``, ``Space`` and ``Backspace``. Also any unicode characters generated by - platform specific extended input modes, such as using the ``AltGr`` key. This - is done so that client programs that are not aware of this mode can still - handle basic text entry, so if a *full mode* using program crashes and does - not reset, the user can still issue a ``reset`` command in the shell to restore - normal key handling. Note that this includes pressing the ``Shift`` modifier - and printable keys. Note that this means there are no repeat and release - events for these keys and also for the left and right shift keys. - - * For non printable keys and key combinations including one or more modifiers, - an escape sequence encoding the key event is sent. For details on the - escape sequence, see below. - -The escape sequence encodes the following properties: - - * Type of event: ``press,repeat,release`` - * Modifiers pressed at the time of the event - * The actual key being pressed - -Schematically:: - - _K\ - -Where ```` is one of ``p`` -- press, ``r`` -- release and ``t`` -- repeat. -Modifiers is a bitmask represented as a single base64 digit. Shift -- ``0x1``, -Alt -- ``0x2``, Control -- ``0x4`` and Super -- ``0x8``. ```` is a number -(encoded in base85) corresponding to the key pressed. The key name to number -mapping is defined in :doc:`this table `. - -Client programs must ignore events for keys they do not know. The mapping in -the above table is stable and will never change, however, new codes might be -added to it in the future, for new keys. - -For example:: - - _KpGp\ is ++x (press) - _KrP8\ is ++++PageUp (release) - -This encoding means each key event is represented by 8 or 9 printable ascii -only bytes, for maximum robustness. - -To see the full mode in action, run:: - - kitty +kitten key_demo - -Support for this mode is indicated by the ``fullkbd`` boolean capability -in the terminfo database, in case querying for it via DECQRM is inconvenient. +kitty has a :doc:`keyboard protocol ` for reporting key +presses to terminal applications that solves all key handling issues in +terminal applications. .. _ext_styles: diff --git a/gen-key-constants.py b/gen-key-constants.py index a19040525..ee36c043d 100644 --- a/gen-key-constants.py +++ b/gen-key-constants.py @@ -5,7 +5,7 @@ from typing import Dict, List -functional_key_defs = '''\ +functional_key_defs = '''# {{{ # kitty XKB macOS escape Escape - enter Return - @@ -111,7 +111,8 @@ media_record XF86AudioRecord - lower_volume XF86AudioLowerVolume - raise_volume XF86AudioRaiseVolume - mute_volume XF86AudioMute - -''' +''' # }}} + functional_key_names: List[str] = [] name_to_code: Dict[str, int] = {} name_to_xkb: Dict[str, str] = {} @@ -135,8 +136,14 @@ def patch_file(path: str, what: str, text: str, start_marker: str = '/* ', end_m with open(path, 'r+') as f: raw = f.read() - start = raw.index(start_q) - end = raw.index(end_q) + try: + start = raw.index(start_q) + except ValueError: + raise SystemExit(f'Failed to find "{start_q}" in {path}') + try: + end = raw.index(end_q) + except ValueError: + raise SystemExit(f'Failed to find "{end_q}" in {path}') raw = raw[:start] + start_q + '\n' + text + '\n' + raw[end:] f.seek(0) f.truncate(0) @@ -164,9 +171,23 @@ def generate_xkb_mapping() -> None: patch_file('glfw/xkb_glfw.c', 'glfw to xkb', '\n'.join(rlines)) +def generate_functional_table() -> None: + lines = [ + '', + '.. csv-table:: Functional key codes', + ' :header: "Name", "Codepoint (base-16)"', + '' + ] + for name, code in name_to_code.items(): + lines.append(f' "{name.upper()}", "{code:X}"') + lines.append('') + patch_file('docs/keyboard-protocol.rst', 'functional key table', '\n'.join(lines), start_marker='.. ', end_marker='') + + def main() -> None: generate_glfw_header() generate_xkb_mapping() + generate_functional_table() if __name__ == '__main__':