From 124b9073f6b246886dfd29bcea58b6b46f2157cf Mon Sep 17 00:00:00 2001 From: LFRon Date: Tue, 9 Jun 2026 17:21:57 +0800 Subject: [PATCH] feat: support relative pointer and pointer constraints Add waylib wrappers for relative-pointer-v1 and pointer-constraints-v1, attach them during treeland startup, and route pointer motion through the seat so locked pointers receive relative motion without moving the compositor cursor. This fixes Xwayland games such as Minecraft Java losing correct mouse input, spinning the camera, or letting the pointer escape while grabbed. --- src/seat/helper.cpp | 4 + waylib/src/server/CMakeLists.txt | 6 + waylib/src/server/kernel/wcursor.cpp | 11 ++ waylib/src/server/kernel/wseat.cpp | 137 ++++++++++++++++++ waylib/src/server/kernel/wseat.h | 4 + .../server/protocols/WPointerConstraintsV1 | 1 + .../protocols/WRelativePointerManagerV1 | 1 + .../protocols/wpointerconstraintsv1.cpp | 72 +++++++++ .../server/protocols/wpointerconstraintsv1.h | 42 ++++++ .../protocols/wrelativepointermanagerv1.cpp | 66 +++++++++ .../protocols/wrelativepointermanagerv1.h | 38 +++++ 11 files changed, 382 insertions(+) create mode 100644 waylib/src/server/protocols/WPointerConstraintsV1 create mode 100644 waylib/src/server/protocols/WRelativePointerManagerV1 create mode 100644 waylib/src/server/protocols/wpointerconstraintsv1.cpp create mode 100644 waylib/src/server/protocols/wpointerconstraintsv1.h create mode 100644 waylib/src/server/protocols/wrelativepointermanagerv1.cpp create mode 100644 waylib/src/server/protocols/wrelativepointermanagerv1.h diff --git a/src/seat/helper.cpp b/src/seat/helper.cpp index 2fdabfbc9..48fc2cf9f 100644 --- a/src/seat/helper.cpp +++ b/src/seat/helper.cpp @@ -73,7 +73,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -1295,6 +1297,8 @@ void Helper::init(Treeland::Treeland *treeland) m_foreignToplevel = m_server->attach(); m_extForeignToplevelListV1 = m_server->attach(); + m_server->attach(); + m_server->attach(); connect(m_shellHandler, &ShellHandler::surfaceWrapperAdded, diff --git a/waylib/src/server/CMakeLists.txt b/waylib/src/server/CMakeLists.txt index 6031937e7..6bc670dd5 100644 --- a/waylib/src/server/CMakeLists.txt +++ b/waylib/src/server/CMakeLists.txt @@ -194,6 +194,8 @@ set(SOURCES protocols/private/wvirtualkeyboardv1.cpp protocols/ext_foreign_toplevel_image_capture_source.c #TODO: Remove after wlroots 0.20 protocols/wcursorshapemanagerv1.cpp + protocols/wrelativepointermanagerv1.cpp + protocols/wpointerconstraintsv1.cpp protocols/woutputmanagerv1.cpp protocols/wextforeigntoplevellistv1.cpp protocols/wsecuritycontextmanager.cpp @@ -287,6 +289,10 @@ set(HEADERS protocols/WInputPopupSurface protocols/wcursorshapemanagerv1.h protocols/WCursorShapeManagerV1 + protocols/wrelativepointermanagerv1.h + protocols/WRelativePointerManagerV1 + protocols/wpointerconstraintsv1.h + protocols/WPointerConstraintsV1 protocols/woutputmanagerv1.h protocols/WOutputManagerV1 protocols/wlayershell.h diff --git a/waylib/src/server/kernel/wcursor.cpp b/waylib/src/server/kernel/wcursor.cpp index 4d90d6265..22a0a9e2e 100644 --- a/waylib/src/server/kernel/wcursor.cpp +++ b/waylib/src/server/kernel/wcursor.cpp @@ -94,6 +94,17 @@ void WCursorPrivate::sendLeaveEvent(WInputDevice *device) void WCursorPrivate::on_motion(wlr_pointer_motion_event *event) { auto device = qw_pointer::from(event->pointer); + if (auto inputDevice = WInputDevice::fromHandle(device)) { + if (auto deviceSeat = inputDevice->seat()) { + deviceSeat->refreshPointerConstraint(); + deviceSeat->notifyRelativeMotion(event->time_msec, + QPointF(event->delta_x, event->delta_y), + QPointF(event->unaccel_dx, event->unaccel_dy)); + if (deviceSeat->pointerMotionLocked()) + return; + } + } + q_func()->move(device, QPointF(event->delta_x, event->delta_y)); processCursorMotion(device, event->time_msec); } diff --git a/waylib/src/server/kernel/wseat.cpp b/waylib/src/server/kernel/wseat.cpp index 86acda8e9..c5128cd27 100644 --- a/waylib/src/server/kernel/wseat.cpp +++ b/waylib/src/server/kernel/wseat.cpp @@ -5,6 +5,8 @@ #include "wcursor.h" #include "winputdevice.h" #include "woutput.h" +#include "wpointerconstraintsv1.h" +#include "wrelativepointermanagerv1.h" #include "wsurface.h" #include "wxdgsurface.h" #include "platformplugin/qwlrootsintegration.h" @@ -15,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +30,8 @@ #include #include +#include + #include #include #include @@ -137,6 +142,104 @@ class Q_DECL_HIDDEN WSeatPrivate : public WWrapObjectPrivate return nativeHandle()->keyboard_state.focused_surface; } + inline WPointerConstraintsV1 *pointerConstraints() const { + const auto *server = q_func()->server(); + return server ? server->findInterface() : nullptr; + } + + inline qw_pointer_constraint_v1 *constraintForSurface(WSurface *surface) const { + auto *constraints = pointerConstraints(); + return constraints ? constraints->constraintForSurface(surface, q_func()) : nullptr; + } + + inline bool pointerConstraintContains(qw_pointer_constraint_v1 *constraint, + const QPointF &surfacePos) const { + if (!constraint || !constraint->handle()) + return false; + + pixman_box32_t box; + return pixman_region32_contains_point(&constraint->handle()->region, + std::floor(surfacePos.x()), + std::floor(surfacePos.y()), + &box); + } + + inline bool pointerMotionLocked() const { + return activePointerConstraint + && activePointerConstraint->handle() + && activePointerConstraint->handle()->type == WLR_POINTER_CONSTRAINT_V1_LOCKED; + } + + inline void deactivatePointerConstraint() { + auto *constraint = activePointerConstraint.data(); + + activePointerConstraint.clear(); + QObject::disconnect(activePointerConstraintDestroyConnection); + QObject::disconnect(activePointerConstraintRegionConnection); + activePointerConstraintDestroyConnection = {}; + activePointerConstraintRegionConnection = {}; + + if (constraint && constraint->handle()) + constraint->send_deactivated(); + } + + inline void updatePointerConstraint(WSurface *surface, const QPointF &surfacePos) { + auto *constraint = constraintForSurface(surface); + if (!constraint) { + if (activePointerConstraint) + deactivatePointerConstraint(); + return; + } + + if (activePointerConstraint && activePointerConstraint->handle() == constraint->handle()) { + if (!pointerConstraintContains(constraint, surfacePos)) + deactivatePointerConstraint(); + return; + } + + if (activePointerConstraint) + deactivatePointerConstraint(); + + if (!pointerConstraintContains(constraint, surfacePos)) + return; + + activePointerConstraint = constraint; + activePointerConstraintDestroyConnection = + QObject::connect(constraint, &qw_pointer_constraint_v1::before_destroy, + q_func(), [this] { + activePointerConstraint.clear(); + QObject::disconnect(activePointerConstraintDestroyConnection); + QObject::disconnect(activePointerConstraintRegionConnection); + activePointerConstraintDestroyConnection = {}; + activePointerConstraintRegionConnection = {}; + }); + activePointerConstraintRegionConnection = + QObject::connect(constraint, &qw_pointer_constraint_v1::notify_set_region, + q_func(), [this] { + updatePointerConstraint(); + }); + constraint->send_activated(); + } + + inline void updatePointerConstraint() { + auto *focus = pointerFocusSurface(); + if (!focus) { + if (activePointerConstraint) + deactivatePointerConstraint(); + return; + } + + auto *surface = WSurface::fromHandle(focus); + if (!surface) { + if (activePointerConstraint) + deactivatePointerConstraint(); + return; + } + + const auto &pointerState = nativeHandle()->pointer_state; + updatePointerConstraint(surface, QPointF(pointerState.sx, pointerState.sy)); + } + inline bool doNotifyMotion(WSurface *target, QObject *eventObject, QPointF localPos, uint32_t timestamp) { if (target) { if (pointerFocusSurface()) { @@ -148,8 +251,13 @@ class Q_DECL_HIDDEN WSeatPrivate : public WWrapObjectPrivate // take pointer focus for this surface. doEnter(target, eventObject, localPos); } + + updatePointerConstraint(target, localPos); } + if (pointerMotionLocked()) + return true; + handle()->pointer_notify_motion(timestamp, localPos.x(), localPos.y()); return true; } @@ -183,6 +291,9 @@ class Q_DECL_HIDDEN WSeatPrivate : public WWrapObjectPrivate } auto tmp = oldPointerFocusSurface; oldPointerFocusSurface = handle()->handle()->pointer_state.focused_surface; + if (activePointerConstraint && activePointerConstraint->handle()->surface != surface->handle()->handle()) + deactivatePointerConstraint(); + handle()->pointer_notify_enter(surface->handle()->handle(), position.x(), position.y()); if (!pointerFocusSurface()) { // Because if the last pointer focus surface is a popup, the 'pointerNotifyEnter' @@ -207,9 +318,13 @@ class Q_DECL_HIDDEN WSeatPrivate : public WWrapObjectPrivate }); } + updatePointerConstraint(surface, position); + return true; } inline void doClearPointerFocus() { + if (activePointerConstraint) + deactivatePointerConstraint(); pointerFocusEventObject.clear(); handle()->pointer_notify_clear_focus(); Q_ASSERT(!handle()->handle()->pointer_state.focused_surface); @@ -370,7 +485,10 @@ class Q_DECL_HIDDEN WSeatPrivate : public WWrapObjectPrivate QPointer focusWindow; QPointer pointerFocusEventObject; QPointer m_keyboardFocusSurface; + QPointer activePointerConstraint; QMetaObject::Connection onEventObjectDestroy; + QMetaObject::Connection activePointerConstraintDestroyConnection; + QMetaObject::Connection activePointerConstraintRegionConnection; wlr_surface *oldPointerFocusSurface = nullptr; bool gestureActive = false; @@ -1168,6 +1286,25 @@ void WSeat::setAlwaysUpdateHoverTarget(bool newIgnoreSurfacePointerEventExclusiv Q_EMIT alwaysUpdateHoverTargetChanged(); } +void WSeat::notifyRelativeMotion(uint32_t timestamp, const QPointF &delta, + const QPointF &unacceleratedDelta) +{ + if (auto *manager = server() ? server()->findInterface() : nullptr) + manager->sendRelativeMotion(this, timestamp, delta, unacceleratedDelta); +} + +void WSeat::refreshPointerConstraint() +{ + W_D(WSeat); + d->updatePointerConstraint(); +} + +bool WSeat::pointerMotionLocked() const +{ + W_DC(WSeat); + return d->pointerMotionLocked(); +} + void WSeat::notifyMotion(WCursor *cursor, WInputDevice *device, uint32_t timestamp) { W_D(WSeat); diff --git a/waylib/src/server/kernel/wseat.h b/waylib/src/server/kernel/wseat.h index 8a58770e0..040f1f294 100644 --- a/waylib/src/server/kernel/wseat.h +++ b/waylib/src/server/kernel/wseat.h @@ -140,6 +140,10 @@ class WAYLIB_SERVER_EXPORT WSeat : public WWrapObject, public WServerInterface bool filterUnacceptedEvent(QWindow *targetWindow, QInputEvent *event); // pointer + void notifyRelativeMotion(uint32_t timestamp, const QPointF &delta, + const QPointF &unacceleratedDelta); + void refreshPointerConstraint(); + bool pointerMotionLocked() const; void notifyMotion(WCursor *cursor, WInputDevice *device, uint32_t timestamp); void notifyButton(WCursor *cursor, WInputDevice *device, Qt::MouseButton button, wl_pointer_button_state_t state, diff --git a/waylib/src/server/protocols/WPointerConstraintsV1 b/waylib/src/server/protocols/WPointerConstraintsV1 new file mode 100644 index 000000000..5d519a897 --- /dev/null +++ b/waylib/src/server/protocols/WPointerConstraintsV1 @@ -0,0 +1 @@ +#include "wpointerconstraintsv1.h" diff --git a/waylib/src/server/protocols/WRelativePointerManagerV1 b/waylib/src/server/protocols/WRelativePointerManagerV1 new file mode 100644 index 000000000..8708937e3 --- /dev/null +++ b/waylib/src/server/protocols/WRelativePointerManagerV1 @@ -0,0 +1 @@ +#include "wrelativepointermanagerv1.h" diff --git a/waylib/src/server/protocols/wpointerconstraintsv1.cpp b/waylib/src/server/protocols/wpointerconstraintsv1.cpp new file mode 100644 index 000000000..0d5b587d7 --- /dev/null +++ b/waylib/src/server/protocols/wpointerconstraintsv1.cpp @@ -0,0 +1,72 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "wpointerconstraintsv1.h" + +#include "wseat.h" +#include "wsurface.h" + +#include "private/wglobal_p.h" + +#include +#include +#include + +QW_USE_NAMESPACE +WAYLIB_SERVER_BEGIN_NAMESPACE + +class Q_DECL_HIDDEN WPointerConstraintsV1Private : public WObjectPrivate +{ +public: + explicit WPointerConstraintsV1Private(WPointerConstraintsV1 *qq) + : WObjectPrivate(qq) + { + } +}; + +WPointerConstraintsV1::WPointerConstraintsV1() + : WObject(*new WPointerConstraintsV1Private(this)) +{ +} + +qw_pointer_constraints_v1 *WPointerConstraintsV1::handle() const +{ + return nativeInterface(); +} + +qw_pointer_constraint_v1 *WPointerConstraintsV1::constraintForSurface(WSurface *surface, + const WSeat *seat) const +{ + if (!handle() || !surface || !seat) + return nullptr; + + return qw_pointer_constraint_v1::from( + handle()->constraint_for_surface(surface->handle()->handle(), seat->nativeHandle())); +} + +QByteArrayView WPointerConstraintsV1::interfaceName() const +{ + return "zwp_pointer_constraints_v1"; +} + +void WPointerConstraintsV1::create(WServer *server) +{ + if (m_handle) + return; + + m_handle = qw_pointer_constraints_v1::create(*server->handle()); + QObject::connect(handle(), &qw_pointer_constraints_v1::notify_new_constraint, + this, [this](wlr_pointer_constraint_v1 *constraint) { + Q_EMIT newConstraint(qw_pointer_constraint_v1::from(constraint)); + }); +} + +wl_global *WPointerConstraintsV1::global() const +{ + if (!handle()) + return nullptr; + + return handle()->handle()->global; +} + +WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/protocols/wpointerconstraintsv1.h b/waylib/src/server/protocols/wpointerconstraintsv1.h new file mode 100644 index 000000000..7a29bf22b --- /dev/null +++ b/waylib/src/server/protocols/wpointerconstraintsv1.h @@ -0,0 +1,42 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include + +#include + +QW_BEGIN_NAMESPACE +class qw_pointer_constraint_v1; +class qw_pointer_constraints_v1; +QW_END_NAMESPACE + +struct wlr_pointer_constraint_v1; + +WAYLIB_SERVER_BEGIN_NAMESPACE + +class WSeat; +class WSurface; + +class WAYLIB_SERVER_EXPORT WPointerConstraintsV1 : public QObject, public WObject, public WServerInterface +{ + Q_OBJECT + +public: + explicit WPointerConstraintsV1(); + + QW_NAMESPACE::qw_pointer_constraints_v1 *handle() const; + QW_NAMESPACE::qw_pointer_constraint_v1 *constraintForSurface(WSurface *surface, const WSeat *seat) const; + + QByteArrayView interfaceName() const override; + +Q_SIGNALS: + void newConstraint(QW_NAMESPACE::qw_pointer_constraint_v1 *constraint); + +protected: + void create(WServer *server) override; + wl_global *global() const override; +}; + +WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/protocols/wrelativepointermanagerv1.cpp b/waylib/src/server/protocols/wrelativepointermanagerv1.cpp new file mode 100644 index 000000000..2e0b766d3 --- /dev/null +++ b/waylib/src/server/protocols/wrelativepointermanagerv1.cpp @@ -0,0 +1,66 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "wrelativepointermanagerv1.h" + +#include "wseat.h" + +#include "private/wglobal_p.h" + +#include +#include + +QW_USE_NAMESPACE +WAYLIB_SERVER_BEGIN_NAMESPACE + +class Q_DECL_HIDDEN WRelativePointerManagerV1Private : public WObjectPrivate +{ +public: + explicit WRelativePointerManagerV1Private(WRelativePointerManagerV1 *qq) + : WObjectPrivate(qq) + { + } +}; + +WRelativePointerManagerV1::WRelativePointerManagerV1() + : WObject(*new WRelativePointerManagerV1Private(this)) +{ +} + +qw_relative_pointer_manager_v1 *WRelativePointerManagerV1::handle() const +{ + return nativeInterface(); +} + +void WRelativePointerManagerV1::sendRelativeMotion(WSeat *seat, uint32_t timeMsec, + const QPointF &delta, + const QPointF &unacceleratedDelta) +{ + if (!seat || !handle()) + return; + + handle()->send_relative_motion(seat->nativeHandle(), uint64_t(timeMsec) * 1000, + delta.x(), delta.y(), + unacceleratedDelta.x(), unacceleratedDelta.y()); +} + +QByteArrayView WRelativePointerManagerV1::interfaceName() const +{ + return "zwp_relative_pointer_manager_v1"; +} + +void WRelativePointerManagerV1::create(WServer *server) +{ + if (!m_handle) + m_handle = qw_relative_pointer_manager_v1::create(*server->handle()); +} + +wl_global *WRelativePointerManagerV1::global() const +{ + if (!handle()) + return nullptr; + + return handle()->handle()->global; +} + +WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/protocols/wrelativepointermanagerv1.h b/waylib/src/server/protocols/wrelativepointermanagerv1.h new file mode 100644 index 000000000..ec267d13d --- /dev/null +++ b/waylib/src/server/protocols/wrelativepointermanagerv1.h @@ -0,0 +1,38 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include + +#include +#include + +QW_BEGIN_NAMESPACE +class qw_relative_pointer_manager_v1; +QW_END_NAMESPACE + +WAYLIB_SERVER_BEGIN_NAMESPACE + +class WSeat; + +class WAYLIB_SERVER_EXPORT WRelativePointerManagerV1 : public QObject, public WObject, public WServerInterface +{ + Q_OBJECT + +public: + explicit WRelativePointerManagerV1(); + + QW_NAMESPACE::qw_relative_pointer_manager_v1 *handle() const; + + void sendRelativeMotion(WSeat *seat, uint32_t timeMsec, const QPointF &delta, + const QPointF &unacceleratedDelta); + + QByteArrayView interfaceName() const override; + +protected: + void create(WServer *server) override; + wl_global *global() const override; +}; + +WAYLIB_SERVER_END_NAMESPACE