From 3fc4dcefd9d1edaa3d430145c1eae0edbb687458 Mon Sep 17 00:00:00 2001 From: wheaney <42350981+wheaney@users.noreply.github.com> Date: Wed, 21 Jan 2026 10:59:14 -0800 Subject: [PATCH] Add dead-zone controls to UI, v2.6.4 --- VERSION | 2 +- gnome/src/virtualdisplayeffect.js | 15 +++++----- kwin/src/kcm/breezydesktopeffectkcm.cpp | 36 ++++++++++++++++++++++++ kwin/src/kcm/breezydesktopeffectkcm.h | 2 ++ kwin/src/kcm/breezydesktopeffectkcm.ui | 37 ++++++++++++++++++++++++- modules/XRLinuxDriver | 2 +- ui/modules/PyXRLinuxDriverIPC | 2 +- ui/src/configmanager.py | 22 +++++++++++++++ ui/src/connecteddevice.py | 3 ++ ui/src/gtk/connected-device.ui | 32 +++++++++++++++++++++ 10 files changed, 141 insertions(+), 12 deletions(-) diff --git a/VERSION b/VERSION index ec1cf33..2714f53 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.6.3 +2.6.4 diff --git a/gnome/src/virtualdisplayeffect.js b/gnome/src/virtualdisplayeffect.js index dd53fcb..be37783 100644 --- a/gnome/src/virtualdisplayeffect.js +++ b/gnome/src/virtualdisplayeffect.js @@ -594,18 +594,17 @@ export const VirtualDisplayEffect = GObject.registerClass({ this.set_uniform_float(this.get_uniform_location('u_look_ahead_ms'), 1, [0.0]); lookAheadSet = true; } - let posePositionPixels = [0.0, 0.0, 0.0]; - if (this.pose_has_position) { - posePositionPixels = this.imu_snapshots.pose_position.map((coord, index) => { - return coord * this.fov_details.fullScreenDistancePixels + this.lens_vector[index]; - }); - } this.set_uniform_matrix(this.get_uniform_location("u_pose_orientation"), false, 4, this.imu_snapshots.pose_orientation); - this.set_uniform_float(this.get_uniform_location("u_pose_position"), 3, posePositionPixels); } else { this.set_uniform_matrix(this.get_uniform_location("u_pose_orientation"), false, 4, this.imu_snapshots.smooth_follow_origin); - this.set_uniform_float(this.get_uniform_location("u_pose_position"), 3, [0.0, 0.0, 0.0]); } + let posePositionPixels = [0.0, 0.0, 0.0]; + if (this.pose_has_position) { + posePositionPixels = this.imu_snapshots.pose_position.map((coord, index) => { + return coord * this.fov_details.fullScreenDistancePixels + this.lens_vector[index]; + }); + } + this.set_uniform_float(this.get_uniform_location("u_pose_position"), 3, posePositionPixels); if (!lookAheadSet) { this.set_uniform_float(this.get_uniform_location('u_look_ahead_ms'), 1, [lookAheadMS(this.imu_snapshots.timestamp_ms, Globals.data_stream.device_data.lookAheadCfg, this.look_ahead_override)]); } diff --git a/kwin/src/kcm/breezydesktopeffectkcm.cpp b/kwin/src/kcm/breezydesktopeffectkcm.cpp index 39ea26c..eb8db1a 100644 --- a/kwin/src/kcm/breezydesktopeffectkcm.cpp +++ b/kwin/src/kcm/breezydesktopeffectkcm.cpp @@ -372,6 +372,12 @@ BreezyDesktopEffectConfig::BreezyDesktopEffectConfig(QObject *parent, const KPlu connect(ui.SmoothFollowTrackRoll, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::updateSmoothFollowTrackRoll); connect(ui.NeckSaverHorizontalMultiplier, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::updateNeckSaverHorizontal); connect(ui.NeckSaverVerticalMultiplier, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::updateNeckSaverVertical); + connect(ui.DeadZoneThresholdDeg, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::updateDeadZoneThresholdDeg); + + if (ui.DeadZoneThresholdDeg) { + ui.DeadZoneThresholdDeg->setValueUnitsSuffix(QStringLiteral("°")); + ui.DeadZoneThresholdDeg->setValueText(0, i18n("Disabled")); + } if (auto label = widget()->findChild("labelAppNameVersion")) { label->setText(QStringLiteral("Breezy Desktop - v%1").arg(QLatin1String(BREEZY_DESKTOP_VERSION_STR))); @@ -763,6 +769,12 @@ void BreezyDesktopEffectConfig::pollDriverState() ui.NeckSaverVerticalMultiplier->setValue(vertInt); } + const double dz = deadZoneThresholdDeg(configJsonOpt); + const int dzInt = static_cast(std::round(dz * 10.0)); + if (ui.DeadZoneThresholdDeg->value() != dzInt) { + ui.DeadZoneThresholdDeg->setValue(dzInt); + } + refreshLicenseUi(stateJson); m_driverStateInitialized = true; @@ -836,6 +848,16 @@ double BreezyDesktopEffectConfig::neckSaverVerticalMultiplier(std::optional configJsonOpt) +{ + if (!configJsonOpt) return 0.0; + const QJsonValue jv = configJsonOpt->value(QStringLiteral("dead_zone_threshold_deg")); + const double v = jv.isDouble() ? jv.toDouble() : 0.0; + if (v < 0.0) return 0.0; + if (v > 5.0) return 5.0; + return v; +} + void BreezyDesktopEffectConfig::updateNeckSaverHorizontal() { auto configJsonOpt = XRDriverIPC::instance().retrieveConfig(); @@ -858,6 +880,20 @@ void BreezyDesktopEffectConfig::updateNeckSaverVertical() XRDriverIPC::instance().writeConfig(newConfig); } +void BreezyDesktopEffectConfig::updateDeadZoneThresholdDeg() +{ + auto configJsonOpt = XRDriverIPC::instance().retrieveConfig(); + double val = ui.DeadZoneThresholdDeg->value() / 10.0; + val = std::clamp(val, 0.0, 5.0); + + const double current = deadZoneThresholdDeg(configJsonOpt); + if (std::abs(current - val) < 1e-9) return; + + QJsonObject newConfig = configJsonOpt ? configJsonOpt.value() : QJsonObject(); + newConfig.insert(QStringLiteral("dead_zone_threshold_deg"), val); + XRDriverIPC::instance().writeConfig(newConfig); +} + bool BreezyDesktopEffectConfig::multitapEnabled(std::optional configJsonOpt) { if (!configJsonOpt) return false; diff --git a/kwin/src/kcm/breezydesktopeffectkcm.h b/kwin/src/kcm/breezydesktopeffectkcm.h index 9e1cb90..69ee642 100644 --- a/kwin/src/kcm/breezydesktopeffectkcm.h +++ b/kwin/src/kcm/breezydesktopeffectkcm.h @@ -39,6 +39,7 @@ private: void updateSmoothFollowTrackRoll(); void updateNeckSaverHorizontal(); void updateNeckSaverVertical(); + void updateDeadZoneThresholdDeg(); void updateUiFromConfig(); void updateUiFromDefaultConfig(); void updateConfigFromUi(); @@ -51,6 +52,7 @@ private: bool smoothFollowTrackRollEnabled(std::optional configJsonOpt); double neckSaverHorizontalMultiplier(std::optional configJsonOpt); double neckSaverVerticalMultiplier(std::optional configJsonOpt); + double deadZoneThresholdDeg(std::optional configJsonOpt); void pollDriverState(); void refreshLicenseUi(const QJsonObject &rootObj); void checkEffectLoaded(); diff --git a/kwin/src/kcm/breezydesktopeffectkcm.ui b/kwin/src/kcm/breezydesktopeffectkcm.ui index 451c275..875ab1e 100644 --- a/kwin/src/kcm/breezydesktopeffectkcm.ui +++ b/kwin/src/kcm/breezydesktopeffectkcm.ui @@ -654,13 +654,48 @@ + + + Dead-zone threshold (deg): + + + + + + + 1 + + + 0 + + + 50 + + + 0 + + + 10 + + + QSlider::NoTicks + + + Qt::Horizontal + + + false + + + + Measurement units: - + diff --git a/modules/XRLinuxDriver b/modules/XRLinuxDriver index b74587d..4267946 160000 --- a/modules/XRLinuxDriver +++ b/modules/XRLinuxDriver @@ -1 +1 @@ -Subproject commit b74587dfc17e85c63ee03fdbdd8f2541400f454f +Subproject commit 4267946ef0d0d24a7563455c70b8cee6bcd1f2da diff --git a/ui/modules/PyXRLinuxDriverIPC b/ui/modules/PyXRLinuxDriverIPC index 44657a5..6f18448 160000 --- a/ui/modules/PyXRLinuxDriverIPC +++ b/ui/modules/PyXRLinuxDriverIPC @@ -1 +1 @@ -Subproject commit 44657a5de6532ac0ec1a4ce6d3ccd73e2c87fdc6 +Subproject commit 6f1844829e11fcc7664398d4c95d8fbdb6341669 diff --git a/ui/src/configmanager.py b/ui/src/configmanager.py index 39fc234..56e74e2 100644 --- a/ui/src/configmanager.py +++ b/ui/src/configmanager.py @@ -10,6 +10,13 @@ class ConfigManager(GObject.GObject): 'follow-track-roll': (bool, 'Follow Track Roll', 'Whether to follow on the roll axis', False, GObject.ParamFlags.READWRITE), 'follow-track-pitch': (bool, 'Follow Track Pitch', 'Whether to follow on the pitch axis', True, GObject.ParamFlags.READWRITE), 'follow-track-yaw': (bool, 'Follow Track Yaw', 'Whether to follow on the yaw axis', True, GObject.ParamFlags.READWRITE), + 'dead-zone-threshold-deg': ( + float, + 'Dead Zone Threshold (deg)', + 'IMU dead-zone threshold in degrees (0.0 disables)', + 0.0, 5.0, 0.0, + GObject.ParamFlags.READWRITE, + ), 'neck-saver-horizontal-multiplier': ( float, 'Neck Saver Horizontal Multiplier', @@ -49,6 +56,7 @@ class ConfigManager(GObject.GObject): self.follow_track_roll = None self.follow_track_pitch = None self.follow_track_yaw = None + self.dead_zone_threshold_deg = None self.neck_saver_horizontal_multiplier = None self.neck_saver_vertical_multiplier = None self._running = True @@ -74,6 +82,9 @@ class ConfigManager(GObject.GObject): if self.config['smooth_follow_track_yaw'] != self.follow_track_yaw: self.set_property('follow-track-yaw', self.config['smooth_follow_track_yaw']) + if self.config['dead_zone_threshold_deg'] != self.dead_zone_threshold_deg: + self.set_property('dead-zone-threshold-deg', self.config['dead_zone_threshold_deg']) + if self.config['neck_saver_horizontal_multiplier'] != self.neck_saver_horizontal_multiplier: self.set_property('neck-saver-horizontal-multiplier', self.config['neck_saver_horizontal_multiplier']) @@ -120,6 +131,13 @@ class ConfigManager(GObject.GObject): self.ipc.write_config(self.config) self.follow_track_yaw = value + def _set_dead_zone_threshold_deg(self, value): + value = round(min(5.0, max(0.0, float(value))), 2) + if self.dead_zone_threshold_deg != value: + self.config['dead_zone_threshold_deg'] = value + self.ipc.write_config(self.config) + self.dead_zone_threshold_deg = value + def _set_neck_saver_horizontal_multiplier(self, value): value = round(min(2.5, max(1.0, float(value))), 2) if self.neck_saver_horizontal_multiplier != value: @@ -145,6 +163,8 @@ class ConfigManager(GObject.GObject): self._set_follow_track_pitch(value) elif prop.name == 'follow-track-yaw': self._set_follow_track_yaw(value) + elif prop.name == 'dead-zone-threshold-deg': + self._set_dead_zone_threshold_deg(value) elif prop.name == 'neck-saver-horizontal-multiplier': self._set_neck_saver_horizontal_multiplier(value) elif prop.name == 'neck-saver-vertical-multiplier': @@ -161,6 +181,8 @@ class ConfigManager(GObject.GObject): return self.follow_track_pitch elif prop.name == 'follow-track-yaw': return self.follow_track_yaw + elif prop.name == 'dead-zone-threshold-deg': + return self.dead_zone_threshold_deg elif prop.name == 'neck-saver-horizontal-multiplier': return self.neck_saver_horizontal_multiplier elif prop.name == 'neck-saver-vertical-multiplier': diff --git a/ui/src/connecteddevice.py b/ui/src/connecteddevice.py index 1d6348f..f1bf08d 100644 --- a/ui/src/connecteddevice.py +++ b/ui/src/connecteddevice.py @@ -73,6 +73,8 @@ class ConnectedDevice(Gtk.Box): neck_saver_horizontal_adjustment = Gtk.Template.Child() neck_saver_vertical_scale = Gtk.Template.Child() neck_saver_vertical_adjustment = Gtk.Template.Child() + dead_zone_threshold_scale = Gtk.Template.Child() + dead_zone_threshold_adjustment = Gtk.Template.Child() enable_multi_tap_switch = Gtk.Template.Child() legacy_follow_mode_switch = Gtk.Template.Child() follow_track_yaw_switch = Gtk.Template.Child() @@ -193,6 +195,7 @@ class ConnectedDevice(Gtk.Box): self._bind_switch_to_config(self.follow_track_roll_switch, 'follow-track-roll') self._bind_switch_to_config(self.follow_track_pitch_switch, 'follow-track-pitch') self._bind_switch_to_config(self.follow_track_yaw_switch, 'follow-track-yaw') + self._bind_scale_to_config(self.dead_zone_threshold_adjustment, 'dead-zone-threshold-deg') self._bind_scale_to_config(self.neck_saver_horizontal_adjustment, 'neck-saver-horizontal-multiplier') self._bind_scale_to_config(self.neck_saver_vertical_adjustment, 'neck-saver-vertical-multiplier') diff --git a/ui/src/gtk/connected-device.ui b/ui/src/gtk/connected-device.ui index 8b25582..89fd925 100644 --- a/ui/src/gtk/connected-device.ui +++ b/ui/src/gtk/connected-device.ui @@ -762,6 +762,38 @@ + + + Dead-zone threshold (degrees) + Stabilize movements below this angle. + + + 3 + true + 0 + 1 + 350 + false + + + 0.0 + 5.0 + 0.1 + 0.0 + + + + Disabled + 1.0 + 2.0 + 3.0 + 4.0 + 5.0 + + + + + Follow mode movement tracking