diff --git a/kitty/config.py b/kitty/config.py index aef545fa9..a567f11c5 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -492,7 +492,7 @@ def parse_mouse_action(val: str, mouse_mappings: List[MouseMapping]) -> None: log_error(f'Mouse button: {xbutton} not recognized, ignoring') return try: - count = {'release': -1, 'press': 1, 'doublepress': 2, 'triplepress': 3}[event.lower()] + count = {'doubleclick': -3, 'click': -2, 'release': -1, 'press': 1, 'doublepress': 2, 'triplepress': 3}[event.lower()] except KeyError: log_error(f'Mouse event type: {event} not recognized, ignoring') return diff --git a/kitty/config_data.py b/kitty/config_data.py index 0e75dfe14..34699cbbe 100644 --- a/kitty/config_data.py +++ b/kitty/config_data.py @@ -154,16 +154,17 @@ with added keyboard modifiers, for example: ``ctrl+shift+left`` refers to holdin the :kbd:`ctrl+shift` keys while clicking with the left mouse button. The number ``b1 ... b8`` can be used to refer to upto eight buttons on a mouse. -``event-type`` is one ``press``, ``release``, ``doublepress`` and ``triplepress``. -``modes`` indicates whether the action is performed when the mouse is grabbed by the -terminal application or not. It can have one or more or the values, ``grabbed,ungrabbed``. +``event-type`` is one ``press``, ``release``, ``doublepress``, ``triplepress``, +``click`` and ``doubleclick``. ``modes`` indicates whether the action is +performed when the mouse is grabbed by the terminal application or not. It can +have one or more or the values, ``grabbed,ungrabbed``. You can run kitty with the :option:`kitty --debug-input` command line option to see mouse events. See the builtin actions below to get a sense of what is possible. .. note:: - Once a selection drag is started, releasing the button that started it will - automatically end it. + Once a selection is started, releasing the button that started it will + automatically end it and no release event will be dispatched. '''), ], diff --git a/kitty/mouse.c b/kitty/mouse.c index 58313068f..4eeba65f0 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -127,6 +127,29 @@ static bool dispatch_mouse_event(Window *w, int button, int count, int modifiers, bool grabbed) { bool handled = false; if (w->render_data.screen && w->render_data.screen->callbacks != Py_None) { + if (OPT(debug_keyboard)) { + const char *evname = "move"; + switch(count) { + case -3: evname = "doubleclick"; break; + case -2: evname = "click"; break; + case -1: evname = "release"; break; + case 1: evname = "press"; break; + case 2: evname = "doublepress"; break; + case 3: evname = "triplepress"; break; + } + const char *bname = "unknown"; + switch(button) { + case GLFW_MOUSE_BUTTON_LEFT: bname = "left"; break; + case GLFW_MOUSE_BUTTON_MIDDLE: bname = "middle"; break; + case GLFW_MOUSE_BUTTON_RIGHT: bname = "right"; break; + case GLFW_MOUSE_BUTTON_4: bname = "b4"; break; + case GLFW_MOUSE_BUTTON_5: bname = "b5"; break; + case GLFW_MOUSE_BUTTON_6: bname = "b6"; break; + case GLFW_MOUSE_BUTTON_7: bname = "b7"; break; + case GLFW_MOUSE_BUTTON_8: bname = "b8"; break; + } + debug("\x1b[33mon_mouse_input\x1b[m: %s button: %s %sgrabbed: %d\n", evname, bname, format_mods(modifiers), grabbed); + } 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(); @@ -402,6 +425,14 @@ clear_click_queue(Window *w, int button) { #define N(n) (q->clicks[q->length - n]) +static bool +release_is_click(Window *w, int button) { + ClickQueue *q = &w->click_queues[button]; + double click_allowed_radius = 1.2 * (global_state.callback_os_window ? global_state.callback_os_window->fonts_data->cell_height : 20); + monotonic_t now = monotonic(); + return (q->length > 0 && distance(N(1).x, N(1).y, w->mouse_pos.x, w->mouse_pos.y) <= click_allowed_radius && now - N(1).at < OPT(click_interval)); +} + static unsigned multi_click_count(Window *w, int button) { ClickQueue *q = &w->click_queues[button]; @@ -424,7 +455,8 @@ multi_click_count(Window *w, int button) { } -HANDLER(add_click) { +static void +add_press(Window *w, int button, int modifiers) { if (button < 0 || button > (ssize_t)arraysz(w->click_queues)) return; modifiers &= ~GLFW_LOCK_MASK; ClickQueue *q = &w->click_queues[button]; @@ -448,6 +480,13 @@ mouse_open_url(Window *w) { screen_open_url(screen); } +static void +dispatch_possible_click(Window *w, int button, int modifiers) { + Screen *screen = w->render_data.screen; + int count = multi_click_count(w, button); + if (release_is_click(w, button)) dispatch_mouse_event(w, button, count == 2 ? -3 : -2, modifiers, screen->modes.mouse_tracking_mode != 0); +} + HANDLER(handle_button_event) { modifiers &= ~GLFW_LOCK_MASK; Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; @@ -463,9 +502,8 @@ HANDLER(handle_button_event) { if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, CSI, mouse_event_buf); } } } - if (!is_release) add_click(w, button, modifiers, 0); - else { - } + if (is_release) dispatch_possible_click(w, button, modifiers); + else add_press(w, button, modifiers); } static inline int @@ -658,6 +696,7 @@ mouse_event(int button, int modifiers, int action) { if (w) { end_drag(w); debug("handled as drag end\n"); + dispatch_possible_click(w, button, modifiers); return; } } @@ -836,7 +875,7 @@ send_mock_mouse_event_to_window(PyObject *self UNUSED, PyObject *args) { dispatch_mouse_event(w, button, is_release ? -1 : 1, modifiers, false); if (!is_release) { last_button_pressed = button; - add_click(w, button, modifiers, 0); + add_press(w, button, modifiers); } } }