From 774fdd7e94dfc4c10f18b8f04d3e5872a6ec8618 Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Fri, 30 Dec 2022 02:06:21 -0800 Subject: [PATCH 1/5] Send sequence events to child if they don't match kitty. --- kitty/boss.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/kitty/boss.py b/kitty/boss.py index aa0c99c20..a714aecdf 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -254,6 +254,8 @@ class Boss: self.current_visual_select: Optional[VisualSelect] = None self.startup_cursor_text_color = opts.cursor_text_color self.pending_sequences: Optional[SubSequenceMap] = None + # A list of events received so far that are potentially part of a sequence keybinding. + self.current_sequence: List[KeyEvent] = [] self.default_pending_action: str = '' self.cached_values = cached_values self.os_window_map: Dict[int, TabManager] = {} @@ -1168,6 +1170,7 @@ class Boss: sequences = get_shortcut(get_options().sequence_map, ev) if sequences and not isinstance(sequences, str): self.set_pending_sequences(sequences) + self.current_sequence = [ev] return True if self.global_shortcuts_map and get_shortcut(self.global_shortcuts_map, ev): return True @@ -1177,6 +1180,7 @@ class Boss: def clear_pending_sequences(self) -> None: self.pending_sequences = None + self.current_sequence = [] self.default_pending_action = '' set_in_sequence_mode(False) @@ -1185,6 +1189,8 @@ class Boss: set_in_sequence_mode(False) return + if len(self.current_sequence): + self.current_sequence.append(ev) remaining = {} matched_action = None for seq, key_action in self.pending_sequences.items(): @@ -1199,9 +1205,15 @@ class Boss: self.pending_sequences = remaining else: matched_action = matched_action or self.default_pending_action - self.clear_pending_sequences() - if matched_action is not None: + if matched_action is not None and matched_action != '': + self.clear_pending_sequences() self.combine(matched_action) + else: + w = self.active_window + if w is not None: + for ev in self.current_sequence: + w.write_to_child(w.encoded_key(ev)) + self.clear_pending_sequences() def cancel_current_visual_select(self) -> None: if self.current_visual_select: From 510c5bd73b59abefdb2a3f8c3216f48abe8a14b3 Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Fri, 30 Dec 2022 11:30:39 -0800 Subject: [PATCH 2/5] handle release --- kitty/boss.py | 14 +++++++++++--- kitty/keys.c | 18 ++++++++++++------ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/kitty/boss.py b/kitty/boss.py index a714aecdf..d955632ee 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -33,7 +33,7 @@ from .constants import ( ) from .fast_data_types import ( CLOSE_BEING_CONFIRMED, GLFW_MOD_ALT, GLFW_MOD_CONTROL, GLFW_MOD_SHIFT, - GLFW_MOD_SUPER, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, IMPERATIVE_CLOSE_REQUESTED, + GLFW_MOD_SUPER, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, GLFW_RELEASE, IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED, ChildMonitor, Color, EllipticCurveKey, KeyEvent, SingleKey, add_timer, apply_options_update, background_opacity_of, change_background_opacity, change_os_window_state, cocoa_hide_app, cocoa_hide_other_apps, @@ -1184,13 +1184,18 @@ class Boss: self.default_pending_action = '' set_in_sequence_mode(False) - def process_sequence(self, ev: KeyEvent) -> None: + def process_sequence(self, ev: KeyEvent) -> bool: + # Process an event as part of a sequence. Returns whether the key + # is consumed as part of a kitty sequence keybinding. if not self.pending_sequences: set_in_sequence_mode(False) - return + return False if len(self.current_sequence): self.current_sequence.append(ev) + if ev.action == GLFW_RELEASE: + return True + # For a press/repeat event, try matching with kitty bindings: remaining = {} matched_action = None for seq, key_action in self.pending_sequences.items(): @@ -1203,17 +1208,20 @@ class Boss: if remaining: self.pending_sequences = remaining + return True else: matched_action = matched_action or self.default_pending_action if matched_action is not None and matched_action != '': self.clear_pending_sequences() self.combine(matched_action) + return True else: w = self.active_window if w is not None: for ev in self.current_sequence: w.write_to_child(w.encoded_key(ev)) self.clear_pending_sequences() + return False def cancel_current_visual_select(self) -> None: if self.current_visual_select: diff --git a/kitty/keys.c b/kitty/keys.c index f7dd7e206..07b6504f3 100644 --- a/kitty/keys.c +++ b/kitty/keys.c @@ -167,14 +167,20 @@ on_key_input(GLFWkeyevent *ev) { PyObject *ke = NULL; #define create_key_event() { ke = convert_glfw_key_event_to_python(ev); if (!ke) { PyErr_Print(); return; } } if (global_state.in_sequence_mode) { - debug("in sequence mode, handling as shortcut\n"); - if ( - action != GLFW_RELEASE && !is_modifier_key(key) - ) { - w->last_special_key_pressed = key; + debug("in sequence mode, handling as a potential shortcut\n"); + if (!is_modifier_key(key)) { create_key_event(); - call_boss(process_sequence, "O", ke); + PyObject *ret = PyObject_CallMethod( + global_state.boss, "process_sequence", "O", ke); Py_CLEAR(ke); + if (ret == NULL) { PyErr_Print(); } + else { + bool consumed = ret == Py_True; + Py_DECREF(ret); + if (consumed && action != GLFW_RELEASE && w) { + w->last_special_key_pressed = key; + } + } } return; } From 1c10c5fcc41d6cb5f9415e6e9b6028e020fbe3bf Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Fri, 30 Dec 2022 22:41:18 -0800 Subject: [PATCH 3/5] replay modifier key as well --- kitty/boss.py | 7 ++++--- kitty/key_encoding.py | 11 +++++++++++ kitty/keys.c | 24 +++++++++++------------- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/kitty/boss.py b/kitty/boss.py index d955632ee..17503d6c5 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -47,7 +47,7 @@ from .fast_data_types import ( set_options, set_os_window_size, set_os_window_title, thread_write, toggle_fullscreen, toggle_maximized, toggle_secure_input, ) -from .key_encoding import get_name_to_functional_number_map +from .key_encoding import get_name_to_functional_number_map, is_modifier_key from .keys import get_shortcut, shortcut_matches from .layout.base import set_layout_options from .notify import notification_activated @@ -1193,9 +1193,10 @@ class Boss: if len(self.current_sequence): self.current_sequence.append(ev) - if ev.action == GLFW_RELEASE: + if ev.action == GLFW_RELEASE or is_modifier_key(ev.key): return True - # For a press/repeat event, try matching with kitty bindings: + # For a press/repeat event that's not a modifier, try matching with + # kitty bindings: remaining = {} matched_action = None for seq, key_action in self.pending_sequences.items(): diff --git a/kitty/key_encoding.py b/kitty/key_encoding.py index 110359b61..99b65819c 100644 --- a/kitty/key_encoding.py +++ b/kitty/key_encoding.py @@ -426,3 +426,14 @@ def decode_key_event_as_window_system_key(text: str) -> Optional[WindowSystemKey except Exception: return None return k.as_window_system_event() + + +# The same as `bool is_modifier_key(key)` in key_encoding.c +def is_modifier_key(key: int) -> bool: + if defines.GLFW_FKEY_LEFT_SHIFT <= key <= defines.GLFW_FKEY_ISO_LEVEL5_SHIFT: + return True + if key == defines.GLFW_FKEY_CAPS_LOCK or \ + key == defines.GLFW_FKEY_SCROLL_LOCK or \ + key == defines.GLFW_FKEY_NUM_LOCK: + return True + return False diff --git a/kitty/keys.c b/kitty/keys.c index 07b6504f3..e251065aa 100644 --- a/kitty/keys.c +++ b/kitty/keys.c @@ -168,19 +168,17 @@ on_key_input(GLFWkeyevent *ev) { #define create_key_event() { ke = convert_glfw_key_event_to_python(ev); if (!ke) { PyErr_Print(); return; } } if (global_state.in_sequence_mode) { debug("in sequence mode, handling as a potential shortcut\n"); - if (!is_modifier_key(key)) { - create_key_event(); - PyObject *ret = PyObject_CallMethod( - global_state.boss, "process_sequence", "O", ke); - Py_CLEAR(ke); - if (ret == NULL) { PyErr_Print(); } - else { - bool consumed = ret == Py_True; - Py_DECREF(ret); - if (consumed && action != GLFW_RELEASE && w) { - w->last_special_key_pressed = key; - } - } + create_key_event(); + PyObject *ret = PyObject_CallMethod( + global_state.boss, "process_sequence", "O", ke); + Py_CLEAR(ke); + if (ret == NULL) { PyErr_Print(); } + else { + bool consumed = ret == Py_True; + Py_DECREF(ret); + if (consumed && action != GLFW_RELEASE && w) { + w->last_special_key_pressed = key; + } } return; } From 13b09346b9016f9e12345a890e93f90211b22b54 Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Fri, 30 Dec 2022 22:54:41 -0800 Subject: [PATCH 4/5] reset current window after processing sequence --- kitty/keys.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kitty/keys.c b/kitty/keys.c index e251065aa..5ff37d805 100644 --- a/kitty/keys.c +++ b/kitty/keys.c @@ -172,6 +172,9 @@ on_key_input(GLFWkeyevent *ev) { PyObject *ret = PyObject_CallMethod( global_state.boss, "process_sequence", "O", ke); Py_CLEAR(ke); + // the shortcut could have created a new window or closed the + // window, rendering the pointer no longer valid + w = window_for_window_id(active_window_id); if (ret == NULL) { PyErr_Print(); } else { bool consumed = ret == Py_True; From 832506d7858079dfabfbbd20ac1bb7bb1cc1dd37 Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Fri, 30 Dec 2022 23:32:46 -0800 Subject: [PATCH 5/5] move is_modifier_key to glfw.c and expose in Python --- kitty/boss.py | 6 +++--- kitty/fast_data_types.pyi | 4 ++++ kitty/glfw.c | 28 ++++++++++++++++++++++++++++ kitty/key_encoding.c | 15 --------------- kitty/key_encoding.py | 11 ----------- kitty/keys.h | 3 ++- 6 files changed, 37 insertions(+), 30 deletions(-) diff --git a/kitty/boss.py b/kitty/boss.py index 17503d6c5..14809d7ea 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -40,14 +40,14 @@ from .fast_data_types import ( cocoa_minimize_os_window, cocoa_set_menubar_title, create_os_window, current_application_quit_request, current_focused_os_window_id, current_os_window, destroy_global_data, focus_os_window, get_boss, get_options, get_os_window_size, - global_font_size, last_focused_os_window_id, mark_os_window_for_close, + glfw_is_modifier_key, global_font_size, last_focused_os_window_id, mark_os_window_for_close, os_window_font_size, patch_global_colors, redirect_mouse_handling, ring_bell, run_with_activation_token, safe_pipe, send_data_to_peer, set_application_quit_request, set_background_image, set_boss, set_in_sequence_mode, set_options, set_os_window_size, set_os_window_title, thread_write, toggle_fullscreen, toggle_maximized, toggle_secure_input, ) -from .key_encoding import get_name_to_functional_number_map, is_modifier_key +from .key_encoding import get_name_to_functional_number_map from .keys import get_shortcut, shortcut_matches from .layout.base import set_layout_options from .notify import notification_activated @@ -1193,7 +1193,7 @@ class Boss: if len(self.current_sequence): self.current_sequence.append(ev) - if ev.action == GLFW_RELEASE or is_modifier_key(ev.key): + if ev.action == GLFW_RELEASE or glfw_is_modifier_key(ev.key): return True # For a press/repeat event that's not a modifier, try matching with # kitty bindings: diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index b9df5db54..07c150faa 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -313,6 +313,10 @@ def glfw_get_key_name(key: int, native_key: int) -> Optional[str]: pass +def glfw_is_modifier_key(key: int) -> bool: + pass + + StartupCtx = NewType('StartupCtx', int) Display = NewType('Display', int) diff --git a/kitty/glfw.c b/kitty/glfw.c index 320340672..96a6a90c1 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -1256,6 +1256,33 @@ glfw_get_key_name(PyObject UNUSED *self, PyObject *args) { return Py_BuildValue("z", glfwGetKeyName(key, native_key)); } + +bool +is_modifier_key(const uint32_t key) { + START_ALLOW_CASE_RANGE + switch (key) { + case GLFW_FKEY_LEFT_SHIFT ... GLFW_FKEY_ISO_LEVEL5_SHIFT: + case GLFW_FKEY_CAPS_LOCK: + case GLFW_FKEY_SCROLL_LOCK: + case GLFW_FKEY_NUM_LOCK: + return true; + default: + return false; + } + END_ALLOW_CASE_RANGE +} + + +static PyObject* +glfw_is_modifier_key(PyObject UNUSED *self, PyObject *args) { + uint32_t key; + if (!PyArg_ParseTuple(args, "I", &key)) return NULL; + PyObject *ans = is_modifier_key(key) ? Py_True : Py_False; + Py_INCREF(ans); + return ans; +} + + static PyObject* glfw_window_hint(PyObject UNUSED *self, PyObject *args) { int key, val; @@ -1762,6 +1789,7 @@ static PyMethodDef module_methods[] = { {"glfw_terminate", (PyCFunction)glfw_terminate, METH_NOARGS, ""}, {"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""}, {"glfw_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""}, + METHODB(glfw_is_modifier_key, METH_VARARGS), {"glfw_primary_monitor_size", (PyCFunction)primary_monitor_size, METH_NOARGS, ""}, {"glfw_primary_monitor_content_scale", (PyCFunction)primary_monitor_content_scale, METH_NOARGS, ""}, {NULL, NULL, 0, NULL} /* Sentinel */ diff --git a/kitty/key_encoding.c b/kitty/key_encoding.c index a643b8c89..47a54f950 100644 --- a/kitty/key_encoding.c +++ b/kitty/key_encoding.c @@ -31,21 +31,6 @@ typedef struct { KeyAction action; } EncodingData; -bool -is_modifier_key(const uint32_t key) { - START_ALLOW_CASE_RANGE - switch (key) { - case GLFW_FKEY_LEFT_SHIFT ... GLFW_FKEY_ISO_LEVEL5_SHIFT: - case GLFW_FKEY_CAPS_LOCK: - case GLFW_FKEY_SCROLL_LOCK: - case GLFW_FKEY_NUM_LOCK: - return true; - default: - return false; - } - END_ALLOW_CASE_RANGE -} - static void convert_glfw_mods(int mods, KeyEvent *ev, const unsigned key_encoding_flags) { if (!key_encoding_flags) mods &= ~GLFW_LOCK_MASK; diff --git a/kitty/key_encoding.py b/kitty/key_encoding.py index 99b65819c..110359b61 100644 --- a/kitty/key_encoding.py +++ b/kitty/key_encoding.py @@ -426,14 +426,3 @@ def decode_key_event_as_window_system_key(text: str) -> Optional[WindowSystemKey except Exception: return None return k.as_window_system_event() - - -# The same as `bool is_modifier_key(key)` in key_encoding.c -def is_modifier_key(key: int) -> bool: - if defines.GLFW_FKEY_LEFT_SHIFT <= key <= defines.GLFW_FKEY_ISO_LEVEL5_SHIFT: - return True - if key == defines.GLFW_FKEY_CAPS_LOCK or \ - key == defines.GLFW_FKEY_SCROLL_LOCK or \ - key == defines.GLFW_FKEY_NUM_LOCK: - return True - return False diff --git a/kitty/keys.h b/kitty/keys.h index 2f70ca6d6..944dc4797 100644 --- a/kitty/keys.h +++ b/kitty/keys.h @@ -18,5 +18,6 @@ int encode_glfw_key_event(const GLFWkeyevent *e, const bool cursor_key_mode, const unsigned flags, char *output); -bool +// Defined in glfw.c +extern bool is_modifier_key(const uint32_t key);