From b20cd6eb982512966c8564faf4baf3c0f7b2583e Mon Sep 17 00:00:00 2001 From: luhongxu Date: Wed, 12 Aug 2026 00:33:05 +0800 Subject: [PATCH] feat: support pointer-constraints protocol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Add WPointerConstraintsV1 wrapper for zwp_pointer_constraints_v1 2. Add WRelativePointerManagerV1 wrapper for zwp_relative_pointer_manager_v1 3. Implement cursor constraint enforcement in WCursorPrivate 4. Locked: send relative motion via sendRelativeMotion and warp back to anchor 5. Confined: clamp cursor to constraint region using inline pixman regionConfine 6. Inline pixman-based regionConfine to avoid wlroots util linking dependency 7. Add setActivePointerConstraint/activePointerConstraint API on WCursor Log: Added pointer lock and confinement support for wayland clients Influence: 1. Test locked pointer with a 3D mouselook client 2. Test confined pointer with a region-limited client 3. Verify cursor warps back to anchor during locked constraint 4. Verify confined cursor stays within the constraint region 5. Verify constraint activation/deactivation lifecycle feat: 支持 pointer-constraints 协议 1. 新增 WPointerConstraintsV1 包装 zwp_pointer_constraints_v1 2. 新增 WRelativePointerManagerV1 包装 zwp_relative_pointer_manager_v1 3. 在 WCursorPrivate 中实现光标约束强制 4. Locked: 通过 sendRelativeMotion 发送相对增量并回 warp 锚点 5. Confined: 用内联 pixman regionConfine 钳制到约束 region 6. 内联基于 pixman 的 regionConfine 避免 wlroots util 链接依赖 7. 在 WCursor 上新增 setActivePointerConstraint/activePointerConstraint 接口 Log: 新增 Wayland 客户端指针锁定和限制功能 Influence: 1. 使用 3D 视角客户端测试锁定指针 2. 使用区域限制客户端测试 confined 指针 3. 验证锁定约束期间光标回 warp 到锚点 4. 验证 confined 光标保持在约束区域内 5. 验证约束激活/失活生命周期 --- src/server/CMakeLists.txt | 4 + src/server/kernel/private/wcursor_p.h | 7 + src/server/kernel/wcursor.cpp | 167 +++++++++++++++++- src/server/kernel/wcursor.h | 4 + .../protocols/wpointerconstraintsv1.cpp | 79 +++++++++ src/server/protocols/wpointerconstraintsv1.h | 43 +++++ src/server/protocols/wrelativepointerv1.cpp | 78 ++++++++ src/server/protocols/wrelativepointerv1.h | 41 +++++ 8 files changed, 421 insertions(+), 2 deletions(-) create mode 100644 src/server/protocols/wpointerconstraintsv1.cpp create mode 100644 src/server/protocols/wpointerconstraintsv1.h create mode 100644 src/server/protocols/wrelativepointerv1.cpp create mode 100644 src/server/protocols/wrelativepointerv1.h diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index 16cdcac64..4cae7752d 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -143,6 +143,8 @@ set(SOURCES protocols/private/wtextinputv3.cpp protocols/private/wvirtualkeyboardv1.cpp protocols/wcursorshapemanagerv1.cpp + protocols/wpointerconstraintsv1.cpp + protocols/wrelativepointerv1.cpp protocols/woutputmanagerv1.cpp protocols/wextforeigntoplevellistv1.cpp @@ -228,6 +230,8 @@ set(HEADERS protocols/winputpopupsurface.h protocols/WInputPopupSurface protocols/wcursorshapemanagerv1.h + protocols/wpointerconstraintsv1.h + protocols/wrelativepointerv1.h protocols/WCursorShapeManagerV1 protocols/woutputmanagerv1.h protocols/WOutputManagerV1 diff --git a/src/server/kernel/private/wcursor_p.h b/src/server/kernel/private/wcursor_p.h index 01301df52..712e7585f 100644 --- a/src/server/kernel/private/wcursor_p.h +++ b/src/server/kernel/private/wcursor_p.h @@ -29,6 +29,7 @@ struct wlr_pointer_pinch_end_event; struct wlr_pointer_hold_begin_event; struct wlr_pointer_hold_end_event; struct wlr_cursor; +struct wlr_pointer_constraint_v1; struct wlr_touch_down_event; struct wlr_touch_up_event; struct wlr_touch_motion_event; @@ -72,6 +73,9 @@ class Q_DECL_HIDDEN WCursorPrivate : public WWrapObjectPrivate void connect(); void processCursorMotion(QW_NAMESPACE::qw_pointer *device, uint32_t time); + bool applyPointerConstraint(QW_NAMESPACE::qw_pointer *device, uint32_t timeMsec, + double dx, double dy, double dxUnaccel, double dyUnaccel, + const QPointF &oldPos); W_DECLARE_PUBLIC(WCursor) @@ -88,6 +92,9 @@ class Q_DECL_HIDDEN WCursorPrivate : public WWrapObjectPrivate Qt::MouseButton button = Qt::NoButton; QPointF lastPressedOrTouchDownPosition; bool visible = true; + wlr_pointer_constraint_v1 *activeConstraint = nullptr; + QPointF lockedWarpTarget; + bool cursorVisibleBeforeLock = true; }; WAYLIB_SERVER_END_NAMESPACE diff --git a/src/server/kernel/wcursor.cpp b/src/server/kernel/wcursor.cpp index a19b3e78e..a0522bc02 100644 --- a/src/server/kernel/wcursor.cpp +++ b/src/server/kernel/wcursor.cpp @@ -30,6 +30,81 @@ #include #include +#include "wrelativepointerv1.h" +#include "wserver.h" + +#include +#include +#include +#include + +// Inline replacement for wlr_region_confine() to avoid linking the wlroots +// util library directly from libwaylibserver (it is only available indirectly +// via QWlroots). The implementation mirrors wlr_region_confine from +// wlroots/util/region.c, using only the pixman API that waylibserver already +// links against. +static void regionConfine(const pixman_region32_t *region, double x1, double y1, + double x2, double y2, double *x2_out, double *y2_out, + const pixman_box32_t *box) +{ + // Clamp the target to the box. + double x = x2; + double y = y2; + x = std::max(x, box->x1); + x = std::min(x, box->x2); + y = std::max(y, box->y1); + y = std::min(y, box->y2); + + // If the clamped point is inside the region we're done. + if (pixman_region32_contains_point(region, std::lround(x), std::lround(y), nullptr)) { + *x2_out = x; + *y2_out = y; + return; + } + + // Calculate the delta from the source point. + const double dx = x - x1; + const double dy = y - y1; + const double delta = std::max(std::fabs(dx), std::fabs(dy)); + if (delta < 1e-9) { + *x2_out = x1; + *y2_out = y1; + return; + } + + // Step along the line from (x1,y1) to (x,y) pixel by pixel. + const double step_x = dx / delta; + const double step_y = dy / delta; + for (int i = static_cast(delta); i > 0; i--) { + x -= step_x; + y -= step_y; + if (pixman_region32_contains_point(region, std::lround(x), std::lround(y), nullptr)) { + *x2_out = x; + *y2_out = y; + return; + } + } + + *x2_out = x1; + *y2_out = y1; +} + +static bool regionConfineWrapper(const pixman_region32_t *region, double x1, double y1, + double x2, double y2, double *x2_out, double *y2_out) +{ + int nrects = 0; + const pixman_box32_t *rects = pixman_region32_rectangles(region, &nrects); + if (nrects == 0) + return false; + + for (int i = 0; i < nrects; i++) { + const pixman_box32_t *box = &rects[i]; + regionConfine(region, x1, y1, x2, y2, x2_out, y2_out, box); + return true; + } + return false; +} + QW_USE_NAMESPACE WAYLIB_SERVER_BEGIN_NAMESPACE @@ -80,15 +155,22 @@ void WCursorPrivate::sendLeaveEvent(WInputDevice *device) void WCursorPrivate::on_motion(wlr_pointer_motion_event *event) { auto device = qw_pointer::from(event->pointer); + const QPointF oldPos = q_func()->position(); q_func()->move(device, QPointF(event->delta_x, event->delta_y)); - processCursorMotion(device, event->time_msec); + if (!applyPointerConstraint(device, event->time_msec, event->delta_x, event->delta_y, + event->unaccel_dx, event->unaccel_dy, oldPos)) + processCursorMotion(device, event->time_msec); } void WCursorPrivate::on_motion_absolute(wlr_pointer_motion_absolute_event *event) { auto device = qw_pointer::from(event->pointer); + const QPointF oldPos = q_func()->position(); q_func()->setScalePosition(device, QPointF(event->x, event->y)); - processCursorMotion(device, event->time_msec); + const QPointF delta = q_func()->position() - oldPos; + if (!applyPointerConstraint(device, event->time_msec, delta.x(), delta.y(), + delta.x(), delta.y(), oldPos)) + processCursorMotion(device, event->time_msec); } void WCursorPrivate::on_button(wlr_pointer_button_event *event) @@ -321,6 +403,50 @@ void WCursorPrivate::processCursorMotion(qw_pointer *device, uint32_t time) seat->notifyMotion(q, WInputDevice::fromHandle(device), time); } +bool WCursorPrivate::applyPointerConstraint(qw_pointer *device, uint32_t timeMsec, + double dx, double dy, + double dxUnaccel, double dyUnaccel, + const QPointF &oldPos) +{ + W_Q(WCursor); + if (!activeConstraint) + return false; + + wlr_seat *wlrSeat = seat ? seat->handle()->handle() : nullptr; + if (!wlrSeat) + return false; + + if (activeConstraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) { + if (auto *server = seat->server()) { + if (auto *relative = server->findInterface()) { + relative->sendRelativeMotion(wlrSeat, static_cast(timeMsec) * 1000, + dx, dy, dxUnaccel, dyUnaccel); + } + } + // Warp back to the anchor so physical movement does not accumulate. + q->setPosition(device, lockedWarpTarget); + return true; + } + + if (activeConstraint->type == WLR_POINTER_CONSTRAINT_V1_CONFINED) { + // The constraint region and the seat's last surface-local position are + // both in logical coordinates, so the output-layout delta maps directly. + const double sx = wlrSeat->pointer_state.sx; + const double sy = wlrSeat->pointer_state.sy; + double confinedSx = sx + dx; + double confinedSy = sy + dy; + if (regionConfineWrapper(&activeConstraint->region, sx, sy, + confinedSx, confinedSy, &confinedSx, &confinedSy)) { + q->setPosition(device, oldPos + QPointF(confinedSx - sx, confinedSy - sy)); + } else { + q->setPosition(device, oldPos); + } + return false; + } + + return false; +} + WCursor::WCursor(WCursorPrivate &dd, QObject *parent) : WWrapObject(dd, parent) { @@ -660,6 +786,43 @@ void WCursor::setVisible(bool visible) Q_EMIT visibleChanged(); } +void WCursor::setActivePointerConstraint(wlr_pointer_constraint_v1 *constraint) +{ + W_D(WCursor); + if (d->activeConstraint == constraint) + return; + + if (d->activeConstraint + && d->activeConstraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) + setVisible(d->cursorVisibleBeforeLock); + + d->activeConstraint = constraint; + + if (!constraint) + return; + + if (constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) { + d->cursorVisibleBeforeLock = isVisible(); + setVisible(false); + wlr_seat *wlrSeat = d->seat ? d->seat->handle()->handle() : nullptr; + if (wlrSeat && constraint->current.cursor_hint.enabled) { + const double sx = wlrSeat->pointer_state.sx; + const double sy = wlrSeat->pointer_state.sy; + d->lockedWarpTarget = position() + + QPointF(constraint->current.cursor_hint.x - sx, + constraint->current.cursor_hint.y - sy); + } else { + d->lockedWarpTarget = position(); + } + } +} + +wlr_pointer_constraint_v1 *WCursor::activePointerConstraint() const +{ + W_DC(WCursor); + return d->activeConstraint; +} + QPointF WCursor::position() const { W_DC(WCursor); diff --git a/src/server/kernel/wcursor.h b/src/server/kernel/wcursor.h index 3755e3670..4a1bb9b7c 100644 --- a/src/server/kernel/wcursor.h +++ b/src/server/kernel/wcursor.h @@ -22,6 +22,8 @@ class qw_output_cursor; class qw_surface; QW_END_NAMESPACE +struct wlr_pointer_constraint_v1; + WAYLIB_SERVER_BEGIN_NAMESPACE class WSeat; @@ -76,6 +78,8 @@ class WAYLIB_SERVER_EXPORT WCursor : public WWrapObject bool isVisible() const; void setVisible(bool visible); + void setActivePointerConstraint(wlr_pointer_constraint_v1 *constraint); + wlr_pointer_constraint_v1 *activePointerConstraint() const; QPointF position() const; QPointF lastPressedOrTouchDownPosition() const; diff --git a/src/server/protocols/wpointerconstraintsv1.cpp b/src/server/protocols/wpointerconstraintsv1.cpp new file mode 100644 index 000000000..8529f7205 --- /dev/null +++ b/src/server/protocols/wpointerconstraintsv1.cpp @@ -0,0 +1,79 @@ +// 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 "private/wglobal_p.h" + +#include +#include + +#include + +WAYLIB_SERVER_BEGIN_NAMESPACE + +using QW_NAMESPACE::qw_pointer_constraints_v1; + +class Q_DECL_HIDDEN WPointerConstraintsV1Private : public WObjectPrivate +{ +public: + WPointerConstraintsV1Private(WPointerConstraintsV1 *qq) + : WObjectPrivate(qq) + { + } + + inline qw_pointer_constraints_v1 *handle() const { + return q_func()->nativeInterface(); + } + + inline wlr_pointer_constraints_v1 *nativeHandle() const { + Q_ASSERT(handle()); + return handle()->handle(); + } + + W_DECLARE_PUBLIC(WPointerConstraintsV1) +}; + +WPointerConstraintsV1::WPointerConstraintsV1() + : WObject(*new WPointerConstraintsV1Private(this)) +{ + +} + +qw_pointer_constraints_v1 *WPointerConstraintsV1::handle() const +{ + return nativeInterface(); +} + +QByteArrayView WPointerConstraintsV1::interfaceName() const +{ + return "zwp_pointer_constraints_v1"; +} + +wlr_pointer_constraint_v1 *WPointerConstraintsV1::constraintForSurface(wlr_surface *surface, wlr_seat *seat) const +{ + if (!m_handle) + return nullptr; + + return handle()->constraint_for_surface(surface, seat); +} + +void WPointerConstraintsV1::create(WServer *server) +{ + if (!m_handle) { + 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(constraint); + }); + } +} + +wl_global *WPointerConstraintsV1::global() const +{ + W_D(const WPointerConstraintsV1); + if (m_handle) + return d->nativeHandle()->global; + + return nullptr; +} + +WAYLIB_SERVER_END_NAMESPACE diff --git a/src/server/protocols/wpointerconstraintsv1.h b/src/server/protocols/wpointerconstraintsv1.h new file mode 100644 index 000000000..54150f79c --- /dev/null +++ b/src/server/protocols/wpointerconstraintsv1.h @@ -0,0 +1,43 @@ +// 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_constraints_v1; +QW_END_NAMESPACE + +struct wlr_pointer_constraint_v1; +struct wlr_seat; +struct wlr_surface; + +WAYLIB_SERVER_BEGIN_NAMESPACE + +class WPointerConstraintsV1Private; +class WAYLIB_SERVER_EXPORT WPointerConstraintsV1 : public QObject, public WObject, public WServerInterface +{ + Q_OBJECT + W_DECLARE_PRIVATE(WPointerConstraintsV1) + +public: + explicit WPointerConstraintsV1(); + + QW_NAMESPACE::qw_pointer_constraints_v1 *handle() const; + + QByteArrayView interfaceName() const override; + + wlr_pointer_constraint_v1 *constraintForSurface(wlr_surface *surface, wlr_seat *seat) const; + +Q_SIGNALS: + void newConstraint(wlr_pointer_constraint_v1 *constraint); + +protected: + void create(WServer *server) override; + wl_global *global() const override; +}; + +WAYLIB_SERVER_END_NAMESPACE diff --git a/src/server/protocols/wrelativepointerv1.cpp b/src/server/protocols/wrelativepointerv1.cpp new file mode 100644 index 000000000..7a9f40d48 --- /dev/null +++ b/src/server/protocols/wrelativepointerv1.cpp @@ -0,0 +1,78 @@ +// 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 "wrelativepointerv1.h" +#include "private/wglobal_p.h" + +#include +#include + +#include + +WAYLIB_SERVER_BEGIN_NAMESPACE + +using QW_NAMESPACE::qw_relative_pointer_manager_v1; + +class Q_DECL_HIDDEN WRelativePointerManagerV1Private : public WObjectPrivate +{ +public: + WRelativePointerManagerV1Private(WRelativePointerManagerV1 *qq) + : WObjectPrivate(qq) + { + } + + inline qw_relative_pointer_manager_v1 *handle() const { + return q_func()->nativeInterface(); + } + + inline wlr_relative_pointer_manager_v1 *nativeHandle() const { + Q_ASSERT(handle()); + return handle()->handle(); + } + + W_DECLARE_PUBLIC(WRelativePointerManagerV1) +}; + +WRelativePointerManagerV1::WRelativePointerManagerV1() + : WObject(*new WRelativePointerManagerV1Private(this)) +{ + +} + +qw_relative_pointer_manager_v1 *WRelativePointerManagerV1::handle() const +{ + return nativeInterface(); +} + +QByteArrayView WRelativePointerManagerV1::interfaceName() const +{ + return "zwp_relative_pointer_manager_v1"; +} + +void WRelativePointerManagerV1::sendRelativeMotion(wlr_seat *seat, uint64_t timeUsec, + double dx, double dy, + double dxUnaccel, double dyUnaccel) const +{ + if (!m_handle || !seat) + return; + + handle()->send_relative_motion(seat, timeUsec, dx, dy, dxUnaccel, dyUnaccel); +} + +void WRelativePointerManagerV1::create(WServer *server) +{ + if (!m_handle) { + m_handle = qw_relative_pointer_manager_v1::create(*server->handle()); + } +} + +wl_global *WRelativePointerManagerV1::global() const +{ + W_D(const WRelativePointerManagerV1); + if (m_handle) + return d->nativeHandle()->global; + + return nullptr; +} + +WAYLIB_SERVER_END_NAMESPACE diff --git a/src/server/protocols/wrelativepointerv1.h b/src/server/protocols/wrelativepointerv1.h new file mode 100644 index 000000000..68ea489f2 --- /dev/null +++ b/src/server/protocols/wrelativepointerv1.h @@ -0,0 +1,41 @@ +// 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 + +struct wlr_seat; + +WAYLIB_SERVER_BEGIN_NAMESPACE + +class WRelativePointerManagerV1Private; +class WAYLIB_SERVER_EXPORT WRelativePointerManagerV1 : public QObject, public WObject, public WServerInterface +{ + Q_OBJECT + W_DECLARE_PRIVATE(WRelativePointerManagerV1) + +public: + explicit WRelativePointerManagerV1(); + + QW_NAMESPACE::qw_relative_pointer_manager_v1 *handle() const; + + QByteArrayView interfaceName() const override; + + void sendRelativeMotion(wlr_seat *seat, uint64_t timeUsec, + double dx, double dy, double dxUnaccel, double dyUnaccel) const; + +protected: + void create(WServer *server) override; + wl_global *global() const override; +}; + +WAYLIB_SERVER_END_NAMESPACE