From bad8a87f4c66638a1ffd1b0bf39e9788a3e59ed3 Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Fri, 12 Dec 2025 16:51:20 +1300 Subject: [PATCH 1/8] server: add xdg-toplevel-drag-v1 manager Add the xdg-toplevel-drag-v1 protocol manager to the server. This protocol allows clients to attach a toplevel window to an ongoing drag-and-drop operation, enabling features like browser tab tear-off. --- include/sway/server.h | 2 ++ sway/server.c | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/include/sway/server.h b/include/sway/server.h index f50d48f05..51ab28a15 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -123,6 +123,8 @@ struct sway_server { struct wl_listener request_set_cursor_shape; + struct wlr_xdg_toplevel_drag_manager_v1 *xdg_toplevel_drag_manager; + struct wlr_tearing_control_manager_v1 *tearing_control_v1; struct wl_listener tearing_control_new_object; struct wl_list tearing_controllers; // sway_tearing_controller::link diff --git a/sway/server.c b/sway/server.c index 7f399a2bb..473671cf8 100644 --- a/sway/server.c +++ b/sway/server.c @@ -51,6 +51,7 @@ #include #include #include +#include #include #include #include "config.h" @@ -450,6 +451,9 @@ bool server_init(struct sway_server *server) { wl_signal_add(&xdg_toplevel_tag_manager_v1->events.set_tag, &server->xdg_toplevel_tag_manager_v1_set_tag); + server->xdg_toplevel_drag_manager = + wlr_xdg_toplevel_drag_manager_v1_create(server->wl_display, 1); + struct wlr_cursor_shape_manager_v1 *cursor_shape_manager = wlr_cursor_shape_manager_v1_create(server->wl_display, 1); server->request_set_cursor_shape.notify = handle_request_set_cursor_shape; From c65b7477b5eff1cae0bcd9c5d64d6f06969bb5a0 Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Fri, 12 Dec 2025 21:01:55 +1300 Subject: [PATCH 2/8] input/seat: track toplevel drag and move container during drag Add support for tracking xdg-toplevel-drag operations and moving the attached container during drag motion events. Implementation detail: - Track the dragged surface ourselves rather than trusting wlroots' toplevel pointer, which may not be NULLed promptly during destruction. This mirrors Mutter's approach with dragged_surface. - Use find_xdg_view_with_toplevel_drag() which searches through sway's views and uses wlroots' wlr_xdg_toplevel_drag_v1_from_wlr_xdg_toplevel() for safe comparison, avoiding stale pointer access. There could be a better way to do this. - Account for XDG surface geometry offset when positioning the container. - Add container_floating_update_scene_position() helper that updates the scene graph position immediately, bypassing the transaction system's batching for smoother visual feedback during drag operations. - Clean up container tracking in seatop_unref when container is destroyed. --- include/sway/input/seat.h | 20 +++++ include/sway/tree/container.h | 6 ++ sway/input/seat.c | 141 ++++++++++++++++++++++++++++++++++ sway/tree/container.c | 11 +++ 4 files changed, 178 insertions(+) diff --git a/include/sway/input/seat.h b/include/sway/input/seat.h index 428f96796..7a055600d 100644 --- a/include/sway/input/seat.h +++ b/include/sway/input/seat.h @@ -74,10 +74,22 @@ struct sway_seat_node { struct wl_listener destroy; }; +struct wlr_xdg_toplevel_drag_v1; + struct sway_drag { struct sway_seat *seat; struct wlr_drag *wlr_drag; + struct wlr_xdg_toplevel_drag_v1 *toplevel_drag; // may be NULL + struct wlr_surface *origin; // surface where drag started + + // For xdg-toplevel-drag: track the dragged surface ourselves rather than + // trusting wlroots' toplevel pointer, which may not be NULLed promptly + // during destruction sequences. This mirrors Mutter's approach. + struct wlr_surface *toplevel_surface; // the attached toplevel's surface + struct wl_listener toplevel_surface_destroy; + struct wl_listener destroy; + struct wl_listener motion; }; struct sway_seat { @@ -93,6 +105,14 @@ struct sway_seat { struct wlr_scene_tree *scene_tree; struct wlr_scene_tree *drag_icons; + // Container being dragged via xdg-toplevel-drag protocol. + // This container is skipped during hit testing so that + // drop targets underneath can receive pointer focus. + struct sway_container *toplevel_drag_container; + + // Origin surface of pending drag (set in request_start_drag, used in start_drag) + struct wlr_surface *pending_drag_origin; + bool has_focus; struct wl_list focus_stack; // list of containers in focus order struct sway_workspace *workspace; diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h index e18fd00ac..bcb34fd8d 100644 --- a/include/sway/tree/container.h +++ b/include/sway/tree/container.h @@ -233,6 +233,12 @@ void container_get_box(struct sway_container *container, struct wlr_box *box); void container_floating_translate(struct sway_container *con, double x_amount, double y_amount); +/** + * Update scene graph position immediately for a floating container. + * Used during drag operations for smoother visual feedback. + */ +void container_floating_update_scene_position(struct sway_container *con); + /** * Choose an output for the floating container's new position. */ diff --git a/sway/input/seat.c b/sway/input/seat.c index 1b63f625b..3c51089cd 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include "config.h" #include "list.h" #include "log.h" @@ -386,12 +388,123 @@ void drag_icons_update_position(struct sway_seat *seat) { } } +static void toplevel_drag_surface_handle_destroy(struct wl_listener *listener, + void *data) { + struct sway_drag *drag = wl_container_of(listener, drag, toplevel_surface_destroy); + // Surface is being destroyed - clear our tracking. This is analogous to + // Mutter's on_dragged_window_unmanaging callback. + wl_list_remove(&drag->toplevel_surface_destroy.link); + wl_list_init(&drag->toplevel_surface_destroy.link); + drag->toplevel_surface = NULL; + drag->seat->toplevel_drag_container = NULL; +} + +// Find a floating XDG view that has a toplevel drag attached to it. +// This is safer than accessing toplevel_drag->toplevel which may be stale. +static struct sway_view *find_xdg_view_with_toplevel_drag( + struct wlr_xdg_toplevel_drag_v1 *toplevel_drag) { + // Search all workspaces for floating containers + for (int i = 0; i < root->outputs->length; i++) { + struct sway_output *output = root->outputs->items[i]; + for (int j = 0; j < output->workspaces->length; j++) { + struct sway_workspace *ws = output->workspaces->items[j]; + for (int k = 0; k < ws->floating->length; k++) { + struct sway_container *con = ws->floating->items[k]; + if (con->view == NULL || con->view->type != SWAY_VIEW_XDG_SHELL) { + continue; + } + struct wlr_xdg_toplevel *wlr_toplevel = con->view->wlr_xdg_toplevel; + if (wlr_toplevel == NULL) { + continue; + } + // Use wlroots' safe lookup function to check if this toplevel + // has the drag attached + struct wlr_xdg_toplevel_drag_v1 *found = + wlr_xdg_toplevel_drag_v1_from_wlr_xdg_toplevel( + server.xdg_toplevel_drag_manager, + wlr_toplevel); + if (found == toplevel_drag) { + return con->view; + } + } + } + } + return NULL; +} + +static void toplevel_drag_handle_motion(struct wl_listener *listener, void *data) { + struct sway_drag *drag = wl_container_of(listener, drag, motion); + struct wlr_xdg_toplevel_drag_v1 *toplevel_drag = drag->toplevel_drag; + struct sway_seat *seat = drag->seat; + + if (toplevel_drag == NULL) { + seat->toplevel_drag_container = NULL; + return; + } + + // If we have a tracked surface being dragged, move its container. + // We track the surface ourselves rather than trusting wlroots' toplevel + // pointer, which may not be NULLed promptly during destruction. + if (drag->toplevel_surface != NULL) { + struct sway_container *con = seat->toplevel_drag_container; + // Verify container is still valid and floating + if (con != NULL && con->view != NULL && con->view->surface != NULL && + container_is_floating(con)) { + // Account for XDG surface geometry offset. The protocol's + // x_offset/y_offset are in surface coordinates, but container + // position is relative to window content. + struct wlr_box *geo = &con->view->geometry; + double x = seat->cursor->cursor->x - toplevel_drag->x_offset - geo->x; + double y = seat->cursor->cursor->y - toplevel_drag->y_offset - geo->y; + container_floating_move_to(con, x, y); + // Update scene position immediately for smooth visual feedback. + container_floating_update_scene_position(con); + } + return; + } + + // No surface tracked yet - search for a floating XDG view that has this + // toplevel drag attached. This avoids accessing toplevel_drag->toplevel + // which may point to freed memory. + struct sway_view *view = find_xdg_view_with_toplevel_drag(toplevel_drag); + if (view == NULL || view->container == NULL) { + return; + } + + if (!container_is_floating(view->container)) { + // During tearout phase (not yet floating), don't skip hit-testing. + return; + } + + struct wlr_surface *surface = view->surface; + if (surface == NULL || !surface->mapped) { + return; + } + + // Start tracking this surface and container for movement. + // Listen for surface destruction so we can clear our pointer. + drag->toplevel_surface = surface; + drag->toplevel_surface_destroy.notify = toplevel_drag_surface_handle_destroy; + wl_signal_add(&surface->events.destroy, &drag->toplevel_surface_destroy); + seat->toplevel_drag_container = view->container; + + // Account for XDG surface geometry offset + struct wlr_box *geo = &view->geometry; + double x = seat->cursor->cursor->x - toplevel_drag->x_offset - geo->x; + double y = seat->cursor->cursor->y - toplevel_drag->y_offset - geo->y; + container_floating_move_to(view->container, x, y); + container_floating_update_scene_position(view->container); +} + static void drag_handle_destroy(struct wl_listener *listener, void *data) { struct sway_drag *drag = wl_container_of(listener, drag, destroy); // Focus enter isn't sent during drag, so refocus the focused node, layer // surface or unmanaged surface. struct sway_seat *seat = drag->seat; + + // Clear toplevel drag container tracking + seat->toplevel_drag_container = NULL; struct sway_node *focus = seat_get_focus(seat); if (focus) { seat_set_focus(seat, NULL); @@ -408,6 +521,13 @@ static void drag_handle_destroy(struct wl_listener *listener, void *data) { drag->wlr_drag->data = NULL; wl_list_remove(&drag->destroy.link); + if (drag->toplevel_drag != NULL) { + wl_list_remove(&drag->motion.link); + // Clean up our surface tracking listener if active + if (drag->toplevel_surface != NULL) { + wl_list_remove(&drag->toplevel_surface_destroy.link); + } + } free(drag); } @@ -418,6 +538,7 @@ static void handle_request_start_drag(struct wl_listener *listener, if (wlr_seat_validate_pointer_grab_serial(seat->wlr_seat, event->origin, event->serial)) { + seat->pending_drag_origin = event->origin; wlr_seat_start_pointer_drag(seat->wlr_seat, event->drag, event->serial); return; } @@ -425,6 +546,7 @@ static void handle_request_start_drag(struct wl_listener *listener, struct wlr_touch_point *point; if (wlr_seat_validate_touch_grab_serial(seat->wlr_seat, event->origin, event->serial, &point)) { + seat->pending_drag_origin = event->origin; wlr_seat_start_touch_drag(seat->wlr_seat, event->drag, event->serial, point); return; @@ -448,11 +570,26 @@ static void handle_start_drag(struct wl_listener *listener, void *data) { } drag->seat = seat; drag->wlr_drag = wlr_drag; + drag->origin = seat->pending_drag_origin; + seat->pending_drag_origin = NULL; wlr_drag->data = drag; drag->destroy.notify = drag_handle_destroy; wl_signal_add(&wlr_drag->events.destroy, &drag->destroy); + // Check if this drag has a toplevel_drag associated with it + if (wlr_drag->source != NULL) { + drag->toplevel_drag = wlr_xdg_toplevel_drag_v1_from_wlr_data_source( + server.xdg_toplevel_drag_manager, wlr_drag->source); + if (drag->toplevel_drag != NULL) { + drag->motion.notify = toplevel_drag_handle_motion; + wl_signal_add(&wlr_drag->events.motion, &drag->motion); + // Initialize surface tracking (will be set on first identification) + drag->toplevel_surface = NULL; + wl_list_init(&drag->toplevel_surface_destroy.link); + } + } + struct wlr_drag_icon *wlr_drag_icon = wlr_drag->icon; if (wlr_drag_icon != NULL) { struct wlr_scene_tree *tree = wlr_scene_drag_icon_create(seat->drag_icons, wlr_drag_icon); @@ -1555,6 +1692,10 @@ void seat_consider_warp_to_focus(struct sway_seat *seat) { } void seatop_unref(struct sway_seat *seat, struct sway_container *con) { + // Clear toplevel drag tracking if this container is being destroyed + if (seat->toplevel_drag_container == con) { + seat->toplevel_drag_container = NULL; + } if (seat->seatop_impl->unref) { seat->seatop_impl->unref(seat, con); } diff --git a/sway/tree/container.c b/sway/tree/container.c index c9ec852fc..23e49c995 100644 --- a/sway/tree/container.c +++ b/sway/tree/container.c @@ -1139,6 +1139,17 @@ void container_floating_translate(struct sway_container *con, node_set_dirty(&con->node); } +void container_floating_update_scene_position(struct sway_container *con) { + if (!container_is_floating(con) || con->scene_tree == NULL) { + return; + } + // Update scene position immediately using pending coordinates. + // This provides smoother visual feedback during drag operations + // by bypassing the transaction system's batching delay. + wlr_scene_node_set_position(&con->scene_tree->node, + con->pending.x, con->pending.y); +} + /** * Choose an output for the floating container's new position. * From 6ac37faaa461cdfe09c88ef5e8194cbc4e932827 Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Fri, 12 Dec 2025 21:04:15 +1300 Subject: [PATCH 3/8] xdg_shell: float and position dragged toplevels Make toplevels attached to an xdg-toplevel-drag automatically float, and position them at the cursor when first mapped. This enables the typical tab tear-off workflow: 1. User starts dragging a tab in a browser 2. Browser creates a new toplevel attached to the drag 3. Sway floats the toplevel and positions it at the cursor 4. The motion handler (from previous commit) keeps it moving with cursor --- sway/desktop/xdg_shell.c | 10 ++++++++++ sway/tree/view.c | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/sway/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c index 48638817c..5428b9f6b 100644 --- a/sway/desktop/xdg_shell.c +++ b/sway/desktop/xdg_shell.c @@ -2,10 +2,13 @@ #include #include #include +#include #include +#include #include #include #include "log.h" +#include "sway/server.h" #include "sway/decoration.h" #include "sway/scene_descriptor.h" #include "sway/desktop/transaction.h" @@ -228,6 +231,13 @@ static void set_resizing(struct sway_view *view, bool resizing) { static bool wants_floating(struct sway_view *view) { struct wlr_xdg_toplevel *toplevel = view->wlr_xdg_toplevel; struct wlr_xdg_toplevel_state *state = &toplevel->current; + + // Check if this toplevel is attached to a toplevel drag + if (wlr_xdg_toplevel_drag_v1_from_wlr_xdg_toplevel( + server.xdg_toplevel_drag_manager, toplevel) != NULL) { + return true; + } + return (state->min_width != 0 && state->min_height != 0 && (state->min_width == state->max_width || state->min_height == state->max_height)) diff --git a/sway/tree/view.c b/sway/tree/view.c index be813be98..71eb5bc68 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -8,10 +8,13 @@ #include #include #include +#include #include #include #include #include +#include +#include #include #if WLR_HAS_XWAYLAND #include @@ -861,6 +864,18 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface, view->container->pending.border = config->floating_border; view->container->pending.border_thickness = config->floating_border_thickness; container_set_floating(view->container, true); + + // If this is a toplevel attached to a drag, position it at the cursor + if (view->wlr_xdg_toplevel != NULL) { + struct wlr_xdg_toplevel_drag_v1 *toplevel_drag = + wlr_xdg_toplevel_drag_v1_from_wlr_xdg_toplevel( + server.xdg_toplevel_drag_manager, view->wlr_xdg_toplevel); + if (toplevel_drag != NULL) { + double x = seat->cursor->cursor->x - toplevel_drag->x_offset; + double y = seat->cursor->cursor->y - toplevel_drag->y_offset; + container_floating_move_to(view->container, x, y); + } + } } else { view->container->pending.border = config->border; view->container->pending.border_thickness = config->border_thickness; From 2b48637c7e93b5fa038d246135d68d6fba2f2bc0 Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Fri, 12 Dec 2025 21:07:06 +1300 Subject: [PATCH 4/8] 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); From ea11148a49f6ace84c60e64d20cd0e7d328646e9 Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Fri, 12 Dec 2025 21:29:46 +1300 Subject: [PATCH 5/8] input/seat: call start/finish for xdg-toplevel-drag lifecycle --- sway/input/seat.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sway/input/seat.c b/sway/input/seat.c index 3c51089cd..9fb581687 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -522,6 +522,8 @@ static void drag_handle_destroy(struct wl_listener *listener, void *data) { drag->wlr_drag->data = NULL; wl_list_remove(&drag->destroy.link); if (drag->toplevel_drag != NULL) { + // Mark drag as ended so client can safely destroy xdg_toplevel_drag + wlr_xdg_toplevel_drag_v1_finish(drag->toplevel_drag); wl_list_remove(&drag->motion.link); // Clean up our surface tracking listener if active if (drag->toplevel_surface != NULL) { @@ -582,6 +584,7 @@ static void handle_start_drag(struct wl_listener *listener, void *data) { drag->toplevel_drag = wlr_xdg_toplevel_drag_v1_from_wlr_data_source( server.xdg_toplevel_drag_manager, wlr_drag->source); if (drag->toplevel_drag != NULL) { + wlr_xdg_toplevel_drag_v1_start(drag->toplevel_drag); drag->motion.notify = toplevel_drag_handle_motion; wl_signal_add(&wlr_drag->events.motion, &drag->motion); // Initialize surface tracking (will be set on first identification) From ccbb811f7fe69da25a1df7c80450dda02410d94d Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Fri, 12 Dec 2025 22:55:08 +1300 Subject: [PATCH 6/8] tree/view: fix initial positioning --- sway/tree/view.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sway/tree/view.c b/sway/tree/view.c index 71eb5bc68..e8d8afbc9 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -871,8 +871,9 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface, wlr_xdg_toplevel_drag_v1_from_wlr_xdg_toplevel( server.xdg_toplevel_drag_manager, view->wlr_xdg_toplevel); if (toplevel_drag != NULL) { - double x = seat->cursor->cursor->x - toplevel_drag->x_offset; - double y = seat->cursor->cursor->y - toplevel_drag->y_offset; + struct wlr_box *geo = &view->geometry; + double x = seat->cursor->cursor->x - toplevel_drag->x_offset - geo->x; + double y = seat->cursor->cursor->y - toplevel_drag->y_offset - geo->y; container_floating_move_to(view->container, x, y); } } From 500ee09be6732fe6fcc4d6799737fe6150dd66db Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Fri, 12 Dec 2025 22:55:24 +1300 Subject: [PATCH 7/8] input/seat: remove dead code --- sway/input/seat.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sway/input/seat.c b/sway/input/seat.c index 9fb581687..f89d46dcd 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -437,11 +437,6 @@ static void toplevel_drag_handle_motion(struct wl_listener *listener, void *data struct wlr_xdg_toplevel_drag_v1 *toplevel_drag = drag->toplevel_drag; struct sway_seat *seat = drag->seat; - if (toplevel_drag == NULL) { - seat->toplevel_drag_container = NULL; - return; - } - // If we have a tracked surface being dragged, move its container. // We track the surface ourselves rather than trusting wlroots' toplevel // pointer, which may not be NULLed promptly during destruction. From ed931b955f74937c273cfa18ae3781335dbdcc00 Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Fri, 12 Dec 2025 22:53:23 +1300 Subject: [PATCH 8/8] input/seat: float tiled containers when attached to drag When a tiled container is attached to a toplevel drag (e.g., re-tearing out a previously tabbed window), float it so it can be moved with the cursor. --- sway/input/seat.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sway/input/seat.c b/sway/input/seat.c index f89d46dcd..cf1e7d2ca 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -467,8 +467,8 @@ static void toplevel_drag_handle_motion(struct wl_listener *listener, void *data } if (!container_is_floating(view->container)) { - // During tearout phase (not yet floating), don't skip hit-testing. - return; + // Tiled container being torn out - float it at cursor position + container_set_floating(view->container, true); } struct wlr_surface *surface = view->surface;