Update Breezy KDE to support 6DoF position

This commit is contained in:
wheaney 2025-10-22 14:49:19 -07:00
parent e31778f6cc
commit c01b18d678
5 changed files with 128 additions and 114 deletions

View File

@ -98,11 +98,12 @@ namespace DataView
constexpr int CUSTOM_BANNER_ENABLED[3] = {dataViewEnd(SBS_ENABLED), BOOL_SIZE, 1}; constexpr int CUSTOM_BANNER_ENABLED[3] = {dataViewEnd(SBS_ENABLED), BOOL_SIZE, 1};
constexpr int SMOOTH_FOLLOW_ENABLED[3] = {dataViewEnd(CUSTOM_BANNER_ENABLED), BOOL_SIZE, 1}; constexpr int SMOOTH_FOLLOW_ENABLED[3] = {dataViewEnd(CUSTOM_BANNER_ENABLED), BOOL_SIZE, 1};
constexpr int SMOOTH_FOLLOW_ORIGIN_DATA[3] = {dataViewEnd(SMOOTH_FOLLOW_ENABLED), FLOAT_SIZE, 16}; constexpr int SMOOTH_FOLLOW_ORIGIN_DATA[3] = {dataViewEnd(SMOOTH_FOLLOW_ENABLED), FLOAT_SIZE, 16};
constexpr int IMU_DATE_MS[3] = {dataViewEnd(SMOOTH_FOLLOW_ORIGIN_DATA), UINT_SIZE, 2}; constexpr int POSE_POSITION_DATA[3] = {dataViewEnd(SMOOTH_FOLLOW_ORIGIN_DATA), FLOAT_SIZE, 3};
constexpr int IMU_QUAT_ENTRIES = 4; constexpr int POSE_DATE_MS[3] = {dataViewEnd(POSE_POSITION_DATA), UINT_SIZE, 2};
constexpr int IMU_QUAT_DATA[3] = {dataViewEnd(IMU_DATE_MS), FLOAT_SIZE, 4 * IMU_QUAT_ENTRIES}; constexpr int POSE_ORIENTATION_ENTRIES = 4;
constexpr int IMU_PARITY_BYTE[3] = {dataViewEnd(IMU_QUAT_DATA), UINT8_SIZE, 1}; constexpr int POSE_ORIENTATION_DATA[3] = {dataViewEnd(POSE_DATE_MS), FLOAT_SIZE, 4 * POSE_ORIENTATION_ENTRIES};
constexpr int LENGTH = dataViewEnd(IMU_PARITY_BYTE); constexpr int POSE_PARITY_BYTE[3] = {dataViewEnd(POSE_ORIENTATION_DATA), UINT8_SIZE, 1};
constexpr int LENGTH = dataViewEnd(POSE_PARITY_BYTE);
} }
namespace KWin namespace KWin
@ -142,7 +143,7 @@ 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 IPC file for changes, even if it doesn't exist at startup
m_shmDirectoryWatcher = new QFileSystemWatcher(this); m_shmDirectoryWatcher = new QFileSystemWatcher(this);
m_shmDirectoryWatcher->addPath(DataView::SHM_DIR); m_shmDirectoryWatcher->addPath(DataView::SHM_DIR);
@ -151,14 +152,14 @@ BreezyDesktopEffect::BreezyDesktopEffect()
// Setup file watcher with recreation detection // Setup file watcher with recreation detection
auto setupFileWatcher = [this]() { auto setupFileWatcher = [this]() {
if (QFile::exists(DataView::SHM_PATH) && ( if (QFile::exists(DataView::SHM_PATH) && (
m_imuTimestamp == 0 || m_poseTimestamp == 0 ||
QDateTime::currentMSecsSinceEpoch() - m_imuTimestamp > 50 || // file may have been deleted and recreated QDateTime::currentMSecsSinceEpoch() - m_poseTimestamp > 50 || // file may have been deleted and recreated
!m_shmFileWatcher->files().contains(DataView::SHM_PATH) !m_shmFileWatcher->files().contains(DataView::SHM_PATH)
)) { )) {
m_shmFileWatcher->removePath(DataView::SHM_PATH); m_shmFileWatcher->removePath(DataView::SHM_PATH);
disconnect(m_shmFileWatcher, &QFileSystemWatcher::fileChanged, this, &BreezyDesktopEffect::updateImuRotation); disconnect(m_shmFileWatcher, &QFileSystemWatcher::fileChanged, this, &BreezyDesktopEffect::updatePoseOrientation);
m_shmFileWatcher->addPath(DataView::SHM_PATH); m_shmFileWatcher->addPath(DataView::SHM_PATH);
connect(m_shmFileWatcher, &QFileSystemWatcher::fileChanged, this, &BreezyDesktopEffect::updateImuRotation); connect(m_shmFileWatcher, &QFileSystemWatcher::fileChanged, this, &BreezyDesktopEffect::updatePoseOrientation);
} }
}; };
@ -168,13 +169,13 @@ BreezyDesktopEffect::BreezyDesktopEffect()
// Initial setup // Initial setup
setupFileWatcher(); setupFileWatcher();
m_imuWatchdogTimer = new QTimer(this); m_watchdogTimer = new QTimer(this);
m_imuWatchdogTimer->setInterval(1000); m_watchdogTimer->setInterval(1000);
connect(m_imuWatchdogTimer, &QTimer::timeout, this, [this]() { connect(m_watchdogTimer, &QTimer::timeout, this, [this]() {
if (!m_enabled) return; if (!m_enabled) return;
this->updateImuRotation(); this->updatePoseOrientation();
}); });
m_imuWatchdogTimer->start(); m_watchdogTimer->start();
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);
@ -206,10 +207,10 @@ BreezyDesktopEffect::~BreezyDesktopEffect()
m_shmDirectoryWatcher->deleteLater(); m_shmDirectoryWatcher->deleteLater();
m_shmDirectoryWatcher = nullptr; m_shmDirectoryWatcher = nullptr;
} }
if (m_imuWatchdogTimer) { if (m_watchdogTimer) {
m_imuWatchdogTimer->stop(); m_watchdogTimer->stop();
m_imuWatchdogTimer->deleteLater(); m_watchdogTimer->deleteLater();
m_imuWatchdogTimer = nullptr; m_watchdogTimer = nullptr;
} }
deactivate(); deactivate();
} }
@ -439,20 +440,24 @@ void BreezyDesktopEffect::toggleSmoothFollow() {
XRDriverIPC::instance().writeControlFlags(flags); XRDriverIPC::instance().writeControlFlags(flags);
} }
bool BreezyDesktopEffect::imuResetState() const { bool BreezyDesktopEffect::poseResetState() const {
return m_imuResetState; return m_poseResetState;
} }
QList<QQuaternion> BreezyDesktopEffect::imuRotations() const { QList<QQuaternion> BreezyDesktopEffect::poseOrientations() const {
return m_imuRotations; return m_poseOrientations;
} }
quint32 BreezyDesktopEffect::imuTimeElapsedMs() const { QVector3D BreezyDesktopEffect::posePosition() const {
return m_imuTimeElapsedMs; return m_posePosition;
} }
quint64 BreezyDesktopEffect::imuTimestamp() const { quint32 BreezyDesktopEffect::poseTimeElapsedMs() const {
return m_imuTimestamp; return m_poseTimeElapsedMs;
}
quint64 BreezyDesktopEffect::poseTimestamp() const {
return m_poseTimestamp;
} }
QList<qreal> BreezyDesktopEffect::lookAheadConfig() const { QList<qreal> BreezyDesktopEffect::lookAheadConfig() const {
@ -581,17 +586,17 @@ bool BreezyDesktopEffect::smoothFollowEnabled() const {
} }
bool BreezyDesktopEffect::checkParityByte(const char* data) { bool BreezyDesktopEffect::checkParityByte(const char* data) {
const uint8_t parityByte = static_cast<uint8_t>(data[DataView::IMU_PARITY_BYTE[DataView::OFFSET_INDEX]]); const uint8_t parityByte = static_cast<uint8_t>(data[DataView::POSE_PARITY_BYTE[DataView::OFFSET_INDEX]]);
uint8_t parity = 0; uint8_t parity = 0;
const int dateBytes = DataView::IMU_DATE_MS[DataView::COUNT_INDEX] * DataView::IMU_DATE_MS[DataView::SIZE_INDEX]; const int dateBytes = DataView::POSE_DATE_MS[DataView::COUNT_INDEX] * DataView::POSE_DATE_MS[DataView::SIZE_INDEX];
for (int i = 0; i < dateBytes; ++i) { for (int i = 0; i < dateBytes; ++i) {
parity ^= static_cast<uint8_t>(data[DataView::IMU_DATE_MS[DataView::OFFSET_INDEX] + i]); parity ^= static_cast<uint8_t>(data[DataView::POSE_DATE_MS[DataView::OFFSET_INDEX] + i]);
} }
const int quatBytes = DataView::IMU_QUAT_DATA[DataView::COUNT_INDEX] * DataView::IMU_QUAT_DATA[DataView::SIZE_INDEX]; const int quatBytes = DataView::POSE_ORIENTATION_DATA[DataView::COUNT_INDEX] * DataView::POSE_ORIENTATION_DATA[DataView::SIZE_INDEX];
for (int i = 0; i < quatBytes; ++i) { for (int i = 0; i < quatBytes; ++i) {
parity ^= static_cast<uint8_t>(data[DataView::IMU_QUAT_DATA[DataView::OFFSET_INDEX] + i]); parity ^= static_cast<uint8_t>(data[DataView::POSE_ORIENTATION_DATA[DataView::OFFSET_INDEX] + i]);
} }
return parityByte == parity; return parityByte == parity;
@ -599,15 +604,15 @@ bool BreezyDesktopEffect::checkParityByte(const char* data) {
static qint64 lastConfigUpdate = 0; static qint64 lastConfigUpdate = 0;
static qint64 activatedAt = 0; static qint64 activatedAt = 0;
void BreezyDesktopEffect::updateImuRotation() { void BreezyDesktopEffect::updatePoseOrientation() {
// Reentrancy guard: if an update is already in progress, skip // Reentrancy guard: if an update is already in progress, skip
bool expected = false; bool expected = false;
if (!m_imuUpdateInProgress.compare_exchange_strong(expected, true)) { if (!m_poseUpdateInProgress.compare_exchange_strong(expected, true)) {
return; return;
} }
// destructor called on function exit, triggers reset of the flag // destructor called on function exit, triggers reset of the flag
struct ResetFlag { std::atomic<bool>* f; ~ResetFlag(){ f->store(false); } } reset{&m_imuUpdateInProgress}; struct ResetFlag { std::atomic<bool>* f; ~ResetFlag(){ f->store(false); } } reset{&m_poseUpdateInProgress};
const QString shmPath = QStringLiteral("/dev/shm/breezy_desktop_imu"); const QString shmPath = QStringLiteral("/dev/shm/breezy_desktop_imu");
QFile shmFile(shmPath); QFile shmFile(shmPath);
@ -623,9 +628,9 @@ void BreezyDesktopEffect::updateImuRotation() {
uint8_t version = static_cast<uint8_t>(data[DataView::VERSION[DataView::OFFSET_INDEX]]); 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]]); uint8_t enabledFlag = static_cast<uint8_t>(data[DataView::ENABLED[DataView::OFFSET_INDEX]]);
uint64_t imuDateMs; uint64_t poseDateMs;
memcpy(&imuDateMs, data + DataView::IMU_DATE_MS[DataView::OFFSET_INDEX], sizeof(imuDateMs)); memcpy(&poseDateMs, data + DataView::POSE_DATE_MS[DataView::OFFSET_INDEX], sizeof(poseDateMs));
imuDateMs = qFromLittleEndian(imuDateMs); poseDateMs = qFromLittleEndian(poseDateMs);
const qint64 currentTimeMs = QDateTime::currentMSecsSinceEpoch(); const qint64 currentTimeMs = QDateTime::currentMSecsSinceEpoch();
const bool updateConfig = lastConfigUpdate == 0 || currentTimeMs - lastConfigUpdate > 1000; const bool updateConfig = lastConfigUpdate == 0 || currentTimeMs - lastConfigUpdate > 1000;
@ -664,9 +669,9 @@ void BreezyDesktopEffect::updateImuRotation() {
lastConfigUpdate = currentTimeMs; lastConfigUpdate = currentTimeMs;
} }
const bool validKeepAlive = (currentTimeMs - imuDateMs) < 5000; const bool validKeepAlive = (currentTimeMs - poseDateMs) < 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 = 5;
bool enabledFlagSet = (enabledFlag != 0); bool enabledFlagSet = (enabledFlag != 0);
bool validVersion = (version == expectedVersion); bool validVersion = (version == expectedVersion);
const bool wasEnabled = m_enabled; const bool wasEnabled = m_enabled;
@ -675,7 +680,7 @@ void BreezyDesktopEffect::updateImuRotation() {
// give a grace period after enabling the effect // give a grace period after enabling the effect
if (wasEnabled && (currentTimeMs - activatedAt > 1000)) { if (wasEnabled && (currentTimeMs - activatedAt > 1000)) {
qCCritical(KWIN_XR) << "\t\t\tBreezy - disabling effect; currentTimeMs:" << currentTimeMs qCCritical(KWIN_XR) << "\t\t\tBreezy - disabling effect; currentTimeMs:" << currentTimeMs
<< "imuDateMs:" << imuDateMs << "poseDateMs:" << poseDateMs
<< "enabledFlag:" << enabledFlag << "enabledFlag:" << enabledFlag
<< "version:" << version << "version:" << version
<< "diagonalFOV:" << m_diagonalFOV; << "diagonalFOV:" << m_diagonalFOV;
@ -686,7 +691,7 @@ void BreezyDesktopEffect::updateImuRotation() {
} }
} else if (!wasEnabled) { } else if (!wasEnabled) {
qCCritical(KWIN_XR) << "\t\t\tBreezy - enabling effect; currentTimeMs:" << currentTimeMs qCCritical(KWIN_XR) << "\t\t\tBreezy - enabling effect; currentTimeMs:" << currentTimeMs
<< "imuDateMs:" << imuDateMs << "poseDateMs:" << poseDateMs
<< "enabledFlag:" << enabledFlag << "enabledFlag:" << enabledFlag
<< "version:" << version << "version:" << version
<< "diagonalFOV:" << m_diagonalFOV; << "diagonalFOV:" << m_diagonalFOV;
@ -698,50 +703,56 @@ void BreezyDesktopEffect::updateImuRotation() {
if (updateConfig) Q_EMIT devicePropertiesChanged(); if (updateConfig) Q_EMIT devicePropertiesChanged();
float imuData[4 * DataView::IMU_QUAT_ENTRIES]; // 4 quaternion-sized rows float posePositionData[3];
memcpy(imuData, data + DataView::IMU_QUAT_DATA[DataView::OFFSET_INDEX], sizeof(imuData)); memcpy(posePositionData, data + DataView::POSE_POSITION_DATA[DataView::OFFSET_INDEX], sizeof(posePositionData));
bool wasImuResetState = m_imuResetState;
m_imuResetState = (imuData[0] == 0.0f && imuData[1] == 0.0f && imuData[2] == 0.0f && imuData[3] == 1.0f); // convert NWU to EUS by passing position values: -y, z, -x
if (m_imuResetState != wasImuResetState) { m_posePosition = QVector3D(-posePositionData[1], posePositionData[2], -posePositionData[0]);
if (m_imuResetState) recenter();
Q_EMIT imuResetStateChanged(); float poseOrientationData[4 * DataView::POSE_ORIENTATION_ENTRIES]; // 4 quaternion-sized rows
memcpy(poseOrientationData, data + DataView::POSE_ORIENTATION_DATA[DataView::OFFSET_INDEX], sizeof(poseOrientationData));
bool wasPoseResetState = m_poseResetState;
m_poseResetState = (poseOrientationData[0] == 0.0f && poseOrientationData[1] == 0.0f && poseOrientationData[2] == 0.0f && poseOrientationData[3] == 1.0f);
if (m_poseResetState != wasPoseResetState) {
if (m_poseResetState) recenter();
Q_EMIT poseResetStateChanged();
} }
// convert NWU to EUS by passing root.rotation values: -y, z, -x // convert NWU to EUS by passing orientation values: -y, z, -x
QQuaternion quatT0(imuData[3], -imuData[1], imuData[2], -imuData[0]); QQuaternion quatT0(poseOrientationData[3], -poseOrientationData[1], poseOrientationData[2], -poseOrientationData[0]);
int imuDataOffset = DataView::IMU_QUAT_ENTRIES; int orientationDataOffset = DataView::POSE_ORIENTATION_ENTRIES;
QQuaternion quatT1(imuData[imuDataOffset + 3], -imuData[imuDataOffset + 1], imuData[imuDataOffset + 2], -imuData[imuDataOffset + 0]); QQuaternion quatT1(poseOrientationData[orientationDataOffset + 3], -poseOrientationData[orientationDataOffset + 1], poseOrientationData[orientationDataOffset + 2], -poseOrientationData[orientationDataOffset + 0]);
imuDataOffset += DataView::IMU_QUAT_ENTRIES; orientationDataOffset += DataView::POSE_ORIENTATION_ENTRIES;
// skip the 3rd quaternion // skip the 3rd quaternion
imuDataOffset += DataView::IMU_QUAT_ENTRIES; orientationDataOffset += DataView::POSE_ORIENTATION_ENTRIES;
// set imuRotations to the last two rotations, leave out the elapsed time // set poseOrientations to the last two rotations, leave out the elapsed time
m_imuRotations.clear(); m_poseOrientations.clear();
m_imuRotations.append(quatT0); m_poseOrientations.append(quatT0);
m_imuRotations.append(quatT1); m_poseOrientations.append(quatT1);
// 4th row isn't actually a quaternion, it contains the timestamps for each of the 3 quaternions // 4th row isn't actually a quaternion, it contains the timestamps for each of the 3 quaternions
// elapsed time between T0 and T1 is: imuData[0] - imuData[1] // elapsed time between T0 and T1 is: poseOrientationData[0] - poseOrientationData[1]
m_imuTimeElapsedMs = static_cast<quint32>(imuData[imuDataOffset + 0] - imuData[imuDataOffset + 1]); m_poseTimeElapsedMs = static_cast<quint32>(poseOrientationData[orientationDataOffset + 0] - poseOrientationData[orientationDataOffset + 1]);
m_imuTimestamp = imuDateMs; m_poseTimestamp = poseDateMs;
float originData[4 * DataView::IMU_QUAT_ENTRIES]; // 4 quaternion-sized rows float originData[4 * DataView::POSE_ORIENTATION_ENTRIES]; // 4 quaternion-sized rows
memcpy(originData, data + DataView::SMOOTH_FOLLOW_ORIGIN_DATA[DataView::OFFSET_INDEX], sizeof(originData)); memcpy(originData, data + DataView::SMOOTH_FOLLOW_ORIGIN_DATA[DataView::OFFSET_INDEX], sizeof(originData));
// convert NWU to EUS by passing root.rotation values: -y, z, -x // convert NWU to EUS by passing root.rotation values: -y, z, -x
QQuaternion sfQuatT0(originData[3], -originData[1], originData[2], -originData[0]); QQuaternion sfQuatT0(originData[3], -originData[1], originData[2], -originData[0]);
int originDataOffset = DataView::IMU_QUAT_ENTRIES; int originDataOffset = DataView::POSE_ORIENTATION_ENTRIES;
QQuaternion sfQuatT1(originData[originDataOffset + 3], -originData[originDataOffset + 1], originData[originDataOffset + 2], -originData[originDataOffset + 0]); QQuaternion sfQuatT1(originData[originDataOffset + 3], -originData[originDataOffset + 1], originData[originDataOffset + 2], -originData[originDataOffset + 0]);
originDataOffset += DataView::IMU_QUAT_ENTRIES; originDataOffset += DataView::POSE_ORIENTATION_ENTRIES;
// skip the 3rd quaternion // skip the 3rd quaternion
originDataOffset += DataView::IMU_QUAT_ENTRIES; originDataOffset += DataView::POSE_ORIENTATION_ENTRIES;
// set smoothFollowOrigin to the last two rotations, leave out the elapsed time // set smoothFollowOrigin to the last two rotations, leave out the elapsed time
m_smoothFollowOrigin.clear(); m_smoothFollowOrigin.clear();
@ -878,9 +889,9 @@ void BreezyDesktopEffect::evaluateCursorOnScreenState(const QPointF &prevPos, co
const bool onScreen = const bool onScreen =
m_effectOnScreenExpandedGeometry.contains(newPos.toPoint()) || m_effectOnScreenExpandedGeometry.contains(newPos.toPoint()) ||
m_effectOnScreenExpandedGeometry.contains(predicted.toPoint()); m_effectOnScreenExpandedGeometry.contains(predicted.toPoint());
if (m_enabled && !m_imuResetState && !m_cursorHidden && onScreen) { if (m_enabled && !m_poseResetState && !m_cursorHidden && onScreen) {
hideCursor(); hideCursor();
} else if (m_cursorHidden && (!m_enabled || m_imuResetState || !onScreen)) { } else if (m_cursorHidden && (!m_enabled || m_poseResetState || !onScreen)) {
showCursor(); showCursor();
} }
} }

View File

@ -24,10 +24,11 @@ namespace KWin
Q_PROPERTY(int effectTargetScreenIndex READ effectTargetScreenIndex WRITE setEffectTargetScreenIndex) Q_PROPERTY(int effectTargetScreenIndex READ effectTargetScreenIndex WRITE setEffectTargetScreenIndex)
Q_PROPERTY(bool zoomOnFocusEnabled READ isZoomOnFocusEnabled WRITE setZoomOnFocusEnabled NOTIFY zoomOnFocusChanged) Q_PROPERTY(bool zoomOnFocusEnabled READ isZoomOnFocusEnabled WRITE setZoomOnFocusEnabled NOTIFY zoomOnFocusChanged)
Q_PROPERTY(int lookingAtScreenIndex READ lookingAtScreenIndex WRITE setLookingAtScreenIndex) Q_PROPERTY(int lookingAtScreenIndex READ lookingAtScreenIndex WRITE setLookingAtScreenIndex)
Q_PROPERTY(bool imuResetState READ imuResetState NOTIFY imuResetStateChanged) Q_PROPERTY(bool poseResetState READ poseResetState NOTIFY poseResetStateChanged)
Q_PROPERTY(QList<QQuaternion> imuRotations READ imuRotations) Q_PROPERTY(QList<QQuaternion> poseOrientations READ poseOrientations)
Q_PROPERTY(quint32 imuTimeElapsedMs READ imuTimeElapsedMs) Q_PROPERTY(QVector3D posePosition READ posePosition)
Q_PROPERTY(quint64 imuTimestamp READ imuTimestamp) Q_PROPERTY(quint32 poseTimeElapsedMs READ poseTimeElapsedMs)
Q_PROPERTY(quint64 poseTimestamp READ poseTimestamp)
Q_PROPERTY(QString cursorImageSource READ cursorImageSource NOTIFY cursorImageSourceChanged) Q_PROPERTY(QString cursorImageSource READ cursorImageSource NOTIFY cursorImageSourceChanged)
Q_PROPERTY(QSize cursorImageSize READ cursorImageSize NOTIFY cursorImageSourceChanged) Q_PROPERTY(QSize cursorImageSize READ cursorImageSize NOTIFY cursorImageSourceChanged)
Q_PROPERTY(QPointF cursorPos READ cursorPos NOTIFY cursorPosChanged) Q_PROPERTY(QPointF cursorPos READ cursorPos NOTIFY cursorPosChanged)
@ -73,10 +74,11 @@ namespace KWin
void setZoomOnFocusEnabled(bool enabled); void setZoomOnFocusEnabled(bool enabled);
int lookingAtScreenIndex() const { return m_lookingAtScreenIndex; } int lookingAtScreenIndex() const { return m_lookingAtScreenIndex; }
void setLookingAtScreenIndex(int index); void setLookingAtScreenIndex(int index);
QList<QQuaternion> imuRotations() const; QList<QQuaternion> poseOrientations() const;
quint32 imuTimeElapsedMs() const; QVector3D posePosition() const;
quint64 imuTimestamp() const; quint32 poseTimeElapsedMs() const;
bool imuResetState() const; quint64 poseTimestamp() const;
bool poseResetState() const;
QList<qreal> lookAheadConfig() const; QList<qreal> lookAheadConfig() const;
qreal lookAheadOverride() const; qreal lookAheadOverride() const;
void setLookAheadOverride(qreal override); void setLookAheadOverride(qreal override);
@ -112,7 +114,7 @@ namespace KWin
void disableDriver(); void disableDriver();
void toggle(); void toggle();
void addVirtualDisplay(QSize size); void addVirtualDisplay(QSize size);
void updateImuRotation(); void updatePoseOrientation();
void updateCursorImage(); void updateCursorImage();
void updateCursorPos(); void updateCursorPos();
QVariantList listVirtualDisplays() const; QVariantList listVirtualDisplays() const;
@ -129,7 +131,7 @@ namespace KWin
void displayWrappingSchemeChanged(); void displayWrappingSchemeChanged();
void enabledStateChanged(); void enabledStateChanged();
void zoomOnFocusChanged(); void zoomOnFocusChanged();
void imuResetStateChanged(); void poseResetStateChanged();
void sbsEnabledChanged(); void sbsEnabledChanged();
void smoothFollowEnabledChanged(); void smoothFollowEnabledChanged();
void devicePropertiesChanged(); void devicePropertiesChanged();
@ -165,10 +167,11 @@ namespace KWin
bool m_zoomOnFocusEnabled = false; bool m_zoomOnFocusEnabled = false;
int m_lookingAtScreenIndex = -1; int m_lookingAtScreenIndex = -1;
int m_effectTargetScreenIndex = -1; int m_effectTargetScreenIndex = -1;
bool m_imuResetState; bool m_poseResetState;
QList<QQuaternion> m_imuRotations; QList<QQuaternion> m_poseOrientations;
quint32 m_imuTimeElapsedMs; QVector3D m_posePosition;
quint64 m_imuTimestamp = 0; quint32 m_poseTimeElapsedMs;
quint64 m_poseTimestamp = 0;
QList<qreal> m_lookAheadConfig; QList<qreal> m_lookAheadConfig;
qreal m_lookAheadOverride = -1.0; // -1 = use device default qreal m_lookAheadOverride = -1.0; // -1 = use device default
QList<quint32> m_displayResolution; QList<quint32> m_displayResolution;
@ -183,8 +186,8 @@ namespace KWin
bool m_cursorHidden = false; bool m_cursorHidden = false;
QPointF m_cursorPos; QPointF m_cursorPos;
QTimer *m_cursorUpdateTimer = nullptr; QTimer *m_cursorUpdateTimer = nullptr;
QTimer *m_imuWatchdogTimer = nullptr; QTimer *m_watchdogTimer = nullptr;
std::atomic<bool> m_imuUpdateInProgress{false}; std::atomic<bool> m_poseUpdateInProgress{false};
qreal m_focusedDisplayDistance = 0.85; qreal m_focusedDisplayDistance = 0.85;
qreal m_allDisplaysDistance = 1.05; qreal m_allDisplaysDistance = 1.05;
qreal m_displaySpacing = 0.0; qreal m_displaySpacing = 0.0;

View File

@ -26,11 +26,11 @@ Node {
} }
function updateFocus(smoothFollowEnabledChanged = false) { function updateFocus(smoothFollowEnabledChanged = false) {
const rotations = smoothFollowEnabled ? effect.smoothFollowOrigin : effect.imuRotations; const orientations = smoothFollowEnabled ? effect.smoothFollowOrigin : effect.poseOrientations;
if (rotations && rotations.length > 0) { if (orientations && orientations.length > 0) {
let focusedIndex = -1; let focusedIndex = -1;
const lookingAtIndex = displays.findFocusedMonitor( const lookingAtIndex = displays.findFocusedMonitor(
displays.eusToNwuQuat(rotations[0]), displays.eusToNwuQuat(orientations[0]),
breezyDesktop.monitorPlacements.map(monitorVectors => monitorVectors.centerLook), breezyDesktop.monitorPlacements.map(monitorVectors => monitorVectors.centerLook),
breezyDesktop.focusedMonitorIndex, breezyDesktop.focusedMonitorIndex,
smoothFollowEnabled, smoothFollowEnabled,
@ -120,10 +120,10 @@ Node {
} }
// smoothFollowOrigin is the rotation away from the original placement of the displays // smoothFollowOrigin is the rotation away from the original placement of the displays
// imuRotations is the smooth follow rotation relative to the camera (very near an identity quat) // poseOrientations is the smooth follow rotation relative to the camera (very near an identity quat)
// subtract the latter from the former to get the complete rotation // subtract the latter from the former to get the complete rotation
function smoothFollowQuat() { function smoothFollowQuat() {
return effect.smoothFollowOrigin[0].times(effect.imuRotations[0].conjugated()); return effect.smoothFollowOrigin[0].times(effect.poseOrientations[0].conjugated());
} }
function displaySmoothFollowVector(display, smoothFollowRotation) { function displaySmoothFollowVector(display, smoothFollowRotation) {
@ -192,9 +192,9 @@ Node {
} }
} }
// smoothFollowEnabled gets cleared before the IMU begins slerping back to the origin so we can't just // smoothFollowEnabled gets cleared before the orientation begins slerping back to the origin so we can't just
// switch off smooth follow logic based on this flag. Instead, we have to rely on // switch off smooth follow logic based on this flag. Instead, we have to rely on
// smoothFollowTransitionProgress to determine how much of the IMU positions to apply. // smoothFollowTransitionProgress to determine how much of the orientations to apply.
onSmoothFollowEnabledChanged: { onSmoothFollowEnabledChanged: {
updateFocus(true); updateFocus(true);
} }
@ -218,7 +218,7 @@ Node {
// When smooth follow is running, we're updating the position of the display manually // When smooth follow is running, we're updating the position of the display manually
// on every frame (avoid binding to a function that uses non-notify effect properties // on every frame (avoid binding to a function that uses non-notify effect properties
// imuRotations and smoothFollowOrigin). // poseOrientations and smoothFollowOrigin).
focusedDisplay.position = displayPosition(focusedDisplay, smoothFollowRotation); focusedDisplay.position = displayPosition(focusedDisplay, smoothFollowRotation);
} else { } else {
focusedDisplay.rotation = Qt.quaternion(1, 0, 0, 0); focusedDisplay.rotation = Qt.quaternion(1, 0, 0, 0);

View File

@ -22,17 +22,17 @@ Item {
aspectRatio aspectRatio
); );
// if true, then smoothFollowEnabled just cleared and the IMU data is slerping back, // if true, then smoothFollowEnabled just cleared and the orientation data is slerping back,
// continue to use the origin data for the duration of the Timer // continue to use the origin data for the duration of the Timer
property bool smoothFollowDisabling: false property bool smoothFollowDisabling: false
property real clipNear: 10.0 property real clipNear: 10.0
property real clipFar: 10000.0 property real clipFar: 10000.0
function ratesOfChange(rotations) { function ratesOfChange(orientations) {
const e0 = rotations[0].toEulerAngles(); const e0 = orientations[0].toEulerAngles();
const e1 = rotations[1].toEulerAngles(); const e1 = orientations[1].toEulerAngles();
const dt = effect.imuTimeElapsedMs; const dt = effect.poseTimeElapsedMs;
const yawDegrees = (e0.y - e1.y) / dt; const yawDegrees = (e0.y - e1.y) / dt;
const pitchDegrees = (e0.x - e1.x) / dt; const pitchDegrees = (e0.x - e1.x) / dt;
const rollDegrees = (e0.z - e1.z) / dt; const rollDegrees = (e0.z - e1.z) / dt;
@ -49,22 +49,22 @@ Item {
}; };
} }
function updateCamera(rotations, rates) { function updateCamera(orientations, position, rates) {
camera.eulerRotation = applyLookAhead( camera.eulerRotation = applyLookAhead(
rates, rates,
lookAheadMS( lookAheadMS(
effect.imuTimestamp, effect.poseTimestamp,
effect.lookAheadConfig, effect.lookAheadConfig,
effect.lookAheadOverride effect.lookAheadOverride
) )
); );
camera.position = rotations[0].times(Qt.vector3d(0, 0, -fovDetails.lensDistancePixels)); camera.position = position.times(fovDetails.completeScreenDistancePixels).plus(orientations[0].times(Qt.vector3d(0, 0, -fovDetails.lensDistancePixels)));
} }
// how far to look ahead is how old the IMU data is plus a constant that is either the default for this device or an override // how far to look ahead is how old the pose data is plus a constant that is either the default for this device or an override
function lookAheadMS(imuDateMs, lookAheadConfig, override) { function lookAheadMS(poseDateMs, lookAheadConfig, override) {
// how stale the imu data is // how stale the pose data is
const dataAge = Date.now() - imuDateMs; const dataAge = Date.now() - poseDateMs;
const lookAheadConstant = lookAheadConfig[0]; const lookAheadConstant = lookAheadConfig[0];
const lookAheadMultiplier = lookAheadConfig[1]; const lookAheadMultiplier = lookAheadConfig[1];
@ -148,10 +148,10 @@ Item {
FrameAnimation { FrameAnimation {
running: true running: true
onTriggered: { onTriggered: {
const rotations = (effect.smoothFollowEnabled || smoothFollowDisabling) ? effect.smoothFollowOrigin : effect.imuRotations; const orientations = (effect.smoothFollowEnabled || smoothFollowDisabling) ? effect.smoothFollowOrigin : effect.poseOrientations;
if (rotations && rotations.length > 0) { if (orientations && orientations.length > 0) {
const rates = ratesOfChange(rotations); const rates = ratesOfChange(orientations);
updateCamera(rotations, rates); updateCamera(orientations, effect.posePosition, rates);
applyRollingShutterShear(rates); applyRollingShutterShear(rates);
} }
} }

View File

@ -93,14 +93,14 @@ Item {
property bool targetScreenSupported: supportedModels.some(model => root.targetScreen.model.includes(model)) property bool targetScreenSupported: supportedModels.some(model => root.targetScreen.model.includes(model))
property bool targetScreenIsVirtual: targetScreen.name.includes("BreezyDesktop") property bool targetScreenIsVirtual: targetScreen.name.includes("BreezyDesktop")
property bool imuResetState: effect.imuResetState property bool poseResetState: effect.poseResetState
property bool isEnabled: effect.isEnabled property bool isEnabled: effect.isEnabled
Component { Component {
id: desktopViewComponent id: desktopViewComponent
SingleDesktopView { SingleDesktopView {
supportsXR: targetScreenSupported supportsXR: targetScreenSupported
showCalibratingBanner: isEnabled && imuResetState showCalibratingBanner: isEnabled && poseResetState
} }
} }
@ -141,8 +141,8 @@ Item {
} }
function checkLoadedComponent() { function checkLoadedComponent() {
console.log(`Breezy - checking screen ${targetScreen.model}: ${targetScreenSupported} ${targetScreenIsVirtual} ${isEnabled} ${imuResetState}`); console.log(`Breezy - checking screen ${targetScreen.model}: ${targetScreenSupported} ${targetScreenIsVirtual} ${isEnabled} ${poseResetState}`);
const show3DView = targetScreenSupported && isEnabled && !imuResetState; const show3DView = targetScreenSupported && isEnabled && !poseResetState;
if (!targetScreenIsVirtual) viewLoader.sourceComponent = show3DView ? view3DComponent : desktopViewComponent; if (!targetScreenIsVirtual) viewLoader.sourceComponent = show3DView ? view3DComponent : desktopViewComponent;
if (targetScreenSupported) effect.effectTargetScreenIndex = KWinComponents.Workspace.screens.indexOf(targetScreen); if (targetScreenSupported) effect.effectTargetScreenIndex = KWinComponents.Workspace.screens.indexOf(targetScreen);
} }
@ -151,7 +151,7 @@ Item {
checkLoadedComponent(); checkLoadedComponent();
} }
onImuResetStateChanged: { onPoseResetStateChanged: {
checkLoadedComponent(); checkLoadedComponent();
} }