From 2b48637c7e93b5fa038d246135d68d6fba2f2bc0 Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Fri, 12 Dec 2025 21:07:06 +1300 Subject: [PATCH] seatop_default: skip hit-testing on dragged toplevel Per the xdg-toplevel-drag protocol: "The attached window does not participate in the selection of the drag target." When the cursor is over the dragged toplevel, temporarily disable its scene node and perform a second hit test to find the surface underneath. This allows drop targets to receive pointer focus while dragging. Also maintain pointer focus on the drag origin surface when the cursor moves outside all windows, preventing drag motion from stopping. --- sway/input/seatop_default.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/sway/input/seatop_default.c b/sway/input/seatop_default.c index df8232afc..428ab0a4b 100644 --- a/sway/input/seatop_default.c +++ b/sway/input/seatop_default.c @@ -1,9 +1,13 @@ #include #include +#include #include +#include #include #include #include +#include +#include #include "gesture.h" #include "sway/desktop/transaction.h" #include "sway/input/cursor.h" @@ -612,11 +616,44 @@ static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) { check_focus_follows_mouse(seat, e, node); } + // Check for active xdg-toplevel-drag + struct wlr_drag *wlr_drag = seat->wlr_seat->drag; + struct sway_drag *drag = wlr_drag ? wlr_drag->data : NULL; + bool toplevel_drag_active = drag && drag->toplevel_drag && drag->origin; + + // Per xdg-toplevel-drag protocol: "The attached window does not participate + // in the selection of the drag target." If cursor is over the dragged + // toplevel, do a second hit test with it disabled to find what's underneath. + // Use seat->toplevel_drag_container which is safely managed by the motion handler. + struct wlr_scene_tree *disabled_tree = NULL; + struct sway_container *dragged_con = seat->toplevel_drag_container; + if (toplevel_drag_active && surface != NULL && dragged_con != NULL && + dragged_con->view != NULL && dragged_con->view->surface != NULL) { + // Check if cursor is over the dragged container's surface + if (dragged_con->view->surface == wlr_surface_get_root_surface(surface)) { + // Cursor is over the dragged toplevel - find what's underneath + if (dragged_con->scene_tree != NULL) { + disabled_tree = dragged_con->scene_tree; + wlr_scene_node_set_enabled(&disabled_tree->node, false); + node = node_at_coords(seat, cursor->cursor->x, cursor->cursor->y, + &surface, &sx, &sy); + } + } + } + // Re-enable after all processing that might destroy the container + if (disabled_tree != NULL) { + wlr_scene_node_set_enabled(&disabled_tree->node, true); + } + if (surface) { if (seat_is_input_allowed(seat, surface)) { wlr_seat_pointer_notify_enter(seat->wlr_seat, surface, sx, sy); wlr_seat_pointer_notify_motion(seat->wlr_seat, time_msec, sx, sy); } + } else if (toplevel_drag_active) { + // Cursor is outside any window - keep focus on origin surface + wlr_seat_pointer_notify_enter(seat->wlr_seat, drag->origin, 0, 0); + wlr_seat_pointer_notify_motion(seat->wlr_seat, time_msec, 0, 0); } else { cursor_update_image(cursor, node); wlr_seat_pointer_notify_clear_focus(seat->wlr_seat);