diff --git a/pointer_input.h b/pointer_input.h --- a/pointer_input.h +++ b/pointer_input.h @@ -155,6 +155,7 @@ 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 @@ 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/pointer_input.cpp b/pointer_input.cpp --- a/pointer_input.cpp +++ b/pointer_input.cpp @@ -594,6 +594,12 @@ m_confinedPointerRegionConnection = QMetaObject::Connection(); } +void PointerInputRedirection::disconnectLockedPointerAboutToBeUnboundConnection() +{ + disconnect(m_lockedPointerAboutToBeUnboundConnection); + m_lockedPointerAboutToBeUnboundConnection = QMetaObject::Connection(); +} + void PointerInputRedirection::disconnectPointerConstraintsConnection() { disconnect(m_constraintsConnection); @@ -689,22 +695,46 @@ 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; } const QRegion r = getConstraintRegion(m_window.data(), lock.data()); 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); // TODO: connect to region change - is it needed at all? If the pointer is locked it's always in the region } } else { m_locked = false; + disconnectLockedPointerAboutToBeUnboundConnection(); } } diff --git a/tests/pointerconstraintstest.h b/tests/pointerconstraintstest.h --- a/tests/pointerconstraintstest.h +++ b/tests/pointerconstraintstest.h @@ -49,14 +49,19 @@ 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) { m_view = view; } int mode() const { return (int)m_mode; } + + bool lockHint() const { + return m_lockHint; + } bool errorsAllowed() const { return m_errorsAllowed; } @@ -87,7 +92,9 @@ Q_SIGNALS: void confineChanged(bool confined); void lockChanged(bool locked); + void lockHintChanged(); void errorsAllowedChanged(); + void forceSurfaceCommit(); protected: enum class Mode { @@ -105,6 +112,8 @@ private: QQuickView *m_view; Mode m_mode; + + bool m_lockHint = true; bool m_errorsAllowed = false; }; diff --git a/tests/pointerconstraintstest.cpp b/tests/pointerconstraintstest.cpp --- a/tests/pointerconstraintstest.cpp +++ b/tests/pointerconstraintstest.cpp @@ -142,8 +142,14 @@ } 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.qml b/tests/pointerconstraintstest.qml --- a/tests/pointerconstraintstest.qml +++ b/tests/pointerconstraintstest.qml @@ -79,6 +79,28 @@ 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 @@ } } + 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)"