Add new UI controls and hook up settings

This commit is contained in:
wheaney 2025-02-05 22:40:29 -08:00
parent 146611ca6f
commit 5f66019580
6 changed files with 196 additions and 90 deletions

View File

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

View File

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

View File

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

View File

@ -91,6 +91,33 @@
The size of the display
</description>
</key>
<key name="viewport-offset-x" type="d">
<default>
0.0
</default>
<summary>Viewport offset x</summary>
<description>
How far to offset the viewport from the target monitor in the x direction
</description>
</key>
<key name="viewport-offset-y" type="d">
<default>
0.0
</default>
<summary>Viewport offset y</summary>
<description>
How far to offset the viewport from the target monitor in the y direction
</description>
</key>
<key name="monitor-wrapping-scheme" type="s">
<default>
"automatic"
</default>
<summary>Monitor wrapping scheme</summary>
<description>
How the monitors are wrapped around the viewport
</description>
</key>
<key name="curved-display" type="b">
<default>
false

View File

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

View File

@ -152,6 +152,30 @@
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes"><!-- dropdown menu -->Multi-monitor wrapping</property>
<property name="subtitle" translatable="yes">When displaying multiple monitors, choose how they should wrap around you</property>
<property name="valign">2</property>
<child>
<object class="GtkBox">
<property name="spacing">30</property>
<property name="width-request">150</property>
<property name="margin-start">30</property>
<child>
<object class="GtkComboBoxText" id="monitor_wrapping_scheme_menu">
<items>
<item translatable="yes" id="automatic">Automatic</item>
<item translatable="yes" id="horizontal">Horizontal</item>
<item translatable="yes" id="vertical">Vertical</item>
<item translatable="yes" id="none">None</item>
</items>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>