From 8ad22e785990343a22932e51755c39683723cbdd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 22 Feb 2026 08:19:31 +0530 Subject: [PATCH] On Wayland detach even on drag cancel Wayland compositors contravene their own spec and make it impossible to distinguish between drop-but-not-accept and drag-cancel cases. https://gitlab.freedesktop.org/wayland/wayland/-/issues/140 --- glfw/wl_window.c | 28 +++++++++++++++++++--------- kitty/options/definition.py | 5 ++++- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 4b142d553..e4f4bde7a 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -3079,10 +3079,10 @@ GLFWAPI bool glfwWaylandBeep(GLFWwindow *handle) { // Drag source {{{ static void -drag_source_cancelled(void *data UNUSED, struct wl_data_source *source UNUSED) { +cancel_drag(GLFWDragEventType type) { _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); if (window) { - GLFWDragEvent ev = {.type=GLFW_DRAG_CANCELLED}; + GLFWDragEvent ev = {.type=type}; _glfwInputDragSourceRequest(window, &ev); } _glfwFreeDragSourceData(); @@ -3120,7 +3120,7 @@ send_drag_data(_GLFWwindow *window, size_t i) { bool has_preset_data = _glfw.drag.items[i].data_size > 0; #define on_fail _glfwInputError(\ GLFW_PLATFORM_ERROR, "Wayland: failed to write drag source data to pipe with error: %s", strerror(errno)); \ - drag_source_cancelled(NULL, NULL) + cancel_drag(GLFW_DRAG_CANCELLED); if (dr.sz > dr.offset) { ret = write_as_much_as_possible(dr.fd, dr.pending_data + dr.offset, dr.sz - dr.offset); @@ -3150,7 +3150,7 @@ send_drag_data(_GLFWwindow *window, size_t i) { _glfwInputDragSourceRequest(window, &ev); if (ev.err_num) { if (ev.err_num == EAGAIN) { removeWatch(&_glfw.wl.eventLoopData, dr.watch_id); dr.watch_id = 0; } - else drag_source_cancelled(NULL, NULL); + else cancel_drag(GLFW_DRAG_CANCELLED); } else { if (ev.data_sz) { ret = write_as_much_as_possible(dr.fd, ev.data, ev.data_sz); @@ -3200,9 +3200,9 @@ _glfwPlatformDragDataReady(const char *mime_type) { } static void -drag_source_send(void *data, struct wl_data_source *source, const char *mime_type, int fd) { +drag_source_send(void *data UNUSED, struct wl_data_source *source UNUSED, const char *mime_type, int fd) { _GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id); -#define abort() safe_close(fd); drag_source_cancelled(data, source); return +#define abort() safe_close(fd); cancel_drag(GLFW_DRAG_CANCELLED); return if (!window) { abort(); } mime_type = _glfw_strdup(mime_type); if (!mime_type) { abort(); } @@ -3224,7 +3224,7 @@ drag_source_target(void *data UNUSED, struct wl_data_source *source UNUSED, cons if (window) { GLFWDragEvent ev = {.type=GLFW_DRAG_ACCEPTED, .mime_type=mime_type}; _glfwInputDragSourceRequest(window, &ev); - } else drag_source_cancelled(data, source); + } else cancel_drag(GLFW_DRAG_CANCELLED); } static void @@ -3240,7 +3240,7 @@ drag_source_action(void *data UNUSED, struct wl_data_source *source UNUSED, uint _glfw.wl.drag.action = op; GLFWDragEvent ev = {.type=GLFW_DRAG_ACTION_CHANGED, .action=op}; _glfwInputDragSourceRequest(window, &ev); - } else drag_source_cancelled(data, source); + } else cancel_drag(GLFW_DRAG_CANCELLED); } static void @@ -3249,7 +3249,7 @@ drag_source_dnd_drop_performed(void *data UNUSED, struct wl_data_source *source if (window) { GLFWDragEvent ev = {.type=GLFW_DRAG_DROPPED}; _glfwInputDragSourceRequest(window, &ev); - } else drag_source_cancelled(data, source); + } else cancel_drag(GLFW_DRAG_CANCELLED); } static void @@ -3262,6 +3262,16 @@ drag_source_dnd_finished(void *data UNUSED, struct wl_data_source *source UNUSED _glfwFreeDragSourceData(); } +static void +drag_source_cancelled(void *data UNUSED, struct wl_data_source *source UNUSED) { + // Uber competent Wayland people contravene their own spec and make it + // impossible to distinguish between drag cancelled and dropped but not + // accepted. https://gitlab.freedesktop.org/wayland/wayland/-/issues/140 + // so we assume this is a drop. Sigh. + cancel_drag(GLFW_DRAG_DROPPED); +} + + static const struct wl_data_source_listener drag_source_listener = { .send = drag_source_send, .cancelled = drag_source_cancelled, diff --git a/kitty/options/definition.py b/kitty/options/definition.py index f8521bcfe..1daff9be3 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -1704,7 +1704,10 @@ Control when dragging of tabs to re-order them happens. The value is the drag threshold in pixels, the distance the mouse must move before a drag begins. A value of zero disables tab dragging entirely. Dragging a tab to another kitty window moves it there, while dragging -outside any kitty window detaches it into a new OS window. +outside any kitty window detaches it into a new OS window. Note that on +Wayland, :link:`because of poor design +` drag and cancel +will still detach tabs. ''') egr() # }}}