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
{
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<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;
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<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?
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<uint8_t>(data[0]);
uint8_t enabledFlag = static_cast<uint8_t>(data[1]);
if (!checkParityByte(data)) return;
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;
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<quint32>(imuData[imuDataOffset + 0] - imuData[imuDataOffset + 1]);
m_imuTimestamp = imuDateMs;
if (!isRunning()) {
qCCritical(KWIN_XR) << "\t\t\tBreezy - activate";
activate();
}
Q_EMIT imuRotationsChanged();
}

View File

@ -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<QQuaternion> 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<QQuaternion> imuRotations() const;
quint32 imuTimeElapsedMs() const;
quint64 imuTimestamp() const;
bool imuResetState() const;
QList<qreal> lookAheadConfig() const;
QList<quint32> displayResolution() const;
@ -64,10 +69,6 @@ namespace KWin
void showCursor();
void hideCursor();
QList<QQuaternion> 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<ElectricBorder> m_touchBorderActivate;
QString m_cursorImageSource;
bool m_enabled = false;
bool m_imuResetState;
QList<QQuaternion> m_imuRotations;
quint32 m_imuTimeElapsedMs;
quint64 m_imuTimestamp;
quint64 m_imuTimestamp = 0;
QList<qreal> m_lookAheadConfig;
QList<quint32> 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;
};

View File

@ -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();
}