diff --git a/kwin/src/breezydesktopeffect.cpp b/kwin/src/breezydesktopeffect.cpp index 29917ca..9caf29d 100644 --- a/kwin/src/breezydesktopeffect.cpp +++ b/kwin/src/breezydesktopeffect.cpp @@ -291,8 +291,6 @@ void BreezyDesktopEffect::activate() // and doesn't allow for interaction with anything on the desktop. These two calls fix that. effects->ungrabKeyboard(); effects->stopMouseInterception(this); - - hideCursor(); } void BreezyDesktopEffect::deactivate() @@ -392,6 +390,14 @@ bool BreezyDesktopEffect::isEnabled() const { return m_enabled; } +void BreezyDesktopEffect::setEffectTargetScreenIndex(int index) { + if (m_effectTargetScreenIndex != index) { + m_effectTargetScreenIndex = index; + invalidateEffectOnScreenGeometryCache(); + evaluateCursorOnScreenState(m_cursorPos, m_cursorPos); + } +} + bool BreezyDesktopEffect::isZoomOnFocusEnabled() const { return m_zoomOnFocusEnabled; } @@ -572,7 +578,6 @@ bool BreezyDesktopEffect::checkParityByte(const char* data) { return parityByte == parity; } -// TODO - can this be something callable from the camera qml code, so it's pulled only when needed? static qint64 lastConfigUpdate = 0; static qint64 activatedAt = 0; void BreezyDesktopEffect::updateImuRotation() { @@ -786,13 +791,19 @@ QPointF BreezyDesktopEffect::cursorPos() const void BreezyDesktopEffect::showCursor() { + if (!m_cursorHidden) return; + effects->showCursor(); + m_cursorHidden = false; } void BreezyDesktopEffect::hideCursor() { + if (m_cursorHidden) return; + updateCursorImage(); effects->hideCursor(); + m_cursorHidden = true; } void BreezyDesktopEffect::updateCursorImage() @@ -810,6 +821,8 @@ void BreezyDesktopEffect::updateCursorImage() m_cursorImageSource = QString(); m_cursorImageSize = QSize(); } + // Cursor size affects the expanded geometry margin; invalidate cache. + invalidateEffectOnScreenGeometryCache(); Q_EMIT cursorImageSourceChanged(); } @@ -819,11 +832,56 @@ void BreezyDesktopEffect::updateCursorPos() const auto cursor = effects->cursorImage(); QPointF newPos = effects->cursorPos() - cursor.hotSpot(); if (m_cursorPos != newPos) { + const QPointF prevPos = m_cursorPos; m_cursorPos = newPos; Q_EMIT cursorPosChanged(); + + evaluateCursorOnScreenState(prevPos, m_cursorPos); } } +void BreezyDesktopEffect::evaluateCursorOnScreenState(const QPointF &prevPos, const QPointF &newPos) +{ + if (!updateEffectOnScreenGeometryCache()) return; + + const QPointF velocity = newPos - prevPos; + const QPointF predicted = newPos + velocity; + + const bool onScreen = + m_effectOnScreenExpandedGeometry.contains(newPos.toPoint()) || + m_effectOnScreenExpandedGeometry.contains(predicted.toPoint()); + if (!m_cursorHidden && onScreen) { + hideCursor(); + } else if (m_enabled && !m_imuResetState && m_cursorHidden && !onScreen) { + showCursor(); + } +} + +void BreezyDesktopEffect::invalidateEffectOnScreenGeometryCache() +{ + m_effectOnScreenGeometryValid = false; +} + +bool BreezyDesktopEffect::updateEffectOnScreenGeometryCache() +{ + if (m_effectOnScreenGeometryValid) + return true; + + if (m_effectTargetScreenIndex == -1) + return false; + + Output *effectOnScreen = effects->screens().at(m_effectTargetScreenIndex); + if (!effectOnScreen) + return false; + + const QRect geometry = effectOnScreen->geometry(); + const int marginX = (m_cursorImageSize.width() > 0) ? m_cursorImageSize.width() : 10; + const int marginY = (m_cursorImageSize.height() > 0) ? m_cursorImageSize.height() : 10; + m_effectOnScreenExpandedGeometry = geometry.adjusted(-marginX, -marginY, marginX, marginY); + m_effectOnScreenGeometryValid = true; + return true; +} + void BreezyDesktopEffect::warpPointerToOutputCenter(Output *output) { if (!output) { @@ -832,6 +890,9 @@ void BreezyDesktopEffect::warpPointerToOutputCenter(Output *output) const QRect geometry = output->geometry(); const QPointF center = geometry.center(); Cursors::self()->mouse()->setPos(center); + + // When warping, we don't have a meaningful previous position; use center for both. + evaluateCursorOnScreenState(center, center); } void BreezyDesktopEffect::moveCursorToFocusedDisplay() diff --git a/kwin/src/breezydesktopeffect.h b/kwin/src/breezydesktopeffect.h index 1334d90..f9cc1cf 100644 --- a/kwin/src/breezydesktopeffect.h +++ b/kwin/src/breezydesktopeffect.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace KWin { @@ -18,6 +19,7 @@ namespace KWin { Q_OBJECT Q_PROPERTY(bool isEnabled READ isEnabled NOTIFY enabledStateChanged) + Q_PROPERTY(int effectTargetScreenIndex READ effectTargetScreenIndex WRITE setEffectTargetScreenIndex) Q_PROPERTY(bool zoomOnFocusEnabled READ isZoomOnFocusEnabled WRITE setZoomOnFocusEnabled NOTIFY zoomOnFocusChanged) Q_PROPERTY(int lookingAtScreenIndex READ lookingAtScreenIndex WRITE setLookingAtScreenIndex) Q_PROPERTY(bool imuResetState READ imuResetState NOTIFY imuResetStateChanged) @@ -48,6 +50,7 @@ namespace KWin Q_PROPERTY(bool curvedDisplay READ curvedDisplay NOTIFY curvedDisplayChanged) Q_PROPERTY(bool curvedDisplaySupported READ curvedDisplaySupported WRITE setCurvedDisplaySupported NOTIFY curvedDisplaySupportedChanged) + public: BreezyDesktopEffect(); @@ -62,6 +65,8 @@ namespace KWin QPointF cursorPos() const; bool isEnabled() const; + int effectTargetScreenIndex() const { return m_effectTargetScreenIndex; } + void setEffectTargetScreenIndex(int index); bool isZoomOnFocusEnabled() const; void setZoomOnFocusEnabled(bool enabled); int lookingAtScreenIndex() const { return m_lookingAtScreenIndex; } @@ -147,6 +152,9 @@ namespace KWin void setSmoothFollowThreshold(float threshold); void updateDriverSmoothFollowSettings(); void warpPointerToOutputCenter(Output *output); + void evaluateCursorOnScreenState(const QPointF &prevPos, const QPointF &newPos); + void invalidateEffectOnScreenGeometryCache(); + bool updateEffectOnScreenGeometryCache(); QString m_cursorImageSource; QSize m_cursorImageSize; @@ -154,6 +162,7 @@ namespace KWin bool m_enabled = false; bool m_zoomOnFocusEnabled = false; int m_lookingAtScreenIndex = -1; + int m_effectTargetScreenIndex = -1; bool m_imuResetState; QList m_imuRotations; quint32 m_imuTimeElapsedMs; @@ -169,6 +178,7 @@ namespace KWin bool m_customBannerEnabled; QFileSystemWatcher *m_shmFileWatcher = nullptr; QFileSystemWatcher *m_shmDirectoryWatcher = nullptr; + bool m_cursorHidden = false; QPointF m_cursorPos; QTimer *m_cursorUpdateTimer = nullptr; qreal m_focusedDisplayDistance = 0.85; @@ -186,6 +196,10 @@ namespace KWin bool m_allDisplaysFollowMode = false; bool m_focusedSmoothFollowEnabled = false; + // Cached geometry for on-screen cursor evaluation + QRect m_effectOnScreenExpandedGeometry; + bool m_effectOnScreenGeometryValid = false; + struct VirtualOutputInfo { Output *output = nullptr; QString id; diff --git a/kwin/src/qml/SingleDesktopView.qml b/kwin/src/qml/SingleDesktopView.qml index 88eeccd..0e7fce2 100644 --- a/kwin/src/qml/SingleDesktopView.qml +++ b/kwin/src/qml/SingleDesktopView.qml @@ -2,33 +2,15 @@ import QtQuick Item { id: singleDesktopView - property point cursorPos: effect.cursorPos property bool supportsXR: false property bool showCalibratingBanner: false - function cursorInBounds() { - const x = cursorPos.x - const y = cursorPos.y - const screenGeom = targetScreen.geometry - return x >= screenGeom.x && - x < screenGeom.x + screenGeom.width && - y >= screenGeom.y && - y < screenGeom.y + screenGeom.height - } - DesktopView { id: desktopViewComponent screen: targetScreen width: targetScreen.geometry.width height: targetScreen.geometry.height } - - Image { - id: cursorImg - x: 0 - y: 0 - z: 9999 // ensure on top - } Image { source: effect.customBannerEnabled ? "custom_banner.png" : "calibrating.png" @@ -36,18 +18,4 @@ Item { anchors.horizontalCenter: desktopViewComponent.horizontalCenter anchors.bottom: desktopViewComponent.bottom } - - onCursorPosChanged: { - if (singleDesktopView.cursorInBounds()) { - const newX = effect.cursorPos.x - targetScreen.geometry.x - const newY = effect.cursorPos.y - targetScreen.geometry.y - const newSrc = effect.cursorImageSource - if (cursorImg.x !== newX) cursorImg.x = newX - if (cursorImg.y !== newY) cursorImg.y = newY - if (cursorImg.source !== newSrc) cursorImg.source = newSrc - if (!cursorImg.visible) cursorImg.visible = true - } else if (cursorImg.visible) { - cursorImg.visible = false - } - } } \ No newline at end of file diff --git a/kwin/src/qml/main.qml b/kwin/src/qml/main.qml index 59bcbb8..2d0bb71 100644 --- a/kwin/src/qml/main.qml +++ b/kwin/src/qml/main.qml @@ -140,6 +140,7 @@ Item { console.log(`Breezy - checking screen ${targetScreen.model}: ${targetScreenSupported} ${targetScreenIsVirtual} ${isEnabled} ${imuResetState}`); const show3DView = targetScreenSupported && isEnabled && !imuResetState; if (!targetScreenIsVirtual) viewLoader.sourceComponent = show3DView ? view3DComponent : desktopViewComponent; + if (targetScreenSupported) effect.effectTargetScreenIndex = KWinComponents.Workspace.screens.indexOf(targetScreen); } onImuResetStateChanged: {