Skip to content

XWayland doesnt support cursor reponsitioning #22

Description

@baconpaul

Summary

On Linux under XWayland, a drag using relative (unbounded) pointer mode
(NEUI_API_POINTER / begin_relative) dies after a single motion event. The
first move is dispatched correctly, the warp-back to the anchor happens, its
echo is correctly filtered — and then no further motion events of any kind
reach the event loop until the button is released
. For a client that brackets
begin_relative / end_relative around a knob drag (the usage d/pointer.h
documents), the control moves once by the initial delta and then freezes, which
makes it effectively unusable.

Turning relative mode off restores a normal, working bounded drag, so this is
specific to the relative-pointer path.

Environment

neui 19c0c579a17ca14c75b788bfd359f4c259b63c95 (origin/bp-review)
host neui.host.crossplatform (only host on Linux)
OS Ubuntu 26.04 LTS, kernel 7.0.0-29-generic, aarch64
Session WaylandXDG_SESSION_TYPE=wayland, GNOME Shell 49.2 / Mutter
X server Xwayland 24.1.10 — XWAYLAND extension present
Compiler GCC 15
XI2 present (NEUI_HAS_XI2=1); also reproduced with XI2 compiled out

Symptom

Client code is the documented pattern — begin_relative from the widget's
MOUSE_BUTTON_DOWN handler, end_relative from MOUSE_BUTTON_UP.
begin_relative returns true, so the mode engages.

Instrumented trace of one drag (host-side logging added to dispatch_motion
plus a raw log of every event entering dispatch_x_event, before the
find_window filter):

[knob] DOWN  y=70.00  began=1                       <- relative mode engaged
[x11] RAW type=6 win=0xc00004 found=1
[x11] dispatch_motion root=(210,291) state=0x0100 rel_active=1 win_match=1
                      anchor=(210,291) last=(210,291)
[x11]   -> ECHO (dropped)
[x11] RAW type=6 win=0xc00004 found=1
[x11] dispatch_motion root=(207,281) anchor=(210,291) last=(210,291)
[x11]   -> RELATIVE dispatch dx=-3 dy=-10           <- correct delta
[knob] DRAG  y=60.00  dy=10.00                      <- reaches the client
[x11]   -> WARP back to (210,291) on root 0x2f2     <- warp issued
[x11] RAW type=6 win=0xc00004 found=1
[x11] dispatch_motion root=(210,291) last=(207,281)
[x11]   -> ECHO (dropped)                           <- warp landed: it worked
[x11] RAW type=5 win=0xc00004 found=1               <- ButtonRelease
[knob] UP
[x11] XI_Motion ev=(70.0,114.0) pressed=0 -> do_motion   <- motion resumes
[x11] XI_Motion ev=(100.0,155.0) ...                     <- freely, post-release

Every branch behaves as designed. The echo landing exactly on the anchor proves
XWarpPointer itself took effect. After that warp, motion delivery simply stops
for the remainder of the gesture; it resumes the instant the button comes up.

Ruled out

  • The XI2 / core-motion gate (platform_linux.cpp:1677, the
    !g_xi2_motion_seen || (state & ButtonNMask) condition). Rebuilding with
    libXi hidden from pkg-config, so NEUI_HAS_XI2 is undefined and the gate
    collapses to an unconditional dispatch_motion, reproduces identically.
  • The warp-echo filter (hosts/shared/relative_pointer.h,
    relative_is_warp_echo). It fires exactly twice in the trace, both on genuine
    anchor-position events. It is not swallowing real motion.
  • Event misrouting. The raw logging sits above find_window, so events
    addressed to an unrecognised window would still have been logged. Nothing
    arrives at all.
  • Anything above the host. The client is dispatched to correctly for the one
    event that does arrive.

Analysis

Confirmed: warp-based relative-pointer mode does not work under XWayland on
this system, with the reproducible signature above.

Inferred: XWayland's handling of XWarpPointer is the cause. Wayland
deliberately does not let clients position the pointer, and XWayland's support
is limited and compositor-dependent. I was not able to prove the precise
Mutter/XWayland mechanism from inside the app — a standalone XTEST-driven
reproducer is not possible here either, because this compositor also blocks
synthetic pointer input for X clients.

Note that platform_supports_relative_pointer()
(hosts/crossplatform/platform_linux.cpp:3695) currently returns true
unconditionally on Linux, so the mode is advertised on XWayland and clients have
no way to know it will not work.

Suggested fix

d/pointer.h already specifies the degradation path:

begin_relative also returns false on a platform with no warping seam (iOS,
null), so a touch platform degrades to ordinary bounded drags rather than
misbehaving.

XWayland is such a platform. Having platform_supports_relative_pointer()
return false when the XWAYLAND extension is present would make
begin_relative return false, and every client would fall back to an ordinary
bounded drag — which works correctly here. Native X11 keeps the feature:

bool platform_supports_relative_pointer()
{
    // Wayland does not let clients position the pointer, and XWayland's
    // XWarpPointer support is limited: the warp appears to succeed, but motion
    // delivery stops for the rest of the button-held gesture. Degrade to a
    // bounded drag rather than advertising a mode that breaks mid-drag.
    int op = 0, ev = 0, err = 0;
    if (g_display && XQueryExtension(g_display, "XWAYLAND", &op, &ev, &err))
        return false;
    return true;
}

A genuine unbounded drag on Wayland would need a different mechanism — XI2 raw
motion with a device grab, or the zwp_relative_pointer /
zwp_pointer_constraints protocols (not directly reachable from an X11 client)
— which is a much larger piece of work than the fallback above.

Reproducing

  1. Run on a Wayland session (GNOME/Mutter here) so the app is an XWayland client.
    Confirm with xdpyinfo | grep XWAYLAND.
  2. In a client, bracket pointer->begin_relative(sess, knob) from the widget's
    NEUI_EVENT_MOUSE_BUTTON_DOWN and pointer->end_relative(sess) from
    MOUSE_BUTTON_UP, exactly as d/pointer.h shows.
  3. Press on the widget and drag. The value moves once, by the first delta, then
    freezes for the rest of the gesture.
  4. Remove the begin_relative call and the same drag works normally.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions