From 7eab1b5ba1dfcc6884bf288fbdd8c38037c855d3 Mon Sep 17 00:00:00 2001
From: wheaney <42350981+wheaney@users.noreply.github.com>
Date: Thu, 15 Jan 2026 13:45:41 -0800
Subject: [PATCH] UI WIP
---
kwin/src/breezydesktopconfig.kcfg | 7 +-
kwin/src/kcm/breezydesktopeffectkcm.cpp | 96 +++++++++++++++++++++++++
kwin/src/kcm/breezydesktopeffectkcm.h | 8 ++-
kwin/src/kcm/breezydesktopeffectkcm.ui | 16 ++++-
kwin/src/kcm/labeledslider.h | 30 ++++++++
5 files changed, 153 insertions(+), 4 deletions(-)
diff --git a/kwin/src/breezydesktopconfig.kcfg b/kwin/src/breezydesktopconfig.kcfg
index d6f1bca..856f890 100644
--- a/kwin/src/breezydesktopconfig.kcfg
+++ b/kwin/src/breezydesktopconfig.kcfg
@@ -17,6 +17,11 @@
230
+
+ cm
+
+ Units for displaying distances in the KCM UI. Valid values: "cm" or "in".
+
false
@@ -30,7 +35,7 @@
How far apart the displays are visually (not logically)
- 100
+ 97
10
250
diff --git a/kwin/src/kcm/breezydesktopeffectkcm.cpp b/kwin/src/kcm/breezydesktopeffectkcm.cpp
index 527cc9e..8ee41ae 100644
--- a/kwin/src/kcm/breezydesktopeffectkcm.cpp
+++ b/kwin/src/kcm/breezydesktopeffectkcm.cpp
@@ -48,6 +48,8 @@
#include
#include
#include
+#include
+#include
#include
#include
@@ -201,6 +203,27 @@ BreezyDesktopEffectConfig::BreezyDesktopEffectConfig(QObject *parent, const KPlu
ui.setupUi(widget());
addConfig(BreezyDesktopConfig::self(), widget());
+ // Advanced tab: measurement units selector (stored as "cm" or "in")
+ if (ui.comboMeasurementUnits) {
+ ui.comboMeasurementUnits->clear();
+ ui.comboMeasurementUnits->addItem(i18n("Centimeters (cm)"), QStringLiteral("cm"));
+ ui.comboMeasurementUnits->addItem(i18n("Inches (in)"), QStringLiteral("in"));
+
+ {
+ QSignalBlocker b(ui.comboMeasurementUnits);
+ const QString saved = KConfigGroup(BreezyDesktopConfig::self()->sharedConfig(), QLatin1String(EFFECT_GROUP))
+ .readEntry(QStringLiteral("measurement_units"), QStringLiteral("cm"));
+ const int idx = ui.comboMeasurementUnits->findData(saved);
+ ui.comboMeasurementUnits->setCurrentIndex(idx >= 0 ? idx : 0);
+ }
+
+ connect(ui.comboMeasurementUnits, qOverload(&QComboBox::currentIndexChanged), this, [this](int) {
+ if (m_updatingFromConfig) return;
+ applyDistanceLabelFormatters();
+ save();
+ });
+ }
+
// One-time check if the KWin effect backend is actually loaded. If not, disable UI early.
checkEffectLoaded();
@@ -394,6 +417,8 @@ BreezyDesktopEffectConfig::BreezyDesktopEffectConfig(QObject *parent, const KPlu
lookAheadOverrideSlider->setValueText(-1, i18n("Default"));
}
+ applyDistanceLabelFormatters();
+
renderVirtualDisplays(dbusListVirtualDisplays());
m_virtualDisplayPollTimer.setInterval(15000);
@@ -431,6 +456,14 @@ void BreezyDesktopEffectConfig::save()
m_updatingFromConfig = true;
updateConfigFromUi();
BreezyDesktopConfig::self()->save();
+
+ // Store measurement_units explicitly (snake_case key) without depending on KConfigXT accessor naming.
+ {
+ KConfigGroup grp(BreezyDesktopConfig::self()->sharedConfig(), QLatin1String(EFFECT_GROUP));
+ grp.writeEntry(QStringLiteral("measurement_units"), measurementUnitsFromUi());
+ grp.sync();
+ }
+
KCModule::save();
ui.kcfg_FocusedDisplayDistance->setEnabled(
ui.kcfg_ZoomOnFocusEnabled->isChecked() || ui.SmoothFollowEnabled->isChecked());
@@ -472,11 +505,29 @@ void BreezyDesktopEffectConfig::updateUiFromConfig()
ui.kcfg_FocusedDisplayDistance->setEnabled(
ui.kcfg_ZoomOnFocusEnabled->isChecked() || ui.SmoothFollowEnabled->isChecked());
ui.kcfg_SmoothFollowThreshold->setValue(BreezyDesktopConfig::self()->smoothFollowThreshold());
+
+ if (ui.comboMeasurementUnits) {
+ QSignalBlocker b(ui.comboMeasurementUnits);
+ const QString saved = KConfigGroup(BreezyDesktopConfig::self()->sharedConfig(), QLatin1String(EFFECT_GROUP))
+ .readEntry(QStringLiteral("measurement_units"), QStringLiteral("cm"));
+ const int idx = ui.comboMeasurementUnits->findData(saved);
+ ui.comboMeasurementUnits->setCurrentIndex(idx >= 0 ? idx : 0);
+ }
+
+ applyDistanceLabelFormatters();
}
void BreezyDesktopEffectConfig::updateUiFromDefaultConfig()
{
ui.shortcutsEditor->allDefault();
+
+ if (ui.comboMeasurementUnits) {
+ QSignalBlocker b(ui.comboMeasurementUnits);
+ const int idx = ui.comboMeasurementUnits->findData(QStringLiteral("cm"));
+ ui.comboMeasurementUnits->setCurrentIndex(idx >= 0 ? idx : 0);
+ }
+
+ applyDistanceLabelFormatters();
}
void BreezyDesktopEffectConfig::updateUnmanagedState()
@@ -648,6 +699,11 @@ void BreezyDesktopEffectConfig::pollDriverState()
auto stateJson = stateJsonOpt.value();
m_connectedDeviceBrand = stateJson.value(QStringLiteral("connected_device_brand")).toString();
m_connectedDeviceModel = stateJson.value(QStringLiteral("connected_device_model")).toString();
+ m_connectedDeviceFullDistanceCm = stateJson.value(QStringLiteral("connected_device_full_distance_cm")).toDouble(0.0);
+ m_connectedDeviceFullSizeCm = stateJson.value(QStringLiteral("connected_device_full_size_cm")).toDouble(0.0);
+ m_connectedDevicePoseHasPosition = stateJson.value(QStringLiteral("connected_device_pose_has_position")).toBool(false);
+
+ applyDistanceLabelFormatters();
const bool smoothFollow = smoothFollowEnabled(stateJsonOpt);
if (ui.SmoothFollowEnabled->isChecked() != smoothFollow) {
@@ -711,6 +767,46 @@ void BreezyDesktopEffectConfig::pollDriverState()
m_driverStateInitialized = true;
}
+QString BreezyDesktopEffectConfig::measurementUnitsFromUi() const
+{
+ if (!ui.comboMeasurementUnits) return QStringLiteral("cm");
+ const QString v = ui.comboMeasurementUnits->currentData().toString();
+ if (v == QLatin1String("in")) return QStringLiteral("in");
+ return QStringLiteral("cm");
+}
+
+void BreezyDesktopEffectConfig::applyDistanceLabelFormatters()
+{
+ auto *focused = ui.kcfg_FocusedDisplayDistance;
+ auto *all = ui.kcfg_AllDisplaysDistance;
+ if (!focused || !all) return;
+
+ // Only apply the unit conversion labels when the driver reports positional tracking.
+ if (!m_connectedDevicePoseHasPosition) {
+ focused->clearValueToDisplayStringFn();
+ all->clearValueToDisplayStringFn();
+ return;
+ }
+
+ const double fullCm = static_cast(m_connectedDeviceFullDistanceCm);
+ const QString units = measurementUnitsFromUi();
+ const QLocale loc;
+
+ LabeledSlider::ValueToDisplayStringFn fn = [fullCm, units, loc](int raw) -> QString {
+ if (fullCm <= 0.0) return QString();
+ const double ratio = static_cast(raw) / 100.0; // slider uses a 2-decimal fixed-point scale
+ const double cm = ratio * fullCm;
+ if (units == QLatin1String("in")) {
+ const double inches = cm / 2.54;
+ return loc.toString(inches, 'f', 1) + QStringLiteral(" in");
+ }
+ return loc.toString(cm, 'f', 0) + QStringLiteral(" cm");
+ };
+
+ focused->setValueToDisplayStringFn(fn);
+ all->setValueToDisplayStringFn(fn);
+}
+
double BreezyDesktopEffectConfig::neckSaverHorizontalMultiplier(std::optional configJsonOpt)
{
if (!configJsonOpt) return 1.0;
diff --git a/kwin/src/kcm/breezydesktopeffectkcm.h b/kwin/src/kcm/breezydesktopeffectkcm.h
index 27ec9cd..9e1cb90 100644
--- a/kwin/src/kcm/breezydesktopeffectkcm.h
+++ b/kwin/src/kcm/breezydesktopeffectkcm.h
@@ -28,6 +28,9 @@ public Q_SLOTS:
void defaults() override;
private:
+ QString measurementUnitsFromUi() const;
+ void applyDistanceLabelFormatters();
+
void updateDriverEnabled();
void updateMultitapEnabled();
void updateSmoothFollowEnabled();
@@ -73,8 +76,9 @@ private:
int m_smoothFollowThreshold = 1;
QString m_connectedDeviceBrand;
QString m_connectedDeviceModel;
- int m_connectedDeviceFullDistanceCm = 0;
- int m_connectedDeviceFullSizeCm = 0;
+ float m_connectedDeviceFullDistanceCm = 0.0;
+ float m_connectedDeviceFullSizeCm = 0.0;
+ bool m_connectedDevicePoseHasPosition = false;
QTimer m_statePollTimer; // periodic driver state polling
QTimer m_virtualDisplayPollTimer; // periodic virtual display list polling
bool m_licenseLoading = false;
diff --git a/kwin/src/kcm/breezydesktopeffectkcm.ui b/kwin/src/kcm/breezydesktopeffectkcm.ui
index 5df95ad..815d884 100644
--- a/kwin/src/kcm/breezydesktopeffectkcm.ui
+++ b/kwin/src/kcm/breezydesktopeffectkcm.ui
@@ -159,11 +159,14 @@
-
+
+ 2
+
QSlider::NoTicks
- 10
+ 5
25
@@ -631,6 +634,17 @@
+ -
+
+
+ Measurement units:
+
+
+
+ -
+
+
+
diff --git a/kwin/src/kcm/labeledslider.h b/kwin/src/kcm/labeledslider.h
index d08190b..1a539bb 100644
--- a/kwin/src/kcm/labeledslider.h
+++ b/kwin/src/kcm/labeledslider.h
@@ -5,6 +5,7 @@
#include
#include
#include // for std::max
+#include
#include
#include
#include
@@ -33,6 +34,8 @@ class LabeledSlider : public QSlider {
// Example: minimum=0, tickInterval=20, tickStartOffset=10 -> labels at 10,30,50,...
Q_PROPERTY(int tickStartOffset READ tickStartOffset WRITE setTickStartOffset)
public:
+ using ValueToDisplayStringFn = std::function;
+
explicit LabeledSlider(QWidget *parent = nullptr)
: QSlider(Qt::Horizontal, parent)
{
@@ -74,6 +77,24 @@ public:
QMap valueTexts() const { return m_valueTexts; }
+ // Optional custom formatter for displayed values.
+ // If set, it is consulted for values without an explicit setValueText() override.
+ // Returning a null QString (QString()) falls back to the built-in formatting.
+ void setValueToDisplayStringFn(ValueToDisplayStringFn fn) {
+ m_valueToDisplayStringFn = std::move(fn);
+ updateGeometry();
+ update();
+ }
+
+ void clearValueToDisplayStringFn() {
+ if (!m_valueToDisplayStringFn) return;
+ m_valueToDisplayStringFn = nullptr;
+ updateGeometry();
+ update();
+ }
+
+ bool hasValueToDisplayStringFn() const { return static_cast(m_valueToDisplayStringFn); }
+
int decimalShift() const { return m_decimalShift; }
void setDecimalShift(int shift) {
// clamp to sensible range
@@ -193,6 +214,14 @@ private:
if (it != m_valueTexts.constEnd()) {
return *it;
}
+
+ if (m_valueToDisplayStringFn) {
+ QString formatted = m_valueToDisplayStringFn(raw);
+ if (!formatted.isNull()) {
+ return formatted;
+ }
+ }
+
if (m_decimalShift == 0) {
return QString::number(raw);
}
@@ -210,6 +239,7 @@ private:
int m_decimalShift = 0; // display-only decimal shift
int m_tickStartOffset = 0; // label positions start offset relative to minimum
QMap m_valueTexts; // optional text overrides for specific values
+ ValueToDisplayStringFn m_valueToDisplayStringFn; // optional custom formatter
private:
int labelInterval() const {
int ti = tickInterval();