Add NumLock and CapsLock reporting to the keyboard protocol

This commit is contained in:
Kovid Goyal 2021-04-13 07:10:00 +05:30
parent c989a7198b
commit 4c644b8556
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
9 changed files with 62 additions and 27 deletions

View File

@ -141,16 +141,19 @@ Modifiers
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
This protocol supports six modifier keys, :kbd:`shift, alt, ctrl, super, hyper This protocol supports six modifier keys, :kbd:`shift, alt, ctrl, super, hyper
and meta`. Here :kbd:`super` is either the *Windows/Linux* key or the *Cmd* key on and meta` as well as :kbd:`num_lock and caps_lock`. Here :kbd:`super` is either
mac keyboards. :kbd:`hyper` and :kbd:`meta` are typically present only on X11 the *Windows/Linux* key or the *Cmd* key on mac keyboards. :kbd:`hyper` and
based systems with special XKB rules. Modifiers are encoded as a bit field with:: :kbd:`meta` are typically present only on X11 based systems with special XKB
rules. Modifiers are encoded as a bit field with::
shift 0b1 (1) shift 0b1 (1)
alt 0b10 (2) alt 0b10 (2)
ctrl 0b100 (4) ctrl 0b100 (4)
super 0b1000 (8) super 0b1000 (8)
hyper 0b10000 (16) hyper 0b10000 (16)
meta 0b100000 (32) meta 0b100000 (32)
caps_lock 0b1000000 (64)
num_lock 0b10000000 (128)
In the escape code, the modifier value is encoded as a decimal number which is In the escape code, the modifier value is encoded as a decimal number which is
``1 + actual modifiers``. So to represent :kbd:`shift` only, the value would be ``1 + ``1 + actual modifiers``. So to represent :kbd:`shift` only, the value would be ``1 +

1
glfw/glfw3.h vendored
View File

@ -510,7 +510,6 @@ typedef enum {
* *
* If this bit is set the Num Lock key is enabled and the @ref * If this bit is set the Num Lock key is enabled and the @ref
* GLFW_LOCK_KEY_MODS input mode is set. * GLFW_LOCK_KEY_MODS input mode is set.
* @note Ravi: Num lock is not supported in this branch
*/ */
#define GLFW_MOD_NUM_LOCK 0x0080 #define GLFW_MOD_NUM_LOCK 0x0080

View File

@ -6,8 +6,8 @@ import sys
from typing import List from typing import List
from kitty.key_encoding import ( from kitty.key_encoding import (
ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, SUPER, HYPER, META, KeyEvent, ALT, CAPS_LOCK, CTRL, HYPER, META, NUM_LOCK, PRESS, RELEASE, REPEAT, SHIFT,
encode_key_event SUPER, KeyEvent, encode_key_event
) )
from ..tui.handler import Handler from ..tui.handler import Handler
@ -34,7 +34,10 @@ class KeysHandler(Handler):
CTRL: 'Ctrl', CTRL: 'Ctrl',
SUPER: 'Super', SUPER: 'Super',
HYPER: 'Hyper', HYPER: 'Hyper',
META: 'Meta'}.items(): META: 'Meta',
NUM_LOCK: 'NumLock',
CAPS_LOCK: 'CapsLock',
}.items():
if key_event.mods & m: if key_event.mods & m:
lmods.append(name) lmods.append(name)
mods = '+'.join(lmods) mods = '+'.join(lmods)

View File

@ -761,11 +761,12 @@ ShortcutMap = Dict[Tuple[SingleKey, ...], KeyAction]
def print_shortcut(key_sequence: Iterable[SingleKey], action: KeyAction) -> None: def print_shortcut(key_sequence: Iterable[SingleKey], action: KeyAction) -> None:
from .fast_data_types import ( from .fast_data_types import (
GLFW_MOD_ALT, GLFW_MOD_CONTROL, GLFW_MOD_SHIFT, GLFW_MOD_SUPER, GLFW_MOD_HYPER, GLFW_MOD_META, GLFW_MOD_ALT, GLFW_MOD_CAPS_LOCK, GLFW_MOD_CONTROL, GLFW_MOD_HYPER,
GLFW_MOD_META, GLFW_MOD_NUM_LOCK, GLFW_MOD_SHIFT, GLFW_MOD_SUPER,
glfw_get_key_name glfw_get_key_name
) )
modmap = {'shift': GLFW_MOD_SHIFT, 'alt': GLFW_MOD_ALT, 'ctrl': GLFW_MOD_CONTROL, ('cmd' if is_macos else 'super'): GLFW_MOD_SUPER, modmap = {'shift': GLFW_MOD_SHIFT, 'alt': GLFW_MOD_ALT, 'ctrl': GLFW_MOD_CONTROL, ('cmd' if is_macos else 'super'): GLFW_MOD_SUPER,
'hyper': GLFW_MOD_HYPER, 'meta': GLFW_MOD_META} 'hyper': GLFW_MOD_HYPER, 'meta': GLFW_MOD_META, 'num_lock': GLFW_MOD_NUM_LOCK, 'caps_lock': GLFW_MOD_CAPS_LOCK}
keys = [] keys = []
for key_spec in key_sequence: for key_spec in key_sequence:
names = [] names = []

View File

@ -136,6 +136,8 @@ GLFW_MOD_ALT: int
GLFW_MOD_SUPER: int GLFW_MOD_SUPER: int
GLFW_MOD_HYPER: int GLFW_MOD_HYPER: int
GLFW_MOD_META: int GLFW_MOD_META: int
GLFW_MOD_CAPS_LOCK: int
GLFW_MOD_NUM_LOCK: int
GLFW_MOD_KITTY: int GLFW_MOD_KITTY: int
GLFW_MOUSE_BUTTON_1: int GLFW_MOUSE_BUTTON_1: int
GLFW_MOUSE_BUTTON_2: int GLFW_MOUSE_BUTTON_2: int

View File

@ -677,6 +677,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
if (is_first_window) glfwSwapInterval(OPT(sync_to_monitor) && !global_state.is_wayland ? 1 : 0); if (is_first_window) glfwSwapInterval(OPT(sync_to_monitor) && !global_state.is_wayland ? 1 : 0);
#endif #endif
glfwSwapBuffers(glfw_window); glfwSwapBuffers(glfw_window);
glfwSetInputMode(glfw_window, GLFW_LOCK_KEY_MODS, true);
if (!global_state.is_wayland) { if (!global_state.is_wayland) {
PyObject *pret = PyObject_CallFunction(pre_show_callback, "N", native_window_handle(glfw_window)); PyObject *pret = PyObject_CallFunction(pre_show_callback, "N", native_window_handle(glfw_window));
if (pret == NULL) return NULL; if (pret == NULL) return NULL;
@ -1568,6 +1569,8 @@ init_glfw(PyObject *m) {
ADDC(GLFW_MOD_HYPER); ADDC(GLFW_MOD_HYPER);
ADDC(GLFW_MOD_META); ADDC(GLFW_MOD_META);
ADDC(GLFW_MOD_KITTY); ADDC(GLFW_MOD_KITTY);
ADDC(GLFW_MOD_CAPS_LOCK);
ADDC(GLFW_MOD_NUM_LOCK);
// --- Mouse ------------------------------------------------------------------- // --- Mouse -------------------------------------------------------------------
ADDC(GLFW_MOUSE_BUTTON_1); ADDC(GLFW_MOUSE_BUTTON_1);

View File

@ -8,12 +8,12 @@
#include "keys.h" #include "keys.h"
#include "charsets.h" #include "charsets.h"
typedef enum { SHIFT=1, ALT=2, CTRL=4, SUPER=8, HYPER=16, META=32} ModifierMasks; typedef enum { SHIFT=1, ALT=2, CTRL=4, SUPER=8, HYPER=16, META=32, CAPS_LOCK=64, NUM_LOCK=128} ModifierMasks;
typedef enum { PRESS = 0, REPEAT = 1, RELEASE = 2} KeyAction; typedef enum { PRESS = 0, REPEAT = 1, RELEASE = 2} KeyAction;
typedef struct { typedef struct {
uint32_t key, shifted_key, alternate_key; uint32_t key, shifted_key, alternate_key;
struct { struct {
bool shift, alt, ctrl, super, hyper, meta; bool shift, alt, ctrl, super, hyper, meta, numlock, capslock;
unsigned value; unsigned value;
char encoded[4]; char encoded[4];
} mods; } mods;
@ -49,12 +49,15 @@ is_modifier_key(const uint32_t key) {
static inline void static inline void
convert_glfw_mods(int mods, KeyEvent *ev) { convert_glfw_mods(int mods, KeyEvent *ev) {
ev->mods.alt = (mods & GLFW_MOD_ALT) > 0, ev->mods.ctrl = (mods & GLFW_MOD_CONTROL) > 0, ev->mods.shift = (mods & GLFW_MOD_SHIFT) > 0, ev->mods.super = (mods & GLFW_MOD_SUPER) > 0, ev->mods.hyper = (mods & GLFW_MOD_HYPER) > 0, ev->mods.meta = (mods & GLFW_MOD_META) > 0; ev->mods.alt = (mods & GLFW_MOD_ALT) > 0, ev->mods.ctrl = (mods & GLFW_MOD_CONTROL) > 0, ev->mods.shift = (mods & GLFW_MOD_SHIFT) > 0, ev->mods.super = (mods & GLFW_MOD_SUPER) > 0, ev->mods.hyper = (mods & GLFW_MOD_HYPER) > 0, ev->mods.meta = (mods & GLFW_MOD_META) > 0;
ev->mods.numlock = (mods & GLFW_MOD_NUM_LOCK) > 0, ev->mods.capslock = (mods & GLFW_MOD_CAPS_LOCK) > 0;
ev->mods.value = ev->mods.shift ? SHIFT : 0; ev->mods.value = ev->mods.shift ? SHIFT : 0;
if (ev->mods.alt) ev->mods.value |= ALT; if (ev->mods.alt) ev->mods.value |= ALT;
if (ev->mods.ctrl) ev->mods.value |= CTRL; if (ev->mods.ctrl) ev->mods.value |= CTRL;
if (ev->mods.super) ev->mods.value |= SUPER; if (ev->mods.super) ev->mods.value |= SUPER;
if (ev->mods.hyper) ev->mods.value |= HYPER; if (ev->mods.hyper) ev->mods.value |= HYPER;
if (ev->mods.meta) ev->mods.value |= META; if (ev->mods.meta) ev->mods.value |= META;
if (ev->mods.capslock) ev->mods.value |= CAPS_LOCK;
if (ev->mods.numlock) ev->mods.value |= NUM_LOCK;
snprintf(ev->mods.encoded, sizeof(ev->mods.encoded), "%u", ev->mods.value + 1); snprintf(ev->mods.encoded, sizeof(ev->mods.encoded), "%u", ev->mods.value + 1);
} }

20
kitty/key_encoding.py generated
View File

@ -212,16 +212,19 @@ class KeyEvent(NamedTuple):
super: bool = False super: bool = False
hyper: bool = False hyper: bool = False
meta: bool = False meta: bool = False
caps_lock: bool = False
num_lock: bool = False
def matches(self, spec: Union[str, ParsedShortcut], types: int = EventType.PRESS | EventType.REPEAT) -> bool: def matches(self, spec: Union[str, ParsedShortcut], types: int = EventType.PRESS | EventType.REPEAT) -> bool:
mods = self.mods & ~(NUM_LOCK | CAPS_LOCK)
if not self.type & types: if not self.type & types:
return False return False
if isinstance(spec, str): if isinstance(spec, str):
spec = parse_shortcut(spec) spec = parse_shortcut(spec)
if (self.mods, self.key) == spec: if (mods, self.key) == spec:
return True return True
is_shifted = bool(self.shifted_key and self.shift) is_shifted = bool(self.shifted_key and self.shift)
if is_shifted and (self.mods & ~SHIFT, self.shifted_key) == spec: if is_shifted and (mods & ~SHIFT, self.shifted_key) == spec:
return True return True
return False return False
@ -245,6 +248,10 @@ class KeyEvent(NamedTuple):
mods |= defines.GLFW_MOD_HYPER mods |= defines.GLFW_MOD_HYPER
if self.meta: if self.meta:
mods |= defines.GLFW_MOD_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() fnm = get_name_to_functional_number_map()
@ -257,7 +264,7 @@ class KeyEvent(NamedTuple):
action=action, text=self.text) action=action, text=self.text)
SHIFT, ALT, CTRL, SUPER, HYPER, META = 1, 2, 4, 8, 16, 32 SHIFT, ALT, CTRL, SUPER, HYPER, META, CAPS_LOCK, NUM_LOCK = 1, 2, 4, 8, 16, 32, 64, 128
enter_key = KeyEvent(key='ENTER') enter_key = KeyEvent(key='ENTER')
backspace_key = KeyEvent(key='BACKSPACE') backspace_key = KeyEvent(key='BACKSPACE')
config_mod_map = { config_mod_map = {
@ -272,6 +279,8 @@ config_mod_map = {
'CONTROL': CTRL, 'CONTROL': CTRL,
'HYPER': HYPER, 'HYPER': HYPER,
'META': META, 'META': META,
'NUM_LOCK': NUM_LOCK,
'CAPS_LOCK': CAPS_LOCK,
} }
@ -306,6 +315,7 @@ def decode_key_event(csi: str, csi_type: str) -> KeyEvent:
mods=mods, shift=bool(mods & SHIFT), alt=bool(mods & ALT), mods=mods, shift=bool(mods & SHIFT), alt=bool(mods & ALT),
ctrl=bool(mods & CTRL), super=bool(mods & SUPER), ctrl=bool(mods & CTRL), super=bool(mods & SUPER),
hyper=bool(mods & HYPER), meta=bool(mods & META), hyper=bool(mods & HYPER), meta=bool(mods & META),
caps_lock=bool(mods & CAPS_LOCK), num_lock=bool(mods & NUM_LOCK),
key=key_name(keynum), key=key_name(keynum),
shifted_key=key_name(first_section[1] if len(first_section) > 1 else 0), 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), alternate_key=key_name(first_section[2] if len(first_section) > 2 else 0),
@ -362,6 +372,10 @@ def encode_key_event(key_event: KeyEvent) -> str:
m |= 16 m |= 16
if key_event.meta: if key_event.meta:
m |= 32 m |= 32
if key_event.caps_lock:
m |= 64
if key_event.num_lock:
m |= 128
if action > 1 or m: if action > 1 or m:
ans += f';{m+1}' ans += f';{m+1}'
if action > 1: if action > 1:

View File

@ -5,11 +5,18 @@
from typing import Optional, Union from typing import Optional, Union
from .config import KeyAction, KeyMap, SequenceMap, SubSequenceMap from .config import KeyAction, KeyMap, SequenceMap, SubSequenceMap
from .fast_data_types import KeyEvent from .fast_data_types import (
GLFW_MOD_ALT, GLFW_MOD_CAPS_LOCK, GLFW_MOD_CONTROL, GLFW_MOD_HYPER,
GLFW_MOD_META, GLFW_MOD_NUM_LOCK, GLFW_MOD_SHIFT, GLFW_MOD_SUPER, KeyEvent
)
from .types import SingleKey from .types import SingleKey
from .typing import ScreenType from .typing import ScreenType
mod_mask = GLFW_MOD_ALT | GLFW_MOD_CONTROL | GLFW_MOD_SHIFT | GLFW_MOD_SUPER | GLFW_MOD_META | GLFW_MOD_HYPER
lock_mask = GLFW_MOD_NUM_LOCK | GLFW_MOD_CAPS_LOCK
def keyboard_mode_name(screen: ScreenType) -> str: def keyboard_mode_name(screen: ScreenType) -> str:
flags = screen.current_key_encoding_flags() flags = screen.current_key_encoding_flags()
if flags: if flags:
@ -18,22 +25,22 @@ def keyboard_mode_name(screen: ScreenType) -> str:
def get_shortcut(keymap: Union[KeyMap, SequenceMap], ev: KeyEvent) -> Optional[Union[KeyAction, SubSequenceMap]]: def get_shortcut(keymap: Union[KeyMap, SequenceMap], ev: KeyEvent) -> Optional[Union[KeyAction, SubSequenceMap]]:
mods = ev.mods & 0b1111 mods = ev.mods & mod_mask
ans = keymap.get(SingleKey(mods, False, ev.key)) ans = keymap.get(SingleKey(mods, False, ev.key))
if ans is None and ev.shifted_key and mods & 0b1: if ans is None and ev.shifted_key and mods & GLFW_MOD_SHIFT:
ans = keymap.get(SingleKey(mods & 0b1110, False, ev.shifted_key)) ans = keymap.get(SingleKey(mods & (~GLFW_MOD_SHIFT), False, ev.shifted_key))
if ans is None: if ans is None:
ans = keymap.get(SingleKey(mods, True, ev.native_key)) ans = keymap.get(SingleKey(mods, True, ev.native_key))
return ans return ans
def shortcut_matches(s: SingleKey, ev: KeyEvent) -> bool: def shortcut_matches(s: SingleKey, ev: KeyEvent) -> bool:
mods = ev.mods & 0b1111 mods = ev.mods & mod_mask
smods = s.mods & 0b1111 smods = s.mods & mod_mask
if s.is_native: if s.is_native:
return s.key == ev.native_key and smods == mods return s.key == ev.native_key and smods == mods
if s.key == ev.key and mods == smods: if s.key == ev.key and mods == smods:
return True return True
if ev.shifted_key and mods & 0b1 and (mods & 0b1110) == smods and ev.shifted_key == s.key: if ev.shifted_key and mods & GLFW_MOD_SHIFT and (mods & ~GLFW_MOD_SHIFT) == smods and ev.shifted_key == s.key:
return True return True
return False return False