From 9da620d443ab046bb58cb7f787466e35f8a9b3a0 Mon Sep 17 00:00:00 2001 From: lkn Date: Thu, 25 Jun 2026 07:20:05 +0200 Subject: [PATCH] wayland: implement pointer warp via pointer-constraints hint Native Wayland clients (e.g. plan9port acme/devdraw) warp the pointer by locking it via zwp_pointer_constraints_v1, setting a cursor_position_hint, then immediately destroying the lock. The compositor is expected to warp the cursor to the hint on unlock. Our handler just activated the constraint and ignored the hint. Now we listen for the constraint destroy event and warp the cursor to the hint position (converted from surface-local to global coordinates). --- src/backend/wayland/wl_handlers.c | 47 ++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/src/backend/wayland/wl_handlers.c b/src/backend/wayland/wl_handlers.c index a69254a..8420e34 100644 --- a/src/backend/wayland/wl_handlers.c +++ b/src/backend/wayland/wl_handlers.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -99,16 +100,54 @@ handle_pointer_axis(struct wl_listener *listener, void *data) * In headless mode the pointer is virtual so there is nothing physically * to lock, but activating keeps the client happy. */ +struct wl_constraint_ctx { + struct wlr_pointer_constraint_v1 *constraint; + struct wl_listener destroy; +}; + +static void +handle_pointer_constraint_destroy(struct wl_listener *listener, void *data) +{ + struct wl_constraint_ctx *ctx = + wl_container_of(listener, ctx, destroy); + struct wlr_pointer_constraint_v1 *constraint = ctx->constraint; + (void)data; + + if (constraint->current.cursor_hint.enabled && constraint->surface) { + /* Convert surface-local hint to global coordinates */ + double sx = constraint->current.cursor_hint.x; + double sy = constraint->current.cursor_hint.y; + int gx = 0, gy = 0; + /* Find the toplevel view owning this surface to get global pos */ + struct wl_toplevel_view *v; + wl_list_for_each(v, &wl_state.toplevel_list, link) { + if (v->xdg_surface && v->xdg_surface->surface == constraint->surface) { + gx = v->x; + gy = v->y; + break; + } + } + wl_pointer_warp_to(gx + (int)sx, gy + (int)sy); + } + + wl_list_remove(&ctx->destroy.link); + free(ctx); +} + void handle_new_pointer_constraint(struct wl_listener *listener, void *data) { (void)listener; struct wlr_pointer_constraint_v1 *constraint = data; - (void)constraint; - /* Activate immediately — in nested mode the real pointer is bounded - * by the host compositor's window anyway. */ + wlr_pointer_constraint_v1_send_activated(constraint); - wwarning("wl_backend: pointer constraint activated"); + + struct wl_constraint_ctx *ctx = calloc(1, sizeof(*ctx)); + if (ctx) { + ctx->constraint = constraint; + ctx->destroy.notify = handle_pointer_constraint_destroy; + wl_signal_add(&constraint->events.destroy, &ctx->destroy); + } } /*