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. *