This commit is contained in:
Ryan 2026-02-03 21:57:29 -06:00 committed by GitHub
commit f4f6f7a367
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 245 additions and 0 deletions

View file

@ -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;

View file

@ -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

View file

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

View file

@ -2,10 +2,13 @@
#include <stdbool.h>
#include <stdlib.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/types/wlr_xdg_toplevel_drag_v1.h>
#include <wlr/types/wlr_xdg_toplevel_tag_v1.h>
#include <wlr/util/edges.h>
#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))

View file

@ -13,6 +13,8 @@
#include <wlr/types/wlr_tablet_v2.h>
#include <wlr/types/wlr_touch.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/types/wlr_xdg_toplevel_drag_v1.h>
#include "config.h"
#include "list.h"
#include "log.h"
@ -386,12 +388,118 @@ 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 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)) {
// Tiled container being torn out - float it at cursor position
container_set_floating(view->container, true);
}
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 +516,15 @@ 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) {
wl_list_remove(&drag->toplevel_surface_destroy.link);
}
}
free(drag);
}
@ -418,6 +535,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 +543,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 +567,27 @@ 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) {
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)
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 +1690,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);
}

View file

@ -1,9 +1,13 @@
#include <float.h>
#include <libevdev/libevdev.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_subcompositor.h>
#include <wlr/types/wlr_tablet_v2.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/types/wlr_xdg_toplevel_drag_v1.h>
#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);

View file

@ -51,6 +51,7 @@
#include <wlr/types/wlr_xdg_foreign_v1.h>
#include <wlr/types/wlr_xdg_foreign_v2.h>
#include <wlr/types/wlr_xdg_output_v1.h>
#include <wlr/types/wlr_xdg_toplevel_drag_v1.h>
#include <wlr/types/wlr_xdg_toplevel_tag_v1.h>
#include <xf86drm.h>
#include "config.h"
@ -439,6 +440,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;

View file

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

View file

@ -8,10 +8,13 @@
#include <wlr/types/wlr_foreign_toplevel_management_v1.h>
#include <wlr/types/wlr_fractional_scale_v1.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_security_context_v1.h>
#include <wlr/types/wlr_server_decoration.h>
#include <wlr/types/wlr_subcompositor.h>
#include <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/types/wlr_xdg_toplevel_drag_v1.h>
#include <wlr/types/wlr_session_lock_v1.h>
#if WLR_HAS_XWAYLAND
#include <wlr/xwayland.h>
@ -863,6 +866,19 @@ 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) {
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);
}
}
} else {
view->container->pending.border = config->border;
view->container->pending.border_thickness = config->border_thickness;