diff --git a/gnome/src/devicedatastream.js b/gnome/src/devicedatastream.js index 75b426a..161e9ff 100644 --- a/gnome/src/devicedatastream.js +++ b/gnome/src/devicedatastream.js @@ -80,7 +80,7 @@ export const DeviceDataStream = GObject.registerClass({ this.breezy_desktop_running = false; this._ipc_file = Gio.file_new_for_path(IPC_FILE_PATH); this._running = false; - this._device_data = null; + this.device_data = null; } start() { @@ -105,9 +105,9 @@ export const DeviceDataStream = GObject.registerClass({ // hasn't been checked within KEEPALIVE_REFRESH_INTERVAL_SEC. refresh_data(keepalive_only = false) { if (this._ipc_file.query_exists(null) && ( - !this._device_data?.imuData || + !this.device_data?.imuData || !keepalive_only || - getEpochSec() - toSec(this._device_data?.imuDateMs ?? 0) > KEEPALIVE_REFRESH_INTERVAL_SEC + getEpochSec() - toSec(this.device_data?.imuDateMs ?? 0) > KEEPALIVE_REFRESH_INTERVAL_SEC )) { let data = this._ipc_file.load_contents(null); if (data[0]) { @@ -125,8 +125,8 @@ export const DeviceDataStream = GObject.registerClass({ // update the widescreen property if the state changes while still enabled, trigger "notify::" events if (enabled && this.widescreen_mode_state !== sbsEnabled) this.widescreen_mode_state = sbsEnabled; - if (!this._device_data) { - this._device_data = { + if (!this.device_data) { + this.device_data = { version, enabled, imuResetState, @@ -142,8 +142,8 @@ export const DeviceDataStream = GObject.registerClass({ while (!success && attempts < 3) { if (dataView.byteLength === DATA_VIEW_LENGTH) { if (checkParityByte(dataView)) { - this._device_data.imuData = imuData; - this._device_data.imuDateMs = imuDateMs; + this.device_data.imuData = imuData; + this.device_data.imuDateMs = imuDateMs; this.imu_snapshots = { imu_data: imuData, timestamp_ms: imuDateMs diff --git a/gnome/src/extension.js b/gnome/src/extension.js index 94bb3b7..6a86e20 100644 --- a/gnome/src/extension.js +++ b/gnome/src/extension.js @@ -48,6 +48,9 @@ export default class BreezyDesktopExtension extends Extension { this._target_monitor = null; this._is_effect_running = false; this._distance_binding = null; + this._monitor_wrapping_scheme_binding = null; + this._viewport_offset_x_binding = null; + this._viewport_offset_y_binding = null; this._distance_connection = null; this._follow_threshold_connection = null; this._widescreen_mode_settings_connection = null; @@ -139,7 +142,7 @@ export default class BreezyDesktopExtension extends Extension { this._running_poller_id = undefined; return GLib.SOURCE_REMOVE; } else { - Globals.logger.log_debug(`BreezyDesktopExtension _poll_for_ready - device connected: ${Globals.data_stream.breezy_desktop_running}, target_monitor: ${!!target_monitor}`); + Globals.logger.log_debug(`BreezyDesktopExtension _poll_for_ready - breezy enabled: ${Globals.data_stream.breezy_desktop_running}, target_monitor: ${!!target_monitor}`); return GLib.SOURCE_CONTINUE; } } catch (e) { @@ -175,31 +178,30 @@ export default class BreezyDesktopExtension extends Extension { try { Globals.logger.log_debug('BreezyDesktopExtension _find_supported_monitor'); - const target_monitor = this._monitor_manager.getMonitorPropertiesList()?.find( + let target_monitor = this._monitor_manager.getMonitorPropertiesList()?.find( monitor => monitor && (SUPPORTED_MONITOR_PRODUCTS.includes(monitor.product) || this.settings.get_string('custom-monitor-product') === monitor.product)); + let is_dummy = target_monitor?.product === NESTED_MONITOR_PRODUCT; + + if (target_monitor === undefined && this.settings.get_boolean('developer-mode')) { + Globals.logger.log_debug('BreezyDesktopExtension _find_supported_monitor - Using dummy monitor'); + // find the first of the physical monitors + target_monitor = this._monitor_manager.getMonitorPropertiesList()?.find( + monitor => monitor && monitor.product !== VIRTUAL_MONITOR_PRODUCT); + is_dummy = true; + } + if (target_monitor !== undefined) { 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, refreshRate: target_monitor.refreshRate, - is_dummy: target_monitor.product === NESTED_MONITOR_PRODUCT, + is_dummy: is_dummy, is_virtual: target_monitor.product === VIRTUAL_MONITOR_PRODUCT }; } - if (this.settings.get_boolean('developer-mode')) { - Globals.logger.log_debug('BreezyDesktopExtension _find_supported_monitor - Using dummy monitor'); - // allow testing XR devices with just USB, no video needed - return { - monitor: this._monitor_manager.getMonitors()[0], - connector: 'dummy', - refreshRate: 60, - is_dummy: true - }; - } - Globals.logger.log_debug('BreezyDesktopExtension _find_supported_monitor - No supported monitor found'); return null; } catch (e) { @@ -292,7 +294,9 @@ export default class BreezyDesktopExtension extends Extension { Globals.data_stream.refresh_data(); this._overlay_content = new VirtualMonitorsActor({ monitors: virtualMonitors, - fov_degrees: 46.0, + monitor_wrapping_scheme: this.settings.get_string('monitor-wrapping-scheme'), + viewport_offset_x: this.settings.get_double('viewport-offset-x'), + viewport_offset_y: this.settings.get_double('viewport-offset-y'), target_monitor: targetMonitor, display_distance: this.settings.get_double('display-distance'), toggle_display_distance_start: this.settings.get_double('toggle-display-distance-start'), @@ -326,6 +330,9 @@ export default class BreezyDesktopExtension extends Extension { this._breezy_desktop_running_connection = Globals.data_stream.connect('notify::breezy-desktop-running', this._handle_breezy_desktop_running_change.bind(this)); this._overlay_content.renderMonitors(); + this._monitor_wrapping_scheme_binding = this.settings.bind('monitor-wrapping-scheme', this._overlay_content, 'monitor-wrapping-scheme', Gio.SettingsBindFlags.DEFAULT); + this._viewport_offset_x_binding = this.settings.bind('viewport-offset-x', this._overlay_content, 'viewport-offset-x', Gio.SettingsBindFlags.DEFAULT); + this._viewport_offset_y_binding = this.settings.bind('viewport-offset-y', this._overlay_content, 'viewport-offset-y', Gio.SettingsBindFlags.DEFAULT); this._distance_binding = this.settings.bind('display-distance', this._overlay_content, 'display-distance', Gio.SettingsBindFlags.DEFAULT); this._distance_connection = this.settings.connect('changed::display-distance', this._update_display_distance.bind(this)); this._follow_threshold_connection = this.settings.connect('changed::follow-threshold', this._update_follow_threshold.bind(this)); @@ -591,6 +598,18 @@ export default class BreezyDesktopExtension extends Extension { this.settings.unbind(this._distance_binding); this._distance_binding = null; } + if (this._viewport_offset_x_binding) { + this.settings.unbind(this._viewport_offset_x_binding); + this._viewport_offset_x_binding = null; + } + if (this._viewport_offset_y_binding) { + this.settings.unbind(this._viewport_offset_y_binding); + this._viewport_offset_y_binding = null; + } + if (this._monitor_wrapping_scheme_binding) { + this.settings.unbind(this._monitor_wrapping_scheme_binding); + this._monitor_wrapping_scheme_binding = null; + } if (this._distance_connection) { this.settings.disconnect(this._distance_connection); this._distance_connection = null; diff --git a/gnome/src/virtualmonitorsactor.js b/gnome/src/virtualmonitorsactor.js index d45a2ba..f005379 100644 --- a/gnome/src/virtualmonitorsactor.js +++ b/gnome/src/virtualmonitorsactor.js @@ -31,7 +31,6 @@ function findClosestVector(quaternion, vectors, previousClosestIndex) { const lookVector = [1.0, 0.0, 0.0]; // NWU vector pointing to the center of the screen const rotatedLookVector = applyQuaternionToVector(lookVector, quaternion); - // Globals.logger.log(`\t\t\tRotated look vector: ${rotatedLookVector}`); let closestIndex = -1; let closestDistance = Infinity; @@ -54,8 +53,6 @@ function findClosestVector(quaternion, vectors, previousClosestIndex) { } }); - // Globals.logger.log(`\t\t\tClosest monitor: ${closestIndex}, distance: ${closestDistance}`); - // only switch if the closest monitor is greater than the previous closest by 25% if (previousClosestIndex !== undefined && closestIndex !== previousClosestIndex && closestDistance * 1.25 > previousDistance) { return previousClosestIndex; @@ -68,10 +65,6 @@ function degreesToRadians(degrees) { return degrees * Math.PI / 180.0; } -function radiansToDegrees(radians) { - return radians * 180.0 / Math.PI; -} - /*** * @returns {Object} - containing `start`, `center`, and `end` radians for rotating the given monitor */ @@ -247,19 +240,18 @@ export const VirtualMonitorEffect = GObject.registerClass({ GObject.ParamFlags.READWRITE, 0, 100, 0 ), + 'monitor-placements': GObject.ParamSpec.jsobject( + 'monitor-placements', + 'Monitor Placements', + 'Target and virtual monitor placement details, as relevant to rendering', + GObject.ParamFlags.READWRITE + ), 'imu-snapshots': GObject.ParamSpec.jsobject( 'imu-snapshots', 'IMU Snapshots', 'Latest IMU quaternion snapshots and epoch timestamp for when it was collected', GObject.ParamFlags.READWRITE ), - 'fov-degrees': GObject.ParamSpec.double( - 'fov-degrees', - 'FOV Degrees', - 'Field of view in degrees', - GObject.ParamFlags.READWRITE, - 30.0, 100.0, 46.0 - ), 'width': GObject.ParamSpec.int( 'width', 'Width', @@ -281,13 +273,6 @@ export const VirtualMonitorEffect = GObject.registerClass({ GObject.ParamFlags.READWRITE, 'horizontal', ['horizontal', 'vertical', 'none'] ), - 'monitor-wrapping-rotation-radians': GObject.ParamSpec.double( - 'monitor-wrapping-rotation-radians', - 'Monitor Wrapping Rotation Radians', - 'Rotation of the monitor wrapping around the viewport', - GObject.ParamFlags.READWRITE, - -360.0, 360.0, 0.0 - ), 'focused-monitor-index': GObject.ParamSpec.int( 'focused-monitor-index', 'Focused Monitor Index', @@ -347,6 +332,8 @@ export const VirtualMonitorEffect = GObject.registerClass({ this.connect('notify::display-distance', this._update_display_distance.bind(this)); this.connect('notify::focused-monitor-index', this._update_display_distance.bind(this)); + this.connect('notify::monitor-placements', this._update_display_position_uniforms.bind(this)); + this.connect('notify::monitor-wrapping-scheme', this._update_display_position_uniforms.bind(this)); } _is_focused() { @@ -361,8 +348,6 @@ export const VirtualMonitorEffect = GObject.registerClass({ this._distance_ease_timeline.stop(); } - - const mid_distance = (this.display_distance_default + desired_distance) / 2; // if we're the focused display, we'll double the timeline and wait for the first half to let other // displays ease out first @@ -390,11 +375,28 @@ export const VirtualMonitorEffect = GObject.registerClass({ this._current_display_distance = this._distance_ease_start + progress * (this._distance_ease_target - this._distance_ease_start); + this._update_display_position_uniforms(); }).bind(this)); this._distance_ease_timeline.start(); } + _update_display_position_uniforms() { + // this is in NWU coordinates + const noRotationVector = this.monitor_placements[this.monitor_index].topLeftNoRotate; + Globals.logger.log_debug(`\t\t\tMonitor ${this.monitor_index} vectors: ${JSON.stringify(this.monitor_placements[this.monitor_index])}`); + + // convert to CoGL's east-down-south coordinates and apply display distance + this.set_uniform_float(this.get_uniform_location("u_display_position"), 3, + [-noRotationVector[1], -noRotationVector[2], this._current_display_distance * -noRotationVector[0]]); + + const rotation_radians = this.monitor_placements[this.monitor_index].rotationAngleRadians; + this.set_uniform_float(this.get_uniform_location("u_rotation_x_radians"), 1, + [this.monitor_wrapping_scheme === 'vertical' ? rotation_radians : 0.0]); + this.set_uniform_float(this.get_uniform_location("u_rotation_y_radians"), 1, + [this.monitor_wrapping_scheme === 'horizontal' ? rotation_radians : 0.0]); + } + perspective(fovDiagonalRadians, aspect, near, far) { // compute horizontal fov given diagonal fov and aspect ratio const h = Math.sqrt(aspect * aspect + 1); @@ -516,22 +518,20 @@ export const VirtualMonitorEffect = GObject.registerClass({ if (!this._initialized) { const aspect = this.get_actor().width / this.get_actor().height; const projection_matrix = this.perspective( - this.fov_degrees * Math.PI / 180.0, + Globals.data_stream.device_data.displayFov * Math.PI / 180.0, aspect, 0.0001, 1000.0 ); this.set_uniform_matrix(this.get_uniform_location("u_projection_matrix"), false, 4, projection_matrix); - this.set_uniform_float(this.get_uniform_location("u_rotation_x_radians"), 1, [this.monitor_wrapping_scheme === 'vertical' ? this.monitor_wrapping_rotation_radians : 0.0]); - this.set_uniform_float(this.get_uniform_location("u_rotation_y_radians"), 1, [this.monitor_wrapping_scheme === 'horizontal' ? this.monitor_wrapping_rotation_radians : 0.0]); this.set_uniform_float(this.get_uniform_location("u_display_resolution"), 2, [this.get_actor().width, this.get_actor().height]); this.set_uniform_float(this.get_uniform_location("u_actor_to_display_ratios"), 2, this.actor_to_display_ratios); this.set_uniform_float(this.get_uniform_location("u_actor_to_display_offsets"), 2, this.actor_to_display_offsets); + this._update_display_position_uniforms(); this._initialized = true; } - this.set_uniform_float(this.get_uniform_location('u_look_ahead_ms'), 1, [lookAheadMS(this.imu_snapshots.timestamp_ms, 0)]); - this.set_uniform_float(this.get_uniform_location("u_display_position"), 3, [this.display_position[0], this.display_position[1], this._current_display_distance * this.display_position[2]]); + this.set_uniform_float(this.get_uniform_location('u_look_ahead_ms'), 1, [lookAheadMS(this.imu_snapshots.timestamp_ms, 5)]); this.set_uniform_matrix(this.get_uniform_location("u_imu_data"), false, 4, this.imu_snapshots.imu_data); this.get_pipeline().set_layer_filters( @@ -552,25 +552,45 @@ export const VirtualMonitorsActor = GObject.registerClass({ 'Array of monitor indexes', GObject.ParamFlags.READWRITE ), + 'monitor-wrapping-scheme': GObject.ParamSpec.string( + 'monitor-wrapping-scheme', + 'Monitor Wrapping Scheme', + 'How the monitors are wrapped around the viewport', + GObject.ParamFlags.READWRITE, + 'horizontal', ['horizontal', 'vertical', 'none'] + ), 'target-monitor': GObject.ParamSpec.jsobject( 'target-monitor', 'Target Monitor', 'Details about the monitor being used as a viewport', GObject.ParamFlags.READWRITE ), + 'viewport-offset-x': GObject.ParamSpec.double( + 'viewport-offset-x', + 'Viewport Offset x', + 'Offset to apply to the viewport', + GObject.ParamFlags.READWRITE, + -2.5, 2.5, 0.0 + ), + 'viewport-offset-y': GObject.ParamSpec.double( + 'viewport-offset-y', + 'Viewport Offset y', + 'Offset to apply to the viewport', + GObject.ParamFlags.READWRITE, + -2.5, 2.5, 0.0 + ), + 'monitor-placements': GObject.ParamSpec.jsobject( + 'monitor-placements', + 'Monitor Placements', + 'Target and virtual monitor placement details, as relevant to rendering', + GObject.ParamFlags.READWRITE + ), 'imu-snapshots': GObject.ParamSpec.jsobject( 'imu-snapshots', 'IMU Snapshots', 'Latest IMU quaternion snapshots and epoch timestamp for when it was collected', GObject.ParamFlags.READWRITE ), - 'fov-degrees': GObject.ParamSpec.double( - 'fov-degrees', - 'FOV Degrees', - 'Field of view in degrees', - GObject.ParamFlags.READWRITE, - 30.0, 100.0, 46.0 - ), 'focused-monitor-index': GObject.ParamSpec.int( 'focused-monitor-index', 'Focused Monitor Index', @@ -629,34 +649,16 @@ export const VirtualMonitorsActor = GObject.registerClass({ this.width = this.target_monitor.width; this.height = this.target_monitor.height; this._frametime_ms = Math.floor(1000 / (this.target_framerate ?? 60.0)); + this._all_monitors = [ this.target_monitor, ...this.monitors - ]; + ] } renderMonitors() { - this._monitorPlacements = monitorsToPlacements( - { - fovDegrees: this.fov_degrees, - widthPixels: this.width, - heightPixels: this.height - }, - this._all_monitors.map(monitor => ({ - x: monitor.x, - y: monitor.y, - width: monitor.width, - height: monitor.height - })), - 'horizontal' - ); + this._update_monitor_placements(); - // normalize the center vectors - this._monitorsAsNormalizedVectors = this._monitorPlacements.map(monitorVectors => { - const vector = monitorVectors.center; - const length = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]); - return [vector[0] / length, vector[1] / length, vector[2] / length]; - }); const actorToDisplayRatios = [ global.stage.width / this.width, global.stage.height / this.height @@ -673,14 +675,8 @@ export const VirtualMonitorsActor = GObject.registerClass({ Globals.logger.log_debug(`\t\t\tActor to display ratios: ${actorToDisplayRatios}, offsets: ${actorToDisplayOffsets}`); this._all_monitors.forEach(((monitor, index) => { - // if (index === 0) return; Globals.logger.log(`\t\t\tMonitor ${index}: ${monitor.x}, ${monitor.y}, ${monitor.width}, ${monitor.height}`); - - // this is in NWU coordinates - const noRotationVector = this._monitorPlacements[index].topLeftNoRotate; - Globals.logger.log_debug(`\t\t\tMonitor ${index} vectors: ${JSON.stringify(this._monitorPlacements[index])}`); - // actor coordinates are east-up-south const containerActor = new Clutter.Actor({ width: this.width, height: this.height, @@ -700,18 +696,18 @@ export const VirtualMonitorsActor = GObject.registerClass({ containerActor.add_child(monitorClone); const effect = new VirtualMonitorEffect({ imu_snapshots: this.imu_snapshots, - fov_degrees: this.fov_degrees, monitor_index: index, - display_position: [-noRotationVector[1], -noRotationVector[2], -noRotationVector[0]], + monitor_placements: this.monitor_placements, display_distance: this.display_distance, display_distance_default: Math.max(this.toggle_display_distance_start, this.toggle_display_distance_end), - monitor_wrapping_scheme: 'horizontal', - monitor_wrapping_rotation_radians: this._monitorPlacements[index].rotationAngleRadians, + monitor_wrapping_scheme: this.monitor_wrapping_scheme, actor_to_display_ratios: actorToDisplayRatios, actor_to_display_offsets: actorToDisplayOffsets }); containerActor.add_effect_with_name('viewport-effect', effect); this.add_child(containerActor); + this.bind_property('monitor-placements', effect, 'monitor-placements', GObject.BindingFlags.DEFAULT); + this.bind_property('monitor-wrapping-scheme', effect, 'monitor-wrapping-scheme', GObject.BindingFlags.DEFAULT); this.bind_property('imu-snapshots', effect, 'imu-snapshots', GObject.BindingFlags.DEFAULT); this.bind_property('focused-monitor-index', effect, 'focused-monitor-index', GObject.BindingFlags.DEFAULT); this.bind_property('display-distance', effect, 'display-distance', GObject.BindingFlags.DEFAULT); @@ -729,7 +725,6 @@ export const VirtualMonitorsActor = GObject.registerClass({ this._monitorsAsNormalizedVectors, this.closestMonitorIndex ); - // only switch if the closest monitor is greater than the previous closest by 25% if (closestMonitorIndex !== -1 && (this.focused_monitor_index === undefined || this.focused_monitor_index !== closestMonitorIndex)) { Globals.logger.log(`Switching to monitor ${closestMonitorIndex}`); this.focused_monitor_index = closestMonitorIndex; @@ -756,8 +751,37 @@ export const VirtualMonitorsActor = GObject.registerClass({ this.connect('notify::toggle-display-distance-start', this._handle_display_distance_properties_change.bind(this)); this.connect('notify::toggle-display-distance-end', this._handle_display_distance_properties_change.bind(this)); this.connect('notify::display-distance', this._handle_display_distance_properties_change.bind(this)); + this.connect('notify::viewport-offset-x', this._update_monitor_placements.bind(this)); + this.connect('notify::viewport-offset-y', this._update_monitor_placements.bind(this)); this._handle_display_distance_properties_change(); } + + _update_monitor_placements() { + Globals.logger.log_debug(`\t\t\tUpdating monitor placements ${this.viewport_offset_x}, ${this.viewport_offset_y} ${Globals.data_stream.device_data.displayFov}`); + this.monitor_placements = monitorsToPlacements( + { + fovDegrees: Globals.data_stream.device_data.displayFov, + widthPixels: this.width, + heightPixels: this.height + }, + + // shift all monitors so they center around the target monitor, then adjusted by the offsets + this._all_monitors.map(monitor => ({ + x: monitor.x - this.target_monitor.x - this.viewport_offset_x * this.target_monitor.width, + y: monitor.y - this.target_monitor.y - this.viewport_offset_y * this.target_monitor.height, + width: monitor.width, + height: monitor.height + })), + this.monitor_wrapping_scheme + ); + + // normalize the center vectors + this._monitorsAsNormalizedVectors = this.monitor_placements.map(monitorVectors => { + const vector = monitorVectors.center; + const length = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]); + return [vector[0] / length, vector[1] / length, vector[2] / length]; + }); + } _handle_display_distance_properties_change() { const distance_from_end = Math.abs(this.display_distance - this.toggle_display_distance_end); diff --git a/ui/data/com.xronlinux.BreezyDesktop.gschema.xml b/ui/data/com.xronlinux.BreezyDesktop.gschema.xml index b5aafdd..71cabf2 100644 --- a/ui/data/com.xronlinux.BreezyDesktop.gschema.xml +++ b/ui/data/com.xronlinux.BreezyDesktop.gschema.xml @@ -91,6 +91,33 @@ The size of the display + + + 0.0 + + Viewport offset x + + How far to offset the viewport from the target monitor in the x direction + + + + + 0.0 + + Viewport offset y + + How far to offset the viewport from the target monitor in the y direction + + + + + "automatic" + + Monitor wrapping scheme + + How the monitors are wrapped around the viewport + + false diff --git a/ui/src/connecteddevice.py b/ui/src/connecteddevice.py index efad3f6..9ab3523 100644 --- a/ui/src/connecteddevice.py +++ b/ui/src/connecteddevice.py @@ -51,6 +51,7 @@ class ConnectedDevice(Gtk.Box): movement_look_ahead_adjustment = Gtk.Template.Child() text_scaling_scale = Gtk.Template.Child() text_scaling_adjustment = Gtk.Template.Child() + monitor_wrapping_scheme_menu = Gtk.Template.Child() def __init__(self): @@ -66,7 +67,8 @@ class ConnectedDevice(Gtk.Box): # self.add_virtual_display_button, self.set_toggle_display_distance_start_button, self.set_toggle_display_distance_end_button, - self.movement_look_ahead_scale + self.movement_look_ahead_scale, + self.monitor_wrapping_scheme_menu ] self.settings = SettingsManager.get_instance().settings @@ -84,7 +86,10 @@ class ConnectedDevice(Gtk.Box): 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) + self.settings.connect('changed::monitor-wrapping-scheme', self._handle_monitor_wrapping_scheme_setting_changed) self.desktop_settings.bind('text-scaling-factor', self.text_scaling_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT) + self.monitor_wrapping_scheme_menu.connect('changed', self._handle_monitor_wrapping_scheme_menu_changed) + self._handle_monitor_wrapping_scheme_setting_changed() bind_shortcut_settings(self.get_parent(), [ [self.reassign_toggle_xr_effect_shortcut_button, self.toggle_xr_effect_shortcut_label], @@ -121,6 +126,13 @@ class ConnectedDevice(Gtk.Box): self.connect("destroy", self._on_widget_destroy) + def _handle_monitor_wrapping_scheme_setting_changed(self): + current_scheme = self.settings.get_string('monitor-wrapping-scheme') + self.monitor_wrapping_scheme_menu.set_active_id(current_scheme) + + def _handle_monitor_wrapping_scheme_menu_changed(self, widget): + self.settings.set_string('monitor-wrapping-scheme', widget.get_active_id()) + def _handle_enabled_features(self, state_manager, val): enabled_breezy_features = [feature for feature in state_manager.get_property('enabled-features-list') if feature in BREEZY_GNOME_FEATURES] breezy_features_granted = len(enabled_breezy_features) > 0 diff --git a/ui/src/gtk/connected-device.ui b/ui/src/gtk/connected-device.ui index d83ad11..3b73751 100644 --- a/ui/src/gtk/connected-device.ui +++ b/ui/src/gtk/connected-device.ui @@ -152,6 +152,30 @@ + + + Multi-monitor wrapping + When displaying multiple monitors, choose how they should wrap around you + 2 + + + 30 + 150 + 30 + + + + Automatic + Horizontal + Vertical + None + + + + + + +