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 |
Wayland — XDG_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
- Run on a Wayland session (GNOME/Mutter here) so the app is an XWayland client.
Confirm with xdpyinfo | grep XWAYLAND.
- 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.
- Press on the widget and drag. The value moves once, by the first delta, then
freezes for the rest of the gesture.
- Remove the
begin_relative call and the same drag works normally.
Summary
On Linux under XWayland, a drag using relative (unbounded) pointer mode
(
NEUI_API_POINTER/begin_relative) dies after a single motion event. Thefirst 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_relativearound a knob drag (the usaged/pointer.hdocuments), 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
19c0c579a17ca14c75b788bfd359f4c259b63c95(origin/bp-review)neui.host.crossplatform(only host on Linux)XDG_SESSION_TYPE=wayland, GNOME Shell 49.2 / MutterXWAYLANDextension presentNEUI_HAS_XI2=1); also reproduced with XI2 compiled outSymptom
Client code is the documented pattern —
begin_relativefrom the widget'sMOUSE_BUTTON_DOWNhandler,end_relativefromMOUSE_BUTTON_UP.begin_relativereturns true, so the mode engages.Instrumented trace of one drag (host-side logging added to
dispatch_motionplus a raw log of every event entering
dispatch_x_event, before thefind_windowfilter):Every branch behaves as designed. The echo landing exactly on the anchor proves
XWarpPointeritself took effect. After that warp, motion delivery simply stopsfor the remainder of the gesture; it resumes the instant the button comes up.
Ruled out
platform_linux.cpp:1677, the!g_xi2_motion_seen || (state & ButtonNMask)condition). Rebuilding withlibXi hidden from pkg-config, so
NEUI_HAS_XI2is undefined and the gatecollapses to an unconditional
dispatch_motion, reproduces identically.hosts/shared/relative_pointer.h,relative_is_warp_echo). It fires exactly twice in the trace, both on genuineanchor-position events. It is not swallowing real motion.
find_window, so eventsaddressed to an unrecognised window would still have been logged. Nothing
arrives at all.
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
XWarpPointeris the cause. Waylanddeliberately 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 returnstrueunconditionally on Linux, so the mode is advertised on XWayland and clients have
no way to know it will not work.
Suggested fix
d/pointer.halready specifies the degradation path:XWayland is such a platform. Having
platform_supports_relative_pointer()return
falsewhen theXWAYLANDextension is present would makebegin_relativereturnfalse, and every client would fall back to an ordinarybounded drag — which works correctly here. Native X11 keeps the feature:
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_constraintsprotocols (not directly reachable from an X11 client)— which is a much larger piece of work than the fallback above.
Reproducing
Confirm with
xdpyinfo | grep XWAYLAND.pointer->begin_relative(sess, knob)from the widget'sNEUI_EVENT_MOUSE_BUTTON_DOWNandpointer->end_relative(sess)fromMOUSE_BUTTON_UP, exactly asd/pointer.hshows.freezes for the rest of the gesture.
begin_relativecall and the same drag works normally.