Implement the extended keyboard protocol

This commit is contained in:
Kovid Goyal 2017-02-10 15:11:07 +05:30
parent 45334d6b35
commit 0f8b83755a
5 changed files with 207 additions and 173 deletions

View File

@ -3,126 +3,126 @@
See link:protocol-extensions.asciidoc#keyboard-handling[Keyboard Handling protocol extension]
|===
| Name | Number
| Name | Encoded representation
| 0 | 48
| 1 | 49
| 2 | 50
| 3 | 51
| 4 | 52
| 5 | 53
| 6 | 54
| 7 | 55
| 8 | 56
| 9 | 57
| A | 65
| APOSTROPHE | 39
| B | 66
| BACKSLASH | 92
| BACKSPACE | 259
| C | 67
| CAPS LOCK | 280
| COMMA | 44
| D | 68
| DELETE | 261
| DOWN | 264
| E | 69
| END | 269
| ENTER | 257
| EQUAL | 61
| ESCAPE | 256
| F | 70
| F1 | 290
| F10 | 299
| F11 | 300
| F12 | 301
| F13 | 302
| F14 | 303
| F15 | 304
| F16 | 305
| F17 | 306
| F18 | 307
| F19 | 308
| F2 | 291
| F20 | 309
| F21 | 310
| F22 | 311
| F23 | 312
| F24 | 313
| F25 | 314
| F3 | 292
| F4 | 293
| F5 | 294
| F6 | 295
| F7 | 296
| F8 | 297
| F9 | 298
| G | 71
| GRAVE ACCENT | 96
| H | 72
| HOME | 268
| I | 73
| INSERT | 260
| J | 74
| K | 75
| KP 0 | 320
| KP 1 | 321
| KP 2 | 322
| KP 3 | 323
| KP 4 | 324
| KP 5 | 325
| KP 6 | 326
| KP 7 | 327
| KP 8 | 328
| KP 9 | 329
| KP ADD | 334
| KP DECIMAL | 330
| KP DIVIDE | 331
| KP ENTER | 335
| KP EQUAL | 336
| KP MULTIPLY | 332
| KP SUBTRACT | 333
| L | 76
| LEFT | 263
| LEFT ALT | 342
| LEFT BRACKET | 91
| LEFT CONTROL | 341
| LEFT SHIFT | 340
| LEFT SUPER | 343
| M | 77
| MINUS | 45
| N | 78
| NUM LOCK | 282
| O | 79
| P | 80
| PAGE DOWN | 267
| PAGE UP | 266
| PAUSE | 284
| PERIOD | 46
| PRINT SCREEN | 283
| Q | 81
| R | 82
| RIGHT | 262
| RIGHT ALT | 346
| RIGHT BRACKET | 93
| RIGHT CONTROL | 345
| RIGHT SHIFT | 344
| RIGHT SUPER | 347
| S | 83
| SCROLL LOCK | 281
| SEMICOLON | 59
| SLASH | 47
| SPACE | 32
| T | 84
| TAB | 258
| U | 85
| UP | 265
| V | 86
| W | 87
| WORLD 1 | 161
| WORLD 2 | 162
| X | 88
| Y | 89
| Z | 90
| 0 | `BM`
| 1 | `BN`
| 2 | `BO`
| 3 | `BP`
| 4 | `BQ`
| 5 | `BR`
| 6 | `BS`
| 7 | `BT`
| 8 | `BU`
| 9 | `BV`
| A | `Bd`
| APOSTROPHE | `BD`
| B | `Be`
| BACKSLASH | `CU`
| BACKSPACE | `HH`
| C | `Bf`
| CAPS LOCK | `Hc`
| COMMA | `BI`
| D | `Bg`
| DELETE | `HJ`
| DOWN | `HM`
| E | `Bh`
| END | `HR`
| ENTER | `HF`
| EQUAL | `BZ`
| ESCAPE | `HE`
| F | `Bi`
| F1 | `IC`
| F10 | `IL`
| F11 | `IM`
| F12 | `IN`
| F13 | `IO`
| F14 | `IP`
| F15 | `IQ`
| F16 | `IR`
| F17 | `IS`
| F18 | `IT`
| F19 | `IU`
| F2 | `ID`
| F20 | `IV`
| F21 | `IW`
| F22 | `IX`
| F23 | `IY`
| F24 | `IZ`
| F25 | `Ia`
| F3 | `IE`
| F4 | `IF`
| F5 | `IG`
| F6 | `IH`
| F7 | `II`
| F8 | `IJ`
| F9 | `IK`
| G | `Bj`
| GRAVE ACCENT | `CY`
| H | `CA`
| HOME | `HQ`
| I | `CB`
| INSERT | `HI`
| J | `CC`
| K | `CD`
| KP 0 | `Ig`
| KP 1 | `Ih`
| KP 2 | `Ii`
| KP 3 | `Ij`
| KP 4 | `JA`
| KP 5 | `JB`
| KP 6 | `JC`
| KP 7 | `JD`
| KP 8 | `JE`
| KP 9 | `JF`
| KP ADD | `JK`
| KP DECIMAL | `JG`
| KP DIVIDE | `JH`
| KP ENTER | `JL`
| KP EQUAL | `JM`
| KP MULTIPLY | `JI`
| KP SUBTRACT | `JJ`
| L | `CE`
| LEFT | `HL`
| LEFT ALT | `JS`
| LEFT BRACKET | `CT`
| LEFT CONTROL | `JR`
| LEFT SHIFT | `JQ`
| LEFT SUPER | `JT`
| M | `CF`
| MINUS | `BJ`
| N | `CG`
| NUM LOCK | `He`
| O | `CH`
| P | `CI`
| PAGE DOWN | `HP`
| PAGE UP | `HO`
| PAUSE | `Hg`
| PERIOD | `BK`
| PRINT SCREEN | `Hf`
| Q | `CJ`
| R | `CK`
| RIGHT | `HK`
| RIGHT ALT | `JW`
| RIGHT BRACKET | `CV`
| RIGHT CONTROL | `JV`
| RIGHT SHIFT | `JU`
| RIGHT SUPER | `JX`
| S | `CL`
| SCROLL LOCK | `Hd`
| SEMICOLON | `BX`
| SLASH | `BL`
| SPACE | `g`
| T | `CM`
| TAB | `HG`
| U | `CN`
| UP | `HN`
| V | `CO`
| W | `CP`
| WORLD 1 | `ER`
| WORLD 2 | `ES`
| X | `CQ`
| Y | `CR`
| Z | `CS`
|===

View File

@ -259,6 +259,7 @@ class Boss(Thread):
is_key_pressed[key] = action == GLFW_PRESS
self.start_cursor_blink()
self.cursor_blink_zero_time = monotonic()
func = None
if action == GLFW_PRESS or action == GLFW_REPEAT:
func = get_shortcut(self.opts.keymap, mods, key, scancode)
if func is not None:
@ -267,24 +268,24 @@ class Boss(Thread):
passthrough = f()
if not passthrough:
return
tab = self.active_tab
if tab is None:
return
window = self.active_window
if window is not None:
yield window
if func is not None:
f = getattr(tab, func, getattr(window, func, None))
if f is not None:
passthrough = f()
if not passthrough:
return
if window.screen.auto_repeat_enabled or action == GLFW_PRESS:
if window.char_grid.scrolled_by and key not in MODIFIER_KEYS:
window.scroll_end()
data = interpret_key_event(key, scancode, mods, window)
if data:
window.write_to_child(data)
tab = self.active_tab
if tab is None:
return
window = self.active_window
if window is None:
return
yield window
if func is not None:
f = getattr(tab, func, getattr(window, func, None))
if f is not None:
passthrough = f()
if not passthrough:
return
if window.char_grid.scrolled_by and key not in MODIFIER_KEYS:
window.scroll_end()
data = interpret_key_event(key, scancode, mods, window, action)
if data:
window.write_to_child(data)
@callback
def on_focus(self, window, focused):

View File

@ -2,7 +2,9 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import kitty.fast_data_types as defines
import string
from . import fast_data_types as defines
from .terminfo import key_as_bytes
smkx_key_map = {
@ -80,22 +82,60 @@ def get_localized_key(key, scancode):
return valid_localized_key_names.get((name or '').upper(), key)
def interpret_key_event(key, scancode, mods, window):
action_map = {defines.GLFW_PRESS: b'p', defines.GLFW_RELEASE: b'r', defines.GLFW_REPEAT: b't'}
def base64_encode(integer, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits + '+/'):
ans = ''
while integer > 0:
integer, remainder = divmod(integer, 36)
ans = chars[remainder] + ans
return ans
def key_extended_map():
ans = {}
for k in dir(defines):
if k.startswith('GLFW_KEY_'):
val = getattr(defines, k)
if val < defines.GLFW_KEY_LAST and val != defines.GLFW_KEY_UNKNOWN:
ans[k[9:]] = base64_encode(val)
return ans
key_extended_map = key_extended_map()
def extended_key_event(key, scancode, mods, action):
if key >= defines.GLFW_KEY_LAST or key == defines.GLFW_KEY_UNKNOWN or (
# Shifted printable key should be handled by interpret_text_event()
mods == defines.GLFW_MOD_SHIFT and 32 <= key <= 126):
return b''
if mods == 0 and key in (defines.GLFW_KEY_BACKSPACE, defines.GLFW_KEY_ENTER):
return smkx_key_map[key]
return '\033_K{}{:x}:{}\033\\'.format(action_map[action], mods, key_extended_map[key]).encode('ascii')
def interpret_key_event(key, scancode, mods, window, action):
screen = window.screen
if screen.extended_keyboard:
return extended_key_event(key, scancode, mods, action)
data = bytearray()
key = get_localized_key(key, scancode)
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 == defines.GLFW_MOD_ALT and key in alt_codes:
# Map Alt+key to Esc-key
data.extend(alt_codes[key])
else:
key_map = get_key_map(window.screen)
x = key_map.get(key)
if x is not None:
if mods == defines.GLFW_MOD_SHIFT:
x = SHIFTED_KEYS.get(key, x)
data.extend(x)
if action == defines.GLFW_PRESS or (action == defines.GLFW_REPEAT and screen.auto_repeat_enabled):
key = get_localized_key(key, scancode)
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 == defines.GLFW_MOD_ALT and key in alt_codes:
# Map Alt+key to Esc-key
data.extend(alt_codes[key])
else:
key_map = get_key_map(screen)
x = key_map.get(key)
if x is not None:
if mods == defines.GLFW_MOD_SHIFT:
x = SHIFTED_KEYS.get(key, x)
data.extend(x)
return bytes(data)
@ -109,13 +149,3 @@ def interpret_text_event(codepoint, mods):
def get_shortcut(keymap, mods, key, scancode):
key = get_localized_key(key, scancode)
return keymap.get((mods & 0b1111, key))
def key_integer_map():
ans = {}
for k in dir(defines):
if k.startswith('GLFW_KEY_'):
val = getattr(defines, k)
if val < defines.GLFW_KEY_LAST and val != defines.GLFW_KEY_UNKNOWN:
ans[k[9:]] = val
return ans

View File

@ -37,15 +37,15 @@ if raw != nraw:
raw = subprocess.check_output([
'kitty', '-c',
'from kitty.keys import *; import json; print(json.dumps(key_integer_map()))'
'from kitty.keys import *; import json; print(json.dumps(key_extended_map))'
]).decode('utf-8')
key_map = json.loads(raw)
lines = [
'See link:protocol-extensions.asciidoc#keyboard-handling[Keyboard Handling protocol extension]',
'', '|===', '| Name | Number', ''
'', '|===', '| Name | Encoded representation', ''
]
for k in sorted(key_map):
lines.append('| {:15s} | {}'.format(k.replace('_', ' '), key_map[k]))
lines.append('| {:15s} | `{}`'.format(k.replace('_', ' '), key_map[k]))
lines += ['', '|===']
with open('key_encoding.asciidoc', 'w') as f:
print('= Key encoding for extended keyboard protocol\n', file=f)

View File

@ -365,15 +365,18 @@ The escape sequence encodes the following properties:
<ESC>_K<type><modifiers>:<key><ESC>\
```
Where `<type>` is one of `p` -- press, `r` -- release and `t` -- repeat.
Modifiers is zero or more of `c` -- Control, `a` -- Alt, `s` -- Shift and `m` -- Meta.
`<key>` is a number corresponding to the key pressed. The key name to number mapping
is defined in link:key_encoding.asciidoc[this table].
Where `<type>` is one of `p` -- press, `r` -- release and `t` -- repeat.
Modifiers is a bitmask represented as a single hexadecimal digit in lower case.
Shift -- `0x1`, Control -- `0x2`, Alt -- `0x4` and Super -- `0x8`. `<key>` is
a number (in lowercase hexadecimal) corresponding to the key pressed. The key name to
number mapping is defined in link:key_encoding.asciidoc[this table].
For example:
```
<ESC>_Kpca:88<ESC>\
<ESC>_Kp6:58<ESC>\ is <Ctrl>+<Alt>+x (press)
<ESC>_Krf:10a<ESC>\ is <Ctrl>+<Alt>+<Shift>+<Super>+PageUp (release)
```
Is the key press event `<Ctrl>+<Alt>+x`.
There is a `:` between the modifiers and the key-press so that in the future
more modifiers can be added, if needed.