diff --git a/kitty/mouse.c b/kitty/mouse.c index d40b772ea..e962d893d 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -178,6 +178,20 @@ update_drag(bool from_button, Window *w, bool is_release, int modifiers) { } } +static inline bool +do_drag_scroll(Window *w, bool upwards) { + Screen *screen = w->render_data.screen; + if (screen->linebuf == screen->main_linebuf) { + screen_history_scroll(screen, SCROLL_LINE, upwards); + update_drag(false, w, false, 0); + if (mouse_cursor_shape != ARROW) { + mouse_cursor_shape = ARROW; + set_mouse_cursor(mouse_cursor_shape); + } + return true; + } + return false; +} bool drag_scroll(Window *w, OSWindow *frame) { @@ -185,15 +199,8 @@ drag_scroll(Window *w, OSWindow *frame) { double y = frame->mouse_y; bool upwards = y <= (w->geometry.top + margin); if (upwards || y >= w->geometry.bottom - margin) { - Screen *screen = w->render_data.screen; - if (screen->linebuf == screen->main_linebuf) { - screen_history_scroll(screen, SCROLL_LINE, upwards); - update_drag(false, w, false, 0); + if (do_drag_scroll(w, upwards)) { frame->last_mouse_activity_at = monotonic(); - if (mouse_cursor_shape != ARROW) { - mouse_cursor_shape = ARROW; - set_mouse_cursor(mouse_cursor_shape); - } return true; } } @@ -272,6 +279,19 @@ detect_url(Screen *screen, unsigned int x, unsigned int y) { } } +static inline void +handle_mouse_movement_in_kitty(Window *w, int button, unsigned int x, unsigned int y) { + Screen *screen = w->render_data.screen; + if (screen->selection.in_progress && button == GLFW_MOUSE_BUTTON_LEFT) { + monotonic_t now = monotonic(); + bool mouse_cell_changed = x != w->mouse_pos.cell_x || y != w->mouse_pos.cell_y; + if ((now - w->last_drag_scroll_at) >= ms_to_monotonic_t(20ll) || mouse_cell_changed) { + update_drag(false, w, false, 0); + w->last_drag_scroll_at = now; + } + } + +} HANDLER(handle_move_event) { unsigned int x = 0, y = 0; @@ -284,7 +304,6 @@ HANDLER(handle_move_event) { if (!cell_for_pos(w, &x, &y, global_state.callback_os_window)) return; Screen *screen = w->render_data.screen; detect_url(screen, x, y); - bool mouse_cell_changed = x != w->mouse_pos.cell_x || y != w->mouse_pos.cell_y; w->mouse_pos.cell_x = x; w->mouse_pos.cell_y = y; bool in_tracking_mode = ( screen->modes.mouse_tracking_mode == ANY_MODE || @@ -292,14 +311,9 @@ HANDLER(handle_move_event) { bool has_terminal_select_modifiers = modifiers == (int)OPT(terminal_select_modifiers) || modifiers == ((int)OPT(rectangle_select_modifiers) | (int)OPT(terminal_select_modifiers)); bool handle_in_kitty = !in_tracking_mode || has_terminal_select_modifiers; if (handle_in_kitty) { - if (screen->selection.in_progress && button == GLFW_MOUSE_BUTTON_LEFT) { - monotonic_t now = monotonic(); - if ((now - w->last_drag_scroll_at) >= ms_to_monotonic_t(20ll) || mouse_cell_changed) { - update_drag(false, w, false, 0); - w->last_drag_scroll_at = now; - } - } + handle_mouse_movement_in_kitty(w, button, x, y); } else { + bool mouse_cell_changed = x != w->mouse_pos.cell_x || y != w->mouse_pos.cell_y; if (!mouse_cell_changed) return; int sz = encode_mouse_event(w, MAX(0, button), button >=0 ? DRAG : MOVE, 0); if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, CSI, mouse_event_buf); } @@ -374,6 +388,24 @@ open_url(Window *w) { screen_open_url(screen); } +static inline void +handle_button_event_in_kitty(Window *w, int button, int modifiers, bool is_release) { + switch(button) { + case GLFW_MOUSE_BUTTON_LEFT: + update_drag(true, w, is_release, modifiers); + if (is_release) { + if (modifiers == (int)OPT(open_url_modifiers)) open_url(w); + } else add_click(w, button, modifiers, 0); + break; + case GLFW_MOUSE_BUTTON_MIDDLE: + if (is_release && !modifiers) { call_boss(paste_from_selection, NULL); return; } + break; + case GLFW_MOUSE_BUTTON_RIGHT: + if (is_release) { extend_selection(w); } + break; + } +} + HANDLER(handle_button_event) { Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; bool is_release = !global_state.callback_os_window->mouse_button_pressed[button]; @@ -388,22 +420,8 @@ HANDLER(handle_button_event) { button == GLFW_MOUSE_BUTTON_MIDDLE || (modifiers == (int)OPT(open_url_modifiers) && button == GLFW_MOUSE_BUTTON_LEFT) ); - if (handle_in_kitty) { - switch(button) { - case GLFW_MOUSE_BUTTON_LEFT: - update_drag(true, w, is_release, modifiers); - if (is_release) { - if (modifiers == (int)OPT(open_url_modifiers)) open_url(w); - } else add_click(w, button, modifiers, window_idx); - break; - case GLFW_MOUSE_BUTTON_MIDDLE: - if (is_release && !modifiers) { call_boss(paste_from_selection, NULL); return; } - break; - case GLFW_MOUSE_BUTTON_RIGHT: - if (is_release) { extend_selection(w); } - break; - } - } else { + if (handle_in_kitty) handle_button_event_in_kitty(w, button, modifiers, is_release); + else { int sz = encode_mouse_event(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); } } @@ -663,8 +681,25 @@ test_encode_mouse(PyObject *self UNUSED, PyObject *args) { return PyUnicode_FromStringAndSize(mouse_event_buf, sz); } +static PyObject* +send_mock_mouse_event_to_window(PyObject *self UNUSED, PyObject *args) { + PyObject *capsule; + int button, modifiers, is_release, x, y; + if (!PyArg_ParseTuple(args, "O!iipii", &PyCapsule_Type, &capsule, &button, &modifiers, &is_release, &x, &y)) return NULL; + Window *w = PyCapsule_GetPointer(capsule, "Window"); + if (!w) return NULL; + if (button == -1) { + if (y < 0) do_drag_scroll(w, true); + else if ((unsigned int)y >= w->render_data.screen->lines) do_drag_scroll(w, false); + else handle_mouse_movement_in_kitty(w, GLFW_MOUSE_BUTTON_LEFT, x, y); + } + else handle_button_event_in_kitty(w, button, modifiers, is_release); + Py_RETURN_NONE; +} + static PyMethodDef module_methods[] = { METHODB(test_encode_mouse, METH_VARARGS), + METHODB(send_mock_mouse_event_to_window, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/state.c b/kitty/state.c index 6e5e5ea45..a72500d4b 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -162,6 +162,18 @@ release_gpu_resources_for_window(Window *w) { w->render_data.gvao_idx = -1; } +static inline void +initialize_window(Window *w, PyObject *title, bool init_gpu_resources) { + w->id = ++global_state.window_id_counter; + w->visible = true; + w->title = title; + Py_XINCREF(title); + if (init_gpu_resources) create_gpu_resources_for_window(w); + else { + w->render_data.vao_idx = -1; + w->render_data.gvao_idx = -1; + } +} static inline id_type add_window(id_type os_window_id, id_type tab_id, PyObject *title) { @@ -169,11 +181,7 @@ add_window(id_type os_window_id, id_type tab_id, PyObject *title) { ensure_space_for(tab, windows, Window, tab->num_windows + 1, capacity, 1, true); make_os_window_context_current(osw); zero_at_i(tab->windows, tab->num_windows); - tab->windows[tab->num_windows].id = ++global_state.window_id_counter; - tab->windows[tab->num_windows].visible = true; - tab->windows[tab->num_windows].title = title; - create_gpu_resources_for_window(&tab->windows[tab->num_windows]); - Py_INCREF(tab->windows[tab->num_windows].title); + initialize_window(tab->windows + tab->num_windows, title, true); return tab->windows[tab->num_windows++].id; END_WITH_TAB; return 0; @@ -964,6 +972,31 @@ PYWRAP0(destroy_global_data) { Py_RETURN_NONE; } +static void +destroy_mock_window(PyObject *capsule) { + Window *w = PyCapsule_GetPointer(capsule, "Window"); + if (w) { + destroy_window(w); + PyMem_Del(w); + } +} + +static PyObject* +pycreate_mock_window(PyObject *self UNUSED, PyObject *args) { + Screen *screen; + PyObject *title = NULL; + if (!PyArg_ParseTuple(args, "O|U", &screen, &title)) return NULL; + Window *w = PyMem_New(Window, 1); + if (!w) return NULL; + Py_INCREF(screen); + PyObject *ans = PyCapsule_New(w, "Window", destroy_mock_window); + if (ans != NULL) { + initialize_window(w, title, false); + w->render_data.screen = screen; + } + return ans; +} + THREE_ID_OBJ(update_window_title) THREE_ID(remove_window) THREE_ID(detach_window) @@ -1019,6 +1052,7 @@ static PyMethodDef module_methods[] = { MW(os_window_font_size, METH_VARARGS), MW(set_boss, METH_O), MW(patch_global_colors, METH_VARARGS), + MW(create_mock_window, METH_VARARGS), MW(destroy_global_data, METH_NOARGS), {NULL, NULL, 0, NULL} /* Sentinel */