Add data validity and parity checks, improve device detection on multiple plug and unplug events

This commit is contained in:
wheaney 2025-07-31 13:39:31 -07:00
parent c266e3cbd3
commit f5cbd9281b
3 changed files with 103 additions and 65 deletions

View File

@ -21,6 +21,9 @@ Q_LOGGING_CATEGORY(KWIN_XR, "kwin.xr")
namespace DataView 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 // Helper constants and functions for shared memory buffer offsets
constexpr int UINT8_SIZE = sizeof(uint8_t); constexpr int UINT8_SIZE = sizeof(uint8_t);
constexpr int BOOL_SIZE = UINT8_SIZE; constexpr int BOOL_SIZE = UINT8_SIZE;
@ -63,10 +66,6 @@ BreezyDesktopEffect::BreezyDesktopEffect()
qCCritical(KWIN_XR) << "\t\t\tBreezy - constructor"; qCCritical(KWIN_XR) << "\t\t\tBreezy - constructor";
qmlRegisterUncreatableType<BreezyDesktopEffect>("org.kde.kwin.effect.breezy_desktop", 1, 0, "BreezyDesktopEffect", QStringLiteral("BreezyDesktop cannot be created in QML")); qmlRegisterUncreatableType<BreezyDesktopEffect>("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; const QKeySequence defaultToggleShortcut = Qt::META | Qt::Key_B;
m_toggleAction = new QAction(this); m_toggleAction = new QAction(this);
m_toggleAction->setObjectName(QStringLiteral("BreezyDesktop")); 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")))); 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 // Monitor the IMU file for changes, even if it doesn't exist at startup
const QString shmPath = QStringLiteral("/dev/shm/breezy_desktop_imu"); m_shmDirectoryWatcher = new QFileSystemWatcher(this);
const QString shmDir = QStringLiteral("/dev/shm"); m_shmDirectoryWatcher->addPath(DataView::SHM_DIR);
m_imuRotationFileWatcher = new QFileSystemWatcher(this);
if (QFile::exists(shmPath)) { m_shmFileWatcher = new QFileSystemWatcher(this);
m_imuRotationFileWatcher->addPath(shmPath);
} else { // Setup file watcher with recreation detection
m_imuRotationFileWatcher->addPath(shmDir); auto setupFileWatcher = [this]() {
connect(m_imuRotationFileWatcher, &QFileSystemWatcher::directoryChanged, this, [this, shmPath](const QString &) { if (QFile::exists(DataView::SHM_PATH) && (
if (QFile::exists(shmPath) && !m_imuRotationFileWatcher->files().contains(shmPath)) { m_imuTimestamp == 0 ||
m_imuRotationFileWatcher->addPath(shmPath); 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);
connect(m_imuRotationFileWatcher, &QFileSystemWatcher::fileChanged, this, &BreezyDesktopEffect::updateImuRotation); 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); m_cursorUpdateTimer = new QTimer(this);
connect(m_cursorUpdateTimer, &QTimer::timeout, this, &BreezyDesktopEffect::updateCursorPos); connect(m_cursorUpdateTimer, &QTimer::timeout, this, &BreezyDesktopEffect::updateCursorPos);
@ -131,21 +140,17 @@ int BreezyDesktopEffect::requestedEffectChainPosition() const
void BreezyDesktopEffect::toggle() void BreezyDesktopEffect::toggle()
{ {
if (isRunning()) { // TODO update this to use a persistent on/off value
deactivate();
} else {
qCCritical(KWIN_XR) << "\t\t\tBreezy - activate";
activate();
}
} }
void BreezyDesktopEffect::activate() void BreezyDesktopEffect::activate()
{ {
if (effects->isScreenLocked()) { qCCritical(KWIN_XR) << "\t\t\tBreezy - activate";
return;
}
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 // 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. // 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() void BreezyDesktopEffect::deactivate()
{ {
if (m_shutdownTimer->isActive()) { qCCritical(KWIN_XR) << "\t\t\tBreezy - deactivate";
return;
}
disconnect(effects, &EffectsHandler::cursorShapeChanged, this, &BreezyDesktopEffect::updateCursorImage); disconnect(effects, &EffectsHandler::cursorShapeChanged, this, &BreezyDesktopEffect::updateCursorImage);
m_cursorUpdateTimer->stop(); m_cursorUpdateTimer->stop();
showCursor(); showCursor();
@ -173,11 +175,12 @@ void BreezyDesktopEffect::deactivate()
} }
} }
m_shutdownTimer->start(animationDuration()); realDeactivate();
} }
void BreezyDesktopEffect::realDeactivate() void BreezyDesktopEffect::realDeactivate()
{ {
qCCritical(KWIN_XR) << "\t\t\tBreezy - realDeactivate";
setRunning(false); setRunning(false);
} }
@ -202,6 +205,10 @@ QColor BreezyDesktopEffect::backgroundColor() const {
return QColor(Qt::black); return QColor(Qt::black);
} }
bool BreezyDesktopEffect::isEnabled() const {
return m_enabled;
}
bool BreezyDesktopEffect::imuResetState() const { bool BreezyDesktopEffect::imuResetState() const {
return m_imuResetState; return m_imuResetState;
} }
@ -242,9 +249,27 @@ bool BreezyDesktopEffect::customBannerEnabled() const {
return m_customBannerEnabled; return m_customBannerEnabled;
} }
bool BreezyDesktopEffect::checkParityByte(const char* data) {
const uint8_t parityByte = static_cast<uint8_t>(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<uint8_t>(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<uint8_t>(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? // TODO - can this be something callable from the camera qml code, so it's pulled only when needed?
static qint64 lastConfigUpdate = 0; static qint64 lastConfigUpdate = 0;
void BreezyDesktopEffect::updateImuRotation() { static qint64 activatedAt = 0;
void BreezyDesktopEffect::updateImuRotation() {
const QString shmPath = QStringLiteral("/dev/shm/breezy_desktop_imu"); const QString shmPath = QStringLiteral("/dev/shm/breezy_desktop_imu");
QFile shmFile(shmPath); QFile shmFile(shmPath);
if (!shmFile.open(QIODevice::ReadOnly)) { if (!shmFile.open(QIODevice::ReadOnly)) {
@ -252,12 +277,13 @@ void BreezyDesktopEffect::updateImuRotation() {
} }
QByteArray buffer = shmFile.readAll(); QByteArray buffer = shmFile.readAll();
shmFile.close(); shmFile.close();
if (buffer.size() < 64) { if (buffer.size() != DataView::LENGTH) return;
return;
}
const char* data = buffer.constData(); const char* data = buffer.constData();
uint8_t version = static_cast<uint8_t>(data[0]); if (!checkParityByte(data)) return;
uint8_t enabledFlag = static_cast<uint8_t>(data[1]);
uint8_t version = static_cast<uint8_t>(data[DataView::VERSION[DataView::OFFSET_INDEX]]);
uint8_t enabledFlag = static_cast<uint8_t>(data[DataView::ENABLED[DataView::OFFSET_INDEX]]);
uint64_t imuDateMs; uint64_t imuDateMs;
memcpy(&imuDateMs, data + DataView::IMU_DATE_MS[DataView::OFFSET_INDEX], sizeof(imuDateMs)); memcpy(&imuDateMs, data + DataView::IMU_DATE_MS[DataView::OFFSET_INDEX], sizeof(imuDateMs));
imuDateMs = qFromLittleEndian(imuDateMs); imuDateMs = qFromLittleEndian(imuDateMs);
@ -302,14 +328,35 @@ void BreezyDesktopEffect::updateImuRotation() {
const bool validKeepAlive = (currentTimeMs - imuDateMs) < 5000; const bool validKeepAlive = (currentTimeMs - imuDateMs) < 5000;
const bool validData = validKeepAlive && m_diagonalFOV != 0.0f; const bool validData = validKeepAlive && m_diagonalFOV != 0.0f;
const uint8_t expectedVersion = 4; 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 (!enabled) {
if (isRunning()) { // give a grace period after enabling the effect
qCCritical(KWIN_XR) << "\t\t\tBreezy - deactivate due to disabled"; 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(); 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(); if (updateConfig) Q_EMIT devicePropertiesChanged();
float imuData[4 * DataView::IMU_QUAT_ENTRIES]; // 4 quaternion-sized rows float imuData[4 * DataView::IMU_QUAT_ENTRIES]; // 4 quaternion-sized rows
@ -337,10 +384,6 @@ void BreezyDesktopEffect::updateImuRotation() {
m_imuTimeElapsedMs = static_cast<quint32>(imuData[imuDataOffset + 0] - imuData[imuDataOffset + 1]); m_imuTimeElapsedMs = static_cast<quint32>(imuData[imuDataOffset + 0] - imuData[imuDataOffset + 1]);
m_imuTimestamp = imuDateMs; m_imuTimestamp = imuDateMs;
if (!isRunning()) {
qCCritical(KWIN_XR) << "\t\t\tBreezy - activate";
activate();
}
Q_EMIT imuRotationsChanged(); Q_EMIT imuRotationsChanged();
} }

View File

@ -18,6 +18,7 @@ namespace KWin
Q_PROPERTY(qreal distanceFactor READ distanceFactor NOTIFY distanceFactorChanged) Q_PROPERTY(qreal distanceFactor READ distanceFactor NOTIFY distanceFactorChanged)
Q_PROPERTY(BackgroundMode backgroundMode READ backgroundMode NOTIFY backgroundModeChanged) Q_PROPERTY(BackgroundMode backgroundMode READ backgroundMode NOTIFY backgroundModeChanged)
Q_PROPERTY(QColor backgroundColor READ backgroundColor NOTIFY backgroundColorChanged) 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(bool imuResetState READ imuResetState NOTIFY imuRotationsChanged)
Q_PROPERTY(QList<QQuaternion> imuRotations READ imuRotations NOTIFY imuRotationsChanged) Q_PROPERTY(QList<QQuaternion> imuRotations READ imuRotations NOTIFY imuRotationsChanged)
Q_PROPERTY(quint32 imuTimeElapsedMs READ imuTimeElapsedMs NOTIFY imuRotationsChanged) Q_PROPERTY(quint32 imuTimeElapsedMs READ imuTimeElapsedMs NOTIFY imuRotationsChanged)
@ -53,6 +54,10 @@ namespace KWin
QString cursorImageSource() const; QString cursorImageSource() const;
QPointF cursorPos() const; QPointF cursorPos() const;
bool isEnabled() const;
QList<QQuaternion> imuRotations() const;
quint32 imuTimeElapsedMs() const;
quint64 imuTimestamp() const;
bool imuResetState() const; bool imuResetState() const;
QList<qreal> lookAheadConfig() const; QList<qreal> lookAheadConfig() const;
QList<quint32> displayResolution() const; QList<quint32> displayResolution() const;
@ -64,10 +69,6 @@ namespace KWin
void showCursor(); void showCursor();
void hideCursor(); void hideCursor();
QList<QQuaternion> imuRotations() const;
quint32 imuTimeElapsedMs() const;
quint64 imuTimestamp() const;
public Q_SLOTS: public Q_SLOTS:
void activate(); void activate();
void deactivate(); void deactivate();
@ -83,6 +84,7 @@ namespace KWin
void skyboxChanged(); void skyboxChanged();
void backgroundModeChanged(); void backgroundModeChanged();
void backgroundColorChanged(); void backgroundColorChanged();
void enabledStateChanged();
void imuRotationsChanged(); void imuRotationsChanged();
void cursorImageChanged(); void cursorImageChanged();
void cursorPosChanged(); void cursorPosChanged();
@ -93,6 +95,7 @@ namespace KWin
private: private:
void realDeactivate(); void realDeactivate();
bool checkParityByte(const char* data);
QTimer *m_shutdownTimer; QTimer *m_shutdownTimer;
QAction *m_toggleAction = nullptr; QAction *m_toggleAction = nullptr;
@ -101,17 +104,19 @@ namespace KWin
QList<ElectricBorder> m_touchBorderActivate; QList<ElectricBorder> m_touchBorderActivate;
QString m_cursorImageSource; QString m_cursorImageSource;
bool m_enabled = false;
bool m_imuResetState; bool m_imuResetState;
QList<QQuaternion> m_imuRotations; QList<QQuaternion> m_imuRotations;
quint32 m_imuTimeElapsedMs; quint32 m_imuTimeElapsedMs;
quint64 m_imuTimestamp; quint64 m_imuTimestamp = 0;
QList<qreal> m_lookAheadConfig; QList<qreal> m_lookAheadConfig;
QList<quint32> m_displayResolution; QList<quint32> m_displayResolution;
qreal m_diagonalFOV; qreal m_diagonalFOV;
qreal m_lensDistanceRatio; qreal m_lensDistanceRatio;
bool m_sbsEnabled; bool m_sbsEnabled;
bool m_customBannerEnabled; bool m_customBannerEnabled;
QFileSystemWatcher *m_imuRotationFileWatcher = nullptr; QFileSystemWatcher *m_shmFileWatcher = nullptr;
QFileSystemWatcher *m_shmDirectoryWatcher = nullptr;
QPointF m_cursorPos; QPointF m_cursorPos;
QTimer *m_cursorUpdateTimer = nullptr; QTimer *m_cursorUpdateTimer = nullptr;
}; };

View File

@ -11,22 +11,12 @@ Item {
required property QtObject effect required property QtObject effect
required property QtObject targetScreen required property QtObject targetScreen
property bool animationEnabled: false
function start() {
root.animationEnabled = true;
}
function stop() {
}
View3D { View3D {
id: view
anchors.fill: parent anchors.fill: parent
environment: SceneEnvironment { environment: SceneEnvironment {
antialiasingMode: SceneEnvironment.MSAA antialiasingMode: SceneEnvironment.MSAA
} }
PerspectiveCamera { PerspectiveCamera {
id: camera id: camera
frustumCullingEnabled: false frustumCullingEnabled: false
@ -42,6 +32,6 @@ Item {
camera: camera camera: camera
} }
} }
Component.onCompleted: start(); Component.onCompleted: start();
} }