From 3433885cdd1cfb8ae05d227a06cb1374fba18e9d Mon Sep 17 00:00:00 2001
From: wheaney <42350981+wheaney@users.noreply.github.com>
Date: Tue, 18 Jun 2024 22:24:17 -0700
Subject: [PATCH] Add advanced settings
---
gnome/src/extension.js | 31 +++++++-
gnome/src/monitormanager.js | 49 +++++++++---
gnome/src/xrEffect.js | 12 ++-
.../com.xronlinux.BreezyDesktop.gschema.xml | 27 +++++++
ui/src/connecteddevice.py | 21 +++++-
ui/src/gtk/connected-device.ui | 74 ++++++++++++++++++-
6 files changed, 197 insertions(+), 17 deletions(-)
diff --git a/gnome/src/extension.js b/gnome/src/extension.js
index 4c27e39..54ea1b3 100644
--- a/gnome/src/extension.js
+++ b/gnome/src/extension.js
@@ -8,7 +8,7 @@ import St from 'gi://St';
import { CursorManager } from './cursormanager.js';
import Globals from './globals.js';
import { Logger } from './logger.js';
-import MonitorManager from './monitormanager.js';
+import { MonitorManager } from './monitormanager.js';
import { isValidKeepAlive } from './time.js';
import { IPC_FILE_PATH, XREffect } from './xrEffect.js';
@@ -48,6 +48,9 @@ export default class BreezyDesktopExtension extends Extension {
this._end_binding = null;
this._curved_display_binding = null;
this._display_size_binding = null;
+ this._look_ahead_override_binding = null;
+ this._optimal_monitor_config_binding = null;
+ this._headset_as_primary_binding = null;
if (!Globals.logger) {
Globals.logger = new Logger({
@@ -65,10 +68,19 @@ export default class BreezyDesktopExtension extends Extension {
Globals.extension_dir = this.path;
this.settings.bind('debug', Globals.logger, 'debug', Gio.SettingsBindFlags.DEFAULT);
- this._monitor_manager = new MonitorManager(this.path);
+ this._monitor_manager = new MonitorManager({
+ use_optimal_monitor_config: this.settings.get_boolean('use-optimal-monitor-config'),
+ headset_as_primary: this.settings.get_boolean('headset-as-primary'),
+ extension_path: this.path
+ });
this._monitor_manager.setChangeHook(this._handle_monitor_change.bind(this));
this._monitor_manager.enable();
+ this._optimal_monitor_config_binding = this.settings.bind('use-optimal-monitor-config',
+ this._monitor_manager, 'use-optimal-monitor-config', Gio.SettingsBindFlags.DEFAULT);
+ this._headset_as_primary_binding = this.settings.bind('headset-as-primary',
+ this._monitor_manager, 'headset-as-primary', Gio.SettingsBindFlags.DEFAULT);
+
this._setup();
} catch (e) {
Globals.logger.log(`ERROR: BreezyDesktopExtension enable ${e.message}\n${e.stack}`);
@@ -207,6 +219,7 @@ export default class BreezyDesktopExtension extends Extension {
display_distance: this.settings.get_double('display-distance'),
toggle_display_distance_start: this.settings.get_double('toggle-display-distance-start'),
toggle_display_distance_end: this.settings.get_double('toggle-display-distance-end'),
+ look_ahead_override: this.settings.get_double('look-ahead-override'),
});
this._update_follow_threshold(this.settings);
@@ -223,6 +236,7 @@ export default class BreezyDesktopExtension extends Extension {
this._end_binding = this.settings.bind('toggle-display-distance-end', this._xr_effect, 'toggle-display-distance-end', Gio.SettingsBindFlags.DEFAULT)
this._curved_display_binding = this.settings.bind('curved-display', this._xr_effect, 'curved-display', Gio.SettingsBindFlags.DEFAULT)
this._display_size_binding = this.settings.bind('display-size', this._xr_effect, 'display-size', Gio.SettingsBindFlags.DEFAULT);
+ this._look_ahead_override_binding = this.settings.bind('look-ahead-override', this._xr_effect, 'look-ahead-override', Gio.SettingsBindFlags.DEFAULT);
this._overlay.add_effect_with_name('xr-desktop', this._xr_effect);
Meta.disable_unredirect_for_display(global.display);
@@ -384,6 +398,10 @@ export default class BreezyDesktopExtension extends Extension {
this.settings.unbind(this._display_size_binding);
this._display_size_binding = null;
}
+ if (this._look_ahead_override_binding) {
+ this.settings.unbind(this._look_ahead_override_binding);
+ this._look_ahead_override_binding = null;
+ }
if (this._xr_effect) {
if (this._widescreen_mode_effect_state_connection) {
this._xr_effect.disconnect(this._widescreen_mode_effect_state_connection);
@@ -412,6 +430,15 @@ export default class BreezyDesktopExtension extends Extension {
this._effect_disable();
this._target_monitor = null;
if (this._monitor_manager) {
+ if (this._optimal_monitor_config_binding) {
+ this.settings.unbind(this._optimal_monitor_config_binding);
+ this._optimal_monitor_config_binding = null
+ }
+ if (this._headset_as_primary_binding) {
+ this.settings.unbind(this._headset_as_primary_binding);
+ this._headset_as_primary_binding = null;
+ }
+
this._monitor_manager.disable();
this._monitor_manager = null;
}
diff --git a/gnome/src/monitormanager.js b/gnome/src/monitormanager.js
index a12bb96..3d52d8e 100644
--- a/gnome/src/monitormanager.js
+++ b/gnome/src/monitormanager.js
@@ -17,6 +17,7 @@
// along with this program. If not, see .
import Gio from 'gi://Gio';
+import GObject from 'gi://GObject';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
@@ -85,7 +86,7 @@ function getMonitorConfig(displayConfigProxy, callback) {
}
// triggers callback with true result if an an async monitor config change was triggered, false if no config change needed
-function performOptimalModeCheck(displayConfigProxy, connectorName, callback) {
+function performOptimalModeCheck(displayConfigProxy, connectorName, headsetAsPrimary, callback) {
Globals.logger.log_debug(`monitormanager.js performOptimalModeCheck for ${connectorName}`);
displayConfigProxy.GetCurrentStateRemote((result, error) => {
if (error) {
@@ -139,12 +140,16 @@ function performOptimalModeCheck(displayConfigProxy, connectorName, callback) {
const [x, y, scale, transform, primary, monitors, logMonProperties] = logicalMonitor;
const hasOurMonitor = !!monitors.some((monitor) => monitor[0] === connectorName);
anyMonitorsChanged |= hasOurMonitor && bestFitMode.bestScale !== scale;
+
+ // there can only be one primary monitor, so we need to set all other monitors to not primary and glasses to primary,
+ // if headsetAsPrimary is true
+ anyMonitorsChanged |= headsetAsPrimary && ((hasOurMonitor && !primary) || (!hasOurMonitor && primary));
return [
x,
y,
hasOurMonitor ? bestFitMode.bestScale : scale,
transform,
- primary, // TODO - user setting should dictate if we want to set ours primary
+ headsetAsPrimary ? hasOurMonitor : primary,
monitors.map((monitor) => {
const monitorConnector = monitor[0];
const isOurMonitor = monitorConnector === connectorName;
@@ -185,22 +190,46 @@ function performOptimalModeCheck(displayConfigProxy, connectorName, callback) {
}
// Monitor change handling
-export default class MonitorManager {
- constructor(extPath) {
- this._extPath = extPath;
+export const MonitorManager = GObject.registerClass({
+ Properties: {
+ 'use-optimal-monitor-config': GObject.ParamSpec.boolean(
+ 'use-optimal-monitor-config',
+ 'Use optimal monitor configuration',
+ 'Automatically set the optimal monitor configuration upon connection',
+ GObject.ParamFlags.READWRITE,
+ true
+ ),
+ 'headset-as-primary': GObject.ParamSpec.boolean(
+ 'headset-as-primary',
+ 'Use headset as primary monitor',
+ 'Automatically set the headset as the primary display upon connection',
+ GObject.ParamFlags.READWRITE,
+ true
+ ),
+ 'extension-path': GObject.ParamSpec.string(
+ 'extension-path',
+ 'Extension path',
+ 'Path to the extension directory',
+ GObject.ParamFlags.READWRITE,
+ ''
+ )
+ }
+}, class MonitorManager extends GObject.Object {
+ constructor(params = {}) {
+ super(params);
this._monitorsChangedConnection = null;
this._displayConfigProxy = null;
this._backendManager = null;
this._monitorProperties = null;
this._changeHookFn = null;
- this._needsConfigCheck = true;
+ this._needsConfigCheck = this.use_optimal_monitor_config;
}
enable() {
Globals.logger.log_debug('MonitorManager enable');
this._backendManager = global.backend.get_monitor_manager();
- newDisplayConfig(this._extPath, ((proxy, error) => {
+ newDisplayConfig(this.extension_path, ((proxy, error) => {
if (error) {
return;
}
@@ -243,7 +272,7 @@ export default class MonitorManager {
}
if (this._needsConfigCheck) {
- performOptimalModeCheck(this._displayConfigProxy, monitorConnector, ((configChanged, error) => {
+ performOptimalModeCheck(this._displayConfigProxy, monitorConnector, this.headset_as_primary, ((configChanged, error) => {
this._needsConfigCheck = false;
if (error) {
Globals.logger.log(`Failed to switch to optimal mode for monitor ${monitorConnector}: ${error}`);
@@ -269,7 +298,7 @@ export default class MonitorManager {
if (this._displayConfigProxy == null) {
return;
}
- this._needsConfigCheck = true;
+ this._needsConfigCheck = this.use_optimal_monitor_config;
getMonitorConfig(this._displayConfigProxy, ((result, error) => {
if (error) {
return;
@@ -297,4 +326,4 @@ export default class MonitorManager {
}
}).bind(this));
}
-}
\ No newline at end of file
+});
\ No newline at end of file
diff --git a/gnome/src/xrEffect.js b/gnome/src/xrEffect.js
index e3764a4..46e9a84 100644
--- a/gnome/src/xrEffect.js
+++ b/gnome/src/xrEffect.js
@@ -258,6 +258,15 @@ export const XREffect = GObject.registerClass({
'The state of widescreen mode from the perspective of the driver',
GObject.ParamFlags.READWRITE,
false
+ ),
+ 'look-ahead-override': GObject.ParamSpec.int(
+ 'look-ahead-override',
+ 'Look ahead override',
+ 'Override the look ahead value',
+ GObject.ParamFlags.READWRITE,
+ -1,
+ 45,
+ -1
)
}
}, class XREffect extends Shell.GLSLEffect {
@@ -342,7 +351,8 @@ export const XREffect = GObject.registerClass({
if (this._dataView.byteLength === DATA_VIEW_LENGTH) {
if (checkParityByte(this._dataView)) {
setSingleFloat(this, 'display_north_offset', this.display_distance);
- setSingleFloat(this, 'look_ahead_ms', lookAheadMS(this._dataView));
+ setSingleFloat(this, 'look_ahead_ms',
+ this.look_ahead_override === -1 ? lookAheadMS(this._dataView) : this.look_ahead_override);
setUniformMatrix(this, 'imu_quat_data', 4, this._dataView, IMU_QUAT_DATA);
setSingleFloat(this, 'display_size', this.display_size);
success = true;
diff --git a/ui/data/com.xronlinux.BreezyDesktop.gschema.xml b/ui/data/com.xronlinux.BreezyDesktop.gschema.xml
index 738ca6f..eb23a32 100644
--- a/ui/data/com.xronlinux.BreezyDesktop.gschema.xml
+++ b/ui/data/com.xronlinux.BreezyDesktop.gschema.xml
@@ -91,6 +91,33 @@
Enable curved display mode
+
+
+ -1
+
+ Look-ahead override
+
+ Manually override the look-ahead calculation
+
+
+
+
+ true
+
+ Use optimal monitor configuration
+
+ Automatically set the optimal monitor configuration upon connection
+
+
+
+
+ true
+
+ Headset as primary
+
+ Automatically set the headset as the primary display upon connection
+
+
false
diff --git a/ui/src/connecteddevice.py b/ui/src/connecteddevice.py
index a47b160..241c4d5 100644
--- a/ui/src/connecteddevice.py
+++ b/ui/src/connecteddevice.py
@@ -29,6 +29,11 @@ class ConnectedDevice(Gtk.Box):
toggle_display_distance_shortcut_label = Gtk.Template.Child()
reassign_toggle_follow_shortcut_button = Gtk.Template.Child()
toggle_follow_shortcut_label = Gtk.Template.Child()
+ headset_as_primary_switch = Gtk.Template.Child()
+ use_optimal_monitor_config_switch = Gtk.Template.Child()
+ movement_look_ahead_scale = Gtk.Template.Child()
+ movement_look_ahead_adjustment = Gtk.Template.Child()
+
def __init__(self):
super(Gtk.Box, self).__init__()
@@ -44,7 +49,10 @@ class ConnectedDevice(Gtk.Box):
self.set_toggle_display_distance_end_button,
self.reassign_recenter_display_shortcut_button,
self.reassign_toggle_display_distance_shortcut_button,
- self.reassign_toggle_follow_shortcut_button
+ self.reassign_toggle_follow_shortcut_button,
+ self.headset_as_primary_switch,
+ self.use_optimal_monitor_config_switch,
+ self.movement_look_ahead_scale
]
self.settings = SettingsManager.get_instance().settings
@@ -56,6 +64,9 @@ class ConnectedDevice(Gtk.Box):
self.settings.bind('follow-threshold', self.follow_threshold_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT)
self.settings.bind('widescreen-mode', self.widescreen_mode_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
self.settings.bind('curved-display', self.curved_display_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
+ self.settings.bind('headset-as-primary', self.headset_as_primary_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
+ self.settings.bind('use-optimal-monitor-config', self.use_optimal_monitor_config_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
+ self.settings.bind('look-ahead-override', self.movement_look_ahead_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT)
bind_shortcut_settings(self.get_parent(), [
[self.reassign_recenter_display_shortcut_button, self.recenter_display_shortcut_label],
@@ -78,8 +89,11 @@ class ConnectedDevice(Gtk.Box):
self.effect_enable_switch.set_active(self._is_config_enabled(self.ipc.retrieve_config()) and self.extensions_manager.is_enabled())
self.effect_enable_switch.connect('notify::active', self._refresh_inputs_for_enabled_state)
+ self.use_optimal_monitor_config_switch.connect('notify::active', self._refresh_use_optimal_monitor_config)
+
self._handle_enabled_features(self.state_manager, None)
self._refresh_inputs_for_enabled_state(self.effect_enable_switch, None)
+ self._refresh_use_optimal_monitor_config(self.use_optimal_monitor_config_switch, None)
self.extensions_manager.bind_property('breezy-enabled', self.effect_enable_switch, 'active', GObject.BindingFlags.BIDIRECTIONAL)
self.connect("destroy", self._on_widget_destroy)
@@ -120,6 +134,11 @@ class ConnectedDevice(Gtk.Box):
'enable_breezy_desktop_smooth_follow': switch.get_active()
})
+ def _refresh_use_optimal_monitor_config(self, switch, param):
+ self.headset_as_primary_switch.set_sensitive(switch.get_active())
+ if not switch.get_active():
+ self.headset_as_primary_switch.set_active(False)
+
def set_device_name(self, name):
self.device_label.set_markup(f"{name}")
diff --git a/ui/src/gtk/connected-device.ui b/ui/src/gtk/connected-device.ui
index c06ffb2..5502bd6 100644
--- a/ui/src/gtk/connected-device.ui
+++ b/ui/src/gtk/connected-device.ui
@@ -209,14 +209,13 @@
+
+
+ advanced
+ Advanced Settings
+ applications-system-symbolic
+
+
+
+
+ Advanced Settings
+
+
+ Find optimal display config
+ Automatically modify the glasses display configuration for maximum resolution and best scaling when plugged in.
+
+
+ 3
+
+
+
+
+
+
+ Always primary display
+ Automatically set the glasses as the primary display when plugged in.
+
+
+ 3
+
+
+
+
+
+
+ Movement look-ahead
+ Counteracts input lag by predicting head-tracking position ahead of render time. Stick with default unless virtual display drags behind your head movements, jumps ahead, or is very shaky.
+
+
+ 3
+ false
+ 0
+ 0
+ 350
+ false
+
+
+ -1
+ 40
+ 1
+ -1
+
+
+
+ Default
+ 10ms
+ 20ms
+ 30ms
+ 40ms
+
+
+
+
+
+
+
+
+
+
+