diff --git a/kwin/src/breezydesktopconfig.kcfg b/kwin/src/breezydesktopconfig.kcfg index d65e0ea..33db6a1 100644 --- a/kwin/src/breezydesktopconfig.kcfg +++ b/kwin/src/breezydesktopconfig.kcfg @@ -67,6 +67,13 @@ Whether to remove any virtual displays when the effect is disabled + + 15 + 1 + 45 + + How closely the display follows + -1 -1 diff --git a/kwin/src/breezydesktopeffect.cpp b/kwin/src/breezydesktopeffect.cpp index 5520e32..5d0f028 100644 --- a/kwin/src/breezydesktopeffect.cpp +++ b/kwin/src/breezydesktopeffect.cpp @@ -118,6 +118,10 @@ BreezyDesktopEffect::BreezyDesktopEffect() this->setZoomOnFocusEnabled(!m_zoomOnFocusEnabled); } ); + setupGlobalShortcut( + BreezyShortcuts::TOGGLE_FOLLOW_MODE, + [this]() { this->toggleSmoothFollow(); } + ); connect(effects, &EffectsHandler::cursorShapeChanged, this, &BreezyDesktopEffect::updateCursorImage); updateCursorImage(); @@ -194,9 +198,9 @@ void BreezyDesktopEffect::setupGlobalShortcut(const BreezyShortcuts::Shortcut &s } void BreezyDesktopEffect::recenter() { - XRDriverIPC::instance().writeControlFlags({ - {"recenter_screen", true} - }); + QJsonObject flags; + flags.insert(QStringLiteral("recenter_screen"), true); + XRDriverIPC::instance().writeControlFlags(flags); } void BreezyDesktopEffect::reconfigure(ReconfigureFlags) @@ -378,6 +382,12 @@ void BreezyDesktopEffect::setZoomOnFocusEnabled(bool enabled) { } } +void BreezyDesktopEffect::toggleSmoothFollow() { + QJsonObject flags; + flags.insert(QStringLiteral("toggle_breezy_desktop_smooth_follow"), true); + XRDriverIPC::instance().writeControlFlags(flags); +} + bool BreezyDesktopEffect::imuResetState() const { return m_imuResetState; } @@ -421,6 +431,8 @@ void BreezyDesktopEffect::setFocusedDisplayDistance(qreal distance) { if (distance != m_focusedDisplayDistance) { m_focusedDisplayDistance = std::clamp(distance, 0.2, m_allDisplaysDistance); Q_EMIT focusedDisplayDistanceChanged(); + + if (m_smoothFollowEnabled) updateDriverDisplayDistance(m_focusedDisplayDistance); } } @@ -661,7 +673,15 @@ void BreezyDesktopEffect::updateImuRotation() { if (m_smoothFollowEnabled != nextSmoothFollowEnabled) { m_smoothFollowEnabled = nextSmoothFollowEnabled; Q_EMIT smoothFollowEnabledChanged(); - } + + if (nextSmoothFollowEnabled) updateDriverDisplayDistance(m_focusedDisplayDistance); + } else if (enabled && !wasEnabled) Q_EMIT smoothFollowEnabledChanged(); +} + +void BreezyDesktopEffect::updateDriverDisplayDistance(float distance) { + QJsonObject flags; + flags.insert(QStringLiteral("breezy_desktop_display_distance"), distance); + XRDriverIPC::instance().writeControlFlags(flags); } QString BreezyDesktopEffect::cursorImageSource() const diff --git a/kwin/src/breezydesktopeffect.h b/kwin/src/breezydesktopeffect.h index f7f1c2a..4c15f89 100644 --- a/kwin/src/breezydesktopeffect.h +++ b/kwin/src/breezydesktopeffect.h @@ -132,6 +132,8 @@ namespace KWin void setupGlobalShortcut(const BreezyShortcuts::Shortcut &shortcut, std::function triggeredFunc); void recenter(); + void toggleSmoothFollow(); + void updateDriverDisplayDistance(float distance); QString m_cursorImageSource; QSize m_cursorImageSize; diff --git a/kwin/src/kcm/breezydesktopeffectkcm.cpp b/kwin/src/kcm/breezydesktopeffectkcm.cpp index 2e4c2fb..5373f8c 100644 --- a/kwin/src/kcm/breezydesktopeffectkcm.cpp +++ b/kwin/src/kcm/breezydesktopeffectkcm.cpp @@ -107,13 +107,16 @@ BreezyDesktopEffectConfig::BreezyDesktopEffectConfig(QObject *parent, const KPlu addShortcutAction(actionCollection, BreezyShortcuts::TOGGLE); addShortcutAction(actionCollection, BreezyShortcuts::RECENTER); addShortcutAction(actionCollection, BreezyShortcuts::TOGGLE_ZOOM_ON_FOCUS); + addShortcutAction(actionCollection, BreezyShortcuts::TOGGLE_FOLLOW_MODE); ui.shortcutsEditor->addCollection(actionCollection); connect(ui.shortcutsEditor, &KShortcutsEditor::keyChange, this, &BreezyDesktopEffectConfig::markAsChanged); connect(ui.EffectEnabled, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::updateDriverEnabled); + connect(ui.SmoothFollowEnabled, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::updateSmoothFollowEnabled); connect(ui.kcfg_ZoomOnFocusEnabled, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::save); connect(ui.kcfg_FocusedDisplayDistance, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save); connect(ui.kcfg_AllDisplaysDistance, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save); connect(ui.kcfg_DisplaySpacing, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save); + connect(ui.kcfg_SmoothFollowThreshold, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::updateSmoothFollowThreshold); connect(ui.kcfg_DisplayHorizontalOffset, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save); connect(ui.kcfg_DisplayVerticalOffset, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save); connect(ui.kcfg_LookAheadOverride, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save); @@ -151,7 +154,9 @@ BreezyDesktopEffectConfig::BreezyDesktopEffectConfig(QObject *parent, const KPlu labelStatus->setVisible(false); bool success = XRDriverIPC::instance().verifyToken(edit->text().trimmed().toStdString()); if (success) { - XRDriverIPC::instance().writeControlFlags({{"refresh_device_license", true}}); + QJsonObject flags; + flags.insert(QStringLiteral("refresh_device_license"), true); + XRDriverIPC::instance().writeControlFlags(flags); } showStatus(labelStatus, success, success ? tr("Your license has been refreshed.") : tr("Invalid or expired token.")); setRequestInProgress({edit, sender()}, false); @@ -216,7 +221,8 @@ void BreezyDesktopEffectConfig::save() updateConfigFromUi(); BreezyDesktopConfig::self()->save(); KCModule::save(); - ui.kcfg_FocusedDisplayDistance->setEnabled(ui.kcfg_ZoomOnFocusEnabled->isChecked()); + ui.kcfg_FocusedDisplayDistance->setEnabled( + ui.kcfg_ZoomOnFocusEnabled->isChecked() || ui.SmoothFollowEnabled->isChecked()); m_updatingFromConfig = false; updateUnmanagedState(); @@ -250,6 +256,7 @@ void BreezyDesktopEffectConfig::updateUiFromConfig() ui.kcfg_RemoveVirtualDisplaysOnDisable->setChecked(BreezyDesktopConfig::self()->removeVirtualDisplaysOnDisable()); ui.kcfg_ZoomOnFocusEnabled->setChecked(BreezyDesktopConfig::self()->zoomOnFocusEnabled()); ui.kcfg_FocusedDisplayDistance->setEnabled(ui.kcfg_ZoomOnFocusEnabled->isChecked()); + ui.kcfg_SmoothFollowThreshold->setValue(BreezyDesktopConfig::self()->smoothFollowThreshold()); } void BreezyDesktopEffectConfig::updateUiFromDefaultConfig() @@ -446,6 +453,13 @@ void BreezyDesktopEffectConfig::pollDriverState() m_connectedDeviceBrand = stateJson.value(QStringLiteral("connected_device_brand")).toString(); m_connectedDeviceModel = stateJson.value(QStringLiteral("connected_device_model")).toString(); + const bool smoothFollow = smoothFollowEnabled(stateJsonOpt); + if (ui.SmoothFollowEnabled->isChecked() != smoothFollow) { + ui.SmoothFollowEnabled->setChecked(smoothFollow); + + ui.kcfg_FocusedDisplayDistance->setEnabled(ui.kcfg_ZoomOnFocusEnabled->isChecked() || smoothFollow); + } + const bool wasDeviceConnected = m_deviceConnected; m_deviceConnected = !m_connectedDeviceBrand.isEmpty() && !m_connectedDeviceModel.isEmpty(); if (!m_driverStateInitialized || m_deviceConnected != wasDeviceConnected) { @@ -471,6 +485,13 @@ bool BreezyDesktopEffectConfig::multitapEnabled(std::optional confi return configJson.value(QStringLiteral("multi_tap_enabled")).toBool(); } +bool BreezyDesktopEffectConfig::smoothFollowEnabled(std::optional stateJsonOpt) +{ + if (!stateJsonOpt) return false; + auto stateJson = stateJsonOpt.value(); + return stateJson.value(QStringLiteral("breezy_desktop_smooth_follow_enabled")).toBool(); +} + void BreezyDesktopEffectConfig::updateMultitapEnabled() { auto configJsonOpt = XRDriverIPC::instance().retrieveConfig(); @@ -486,6 +507,26 @@ void BreezyDesktopEffectConfig::updateMultitapEnabled() XRDriverIPC::instance().writeConfig(newConfig); } +void BreezyDesktopEffectConfig::updateSmoothFollowEnabled() +{ + auto stateJsonOpt = XRDriverIPC::instance().retrieveDriverState(); + if (smoothFollowEnabled(stateJsonOpt) == ui.SmoothFollowEnabled->isChecked()) { + return; + } + bool enabled = ui.SmoothFollowEnabled->isChecked(); + QJsonObject flags; + flags.insert(QStringLiteral("enable_breezy_desktop_smooth_follow"), enabled); + XRDriverIPC::instance().writeControlFlags(flags); +} + +void BreezyDesktopEffectConfig::updateSmoothFollowThreshold() +{ + BreezyDesktopEffectConfig::save(); + QJsonObject flags; + flags.insert(QStringLiteral("breezy_desktop_follow_threshold"), ui.kcfg_SmoothFollowThreshold->value()); + XRDriverIPC::instance().writeControlFlags(flags); +} + void BreezyDesktopEffectConfig::showStatus(QLabel *label, bool success, const QString &message) { if (!label) return; QPalette pal = label->palette(); diff --git a/kwin/src/kcm/breezydesktopeffectkcm.h b/kwin/src/kcm/breezydesktopeffectkcm.h index 8ec8404..4a8451e 100644 --- a/kwin/src/kcm/breezydesktopeffectkcm.h +++ b/kwin/src/kcm/breezydesktopeffectkcm.h @@ -30,12 +30,15 @@ public Q_SLOTS: private: void updateDriverEnabled(); void updateMultitapEnabled(); + void updateSmoothFollowEnabled(); + void updateSmoothFollowThreshold(); void updateUiFromConfig(); void updateUiFromDefaultConfig(); void updateConfigFromUi(); void updateUnmanagedState(); bool driverEnabled(std::optional configJsonOpt); bool multitapEnabled(std::optional configJsonOpt); + bool smoothFollowEnabled(std::optional stateJsonOpt); void pollDriverState(); void refreshLicenseUi(const QJsonObject &rootObj); void checkEffectLoaded(); @@ -55,6 +58,8 @@ private: bool m_updatingFromConfig = false; bool m_driverStateInitialized = false; bool m_deviceConnected = false; + bool m_smoothFollowEnabled = false; + int m_smoothFollowThreshold = 15; QString m_connectedDeviceBrand; QString m_connectedDeviceModel; QTimer m_statePollTimer; // periodic driver state polling diff --git a/kwin/src/kcm/breezydesktopeffectkcm.ui b/kwin/src/kcm/breezydesktopeffectkcm.ui index 4061954..f3d42bc 100644 --- a/kwin/src/kcm/breezydesktopeffectkcm.ui +++ b/kwin/src/kcm/breezydesktopeffectkcm.ui @@ -72,14 +72,24 @@ - + + + + Follow mode + + + false + + + + Focused Display Distance: - + 2 @@ -101,14 +111,14 @@ - + All Displays Distance: - + 2 @@ -130,14 +140,14 @@ - + Display Spacing: - + Qt::Horizontal @@ -147,7 +157,36 @@ - + + + + Follow threshold: + + + + + + + Qt::Horizontal + + + false + + + QSlider::NoTicks + + + 9 + + + 10 + + + Qt::Horizontal + + + + Add Virtual Display: @@ -160,7 +199,7 @@ - + false @@ -193,7 +232,7 @@ - + false false @@ -206,7 +245,7 @@ - + diff --git a/kwin/src/kcm/shortcuts.h b/kwin/src/kcm/shortcuts.h index f5792a6..1fd778d 100644 --- a/kwin/src/kcm/shortcuts.h +++ b/kwin/src/kcm/shortcuts.h @@ -28,4 +28,10 @@ namespace BreezyShortcuts { QStringLiteral("Toggle Zoom on Focus"), QStringLiteral("Toggle Zoom on Focus") }; + + const Shortcut TOGGLE_FOLLOW_MODE = { + Qt::CTRL | Qt::META | Qt::Key_Return, + QStringLiteral("Toggle Follow Mode"), + QStringLiteral("Toggle Follow Mode") + }; } diff --git a/kwin/src/xrdriveripc/xrdriveripc.cpp b/kwin/src/xrdriveripc/xrdriveripc.cpp index 4508f3d..2e93e56 100644 --- a/kwin/src/xrdriveripc/xrdriveripc.cpp +++ b/kwin/src/xrdriveripc/xrdriveripc.cpp @@ -89,9 +89,8 @@ bool XRDriverIPC::writeConfig(const QJsonObject &configUpdate) { return !out.isEmpty(); } -bool XRDriverIPC::writeControlFlags(const std::map &flags) { - QJsonObject obj; for (const auto &kv : flags) obj.insert(QString::fromStdString(kv.first), kv.second); - QByteArray payload = QJsonDocument(obj).toJson(QJsonDocument::Compact); +bool XRDriverIPC::writeControlFlags(const QJsonObject &flags) { + QByteArray payload = QJsonDocument(flags).toJson(QJsonDocument::Compact); QByteArray out = invokePython(QStringLiteral("write_control_flags"), payload, {}); return !out.isEmpty(); } diff --git a/kwin/src/xrdriveripc/xrdriveripc.h b/kwin/src/xrdriveripc/xrdriveripc.h index ee6ca81..d349c6e 100644 --- a/kwin/src/xrdriveripc/xrdriveripc.h +++ b/kwin/src/xrdriveripc/xrdriveripc.h @@ -82,7 +82,7 @@ public: std::optional retrieveConfig(); std::optional retrieveDriverState(); bool writeConfig(const QJsonObject &configUpdate); - bool writeControlFlags(const std::map &flags); + bool writeControlFlags(const QJsonObject &flags); bool requestToken(const std::string &email); bool verifyToken(const std::string &token); diff --git a/ui/modules/PyXRLinuxDriverIPC b/ui/modules/PyXRLinuxDriverIPC index 7fecfc6..d047bdf 160000 --- a/ui/modules/PyXRLinuxDriverIPC +++ b/ui/modules/PyXRLinuxDriverIPC @@ -1 +1 @@ -Subproject commit 7fecfc604b553b9155837c58aaae423ba00afc63 +Subproject commit d047bdf7b7300751a09464b6f62d67cb3e291031