#3988

Add wp_pointer_warp_v1 protocol support

Openzakissimoopened 5mo ago

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_pointer on State. 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

3
YaLTeR5mo ago

Sure, sounds good. Put it somewhere near other relevant globals like pointer constraint. Also I want to see references to Mutter and KWin impl.

YaLTeR5mo ago

Sure, sounds good. Put it somewhere near other relevant globals like pointer constraint. Also I want to see references to Mutter and KWin impl.

zakissimo5mo ago

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?