The wp_pointer_warp_v1 staging protocol (wayland-protocols 1.45) gives Wayland clients
a direct cursor-warp primitive, replacing workarounds based on zwp_locked_pointer_v1's
set_cursor_position_hint. KWin (≥6.4), Mutter (≥49), and wlroots (≥0.19) already
implement it.
Smithay merged the server-side handler in Smithay#1757 (Aug 2025), and niri's pinned Smithay revision already includes it — so the remaining work is compositor wiring and the validation policy that Smithay deliberately leaves to each compositor:
- Call
PointerWarpManager::new::<State>(...)at startup to advertise the global. - Implement
PointerWarpHandler::warp_pointeronState. The protocol's "should" guidelines:- Reject if the enter serial isn't recent for any of the client's surfaces.
- Reject if the requested position is outside the target surface.
- Honor when the surface has pointer focus (or an implicit pointer grab).
- On valid requests, call into niri's existing cursor-positioning code.
- Add the
delegate_dispatch!glue.
Motivation
I'm working on the matching Wine client-side support. With this protocol advertised,
Wine apps calling SetCursorPos get a direct warp; without it, Wine falls back to the
older lock/hint workaround that has known reliability issues
(see Wine MR !7353).
Adding niri support closes that gap end-to-end.
Could I take this on?
I'd like to attempt the implementation if you're open to it — first-time niri contributor, so pointers welcome on policy specifics and where the Smithay state should sit alongside niri's other protocol globals.
Activity
3Sure, sounds good. Put it somewhere near other relevant globals like pointer constraint. Also I want to see references to Mutter and KWin impl.
Following up on the validation policy after reading both reference implementations.
KWin (pointer_input.cpp, warpRequested lambda) — focus + bounds, no button-press requirement:
if (serial != waylandServer()->seat()->pointer()->focusedSerial()) return;
if (!surface->boundingRect().contains(point)) return;
Window *window = waylandServer()->findWindow(surface->mainSurface());
if (!window) return;
input()->pointer()->warp(window->mapFromLocal(surface->mapToMainSurface(point)));Mutter (meta_wayland_pointer_can_warp) — adds button_count > 0:
return (pointer->button_count > 0 &&
pointer->focus_serial == serial &&
pointer_can_grab_surface (pointer, surface));The protocol says: "honor it if the surface has pointer focus, including when it has an implicit pointer grab". The "including" reads as "focus is sufficient; implicit grab is one valid case", not as a requirement — so KWin matches the spec wording, and Mutter is stricter than the spec requires.
I'd like to go with the lenient (KWin-style) policy: focus + bounds, no grab requirement. Most legitimate SetCursorPos-class callers (Wine apps, mouselook recentering, drag-completion, menu navigation) happen outside a button-down, so the strict reading rejects most of them. If abuse becomes a real concern down the line, requiring a grab is a small follow-up change.
Two Mutter bugfixes worth replicating in our impl (Mutter !4626): the serial is the enter serial (focus_serial), not the grab serial; and the bounds check needs to be on wl_fixed_to_double-converted coords, not raw wl_fixed_t. Smithay's Dispatch2 already converts before handing us a Point<f64, Logical>, so we get the second one for free.
One implementation question while I'm at it: niri currently has active_window_visual_rectangle() (for the keyboard-active window only) at three layers. For pointer-warp the target may be any window the cursor recently entered, so I was planning to add a window_visual_rectangle(window_id) -> Option<Rectangle<f64, Logical>> parallel to it — small generalization, ~20 LOC. OK with that scope as part of the same PR?
Sure, sounds good. Put it somewhere near other relevant globals like pointer constraint. Also I want to see references to Mutter and KWin impl.