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 @@ shortcuts - Shortcuts + Keyboard Shortcuts preferences-desktop-keyboard-shortcuts-symbolic - Shortcuts - Modify keyboard shortcuts and how they work. + Keyboard Shortcuts Re-center display shortcut @@ -310,6 +309,75 @@ + + + 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 + + + + + + + + + + +