diff --git a/docs/keyboard-protocol.rst b/docs/keyboard-protocol.rst index a742f24db..03c6a9fbe 100644 --- a/docs/keyboard-protocol.rst +++ b/docs/keyboard-protocol.rst @@ -430,6 +430,9 @@ Bugs in fixterms * Incorrectly claims special keys are sometimes encoded using ``CSI letter`` encodings when it is actually ``ESC O letter``. * ``Enter`` and ``F3`` are both assigned the number 13. + * :kbd:`ctrl+shift+tab`` should be ``CSI 9 ; 6 u`` not ``CSI 1 ; 5 Z`` + (shift+tab is not a separate key from tab) + * 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`. diff --git a/gen-key-constants.py b/gen-key-constants.py index b2532e796..f46f90e39 100644 --- a/gen-key-constants.py +++ b/gen-key-constants.py @@ -180,7 +180,7 @@ def generate_glfw_header() -> None: lines.append('} GLFWFunctionKey;') patch_file('glfw/glfw3.h', 'functional key names', '\n'.join(lines)) patch_file('kitty/glfw.c', 'glfw functional keys', '\n'.join(klines)) - patch_file('kitty/fast_data_types.pyi', 'glfw functional keys', '\n'.join(klines), start_marker='# ', end_marker='') + patch_file('kitty/fast_data_types.pyi', 'glfw functional keys', '\n'.join(pyi), start_marker='# ', end_marker='') patch_file('glfw/input.c', 'functional key names', '\n'.join(names)) diff --git a/kitty/config.py b/kitty/config.py index ed0533a67..a53da8ba7 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -394,7 +394,7 @@ def parse_key(val: str, key_definitions: List[KeyDefinition]) -> None: mods, is_native, key = parse_shortcut(part) except InvalidMods: return - if key is defines.GLFW_KEY_UNKNOWN: + if key == 0: if mods is not None: log_error('Shortcut: {} has unknown key, ignoring'.format(sc)) return @@ -408,7 +408,7 @@ def parse_key(val: str, key_definitions: List[KeyDefinition]) -> None: mods, is_native, key = parse_shortcut(sc) except InvalidMods: return - if key is defines.GLFW_KEY_UNKNOWN: + if key == 0: if mods is not None: log_error('Shortcut: {} has unknown key, ignoring'.format(sc)) return diff --git a/kitty/config_data.py b/kitty/config_data.py index 834d59746..8e9cbbd88 100644 --- a/kitty/config_data.py +++ b/kitty/config_data.py @@ -78,7 +78,7 @@ def parse_shortcut(sc: str) -> SingleKey: else: key = get_key_name_lookup()(q, False) is_native = key is not None - return SingleKey(mods, is_native, key or defines.GLFW_KEY_UNKNOWN) + return SingleKey(mods, is_native, key or 0) T = TypeVar('T') diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 32e62bf39..5061cedbc 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -18,110 +18,110 @@ ERROR_PREFIX: str GLSL_VERSION: int GLFW_IBEAM_CURSOR: int # start glfw functional keys (auto generated by gen-key-constants.py do not edit) - ADDC(GLFW_FKEY_ESCAPE); - ADDC(GLFW_FKEY_ENTER); - ADDC(GLFW_FKEY_TAB); - ADDC(GLFW_FKEY_BACKSPACE); - ADDC(GLFW_FKEY_INSERT); - ADDC(GLFW_FKEY_DELETE); - ADDC(GLFW_FKEY_LEFT); - ADDC(GLFW_FKEY_RIGHT); - ADDC(GLFW_FKEY_UP); - ADDC(GLFW_FKEY_DOWN); - ADDC(GLFW_FKEY_PAGE_UP); - ADDC(GLFW_FKEY_PAGE_DOWN); - ADDC(GLFW_FKEY_HOME); - ADDC(GLFW_FKEY_END); - ADDC(GLFW_FKEY_CAPS_LOCK); - ADDC(GLFW_FKEY_SCROLL_LOCK); - ADDC(GLFW_FKEY_NUM_LOCK); - ADDC(GLFW_FKEY_PRINT_SCREEN); - ADDC(GLFW_FKEY_PAUSE); - ADDC(GLFW_FKEY_MENU); - ADDC(GLFW_FKEY_F1); - ADDC(GLFW_FKEY_F2); - ADDC(GLFW_FKEY_F3); - ADDC(GLFW_FKEY_F4); - ADDC(GLFW_FKEY_F5); - ADDC(GLFW_FKEY_F6); - ADDC(GLFW_FKEY_F7); - ADDC(GLFW_FKEY_F8); - ADDC(GLFW_FKEY_F9); - ADDC(GLFW_FKEY_F10); - ADDC(GLFW_FKEY_F11); - ADDC(GLFW_FKEY_F12); - ADDC(GLFW_FKEY_F13); - ADDC(GLFW_FKEY_F14); - ADDC(GLFW_FKEY_F15); - ADDC(GLFW_FKEY_F16); - ADDC(GLFW_FKEY_F17); - ADDC(GLFW_FKEY_F18); - ADDC(GLFW_FKEY_F19); - ADDC(GLFW_FKEY_F20); - ADDC(GLFW_FKEY_F21); - ADDC(GLFW_FKEY_F22); - ADDC(GLFW_FKEY_F23); - ADDC(GLFW_FKEY_F24); - ADDC(GLFW_FKEY_F25); - ADDC(GLFW_FKEY_F26); - ADDC(GLFW_FKEY_F27); - ADDC(GLFW_FKEY_F28); - ADDC(GLFW_FKEY_F29); - ADDC(GLFW_FKEY_F30); - ADDC(GLFW_FKEY_F31); - ADDC(GLFW_FKEY_F32); - ADDC(GLFW_FKEY_F33); - ADDC(GLFW_FKEY_F34); - ADDC(GLFW_FKEY_F35); - ADDC(GLFW_FKEY_KP_0); - ADDC(GLFW_FKEY_KP_1); - ADDC(GLFW_FKEY_KP_2); - ADDC(GLFW_FKEY_KP_3); - ADDC(GLFW_FKEY_KP_4); - ADDC(GLFW_FKEY_KP_5); - ADDC(GLFW_FKEY_KP_6); - ADDC(GLFW_FKEY_KP_7); - ADDC(GLFW_FKEY_KP_8); - ADDC(GLFW_FKEY_KP_9); - ADDC(GLFW_FKEY_KP_DECIMAL); - ADDC(GLFW_FKEY_KP_DIVIDE); - ADDC(GLFW_FKEY_KP_MULTIPLY); - ADDC(GLFW_FKEY_KP_SUBTRACT); - ADDC(GLFW_FKEY_KP_ADD); - ADDC(GLFW_FKEY_KP_ENTER); - ADDC(GLFW_FKEY_KP_EQUAL); - ADDC(GLFW_FKEY_KP_SEPARATOR); - ADDC(GLFW_FKEY_KP_LEFT); - ADDC(GLFW_FKEY_KP_RIGHT); - ADDC(GLFW_FKEY_KP_UP); - ADDC(GLFW_FKEY_KP_DOWN); - ADDC(GLFW_FKEY_KP_PAGE_UP); - ADDC(GLFW_FKEY_KP_PAGE_DOWN); - ADDC(GLFW_FKEY_KP_HOME); - ADDC(GLFW_FKEY_KP_END); - ADDC(GLFW_FKEY_KP_INSERT); - ADDC(GLFW_FKEY_KP_DELETE); - ADDC(GLFW_FKEY_LEFT_SHIFT); - ADDC(GLFW_FKEY_LEFT_CONTROL); - ADDC(GLFW_FKEY_LEFT_ALT); - ADDC(GLFW_FKEY_LEFT_SUPER); - ADDC(GLFW_FKEY_RIGHT_SHIFT); - ADDC(GLFW_FKEY_RIGHT_CONTROL); - ADDC(GLFW_FKEY_RIGHT_ALT); - ADDC(GLFW_FKEY_RIGHT_SUPER); - ADDC(GLFW_FKEY_MEDIA_PLAY); - ADDC(GLFW_FKEY_MEDIA_PAUSE); - ADDC(GLFW_FKEY_MEDIA_PLAY_PAUSE); - ADDC(GLFW_FKEY_MEDIA_REVERSE); - ADDC(GLFW_FKEY_MEDIA_STOP); - ADDC(GLFW_FKEY_MEDIA_FAST_FORWARD); - ADDC(GLFW_FKEY_MEDIA_REWIND); - ADDC(GLFW_FKEY_MEDIA_TRACK_NEXT); - ADDC(GLFW_FKEY_MEDIA_TRACK_PREVIOUS); - ADDC(GLFW_FKEY_MEDIA_RECORD); - ADDC(GLFW_FKEY_LOWER_VOLUME); - ADDC(GLFW_FKEY_RAISE_VOLUME); - ADDC(GLFW_FKEY_MUTE_VOLUME); +GLFW_FKEY_ESCAPE: int +GLFW_FKEY_ENTER: int +GLFW_FKEY_TAB: int +GLFW_FKEY_BACKSPACE: int +GLFW_FKEY_INSERT: int +GLFW_FKEY_DELETE: int +GLFW_FKEY_LEFT: int +GLFW_FKEY_RIGHT: int +GLFW_FKEY_UP: int +GLFW_FKEY_DOWN: int +GLFW_FKEY_PAGE_UP: int +GLFW_FKEY_PAGE_DOWN: int +GLFW_FKEY_HOME: int +GLFW_FKEY_END: int +GLFW_FKEY_CAPS_LOCK: int +GLFW_FKEY_SCROLL_LOCK: int +GLFW_FKEY_NUM_LOCK: int +GLFW_FKEY_PRINT_SCREEN: int +GLFW_FKEY_PAUSE: int +GLFW_FKEY_MENU: int +GLFW_FKEY_F1: int +GLFW_FKEY_F2: int +GLFW_FKEY_F3: int +GLFW_FKEY_F4: int +GLFW_FKEY_F5: int +GLFW_FKEY_F6: int +GLFW_FKEY_F7: int +GLFW_FKEY_F8: int +GLFW_FKEY_F9: int +GLFW_FKEY_F10: int +GLFW_FKEY_F11: int +GLFW_FKEY_F12: int +GLFW_FKEY_F13: int +GLFW_FKEY_F14: int +GLFW_FKEY_F15: int +GLFW_FKEY_F16: int +GLFW_FKEY_F17: int +GLFW_FKEY_F18: int +GLFW_FKEY_F19: int +GLFW_FKEY_F20: int +GLFW_FKEY_F21: int +GLFW_FKEY_F22: int +GLFW_FKEY_F23: int +GLFW_FKEY_F24: int +GLFW_FKEY_F25: int +GLFW_FKEY_F26: int +GLFW_FKEY_F27: int +GLFW_FKEY_F28: int +GLFW_FKEY_F29: int +GLFW_FKEY_F30: int +GLFW_FKEY_F31: int +GLFW_FKEY_F32: int +GLFW_FKEY_F33: int +GLFW_FKEY_F34: int +GLFW_FKEY_F35: int +GLFW_FKEY_KP_0: int +GLFW_FKEY_KP_1: int +GLFW_FKEY_KP_2: int +GLFW_FKEY_KP_3: int +GLFW_FKEY_KP_4: int +GLFW_FKEY_KP_5: int +GLFW_FKEY_KP_6: int +GLFW_FKEY_KP_7: int +GLFW_FKEY_KP_8: int +GLFW_FKEY_KP_9: int +GLFW_FKEY_KP_DECIMAL: int +GLFW_FKEY_KP_DIVIDE: int +GLFW_FKEY_KP_MULTIPLY: int +GLFW_FKEY_KP_SUBTRACT: int +GLFW_FKEY_KP_ADD: int +GLFW_FKEY_KP_ENTER: int +GLFW_FKEY_KP_EQUAL: int +GLFW_FKEY_KP_SEPARATOR: int +GLFW_FKEY_KP_LEFT: int +GLFW_FKEY_KP_RIGHT: int +GLFW_FKEY_KP_UP: int +GLFW_FKEY_KP_DOWN: int +GLFW_FKEY_KP_PAGE_UP: int +GLFW_FKEY_KP_PAGE_DOWN: int +GLFW_FKEY_KP_HOME: int +GLFW_FKEY_KP_END: int +GLFW_FKEY_KP_INSERT: int +GLFW_FKEY_KP_DELETE: int +GLFW_FKEY_LEFT_SHIFT: int +GLFW_FKEY_LEFT_CONTROL: int +GLFW_FKEY_LEFT_ALT: int +GLFW_FKEY_LEFT_SUPER: int +GLFW_FKEY_RIGHT_SHIFT: int +GLFW_FKEY_RIGHT_CONTROL: int +GLFW_FKEY_RIGHT_ALT: int +GLFW_FKEY_RIGHT_SUPER: int +GLFW_FKEY_MEDIA_PLAY: int +GLFW_FKEY_MEDIA_PAUSE: int +GLFW_FKEY_MEDIA_PLAY_PAUSE: int +GLFW_FKEY_MEDIA_REVERSE: int +GLFW_FKEY_MEDIA_STOP: int +GLFW_FKEY_MEDIA_FAST_FORWARD: int +GLFW_FKEY_MEDIA_REWIND: int +GLFW_FKEY_MEDIA_TRACK_NEXT: int +GLFW_FKEY_MEDIA_TRACK_PREVIOUS: int +GLFW_FKEY_MEDIA_RECORD: int +GLFW_FKEY_LOWER_VOLUME: int +GLFW_FKEY_RAISE_VOLUME: int +GLFW_FKEY_MUTE_VOLUME: int # end glfw functional keys GLFW_MOD_SHIFT: int GLFW_MOD_CONTROL: int @@ -257,6 +257,19 @@ MOVE: int # }}} +def encode_key_for_tty( + key: int = 0, + shifted_key: int = 0, + alternate_key: int = 0, + mods: int = 0, + action: int = 1, + key_encoding_flags: int = 0, + text: str = "", + cursor_key_mode: bool = False +) -> str: + pass + + def log_error_string(s: str) -> None: pass @@ -908,7 +921,6 @@ class Screen: scrolled_by: int cursor: Cursor disable_ligatures: int - extended_keyboard: bool cursor_key_mode: bool auto_repeat_enabled: bool @@ -922,6 +934,9 @@ class Screen: ): pass + def current_key_encoding_flags(self) -> int: + pass + def line(self, num: int) -> Line: pass @@ -1086,9 +1101,5 @@ def spawn( pass -def key_to_bytes(glfw_key: int, smkx: bool, extended: bool, mods: int, action: int) -> bytes: - pass - - def set_window_padding(os_window_id: int, tab_id: int, window_id: int, left: int, top: int, right: int, bottom: int) -> None: pass diff --git a/kitty/key_encoding.c b/kitty/key_encoding.c index ab29a06e1..094fb1e3d 100644 --- a/kitty/key_encoding.c +++ b/kitty/key_encoding.c @@ -116,11 +116,6 @@ encode_function_key(const KeyEvent *ev, char *output) { default: break; } } - if (key_number == GLFW_FKEY_TAB) { - if (ev->mods.value == SHIFT) return encode_csi_string('Z', "", output); - if (ev->mods.value == (CTRL | SHIFT)) return encode_csi_string('Z', "1;5", output); - if (ev->mods.value == ALT) SIMPLE("\x1b\t"); - } if (ev->mods.value == ALT) { switch(key_number) { case GLFW_FKEY_TAB: SIMPLE("\x1b\t"); @@ -128,6 +123,7 @@ encode_function_key(const KeyEvent *ev, char *output) { case GLFW_FKEY_BACKSPACE: SIMPLE("\x1b\x7f"); } } + if (ev->mods.value == SHIFT && key_number == GLFW_FKEY_TAB) { SIMPLE("\x1b[Z"); } #undef SIMPLE #define S(number, trailer) key_number = number; csi_trailer = trailer; break @@ -185,7 +181,7 @@ encode_printable_ascii_key_legacy(const KeyEvent *ev, char *output) { shifted_key = (shifted_key && ev->mods.shift) ? shifted_key : (char)ev->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) + if (ev->mods.value == CTRL && ev->key && 0x1f != ev->key) return snprintf(output, KEY_BUFFER_SIZE, "%c", ev->key & 0x1f); if (ev->mods.value == (CTRL | ALT)) return snprintf(output, KEY_BUFFER_SIZE, "\x1b%c", ev->key & 0x1f); diff --git a/kitty/key_names.py b/kitty/key_names.py index b5e94251a..b7a4f6523 100644 --- a/kitty/key_names.py +++ b/kitty/key_names.py @@ -34,57 +34,6 @@ key_name_aliases = { '^': 'CIRCUMFLEX', '_': 'UNDERSCORE', '`': 'GRAVE_ACCENT', - '§': 'PARAGRAPH', - 'º': 'MASCULINE', - 'À': 'A_GRAVE', - 'Ä': 'A_DIAERESIS', - 'Å': 'A_RING', - 'Æ': 'AE', - 'Ç': 'C_CEDILLA', - 'È': 'E_GRAVE', - 'É': 'E_ACUTE', - 'Ì': 'I_GRAVE', - 'Ñ': 'N_TILDE', - 'Ò': 'O_GRAVE', - 'Ö': 'O_DIAERESIS', - 'Ø': 'O_SLASH', - 'Ù': 'U_GRAVE', - 'Ü': 'U_DIAERESIS', - 'SS': 'S_SHARP', # 'ß'.upper() == 'SS' - 'А': 'CYRILLIC_A', - 'Б': 'CYRILLIC_BE', - 'В': 'CYRILLIC_VE', - 'Г': 'CYRILLIC_GHE', - 'Д': 'CYRILLIC_DE', - 'Е': 'CYRILLIC_IE', - 'Ж': 'CYRILLIC_ZHE', - 'З': 'CYRILLIC_ZE', - 'И': 'CYRILLIC_I', - 'Й': 'CYRILLIC_SHORT_I', - 'К': 'CYRILLIC_KA', - 'Л': 'CYRILLIC_EL', - 'М': 'CYRILLIC_EM', - 'Н': 'CYRILLIC_EN', - 'О': 'CYRILLIC_O', - 'П': 'CYRILLIC_PE', - 'Р': 'CYRILLIC_ER', - 'С': 'CYRILLIC_ES', - 'Т': 'CYRILLIC_TE', - 'У': 'CYRILLIC_U', - 'Ф': 'CYRILLIC_EF', - 'Х': 'CYRILLIC_HA', - 'Ц': 'CYRILLIC_TSE', - 'Ч': 'CYRILLIC_CHE', - 'Ш': 'CYRILLIC_SHA', - 'Щ': 'CYRILLIC_SHCHA', - 'Ъ': 'CYRILLIC_HARD_SIGN', - 'Ы': 'CYRILLIC_YERU', - 'Ь': 'CYRILLIC_SOFT_SIGN', - 'Э': 'CYRILLIC_E', - 'Ю': 'CYRILLIC_YU', - 'Я': 'CYRILLIC_YA', - 'Ё': 'CYRILLIC_IO', - 'ESC': 'ESCAPE', 'PGUP': 'PAGE_UP', 'PAGEUP': 'PAGE_UP', diff --git a/kitty/keys.py b/kitty/keys.py index e8b66bf69..e5e3fc6e4 100644 --- a/kitty/keys.py +++ b/kitty/keys.py @@ -2,281 +2,19 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal -import string -from typing import Any, Callable, Dict, Iterable, Optional, Tuple, Union +from typing import Optional, Union -from . import fast_data_types as defines from .constants import SingleKey from .config import KeyAction, KeyMap, SequenceMap, SubSequenceMap -from .key_encoding import KEY_MAP -from .terminfo import key_as_bytes, modify_key_bytes -from .typing import ScreenType, WindowType -from .utils import base64_encode - - -def modify_complex_key(name: Union[str, bytes], amt: int) -> bytes: - q = name if isinstance(name, bytes) else key_as_bytes(name) - return modify_key_bytes(q, amt) - - -control_codes: Dict[int, Union[bytes, Tuple[int, ...]]] = { - defines.GLFW_KEY_BACKSPACE: b'\x08' -} -smkx_key_map = {} -alt_codes = { - defines.GLFW_KEY_TAB: b'\033\t', - defines.GLFW_KEY_ENTER: b'\033\r', - defines.GLFW_KEY_ESCAPE: b'\033\033', - defines.GLFW_KEY_BACKSPACE: b'\033\177' -} -shift_alt_codes = alt_codes.copy() -shift_alt_codes[defines.GLFW_KEY_TAB] = key_as_bytes('kcbt') -alt_mods = (defines.GLFW_MOD_ALT, defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_ALT) -ctrl_shift_mod = defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_CONTROL -ctrl_alt_mod = defines.GLFW_MOD_ALT | defines.GLFW_MOD_CONTROL -ctrl_alt_shift_mod = ctrl_alt_mod | defines.GLFW_MOD_SHIFT -SHIFTED_KEYS = { - defines.GLFW_KEY_TAB: key_as_bytes('kcbt'), - defines.GLFW_KEY_HOME: key_as_bytes('kHOM'), - defines.GLFW_KEY_END: key_as_bytes('kEND'), - defines.GLFW_KEY_LEFT: key_as_bytes('kLFT'), - defines.GLFW_KEY_RIGHT: key_as_bytes('kRIT'), - defines.GLFW_KEY_UP: key_as_bytes('kri'), - defines.GLFW_KEY_DOWN: key_as_bytes('kind'), - defines.GLFW_KEY_PAGE_UP: modify_complex_key('kpp', 2), - defines.GLFW_KEY_PAGE_DOWN: modify_complex_key('knp', 2), -} - -control_alt_codes = { - defines.GLFW_KEY_SPACE: b'\x1b\0', -} -control_alt_shift_codes: Dict[int, bytes] = {} -ASCII_C0_SHIFTED = { - # ^@ - '2': b'\x00', - # ^^ - '6': b'\x1e', - # ^_ - 'MINUS': b'\x1f', - # ^? - 'SLASH': b'\x7f', -} -control_shift_keys = {getattr(defines, 'GLFW_KEY_' + k): v for k, v in ASCII_C0_SHIFTED.items()} - - -def create_modifier_variants(keycode: int, terminfo_name_or_bytes: Union[str, bytes], add_shifted_key: bool = True) -> None: - kn = terminfo_name_or_bytes - smkx_key_map[keycode] = kn if isinstance(kn, bytes) else key_as_bytes(kn) - if add_shifted_key: - SHIFTED_KEYS[keycode] = modify_complex_key(kn, 2) - alt_codes[keycode] = modify_complex_key(kn, 3) - shift_alt_codes[keycode] = modify_complex_key(kn, 4) - control_codes[keycode] = modify_complex_key(kn, 5) - control_shift_keys[keycode] = modify_complex_key(kn, 6) - control_alt_codes[keycode] = modify_complex_key(kn, 7) - control_alt_shift_codes[keycode] = modify_complex_key(kn, 8) - - -for kf, kn in { - defines.GLFW_KEY_UP: 'kcuu1', - defines.GLFW_KEY_DOWN: 'kcud1', - defines.GLFW_KEY_LEFT: 'kcub1', - defines.GLFW_KEY_RIGHT: 'kcuf1', - defines.GLFW_KEY_HOME: 'khome', - defines.GLFW_KEY_END: 'kend', - defines.GLFW_KEY_INSERT: 'kich1', - defines.GLFW_KEY_DELETE: 'kdch1', - defines.GLFW_KEY_PAGE_UP: 'kpp', - defines.GLFW_KEY_PAGE_DOWN: 'knp', -}.items(): - create_modifier_variants(kf, kn, add_shifted_key=False) -for f in range(1, 13): - kf = getattr(defines, 'GLFW_KEY_F{}'.format(f)) - kn = 'kf{}'.format(f) - create_modifier_variants(kf, kn) -for f in range(13, 26): - kf = getattr(defines, 'GLFW_KEY_F{}'.format(f)) - kn = 'kf{}'.format(f) - smkx_key_map[kf] = key_as_bytes(kn) -create_modifier_variants(defines.GLFW_KEY_MENU, b'\x1b[29~') -f_ = {k: k for k in '0123456789'} -f_.update({ - 'COMMA': ',', - 'PERIOD': '.', - 'SEMICOLON': ';', - 'APOSTROPHE': "'", - 'MINUS': '-', - 'EQUAL': '=', -}) -for kf_, kn_ in f_.items(): - control_codes[getattr(defines, 'GLFW_KEY_' + kf_)] = (ord(kn_),) -del f, f_, kf, kn, kf_, kn_ - -smkx_key_map[defines.GLFW_KEY_ESCAPE] = b'\033' -smkx_key_map[defines.GLFW_KEY_ENTER] = b'\r' -smkx_key_map[defines.GLFW_KEY_KP_ENTER] = b'\r' -smkx_key_map[defines.GLFW_KEY_BACKSPACE] = key_as_bytes('kbs') -smkx_key_map[defines.GLFW_KEY_TAB] = b'\t' - -control_codes.update({ - k: (1 + i, ) - for i, k in - enumerate(range(defines.GLFW_KEY_A, defines.GLFW_KEY_RIGHT_BRACKET + 1)) -}) -control_codes[defines.GLFW_KEY_GRAVE_ACCENT] = control_codes[defines.GLFW_KEY_UNDERSCORE] = \ - control_codes[defines.GLFW_KEY_SPACE] = control_codes[defines.GLFW_KEY_2] = (0,) -control_codes[defines.GLFW_KEY_3] = (27,) -control_codes[defines.GLFW_KEY_4] = (28,) -control_codes[defines.GLFW_KEY_5] = (29,) -control_codes[defines.GLFW_KEY_6] = control_codes[defines.GLFW_KEY_CIRCUMFLEX] = (30,) -control_codes[defines.GLFW_KEY_7] = control_codes[defines.GLFW_KEY_SLASH] = (31,) -control_codes[defines.GLFW_KEY_8] = (127,) - -rmkx_key_map = smkx_key_map.copy() -rmkx_key_map.update({ - defines.GLFW_KEY_UP: b'\033[A', - defines.GLFW_KEY_DOWN: b'\033[B', - defines.GLFW_KEY_LEFT: b'\033[D', - defines.GLFW_KEY_RIGHT: b'\033[C', - defines.GLFW_KEY_HOME: b'\033[H', - defines.GLFW_KEY_END: b'\033[F', -}) - -cursor_key_mode_map = {True: smkx_key_map, False: rmkx_key_map} +from .typing import ScreenType def keyboard_mode_name(screen: ScreenType) -> str: - if screen.extended_keyboard: + if screen.current_key_encoding_flags() & 0b1000: return 'kitty' return 'application' if screen.cursor_key_mode else 'normal' -action_map = { - defines.GLFW_PRESS: 'p', - defines.GLFW_RELEASE: 'r', - defines.GLFW_REPEAT: 't' -} - - -def extended_key_event(key: int, mods: int, action: int) -> bytes: - if key >= defines.GLFW_KEY_LAST or key == defines.GLFW_KEY_UNKNOWN or ( - # Shifted printable key should be handled by on_text_input() - mods <= defines.GLFW_MOD_SHIFT and defines.GLFW_KEY_SPACE <= key <= defines.GLFW_KEY_LAST_PRINTABLE - ): - return b'' - if mods == 0 and key in ( - defines.GLFW_KEY_BACKSPACE, defines.GLFW_KEY_ENTER - ): - if action == defines.GLFW_RELEASE: - return b'' - return smkx_key_map[key] - if key in (defines.GLFW_KEY_LEFT_SHIFT, defines.GLFW_KEY_RIGHT_SHIFT): - return b'' - name = KEY_MAP.get(key) - if name is None: - return b'' - m = 0 - if mods & defines.GLFW_MOD_SHIFT: - m |= 0x1 - if mods & defines.GLFW_MOD_ALT: - m |= 0x2 - if mods & defines.GLFW_MOD_CONTROL: - m |= 0x4 - if mods & defines.GLFW_MOD_SUPER: - m |= 0x8 - return 'K{}{}{}'.format( - action_map[action], base64_encode(m), name - ).encode('ascii') - - -def pmap(names: str, r: Iterable[str]) -> Dict[int, bytes]: - snames = names.split() - b = [x.encode('ascii') for x in r] - if len(snames) != len(b): - raise ValueError('Incorrect mapping for {}'.format(names)) - anames = [getattr(defines, 'GLFW_KEY_' + n) for n in snames] - return dict(zip(anames, b)) - - -UN_SHIFTED_PRINTABLE = { - getattr(defines, 'GLFW_KEY_' + x): x.lower().encode('ascii') - for x in string.digits + string.ascii_uppercase -} -UN_SHIFTED_PRINTABLE.update(pmap( - 'SPACE APOSTROPHE COMMA MINUS PERIOD SLASH SEMICOLON EQUAL', - " ',-./;=" -)) -UN_SHIFTED_PRINTABLE.update(pmap( - 'LEFT_BRACKET BACKSLASH RIGHT_BRACKET GRAVE_ACCENT UNDERSCORE', - "[\\]`_" -)) -SHIFTED_PRINTABLE = UN_SHIFTED_PRINTABLE.copy() -SHIFTED_PRINTABLE.update({ - getattr(defines, 'GLFW_KEY_' + x): x.encode('ascii') for x in string.ascii_uppercase -}) -SHIFTED_PRINTABLE.update(pmap( - '1 2 3 4 5 6 7 8 9 0', - '!@#$%^&*()' -)) -SHIFTED_PRINTABLE.update(pmap( - 'APOSTROPHE COMMA MINUS PERIOD SLASH SEMICOLON EQUAL', - '"<_>?:+' -)) -SHIFTED_PRINTABLE.update(pmap( - 'LEFT_BRACKET BACKSLASH RIGHT_BRACKET GRAVE_ACCENT', - "{|}~" -)) - -CTRL_ALT_KEYS = {getattr(defines, 'GLFW_KEY_' + k) for k in string.ascii_uppercase} -all_control_alt_keys = set(CTRL_ALT_KEYS) | set(control_alt_codes) - - -def key_to_bytes(key: int, smkx: bool, extended: bool, mods: int, action: int) -> bytes: - if extended: - return extended_key_event(key, mods, action) - data = bytearray() - if mods == defines.GLFW_MOD_CONTROL and key in control_codes: - # Map Ctrl-key to ascii control code - data.extend(control_codes[key]) - elif mods == ctrl_shift_mod and key in control_shift_keys: - data.extend(control_shift_keys[key]) - elif mods in alt_mods: - if key in alt_codes: - data.extend((alt_codes if mods == defines.GLFW_MOD_ALT else shift_alt_codes)[key]) - elif key in UN_SHIFTED_PRINTABLE: - m = UN_SHIFTED_PRINTABLE if mods == defines.GLFW_MOD_ALT else SHIFTED_PRINTABLE - data.append(0o33) - data.extend(m[key]) - elif mods == ctrl_alt_mod and key in all_control_alt_keys: - if key in CTRL_ALT_KEYS: - data.append(0x1b) - data.extend(control_codes[key]) - else: - data.extend(control_alt_codes[key]) - elif mods == ctrl_alt_shift_mod and key in control_alt_shift_codes: - data.extend(control_alt_shift_codes[key]) - else: - key_map = cursor_key_mode_map[smkx] - x = key_map.get(key) - if x is not None: - if mods == defines.GLFW_MOD_SHIFT: - x = SHIFTED_KEYS.get(key, x) - assert x is not None - data.extend(x) - return bytes(data) - - -def interpret_key_event(key: int, native_key: int, mods: int, window: WindowType, action: int) -> bytes: - screen = window.screen - if ( - action == defines.GLFW_PRESS or - (action == defines.GLFW_REPEAT and screen.auto_repeat_enabled) or - screen.extended_keyboard - ): - return defines.key_to_bytes(key, screen.cursor_key_mode, screen.extended_keyboard, mods, action) - return b'' - - def get_shortcut(keymap: Union[KeyMap, SequenceMap], mods: int, key: int, native_key: int) -> Optional[Union[KeyAction, SubSequenceMap]]: mods &= 0b1111 ans = keymap.get(SingleKey(mods, False, key)) @@ -289,109 +27,3 @@ def shortcut_matches(s: SingleKey, mods: int, key: int, native_key: int) -> bool mods &= 0b1111 q = native_key if s[1] else key return bool(s[0] & 0b1111 == mods & 0b1111 and s[2] == q) - - -def generate_key_table_impl(w: Callable) -> None: - w('// auto-generated from keys.py, do not edit!') - w('#pragma once') - w('#include ') - w('#include ') - w('#include ') - w('#include ') - number_of_keys = defines.GLFW_KEY_LAST + 1 - w('// map glfw key numbers to 7-bit numbers for compact data storage') - w('static const uint8_t key_map[%d] = {' % number_of_keys) - key_count = 0 - - def key_name(k: str) -> str: - return k[len('GLFW_KEY_'):] - - keys = {v: k for k, v in vars(defines).items() if k.startswith('GLFW_KEY_') and k not in {'GLFW_KEY_LAST', 'GLFW_KEY_LAST_PRINTABLE', 'GLFW_KEY_UNKNOWN'}} - key_rmap = [] - for i in range(number_of_keys): - k = keys.get(i) - if k is None: - w('UINT8_MAX,') - else: - w('%d, /* %s */' % (key_count, key_name(k))) - key_rmap.append(i) - key_count += 1 - if key_count > 256: - raise OverflowError('Too many keys') - w('};\n') - w('static inline const char* key_name(int key) { switch(key) {') - for i in range(number_of_keys): - k = keys.get(i) - if k is not None: - w('case %d: return "%s";' % (i, key_name(k))) - w('default: return NULL; }}\n') - w('typedef enum { NORMAL, APPLICATION, EXTENDED } KeyboardMode;\n') - w('static inline const char*\nkey_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {') - i = 1 - - def ind(*a: Any) -> None: - w((' ' * i)[:-1], *a) - ind('switch(mode) {') - mmap = [(False, False), (True, False), (False, True)] - for (smkx, extended), mode in zip(mmap, 'NORMAL APPLICATION EXTENDED'.split()): - i += 1 - ind('case {}:'.format(mode)) - i += 1 - ind('switch(action & 3) { case 3: return NULL;') - for action in (defines.GLFW_RELEASE, defines.GLFW_PRESS, defines.GLFW_REPEAT): - i += 1 - ind('case {}: // {}'.format(action, 'RELEASE PRESS REPEAT'.split()[action])) - i += 1 - if action != defines.GLFW_RELEASE or mode == 'EXTENDED': - ind('switch (mods & 0xf) {') - i += 1 - for mods in range(16): - key_bytes = {} - for key in range(key_count): - glfw_key = key_rmap[key] - data = key_to_bytes(glfw_key, smkx, extended, mods, action) - if data: - key_bytes[key] = data, glfw_key - i += 1 - ind('case 0x{:x}:'.format(mods)) - i += 1 - if key_bytes: - ind('switch(key & 0xff) { default: return NULL;') - i += 1 - for key, (data, glfw_key) in key_bytes.items(): - ind('case {}: // {}'.format(key, key_name(keys[glfw_key]))) - i += 1 - items = bytearray(data) - items.insert(0, len(items)) - ind('return "{}";'.format(''.join('\\x{:02x}'.format(x) for x in items))) - i -= 1 - i -= 1 - ind('} // end switch(key)') - else: - ind('return NULL;') - i -= 2 - i -= 1 - ind('} // end switch(mods)') - ind('break;\n') - i -= 1 - else: - ind('return NULL;\n') - i -= 1 - i -= 1 - ind('}} // end switch(action) in mode {}'.format(mode)) - ind('break;\n\n') - i -= 1 - i -= 1 - ind('}') - ind('return NULL;') - i -= 1 - w('}') - - -def generate_key_table() -> None: - # To run this, use: ./kitty/launcher/kitty +runpy "from kitty.keys import *; generate_key_table()" - import os - from functools import partial - with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'keys.h'), 'w') as f: - w = partial(print, file=f) - generate_key_table_impl(w) diff --git a/kitty/screen.c b/kitty/screen.c index 2ba485cea..bf13b3e2a 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -2324,6 +2324,7 @@ WRAP0(carriage_return) WRAP2(resize, 1, 1) WRAP2(set_margins, 1, 1) WRAP0(rescale_images) +WRAP0(current_key_encoding_flags) static PyObject* start_selection(Screen *self, PyObject *args) { @@ -2886,6 +2887,7 @@ static PyMethodDef methods[] = { MND(resize, METH_VARARGS) MND(set_margins, METH_VARARGS) MND(rescale_images, METH_NOARGS) + MND(current_key_encoding_flags, METH_NOARGS) MND(text_for_selection, METH_NOARGS) MND(is_rectangle_select, METH_NOARGS) MND(scroll, METH_VARARGS) diff --git a/kitty/window.py b/kitty/window.py index 020b4440c..d1c41b22f 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -22,16 +22,16 @@ from .config import build_ansi_color_table from .constants import ScreenGeometry, WindowGeometry, appname, wakeup from .fast_data_types import ( BGIMAGE_PROGRAM, BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM, - CELL_PROGRAM, CELL_SPECIAL_PROGRAM, DCS, DECORATION, DIM, + CELL_PROGRAM, CELL_SPECIAL_PROGRAM, DCS, DECORATION, DIM, GLFW_MOD_CONTROL, GRAPHICS_ALPHA_MASK_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_PROGRAM, MARK, MARK_MASK, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, STRIKETHROUGH, TINT_PROGRAM, Screen, add_timer, add_window, - cell_size_for_window, compile_program, get_boss, get_clipboard_string, - init_cell_program, pt_to_px, set_clipboard_string, set_titlebar_color, - set_window_padding, set_window_render_data, update_window_title, - update_window_visibility, viewport_for_window + cell_size_for_window, compile_program, encode_key_for_tty, get_boss, + get_clipboard_string, init_cell_program, pt_to_px, set_clipboard_string, + set_titlebar_color, set_window_padding, set_window_render_data, + update_window_title, update_window_visibility, viewport_for_window ) -from .keys import defines, extended_key_event, keyboard_mode_name +from .keys import keyboard_mode_name from .notify import NotificationCommand, handle_notification_cmd from .options_stub import Options from .rgb import to_color @@ -886,9 +886,11 @@ class Window: if text: set_clipboard_string(text) else: - mode = keyboard_mode_name(self.screen) - data = extended_key_event(defines.GLFW_KEY_C, defines.GLFW_MOD_CONTROL, defines.GLFW_PRESS) if mode == 'kitty' else b'\x03' - self.write_to_child(data) + data = encode_key_for_tty( + key=ord('c'), mods=GLFW_MOD_CONTROL, key_encoding_flags=self.screen.current_key_encoding_flags(), + cursor_key_mode=self.screen.cursor_key_mode + ) + self.write_to_child(data.encode('ascii')) def copy_and_clear_or_interrupt(self) -> None: self.copy_or_interrupt() diff --git a/kitty_tests/keys.py b/kitty_tests/keys.py index bc654817e..506a1cd83 100644 --- a/kitty_tests/keys.py +++ b/kitty_tests/keys.py @@ -2,6 +2,7 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal +from functools import partial import kitty.fast_data_types as defines from . import BaseTest @@ -9,7 +10,46 @@ from . import BaseTest class TestKeys(BaseTest): def test_encode_key_event(self): - pass + enc = defines.encode_key_for_tty + ae = self.assertEqual + shift, alt, ctrl, super = defines.GLFW_MOD_SHIFT, defines.GLFW_MOD_ALT, defines.GLFW_MOD_CONTROL, defines.GLFW_MOD_SUPER # noqa + press, repeat, release = defines.GLFW_PRESS, defines.GLFW_REPEAT, defines.GLFW_RELEASE # noqa + + def csi(mods=0, num=1, trailer='u'): + ans = f'\033[{num}' + if mods: + m = 0 + if mods & shift: + m |= 1 + if mods & alt: + m |= 2 + if mods & ctrl: + m |= 4 + if mods & super: + m |= 8 + ans += f';{m+1}' + return ans + trailer + + def mods_test(key, plain, shift=None, ctrl=None, alt=None, calt=None, cshift=None, ashift=None, csi_num=None, trailer='u'): + c = partial(csi, num=csi_num or key, trailer=trailer) + e = partial(enc, key=key) + ae(e(), plain) + ae(e(mods=defines.GLFW_MOD_SHIFT), shift or c(defines.GLFW_MOD_SHIFT)) + ae(e(mods=defines.GLFW_MOD_CONTROL), ctrl or c(defines.GLFW_MOD_CONTROL)) + ae(e(mods=defines.GLFW_MOD_ALT | defines.GLFW_MOD_CONTROL), calt or c(defines.GLFW_MOD_ALT | defines.GLFW_MOD_CONTROL)) + ae(e(mods=defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_CONTROL), cshift or c(defines.GLFW_MOD_CONTROL | defines.GLFW_MOD_SHIFT)) + ae(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_ESCAPE, '\x1b', alt='\033\033', csi_num=27) + mods_test(defines.GLFW_FKEY_BACKSPACE, '\x7f', alt='\033\x7f', csi_num=127) + mods_test(defines.GLFW_FKEY_TAB, '\t', alt='\033\t', shift='\x1b[Z', csi_num=ord('\t')) + + q = partial(enc, key=ord('a')) + ae(q(), 'a') + ae(q(text='a'), 'a') + ae(q(action=repeat), 'a') + ae(q(action=release), '') def test_encode_mouse_event(self): NORMAL_PROTOCOL, UTF8_PROTOCOL, SGR_PROTOCOL, URXVT_PROTOCOL = range(4)