diff --git a/docs/keyboard-protocol.rst b/docs/keyboard-protocol.rst index 5954127fb..9bd15d4d8 100644 --- a/docs/keyboard-protocol.rst +++ b/docs/keyboard-protocol.rst @@ -141,16 +141,19 @@ Modifiers ~~~~~~~~~~~~~~ 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 -mac keyboards. :kbd:`hyper` and :kbd:`meta` are typically present only on X11 -based systems with special XKB rules. Modifiers are encoded as a bit field with:: +and meta` as well as :kbd:`num_lock and caps_lock`. Here :kbd:`super` is either +the *Windows/Linux* key or the *Cmd* key on mac keyboards. :kbd:`hyper` and +: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) - alt 0b10 (2) - ctrl 0b100 (4) - super 0b1000 (8) - hyper 0b10000 (16) - meta 0b100000 (32) + shift 0b1 (1) + alt 0b10 (2) + ctrl 0b100 (4) + super 0b1000 (8) + hyper 0b10000 (16) + 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 ``1 + actual modifiers``. So to represent :kbd:`shift` only, the value would be ``1 + diff --git a/glfw/glfw3.h b/glfw/glfw3.h index 25ee586d1..05c73164c 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -510,7 +510,6 @@ typedef enum { * * If this bit is set the Num Lock key is enabled and the @ref * GLFW_LOCK_KEY_MODS input mode is set. - * @note Ravi: Num lock is not supported in this branch */ #define GLFW_MOD_NUM_LOCK 0x0080 diff --git a/kittens/key_demo/main.py b/kittens/key_demo/main.py index 90687b737..85f4ac9c8 100644 --- a/kittens/key_demo/main.py +++ b/kittens/key_demo/main.py @@ -6,8 +6,8 @@ import sys from typing import List from kitty.key_encoding import ( - ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, SUPER, HYPER, META, KeyEvent, - encode_key_event + ALT, CAPS_LOCK, CTRL, HYPER, META, NUM_LOCK, PRESS, RELEASE, REPEAT, SHIFT, + SUPER, KeyEvent, encode_key_event ) from ..tui.handler import Handler @@ -34,7 +34,10 @@ class KeysHandler(Handler): CTRL: 'Ctrl', SUPER: 'Super', HYPER: 'Hyper', - META: 'Meta'}.items(): + META: 'Meta', + NUM_LOCK: 'NumLock', + CAPS_LOCK: 'CapsLock', + }.items(): if key_event.mods & m: lmods.append(name) mods = '+'.join(lmods) diff --git a/kitty/cli.py b/kitty/cli.py index a17072f58..5229c7a92 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -761,11 +761,12 @@ ShortcutMap = Dict[Tuple[SingleKey, ...], KeyAction] def print_shortcut(key_sequence: Iterable[SingleKey], action: KeyAction) -> None: 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 ) 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 = [] for key_spec in key_sequence: names = [] diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index ab7ec8e3e..dbff90136 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -136,6 +136,8 @@ GLFW_MOD_ALT: int GLFW_MOD_SUPER: int GLFW_MOD_HYPER: int GLFW_MOD_META: int +GLFW_MOD_CAPS_LOCK: int +GLFW_MOD_NUM_LOCK: int GLFW_MOD_KITTY: int GLFW_MOUSE_BUTTON_1: int GLFW_MOUSE_BUTTON_2: int diff --git a/kitty/glfw.c b/kitty/glfw.c index adacf19fa..4fe3ebe30 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -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); #endif glfwSwapBuffers(glfw_window); + glfwSetInputMode(glfw_window, GLFW_LOCK_KEY_MODS, true); if (!global_state.is_wayland) { PyObject *pret = PyObject_CallFunction(pre_show_callback, "N", native_window_handle(glfw_window)); if (pret == NULL) return NULL; @@ -1568,6 +1569,8 @@ init_glfw(PyObject *m) { ADDC(GLFW_MOD_HYPER); ADDC(GLFW_MOD_META); ADDC(GLFW_MOD_KITTY); + ADDC(GLFW_MOD_CAPS_LOCK); + ADDC(GLFW_MOD_NUM_LOCK); // --- Mouse ------------------------------------------------------------------- ADDC(GLFW_MOUSE_BUTTON_1); diff --git a/kitty/key_encoding.c b/kitty/key_encoding.c index bd96b6e76..e7f10719e 100644 --- a/kitty/key_encoding.c +++ b/kitty/key_encoding.c @@ -8,12 +8,12 @@ #include "keys.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 struct { uint32_t key, shifted_key, alternate_key; struct { - bool shift, alt, ctrl, super, hyper, meta; + bool shift, alt, ctrl, super, hyper, meta, numlock, capslock; unsigned value; char encoded[4]; } mods; @@ -49,12 +49,15 @@ is_modifier_key(const uint32_t key) { static inline void 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.numlock = (mods & GLFW_MOD_NUM_LOCK) > 0, ev->mods.capslock = (mods & GLFW_MOD_CAPS_LOCK) > 0; ev->mods.value = ev->mods.shift ? SHIFT : 0; if (ev->mods.alt) ev->mods.value |= ALT; if (ev->mods.ctrl) ev->mods.value |= CTRL; if (ev->mods.super) ev->mods.value |= SUPER; if (ev->mods.hyper) ev->mods.value |= HYPER; 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); } diff --git a/kitty/key_encoding.py b/kitty/key_encoding.py index 487a98ac9..f6617da7a 100644 --- a/kitty/key_encoding.py +++ b/kitty/key_encoding.py @@ -212,16 +212,19 @@ class KeyEvent(NamedTuple): super: bool = False hyper: 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: + mods = self.mods & ~(NUM_LOCK | CAPS_LOCK) if not self.type & types: return False if isinstance(spec, str): spec = parse_shortcut(spec) - if (self.mods, self.key) == spec: + if (mods, self.key) == spec: return True 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 False @@ -245,6 +248,10 @@ class KeyEvent(NamedTuple): 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() @@ -257,7 +264,7 @@ class KeyEvent(NamedTuple): 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') backspace_key = KeyEvent(key='BACKSPACE') config_mod_map = { @@ -272,6 +279,8 @@ config_mod_map = { 'CONTROL': CTRL, 'HYPER': HYPER, '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), 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), @@ -362,6 +372,10 @@ def encode_key_event(key_event: KeyEvent) -> str: 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: diff --git a/kitty/keys.py b/kitty/keys.py index 6aa2ef231..3b92324a7 100644 --- a/kitty/keys.py +++ b/kitty/keys.py @@ -5,11 +5,18 @@ from typing import Optional, Union 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 .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: flags = screen.current_key_encoding_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]]: - mods = ev.mods & 0b1111 + mods = ev.mods & mod_mask ans = keymap.get(SingleKey(mods, False, ev.key)) - if ans is None and ev.shifted_key and mods & 0b1: - ans = keymap.get(SingleKey(mods & 0b1110, False, ev.shifted_key)) + if ans is None and ev.shifted_key and mods & GLFW_MOD_SHIFT: + ans = keymap.get(SingleKey(mods & (~GLFW_MOD_SHIFT), False, ev.shifted_key)) if ans is None: ans = keymap.get(SingleKey(mods, True, ev.native_key)) return ans def shortcut_matches(s: SingleKey, ev: KeyEvent) -> bool: - mods = ev.mods & 0b1111 - smods = s.mods & 0b1111 + mods = ev.mods & mod_mask + smods = s.mods & mod_mask if s.is_native: return s.key == ev.native_key and smods == mods if s.key == ev.key and mods == smods: 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 False