breezy-desktop/kwin/src/breezydesktopeffect.cpp

235 lines
6.7 KiB
C++

#include "breezydesktopeffect.h"
#include "effect/effect.h"
#include "effect/effecthandler.h"
#include <QAction>
#include <QFile>
#include <QLoggingCategory>
#include <QQuickItem>
#include <QTimer>
#include <KGlobalAccel>
#include <KLocalizedString>
Q_LOGGING_CATEGORY(KWIN_XR, "kwin.xr")
namespace KWin
{
BreezyDesktopEffect::BreezyDesktopEffect()
: m_shutdownTimer(new QTimer(this))
{
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"));
m_toggleAction->setText(i18n("Toggle BreezyDesktop"));
KGlobalAccel::self()->setDefaultShortcut(m_toggleAction, {defaultToggleShortcut});
KGlobalAccel::self()->setShortcut(m_toggleAction, {defaultToggleShortcut});
m_toggleShortcut = KGlobalAccel::self()->shortcut(m_toggleAction);
connect(m_toggleAction, &QAction::triggered, this, &BreezyDesktopEffect::toggle);
connect(KGlobalAccel::self(), &KGlobalAccel::globalShortcutChanged, this, [this](QAction *action, const QKeySequence &seq) {
if (action->objectName() == QStringLiteral("BreezyDesktop")) {
m_toggleShortcut.clear();
m_toggleShortcut.append(seq);
}
});
setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/effects/breezy_desktop/qml/main.qml"))));
m_xrRotationTimer = new QTimer(this);
m_xrRotationTimer->setInterval(16); // ~60Hz
connect(m_xrRotationTimer, &QTimer::timeout, this, &BreezyDesktopEffect::updateXrRotation);
m_xrRotationTimer->start();
toggle();
}
QVariantMap BreezyDesktopEffect::initialProperties(Output *screen)
{
return QVariantMap{
{QStringLiteral("effect"), QVariant::fromValue(this)},
{QStringLiteral("targetScreen"), QVariant::fromValue(screen)},
};
}
int BreezyDesktopEffect::requestedEffectChainPosition() const
{
return 70;
}
void BreezyDesktopEffect::toggle()
{
if (isRunning()) {
deactivate();
} else {
qCCritical(KWIN_XR) << "\t\t\tBreezy - activate";
activate();
}
}
void BreezyDesktopEffect::activate()
{
if (effects->isScreenLocked()) {
return;
}
setRunning(true);
// 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.
// TODO - move away from QuickSceneEffect
effects->ungrabKeyboard();
effects->stopMouseInterception(this);
}
void BreezyDesktopEffect::deactivate()
{
if (m_shutdownTimer->isActive()) {
return;
}
const QList<Output *> screens = effects->screens();
for (Output *screen : screens) {
if (QuickSceneView *view = viewForScreen(screen)) {
QMetaObject::invokeMethod(view->rootItem(), "stop");
}
}
m_shutdownTimer->start(animationDuration());
}
void BreezyDesktopEffect::realDeactivate()
{
setRunning(false);
}
int BreezyDesktopEffect::animationDuration() const
{
return 200;
}
qreal BreezyDesktopEffect::faceDisplacement() const
{
return 100;
}
qreal BreezyDesktopEffect::distanceFactor() const
{
return 1.5;
}
bool BreezyDesktopEffect::mouseInvertedX() const
{
return false;
}
bool BreezyDesktopEffect::mouseInvertedY() const
{
return false;
}
BreezyDesktopEffect::BackgroundMode BreezyDesktopEffect::backgroundMode() const
{
return BackgroundMode::Color;
}
QColor BreezyDesktopEffect::backgroundColor() const
{
return QColor(Qt::black);
}
QQuaternion BreezyDesktopEffect::xrRotation() const {
return m_xrRotation;
}
void BreezyDesktopEffect::updateXrRotation() {
const QString shmPath = QStringLiteral("/dev/shm/breezy_desktop_imu");
QFile shmFile(shmPath);
if (!shmFile.open(QIODevice::ReadOnly)) {
return;
}
QByteArray buffer = shmFile.readAll();
shmFile.close();
if (buffer.size() < 64) { // Minimum expected size based on the data structure
return;
}
// Create a data view for reading binary data
const char* data = buffer.constData();
// Use proper data positions based on the original GJS layout
// VERSION at offset 0, ENABLED at offset 1, etc.
// Read version and enabled flags at their correct positions
quint8 version = static_cast<quint8>(data[0]); // VERSION at offset 0
quint8 enabledFlag = static_cast<quint8>(data[1]); // ENABLED at offset 1
// DISPLAY_FOV is at offset: 1 + 1 + (4*4) + (4*2) = 26
float displayFov;
memcpy(&displayFov, data + 26, sizeof(float));
// EPOCH_MS is at offset: 26 + 4 + 4 + 1 + 1 + 1 + (4*16) = 101
quint64 imuDateMs;
memcpy(&imuDateMs, data + 101, sizeof(quint64));
imuDateMs = qFromLittleEndian(imuDateMs);
// IMU_QUAT_DATA is at offset: 101 + 8 = 109
float imuData[4];
memcpy(imuData, data + 109, sizeof(imuData));
// Validate data
const quint64 currentTimeMs = QDateTime::currentMSecsSinceEpoch();
const bool validKeepAlive = (currentTimeMs - imuDateMs) < 5000; // 5 second timeout
const bool validData = validKeepAlive && displayFov != 0.0f;
const quint8 expectedVersion = 4; // Define expected data layout version
const bool enabled = (enabledFlag != 0) && (version == expectedVersion) && validData;
if (!enabled) {
if (isRunning()) {
qCCritical(KWIN_XR) << "\t\t\tBreezy - deactivate due to disabled";
deactivate();
}
return;
}
// Check for reset state (identity quaternion)
const bool imuResetState = (imuData[0] == 0.0f && imuData[1] == 0.0f &&
imuData[2] == 0.0f && imuData[3] == 1.0f);
if (imuResetState) {
if (isRunning()) {
qCCritical(KWIN_XR) << "\t\t\tBreezy - deactivate due to reset state";
deactivate();
}
return;
}
// Create quaternion (w, x, y, z)
QQuaternion quat(imuData[3], imuData[0], imuData[1], imuData[2]);
if (quat != m_xrRotation) {
m_xrRotation = quat;
if (!isRunning()) {
qCCritical(KWIN_XR) << "\t\t\tBreezy - activate";
activate();
}
Q_EMIT xrRotationChanged();
}
}
} // namespace KWin