diff --git a/pointer_input.cpp b/pointer_input.cpp index 68c9e7e08..955fd8e9a 100644 --- a/pointer_input.cpp +++ b/pointer_input.cpp @@ -594,6 +594,12 @@ void PointerInputRedirection::disconnectConfinedPointerRegionConnection() m_confinedPointerRegionConnection = QMetaObject::Connection(); } +void PointerInputRedirection::disconnectLockedPointerAboutToBeUnboundConnection() +{ + disconnect(m_lockedPointerAboutToBeUnboundConnection); + m_lockedPointerAboutToBeUnboundConnection = QMetaObject::Connection(); +} + void PointerInputRedirection::disconnectPointerConstraintsConnection() { disconnect(m_constraintsConnection); @@ -689,8 +695,13 @@ void PointerInputRedirection::updatePointerConstraints() if (lock) { if (lock->isLocked()) { if (!canConstrain) { + const auto hint = lock->cursorPositionHint(); lock->setLocked(false); m_locked = false; + disconnectLockedPointerAboutToBeUnboundConnection(); + if (! (hint.x() < 0 || hint.y() < 0) && m_window) { + processMotion(m_window->pos() - m_window->clientContentPos() + hint, waylandServer()->seat()->timestamp()); + } } return; } @@ -698,6 +709,24 @@ void PointerInputRedirection::updatePointerConstraints() if (canConstrain && r.contains(m_pos.toPoint())) { lock->setLocked(true); m_locked = true; + + // The client might cancel pointer locking from its side by unbinding the LockedPointerInterface. + // In this case the cached cursor position hint must be fetched before the resource goes away + m_lockedPointerAboutToBeUnboundConnection = connect(lock.data(), &KWayland::Server::LockedPointerInterface::aboutToBeUnbound, this, + [this, lock]() { + const auto hint = lock->cursorPositionHint(); + if (hint.x() < 0 || hint.y() < 0 || !m_window) { + return; + } + auto globalHint = m_window->pos() - m_window->clientContentPos() + hint; + + // When the resource finally goes away, reposition the cursor according to the hint + connect(lock.data(), &KWayland::Server::LockedPointerInterface::unbound, this, + [this, globalHint]() { + processMotion(globalHint, waylandServer()->seat()->timestamp()); + }); + } + ); OSD::show(i18nc("notification about mouse pointer locked", "Pointer locked to current position.\nTo end pointer lock hold Escape for 3 seconds."), QStringLiteral("preferences-desktop-mouse"), 5000); @@ -705,6 +734,7 @@ void PointerInputRedirection::updatePointerConstraints() } } else { m_locked = false; + disconnectLockedPointerAboutToBeUnboundConnection(); } } diff --git a/pointer_input.h b/pointer_input.h index 05d7f4c6e..d3374de5c 100644 --- a/pointer_input.h +++ b/pointer_input.h @@ -155,6 +155,7 @@ private: void warpXcbOnSurfaceLeft(KWayland::Server::SurfaceInterface *surface); QPointF applyPointerConfinement(const QPointF &pos) const; void disconnectConfinedPointerRegionConnection(); + void disconnectLockedPointerAboutToBeUnboundConnection(); void disconnectPointerConstraintsConnection(); void breakPointerConstraints(KWayland::Server::SurfaceInterface *surface); bool areButtonsPressed() const; @@ -169,6 +170,7 @@ private: QMetaObject::Connection m_constraintsConnection; QMetaObject::Connection m_constraintsActivatedConnection; QMetaObject::Connection m_confinedPointerRegionConnection; + QMetaObject::Connection m_lockedPointerAboutToBeUnboundConnection; QMetaObject::Connection m_decorationGeometryConnection; bool m_confined = false; bool m_locked = false; diff --git a/tests/pointerconstraintstest.cpp b/tests/pointerconstraintstest.cpp index 9f6df023f..8334d3b1a 100644 --- a/tests/pointerconstraintstest.cpp +++ b/tests/pointerconstraintstest.cpp @@ -142,8 +142,14 @@ void WaylandBackend::lockRequest(bool persistent, QRect region) } m_lockedPointer = lockedPointer; m_lockedPointerPersistent = persistent; + connect(lockedPointer, &LockedPointer::locked, this, [this]() { qDebug() << "------ LOCKED! ------"; + if(lockHint()) { + m_lockedPointer->setCursorPositionHint(QPointF(10., 10.)); + forceSurfaceCommit(); + } + Q_EMIT lockChanged(true); }); connect(lockedPointer, &LockedPointer::unlocked, this, [this]() { diff --git a/tests/pointerconstraintstest.h b/tests/pointerconstraintstest.h index b63fdb22f..7d828763c 100644 --- a/tests/pointerconstraintstest.h +++ b/tests/pointerconstraintstest.h @@ -49,6 +49,7 @@ public: Backend(QObject *parent = nullptr) : QObject(parent) {} Q_PROPERTY(int mode READ mode CONSTANT) + Q_PROPERTY(bool lockHint MEMBER m_lockHint NOTIFY lockHintChanged) Q_PROPERTY(bool errorsAllowed READ errorsAllowed WRITE setErrorsAllowed NOTIFY errorsAllowedChanged) virtual void init(QQuickView *view) { @@ -57,6 +58,10 @@ public: int mode() const { return (int)m_mode; } + + bool lockHint() const { + return m_lockHint; + } bool errorsAllowed() const { return m_errorsAllowed; } @@ -87,7 +92,9 @@ public: Q_SIGNALS: void confineChanged(bool confined); void lockChanged(bool locked); + void lockHintChanged(); void errorsAllowedChanged(); + void forceSurfaceCommit(); protected: enum class Mode { @@ -105,6 +112,8 @@ protected: private: QQuickView *m_view; Mode m_mode; + + bool m_lockHint = true; bool m_errorsAllowed = false; }; diff --git a/tests/pointerconstraintstest.qml b/tests/pointerconstraintstest.qml index 6ea865a19..934fc653e 100644 --- a/tests/pointerconstraintstest.qml +++ b/tests/pointerconstraintstest.qml @@ -79,6 +79,28 @@ ColumnLayout { return activArea.rect(); } + Connections { + target: org_kde_kwin_tests_pointerconstraints_backend + onForceSurfaceCommit: { + forceCommitRect.visible = true + } + } + + Rectangle { + id: forceCommitRect + width: 10 + height: 10 + color: "red" + visible: false + + Timer { + interval: 500 + running: forceCommitRect.visible + repeat: false + onTriggered: forceCommitRect.visible = false; + } + } + GridLayout { columns: 2 rowSpacing: 10 @@ -120,6 +142,13 @@ ColumnLayout { } } + CheckBox { + id: lockHintChck + text: "Send position hint on lock" + checked: root.waylandNative + enabled: root.waylandNative + onCheckedChanged: org_kde_kwin_tests_pointerconstraints_backend.lockHint = checked; + } CheckBox { id: restrAreaChck text: "Restrict input area (not yet implemented)"