diff --git a/docs/changelog.rst b/docs/changelog.rst index b8150bd80..50c1e8650 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -86,6 +86,8 @@ Detailed list of changes - macOS 15: Handle Fn modifier when detecting global shortcuts (:iss:`7582`) +- Dispatch any clicks waiting for :opt:`click_interval` on key events (:iss:`7601`) + 0.35.2 [2024-06-22] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/kitty/keys.c b/kitty/keys.c index dfab1aa42..a0861f208 100644 --- a/kitty/keys.c +++ b/kitty/keys.c @@ -180,6 +180,7 @@ on_key_input(GLFWkeyevent *ev) { } } if (!w) { debug("no active window, ignoring\n"); return; } + send_pending_click_to_window(w, -1); if (OPT(mouse_hide_wait) < 0 && !is_no_action_key(key, native_key)) hide_mouse(global_state.callback_os_window); Screen *screen = w->render_data.screen; id_type active_window_id = w->id; diff --git a/kitty/mouse.c b/kitty/mouse.c index 37323e6c8..9c2cfe7bb 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -491,38 +491,30 @@ move_cursor_to_mouse_if_at_shell_prompt(Window *w) { } -typedef struct PendingClick { - id_type window_id; - int button, count, modifiers; - bool grabbed; - monotonic_t at; - MousePosition mouse_pos; - unsigned long press_num; - double radius_for_multiclick; -} PendingClick; - -static void -free_pending_click(id_type timer_id UNUSED, void *pc) { free(pc); } - void -send_pending_click_to_window(Window *w, void *data) { - PendingClick *pc = (PendingClick*)data; - const ClickQueue *q = &w->click_queues[pc->button]; +send_pending_click_to_window(Window *w, int i) { + if (i < 0) { + while (w->pending_clicks.num) send_pending_click_to_window(w, w->pending_clicks.num - 1); + return; + } + PendingClick pc = w->pending_clicks.clicks[i]; + remove_i_from_array(w->pending_clicks.clicks, (unsigned)i, w->pending_clicks.num); + const ClickQueue *q = &w->click_queues[pc.button]; // only send click if no presses have happened since the release that triggered // the click or if the subsequent press is too far or too late for a double click if (!q->length) return; #define press(n) q->clicks[q->length - n] if ( - press(1).at <= pc->at || // latest press is before click release - (q->length > 1 && press(2).num == pc->press_num && ( // penultimate press is the press that belongs to this click + press(1).at <= pc.at || // latest press is before click release + (q->length > 1 && press(2).num == pc.press_num && ( // penultimate press is the press that belongs to this click press(1).at - press(2).at > OPT(click_interval) || // too long between the presses for it to be a double click - distance(press(1).x, press(1).y, press(2).x, press(2).y) > pc->radius_for_multiclick // presses are too far apart + distance(press(1).x, press(1).y, press(2).x, press(2).y) > pc.radius_for_multiclick // presses are too far apart )) ) { MousePosition current_pos = w->mouse_pos; - w->mouse_pos = pc->mouse_pos; + w->mouse_pos = pc.mouse_pos; id_type wid = w->id; - dispatch_mouse_event(w, pc->button, pc->count, pc->modifiers, pc->grabbed); + dispatch_mouse_event(w, pc.button, pc.count, pc.modifiers, pc.grabbed); w = window_for_id(wid); if (w) w->mouse_pos = current_pos; } @@ -534,20 +526,20 @@ 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)) { - PendingClick *pc = calloc(1, sizeof(PendingClick)); - if (pc) { - const ClickQueue *q = &w->click_queues[button]; - pc->press_num = q->length ? q->clicks[q->length - 1].num : 0; - pc->window_id = w->id; - pc->mouse_pos = w->mouse_pos; - pc->at = monotonic(); - pc->button = button; - pc->count = count == 2 ? -3 : -2; - pc->modifiers = modifiers; - pc->grabbed = screen->modes.mouse_tracking_mode != 0; - pc->radius_for_multiclick = radius_for_multiclick(); - add_main_loop_timer(OPT(click_interval), false, send_pending_click_to_window_id, pc, free_pending_click); - } + ensure_space_for(&(w->pending_clicks), clicks, PendingClick, w->pending_clicks.num + 1, capacity, 4, true); + PendingClick *pc = w->pending_clicks.clicks + w->pending_clicks.num++; + zero_at_ptr(pc); + const ClickQueue *q = &w->click_queues[button]; + pc->press_num = q->length ? q->clicks[q->length - 1].num : 0; + pc->window_id = w->id; + pc->mouse_pos = w->mouse_pos; + pc->at = monotonic(); + pc->button = button; + pc->count = count == 2 ? -3 : -2; + pc->modifiers = modifiers; + pc->grabbed = screen->modes.mouse_tracking_mode != 0; + pc->radius_for_multiclick = radius_for_multiclick(); + add_main_loop_timer(OPT(click_interval), false, dispatch_pending_clicks, NULL, NULL); } } diff --git a/kitty/state.c b/kitty/state.c index c124b7b41..c11f7e86b 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -337,6 +337,7 @@ update_os_window_title(OSWindow *os_window) { static void destroy_window(Window *w) { + free(w->pending_clicks.clicks); w->pending_clicks.clicks = NULL; w->pending_clicks.num = 0; w->pending_clicks.capacity = 0; Py_CLEAR(w->render_data.screen); Py_CLEAR(w->title); Py_CLEAR(w->title_bar_data.last_drawn_title_object_id); free(w->title_bar_data.buf); w->title_bar_data.buf = NULL; @@ -611,21 +612,27 @@ make_window_context_current(id_type window_id) { } void -send_pending_click_to_window_id(id_type timer_id UNUSED, void *data) { - id_type window_id = *((id_type*)data); - for (size_t o = 0; o < global_state.num_os_windows; o++) { - OSWindow *osw = global_state.os_windows + o; - for (size_t t = 0; t < osw->num_tabs; t++) { - Tab *qtab = osw->tabs + t; - for (size_t w = 0; w < qtab->num_windows; w++) { - Window *window = qtab->windows + w; - if (window->id == window_id) { - send_pending_click_to_window(window, data); - return; +dispatch_pending_clicks(id_type timer_id UNUSED, void *data UNUSED) { + bool dispatched = false; + do { // dispatching a click can cause windows/tabs/etc to close so do it one at a time. + const monotonic_t now = monotonic(); + dispatched = false; + for (size_t o = 0; o < global_state.num_os_windows && !dispatched; o++) { + OSWindow *osw = global_state.os_windows + o; + for (size_t t = 0; t < osw->num_tabs && !dispatched; t++) { + Tab *qtab = osw->tabs + t; + for (size_t w = 0; w < qtab->num_windows && !dispatched; w++) { + Window *window = qtab->windows + w; + for (size_t i = 0; i < window->pending_clicks.num && !dispatched; i++) { + if (now - window->pending_clicks.clicks[i].at >= OPT(click_interval)) { + dispatched = true; + send_pending_click_to_window(window, i); + } + } } } } - } + } while (dispatched); } bool diff --git a/kitty/state.h b/kitty/state.h index b9b27f01d..a283bba55 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -157,6 +157,17 @@ typedef struct MousePosition { bool in_left_half_of_cell; } MousePosition; +typedef struct PendingClick { + id_type window_id; + int button, count, modifiers; + bool grabbed; + monotonic_t at; + MousePosition mouse_pos; + unsigned long press_num; + double radius_for_multiclick; +} PendingClick; + + typedef struct WindowBarData { unsigned width, height; uint8_t *buf; @@ -181,6 +192,10 @@ typedef struct { monotonic_t last_drag_scroll_at; uint32_t last_special_key_pressed; WindowBarData title_bar_data, url_target_bar_data; + struct { + PendingClick *clicks; + size_t num, capacity; + } pending_clicks; } Window; typedef struct { @@ -396,8 +411,8 @@ bool mouse_select_cmd_output(Window *w); bool move_cursor_to_mouse_if_at_shell_prompt(Window *w); void mouse_selection(Window *w, int code, int button); const char* format_mods(unsigned mods); -void send_pending_click_to_window_id(id_type, void*); -void send_pending_click_to_window(Window*, void*); +void dispatch_pending_clicks(id_type, void*); +void send_pending_click_to_window(Window*, int); void get_platform_dependent_config_values(void *glfw_window); bool draw_window_title(OSWindow *window, const char *text, color_type fg, color_type bg, uint8_t *output_buf, size_t width, size_t height); uint8_t* draw_single_ascii_char(const char ch, size_t *result_width, size_t *result_height);