From 276a70a84fed5be229c12546df1ac393158a4ec9 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Tue, 13 Oct 2026 16:23:07 +0300 Subject: Implement relative pointer mode on Wayland yasl_surface_set_pointer_mode(YASL_POINTER_RELATIVE) now takes a persistent zwp_locked_pointer_v1 on the surface and hides its cursor. While the compositor keeps the lock engaged, zwp_relative_pointer_v1 motion is pushed as POINTER_MOTION with pos frozen and delta in device pixels. When the lock disengages, as on focus loss, motion is absolute again with a delta derived by the input queue. The lock follows the pointer's seat: it moves to another seat when its seat loses the pointer, and is released on NORMAL and on surface destruction. Compositors without both protocols return false. Co-Authored-By: Claude Opus 5.5 --- src/platform/wayland/pointer.c | 131 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 3 deletions(-) (limited to 'src/platform/wayland/pointer.c') diff --git a/src/platform/wayland/pointer.c b/src/platform/wayland/pointer.c index 83f6e2f..cdc5780 100644 --- a/src/platform/wayland/pointer.c +++ b/src/platform/wayland/pointer.c @@ -152,8 +152,10 @@ static uint32_t cursor_shape_of(enum yasl_cursor shape) } } -static void seat_set_cursor(struct seat *s, struct wayland_surface *w, enum yasl_cursor shape) +/* Relative mode hides the pointer but keeps w->cursor for the return to NORMAL. */ +static void seat_set_cursor(struct seat *s, struct wayland_surface *w) { + enum yasl_cursor shape = w->relative ? YASL_CURSOR_HIDDEN : w->cursor; if (!s->pointer || !s->pointer_serial || s->pointer_focus != w || s->cursor_shape == (int)shape) return; @@ -183,7 +185,7 @@ void wayland_surface_set_cursor(struct wayland_surface *w, enum yasl_cursor shap if (!w || !w->conn || w->destroyed || (unsigned)shape > YASL_CURSOR_HIDDEN) return; w->cursor = shape; for (struct seat *s = w->conn->seats; s; s = s->next) { - seat_set_cursor(s, w, shape); + seat_set_cursor(s, w); if (s->tool && s->tablet_focus == w) tool_set_cursor(s->tool, shape); } @@ -279,7 +281,7 @@ static void pointer_enter(void *data, struct wl_pointer *pointer, s->pointer_pos = device_from_fixed(w, surface_x, surface_y); s->pointer_serial = serial; s->cursor_shape = -1; - seat_set_cursor(s, w, w->cursor); + seat_set_cursor(s, w); s->motion_pending = true; pointer_event_done(s); } @@ -448,6 +450,105 @@ static void pointer_axis_relative_direction(void *data, struct wl_pointer *point static void pointer_warp(void *data, struct wl_pointer *pointer, wl_fixed_t x, wl_fixed_t y) { (void)data; (void)pointer; (void)x; (void)y; } +static void relative_motion(void *data, struct zwp_relative_pointer_v1 *pointer, + uint32_t utime_hi, uint32_t utime_lo, wl_fixed_t dx, wl_fixed_t dy, + wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel) +{ + (void)pointer; (void)utime_hi; (void)utime_lo; (void)dx_unaccel; (void)dy_unaccel; + struct seat *s = data; + struct wayland_surface *w = s->pointer_focus; + /* Unlocked movement arrives as wl_pointer.motion; the queue derives its delta. */ + if (!w || !w->pointer_locked || w->lock_seat != s) return; + flush_pending_motion(s, w); + push_pointer(s, w, YASL_EVENT_POINTER_MOTION, (struct yasl_pointer_event){ + .pos = s->pointer_pos, + .delta = {wl_fixed_to_double(dx) * w->scale_factor, + wl_fixed_to_double(dy) * w->scale_factor}, + }); +} + +static const struct zwp_relative_pointer_v1_listener relative_pointer_listener = { + .relative_motion = relative_motion, +}; + +static void pointer_locked(void *data, struct zwp_locked_pointer_v1 *lock) +{ + (void)lock; + struct wayland_surface *w = data; + w->pointer_locked = true; + surface_root(w)->input.relative = true; +} + +static void pointer_unlocked(void *data, struct zwp_locked_pointer_v1 *lock) +{ + (void)lock; + struct wayland_surface *w = data; + w->pointer_locked = false; + surface_root(w)->input.relative = false; +} + +static const struct zwp_locked_pointer_v1_listener locked_pointer_listener = { + .locked = pointer_locked, + .unlocked = pointer_unlocked, +}; + +/* A persistent lock re-engages each time the compositor focuses the surface. */ +static void surface_lock_pointer(struct wayland_surface *w, struct seat *s) +{ + struct wayland_display *c = w->conn; + if (!w->relative || w->pointer_lock || w->destroyed || !s->pointer) + return; + w->pointer_lock = zwp_pointer_constraints_v1_lock_pointer(c->pointer_constraints, + w->surface, s->pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + if (!w->pointer_lock) + return; + w->lock_seat = s; + zwp_locked_pointer_v1_add_listener(w->pointer_lock, &locked_pointer_listener, w); +} + +void surface_unlock_pointer(struct wayland_surface *w) +{ + if (!w->pointer_lock) + return; + zwp_locked_pointer_v1_destroy(w->pointer_lock); + w->pointer_lock = NULL; + w->lock_seat = NULL; + if (w->pointer_locked) { + w->pointer_locked = false; + surface_root(w)->input.relative = false; + } +} + +static void seat_lock_surfaces(struct seat *s) +{ + for (struct wayland_surface *w = s->conn->surfaces; w; w = w->conn_next) + surface_lock_pointer(w, s); +} + +bool wayland_surface_set_pointer_mode(struct wayland_surface *w, enum yasl_pointer_mode mode) +{ + if (!w || !w->conn || w->destroyed) + return false; + struct wayland_display *c = w->conn; + bool relative = mode == YASL_POINTER_RELATIVE; + if (relative && (!c->relative_pointer_mgr || !c->pointer_constraints)) + return false; + w->relative = relative; + if (relative) { + /* Prefer the seat that last delivered input; others lock once it goes away. */ + struct seat *seat = c->active_seat && c->active_seat->pointer ? c->active_seat : NULL; + for (struct seat *s = c->seats; s && !seat; s = s->next) + if (s->pointer) seat = s; + if (seat) + surface_lock_pointer(w, seat); + } else { + surface_unlock_pointer(w); + } + for (struct seat *s = c->seats; s; s = s->next) + seat_set_cursor(s, w); + return true; +} + static const struct wl_pointer_listener pointer_listener = { .axis_relative_direction = pointer_axis_relative_direction, .warp = pointer_warp, @@ -984,6 +1085,18 @@ void seat_clipboard_ensure(struct seat *s) static void seat_drop_pointer(struct seat *s) { + struct wayland_display *c = s->conn; + bool unlocked = false; + for (struct wayland_surface *w = c->surfaces; w; w = w->conn_next) { + if (w->lock_seat == s) { + surface_unlock_pointer(w); + unlocked = true; + } + } + if (s->relative_pointer) { + zwp_relative_pointer_v1_destroy(s->relative_pointer); + s->relative_pointer = NULL; + } if (s->cursor_shape_dev) { wp_cursor_shape_device_v1_destroy(s->cursor_shape_dev); s->cursor_shape_dev = NULL; @@ -998,6 +1111,9 @@ static void seat_drop_pointer(struct seat *s) seat_pointer_reset(s); s->pointer_focus = s->ptr_left = NULL; s->ptr_presses.n = 0; + for (struct seat *o = c->seats; unlocked && o; o = o->next) + if (o != s && o->pointer) + seat_lock_surfaces(o); } static void seat_drop_keyboard(struct seat *s) @@ -1057,6 +1173,15 @@ static void seat_capabilities(void *data, struct wl_seat *proxy, uint32_t caps) if (s->pointer && c->cursor_shape_mgr) s->cursor_shape_dev = wp_cursor_shape_manager_v1_get_pointer( c->cursor_shape_mgr, s->pointer); + if (s->pointer && c->relative_pointer_mgr) { + s->relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer( + c->relative_pointer_mgr, s->pointer); + if (s->relative_pointer) + zwp_relative_pointer_v1_add_listener(s->relative_pointer, + &relative_pointer_listener, s); + } + if (s->pointer) + seat_lock_surfaces(s); } } else if (s->pointer) { seat_drop_pointer(s); -- cgit v1.2.3