1 /*
2  * mouse.c
3  * Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
4  *
5  * Distributed under terms of the GPL3 license.
6  */
7 
8 #include "state.h"
9 #include "screen.h"
10 #include "lineops.h"
11 #include "charsets.h"
12 #include <limits.h>
13 #include <math.h>
14 #include "glfw-wrapper.h"
15 #include "control-codes.h"
16 #include "monotonic.h"
17 
18 extern PyTypeObject Screen_Type;
19 
20 static MouseShape mouse_cursor_shape = BEAM;
21 typedef enum MouseActions { PRESS, RELEASE, DRAG, MOVE } MouseAction;
22 #define debug(...) if (OPT(debug_keyboard)) printf(__VA_ARGS__);
23 
24 // Encoding of mouse events {{{
25 #define SHIFT_INDICATOR  (1 << 2)
26 #define ALT_INDICATOR (1 << 3)
27 #define CONTROL_INDICATOR (1 << 4)
28 #define MOTION_INDICATOR  (1 << 5)
29 #define SCROLL_BUTTON_INDICATOR (1 << 6)
30 #define EXTRA_BUTTON_INDICATOR (1 << 7)
31 
32 
33 static unsigned int
button_map(int button)34 button_map(int button) {
35     switch(button) {
36         case GLFW_MOUSE_BUTTON_LEFT:
37             return 1;
38         case GLFW_MOUSE_BUTTON_RIGHT:
39             return 3;
40         case GLFW_MOUSE_BUTTON_MIDDLE:
41             return 2;
42         case GLFW_MOUSE_BUTTON_4:
43         case GLFW_MOUSE_BUTTON_5:
44         case GLFW_MOUSE_BUTTON_6:
45         case GLFW_MOUSE_BUTTON_7:
46         case GLFW_MOUSE_BUTTON_8:
47             return button + 5;
48         default:
49             return UINT_MAX;
50     }
51 }
52 
53 static unsigned int
encode_button(unsigned int button)54 encode_button(unsigned int button) {
55     if (button >= 8 && button <= 11) {
56         return (button - 8) | EXTRA_BUTTON_INDICATOR;
57     } else if (button >= 4 && button <= 7) {
58         return (button - 4) | SCROLL_BUTTON_INDICATOR;
59     } else if (button >= 1 && button <= 3) {
60         return button - 1;
61     } else {
62         return UINT_MAX;
63     }
64 }
65 
66 static char mouse_event_buf[64];
67 
68 static int
encode_mouse_event_impl(unsigned int x,unsigned int y,int mouse_tracking_protocol,int button,MouseAction action,int mods)69 encode_mouse_event_impl(unsigned int x, unsigned int y, int mouse_tracking_protocol, int button, MouseAction action, int mods) {
70     unsigned int cb = 0;
71     if (action == MOVE) {
72         cb = 3;
73     } else {
74         cb = encode_button(button);
75         if (cb == UINT_MAX) return 0;
76     }
77     if (action == DRAG || action == MOVE) cb |= MOTION_INDICATOR;
78     else if (action == RELEASE && mouse_tracking_protocol != SGR_PROTOCOL) cb = 3;
79     if (mods & GLFW_MOD_SHIFT) cb |= SHIFT_INDICATOR;
80     if (mods & GLFW_MOD_ALT) cb |= ALT_INDICATOR;
81     if (mods & GLFW_MOD_CONTROL) cb |= CONTROL_INDICATOR;
82     switch(mouse_tracking_protocol) {
83         case SGR_PROTOCOL:
84             return snprintf(mouse_event_buf, sizeof(mouse_event_buf), "<%d;%d;%d%s", cb, x, y, action == RELEASE ? "m" : "M");
85             break;
86         case URXVT_PROTOCOL:
87             return snprintf(mouse_event_buf, sizeof(mouse_event_buf), "%d;%d;%dM", cb + 32, x, y);
88             break;
89         case UTF8_PROTOCOL:
90             mouse_event_buf[0] = 'M'; mouse_event_buf[1] = cb + 32;
91             unsigned int sz = 2;
92             sz += encode_utf8(x + 32, mouse_event_buf + sz);
93             sz += encode_utf8(y + 32, mouse_event_buf + sz);
94             return sz;
95             break;
96         default:
97             if (x > 223 || y > 223) return 0;
98             else {
99                 mouse_event_buf[0] = 'M'; mouse_event_buf[1] = cb + 32; mouse_event_buf[2] = x + 32; mouse_event_buf[3] = y + 32;
100                 return 4;
101             }
102             break;
103     }
104     return 0;
105 }
106 
107 static int
encode_mouse_event(Window * w,int button,MouseAction action,int mods)108 encode_mouse_event(Window *w, int button, MouseAction action, int mods) {
109     unsigned int x = w->mouse_pos.cell_x + 1, y = w->mouse_pos.cell_y + 1; // 1 based indexing
110     Screen *screen = w->render_data.screen;
111     return encode_mouse_event_impl(x, y, screen->modes.mouse_tracking_protocol, button, action, mods);
112 }
113 
114 static int
encode_mouse_button(Window * w,int button,MouseAction action,int mods)115 encode_mouse_button(Window *w, int button, MouseAction action, int mods) {
116     return encode_mouse_event(w, button_map(button), action, mods);
117 }
118 
119 static int
encode_mouse_scroll(Window * w,bool upwards,int mods)120 encode_mouse_scroll(Window *w, bool upwards, int mods) {
121     return encode_mouse_event(w, upwards ? 4 : 5, PRESS, mods);
122 }
123 
124 // }}}
125 
126 static bool
dispatch_mouse_event(Window * w,int button,int count,int modifiers,bool grabbed)127 dispatch_mouse_event(Window *w, int button, int count, int modifiers, bool grabbed) {
128     bool handled = false;
129     if (w->render_data.screen && w->render_data.screen->callbacks != Py_None) {
130         if (OPT(debug_keyboard)) {
131             const char *evname = "move";
132             switch(count) {
133                 case -3: evname = "doubleclick"; break;
134                 case -2: evname = "click"; break;
135                 case -1: evname = "release"; break;
136                 case 1: evname = "press"; break;
137                 case 2: evname = "doublepress"; break;
138                 case 3: evname = "triplepress"; break;
139             }
140             const char *bname = "unknown";
141             switch(button) {
142                 case GLFW_MOUSE_BUTTON_LEFT: bname = "left"; break;
143                 case GLFW_MOUSE_BUTTON_MIDDLE: bname = "middle"; break;
144                 case GLFW_MOUSE_BUTTON_RIGHT: bname = "right"; break;
145                 case GLFW_MOUSE_BUTTON_4: bname = "b4"; break;
146                 case GLFW_MOUSE_BUTTON_5: bname = "b5"; break;
147                 case GLFW_MOUSE_BUTTON_6: bname = "b6"; break;
148                 case GLFW_MOUSE_BUTTON_7: bname = "b7"; break;
149                 case GLFW_MOUSE_BUTTON_8: bname = "b8"; break;
150             }
151             debug("\x1b[33mon_mouse_input\x1b[m: %s button: %s %sgrabbed: %d\n", evname, bname, format_mods(modifiers), grabbed);
152         }
153         PyObject *callback_ret = PyObject_CallMethod(w->render_data.screen->callbacks, "on_mouse_event", "{si si si sO}",
154             "button", button, "repeat_count", count, "mods", modifiers, "grabbed", grabbed ? Py_True : Py_False);
155         if (callback_ret == NULL) PyErr_Print();
156         else {
157             handled = callback_ret == Py_True;
158             Py_DECREF(callback_ret);
159         }
160     }
161     return handled;
162 }
163 
164 static unsigned int
window_left(Window * w)165 window_left(Window *w) {
166     return w->geometry.left - w->padding.left;
167 }
168 
169 static unsigned int
window_right(Window * w)170 window_right(Window *w) {
171     return w->geometry.right + w->padding.right;
172 }
173 
174 static unsigned int
window_top(Window * w)175 window_top(Window *w) {
176     return w->geometry.top - w->padding.top;
177 }
178 
179 static unsigned int
window_bottom(Window * w)180 window_bottom(Window *w) {
181     return w->geometry.bottom + w->padding.bottom;
182 }
183 
184 static bool
contains_mouse(Window * w)185 contains_mouse(Window *w) {
186     double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y;
187     return (w->visible && window_left(w) <= x && x <= window_right(w) && window_top(w) <= y && y <= window_bottom(w));
188 }
189 
190 static double
distance_to_window(Window * w)191 distance_to_window(Window *w) {
192     double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y;
193     double cx = (window_left(w) + window_right(w)) / 2.0;
194     double cy = (window_top(w) + window_bottom(w)) / 2.0;
195     return (x - cx) * (x - cx) + (y - cy) * (y - cy);
196 }
197 
198 static bool clamp_to_window = false;
199 
200 static bool
cell_for_pos(Window * w,unsigned int * x,unsigned int * y,bool * in_left_half_of_cell,OSWindow * os_window)201 cell_for_pos(Window *w, unsigned int *x, unsigned int *y, bool *in_left_half_of_cell, OSWindow *os_window) {
202     WindowGeometry *g = &w->geometry;
203     Screen *screen = w->render_data.screen;
204     if (!screen) return false;
205     unsigned int qx = 0, qy = 0;
206     bool in_left_half = true;
207     double mouse_x = global_state.callback_os_window->mouse_x;
208     double mouse_y = global_state.callback_os_window->mouse_y;
209     double left = window_left(w), top = window_top(w), right = window_right(w), bottom = window_bottom(w);
210     if (clamp_to_window) {
211         mouse_x = MIN(MAX(mouse_x, left), right);
212         mouse_y = MIN(MAX(mouse_y, top), bottom);
213     }
214     w->mouse_pos.x = mouse_x - left; w->mouse_pos.y = mouse_y - top;
215     if (mouse_x < left || mouse_y < top || mouse_x > right || mouse_y > bottom) return false;
216     if (mouse_x >= g->right) {
217         qx = screen->columns - 1;
218         in_left_half = false;
219     } else if (mouse_x >= g->left) {
220         double xval = (double)(mouse_x - g->left) / os_window->fonts_data->cell_width;
221         double fxval = floor(xval);
222         qx = (unsigned int)fxval;
223         in_left_half = (xval - fxval <= 0.5) ? true : false;
224     }
225     if (mouse_y >= g->bottom) qy = screen->lines - 1;
226     else if (mouse_y >= g->top) qy = (unsigned int)((double)(mouse_y - g->top) / os_window->fonts_data->cell_height);
227     if (qx < screen->columns && qy < screen->lines) {
228         *x = qx; *y = qy;
229         *in_left_half_of_cell = in_left_half;
230         return true;
231     }
232     return false;
233 }
234 
235 #define HANDLER(name) static void name(Window UNUSED *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx)
236 
237 static void
set_mouse_cursor_when_dragging(void)238 set_mouse_cursor_when_dragging(void) {
239     if (mouse_cursor_shape != OPT(pointer_shape_when_dragging)) {
240         mouse_cursor_shape = OPT(pointer_shape_when_dragging);
241         set_mouse_cursor(mouse_cursor_shape);
242     }
243 }
244 
245 static void
update_drag(Window * w)246 update_drag(Window *w) {
247     Screen *screen = w->render_data.screen;
248     if (screen && screen->selections.in_progress) {
249         screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, (SelectionUpdate){0});
250     }
251     set_mouse_cursor_when_dragging();
252 }
253 
254 static bool
do_drag_scroll(Window * w,bool upwards)255 do_drag_scroll(Window *w, bool upwards) {
256     Screen *screen = w->render_data.screen;
257     if (screen->linebuf == screen->main_linebuf) {
258         screen_history_scroll(screen, SCROLL_LINE, upwards);
259         update_drag(w);
260         if (mouse_cursor_shape != ARROW) {
261             mouse_cursor_shape = ARROW;
262             set_mouse_cursor(mouse_cursor_shape);
263         }
264         return true;
265     }
266     return false;
267 }
268 
269 bool
drag_scroll(Window * w,OSWindow * frame)270 drag_scroll(Window *w, OSWindow *frame) {
271     unsigned int margin = frame->fonts_data->cell_height / 2;
272     double y = frame->mouse_y;
273     bool upwards = y <= (w->geometry.top + margin);
274     if (upwards || y >= w->geometry.bottom - margin) {
275         if (do_drag_scroll(w, upwards)) {
276             frame->last_mouse_activity_at = monotonic();
277             return true;
278         }
279     }
280     return false;
281 }
282 
283 static void
extend_selection(Window * w,bool ended,bool extend_nearest)284 extend_selection(Window *w, bool ended, bool extend_nearest) {
285     Screen *screen = w->render_data.screen;
286     if (screen_has_selection(screen)) {
287         screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell,
288                 (SelectionUpdate){.ended=ended, .set_as_nearest_extend=extend_nearest});
289     }
290 }
291 
292 static void
set_mouse_cursor_for_screen(Screen * screen)293 set_mouse_cursor_for_screen(Screen *screen) {
294     mouse_cursor_shape = screen->modes.mouse_tracking_mode == NO_TRACKING ? OPT(default_pointer_shape): OPT(pointer_shape_when_grabbed);
295 }
296 
297 static void
handle_mouse_movement_in_kitty(Window * w,int button,bool mouse_cell_changed)298 handle_mouse_movement_in_kitty(Window *w, int button, bool mouse_cell_changed) {
299     Screen *screen = w->render_data.screen;
300     if (screen->selections.in_progress && (button == global_state.active_drag_button)) {
301         monotonic_t now = monotonic();
302         if ((now - w->last_drag_scroll_at) >= ms_to_monotonic_t(20ll) || mouse_cell_changed) {
303             update_drag(w);
304             w->last_drag_scroll_at = now;
305         }
306     }
307 
308 }
309 
310 static void
detect_url(Screen * screen,unsigned int x,unsigned int y)311 detect_url(Screen *screen, unsigned int x, unsigned int y) {
312     if (screen_detect_url(screen, x, y)) mouse_cursor_shape = HAND;
313     else set_mouse_cursor_for_screen(screen);
314 }
315 
HANDLER(handle_move_event)316 HANDLER(handle_move_event) {
317     modifiers &= ~GLFW_LOCK_MASK;
318     unsigned int x = 0, y = 0;
319     if (OPT(focus_follows_mouse)) {
320         Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
321         if (window_idx != t->active_window) {
322             call_boss(switch_focus_to, "K", t->windows[window_idx].id);
323         }
324     }
325     bool in_left_half_of_cell = false;
326     if (!cell_for_pos(w, &x, &y, &in_left_half_of_cell, global_state.callback_os_window)) return;
327     Screen *screen = w->render_data.screen;
328     if(OPT(detect_urls)) detect_url(screen, x, y);
329     bool mouse_cell_changed = x != w->mouse_pos.cell_x || y != w->mouse_pos.cell_y;
330     bool cell_half_changed = in_left_half_of_cell != w->mouse_pos.in_left_half_of_cell;
331     w->mouse_pos.cell_x = x; w->mouse_pos.cell_y = y;
332     w->mouse_pos.in_left_half_of_cell = in_left_half_of_cell;
333     bool in_tracking_mode = (
334         screen->modes.mouse_tracking_mode == ANY_MODE ||
335         (screen->modes.mouse_tracking_mode == MOTION_MODE && button >= 0));
336     bool handle_in_kitty = !in_tracking_mode || global_state.active_drag_in_window == w->id;
337     if (handle_in_kitty) {
338         handle_mouse_movement_in_kitty(w, button, mouse_cell_changed | cell_half_changed);
339     } else {
340         if (!mouse_cell_changed) return;
341         int sz = encode_mouse_button(w, MAX(0, button), button >=0 ? DRAG : MOVE, modifiers);
342         if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, CSI, mouse_event_buf); }
343     }
344 }
345 
346 static double
distance(double x1,double y1,double x2,double y2)347 distance(double x1, double y1, double x2, double y2) {
348     return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
349 }
350 
351 static void
clear_click_queue(Window * w,int button)352 clear_click_queue(Window *w, int button) {
353     if (0 <= button && button <= (ssize_t)arraysz(w->click_queues)) w->click_queues[button].length = 0;
354 }
355 
356 #define N(n) (q->clicks[q->length - n])
357 
358 static bool
release_is_click(Window * w,int button)359 release_is_click(Window *w, int button) {
360     ClickQueue *q = &w->click_queues[button];
361     double click_allowed_radius = 1.2 * (global_state.callback_os_window ? global_state.callback_os_window->fonts_data->cell_height : 20);
362     monotonic_t now = monotonic();
363     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));
364 }
365 
366 static unsigned
multi_click_count(Window * w,int button)367 multi_click_count(Window *w, int button) {
368     ClickQueue *q = &w->click_queues[button];
369     double multi_click_allowed_radius = 1.2 * (global_state.callback_os_window ? global_state.callback_os_window->fonts_data->cell_height : 20);
370     if (q->length > 2) {
371         // possible triple-click
372         if (
373                 N(1).at - N(3).at <= 2 * OPT(click_interval) &&
374                 distance(N(1).x, N(1).y, N(3).x, N(3).y) <= multi_click_allowed_radius
375            ) return 3;
376     }
377     if (q->length > 1) {
378         // possible double-click
379         if (
380                 N(1).at - N(2).at <= OPT(click_interval) &&
381                 distance(N(1).x, N(1).y, N(2).x, N(2).y) <= multi_click_allowed_radius
382            ) return 2;
383     }
384     return q->length ? 1 : 0;
385 }
386 
387 
388 static void
add_press(Window * w,int button,int modifiers)389 add_press(Window *w, int button, int modifiers) {
390     if (button < 0 || button > (ssize_t)arraysz(w->click_queues)) return;
391     modifiers &= ~GLFW_LOCK_MASK;
392     ClickQueue *q = &w->click_queues[button];
393     if (q->length == CLICK_QUEUE_SZ) { memmove(q->clicks, q->clicks + 1, sizeof(Click) * (CLICK_QUEUE_SZ - 1)); q->length--; }
394     monotonic_t now = monotonic();
395     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;
396     q->length++;
397     Screen *screen = w->render_data.screen;
398     int count = multi_click_count(w, button);
399     if (count > 1) {
400         if (screen) dispatch_mouse_event(w, button, count, modifiers, screen->modes.mouse_tracking_mode != 0);
401         if (count > 2) q->length = 0;
402     }
403 }
404 #undef N
405 
406 void
mouse_open_url(Window * w)407 mouse_open_url(Window *w) {
408     Screen *screen = w->render_data.screen;
409     detect_url(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y);
410     screen_open_url(screen);
411 }
412 
413 typedef struct PendingClick {
414     id_type window_id;
415     int button, count, modifiers;
416     bool grabbed;
417     monotonic_t at;
418 } PendingClick;
419 
420 static void
free_pending_click(id_type timer_id UNUSED,void * pc)421 free_pending_click(id_type timer_id UNUSED, void *pc) { free(pc); }
422 
423 void
send_pending_click_to_window(Window * w,void * data)424 send_pending_click_to_window(Window *w, void *data) {
425     PendingClick *pc = (PendingClick*)data;
426     ClickQueue *q = &w->click_queues[pc->button];
427     // only send click if no presses have happened since the release that triggered the click
428     if (q->length && q->clicks[q->length - 1].at <= pc->at) {
429         dispatch_mouse_event(w, pc->button, pc->count, pc->modifiers, pc->grabbed);
430     }
431 }
432 
433 static void
dispatch_possible_click(Window * w,int button,int modifiers)434 dispatch_possible_click(Window *w, int button, int modifiers) {
435     Screen *screen = w->render_data.screen;
436     int count = multi_click_count(w, button);
437     if (release_is_click(w, button)) {
438         PendingClick *pc = calloc(sizeof(PendingClick), 1);
439         if (pc) {
440             pc->window_id = w->id;
441             pc->at = monotonic();
442             pc->button = button;
443             pc->count = count == 2 ? -3 : -2;
444             pc->modifiers = modifiers;
445             pc->grabbed = screen->modes.mouse_tracking_mode != 0;
446             add_main_loop_timer(OPT(click_interval), false, send_pending_click_to_window_id, pc, free_pending_click);
447         }
448     }
449 }
450 
HANDLER(handle_button_event)451 HANDLER(handle_button_event) {
452     modifiers &= ~GLFW_LOCK_MASK;
453     Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
454     bool is_release = !global_state.callback_os_window->mouse_button_pressed[button];
455     if (window_idx != t->active_window && !is_release) {
456         call_boss(switch_focus_to, "K", t->windows[window_idx].id);
457     }
458     Screen *screen = w->render_data.screen;
459     if (!screen) return;
460     if (!dispatch_mouse_event(w, button, is_release ? -1 : 1, modifiers, screen->modes.mouse_tracking_mode != 0)) {
461         if (screen->modes.mouse_tracking_mode != 0) {
462             int sz = encode_mouse_button(w, button, is_release ? RELEASE : PRESS, modifiers);
463             if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, CSI, mouse_event_buf); }
464         }
465     }
466     if (is_release) dispatch_possible_click(w, button, modifiers);
467     else add_press(w, button, modifiers);
468 }
469 
470 static int
currently_pressed_button(void)471 currently_pressed_button(void) {
472     for (int i = 0; i <= GLFW_MOUSE_BUTTON_LAST; i++) {
473         if (global_state.callback_os_window->mouse_button_pressed[i]) return i;
474     }
475     return -1;
476 }
477 
HANDLER(handle_event)478 HANDLER(handle_event) {
479     modifiers &= ~GLFW_LOCK_MASK;
480     if (button == -1) {
481         button = currently_pressed_button();
482         handle_move_event(w, button, modifiers, window_idx);
483     } else {
484         handle_button_event(w, button, modifiers, window_idx);
485     }
486 }
487 
488 static void
handle_tab_bar_mouse(int button,int UNUSED modifiers)489 handle_tab_bar_mouse(int button, int UNUSED modifiers) {
490     static monotonic_t last_click_at = 0;
491     if (button != GLFW_MOUSE_BUTTON_LEFT || !global_state.callback_os_window->mouse_button_pressed[button]) return;
492     monotonic_t now = monotonic();
493     bool is_double = now - last_click_at <= OPT(click_interval);
494     last_click_at = is_double ? 0 : now;
495     call_boss(activate_tab_at, "KdO", global_state.callback_os_window->id, global_state.callback_os_window->mouse_x, is_double ? Py_True : Py_False);
496 }
497 
498 static bool
mouse_in_region(Region * r)499 mouse_in_region(Region *r) {
500     if (r->left == r->right) return false;
501     if (global_state.callback_os_window->mouse_y < r->top || global_state.callback_os_window->mouse_y > r->bottom) return false;
502     if (global_state.callback_os_window->mouse_x < r->left || global_state.callback_os_window->mouse_x > r->right) return false;
503     return true;
504 }
505 
506 static Window*
window_for_id(id_type window_id)507 window_for_id(id_type window_id) {
508     Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
509     for (unsigned int i = 0; i < t->num_windows; i++) {
510         Window *w = t->windows + i;
511         if (w->id == window_id) return w;
512     }
513     return NULL;
514 }
515 
516 static Window*
window_for_event(unsigned int * window_idx,bool * in_tab_bar)517 window_for_event(unsigned int *window_idx, bool *in_tab_bar) {
518     Region central, tab_bar;
519     os_window_regions(global_state.callback_os_window, &central, &tab_bar);
520     *in_tab_bar = !mouse_in_region(&central);
521     if (!*in_tab_bar && global_state.callback_os_window->num_tabs > 0) {
522         Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
523         for (unsigned int i = 0; i < t->num_windows; i++) {
524             if (contains_mouse(t->windows + i) && t->windows[i].render_data.screen) {
525                 *window_idx = i;
526                 return t->windows + i;
527             }
528         }
529     }
530     return NULL;
531 }
532 
533 static Window*
closest_window_for_event(unsigned int * window_idx)534 closest_window_for_event(unsigned int *window_idx) {
535     Window *ans = NULL;
536     double closest_distance = UINT_MAX;
537     if (global_state.callback_os_window->num_tabs > 0) {
538         Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
539         for (unsigned int i = 0; i < t->num_windows; i++) {
540             Window *w = t->windows + i;
541             if (w->visible) {
542                 double d = distance_to_window(w);
543                 if (d < closest_distance) { ans = w; closest_distance = d; *window_idx = i; }
544             }
545         }
546     }
547     return ans;
548 }
549 
550 void
focus_in_event()551 focus_in_event() {
552     // Ensure that no URL is highlighted and the mouse cursor is in default shape
553     bool in_tab_bar;
554     unsigned int window_idx = 0;
555     mouse_cursor_shape = BEAM;
556     Window *w = window_for_event(&window_idx, &in_tab_bar);
557     if (w && w->render_data.screen) {
558         screen_mark_url(w->render_data.screen, 0, 0, 0, 0);
559         set_mouse_cursor_for_screen(w->render_data.screen);
560     }
561     set_mouse_cursor(mouse_cursor_shape);
562 }
563 
564 void
enter_event()565 enter_event() {
566 #ifdef __APPLE__
567     // On cocoa there is no way to configure the window manager to
568     // focus windows on mouse enter, so we do it ourselves
569     if (OPT(focus_follows_mouse) && !global_state.callback_os_window->is_focused) {
570         focus_os_window(global_state.callback_os_window, false);
571     }
572 #endif
573 }
574 
575 static void
end_drag(Window * w)576 end_drag(Window *w) {
577     Screen *screen = w->render_data.screen;
578     global_state.active_drag_in_window = 0;
579     global_state.active_drag_button = -1;
580     w->last_drag_scroll_at = 0;
581     if (screen->selections.in_progress) {
582         screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, (SelectionUpdate){.ended=true});
583     }
584 }
585 
586 typedef enum MouseSelectionType {
587     MOUSE_SELECTION_NORMAL,
588     MOUSE_SELECTION_EXTEND,
589     MOUSE_SELECTION_RECTANGLE,
590     MOUSE_SELECTION_WORD,
591     MOUSE_SELECTION_LINE,
592     MOUSE_SELECTION_LINE_FROM_POINT,
593     MOUSE_SELECTION_MOVE_END,
594 } MouseSelectionType;
595 
596 
597 void
mouse_selection(Window * w,int code,int button)598 mouse_selection(Window *w, int code, int button) {
599     global_state.active_drag_in_window = w->id;
600     global_state.active_drag_button = button;
601     Screen *screen = w->render_data.screen;
602     index_type start, end;
603     unsigned int y1, y2;
604 #define S(mode) {\
605         screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, mode); \
606         screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, (SelectionUpdate){.start_extended_selection=true}); }
607 
608     switch((MouseSelectionType)code) {
609         case MOUSE_SELECTION_NORMAL:
610             screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, EXTEND_CELL);
611             break;
612         case MOUSE_SELECTION_RECTANGLE:
613             screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, true, EXTEND_CELL);
614             break;
615         case MOUSE_SELECTION_WORD:
616             if (screen_selection_range_for_word(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, &y1, &y2, &start, &end, true)) S(EXTEND_WORD);
617             break;
618         case MOUSE_SELECTION_LINE:
619             if (screen_selection_range_for_line(screen, w->mouse_pos.cell_y, &start, &end)) S(EXTEND_LINE);
620             break;
621         case MOUSE_SELECTION_LINE_FROM_POINT:
622             if (screen_selection_range_for_line(screen, w->mouse_pos.cell_y, &start, &end) && end > w->mouse_pos.cell_x) S(EXTEND_LINE_FROM_POINT);
623             break;
624         case MOUSE_SELECTION_EXTEND:
625             extend_selection(w, false, true);
626             break;
627         case MOUSE_SELECTION_MOVE_END:
628             extend_selection(w, false, false);
629             break;
630     }
631     set_mouse_cursor_when_dragging();
632 #undef S
633 }
634 
635 
636 void
mouse_event(int button,int modifiers,int action)637 mouse_event(int button, int modifiers, int action) {
638     MouseShape old_cursor = mouse_cursor_shape;
639     bool in_tab_bar;
640     unsigned int window_idx = 0;
641     Window *w = NULL;
642     debug("%s mouse_button: %d %s", action == GLFW_RELEASE ? "\x1b[32mRelease\x1b[m" : (button < 0 ? "\x1b[36mMove\x1b[m" : "\x1b[31mPress\x1b[m"), button, format_mods(modifiers));
643     if (global_state.active_drag_in_window) {
644         if (button == -1) {  // drag move
645             w = window_for_id(global_state.active_drag_in_window);
646             if (w) {
647                 button = currently_pressed_button();
648                 if (button == global_state.active_drag_button) {
649                     clamp_to_window = true;
650                     Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
651                     for (window_idx = 0; window_idx < t->num_windows && t->windows[window_idx].id != w->id; window_idx++);
652                     handle_move_event(w, button, modifiers, window_idx);
653                     clamp_to_window = false;
654                     debug("handled as drag move\n");
655                     return;
656                 }
657             }
658         }
659         else if (action == GLFW_RELEASE && button == global_state.active_drag_button) {
660             w = window_for_id(global_state.active_drag_in_window);
661             if (w) {
662                 end_drag(w);
663                 debug("handled as drag end\n");
664                 dispatch_possible_click(w, button, modifiers);
665                 return;
666             }
667         }
668     }
669     w = window_for_event(&window_idx, &in_tab_bar);
670     if (in_tab_bar) {
671         mouse_cursor_shape = HAND;
672         handle_tab_bar_mouse(button, modifiers);
673         debug("handled by tab bar\n");
674     } else if (w) {
675         debug("grabbed: %d\n", w->render_data.screen->modes.mouse_tracking_mode != 0);
676         handle_event(w, button, modifiers, window_idx);
677     } else if (button == GLFW_MOUSE_BUTTON_LEFT && global_state.callback_os_window->mouse_button_pressed[button]) {
678         // initial click, clamp it to the closest window
679         w = closest_window_for_event(&window_idx);
680         if (w) {
681             clamp_to_window = true;
682             debug("grabbed: %d\n", w->render_data.screen->modes.mouse_tracking_mode != 0);
683             handle_event(w, button, modifiers, window_idx);
684             clamp_to_window = false;
685         } else debug("no window for event\n");
686     } else debug("\n");
687     if (mouse_cursor_shape != old_cursor) {
688         set_mouse_cursor(mouse_cursor_shape);
689     }
690 }
691 
692 void
scroll_event(double UNUSED xoffset,double yoffset,int flags,int modifiers)693 scroll_event(double UNUSED xoffset, double yoffset, int flags, int modifiers) {
694     bool in_tab_bar;
695     static id_type window_for_momentum_scroll = 0;
696     static bool main_screen_for_momentum_scroll = false;
697     unsigned int window_idx = 0;
698     Window *w = window_for_event(&window_idx, &in_tab_bar);
699     if (!w && !in_tab_bar) {
700         // allow scroll events even if window is not currently focused (in
701         // which case on some platforms such as macOS the mouse location is zeroed so
702         // window_for_event() does not work).
703         Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
704         if (t) w = t->windows + t->active_window;
705     }
706     if (!w) return;
707     Screen *screen = w->render_data.screen;
708 
709     enum MomentumData { NoMomentumData, MomentumPhaseBegan, MomentumPhaseStationary, MomentumPhaseActive, MomentumPhaseEnded, MomentumPhaseCancelled, MomentumPhaseMayBegin };
710     enum MomentumData momentum_data = (flags >> 1) & 7;
711 
712     switch(momentum_data) {
713         case NoMomentumData:
714             break;
715         case MomentumPhaseBegan:
716             window_for_momentum_scroll = w->id;
717             main_screen_for_momentum_scroll = screen->linebuf == screen->main_linebuf;
718             break;
719         case MomentumPhaseStationary:
720         case MomentumPhaseActive:
721             if (window_for_momentum_scroll != w->id || main_screen_for_momentum_scroll != (screen->linebuf == screen->main_linebuf)) return;
722             break;
723         case MomentumPhaseEnded:
724         case MomentumPhaseCancelled:
725             window_for_momentum_scroll = 0;
726             break;
727         case MomentumPhaseMayBegin:
728         default:
729             break;
730     }
731     if (yoffset == 0.0) return;
732 
733     int s;
734     bool is_high_resolution = flags & 1;
735 
736     if (is_high_resolution) {
737         yoffset *= OPT(touch_scroll_multiplier);
738         if (yoffset * screen->pending_scroll_pixels < 0) {
739             screen->pending_scroll_pixels = 0;  // change of direction
740         }
741         double pixels = screen->pending_scroll_pixels + yoffset;
742         if (fabs(pixels) < global_state.callback_os_window->fonts_data->cell_height) {
743             screen->pending_scroll_pixels = pixels;
744             return;
745         }
746         s = (int)round(pixels) / (int)global_state.callback_os_window->fonts_data->cell_height;
747         screen->pending_scroll_pixels = pixels - s * (int) global_state.callback_os_window->fonts_data->cell_height;
748     } else {
749         if (!screen->modes.mouse_tracking_mode) {
750             // Dont use multiplier if we are sending events to the application
751             yoffset *= OPT(wheel_scroll_multiplier);
752         } else if (OPT(wheel_scroll_multiplier) < 0) {
753             // ensure that changing scroll direction still works, even though
754             // we are not using wheel_scroll_multiplier
755             yoffset *= -1;
756         }
757         s = (int) round(yoffset);
758         // apparently on cocoa some mice generate really small yoffset values
759         // when scrolling slowly https://github.com/kovidgoyal/kitty/issues/1238
760         if (s == 0 && yoffset != 0) s = yoffset > 0 ? 1 : -1;
761         screen->pending_scroll_pixels = 0;
762     }
763     if (s == 0) return;
764     bool upwards = s > 0;
765     if (screen->modes.mouse_tracking_mode) {
766         int sz = encode_mouse_scroll(w, upwards, modifiers);
767         if (sz > 0) {
768             mouse_event_buf[sz] = 0;
769             for (s = abs(s); s > 0; s--) {
770                 write_escape_code_to_child(screen, CSI, mouse_event_buf);
771             }
772         }
773     } else {
774         if (screen->linebuf == screen->main_linebuf) screen_history_scroll(screen, abs(s), upwards);
775         else fake_scroll(w, abs(s), upwards);
776     }
777 }
778 
779 static PyObject*
send_mouse_event(PyObject * self UNUSED,PyObject * args)780 send_mouse_event(PyObject *self UNUSED, PyObject *args) {
781     Screen *screen;
782     unsigned int x, y;
783     int button, action, mods;
784     if (!PyArg_ParseTuple(args, "O!IIiii", &Screen_Type, &screen, &x, &y, &button, &action, &mods)) return NULL;
785 
786     MouseTrackingMode mode = screen->modes.mouse_tracking_mode;
787     if (mode == ANY_MODE || (mode == MOTION_MODE && action != MOVE) || (mode == BUTTON_MODE && (action == PRESS || action == RELEASE))) {
788         int sz = encode_mouse_event_impl(x + 1, y + 1, screen->modes.mouse_tracking_protocol, button, action, mods);
789         if (sz > 0) {
790             mouse_event_buf[sz] = 0;
791             write_escape_code_to_child(screen, CSI, mouse_event_buf);
792             Py_RETURN_TRUE;
793         }
794     }
795     Py_RETURN_FALSE;
796 }
797 
798 static PyObject*
test_encode_mouse(PyObject * self UNUSED,PyObject * args)799 test_encode_mouse(PyObject *self UNUSED, PyObject *args) {
800     unsigned int x, y;
801     int mouse_tracking_protocol, button, action, mods;
802     if (!PyArg_ParseTuple(args, "IIiiii", &x, &y, &mouse_tracking_protocol, &button, &action, &mods)) return NULL;
803     int sz = encode_mouse_event_impl(x, y, mouse_tracking_protocol, button, action, mods);
804     return PyUnicode_FromStringAndSize(mouse_event_buf, sz);
805 }
806 
807 static PyObject*
mock_mouse_selection(PyObject * self UNUSED,PyObject * args)808 mock_mouse_selection(PyObject *self UNUSED, PyObject *args) {
809     PyObject *capsule;
810     int button, code;
811     if (!PyArg_ParseTuple(args, "O!ii", &PyCapsule_Type, &capsule, &button, &code)) return NULL;
812     Window *w = PyCapsule_GetPointer(capsule, "Window");
813     if (!w) return NULL;
814     mouse_selection(w, code, button);
815     Py_RETURN_NONE;
816 }
817 
818 static PyObject*
send_mock_mouse_event_to_window(PyObject * self UNUSED,PyObject * args)819 send_mock_mouse_event_to_window(PyObject *self UNUSED, PyObject *args) {
820     PyObject *capsule;
821     int button, modifiers, is_release, clear_clicks, in_left_half_of_cell;
822     unsigned int x, y;
823     if (!PyArg_ParseTuple(args, "O!iipIIpp", &PyCapsule_Type, &capsule, &button, &modifiers, &is_release, &x, &y, &clear_clicks, &in_left_half_of_cell)) return NULL;
824     Window *w = PyCapsule_GetPointer(capsule, "Window");
825     if (!w) return NULL;
826     if (clear_clicks) clear_click_queue(w, button);
827     bool mouse_cell_changed = x != w->mouse_pos.cell_x || y != w->mouse_pos.cell_y || w->mouse_pos.in_left_half_of_cell != in_left_half_of_cell;
828     w->mouse_pos.x = 10 * x; w->mouse_pos.y = 20 * y;
829     w->mouse_pos.cell_x = x; w->mouse_pos.cell_y = y;
830     w->mouse_pos.in_left_half_of_cell = in_left_half_of_cell;
831     static int last_button_pressed = GLFW_MOUSE_BUTTON_LEFT;
832     if (button < 0) {
833         if (button == -2) do_drag_scroll(w, true);
834         else if (button == -3) do_drag_scroll(w, false);
835         else handle_mouse_movement_in_kitty(w, last_button_pressed, mouse_cell_changed);
836     } else {
837         if (global_state.active_drag_in_window && is_release && button == global_state.active_drag_button) {
838             end_drag(w);
839         } else {
840             dispatch_mouse_event(w, button, is_release ? -1 : 1, modifiers, false);
841             if (!is_release) {
842                 last_button_pressed = button;
843                 add_press(w, button, modifiers);
844             }
845         }
846     }
847     Py_RETURN_NONE;
848 }
849 
850 static PyMethodDef module_methods[] = {
851     METHODB(send_mouse_event, METH_VARARGS),
852     METHODB(test_encode_mouse, METH_VARARGS),
853     METHODB(send_mock_mouse_event_to_window, METH_VARARGS),
854     METHODB(mock_mouse_selection, METH_VARARGS),
855     {NULL, NULL, 0, NULL}        /* Sentinel */
856 };
857 
858 bool
init_mouse(PyObject * module)859 init_mouse(PyObject *module) {
860     PyModule_AddIntMacro(module, PRESS);
861     PyModule_AddIntMacro(module, RELEASE);
862     PyModule_AddIntMacro(module, DRAG);
863     PyModule_AddIntMacro(module, MOVE);
864     PyModule_AddIntMacro(module, MOUSE_SELECTION_NORMAL);
865     PyModule_AddIntMacro(module, MOUSE_SELECTION_EXTEND);
866     PyModule_AddIntMacro(module, MOUSE_SELECTION_RECTANGLE);
867     PyModule_AddIntMacro(module, MOUSE_SELECTION_WORD);
868     PyModule_AddIntMacro(module, MOUSE_SELECTION_LINE);
869     PyModule_AddIntMacro(module, MOUSE_SELECTION_LINE_FROM_POINT);
870     PyModule_AddIntMacro(module, MOUSE_SELECTION_MOVE_END);
871     if (PyModule_AddFunctions(module, module_methods) != 0) return false;
872     return true;
873 }
874