Start documenting new keyboard protocol
This commit is contained in:
parent
c8a9336160
commit
b63ae10a09
263
docs/keyboard-protocol.rst
Normal file
263
docs/keyboard-protocol.rst
Normal file
@ -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 <https://github.com/neovim/neovim/issues/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
|
||||
<http://www.leonerd.org.uk/hacks/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:
|
||||
|
||||
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
|
||||
@ -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 <https://github.com/neovim/neovim/issues/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::
|
||||
|
||||
<ESC>[?2017h
|
||||
|
||||
and to leave *full mode*, use DECRST::
|
||||
|
||||
<ESC>[?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 <https://vt100.net/docs/vt510-rm/DECRQM.html>`_ 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::
|
||||
|
||||
<ESC>_K<type><modifiers><key><ESC>\
|
||||
|
||||
Where ``<type>`` 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``. ``<key>`` is a number
|
||||
(encoded in base85) corresponding to the key pressed. The key name to number
|
||||
mapping is defined in :doc:`this table <key-encoding>`.
|
||||
|
||||
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::
|
||||
|
||||
<ESC>_KpGp<ESC>\ is <Ctrl>+<Alt>+x (press)
|
||||
<ESC>_KrP8<ESC>\ is <Ctrl>+<Alt>+<Shift>+<Super>+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 <keyboard-protocol>` for reporting key
|
||||
presses to terminal applications that solves all key handling issues in
|
||||
terminal applications.
|
||||
|
||||
.. _ext_styles:
|
||||
|
||||
|
||||
@ -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__':
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user