From 111b35b6a7629c11c9d770709798b44cae4f84a1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 6 Mar 2026 11:18:33 +0530 Subject: [PATCH] Implement drop leaving window --- docs/dnd-protocol.rst | 10 ++++++++-- kitty/dnd.c | 25 ++++++++++++++++++++++--- kitty/dnd.h | 1 + kitty/glfw.c | 17 +++++++++++++++-- kitty/mouse.c | 2 ++ kitty/state.h | 2 +- 6 files changed, 49 insertions(+), 8 deletions(-) diff --git a/docs/dnd-protocol.rst b/docs/dnd-protocol.rst index 70459e6c1..487318881 100644 --- a/docs/dnd-protocol.rst +++ b/docs/dnd-protocol.rst @@ -63,6 +63,12 @@ list of MIME types that are available for dropping. To avoid overhead, the terminal should only send this list for the first move event and subsequently only if the list changes. +When the drag leaves the window, the terminal will send the same event but +with ``x, y = -1, -1`` to indicate that the drag has left the window. For such +events the list of MIME types must be empty. Note that the terminal must never +send negative cell co-ordinates for any other reason. + + Metadata reference --------------------------- @@ -85,8 +91,8 @@ Key Value Default Description have it set to the same value. **Keys for location** ----------------------------------------------------------- -``x`` Positive integer ``0`` Cell x-coordinate origin is 0, 0 at top left of screen -``y`` Positive integer ``0`` Cell y-coordinate origin is 0, 0 at top left of screen +``x`` Integer ``0`` Cell x-coordinate origin is 0, 0 at top left of screen +``y`` Integer ``0`` Cell y-coordinate origin is 0, 0 at top left of screen ``X`` Integer ``0`` Pixel x-coordinate origin is 0, 0 at top left of screen ``Y`` Integer ``0`` Pixel y-coordinate origin is 0, 0 at top left of screen ======= ==================== ========= ================= diff --git a/kitty/dnd.c b/kitty/dnd.c index 4e3e2b06f..be3e4d08a 100644 --- a/kitty/dnd.c +++ b/kitty/dnd.c @@ -44,11 +44,18 @@ string_arrays_cmp(const char **a, size_t an, const char **b, size_t bn) { } static size_t -send_payload_to_child(id_type id, const char *header, size_t header_sz, const char *data, size_t data_sz) { +send_payload_to_child(id_type id, const char *header, size_t header_sz, const char *data, const size_t data_sz) { size_t offset = 0; char buf[4096 + 1024]; memcpy(buf, header, header_sz); buf[header_sz++] = ':'; buf[header_sz++] = 'm'; buf[header_sz++] = '='; + if (!data_sz) { + buf[header_sz++] = 0x1b; buf[header_sz++] = '\\'; + bool found, too_much_data; + schedule_write_to_child_if_possible(id, buf, header_sz, &found, &too_much_data); + if (too_much_data) return 0; + return 1; + } while (offset < data_sz) { size_t chunk = data_sz - offset; size_t p = header_sz; @@ -79,7 +86,8 @@ flush_pending(id_type id, PendingData *pending) { } break; } else { - free(e->buf); + if (!e->data_sz && !written) break; + free(e->buf); zero_at_ptr(e); remove_i_from_array(pending->items, 0, pending->count); } } @@ -90,7 +98,7 @@ static void queue_payload_to_child(id_type id, PendingData *pending, const char *header, size_t header_sz, const char *data, size_t data_sz) { size_t offset = 0; if (flush_pending(id, pending)) offset = send_payload_to_child(id, header, header_sz, data, data_sz); - if (offset < data_sz) { + if (offset < data_sz || (!offset && !data_sz)) { ensure_space_for(pending, items, PendingEntry, pending->count + 1, capacity, 32, true); char *buf = malloc(header_sz + data_sz - offset); if (!buf) fatal("Out of memory"); @@ -145,3 +153,14 @@ drop_move_on_child(Window *w, const char** mimes, size_t num_mimes) { schedule_write_to_child_if_possible(w->id, buf, header_size, &found, &too_much_data); } } + +void +drop_left_child(Window *w) { + w->drop.hovered = false; + drop_free_offered_mimes(w); + if (w->drop.allowed) { + char buf[128]; + int header_size = snprintf(buf, sizeof(buf), "\x1b]%d;i=%u:t=m:x=-1:y=-1", DND_CODE, w->drop.client_id); + queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, NULL, 0); + } +} diff --git a/kitty/dnd.h b/kitty/dnd.h index 2b04664ea..59fc3ece8 100644 --- a/kitty/dnd.h +++ b/kitty/dnd.h @@ -9,4 +9,5 @@ #include "state.h" void drop_move_on_child(Window *w, const char **mimes, size_t num_mimes); +void drop_left_child(Window *w); void drop_free_data(Window *w); diff --git a/kitty/glfw.c b/kitty/glfw.c index 7d4d741c9..a6ad39a5e 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -796,13 +796,17 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) { if (!set_callback_window(window)) return; OSWindow *os_window = global_state.callback_os_window; Window *w = NULL; + bool is_kitty_ui_drag = false; + for (size_t i = 0; i < ev->num_mimes; i++) { + if (is_droppable_mime(ev->mimes[i]) >= TAB_DRAG_MIME_NUMBER) { is_kitty_ui_drag = true; break;} + } switch (ev->type) { case GLFW_DROP_ENTER: case GLFW_DROP_MOVE: os_window->last_drag_event.x = (int)(ev->xpos * os_window->viewport_x_ratio); os_window->last_drag_event.y = (int)(ev->ypos * os_window->viewport_y_ratio); on_mouse_position_update(ev->xpos, ev->ypos); - if (global_state.mouse_hover_in_window && (w = window_for_window_id(global_state.mouse_hover_in_window)) && w->drop.allowed) { + if (!is_kitty_ui_drag && global_state.mouse_hover_in_window && (w = window_for_window_id(global_state.mouse_hover_in_window)) && w->drop.allowed) { drop_move_on_child(w, ev->mimes, ev->num_mimes); return; } @@ -811,9 +815,18 @@ on_drop(GLFWwindow *window, GLFWDropEvent *ev) { ev->from_self ? Py_True : Py_False, Py_False); /* fallthrough */ case GLFW_DROP_STATUS_UPDATE: - update_allowed_mimes_for_drop(ev); + if (is_kitty_ui_drag) update_allowed_mimes_for_drop(ev); + else { + } break; case GLFW_DROP_LEAVE: + for (size_t tc = 0; tc < os_window->num_tabs; tc++) { + Tab *t = os_window->tabs + tc; + for (size_t i = 0; i < t->num_windows; i++) { + Window *w = t->windows + i; + if (w->drop.hovered) drop_left_child(w); + } + } call_boss(on_drop_move, "KiiOO", os_window->id, os_window->last_drag_event.x, os_window->last_drag_event.y, ev->from_self ? Py_True : Py_False, Py_True); diff --git a/kitty/mouse.c b/kitty/mouse.c index 9a72d2de9..a80375cd1 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -8,6 +8,7 @@ #include "state.h" #include "screen.h" #include "charsets.h" +#include "dnd.h" #include #include #include "glfw-wrapper.h" @@ -179,6 +180,7 @@ set_currently_hovered_window(id_type window_id, int modifiers) { if (left_window) { if (left_window->scrollbar.is_hovering) update_scrollbar_hover_state(left_window, false); if (left_window->render_data.screen) screen_mark_url(left_window->render_data.screen, 0, 0, 0, 0); + if (left_window->drop.hovered) drop_left_child(left_window); int sz = encode_mouse_event(left_window, 0, LEAVE, modifiers); if (sz > 0) { mouse_event_buf[sz] = 0; diff --git a/kitty/state.h b/kitty/state.h index b32202c1b..9320e690d 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -180,7 +180,7 @@ typedef struct ClickQueue { } ClickQueue; typedef struct MousePosition { - unsigned int cell_x, cell_y; + unsigned cell_x, cell_y; double global_x, global_y; bool in_left_half_of_cell; } MousePosition;