diff --git a/kwin/src/breezydesktopeffect.cpp b/kwin/src/breezydesktopeffect.cpp index c74783c..d298320 100644 --- a/kwin/src/breezydesktopeffect.cpp +++ b/kwin/src/breezydesktopeffect.cpp @@ -21,6 +21,9 @@ Q_LOGGING_CATEGORY(KWIN_XR, "kwin.xr") namespace DataView { + const QString SHM_DIR = QStringLiteral("/dev/shm"); + const QString SHM_PATH = SHM_DIR + QStringLiteral("/breezy_desktop_imu"); + // Helper constants and functions for shared memory buffer offsets constexpr int UINT8_SIZE = sizeof(uint8_t); constexpr int BOOL_SIZE = UINT8_SIZE; @@ -63,10 +66,6 @@ BreezyDesktopEffect::BreezyDesktopEffect() qCCritical(KWIN_XR) << "\t\t\tBreezy - constructor"; qmlRegisterUncreatableType("org.kde.kwin.effect.breezy_desktop", 1, 0, "BreezyDesktopEffect", QStringLiteral("BreezyDesktop cannot be created in QML")); - m_shutdownTimer->setSingleShot(true); - connect(m_shutdownTimer, &QTimer::timeout, this, &BreezyDesktopEffect::realDeactivate); - connect(effects, &EffectsHandler::screenAboutToLock, this, &BreezyDesktopEffect::realDeactivate); - const QKeySequence defaultToggleShortcut = Qt::META | Qt::Key_B; m_toggleAction = new QAction(this); m_toggleAction->setObjectName(QStringLiteral("BreezyDesktop")); @@ -90,20 +89,30 @@ BreezyDesktopEffect::BreezyDesktopEffect() setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/effects/breezy_desktop/qml/main.qml")))); // Monitor the IMU file for changes, even if it doesn't exist at startup - const QString shmPath = QStringLiteral("/dev/shm/breezy_desktop_imu"); - const QString shmDir = QStringLiteral("/dev/shm"); - m_imuRotationFileWatcher = new QFileSystemWatcher(this); - if (QFile::exists(shmPath)) { - m_imuRotationFileWatcher->addPath(shmPath); - } else { - m_imuRotationFileWatcher->addPath(shmDir); - connect(m_imuRotationFileWatcher, &QFileSystemWatcher::directoryChanged, this, [this, shmPath](const QString &) { - if (QFile::exists(shmPath) && !m_imuRotationFileWatcher->files().contains(shmPath)) { - m_imuRotationFileWatcher->addPath(shmPath); - } - }); - } - connect(m_imuRotationFileWatcher, &QFileSystemWatcher::fileChanged, this, &BreezyDesktopEffect::updateImuRotation); + m_shmDirectoryWatcher = new QFileSystemWatcher(this); + m_shmDirectoryWatcher->addPath(DataView::SHM_DIR); + + m_shmFileWatcher = new QFileSystemWatcher(this); + + // Setup file watcher with recreation detection + auto setupFileWatcher = [this]() { + if (QFile::exists(DataView::SHM_PATH) && ( + m_imuTimestamp == 0 || + QDateTime::currentMSecsSinceEpoch() - m_imuTimestamp > 50 || // file may have been deleted and recreated + !m_shmFileWatcher->files().contains(DataView::SHM_PATH) + )) { + m_shmFileWatcher->removePath(DataView::SHM_PATH); + disconnect(m_shmFileWatcher, &QFileSystemWatcher::fileChanged, this, &BreezyDesktopEffect::updateImuRotation); + m_shmFileWatcher->addPath(DataView::SHM_PATH); + connect(m_shmFileWatcher, &QFileSystemWatcher::fileChanged, this, &BreezyDesktopEffect::updateImuRotation); + } + }; + + // Handle directory changes (file creation/recreation) + connect(m_shmDirectoryWatcher, &QFileSystemWatcher::directoryChanged, this, setupFileWatcher); + + // Initial setup + setupFileWatcher(); m_cursorUpdateTimer = new QTimer(this); connect(m_cursorUpdateTimer, &QTimer::timeout, this, &BreezyDesktopEffect::updateCursorPos); @@ -131,21 +140,17 @@ int BreezyDesktopEffect::requestedEffectChainPosition() const void BreezyDesktopEffect::toggle() { - if (isRunning()) { - deactivate(); - } else { - qCCritical(KWIN_XR) << "\t\t\tBreezy - activate"; - activate(); - } + // TODO update this to use a persistent on/off value } void BreezyDesktopEffect::activate() { - if (effects->isScreenLocked()) { - return; - } + qCCritical(KWIN_XR) << "\t\t\tBreezy - activate"; - setRunning(true); + if (!isRunning()) setRunning(true); + + connect(effects, &EffectsHandler::cursorShapeChanged, this, &BreezyDesktopEffect::updateCursorImage); + m_cursorUpdateTimer->start(); // QuickSceneEffect grabs the keyboard and mouse input, which pulls focus away from the active window // and doesn't allow for interaction with anything on the desktop. These two calls fix that. @@ -158,10 +163,7 @@ void BreezyDesktopEffect::activate() void BreezyDesktopEffect::deactivate() { - if (m_shutdownTimer->isActive()) { - return; - } - + qCCritical(KWIN_XR) << "\t\t\tBreezy - deactivate"; disconnect(effects, &EffectsHandler::cursorShapeChanged, this, &BreezyDesktopEffect::updateCursorImage); m_cursorUpdateTimer->stop(); showCursor(); @@ -173,11 +175,12 @@ void BreezyDesktopEffect::deactivate() } } - m_shutdownTimer->start(animationDuration()); + realDeactivate(); } void BreezyDesktopEffect::realDeactivate() { + qCCritical(KWIN_XR) << "\t\t\tBreezy - realDeactivate"; setRunning(false); } @@ -202,6 +205,10 @@ QColor BreezyDesktopEffect::backgroundColor() const { return QColor(Qt::black); } +bool BreezyDesktopEffect::isEnabled() const { + return m_enabled; +} + bool BreezyDesktopEffect::imuResetState() const { return m_imuResetState; } @@ -242,9 +249,27 @@ bool BreezyDesktopEffect::customBannerEnabled() const { return m_customBannerEnabled; } +bool BreezyDesktopEffect::checkParityByte(const char* data) { + const uint8_t parityByte = static_cast(data[DataView::IMU_PARITY_BYTE[DataView::OFFSET_INDEX]]); + uint8_t parity = 0; + + const int dateBytes = DataView::IMU_DATE_MS[DataView::COUNT_INDEX] * DataView::IMU_DATE_MS[DataView::SIZE_INDEX]; + for (int i = 0; i < dateBytes; ++i) { + parity ^= static_cast(data[DataView::IMU_DATE_MS[DataView::OFFSET_INDEX] + i]); + } + + const int quatBytes = DataView::IMU_QUAT_DATA[DataView::COUNT_INDEX] * DataView::IMU_QUAT_DATA[DataView::SIZE_INDEX]; + for (int i = 0; i < quatBytes; ++i) { + parity ^= static_cast(data[DataView::IMU_QUAT_DATA[DataView::OFFSET_INDEX] + i]); + } + + 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; -void BreezyDesktopEffect::updateImuRotation() { +static qint64 activatedAt = 0; +void BreezyDesktopEffect::updateImuRotation() { const QString shmPath = QStringLiteral("/dev/shm/breezy_desktop_imu"); QFile shmFile(shmPath); if (!shmFile.open(QIODevice::ReadOnly)) { @@ -252,12 +277,13 @@ void BreezyDesktopEffect::updateImuRotation() { } QByteArray buffer = shmFile.readAll(); shmFile.close(); - if (buffer.size() < 64) { - return; - } + if (buffer.size() != DataView::LENGTH) return; + const char* data = buffer.constData(); - uint8_t version = static_cast(data[0]); - uint8_t enabledFlag = static_cast(data[1]); + if (!checkParityByte(data)) return; + + uint8_t version = static_cast(data[DataView::VERSION[DataView::OFFSET_INDEX]]); + uint8_t enabledFlag = static_cast(data[DataView::ENABLED[DataView::OFFSET_INDEX]]); uint64_t imuDateMs; memcpy(&imuDateMs, data + DataView::IMU_DATE_MS[DataView::OFFSET_INDEX], sizeof(imuDateMs)); imuDateMs = qFromLittleEndian(imuDateMs); @@ -302,14 +328,35 @@ void BreezyDesktopEffect::updateImuRotation() { const bool validKeepAlive = (currentTimeMs - imuDateMs) < 5000; const bool validData = validKeepAlive && m_diagonalFOV != 0.0f; const uint8_t expectedVersion = 4; - const bool enabled = (enabledFlag != 0) && (version == expectedVersion) && validData; + bool enabledFlagSet = (enabledFlag != 0); + bool validVersion = (version == expectedVersion); + const bool wasEnabled = m_enabled; + const bool enabled = enabledFlagSet && validVersion && validData; if (!enabled) { - if (isRunning()) { - qCCritical(KWIN_XR) << "\t\t\tBreezy - deactivate due to disabled"; + // give a grace period after enabling the effect + if (wasEnabled && (currentTimeMs - activatedAt > 1000)) { + qCCritical(KWIN_XR) << "\t\t\tBreezy - disabling effect; currentTimeMs:" << currentTimeMs + << "imuDateMs:" << imuDateMs + << "enabledFlag:" << enabledFlag + << "version:" << version + << "diagonalFOV:" << m_diagonalFOV; deactivate(); + m_enabled = false; + Q_EMIT enabledStateChanged(); + return; } - return; + } else if (!wasEnabled) { + qCCritical(KWIN_XR) << "\t\t\tBreezy - enabling effect; currentTimeMs:" << currentTimeMs + << "imuDateMs:" << imuDateMs + << "enabledFlag:" << enabledFlag + << "version:" << version + << "diagonalFOV:" << m_diagonalFOV; + activate(); + m_enabled = true; + Q_EMIT enabledStateChanged(); + activatedAt = currentTimeMs; } + if (updateConfig) Q_EMIT devicePropertiesChanged(); float imuData[4 * DataView::IMU_QUAT_ENTRIES]; // 4 quaternion-sized rows @@ -337,10 +384,6 @@ void BreezyDesktopEffect::updateImuRotation() { m_imuTimeElapsedMs = static_cast(imuData[imuDataOffset + 0] - imuData[imuDataOffset + 1]); m_imuTimestamp = imuDateMs; - if (!isRunning()) { - qCCritical(KWIN_XR) << "\t\t\tBreezy - activate"; - activate(); - } Q_EMIT imuRotationsChanged(); } diff --git a/kwin/src/breezydesktopeffect.h b/kwin/src/breezydesktopeffect.h index 1867413..cb5ab79 100644 --- a/kwin/src/breezydesktopeffect.h +++ b/kwin/src/breezydesktopeffect.h @@ -18,6 +18,7 @@ namespace KWin Q_PROPERTY(qreal distanceFactor READ distanceFactor NOTIFY distanceFactorChanged) Q_PROPERTY(BackgroundMode backgroundMode READ backgroundMode NOTIFY backgroundModeChanged) Q_PROPERTY(QColor backgroundColor READ backgroundColor NOTIFY backgroundColorChanged) + Q_PROPERTY(bool isEnabled READ isEnabled NOTIFY enabledStateChanged) Q_PROPERTY(bool imuResetState READ imuResetState NOTIFY imuRotationsChanged) Q_PROPERTY(QList imuRotations READ imuRotations NOTIFY imuRotationsChanged) Q_PROPERTY(quint32 imuTimeElapsedMs READ imuTimeElapsedMs NOTIFY imuRotationsChanged) @@ -53,6 +54,10 @@ namespace KWin QString cursorImageSource() const; QPointF cursorPos() const; + bool isEnabled() const; + QList imuRotations() const; + quint32 imuTimeElapsedMs() const; + quint64 imuTimestamp() const; bool imuResetState() const; QList lookAheadConfig() const; QList displayResolution() const; @@ -64,10 +69,6 @@ namespace KWin void showCursor(); void hideCursor(); - QList imuRotations() const; - quint32 imuTimeElapsedMs() const; - quint64 imuTimestamp() const; - public Q_SLOTS: void activate(); void deactivate(); @@ -83,6 +84,7 @@ namespace KWin void skyboxChanged(); void backgroundModeChanged(); void backgroundColorChanged(); + void enabledStateChanged(); void imuRotationsChanged(); void cursorImageChanged(); void cursorPosChanged(); @@ -93,6 +95,7 @@ namespace KWin private: void realDeactivate(); + bool checkParityByte(const char* data); QTimer *m_shutdownTimer; QAction *m_toggleAction = nullptr; @@ -101,17 +104,19 @@ namespace KWin QList m_touchBorderActivate; QString m_cursorImageSource; + bool m_enabled = false; bool m_imuResetState; QList m_imuRotations; quint32 m_imuTimeElapsedMs; - quint64 m_imuTimestamp; + quint64 m_imuTimestamp = 0; QList m_lookAheadConfig; QList m_displayResolution; qreal m_diagonalFOV; qreal m_lensDistanceRatio; bool m_sbsEnabled; bool m_customBannerEnabled; - QFileSystemWatcher *m_imuRotationFileWatcher = nullptr; + QFileSystemWatcher *m_shmFileWatcher = nullptr; + QFileSystemWatcher *m_shmDirectoryWatcher = nullptr; QPointF m_cursorPos; QTimer *m_cursorUpdateTimer = nullptr; }; diff --git a/kwin/src/qml/main.qml b/kwin/src/qml/main.qml index 4c60e3d..44e1e11 100644 --- a/kwin/src/qml/main.qml +++ b/kwin/src/qml/main.qml @@ -11,22 +11,12 @@ Item { required property QtObject effect required property QtObject targetScreen - property bool animationEnabled: false - - function start() { - root.animationEnabled = true; - } - - function stop() { - } - View3D { - id: view anchors.fill: parent environment: SceneEnvironment { antialiasingMode: SceneEnvironment.MSAA } - + PerspectiveCamera { id: camera frustumCullingEnabled: false @@ -42,6 +32,6 @@ Item { camera: camera } } - + Component.onCompleted: start(); }