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.
325 lines
15 KiB
Python
325 lines
15 KiB
Python
#!/usr/bin/env python
|
|
# vim:fileencoding=utf-8
|
|
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
|
|
|
import string
|
|
from typing import Dict, List, Any
|
|
from pprint import pformat
|
|
|
|
functional_key_defs = '''# {{{
|
|
# kitty XKB macOS
|
|
escape Escape -
|
|
enter Return -
|
|
tab Tab -
|
|
backspace BackSpace -
|
|
insert Insert -
|
|
delete Delete -
|
|
left Left -
|
|
right Right -
|
|
up Up -
|
|
down Down -
|
|
page_up Page_Up -
|
|
page_down Page_Down -
|
|
home Home -
|
|
end End -
|
|
caps_lock Caps_Lock -
|
|
scroll_lock Scroll_Lock -
|
|
num_lock Num_Lock -
|
|
print_screen Print -
|
|
pause Pause -
|
|
menu Menu -
|
|
f1 F1 -
|
|
f2 F2 -
|
|
f3 F3 -
|
|
f4 F4 -
|
|
f5 F5 -
|
|
f6 F6 -
|
|
f7 F7 -
|
|
f8 F8 -
|
|
f9 F9 -
|
|
f10 F10 -
|
|
f11 F11 -
|
|
f12 F12 -
|
|
f13 F13 -
|
|
f14 F14 -
|
|
f15 F15 -
|
|
f16 F16 -
|
|
f17 F17 -
|
|
f18 F18 -
|
|
f19 F19 -
|
|
f20 F20 -
|
|
f21 F21 -
|
|
f22 F22 -
|
|
f23 F23 -
|
|
f24 F24 -
|
|
f25 F25 -
|
|
f26 F26 -
|
|
f27 F27 -
|
|
f28 F28 -
|
|
f29 F29 -
|
|
f30 F30 -
|
|
f31 F31 -
|
|
f32 F32 -
|
|
f33 F33 -
|
|
f34 F34 -
|
|
f35 F35 -
|
|
kp_0 KP_0 -
|
|
kp_1 KP_1 -
|
|
kp_2 KP_2 -
|
|
kp_3 KP_3 -
|
|
kp_4 KP_4 -
|
|
kp_5 KP_5 -
|
|
kp_6 KP_6 -
|
|
kp_7 KP_7 -
|
|
kp_8 KP_8 -
|
|
kp_9 KP_9 -
|
|
kp_decimal KP_Decimal -
|
|
kp_divide KP_Divide -
|
|
kp_multiply KP_Multiply -
|
|
kp_subtract KP_Subtract -
|
|
kp_add KP_Add -
|
|
kp_enter KP_Enter -
|
|
kp_equal KP_Equal -
|
|
kp_separator KP_Separator -
|
|
kp_left KP_Left -
|
|
kp_right KP_Right -
|
|
kp_up KP_Up -
|
|
kp_down KP_Down -
|
|
kp_page_up KP_Page_Up -
|
|
kp_page_down KP_Page_Down -
|
|
kp_home KP_Home -
|
|
kp_end KP_End -
|
|
kp_insert KP_Insert -
|
|
kp_delete KP_Delete -
|
|
left_shift Shift_L -
|
|
left_control Control_L -
|
|
left_alt Alt_L -
|
|
left_super Super_L -
|
|
right_shift Shift_R -
|
|
right_control Control_R -
|
|
right_alt Alt_R -
|
|
right_super Super_R -
|
|
media_play XF86AudioPlay -
|
|
media_pause XF86AudioPause -
|
|
media_play_pause - -
|
|
media_reverse - -
|
|
media_stop XF86AudioStop -
|
|
media_fast_forward XF86AudioForward -
|
|
media_rewind XF86AudioRewind -
|
|
media_track_next XF86AudioNext -
|
|
media_track_previous XF86AudioPrev -
|
|
media_record XF86AudioRecord -
|
|
lower_volume XF86AudioLowerVolume -
|
|
raise_volume XF86AudioRaiseVolume -
|
|
mute_volume XF86AudioMute -
|
|
''' # }}}
|
|
|
|
shift_map = {x[0]: x[1] for x in '`~ 1! 2@ 3# 4$ 5% 6^ 7& 8* 9( 0) -_ =+ [{ ]} \\| ;: \'" ,< .> /?'.split()}
|
|
shift_map.update({x: x.upper() for x in string.ascii_lowercase})
|
|
functional_encoding_overrides = {
|
|
'insert': 2, 'delete': 3, 'page_up': 5, 'page_down': 6,
|
|
'home': 7, 'end': 8, 'tab': 9, 'f1': 11, 'f2': 12, 'f3': 13, 'enter': 13, 'f4': 14,
|
|
'f5': 15, 'f6': 17, 'f7': 18, 'f8': 19, 'f9': 20, 'f10': 21,
|
|
'f11': 23, 'f12': 24, 'escape': 27, 'backspace': 127
|
|
}
|
|
different_trailer_functionals = {
|
|
'up': 'A', 'down': 'B', 'right': 'C', 'left': 'D', 'end': 'F', 'home': 'H',
|
|
'f1': 'P', 'f2': 'Q', 'f3': 'R', 'f4': 'S', 'enter': 'u', 'tab': 'u',
|
|
'backspace': 'u', 'escape': 'u'
|
|
}
|
|
functional_key_names: List[str] = []
|
|
name_to_code: Dict[str, int] = {}
|
|
name_to_xkb: Dict[str, str] = {}
|
|
start_code = 0xe000
|
|
for line in functional_key_defs.splitlines():
|
|
line = line.strip()
|
|
if not line or line.startswith('#'):
|
|
continue
|
|
parts = line.split()
|
|
name = parts[0]
|
|
functional_key_names.append(name)
|
|
name_to_code[name] = len(name_to_code) + start_code
|
|
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:
|
|
simple_start_q = f'{start_marker}start {what}{end_marker}'
|
|
start_q = f'{start_marker}start {what} (auto generated by gen-key-constants.py do not edit){end_marker}'
|
|
end_q = f'{start_marker}end {what}{end_marker}'
|
|
|
|
with open(path, 'r+') as f:
|
|
raw = f.read()
|
|
try:
|
|
start = raw.index(start_q)
|
|
except ValueError:
|
|
try:
|
|
start = raw.index(simple_start_q)
|
|
except ValueError:
|
|
raise SystemExit(f'Failed to find "{simple_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)
|
|
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 {',
|
|
f' GLFW_FKEY_FIRST = 0x{start_code:x}u,',
|
|
]
|
|
klines, pyi, names, knames = [], [], [], []
|
|
for name, code in name_to_code.items():
|
|
lines.append(f' GLFW_FKEY_{name.upper()} = 0x{code:x}u,')
|
|
klines.append(f' ADDC(GLFW_FKEY_{name.upper()});')
|
|
pyi.append(f'GLFW_FKEY_{name.upper()}: int')
|
|
names.append(f' case GLFW_FKEY_{name.upper()}: return "{name.upper()}";')
|
|
knames.append(f' case GLFW_FKEY_{name.upper()}: return PyUnicode_FromString("{name}");')
|
|
lines.append(f' GLFW_FKEY_LAST = 0x{last_code:x}u')
|
|
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(pyi), start_marker='# ', end_marker='')
|
|
patch_file('glfw/input.c', 'functional key names', '\n'.join(names))
|
|
patch_file('kitty/glfw.c', 'glfw functional key names', '\n'.join(knames))
|
|
|
|
|
|
def generate_xkb_mapping() -> None:
|
|
lines, rlines = [], []
|
|
for name, xkb in name_to_xkb.items():
|
|
lines.append(f' case XKB_KEY_{xkb}: return GLFW_FKEY_{name.upper()};')
|
|
rlines.append(f' case GLFW_FKEY_{name.upper()}: return XKB_KEY_{xkb};')
|
|
patch_file('glfw/xkb_glfw.c', 'xkb to glfw', '\n'.join(lines))
|
|
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", "CSI sequence"',
|
|
''
|
|
]
|
|
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:
|
|
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'):
|
|
trailer += f' or CSI {oc} ~'
|
|
else:
|
|
trailer = 'u'
|
|
name = f'"{name.upper()}",'.ljust(25)
|
|
lines.append(f' {name} "CSI {code} {trailer}"')
|
|
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:
|
|
tests = []
|
|
tp = ' ' * 8
|
|
shift, alt, ctrl = 1, 2, 4
|
|
|
|
lines = []
|
|
for c, s in shift_map.items():
|
|
if c in '\\\'':
|
|
c = '\\' + c
|
|
lines.append(f" case '{c}': return '{s}';")
|
|
patch_file('kitty/key_encoding.c', 'shifted key map', '\n'.join(lines))
|
|
|
|
def simple(c: str) -> None:
|
|
shifted = shift_map.get(c, c)
|
|
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})')
|
|
elif m == shift:
|
|
tests.append(f'{tp}ae(enc(ord({c!r}), mods=shift), {shifted!r})')
|
|
elif m == alt:
|
|
tests.append(f'{tp}ae(enc(ord({c!r}), mods=alt), "\\x1b" + {c!r})')
|
|
elif m == ctrl:
|
|
tests.append(f'{tp}ae(enc(ord({c!r}), mods=ctrl), {ctrled!r})')
|
|
elif m == shift | alt:
|
|
tests.append(f'{tp}ae(enc(ord({c!r}), mods=shift | alt), "\\x1b" + {shifted!r})')
|
|
elif m == ctrl | alt:
|
|
tests.append(f'{tp}ae(enc(ord({c!r}), mods=ctrl | alt), "\\x1b" + {ctrled!r})')
|
|
|
|
for k in shift_map:
|
|
simple(k)
|
|
|
|
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__':
|
|
main()
|