Fix handling of ctrl key in legacy mode

Also change the glfw constants used for the modifiers to match those
used in the terminal encoding. Less likely to make mistakes translating
that way.
This commit is contained in:
Kovid Goyal 2021-01-14 16:05:07 +05:30
parent 39f41faf9f
commit 0714fd376b
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
8 changed files with 271 additions and 95 deletions

View File

@ -26,6 +26,12 @@ advanced usages. The protocol is based on initial work in `fixterms
issues in that proposal, listed at the :ref:`bottom of this document
<fixterms_bugs>`.
You can see this protocol with all enhancements in action by running::
kitty +kitten key_demo
inside the kitty terminal to report key events.
.. versionadded:: 0.20.0
Quickstart
@ -46,7 +52,7 @@ text, or using these escape codes, for keys that do not produce text (``CSI``
is the bytes ``0x1b 0x5b``::
CSI number ; modifiers [u~]
CSI 1; modifiers [ABCDFHPQRSZ]
CSI 1; modifiers [ABCDFHPQRS]
0x0d - for the Enter key
0x7f or 0x08 - for Backspace
0x09 - for Tab
@ -58,9 +64,9 @@ modifiers pressed for the key event. The encoding is described in the
:ref:`modifiers` section.
The second form is used for a few functional keys, such as the :kbd:`Home, End,
Tab, Arrow keys and F1-F4`, they are enumerated in the :ref:`functional` table below.
Arrow keys and F1-F4`, they are enumerated in the :ref:`functional` table below.
Note that if no modifiers are present the parameters are omitted entirely
giving an escape code of the form ``CSI [ABCDFHPQRSZ]``.
giving an escape code of the form ``CSI [ABCDFHPQRS]``.
If you want support for more advanced features such as repeat and release
events, alternate keys for shortcut matching et cetera, these can be turned on
@ -274,7 +280,7 @@ With this flag turned on, all key events that do not generate text are
represented in one of the following two forms::
CSI number; modifier u
CSI 1; modifier [~ABCDFHPQRSZ]
CSI 1; modifier [~ABCDFHPQRS]
This makes it very easy to parse key events in an application. In particular,
:kbd:`ctrl+c` will no longer generate the ``SIGINT`` signal, but instead be
@ -420,8 +426,8 @@ For legacy compatibility, the keys
following algorithm:
#. If the :kbd:`alt` key is pressed output the byte for ``ESC (0x1b)``
#. If the :kbd:`ctrl` modifier is pressed mask the seventh bit ``(0b1000000)``
in the key's ASCII code number and output that
#. If the :kbd:`ctrl` modifier is pressed map the key using the table
in :ref:`ctrl_mapping`.
#. Otherwise, if the :kbd:`shift` modifier is pressed, output the shifted key,
for example, ``A`` for ``a`` and ``$`` for ``4``.
#. Otherwise, output the key unmodified
@ -572,6 +578,45 @@ compatibility reasons.
Note that the escape codes above of the form ``CSI 1 letter`` will omit the
``1`` if there are no modifiers, since ``1`` is the default value.
.. _ctrl_mapping:
Legacy :kbd:`ctrl` mapping of ASCII keys
------------------------------------------
When the :kbd:`ctrl` key and another key are pressed on the keyboard, terminals
map the result *for some keys* to a *C0 control code* i.e. an value from ``0 -
31``. This mapping was historically dependent on the layout of hardware
terminal keyboards and is not specified anywhere, completely. The best known
reference is `Tabe 3-5 here <https://vt100.net/docs/vt100-ug/chapter3.html>`_.
The table below provides a mapping that is a commonly used superset of the table above.
Any ASCII keys not in the table must be left untouched by :kbd:`ctrl`.
.. {{{
.. start ctrl mapping (auto generated by gen-key-constants.py do not edit)
.. csv-table:: Emitted bytes when :kbd:`ctrl` is held down and a key is pressed
:header: "Key", "Byte", "Key", "Byte", "Key", "Byte"
" ", "0", "/", "31", "0", "48"
"1", "49", "2", "0", "3", "27"
"4", "28", "5", "29", "6", "30"
"7", "31", "8", "127", "9", "57"
"?", "127", "@", "0", "[", "27"
"\", "28", "]", "29", "^", "30"
"_", "31", "a", "1", "b", "2"
"c", "3", "d", "4", "e", "5"
"f", "6", "g", "7", "h", "8"
"i", "9", "j", "10", "k", "11"
"l", "12", "m", "13", "n", "14"
"o", "15", "p", "16", "q", "17"
"r", "18", "s", "19", "t", "20"
"u", "21", "v", "22", "w", "23"
"x", "24", "y", "25", "z", "26"
"~", "30"
.. end ctrl mapping
.. }}}
.. _fixterms_bugs:
Bugs in fixterms
@ -590,7 +635,7 @@ specification.
* No support for the :kbd:`super` modifier.
* Makes no mention of cursor key mode and how it changes encodings
* Incorrectly encoding shifted keys when shift modifier is used, for
instance, for :kbd:`ctrl+shift+I`.
instance, for :kbd:`ctrl+shift+i` is encoded as :kbd:`ctrl+I`.
* No way to have non-conflicting escape codes for :kbd:`alt+letter,
ctrl+letter, ctrl+alt+letter` key presses
* No way to specify both shifted and unshifted keys for robust shortcut
@ -602,3 +647,12 @@ specification.
* No way to report key events for presses that generate text, useful for
gaming. Think of using the :kbd:`WASD` keys to control movement.
* Only a small subset of all possible functional keys are assigned numbers.
* Claims the ``CSI u`` escape code has no fixed meaning, but has been used
for decades as ``SCORC`` for instance by xterm and ansi.sys and
`DECSMBV <https://vt100.net/docs/vt510-rm/DECSMBV.html`_
by the VT-510 hardware terminal. This doesn't really matter since these uses
are for communication to the terminal not from the terminal.
* Handwaves that :kbd:`ctrl` *tends to* mask with ``0x1f``. In actual fact it
does this only for some keys. The action of :kbd:`ctrl` is not specified
and varies between terminals, historically because of different keyboard
layouts.

View File

@ -3,7 +3,8 @@
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
import string
from typing import Dict, List
from typing import Dict, List, Any
from pprint import pformat
functional_key_defs = '''# {{{
# kitty XKB macOS
@ -141,6 +142,14 @@ for line in functional_key_defs.splitlines():
if parts[1] != '-':
name_to_xkb[name] = parts[1]
last_code = start_code + len(functional_key_names) - 1
ctrl_mapping = {
' ': 0, '@': 0, 'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7,
'h': 8, 'i': 9, 'j': 10, 'k': 11, 'l': 12, 'm': 13, 'n': 14, 'o': 15, 'p': 16,
'q': 17, 'r': 18, 's': 19, 't': 20, 'u': 21, 'v': 22, 'w': 23, 'x': 24,
'y': 25, 'z': 26, '[': 27, '\\': 28, ']': 29, '^': 30, '~': 30, '/': 31,
'_': 31, '?': 127, '0': 48, '1': 49, '2': 0, '3': 27, '4': 28,
'5': 29, '6': 30, '7': 31, '8': 127, '9': 57
}
def patch_file(path: str, what: str, text: str, start_marker: str = '/* ', end_marker: str = ' */') -> None:
@ -167,6 +176,10 @@ def patch_file(path: str, what: str, text: str, start_marker: str = '/* ', end_m
f.write(raw)
def serialize_dict(x: dict) -> str:
return pformat(x, indent=4).replace('{', '{\n ', 1)
def generate_glfw_header() -> None:
lines = [
'typedef enum {',
@ -205,10 +218,13 @@ def generate_functional_table() -> None:
''
]
enc_lines = []
tilde_trailers = set()
for name, code in name_to_code.items():
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, '~')
if trailer == '~':
tilde_trailers.add(code)
code = oc = functional_encoding_overrides.get(name, code)
code = code if trailer in '~u' else 1
enc_lines.append((' ' * 8) + f"case GLFW_FKEY_{name.upper()}: S({code}, '{trailer}');")
if code == 1 and name not in ('up', 'down', 'left', 'right'):
@ -220,6 +236,16 @@ def generate_functional_table() -> None:
lines.append('')
patch_file('docs/keyboard-protocol.rst', 'functional key table', '\n'.join(lines), start_marker='.. ', end_marker='')
patch_file('kitty/key_encoding.c', 'special numbers', '\n'.join(enc_lines))
code_to_name = {v: k.upper() for k, v in name_to_code.items()}
csi_map = {v: name_to_code[k] for k, v in functional_encoding_overrides.items()}
letter_trailer_codes = {
v: functional_encoding_overrides.get(k, name_to_code.get(k))
for k, v in different_trailer_functionals.items() if v in 'ABCDHFPQRSZ'}
text = f'functional_key_number_to_name_map = {serialize_dict(code_to_name)}'
text += f'\ncsi_number_to_functional_number_map = {serialize_dict(csi_map)}'
text += f'\nletter_trailer_to_csi_number_map = {letter_trailer_codes!r}'
text += f'\ntilde_trailers = {tilde_trailers!r}'
patch_file('kitty/key_encoding.py', 'csi mapping', text, start_marker='# ', end_marker='')
def generate_legacy_text_key_maps() -> None:
@ -236,7 +262,7 @@ def generate_legacy_text_key_maps() -> None:
def simple(c: str) -> None:
shifted = shift_map.get(c, c)
ctrled = chr(ord(c) & 0b111111)
ctrled = chr(ctrl_mapping.get(c, ord(c)))
for m in range(16):
if m == 0:
tests.append(f'{tp}ae(enc(ord({c!r})), {c!r})')
@ -257,11 +283,41 @@ def generate_legacy_text_key_maps() -> None:
patch_file('kitty_tests/keys.py', 'legacy letter tests', '\n'.join(tests), start_marker='# ', end_marker='')
def chunks(lst: List, n: int) -> Any:
"""Yield successive n-sized chunks from lst."""
for i in range(0, len(lst), n):
yield lst[i:i + n]
def generate_ctrl_mapping() -> None:
lines = [
'.. csv-table:: Emitted bytes when :kbd:`ctrl` is held down and a key is pressed',
' :header: "Key", "Byte", "Key", "Byte", "Key", "Byte"',
''
]
items = []
mi = []
for k in sorted(ctrl_mapping):
items.append(k)
val = str(ctrl_mapping[k])
items.append(val)
if k in "\\'":
k = '\\' + k
mi.append(f" case '{k}': return {val};")
for line_items in chunks(items, 6):
lines.append(' ' + ', '.join(f'"{x}"' for x in line_items))
lines.append('')
patch_file('docs/keyboard-protocol.rst', 'ctrl mapping', '\n'.join(lines), start_marker='.. ', end_marker='')
patch_file('kitty/key_encoding.c', 'ctrl mapping', '\n'.join(mi))
def main() -> None:
generate_glfw_header()
generate_xkb_mapping()
generate_functional_table()
generate_legacy_text_key_maps()
generate_ctrl_mapping()
if __name__ == '__main__':

4
glfw/glfw3.h vendored
View File

@ -472,12 +472,12 @@ typedef enum {
*
* If this bit is set one or more Control keys were held down.
*/
#define GLFW_MOD_CONTROL 0x0002
#define GLFW_MOD_ALT 0x0002
/*! @brief If this bit is set one or more Alt keys were held down.
*
* If this bit is set one or more Alt keys were held down.
*/
#define GLFW_MOD_ALT 0x0004
#define GLFW_MOD_CONTROL 0x0004
/*! @brief If this bit is set one or more Super keys were held down.
*
* If this bit is set one or more Super keys were held down.

View File

@ -1083,6 +1083,12 @@ class ChildMonitor:
class KeyEvent:
def __init__(
self, key: int, shifted_key: int = 0, alternate_key: int = 0, mods: int = 0, action: int = 1, native_key: int = 1, ime_state: int = 0, text: str = ''
):
pass
@property
def key(self) -> int:
pass

4
kitty/glfw-wrapper.h generated
View File

@ -210,12 +210,12 @@ typedef enum {
*
* If this bit is set one or more Control keys were held down.
*/
#define GLFW_MOD_CONTROL 0x0002
#define GLFW_MOD_ALT 0x0002
/*! @brief If this bit is set one or more Alt keys were held down.
*
* If this bit is set one or more Alt keys were held down.
*/
#define GLFW_MOD_ALT 0x0004
#define GLFW_MOD_CONTROL 0x0004
/*! @brief If this bit is set one or more Super keys were held down.
*
* If this bit is set one or more Super keys were held down.

View File

@ -206,7 +206,7 @@ encode_function_key(const KeyEvent *ev, char *output) {
}
static inline char
shifted_ascii_key(const uint32_t key) {
shifted_ascii_key(const uint32_t key) { // {{{
switch(key) {
/* start shifted key map (auto generated by gen-key-constants.py do not edit) */
case '`': return '~';
@ -258,25 +258,80 @@ shifted_ascii_key(const uint32_t key) {
case 'z': return 'Z';
/* end shifted key map */
default:
return 0;
return key;
}
}
} // }}}
static char
ctrled_key(const uint32_t key) { // {{{
switch(key) {
/* start ctrl mapping (auto generated by gen-key-constants.py do not edit) */
case ' ': return 0;
case '/': return 31;
case '0': return 48;
case '1': return 49;
case '2': return 0;
case '3': return 27;
case '4': return 28;
case '5': return 29;
case '6': return 30;
case '7': return 31;
case '8': return 127;
case '9': return 57;
case '?': return 127;
case '@': return 0;
case '[': return 27;
case '\\': return 28;
case ']': return 29;
case '^': return 30;
case '_': return 31;
case 'a': return 1;
case 'b': return 2;
case 'c': return 3;
case 'd': return 4;
case 'e': return 5;
case 'f': return 6;
case 'g': return 7;
case 'h': return 8;
case 'i': return 9;
case 'j': return 10;
case 'k': return 11;
case 'l': return 12;
case 'm': return 13;
case 'n': return 14;
case 'o': return 15;
case 'p': return 16;
case 'q': return 17;
case 'r': return 18;
case 's': return 19;
case 't': return 20;
case 'u': return 21;
case 'v': return 22;
case 'w': return 23;
case 'x': return 24;
case 'y': return 25;
case 'z': return 26;
case '~': return 30;
/* end ctrl mapping */
default:
return key;
}
} // }}}
static int
encode_printable_ascii_key_legacy(const KeyEvent *ev, char *output) {
if (!ev->mods.value) return snprintf(output, KEY_BUFFER_SIZE, "%c", (char)ev->key);
if (ev->disambiguate) return 0;
char shifted_key = shifted_ascii_key(ev->key);
shifted_key = (shifted_key && ev->mods.shift) ? shifted_key : (char)ev->key;
char shifted_key = (ev->mods.shift) ? shifted_ascii_key(ev->key) : (char)ev->key;
if (ev->mods.value == SHIFT)
return snprintf(output, KEY_BUFFER_SIZE, "%c", shifted_key);
if ((ev->mods.value == ALT || ev->mods.value == (SHIFT | ALT)))
return snprintf(output, KEY_BUFFER_SIZE, "\x1b%c", shifted_key);
if (ev->mods.value == CTRL)
return snprintf(output, KEY_BUFFER_SIZE, "%c", ev->key & 0x3f);
return snprintf(output, KEY_BUFFER_SIZE, "%c", ctrled_key(ev->key));
if (ev->mods.value == (CTRL | ALT))
return snprintf(output, KEY_BUFFER_SIZE, "\x1b%c", ev->key & 0x3f);
return snprintf(output, KEY_BUFFER_SIZE, "\x1b%c", ctrled_key(ev->key));
return 0;
}

View File

@ -20,10 +20,14 @@ typedef struct {
PyObject *text;
} PyKeyEvent;
static inline PyObject* convert_glfw_key_event_to_python(const GLFWkeyevent *ev);
static PyObject*
new(PyTypeObject *type UNUSED, PyObject UNUSED *args, PyObject UNUSED *kwds) {
PyErr_SetString(PyExc_TypeError, "Direct creation of KeyEvent objects is not supported");
return NULL;
new(PyTypeObject *type UNUSED, PyObject *args, PyObject *kw) {
static char *kwds[] = {"key", "shifted_key", "alternate_key", "mods", "action", "native_key", "ime_state", "text", NULL};
GLFWkeyevent ev = {.action=GLFW_PRESS};
if (!PyArg_ParseTupleAndKeywords(args, kw, "I|IIiiiiz", kwds, &ev.key, &ev.shifted_key, &ev.alternate_key, &ev.mods, &ev.action, &ev.native_key, &ev.ime_state, &ev.text)) return NULL;
return convert_glfw_key_event_to_python(&ev);
}
static void
@ -229,7 +233,7 @@ pyencode_key_for_tty(PyObject *self UNUSED, PyObject *args, PyObject *kw) {
unsigned int key = 0, shifted_key = 0, alternate_key = 0, mods = 0, action = GLFW_PRESS, key_encoding_flags = 0;
const char *text = NULL;
int cursor_key_mode = 0;
if (!PyArg_ParseTupleAndKeywords(args, kw, "I|IIIIIsp", kwds, &key, &shifted_key, &alternate_key, &mods, &action, &key_encoding_flags, &text, &cursor_key_mode)) return NULL;
if (!PyArg_ParseTupleAndKeywords(args, kw, "I|IIIIIzp", kwds, &key, &shifted_key, &alternate_key, &mods, &action, &key_encoding_flags, &text, &cursor_key_mode)) return NULL;
GLFWkeyevent ev = { .key = key, .shifted_key = shifted_key, .alternate_key = alternate_key, .text = text, .action = action, .mods = mods };
char output[KEY_BUFFER_SIZE+1] = {0};
int num = encode_glfw_key_event(&ev, cursor_key_mode, key_encoding_flags, output);

View File

@ -105,8 +105,8 @@ class TestKeys(BaseTest):
ae(enc(ord('`'), mods=shift), '~')
ae(enc(ord('`'), mods=alt), "\x1b" + '`')
ae(enc(ord('`'), mods=shift | alt), "\x1b" + '~')
ae(enc(ord('`'), mods=ctrl), ' ')
ae(enc(ord('`'), mods=ctrl | alt), "\x1b" + ' ')
ae(enc(ord('`'), mods=ctrl), '`')
ae(enc(ord('`'), mods=ctrl | alt), "\x1b" + '`')
ae(enc(ord('1')), '1')
ae(enc(ord('1'), mods=shift), '!')
ae(enc(ord('1'), mods=alt), "\x1b" + '1')
@ -117,44 +117,44 @@ class TestKeys(BaseTest):
ae(enc(ord('2'), mods=shift), '@')
ae(enc(ord('2'), mods=alt), "\x1b" + '2')
ae(enc(ord('2'), mods=shift | alt), "\x1b" + '@')
ae(enc(ord('2'), mods=ctrl), '2')
ae(enc(ord('2'), mods=ctrl | alt), "\x1b" + '2')
ae(enc(ord('2'), mods=ctrl), '\x00')
ae(enc(ord('2'), mods=ctrl | alt), "\x1b" + '\x00')
ae(enc(ord('3')), '3')
ae(enc(ord('3'), mods=shift), '#')
ae(enc(ord('3'), mods=alt), "\x1b" + '3')
ae(enc(ord('3'), mods=shift | alt), "\x1b" + '#')
ae(enc(ord('3'), mods=ctrl), '3')
ae(enc(ord('3'), mods=ctrl | alt), "\x1b" + '3')
ae(enc(ord('3'), mods=ctrl), '\x1b')
ae(enc(ord('3'), mods=ctrl | alt), "\x1b" + '\x1b')
ae(enc(ord('4')), '4')
ae(enc(ord('4'), mods=shift), '$')
ae(enc(ord('4'), mods=alt), "\x1b" + '4')
ae(enc(ord('4'), mods=shift | alt), "\x1b" + '$')
ae(enc(ord('4'), mods=ctrl), '4')
ae(enc(ord('4'), mods=ctrl | alt), "\x1b" + '4')
ae(enc(ord('4'), mods=ctrl), '\x1c')
ae(enc(ord('4'), mods=ctrl | alt), "\x1b" + '\x1c')
ae(enc(ord('5')), '5')
ae(enc(ord('5'), mods=shift), '%')
ae(enc(ord('5'), mods=alt), "\x1b" + '5')
ae(enc(ord('5'), mods=shift | alt), "\x1b" + '%')
ae(enc(ord('5'), mods=ctrl), '5')
ae(enc(ord('5'), mods=ctrl | alt), "\x1b" + '5')
ae(enc(ord('5'), mods=ctrl), '\x1d')
ae(enc(ord('5'), mods=ctrl | alt), "\x1b" + '\x1d')
ae(enc(ord('6')), '6')
ae(enc(ord('6'), mods=shift), '^')
ae(enc(ord('6'), mods=alt), "\x1b" + '6')
ae(enc(ord('6'), mods=shift | alt), "\x1b" + '^')
ae(enc(ord('6'), mods=ctrl), '6')
ae(enc(ord('6'), mods=ctrl | alt), "\x1b" + '6')
ae(enc(ord('6'), mods=ctrl), '\x1e')
ae(enc(ord('6'), mods=ctrl | alt), "\x1b" + '\x1e')
ae(enc(ord('7')), '7')
ae(enc(ord('7'), mods=shift), '&')
ae(enc(ord('7'), mods=alt), "\x1b" + '7')
ae(enc(ord('7'), mods=shift | alt), "\x1b" + '&')
ae(enc(ord('7'), mods=ctrl), '7')
ae(enc(ord('7'), mods=ctrl | alt), "\x1b" + '7')
ae(enc(ord('7'), mods=ctrl), '\x1f')
ae(enc(ord('7'), mods=ctrl | alt), "\x1b" + '\x1f')
ae(enc(ord('8')), '8')
ae(enc(ord('8'), mods=shift), '*')
ae(enc(ord('8'), mods=alt), "\x1b" + '8')
ae(enc(ord('8'), mods=shift | alt), "\x1b" + '*')
ae(enc(ord('8'), mods=ctrl), '8')
ae(enc(ord('8'), mods=ctrl | alt), "\x1b" + '8')
ae(enc(ord('8'), mods=ctrl), '\x7f')
ae(enc(ord('8'), mods=ctrl | alt), "\x1b" + '\x7f')
ae(enc(ord('9')), '9')
ae(enc(ord('9'), mods=shift), '(')
ae(enc(ord('9'), mods=alt), "\x1b" + '9')
@ -225,164 +225,164 @@ class TestKeys(BaseTest):
ae(enc(ord('/'), mods=shift), '?')
ae(enc(ord('/'), mods=alt), "\x1b" + '/')
ae(enc(ord('/'), mods=shift | alt), "\x1b" + '?')
ae(enc(ord('/'), mods=ctrl), '/')
ae(enc(ord('/'), mods=ctrl | alt), "\x1b" + '/')
ae(enc(ord('/'), mods=ctrl), '\x1f')
ae(enc(ord('/'), mods=ctrl | alt), "\x1b" + '\x1f')
ae(enc(ord('a')), 'a')
ae(enc(ord('a'), mods=shift), 'A')
ae(enc(ord('a'), mods=alt), "\x1b" + 'a')
ae(enc(ord('a'), mods=shift | alt), "\x1b" + 'A')
ae(enc(ord('a'), mods=ctrl), '!')
ae(enc(ord('a'), mods=ctrl | alt), "\x1b" + '!')
ae(enc(ord('a'), mods=ctrl), '\x01')
ae(enc(ord('a'), mods=ctrl | alt), "\x1b" + '\x01')
ae(enc(ord('b')), 'b')
ae(enc(ord('b'), mods=shift), 'B')
ae(enc(ord('b'), mods=alt), "\x1b" + 'b')
ae(enc(ord('b'), mods=shift | alt), "\x1b" + 'B')
ae(enc(ord('b'), mods=ctrl), '"')
ae(enc(ord('b'), mods=ctrl | alt), "\x1b" + '"')
ae(enc(ord('b'), mods=ctrl), '\x02')
ae(enc(ord('b'), mods=ctrl | alt), "\x1b" + '\x02')
ae(enc(ord('c')), 'c')
ae(enc(ord('c'), mods=shift), 'C')
ae(enc(ord('c'), mods=alt), "\x1b" + 'c')
ae(enc(ord('c'), mods=shift | alt), "\x1b" + 'C')
ae(enc(ord('c'), mods=ctrl), '#')
ae(enc(ord('c'), mods=ctrl | alt), "\x1b" + '#')
ae(enc(ord('c'), mods=ctrl), '\x03')
ae(enc(ord('c'), mods=ctrl | alt), "\x1b" + '\x03')
ae(enc(ord('d')), 'd')
ae(enc(ord('d'), mods=shift), 'D')
ae(enc(ord('d'), mods=alt), "\x1b" + 'd')
ae(enc(ord('d'), mods=shift | alt), "\x1b" + 'D')
ae(enc(ord('d'), mods=ctrl), '$')
ae(enc(ord('d'), mods=ctrl | alt), "\x1b" + '$')
ae(enc(ord('d'), mods=ctrl), '\x04')
ae(enc(ord('d'), mods=ctrl | alt), "\x1b" + '\x04')
ae(enc(ord('e')), 'e')
ae(enc(ord('e'), mods=shift), 'E')
ae(enc(ord('e'), mods=alt), "\x1b" + 'e')
ae(enc(ord('e'), mods=shift | alt), "\x1b" + 'E')
ae(enc(ord('e'), mods=ctrl), '%')
ae(enc(ord('e'), mods=ctrl | alt), "\x1b" + '%')
ae(enc(ord('e'), mods=ctrl), '\x05')
ae(enc(ord('e'), mods=ctrl | alt), "\x1b" + '\x05')
ae(enc(ord('f')), 'f')
ae(enc(ord('f'), mods=shift), 'F')
ae(enc(ord('f'), mods=alt), "\x1b" + 'f')
ae(enc(ord('f'), mods=shift | alt), "\x1b" + 'F')
ae(enc(ord('f'), mods=ctrl), '&')
ae(enc(ord('f'), mods=ctrl | alt), "\x1b" + '&')
ae(enc(ord('f'), mods=ctrl), '\x06')
ae(enc(ord('f'), mods=ctrl | alt), "\x1b" + '\x06')
ae(enc(ord('g')), 'g')
ae(enc(ord('g'), mods=shift), 'G')
ae(enc(ord('g'), mods=alt), "\x1b" + 'g')
ae(enc(ord('g'), mods=shift | alt), "\x1b" + 'G')
ae(enc(ord('g'), mods=ctrl), "'")
ae(enc(ord('g'), mods=ctrl | alt), "\x1b" + "'")
ae(enc(ord('g'), mods=ctrl), '\x07')
ae(enc(ord('g'), mods=ctrl | alt), "\x1b" + '\x07')
ae(enc(ord('h')), 'h')
ae(enc(ord('h'), mods=shift), 'H')
ae(enc(ord('h'), mods=alt), "\x1b" + 'h')
ae(enc(ord('h'), mods=shift | alt), "\x1b" + 'H')
ae(enc(ord('h'), mods=ctrl), '(')
ae(enc(ord('h'), mods=ctrl | alt), "\x1b" + '(')
ae(enc(ord('h'), mods=ctrl), '\x08')
ae(enc(ord('h'), mods=ctrl | alt), "\x1b" + '\x08')
ae(enc(ord('i')), 'i')
ae(enc(ord('i'), mods=shift), 'I')
ae(enc(ord('i'), mods=alt), "\x1b" + 'i')
ae(enc(ord('i'), mods=shift | alt), "\x1b" + 'I')
ae(enc(ord('i'), mods=ctrl), ')')
ae(enc(ord('i'), mods=ctrl | alt), "\x1b" + ')')
ae(enc(ord('i'), mods=ctrl), '\t')
ae(enc(ord('i'), mods=ctrl | alt), "\x1b" + '\t')
ae(enc(ord('j')), 'j')
ae(enc(ord('j'), mods=shift), 'J')
ae(enc(ord('j'), mods=alt), "\x1b" + 'j')
ae(enc(ord('j'), mods=shift | alt), "\x1b" + 'J')
ae(enc(ord('j'), mods=ctrl), '*')
ae(enc(ord('j'), mods=ctrl | alt), "\x1b" + '*')
ae(enc(ord('j'), mods=ctrl), '\n')
ae(enc(ord('j'), mods=ctrl | alt), "\x1b" + '\n')
ae(enc(ord('k')), 'k')
ae(enc(ord('k'), mods=shift), 'K')
ae(enc(ord('k'), mods=alt), "\x1b" + 'k')
ae(enc(ord('k'), mods=shift | alt), "\x1b" + 'K')
ae(enc(ord('k'), mods=ctrl), '+')
ae(enc(ord('k'), mods=ctrl | alt), "\x1b" + '+')
ae(enc(ord('k'), mods=ctrl), '\x0b')
ae(enc(ord('k'), mods=ctrl | alt), "\x1b" + '\x0b')
ae(enc(ord('l')), 'l')
ae(enc(ord('l'), mods=shift), 'L')
ae(enc(ord('l'), mods=alt), "\x1b" + 'l')
ae(enc(ord('l'), mods=shift | alt), "\x1b" + 'L')
ae(enc(ord('l'), mods=ctrl), ',')
ae(enc(ord('l'), mods=ctrl | alt), "\x1b" + ',')
ae(enc(ord('l'), mods=ctrl), '\x0c')
ae(enc(ord('l'), mods=ctrl | alt), "\x1b" + '\x0c')
ae(enc(ord('m')), 'm')
ae(enc(ord('m'), mods=shift), 'M')
ae(enc(ord('m'), mods=alt), "\x1b" + 'm')
ae(enc(ord('m'), mods=shift | alt), "\x1b" + 'M')
ae(enc(ord('m'), mods=ctrl), '-')
ae(enc(ord('m'), mods=ctrl | alt), "\x1b" + '-')
ae(enc(ord('m'), mods=ctrl), '\r')
ae(enc(ord('m'), mods=ctrl | alt), "\x1b" + '\r')
ae(enc(ord('n')), 'n')
ae(enc(ord('n'), mods=shift), 'N')
ae(enc(ord('n'), mods=alt), "\x1b" + 'n')
ae(enc(ord('n'), mods=shift | alt), "\x1b" + 'N')
ae(enc(ord('n'), mods=ctrl), '.')
ae(enc(ord('n'), mods=ctrl | alt), "\x1b" + '.')
ae(enc(ord('n'), mods=ctrl), '\x0e')
ae(enc(ord('n'), mods=ctrl | alt), "\x1b" + '\x0e')
ae(enc(ord('o')), 'o')
ae(enc(ord('o'), mods=shift), 'O')
ae(enc(ord('o'), mods=alt), "\x1b" + 'o')
ae(enc(ord('o'), mods=shift | alt), "\x1b" + 'O')
ae(enc(ord('o'), mods=ctrl), '/')
ae(enc(ord('o'), mods=ctrl | alt), "\x1b" + '/')
ae(enc(ord('o'), mods=ctrl), '\x0f')
ae(enc(ord('o'), mods=ctrl | alt), "\x1b" + '\x0f')
ae(enc(ord('p')), 'p')
ae(enc(ord('p'), mods=shift), 'P')
ae(enc(ord('p'), mods=alt), "\x1b" + 'p')
ae(enc(ord('p'), mods=shift | alt), "\x1b" + 'P')
ae(enc(ord('p'), mods=ctrl), '0')
ae(enc(ord('p'), mods=ctrl | alt), "\x1b" + '0')
ae(enc(ord('p'), mods=ctrl), '\x10')
ae(enc(ord('p'), mods=ctrl | alt), "\x1b" + '\x10')
ae(enc(ord('q')), 'q')
ae(enc(ord('q'), mods=shift), 'Q')
ae(enc(ord('q'), mods=alt), "\x1b" + 'q')
ae(enc(ord('q'), mods=shift | alt), "\x1b" + 'Q')
ae(enc(ord('q'), mods=ctrl), '1')
ae(enc(ord('q'), mods=ctrl | alt), "\x1b" + '1')
ae(enc(ord('q'), mods=ctrl), '\x11')
ae(enc(ord('q'), mods=ctrl | alt), "\x1b" + '\x11')
ae(enc(ord('r')), 'r')
ae(enc(ord('r'), mods=shift), 'R')
ae(enc(ord('r'), mods=alt), "\x1b" + 'r')
ae(enc(ord('r'), mods=shift | alt), "\x1b" + 'R')
ae(enc(ord('r'), mods=ctrl), '2')
ae(enc(ord('r'), mods=ctrl | alt), "\x1b" + '2')
ae(enc(ord('r'), mods=ctrl), '\x12')
ae(enc(ord('r'), mods=ctrl | alt), "\x1b" + '\x12')
ae(enc(ord('s')), 's')
ae(enc(ord('s'), mods=shift), 'S')
ae(enc(ord('s'), mods=alt), "\x1b" + 's')
ae(enc(ord('s'), mods=shift | alt), "\x1b" + 'S')
ae(enc(ord('s'), mods=ctrl), '3')
ae(enc(ord('s'), mods=ctrl | alt), "\x1b" + '3')
ae(enc(ord('s'), mods=ctrl), '\x13')
ae(enc(ord('s'), mods=ctrl | alt), "\x1b" + '\x13')
ae(enc(ord('t')), 't')
ae(enc(ord('t'), mods=shift), 'T')
ae(enc(ord('t'), mods=alt), "\x1b" + 't')
ae(enc(ord('t'), mods=shift | alt), "\x1b" + 'T')
ae(enc(ord('t'), mods=ctrl), '4')
ae(enc(ord('t'), mods=ctrl | alt), "\x1b" + '4')
ae(enc(ord('t'), mods=ctrl), '\x14')
ae(enc(ord('t'), mods=ctrl | alt), "\x1b" + '\x14')
ae(enc(ord('u')), 'u')
ae(enc(ord('u'), mods=shift), 'U')
ae(enc(ord('u'), mods=alt), "\x1b" + 'u')
ae(enc(ord('u'), mods=shift | alt), "\x1b" + 'U')
ae(enc(ord('u'), mods=ctrl), '5')
ae(enc(ord('u'), mods=ctrl | alt), "\x1b" + '5')
ae(enc(ord('u'), mods=ctrl), '\x15')
ae(enc(ord('u'), mods=ctrl | alt), "\x1b" + '\x15')
ae(enc(ord('v')), 'v')
ae(enc(ord('v'), mods=shift), 'V')
ae(enc(ord('v'), mods=alt), "\x1b" + 'v')
ae(enc(ord('v'), mods=shift | alt), "\x1b" + 'V')
ae(enc(ord('v'), mods=ctrl), '6')
ae(enc(ord('v'), mods=ctrl | alt), "\x1b" + '6')
ae(enc(ord('v'), mods=ctrl), '\x16')
ae(enc(ord('v'), mods=ctrl | alt), "\x1b" + '\x16')
ae(enc(ord('w')), 'w')
ae(enc(ord('w'), mods=shift), 'W')
ae(enc(ord('w'), mods=alt), "\x1b" + 'w')
ae(enc(ord('w'), mods=shift | alt), "\x1b" + 'W')
ae(enc(ord('w'), mods=ctrl), '7')
ae(enc(ord('w'), mods=ctrl | alt), "\x1b" + '7')
ae(enc(ord('w'), mods=ctrl), '\x17')
ae(enc(ord('w'), mods=ctrl | alt), "\x1b" + '\x17')
ae(enc(ord('x')), 'x')
ae(enc(ord('x'), mods=shift), 'X')
ae(enc(ord('x'), mods=alt), "\x1b" + 'x')
ae(enc(ord('x'), mods=shift | alt), "\x1b" + 'X')
ae(enc(ord('x'), mods=ctrl), '8')
ae(enc(ord('x'), mods=ctrl | alt), "\x1b" + '8')
ae(enc(ord('x'), mods=ctrl), '\x18')
ae(enc(ord('x'), mods=ctrl | alt), "\x1b" + '\x18')
ae(enc(ord('y')), 'y')
ae(enc(ord('y'), mods=shift), 'Y')
ae(enc(ord('y'), mods=alt), "\x1b" + 'y')
ae(enc(ord('y'), mods=shift | alt), "\x1b" + 'Y')
ae(enc(ord('y'), mods=ctrl), '9')
ae(enc(ord('y'), mods=ctrl | alt), "\x1b" + '9')
ae(enc(ord('y'), mods=ctrl), '\x19')
ae(enc(ord('y'), mods=ctrl | alt), "\x1b" + '\x19')
ae(enc(ord('z')), 'z')
ae(enc(ord('z'), mods=shift), 'Z')
ae(enc(ord('z'), mods=alt), "\x1b" + 'z')
ae(enc(ord('z'), mods=shift | alt), "\x1b" + 'Z')
ae(enc(ord('z'), mods=ctrl), ':')
ae(enc(ord('z'), mods=ctrl | alt), "\x1b" + ':')
ae(enc(ord('z'), mods=ctrl), '\x1a')
ae(enc(ord('z'), mods=ctrl | alt), "\x1b" + '\x1a')
# end legacy letter tests
# }}}
@ -405,6 +405,7 @@ class TestKeys(BaseTest):
ae(dq(defines.GLFW_FKEY_ENTER, mods=shift), csi(shift, 13))
ae(dq(defines.GLFW_FKEY_TAB), '\t')
ae(dq(defines.GLFW_FKEY_BACKSPACE), '\x7f')
ae(dq(defines.GLFW_FKEY_TAB, mods=shift), csi(shift, 9))
for mods in (ctrl, alt, ctrl | shift, alt | shift):
ae(dq(ord('a'), mods=mods), csi(mods, ord('a')))
ae(dq(ord(' '), mods=ctrl), csi(ctrl, ord(' ')))