diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-13 16:23:07 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-13 16:23:07 +0300 |
| commit | 276a70a84fed5be229c12546df1ac393158a4ec9 (patch) | |
| tree | f3d0e00d6339efacc466b32048644c388315e90d | |
| parent | 138b9c40aa211edff1d0201083aa25a8b467ee15 (diff) | |
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 <noreply@anthropic.com>
| -rw-r--r-- | docs/guide.md | 8 | ||||
| -rw-r--r-- | include/yasl/yasl.h | 6 | ||||
| -rw-r--r-- | src/platform/wayland/api.h | 1 | ||||
| -rw-r--r-- | src/platform/wayland/backend.c | 6 | ||||
| -rw-r--r-- | src/platform/wayland/internal.h | 11 | ||||
| -rw-r--r-- | src/platform/wayland/pointer.c | 131 | ||||
| -rw-r--r-- | src/platform/wayland/shell.c | 6 | ||||
| -rw-r--r-- | src/platform/wayland/surface.c | 3 | ||||
| -rw-r--r-- | tests/meson.build | 5 | ||||
| -rw-r--r-- | tests/wayland/test_relative_pointer.c | 170 |
10 files changed, 340 insertions, 7 deletions
diff --git a/docs/guide.md b/docs/guide.md index a65972f..62c2e1a 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -224,8 +224,12 @@ meaningless. `POINTER_MOTION` reports `delta`, the device-pixel movement since the previous sample, which restarts from zero after enter, leave, or reset. `yasl_surface_set_pointer_mode()` with `YASL_POINTER_RELATIVE` hides and locks the cursor while the surface is focused; motion then carries unbounded `delta` and -`pos` stays at the lock position. No backend implements it yet, so it -currently returns false and leaves the surface in `YASL_POINTER_NORMAL`. Pen +`pos` stays at the lock position. Wayland implements it with the +relative-pointer and pointer-constraints protocols, reporting the compositor's +accelerated motion in device pixels. The compositor decides when the lock +engages; until then, and while it is released on focus loss, motion stays +absolute with a computed `delta`. X11, Cocoa, and compositors without those +protocols return false and keep the current mode. Pen pressure is normalized to 0..65535, signed tilt is in degrees, and missing axes are zero. Pressure-only changes can produce motion. Zero pressure alone does not mean release. This interface does not expose simultaneous touch contacts. diff --git a/include/yasl/yasl.h b/include/yasl/yasl.h index 22ed308..de9a62c 100644 --- a/include/yasl/yasl.h +++ b/include/yasl/yasl.h @@ -757,10 +757,12 @@ YASL_API bool yasl_surface_poll_event(struct yasl_surface *s, struct yasl_event * Select absolute or relative (locked) pointer input for a surface. * @param s Surface. * @param mode YASL_POINTER_NORMAL or YASL_POINTER_RELATIVE. - * @return true if the mode is supported by the backend; false leaves NORMAL. + * @return true if the mode is supported; false leaves the current mode. * RELATIVE hides the cursor and locks it while the surface is focused; * POINTER_MOTION then carries unbounded delta. Returning to NORMAL restores - * the cursor selected with yasl_surface_set_cursor(). + * the cursor selected with yasl_surface_set_cursor(). RELATIVE is supported on + * Wayland compositors with the relative-pointer and pointer-constraints + * protocols; X11 and Cocoa return false. */ YASL_API bool yasl_surface_set_pointer_mode(struct yasl_surface *s, enum yasl_pointer_mode mode); diff --git a/src/platform/wayland/api.h b/src/platform/wayland/api.h index aabc182..e5e933e 100644 --- a/src/platform/wayland/api.h +++ b/src/platform/wayland/api.h @@ -29,6 +29,7 @@ bool wayland_surface_poll_event(struct wayland_surface *s, struct yasl_event *ev struct yasl_geometry wayland_surface_geometry(const struct wayland_surface *s); struct yasl_timing wayland_surface_timing(const struct wayland_surface *s); void wayland_surface_set_cursor(struct wayland_surface *s, enum yasl_cursor cursor); +bool wayland_surface_set_pointer_mode(struct wayland_surface *s, enum yasl_pointer_mode mode); void wayland_surface_set_background_color(struct wayland_surface *s, uint32_t bg); bool wayland_surface_ime_preedit_cursor(const struct wayland_surface *s, int *begin, int *end); void wayland_surface_set_ime_caret(struct wayland_surface *s, struct yasl_rect2i caret); diff --git a/src/platform/wayland/backend.c b/src/platform/wayland/backend.c index 1d83ec6..c3c7bed 100644 --- a/src/platform/wayland/backend.c +++ b/src/platform/wayland/backend.c @@ -126,6 +126,11 @@ static void impl_surface_set_cursor(struct yasl_surface *s, enum yasl_cursor cur wayland_surface_set_cursor((struct wayland_surface *)s, cursor); } +static bool impl_surface_set_pointer_mode(struct yasl_surface *s, enum yasl_pointer_mode mode) +{ + return wayland_surface_set_pointer_mode((struct wayland_surface *)s, mode); +} + static void impl_surface_set_background_color(struct yasl_surface *s, uint32_t bg) { wayland_surface_set_background_color((struct wayland_surface *)s, bg); @@ -206,6 +211,7 @@ const struct yasl_backend_ops yasl_wayland_backend = { .surface_schedule_redraw = impl_surface_schedule_redraw, .surface_present = impl_surface_present, .surface_poll_event = impl_surface_poll_event, + .surface_set_pointer_mode = impl_surface_set_pointer_mode, .surface_geometry = impl_surface_geometry, .surface_timing = impl_surface_timing, .surface_frame_time_ns = impl_surface_frame_time_ns, diff --git a/src/platform/wayland/internal.h b/src/platform/wayland/internal.h index d619dba..c35db7f 100644 --- a/src/platform/wayland/internal.h +++ b/src/platform/wayland/internal.h @@ -23,6 +23,8 @@ #include "protocols/commit-timing-v1-client-protocol.h" #include "protocols/xdg-toplevel-drag-v1-client-protocol.h" #include "protocols/primary-selection-unstable-v1-client-protocol.h" +#include "protocols/relative-pointer-unstable-v1-client-protocol.h" +#include "protocols/pointer-constraints-unstable-v1-client-protocol.h" #include <fcntl.h> #include <limits.h> @@ -131,6 +133,7 @@ struct seat { struct wl_pointer *pointer; struct wl_touch *touch; struct wp_cursor_shape_device_v1 *cursor_shape_dev; + struct zwp_relative_pointer_v1 *relative_pointer; struct wl_surface *cursor_surface; struct wp_viewport *cursor_viewport; struct zwp_tablet_seat_v2 *tablet_seat; @@ -212,6 +215,8 @@ struct wayland_display { struct wl_data_device_manager *data_manager; struct zwp_primary_selection_device_manager_v1 *primary_selection_mgr; struct wp_cursor_shape_manager_v1 *cursor_shape_mgr; + struct zwp_relative_pointer_manager_v1 *relative_pointer_mgr; + struct zwp_pointer_constraints_v1 *pointer_constraints; struct wl_cursor_theme *cursor_theme; const char *cursor_theme_name; struct zwp_tablet_manager_v2 *tablet_mgr; @@ -275,6 +280,9 @@ struct wayland_surface { struct wp_fifo_v1 *fifo; struct wp_commit_timer_v1 *commit_timer; struct wl_callback *frame_cb; + /* Persistent lock held while relative; lock_seat owns the locked wl_pointer. */ + struct zwp_locked_pointer_v1 *pointer_lock; + struct seat *lock_seat; const struct yasl_window_cbs *cbs; void *ud; uint64_t frames_skipped; @@ -316,6 +324,8 @@ struct wayland_surface { bool destroyed; bool opaque; bool want_draw; + bool relative; + bool pointer_locked; }; @@ -388,6 +398,7 @@ int seat_modifiers(const struct seat *s); void surface_init_cursor(struct wayland_display *c); void seat_destroy_tablet(struct seat *s); void seat_pointer_reset(struct seat *s); +void surface_unlock_pointer(struct wayland_surface *w); extern const struct zwp_text_input_v3_listener text_input_listener; void ime_reset(struct wayland_surface *w); 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); diff --git a/src/platform/wayland/shell.c b/src/platform/wayland/shell.c index 92c89d1..a12bd4d 100644 --- a/src/platform/wayland/shell.c +++ b/src/platform/wayland/shell.c @@ -208,6 +208,12 @@ static void registry_global(void *data, struct wl_registry *registry, } else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) { c->cursor_shape_mgr = wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, 1); + } else if (strcmp(interface, zwp_relative_pointer_manager_v1_interface.name) == 0) { + c->relative_pointer_mgr = wl_registry_bind(registry, name, + &zwp_relative_pointer_manager_v1_interface, 1); + } else if (strcmp(interface, zwp_pointer_constraints_v1_interface.name) == 0) { + c->pointer_constraints = wl_registry_bind(registry, name, + &zwp_pointer_constraints_v1_interface, 1); } else if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) { c->scale_mgr = wl_registry_bind(registry, name, &wp_fractional_scale_manager_v1_interface, 1); diff --git a/src/platform/wayland/surface.c b/src/platform/wayland/surface.c index ad3a24c..b17d203 100644 --- a/src/platform/wayland/surface.c +++ b/src/platform/wayland/surface.c @@ -200,6 +200,8 @@ static void display_free(struct wayland_display *c) if (c->primary_selection_mgr) zwp_primary_selection_device_manager_v1_destroy(c->primary_selection_mgr); if (c->data_manager) wl_data_device_manager_destroy(c->data_manager); if (c->cursor_shape_mgr) wp_cursor_shape_manager_v1_destroy(c->cursor_shape_mgr); + if (c->relative_pointer_mgr) zwp_relative_pointer_manager_v1_destroy(c->relative_pointer_mgr); + if (c->pointer_constraints) zwp_pointer_constraints_v1_destroy(c->pointer_constraints); if (c->cursor_theme) wl_cursor_theme_destroy(c->cursor_theme); if (c->tablet_mgr) zwp_tablet_manager_v2_destroy(c->tablet_mgr); if (c->text_input_mgr) zwp_text_input_manager_v3_destroy(c->text_input_mgr); @@ -399,6 +401,7 @@ void wayland_surface_destroy(struct wayland_surface *w) if (!w || w->destroyed) return; for (struct wayland_surface *o = w->conn ? w->conn->surfaces : NULL; o; o = o->conn_next) assert(o->parent != w && "destroy a popup before its parent"); + surface_unlock_pointer(w); input_queue_clear(&w->input); free(w->ime_preedit); free(w->ime_commit); diff --git a/tests/meson.build b/tests/meson.build index 951b30e..980f5db 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -189,6 +189,11 @@ if has_wayland include_directories: inc, dependencies: yasl_dep, ) test('cursor-wayland', cursor_wayland_test) + relative_pointer_test = executable('yasl-relative-pointer-wayland-test', + 'wayland/test_relative_pointer.c', + include_directories: inc, dependencies: yasl_dep, + ) + test('relative-pointer-wayland', relative_pointer_test) endif benchmark_exe = executable( diff --git a/tests/wayland/test_relative_pointer.c b/tests/wayland/test_relative_pointer.c new file mode 100644 index 0000000..bcc41e3 --- /dev/null +++ b/tests/wayland/test_relative_pointer.c @@ -0,0 +1,170 @@ +#include "platform/wayland/internal.h" +#include <assert.h> + +/* Record lock, relative-pointer and cursor requests without a compositor. */ +static char lock_objects[4]; +static int locks, unlocks, pointer_hides, pointer_shapes; +static struct wl_surface *locked_surface; +static struct wl_pointer *locked_wl_pointer; +static uint32_t lock_lifetime; + +static struct zwp_locked_pointer_v1 *lock_pointer(struct zwp_pointer_constraints_v1 *mgr, + struct wl_surface *surface, struct wl_pointer *pointer, + struct wl_region *region, uint32_t lifetime) +{ + assert(mgr && surface && pointer && !region); + locked_surface = surface; + locked_wl_pointer = pointer; + lock_lifetime = lifetime; + return (struct zwp_locked_pointer_v1 *)&lock_objects[locks++ % 4]; +} + +static int lock_add_listener(struct zwp_locked_pointer_v1 *lock, + const struct zwp_locked_pointer_v1_listener *listener, void *data) +{ + assert(lock && listener && data); + return 0; +} + +static void lock_destroy(struct zwp_locked_pointer_v1 *lock) +{ + assert(lock); + unlocks++; +} + +static void pointer_cursor(struct wl_pointer *pointer, uint32_t serial, + struct wl_surface *surface, int32_t x, int32_t y) +{ + assert(pointer && serial && !surface && !x && !y); + pointer_hides++; +} + +static void cursor_shape(struct wp_cursor_shape_device_v1 *device, + uint32_t serial, uint32_t shape) +{ + assert(device && serial && shape == WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT); + pointer_shapes++; +} + +static uint32_t pointer_version(struct wl_pointer *pointer) +{ + assert(pointer); + return WL_POINTER_FRAME_SINCE_VERSION; +} + +static void pointer_release(struct wl_pointer *pointer) { assert(pointer); } +static void shape_device_destroy(struct wp_cursor_shape_device_v1 *device) { assert(device); } + +#define zwp_pointer_constraints_v1_lock_pointer lock_pointer +#define zwp_locked_pointer_v1_add_listener lock_add_listener +#define zwp_locked_pointer_v1_destroy lock_destroy +#define wl_pointer_set_cursor pointer_cursor +#define wp_cursor_shape_device_v1_set_shape cursor_shape +#define wp_cursor_shape_device_v1_destroy shape_device_destroy +#define wl_pointer_get_version pointer_version +#define wl_pointer_release pointer_release +#include "../../src/platform/wayland/pointer.c" + +static struct yasl_event next_event(struct wayland_surface *w) +{ + struct yasl_event event; + assert(yasl_surface_poll_event(&w->base, &event)); + return event; +} + +static void move_to(struct seat *s, int x, int y) +{ + pointer_motion(s, s->pointer, 0, wl_fixed_from_int(x), wl_fixed_from_int(y)); + pointer_frame(s, s->pointer); +} + +static void relative_move(struct seat *s, double dx, double dy) +{ + relative_motion(s, NULL, 0, 0, wl_fixed_from_double(dx), wl_fixed_from_double(dy), 0, 0); +} + +int main(void) +{ + struct wayland_display display = {.base.ops = &yasl_wayland_backend}; + struct wayland_surface surface = {.base.display = &display.base, .conn = &display, + .scale_factor = 2, .cursor = YASL_CURSOR_TEXT}; + surface.surface = (struct wl_surface *)&surface; + wl_surface_set_user_data(surface.surface, &surface); + display.surfaces = &surface; + struct seat seat = {.conn = &display, .cursor_shape = -1}; + seat.pointer = (struct wl_pointer *)&seat; + seat.cursor_shape_dev = (struct wp_cursor_shape_device_v1 *)&seat; + display.seats = &seat; + struct yasl_event event; + + /* Without both protocols the surface stays in absolute mode. */ + assert(!yasl_surface_set_pointer_mode(&surface.base, YASL_POINTER_RELATIVE)); + assert(!surface.relative && !locks); + display.relative_pointer_mgr = (struct zwp_relative_pointer_manager_v1 *)&display; + display.pointer_constraints = (struct zwp_pointer_constraints_v1 *)&display; + + pointer_enter(&seat, seat.pointer, 10, surface.surface, wl_fixed_from_int(5), wl_fixed_from_int(5)); + pointer_frame(&seat, seat.pointer); + assert(pointer_shapes == 1); + event = next_event(&surface); + assert(event.type == YASL_EVENT_POINTER_MOTION && event.pointer.pos.x == 10); + + /* Relative mode locks persistently on the pointer's seat and hides the cursor. */ + assert(yasl_surface_set_pointer_mode(&surface.base, YASL_POINTER_RELATIVE)); + assert(locks == 1 && locked_surface == surface.surface && locked_wl_pointer == seat.pointer); + assert(lock_lifetime == ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + assert(pointer_hides == 1 && surface.lock_seat == &seat); + + /* Until the compositor activates the lock, motion is absolute with a queue delta. */ + relative_move(&seat, 3, 3); + move_to(&seat, 7, 5); + event = next_event(&surface); + assert(event.pointer.pos.x == 14 && event.pointer.pos.y == 10); + assert(event.pointer.delta.x == 4 && event.pointer.delta.y == 0); + assert(!yasl_surface_poll_event(&surface.base, &event)); + + /* Locked: relative samples in device pixels, position frozen at the lock. */ + pointer_locked(&surface, surface.pointer_lock); + relative_move(&seat, 3.5, -2); + relative_move(&seat, 1000, 0); + event = next_event(&surface); + assert(event.type == YASL_EVENT_POINTER_MOTION); + assert(event.pointer.pos.x == 14 && event.pointer.pos.y == 10); + assert(event.pointer.delta.x == 7 && event.pointer.delta.y == -4); + event = next_event(&surface); + assert(event.pointer.delta.x == 2000 && event.pointer.pos.x == 14); + + /* Deactivation (e.g. focus loss) returns to absolute motion without unlocking. */ + pointer_unlocked(&surface, surface.pointer_lock); + relative_move(&seat, 5, 5); + assert(!yasl_surface_poll_event(&surface.base, &event)); + move_to(&seat, 8, 5); + event = next_event(&surface); + assert(event.pointer.delta.x == 2 && event.pointer.delta.y == 0); + assert(unlocks == 0 && surface.pointer_lock); + pointer_locked(&surface, surface.pointer_lock); + + /* Returning to normal destroys the lock and restores the chosen cursor. */ + assert(yasl_surface_set_pointer_mode(&surface.base, YASL_POINTER_NORMAL)); + assert(unlocks == 1 && !surface.pointer_lock && !surface.input.relative); + assert(pointer_shapes == 2); + assert(yasl_surface_set_pointer_mode(&surface.base, YASL_POINTER_NORMAL)); + assert(unlocks == 1); + assert(yasl_surface_set_pointer_mode(&surface.base, YASL_POINTER_RELATIVE)); + assert(locks == 2 && pointer_hides == 2); + pointer_locked(&surface, surface.pointer_lock); + + /* Losing the seat's pointer hands the lock to another seat with a pointer. */ + struct seat other = {.conn = &display, .cursor_shape = -1}; + other.pointer = (struct wl_pointer *)&other; + seat.next = &other; + seat_drop_pointer(&seat); + assert(unlocks == 2 && locks == 3 && locked_wl_pointer == other.pointer); + assert(surface.lock_seat == &other && !surface.input.relative); + + /* Surface destruction releases the lock through surface_unlock_pointer(). */ + surface_unlock_pointer(&surface); + assert(unlocks == 3 && !surface.lock_seat && !surface.pointer_lock); + input_queue_clear(&surface.input); + return 0; +} |
