Add advanced settings

This commit is contained in:
wheaney 2024-06-18 22:24:17 -07:00
parent 7936717ac7
commit 3433885cdd
6 changed files with 197 additions and 17 deletions

View File

@ -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;
}

View File

@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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));
}
}
});

View File

@ -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;

View File

@ -91,6 +91,33 @@
Enable curved display mode
</description>
</key>
<key name="look-ahead-override" type="i">
<default>
-1
</default>
<summary>Look-ahead override</summary>
<description>
Manually override the look-ahead calculation
</description>
</key>
<key name="use-optimal-monitor-config" type="b">
<default>
true
</default>
<summary>Use optimal monitor configuration</summary>
<description>
Automatically set the optimal monitor configuration upon connection
</description>
</key>
<key name="headset-as-primary" type="b">
<default>
true
</default>
<summary>Headset as primary</summary>
<description>
Automatically set the headset as the primary display upon connection
</description>
</key>
<key name="developer-mode" type="b">
<default>
false

View File

@ -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"<b>{name}</b>")

View File

@ -209,14 +209,13 @@
<child>
<object class="AdwViewStackPage">
<property name="name">shortcuts</property>
<property name="title">Shortcuts</property>
<property name="title">Keyboard Shortcuts</property>
<property name="icon-name">preferences-desktop-keyboard-shortcuts-symbolic</property>
<property name="child">
<object class="GtkBox">
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="true">Shortcuts</property>
<property name="description" translatable="true">Modify keyboard shortcuts and how they work.</property>
<property name="title" translatable="true">Keyboard Shortcuts</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="true">Re-center display shortcut</property>
@ -310,6 +309,75 @@
</property>
</object>
</child>
<child>
<object class="AdwViewStackPage">
<property name="name">advanced</property>
<property name="title">Advanced Settings</property>
<property name="icon-name">applications-system-symbolic</property>
<property name="child">
<object class="GtkBox">
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="true">Advanced Settings</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="true">Find optimal display config</property>
<property name="subtitle" translatable="true">Automatically modify the glasses display configuration for maximum resolution and best scaling when plugged in.</property>
<child>
<object class="GtkSwitch" id="use_optimal_monitor_config_switch">
<property name="valign">3</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="true">Always primary display</property>
<property name="subtitle" translatable="true">Automatically set the glasses as the primary display when plugged in.</property>
<child>
<object class="GtkSwitch" id="headset_as_primary_switch">
<property name="valign">3</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="true">Movement look-ahead</property>
<property name="subtitle" translatable="true">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.</property>
<child>
<object class="GtkScale" id="movement_look_ahead_scale">
<property name="valign">3</property>
<property name="draw-value">false</property>
<property name="value-pos">0</property>
<property name="digits">0</property>
<property name="width-request">350</property>
<property name="has-origin">false</property>
<property name="adjustment">
<object class="GtkAdjustment" id="movement_look_ahead_adjustment">
<property name="lower">-1</property>
<property name="upper">40</property>
<property name="step-increment">1</property>
<property name="value">-1</property>
</object>
</property>
<marks>
<mark value="-1" position="bottom">Default</mark>
<mark value="10" position="bottom">10ms</mark>
<mark value="20" position="bottom">20ms</mark>
<mark value="30" position="bottom">30ms</mark>
<mark value="40" position="bottom">40ms</mark>
</marks>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</child>
</object>
</child>
<child>