From 04edf2eecc9439b0529998688a3d51d90980c859 Mon Sep 17 00:00:00 2001 From: wheaney <42350981+wheaney@users.noreply.github.com> Date: Wed, 17 Jul 2024 21:55:49 -0700 Subject: [PATCH] Add refresh rate and fast SBS mode switching settings --- gnome/src/cursormanager.js | 1 - gnome/src/extension.js | 48 +++++++++++++++---- gnome/src/monitormanager.js | 26 ++++++++-- ui/bin/package | 4 +- .../com.xronlinux.BreezyDesktop.gschema.xml | 18 +++++++ ...om.xronlinux.BreezyDesktop.metainfo.xml.in | 8 +++- ui/src/connecteddevice.py | 8 ++++ ui/src/gtk/connected-device.ui | 22 +++++++++ 8 files changed, 118 insertions(+), 17 deletions(-) diff --git a/gnome/src/cursormanager.js b/gnome/src/cursormanager.js index c2f5e82..44b3f5f 100644 --- a/gnome/src/cursormanager.js +++ b/gnome/src/cursormanager.js @@ -1,5 +1,4 @@ import Clutter from 'gi://Clutter'; -import GLib from 'gi://GLib'; import Meta from 'gi://Meta'; import * as PointerWatcher from 'resource:///org/gnome/shell/ui/pointerWatcher.js'; import { MouseSpriteContent } from './cursor.js'; diff --git a/gnome/src/extension.js b/gnome/src/extension.js index 16958a5..a7edafd 100644 --- a/gnome/src/extension.js +++ b/gnome/src/extension.js @@ -71,6 +71,7 @@ export default class BreezyDesktopExtension extends Extension { 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'), + use_highest_refresh_rate: this.settings.get_boolean('use-highest-refresh-rate'), extension_path: this.path }); this._monitor_manager.setChangeHook(this._handle_monitor_change.bind(this)); @@ -120,7 +121,7 @@ export default class BreezyDesktopExtension extends Extension { const target_monitor = this._monitor_manager.getMonitorPropertiesList()?.find( monitor => SUPPORTED_MONITOR_PRODUCTS.includes(monitor.product)); if (target_monitor !== undefined) { - Globals.logger.log_debug(`BreezyDesktopExtension _find_supported_monitor - Identified supported monitor: ${target_monitor.connector}`); + Globals.logger.log(`Identified supported monitor: ${target_monitor.product} on ${target_monitor.connector}`); return { monitor: this._monitor_manager.getMonitors()[target_monitor.index], connector: target_monitor.connector, @@ -151,8 +152,11 @@ export default class BreezyDesktopExtension extends Extension { // A false result means we'll expect _handle_monitor_change to be triggered, so active polling // can be disabled. _target_monitor_ready(target_monitor) { - return target_monitor.is_dummy || - !this._monitor_manager.needsOptimalModeCheck(target_monitor.connector); + if (target_monitor.is_dummy) return true; + + const needs_sbs_mode_switch = this.settings.get_boolean('fast-sbs-mode-switching') && + this._needs_widescreen_monitor_update(); + return !needs_sbs_mode_switch && !this._monitor_manager.needsOptimalModeCheck(target_monitor.connector); } _setup() { @@ -213,7 +217,7 @@ export default class BreezyDesktopExtension extends Extension { const widescreen_setting_enabled = this.settings.get_boolean('widescreen-mode'); if (widescreen_setting_enabled !== sbs_enabled) { Globals.logger.log_debug('BreezyDesktopExtension _needs_widescreen_monitor_update - true'); - this._write_control('sbs_mode', widescreen_setting_enabled ? 'enable' : 'disable'); + this._request_sbs_mode_change(widescreen_setting_enabled); return true; } @@ -259,7 +263,10 @@ export default class BreezyDesktopExtension extends Extension { }); this._update_follow_threshold(this.settings); - this._update_widescreen_mode_from_settings(this.settings); + + // this gets triggered before _effect_enable if in fast-sbs-mode-switching mode + if (!this.settings.get_boolean('fast-sbs-mode-switching')) + this._update_widescreen_mode_from_settings(this.settings); this._widescreen_mode_effect_state_connection = this._xr_effect.connect('notify::widescreen-mode-state', this._update_widescreen_mode_from_state.bind(this)); this._supported_device_detected_connected = this._xr_effect.connect('notify::supported-device-detected', this._handle_supported_device_change.bind(this)); @@ -362,16 +369,41 @@ export default class BreezyDesktopExtension extends Extension { if (value !== undefined) this._write_control('breezy_desktop_follow_threshold', value); } + // requests sbs_mode change and monitors to ensure the state reflects the setting + _request_sbs_mode_change(value) { + this._write_control('sbs_mode', value ? 'enable' : 'disable'); + if (!this._sbs_mode_update_timeout) { + var attempts = 0; + this._sbs_mode_update_timeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 10000, (() => { + if (attempts++ < 3) { + this._write_control('sbs_mode', value ? 'enable' : 'disable'); + return GLib.SOURCE_CONTINUE; + } + + // the state never updated to reflect our request, revert the setting + this.settings.set_boolean('widescreen-mode', !value); + this._sbs_mode_update_timeout = undefined; + return GLib.SOURCE_REMOVE; + }).bind(this)); + } + } + _update_widescreen_mode_from_settings(settings, event) { const value = settings.get_boolean('widescreen-mode'); Globals.logger.log_debug(`BreezyDesktopExtension _update_widescreen_mode_from_settings ${value}`); - if (value !== undefined && value !== this._xr_effect.widescreen_mode_state) - this._write_control('sbs_mode', value ? 'enable' : 'disable'); - else + if (value !== undefined && value !== this._xr_effect.widescreen_mode_state) { + this._request_sbs_mode_change(value); + } else Globals.logger.log_debug('effect.widescreen_mode_state already matched setting'); } _update_widescreen_mode_from_state(effect, _pspec) { + // kill our state checker if it's running + if (this._sbs_mode_update_timeout) { + GLib.source_remove(this._sbs_mode_update_timeout); + this._sbs_mode_update_timeout = undefined; + } + const value = effect.widescreen_mode_state; Globals.logger.log_debug(`BreezyDesktopExtension _update_widescreen_mode_from_state ${value}`); if (value !== this.settings.get_boolean('widescreen-mode')) diff --git a/gnome/src/monitormanager.js b/gnome/src/monitormanager.js index 8126ecf..1d4b98c 100644 --- a/gnome/src/monitormanager.js +++ b/gnome/src/monitormanager.js @@ -86,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, headsetAsPrimary, callback) { +function performOptimalModeCheck(displayConfigProxy, connectorName, headsetAsPrimary, useHighestRefreshRate, callback) { Globals.logger.log_debug(`monitormanager.js performOptimalModeCheck for ${connectorName}`); displayConfigProxy.GetCurrentStateRemote((result, error) => { if (error) { @@ -101,10 +101,19 @@ function performOptimalModeCheck(displayConfigProxy, connectorName, headsetAsPri let monitorToModeIdMap = {}; let bestFitMode = undefined; for (let monitor of monitors) { - const [details, modes, monProperties] = monitor; + const [details, availableModes, monProperties] = monitor; const [connector, vendor, product, monitorSerial] = details; const isOurMonitor = connector == connectorName; - if (isOurMonitor) ourMonitor = monitor; + let modes = availableModes; + if (isOurMonitor) { + ourMonitor = monitor; + if (!useHighestRefreshRate) { + const currentMode = modes.find((mode) => !!mode[6]['is-current']); + + // filter modes to only include the current refresh rate + modes = availableModes.filter((mode) => mode[3] === currentMode[3]); + } + } for (let mode of modes) { const [modeId, width, height, refreshRate, preferredScale, supportedScales, modeProperites] = mode; @@ -199,6 +208,13 @@ export const MonitorManager = GObject.registerClass({ GObject.ParamFlags.READWRITE, true ), + 'use-highest-refresh-rate': GObject.ParamSpec.boolean( + 'use-highest-refresh-rate', + 'Use highest refresh rate', + 'Set the highest refresh rate which choosing optimal configs', + GObject.ParamFlags.READWRITE, + true + ), 'headset-as-primary': GObject.ParamSpec.boolean( 'headset-as-primary', 'Use headset as primary monitor', @@ -272,7 +288,7 @@ export const MonitorManager = GObject.registerClass({ } if (this._needsConfigCheck) { - performOptimalModeCheck(this._displayConfigProxy, monitorConnector, this.headset_as_primary, ((configChanged, error) => { + performOptimalModeCheck(this._displayConfigProxy, monitorConnector, this.headset_as_primary, this.use_highest_refresh_rate, ((configChanged, error) => { this._needsConfigCheck = false; if (error) { Globals.logger.log(`Failed to switch to optimal mode for monitor ${monitorConnector}: ${error}`); @@ -309,7 +325,7 @@ export const MonitorManager = GObject.registerClass({ for (let i = 0; i < result.length; i++) { const [monitorName, connectorName, vendor, product, serial, refreshRate] = result[i]; const monitorIndex = this._backendManager.get_monitor_for_connector(connectorName); - Globals.logger.log(`Found monitor ${monitorName}, vendor ${vendor}, product ${product}, serial ${serial}, connector ${connectorName}, index ${monitorIndex}`); + Globals.logger.log_debug(`Found monitor ${monitorName}, vendor ${vendor}, product ${product}, serial ${serial}, connector ${connectorName}, index ${monitorIndex}`); if (monitorIndex >= 0) { monitorProperties[monitorIndex] = { index: monitorIndex, diff --git a/ui/bin/package b/ui/bin/package index a3c6764..7b04f59 100755 --- a/ui/bin/package +++ b/ui/bin/package @@ -16,12 +16,12 @@ check_command "flatpak-builder" # https://stackoverflow.com/a/246128 SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) -TMP_DIR=$(mktemp -d -t breezy-ui-flatpak-XXXXXXXXXX) +TMP_DIR=$(mktemp -d -t --tmpdir=$SCRIPT_DIR/.. breezy-ui-flatpak-XXXXXXXXXX) OUT_DIR=$SCRIPT_DIR/../out rm -rf $OUT_DIR mkdir -p $OUT_DIR -flatpak-builder --force-clean $TMP_DIR/build $SCRIPT_DIR/../com.xronlinux.BreezyDesktop.json +flatpak-builder --force-clean --delete-build-dirs $TMP_DIR/build $SCRIPT_DIR/../com.xronlinux.BreezyDesktop.json flatpak build-export $TMP_DIR/export $TMP_DIR/build flatpak build-bundle $TMP_DIR/export $OUT_DIR/com.xronlinux.BreezyDesktop.flatpak com.xronlinux.BreezyDesktop --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo diff --git a/ui/data/com.xronlinux.BreezyDesktop.gschema.xml b/ui/data/com.xronlinux.BreezyDesktop.gschema.xml index 7b33ad7..4372563 100644 --- a/ui/data/com.xronlinux.BreezyDesktop.gschema.xml +++ b/ui/data/com.xronlinux.BreezyDesktop.gschema.xml @@ -118,6 +118,24 @@ Automatically set the headset as the primary display upon connection + + + true + + Use highest refresh rate + + Automatically set the highest refresh rate upon connection + + + + + true + + Fast SBS mode switching + + Enable fast SBS mode switching + + false diff --git a/ui/data/com.xronlinux.BreezyDesktop.metainfo.xml.in b/ui/data/com.xronlinux.BreezyDesktop.metainfo.xml.in index eab0852..b16137d 100644 --- a/ui/data/com.xronlinux.BreezyDesktop.metainfo.xml.in +++ b/ui/data/com.xronlinux.BreezyDesktop.metainfo.xml.in @@ -3,7 +3,13 @@ com.xronlinux.BreezyDesktop.desktop CC0-1.0 GPL-3.0-or-later + Breezy Desktop + XR Desktop Control Panel -

No description

+

XR Desktop Control Panel

+ + Office + Development + diff --git a/ui/src/connecteddevice.py b/ui/src/connecteddevice.py index 241c4d5..133d2c2 100644 --- a/ui/src/connecteddevice.py +++ b/ui/src/connecteddevice.py @@ -31,6 +31,8 @@ class ConnectedDevice(Gtk.Box): toggle_follow_shortcut_label = Gtk.Template.Child() headset_as_primary_switch = Gtk.Template.Child() use_optimal_monitor_config_switch = Gtk.Template.Child() + use_highest_refresh_rate_switch = Gtk.Template.Child() + fast_sbs_mode_switch = Gtk.Template.Child() movement_look_ahead_scale = Gtk.Template.Child() movement_look_ahead_adjustment = Gtk.Template.Child() @@ -52,6 +54,8 @@ class ConnectedDevice(Gtk.Box): self.reassign_toggle_follow_shortcut_button, self.headset_as_primary_switch, self.use_optimal_monitor_config_switch, + self.use_highest_refresh_rate_switch, + self.fast_sbs_mode_switch, self.movement_look_ahead_scale ] @@ -66,6 +70,8 @@ class ConnectedDevice(Gtk.Box): 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('use-highest-refresh-rate', self.use_highest_refresh_rate_switch, 'active', Gio.SettingsBindFlags.DEFAULT) + self.settings.bind('fast-sbs-mode-switching', self.fast_sbs_mode_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(), [ @@ -136,8 +142,10 @@ class ConnectedDevice(Gtk.Box): def _refresh_use_optimal_monitor_config(self, switch, param): self.headset_as_primary_switch.set_sensitive(switch.get_active()) + self.use_highest_refresh_rate_switch.set_sensitive(switch.get_active()) if not switch.get_active(): self.headset_as_primary_switch.set_active(False) + self.use_highest_refresh_rate_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 215ca1c..b5953c3 100644 --- a/ui/src/gtk/connected-device.ui +++ b/ui/src/gtk/connected-device.ui @@ -328,6 +328,17 @@ + + + Use highest refresh rate + Refresh rate may affect performance, disable this to set it manually. + + + 3 + + + + Always primary display @@ -339,6 +350,17 @@ + + + Fast SBS mode switching + Switches glasses to SBS mode immediately when plugged in, if widescreen mode is on. May cause instability. + + + 3 + + + + Movement look-ahead