diff --git a/kitty/boss.py b/kitty/boss.py index 51a88b973..1475bc375 100755 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -736,27 +736,34 @@ class Boss: if t is not None: t.relayout_borders() - def dispatch_action(self, key_action: KeyAction) -> bool: + def dispatch_action( + self, + key_action: KeyAction, + window_for_dispatch: Optional[Window] = None, + dispatch_type: str = 'KeyPress' + ) -> bool: if key_action is not None: f = getattr(self, key_action.func, None) if f is not None: if self.args.debug_keyboard: - print('Keypress matched action:', func_name(f)) + print(f'{dispatch_type} matched action:', func_name(f)) passthrough = f(*key_action.args) if passthrough is not True: return True - tab = self.active_tab - if tab is None: - return False - window = self.active_window - if window is None: + if window_for_dispatch is None: + tab = self.active_tab + window = self.active_window + else: + window = window_for_dispatch + tab = window.tabref() + if tab is None or window is None: return False if key_action is not None: f = getattr(tab, key_action.func, getattr(window, key_action.func, None)) if f is not None: passthrough = f(*key_action.args) if self.args.debug_keyboard: - print('Keypress matched action:', func_name(f)) + print(f'{dispatch_type} matched action:', func_name(f)) if passthrough is not True: return True return False diff --git a/kitty/mouse.c b/kitty/mouse.c index 1e515dbdd..9f1304dfe 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -122,14 +122,19 @@ encode_mouse_scroll(Window *w, bool upwards, int mods) { // }}} -static inline void +static bool dispatch_mouse_event(Window *w, int button, int count, int modifiers, bool grabbed) { - if (w->render_data.screen && PyCallable_Check(w->render_data.screen->callbacks)) { + bool handled = false; + if (w->render_data.screen && w->render_data.screen->callbacks != Py_None) { PyObject *callback_ret = PyObject_CallMethod(w->render_data.screen->callbacks, "on_mouse_event", "{si si si sO}", "button", button, "repeat_count", count, "mods", modifiers, "grabbed", grabbed ? Py_True : Py_False); if (callback_ret == NULL) PyErr_Print(); - else Py_DECREF(callback_ret); + else { + handled = callback_ret == Py_True; + Py_DECREF(callback_ret); + } } + return handled; } static inline unsigned int @@ -443,6 +448,7 @@ HANDLER(add_click) { N(0).at = now; N(0).button = button; N(0).modifiers = modifiers; N(0).x = w->mouse_pos.x; N(0).y = w->mouse_pos.y; q->length++; double multi_click_allowed_radius = 1.2 * (global_state.callback_os_window ? global_state.callback_os_window->fonts_data->cell_height : 20); + Screen *screen = w->render_data.screen; // Now dispatch the multi-click if any if (q->length > 2) { // possible triple-click @@ -450,7 +456,7 @@ HANDLER(add_click) { N(1).at - N(3).at <= 2 * OPT(click_interval) && distance(N(1).x, N(1).y, N(3).x, N(3).y) <= multi_click_allowed_radius ) { - multi_click(w, 3); + if (screen) dispatch_mouse_event(w, button, 3, modifiers, screen->modes.mouse_tracking_mode != 0); q->length = 0; } } @@ -460,7 +466,7 @@ HANDLER(add_click) { N(1).at - N(2).at <= OPT(click_interval) && distance(N(1).x, N(1).y, N(2).x, N(2).y) <= multi_click_allowed_radius ) { - multi_click(w, 2); + if (screen) dispatch_mouse_event(w, button, 2, modifiers, screen->modes.mouse_tracking_mode != 0); } } #undef N @@ -473,7 +479,7 @@ open_url(Window *w) { screen_open_url(screen); } -static inline void +static void handle_button_event_in_kitty(Window *w, int button, int modifiers, bool is_release) { switch(button) { case GLFW_MOUSE_BUTTON_LEFT: @@ -500,17 +506,11 @@ HANDLER(handle_button_event) { } Screen *screen = w->render_data.screen; if (!screen) return; - const int ts1 = OPT(terminal_select_modifiers), ts2 = OPT(terminal_select_modifiers) | OPT(rectangle_select_modifiers); - bool handle_in_kitty = ( - modifiers == ts1 || modifiers == ts2 || - screen->modes.mouse_tracking_mode == 0 || - (modifiers == (int)OPT(open_url_modifiers) && button == GLFW_MOUSE_BUTTON_LEFT) - ); - if (handle_in_kitty) handle_button_event_in_kitty(w, button, modifiers, is_release); - else { + if (!dispatch_mouse_event(w, button, is_release ? -1 : 1, modifiers, screen->modes.mouse_tracking_mode != 0)) { int sz = encode_mouse_button(w, button, is_release ? RELEASE : PRESS, modifiers); if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, CSI, mouse_event_buf); } } + add_click(w, button, modifiers, 0); } static inline int diff --git a/kitty/window.py b/kitty/window.py index 439f5bbd6..d65d58c6f 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -19,7 +19,7 @@ from typing import ( from .child import ProcessDesc from .cli_stub import CLIOptions from .config import build_ansi_color_table -from .constants import appname, wakeup, is_macos +from .constants import appname, is_macos, wakeup from .fast_data_types import ( BGIMAGE_PROGRAM, BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM, CELL_PROGRAM, CELL_SPECIAL_PROGRAM, DCS, DECORATION, DIM, GLFW_MOD_CONTROL, @@ -36,7 +36,7 @@ from .notify import NotificationCommand, handle_notification_cmd from .options_stub import Options from .rgb import to_color from .terminfo import get_capabilities -from .types import ScreenGeometry, WindowGeometry +from .types import MouseEvent, ScreenGeometry, WindowGeometry from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict from .utils import ( color_as_int, get_primary_selection, load_shaders, log_error, open_cmd, @@ -530,6 +530,13 @@ class Window: def use_utf8(self, on: bool) -> None: get_boss().child_monitor.set_iutf8_winid(self.id, on) + def on_mouse_event(self, event: Dict[str, Any]) -> bool: + ev = MouseEvent(**event) + action = self.opts.mousemap.get(ev) + if action is None: + return False + return get_boss().dispatch_action(action, window_for_dispatch=self, dispatch_type='MouseEvent') + def open_url(self, url: str, hyperlink_id: int, cwd: Optional[str] = None) -> None: if hyperlink_id: if not self.opts.allow_hyperlinks: