From 6e01fd898f81518883bf624a842476e2c6b5fac7 Mon Sep 17 00:00:00 2001 From: LFRon Date: Mon, 5 Oct 2026 14:07:53 +0800 Subject: [PATCH] fix(xwayland): stop per-motion X pointer warp causing stuck clicks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 5fe9fd78f ("fix(xwayland): sync cursor global position to X server") warped the X pointer to the cursor position through xcb_warp_pointer on XCB_NONE/screen->root on every WCursor::positionChanged. Treeland's underlying problem: any client-side XWarpPointer to the root is routed by Xwayland through xwl_cursor_warped_to() and xwl_seat_emulate_pointer_warp(), which creates a persistent zwp_locked_pointer_v1 lock whenever the target lands inside the focused X window. Treeland's PointerConstraintsManager then activates that lock, so WCursor::applyPointerConstraint() pins the cursor to the locked anchor and stops dispatching motion, while Xwayland switches the pointer to relative + fake positions instead of absolute coordinates. The result was the reported XWayland symptom: the cursor jumped back and clicks landed on stale coordinates, or left-click did not register at all. The warp also used logical cursor coordinates while Xwayland's root space is DPR scaled, so the position mismatched under HiDPI. This commit removes the cursor-position sync entirely: the positionChanged hook, the warp helper and the seat-cursor watch are gone, so no movement triggers an X pointer warp and Xwayland no longer creates the spurious persistent lock. setSeat() keeps its null-safe seat argument. 当前问题:5fe9fd78f 在每次光标移动时通过 xcb_warp_pointer 把 X 指针 warp 到 屏幕 root。Xwayland 会把任何指向 root 的 XWarpPointer 交给 xwl_cursor_warped_to() / xwl_seat_emulate_pointer_warp(),只要目标落在当前 获得焦点的 X 窗口内,就会创建一个持久 zwp_locked_pointer_v1。treeland 的 PointerConstraintsManager 随后激活该锁,WCursor::applyPointerConstraint() 把 光标钉回锚点并停止派发移动,Xwayland 则改用相对增量加伪造位置、不再使用绝对 坐标,于是表现为光标回跳、点击落到旧坐标甚至左键点不下去;且 warp 用的是逻辑 坐标,而 Xwayland 的 root 空间按 DPR 放大,HiDPI 下坐标同样错位。 解决方式:完整移除该坐标同步机制(移动钩子、warp 辅助函数与 cursor 监听), 不再有任何移动触发 X 指针 warp,Xwayland 不再被诱导创建持久指针锁; setSeat() 保留空安全参数。 Log: 移除光标移动时向 Xwayland 发送的 X 指针 warp,避免 Xwayland 创建持久指针锁,修复 X 应用点击错位/左键点不下去 Influence: Xwayland 不再随光标移动 warp X 指针;依赖 XQueryPointer 全局坐标的 X 窗口在指针位于原生 Wayland 表面上方时坐标可能不刷新(恢复到 5fe9fd78f 之前的行为)。真实指针约束(游戏/远程桌面)路径不受影响。 --- waylib/src/server/protocols/wxwayland.cpp | 70 ----------------------- 1 file changed, 70 deletions(-) diff --git a/waylib/src/server/protocols/wxwayland.cpp b/waylib/src/server/protocols/wxwayland.cpp index 9bf800f5b..f7efee155 100644 --- a/waylib/src/server/protocols/wxwayland.cpp +++ b/waylib/src/server/protocols/wxwayland.cpp @@ -6,7 +6,6 @@ #include "private/wglobal_p.h" #include "private/wwaylandresource_p.h" #include "private/wxwaylandsurface_p.h" -#include "wcursor.h" #include "wseat.h" #include "wscoplistener.h" #include "wsocket.h" @@ -53,12 +52,6 @@ class Q_DECL_HIDDEN WXWaylandPrivate : public WWaylandResourcePrivate void on_surface_destroy(WXWaylandSurface *surface); // end slot function - // Cursor position sync to the Xwayland server. - void syncCursorPositionToXWayland(); - void watchSeatCursor(); - - QMetaObject::Connection cursorPositionChangedConnection; - // Async property reading struct PerWindowProps { @@ -431,69 +424,10 @@ void WXWayland::setDesktopProperties(uint32_t count, xcb_flush(connection); } -void WXWaylandPrivate::syncCursorPositionToXWayland() -{ - W_Q(WXWayland); - - auto *xwayland = q->handle(); - if (!xwayland || !xwayland->seat) - return; - - auto *cursor = WSeat::fromHandle(xwayland->seat)->cursor(); - if (!cursor) - return; - - auto *connection = q->xcbConnection(); - if (!connection || !screen) - return; - - // Xwayland only learns the pointer position via wl_pointer while the - // pointer is over an X window, so warp the X pointer to the real cursor - // position to keep the global position seen by X clients (e.g. via - // XQueryPointer) fresh while it moves over native Wayland surfaces. - // Warping to the current position (pointer over an X window) is a no-op. - const QPointF pos = cursor->position(); - xcb_warp_pointer(connection, XCB_NONE, screen->root, - 0, 0, 0, 0, qRound(pos.x()), qRound(pos.y())); -} - -void WXWaylandPrivate::watchSeatCursor() -{ - W_Q(WXWayland); - - // setSeat() may switch to another seat (or clear it), so rebind the - // cursor connection to the current seat on every call. The connection - // is auto-disconnected when the WXWayland instance is destroyed. - if (cursorPositionChangedConnection) { - QObject::disconnect(cursorPositionChangedConnection); - cursorPositionChangedConnection = {}; - } - - auto *xwayland = q->handle(); - auto *seat = xwayland ? xwayland->seat : nullptr; - if (!seat) - return; - - if (auto *seatObject = WSeat::fromHandle(seat)) { - if (auto *cursor = seatObject->cursor()) { - cursorPositionChangedConnection = QObject::connect(cursor, - &WCursor::positionChanged, - q, [this] { - syncCursorPositionToXWayland(); - }); - } - } -} - void WXWayland::setSeat(WSeat *seat) { - W_D(WXWayland); - if (auto handle = this->handle()) wlr_xwayland_set_seat(handle, seat ? seat->handle() : nullptr); - - d->watchSeatCursor(); - d->syncCursorPositionToXWayland(); } WSeat *WXWayland::seat() const @@ -611,10 +545,6 @@ void WXWayland::create(WServer *server) listeners()->add(&handle->events.ready, this, [this, d] (void *) { d->init(); - // The seat may be set before the Xwayland server starts; sync the - // current cursor position once the server client is available. - d->watchSeatCursor(); - d->syncCursorPositionToXWayland(); Q_EMIT ready(); });