mirror of
https://github.com/kovidgoyal/kitty.git
synced 2025-12-18 12:01:19 +01:00
429 lines
12 KiB
Python
Generated
429 lines
12 KiB
Python
Generated
#!/usr/bin/env python
|
|
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
|
|
|
from enum import IntEnum
|
|
from functools import lru_cache
|
|
from typing import NamedTuple
|
|
|
|
from . import fast_data_types as defines
|
|
from .fast_data_types import KeyEvent as WindowSystemKeyEvent
|
|
from .key_names import character_key_name_aliases, functional_key_name_aliases
|
|
from .types import ParsedShortcut
|
|
|
|
# number name mappings {{{
|
|
# start csi mapping (auto generated by gen-key-constants.py do not edit)
|
|
functional_key_number_to_name_map = {
|
|
57344: 'ESCAPE',
|
|
57345: 'ENTER',
|
|
57346: 'TAB',
|
|
57347: 'BACKSPACE',
|
|
57348: 'INSERT',
|
|
57349: 'DELETE',
|
|
57350: 'LEFT',
|
|
57351: 'RIGHT',
|
|
57352: 'UP',
|
|
57353: 'DOWN',
|
|
57354: 'PAGE_UP',
|
|
57355: 'PAGE_DOWN',
|
|
57356: 'HOME',
|
|
57357: 'END',
|
|
57358: 'CAPS_LOCK',
|
|
57359: 'SCROLL_LOCK',
|
|
57360: 'NUM_LOCK',
|
|
57361: 'PRINT_SCREEN',
|
|
57362: 'PAUSE',
|
|
57363: 'MENU',
|
|
57364: 'F1',
|
|
57365: 'F2',
|
|
57366: 'F3',
|
|
57367: 'F4',
|
|
57368: 'F5',
|
|
57369: 'F6',
|
|
57370: 'F7',
|
|
57371: 'F8',
|
|
57372: 'F9',
|
|
57373: 'F10',
|
|
57374: 'F11',
|
|
57375: 'F12',
|
|
57376: 'F13',
|
|
57377: 'F14',
|
|
57378: 'F15',
|
|
57379: 'F16',
|
|
57380: 'F17',
|
|
57381: 'F18',
|
|
57382: 'F19',
|
|
57383: 'F20',
|
|
57384: 'F21',
|
|
57385: 'F22',
|
|
57386: 'F23',
|
|
57387: 'F24',
|
|
57388: 'F25',
|
|
57389: 'F26',
|
|
57390: 'F27',
|
|
57391: 'F28',
|
|
57392: 'F29',
|
|
57393: 'F30',
|
|
57394: 'F31',
|
|
57395: 'F32',
|
|
57396: 'F33',
|
|
57397: 'F34',
|
|
57398: 'F35',
|
|
57399: 'KP_0',
|
|
57400: 'KP_1',
|
|
57401: 'KP_2',
|
|
57402: 'KP_3',
|
|
57403: 'KP_4',
|
|
57404: 'KP_5',
|
|
57405: 'KP_6',
|
|
57406: 'KP_7',
|
|
57407: 'KP_8',
|
|
57408: 'KP_9',
|
|
57409: 'KP_DECIMAL',
|
|
57410: 'KP_DIVIDE',
|
|
57411: 'KP_MULTIPLY',
|
|
57412: 'KP_SUBTRACT',
|
|
57413: 'KP_ADD',
|
|
57414: 'KP_ENTER',
|
|
57415: 'KP_EQUAL',
|
|
57416: 'KP_SEPARATOR',
|
|
57417: 'KP_LEFT',
|
|
57418: 'KP_RIGHT',
|
|
57419: 'KP_UP',
|
|
57420: 'KP_DOWN',
|
|
57421: 'KP_PAGE_UP',
|
|
57422: 'KP_PAGE_DOWN',
|
|
57423: 'KP_HOME',
|
|
57424: 'KP_END',
|
|
57425: 'KP_INSERT',
|
|
57426: 'KP_DELETE',
|
|
57427: 'KP_BEGIN',
|
|
57428: 'MEDIA_PLAY',
|
|
57429: 'MEDIA_PAUSE',
|
|
57430: 'MEDIA_PLAY_PAUSE',
|
|
57431: 'MEDIA_REVERSE',
|
|
57432: 'MEDIA_STOP',
|
|
57433: 'MEDIA_FAST_FORWARD',
|
|
57434: 'MEDIA_REWIND',
|
|
57435: 'MEDIA_TRACK_NEXT',
|
|
57436: 'MEDIA_TRACK_PREVIOUS',
|
|
57437: 'MEDIA_RECORD',
|
|
57438: 'LOWER_VOLUME',
|
|
57439: 'RAISE_VOLUME',
|
|
57440: 'MUTE_VOLUME',
|
|
57441: 'LEFT_SHIFT',
|
|
57442: 'LEFT_CONTROL',
|
|
57443: 'LEFT_ALT',
|
|
57444: 'LEFT_SUPER',
|
|
57445: 'LEFT_HYPER',
|
|
57446: 'LEFT_META',
|
|
57447: 'RIGHT_SHIFT',
|
|
57448: 'RIGHT_CONTROL',
|
|
57449: 'RIGHT_ALT',
|
|
57450: 'RIGHT_SUPER',
|
|
57451: 'RIGHT_HYPER',
|
|
57452: 'RIGHT_META',
|
|
57453: 'ISO_LEVEL3_SHIFT',
|
|
57454: 'ISO_LEVEL5_SHIFT'}
|
|
csi_number_to_functional_number_map = {
|
|
2: 57348,
|
|
3: 57349,
|
|
5: 57354,
|
|
6: 57355,
|
|
7: 57356,
|
|
8: 57357,
|
|
9: 57346,
|
|
11: 57364,
|
|
12: 57365,
|
|
13: 57345,
|
|
14: 57367,
|
|
15: 57368,
|
|
17: 57369,
|
|
18: 57370,
|
|
19: 57371,
|
|
20: 57372,
|
|
21: 57373,
|
|
23: 57374,
|
|
24: 57375,
|
|
27: 57344,
|
|
127: 57347}
|
|
letter_trailer_to_csi_number_map = {'A': 57352, 'B': 57353, 'C': 57351, 'D': 57350, 'E': 57427, 'F': 8, 'H': 7, 'P': 11, 'Q': 12, 'S': 14}
|
|
tilde_trailers = {57348, 57349, 57354, 57355, 57366, 57368, 57369, 57370, 57371, 57372, 57373, 57374, 57375}
|
|
# end csi mapping
|
|
# }}}
|
|
|
|
|
|
@lru_cache(2)
|
|
def get_name_to_functional_number_map() -> dict[str, int]:
|
|
return {v: k for k, v in functional_key_number_to_name_map.items()}
|
|
|
|
|
|
@lru_cache(2)
|
|
def get_functional_to_csi_number_map() -> dict[int, int]:
|
|
return {v: k for k, v in csi_number_to_functional_number_map.items()}
|
|
|
|
|
|
@lru_cache(2)
|
|
def get_csi_number_to_letter_trailer_map() -> dict[int, str]:
|
|
return {v: k for k, v in letter_trailer_to_csi_number_map.items()}
|
|
|
|
|
|
PRESS: int = 1
|
|
REPEAT: int = 2
|
|
RELEASE: int = 4
|
|
|
|
|
|
class EventType(IntEnum):
|
|
PRESS = PRESS
|
|
REPEAT = REPEAT
|
|
RELEASE = RELEASE
|
|
|
|
|
|
@lru_cache(maxsize=128)
|
|
def parse_shortcut(spec: str) -> ParsedShortcut:
|
|
if spec.endswith('+'):
|
|
spec = f'{spec[:-1]}plus'
|
|
parts = spec.split('+')
|
|
key_name = parts[-1]
|
|
key_name = functional_key_name_aliases.get(key_name.upper(), key_name)
|
|
is_functional_key = key_name.upper() in get_name_to_functional_number_map()
|
|
if is_functional_key:
|
|
key_name = key_name.upper()
|
|
else:
|
|
key_name = character_key_name_aliases.get(key_name.upper(), key_name)
|
|
mod_val = 0
|
|
if len(parts) > 1:
|
|
mods = tuple(config_mod_map.get(x.upper(), META << 8) for x in parts[:-1])
|
|
for x in mods:
|
|
mod_val |= x
|
|
return ParsedShortcut(mod_val, key_name)
|
|
|
|
|
|
class KeyEvent(NamedTuple):
|
|
type: EventType = EventType.PRESS
|
|
mods: int = 0
|
|
key: str = ''
|
|
text: str = ''
|
|
shifted_key: str = ''
|
|
alternate_key: str = ''
|
|
shift: bool = False
|
|
alt: bool = False
|
|
ctrl: bool = False
|
|
super: bool = False
|
|
hyper: bool = False
|
|
meta: bool = False
|
|
caps_lock: bool = False
|
|
num_lock: bool = False
|
|
|
|
def matches(self, spec: str | ParsedShortcut, types: int = EventType.PRESS | EventType.REPEAT) -> bool:
|
|
mods = self.mods_without_locks
|
|
if not self.type & types:
|
|
return False
|
|
if isinstance(spec, str):
|
|
spec = parse_shortcut(spec)
|
|
if (mods, self.key) == spec:
|
|
return True
|
|
is_shifted = bool(self.shifted_key and self.shift)
|
|
if is_shifted and (mods & ~SHIFT, self.shifted_key) == spec:
|
|
return True
|
|
return False
|
|
|
|
def matches_without_mods(self, spec: str | ParsedShortcut, types: int = EventType.PRESS | EventType.REPEAT) -> bool:
|
|
if not self.type & types:
|
|
return False
|
|
if isinstance(spec, str):
|
|
spec = parse_shortcut(spec)
|
|
return self.key == spec[1]
|
|
|
|
def matches_text(self, text: str, case_sensitive: bool = False) -> bool:
|
|
if case_sensitive:
|
|
return self.text == text
|
|
return self.text.lower() == text.lower()
|
|
|
|
@property
|
|
def is_release(self) -> bool:
|
|
return self.type is EventType.RELEASE
|
|
|
|
@property
|
|
def mods_without_locks(self) -> int:
|
|
return self.mods & ~(NUM_LOCK | CAPS_LOCK)
|
|
|
|
@property
|
|
def has_mods(self) -> bool:
|
|
return bool(self.mods_without_locks)
|
|
|
|
def as_window_system_event(self) -> WindowSystemKeyEvent:
|
|
action = defines.GLFW_PRESS
|
|
if self.type is EventType.REPEAT:
|
|
action = defines.GLFW_REPEAT
|
|
elif self.type is EventType.RELEASE:
|
|
action = defines.GLFW_RELEASE
|
|
mods = 0
|
|
if self.mods:
|
|
if self.shift:
|
|
mods |= defines.GLFW_MOD_SHIFT
|
|
if self.alt:
|
|
mods |= defines.GLFW_MOD_ALT
|
|
if self.ctrl:
|
|
mods |= defines.GLFW_MOD_CONTROL
|
|
if self.super:
|
|
mods |= defines.GLFW_MOD_SUPER
|
|
if self.hyper:
|
|
mods |= defines.GLFW_MOD_HYPER
|
|
if self.meta:
|
|
mods |= defines.GLFW_MOD_META
|
|
if self.caps_lock:
|
|
mods |= defines.GLFW_MOD_CAPS_LOCK
|
|
if self.num_lock:
|
|
mods |= defines.GLFW_MOD_NUM_LOCK
|
|
|
|
fnm = get_name_to_functional_number_map()
|
|
|
|
def as_num(key: str) -> int:
|
|
return (fnm.get(key) or ord(key)) if key else 0
|
|
|
|
return WindowSystemKeyEvent(
|
|
key=as_num(self.key), shifted_key=as_num(self.shifted_key),
|
|
alternate_key=as_num(self.alternate_key), mods=mods,
|
|
action=action, text=self.text)
|
|
|
|
|
|
SHIFT, ALT, CTRL, SUPER, HYPER, META, CAPS_LOCK, NUM_LOCK = 1, 2, 4, 8, 16, 32, 64, 128
|
|
enter_key = KeyEvent(key='ENTER')
|
|
backspace_key = KeyEvent(key='BACKSPACE')
|
|
config_mod_map = {
|
|
'SHIFT': SHIFT,
|
|
'⇧': SHIFT,
|
|
'ALT': ALT,
|
|
'OPTION': ALT,
|
|
'OPT': ALT,
|
|
'⌥': ALT,
|
|
'SUPER': SUPER,
|
|
'COMMAND': SUPER,
|
|
'CMD': SUPER,
|
|
'⌘': SUPER,
|
|
'CONTROL': CTRL,
|
|
'CTRL': CTRL,
|
|
'⌃': CTRL,
|
|
'HYPER': HYPER,
|
|
'META': META,
|
|
'NUM_LOCK': NUM_LOCK,
|
|
'CAPS_LOCK': CAPS_LOCK,
|
|
}
|
|
|
|
|
|
def decode_key_event(csi: str, csi_type: str) -> KeyEvent:
|
|
parts = csi.split(';')
|
|
|
|
def get_sub_sections(x: str, missing: int = 0) -> tuple[int, ...]:
|
|
return tuple(int(y) if y else missing for y in x.split(':'))
|
|
|
|
first_section = get_sub_sections(parts[0])
|
|
second_section = get_sub_sections(parts[1], 1) if len(parts) > 1 else ()
|
|
third_section = get_sub_sections(parts[2]) if len(parts) > 2 else ()
|
|
mods = (second_section[0] - 1) if second_section else 0
|
|
action = second_section[1] if len(second_section) > 1 else 1
|
|
keynum = first_section[0]
|
|
if csi_type in 'ABCDEHFPQRS':
|
|
keynum = letter_trailer_to_csi_number_map[csi_type]
|
|
|
|
def key_name(num: int) -> str:
|
|
if not num:
|
|
return ''
|
|
if num != 13:
|
|
num = csi_number_to_functional_number_map.get(num, num)
|
|
ans = functional_key_number_to_name_map.get(num)
|
|
else:
|
|
ans = 'ENTER' if csi_type == 'u' else 'F3'
|
|
if ans is None:
|
|
ans = chr(num)
|
|
return ans
|
|
|
|
return KeyEvent(
|
|
mods=mods, shift=bool(mods & SHIFT), alt=bool(mods & ALT),
|
|
ctrl=bool(mods & CTRL), super=bool(mods & SUPER),
|
|
hyper=bool(mods & HYPER), meta=bool(mods & META),
|
|
caps_lock=bool(mods & CAPS_LOCK), num_lock=bool(mods & NUM_LOCK),
|
|
key=key_name(keynum),
|
|
shifted_key=key_name(first_section[1] if len(first_section) > 1 else 0),
|
|
alternate_key=key_name(first_section[2] if len(first_section) > 2 else 0),
|
|
type={1: EventType.PRESS, 2: EventType.REPEAT, 3: EventType.RELEASE}[action],
|
|
text=''.join(map(chr, third_section))
|
|
)
|
|
|
|
|
|
def csi_number_for_name(key_name: str) -> int:
|
|
if not key_name:
|
|
return 0
|
|
if key_name in ('F3', 'ENTER'):
|
|
return 13
|
|
fn = get_name_to_functional_number_map().get(key_name)
|
|
if fn is None:
|
|
return ord(key_name)
|
|
return get_functional_to_csi_number_map().get(fn, fn)
|
|
|
|
|
|
def encode_key_event(key_event: KeyEvent) -> str:
|
|
key = csi_number_for_name(key_event.key)
|
|
shifted_key = csi_number_for_name(key_event.shifted_key)
|
|
alternate_key = csi_number_for_name(key_event.alternate_key)
|
|
lt = get_csi_number_to_letter_trailer_map()
|
|
if key_event.key == 'ENTER':
|
|
trailer = 'u'
|
|
else:
|
|
trailer = lt.get(key, 'u')
|
|
if trailer != 'u':
|
|
key = 1
|
|
mods = key_event.mods
|
|
text = key_event.text
|
|
ans = '\033['
|
|
if key != 1 or mods or shifted_key or alternate_key or text:
|
|
ans += f'{key}'
|
|
if shifted_key or alternate_key:
|
|
ans += ':' + (f'{shifted_key}' if shifted_key else '')
|
|
if alternate_key:
|
|
ans += f':{alternate_key}'
|
|
action = 1
|
|
if key_event.type is EventType.REPEAT:
|
|
action = 2
|
|
elif key_event.type is EventType.RELEASE:
|
|
action = 3
|
|
if mods or action > 1 or text:
|
|
m = 0
|
|
if key_event.shift:
|
|
m |= 1
|
|
if key_event.alt:
|
|
m |= 2
|
|
if key_event.ctrl:
|
|
m |= 4
|
|
if key_event.super:
|
|
m |= 8
|
|
if key_event.hyper:
|
|
m |= 16
|
|
if key_event.meta:
|
|
m |= 32
|
|
if key_event.caps_lock:
|
|
m |= 64
|
|
if key_event.num_lock:
|
|
m |= 128
|
|
if action > 1 or m:
|
|
ans += f';{m+1}'
|
|
if action > 1:
|
|
ans += f':{action}'
|
|
elif text:
|
|
ans += ';'
|
|
if text:
|
|
ans += ';' + ':'.join(map(str, map(ord, text)))
|
|
fn = get_name_to_functional_number_map().get(key_event.key)
|
|
if fn is not None and fn in tilde_trailers:
|
|
trailer = '~'
|
|
return ans + trailer
|
|
|
|
|
|
def decode_key_event_as_window_system_key(text: str) -> WindowSystemKeyEvent | None:
|
|
csi, trailer = text[2:-1], text[-1]
|
|
try:
|
|
k = decode_key_event(csi, trailer)
|
|
except Exception:
|
|
return None
|
|
return k.as_window_system_event()
|