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.breezy_desktop_running = false;
this._ipc_file = Gio.file_new_for_path(IPC_FILE_PATH); this._ipc_file = Gio.file_new_for_path(IPC_FILE_PATH);
this._running = false; this._running = false;
this._device_data = null; this.device_data = null;
} }
start() { start() {
@ -105,9 +105,9 @@ export const DeviceDataStream = GObject.registerClass({
// hasn't been checked within KEEPALIVE_REFRESH_INTERVAL_SEC. // hasn't been checked within KEEPALIVE_REFRESH_INTERVAL_SEC.
refresh_data(keepalive_only = false) { refresh_data(keepalive_only = false) {
if (this._ipc_file.query_exists(null) && ( if (this._ipc_file.query_exists(null) && (
!this._device_data?.imuData || !this.device_data?.imuData ||
!keepalive_only || !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); let data = this._ipc_file.load_contents(null);
if (data[0]) { 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 // 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 (enabled && this.widescreen_mode_state !== sbsEnabled) this.widescreen_mode_state = sbsEnabled;
if (!this._device_data) { if (!this.device_data) {
this._device_data = { this.device_data = {
version, version,
enabled, enabled,
imuResetState, imuResetState,
@ -142,8 +142,8 @@ export const DeviceDataStream = GObject.registerClass({
while (!success && attempts < 3) { while (!success && attempts < 3) {
if (dataView.byteLength === DATA_VIEW_LENGTH) { if (dataView.byteLength === DATA_VIEW_LENGTH) {
if (checkParityByte(dataView)) { if (checkParityByte(dataView)) {
this._device_data.imuData = imuData; this.device_data.imuData = imuData;
this._device_data.imuDateMs = imuDateMs; this.device_data.imuDateMs = imuDateMs;
this.imu_snapshots = { this.imu_snapshots = {
imu_data: imuData, imu_data: imuData,
timestamp_ms: imuDateMs timestamp_ms: imuDateMs

View File

@ -48,6 +48,9 @@ export default class BreezyDesktopExtension extends Extension {
this._target_monitor = null; this._target_monitor = null;
this._is_effect_running = false; this._is_effect_running = false;
this._distance_binding = null; 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._distance_connection = null;
this._follow_threshold_connection = null; this._follow_threshold_connection = null;
this._widescreen_mode_settings_connection = null; this._widescreen_mode_settings_connection = null;
@ -139,7 +142,7 @@ export default class BreezyDesktopExtension extends Extension {
this._running_poller_id = undefined; this._running_poller_id = undefined;
return GLib.SOURCE_REMOVE; return GLib.SOURCE_REMOVE;
} else { } 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; return GLib.SOURCE_CONTINUE;
} }
} catch (e) { } catch (e) {
@ -175,31 +178,30 @@ export default class BreezyDesktopExtension extends Extension {
try { try {
Globals.logger.log_debug('BreezyDesktopExtension _find_supported_monitor'); 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) || monitor => monitor && (SUPPORTED_MONITOR_PRODUCTS.includes(monitor.product) ||
this.settings.get_string('custom-monitor-product') === 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) { if (target_monitor !== undefined) {
Globals.logger.log(`Identified supported monitor: ${target_monitor.product} on ${target_monitor.connector}`); Globals.logger.log(`Identified supported monitor: ${target_monitor.product} on ${target_monitor.connector}`);
return { return {
monitor: this._monitor_manager.getMonitors()[target_monitor.index], monitor: this._monitor_manager.getMonitors()[target_monitor.index],
connector: target_monitor.connector, connector: target_monitor.connector,
refreshRate: target_monitor.refreshRate, refreshRate: target_monitor.refreshRate,
is_dummy: target_monitor.product === NESTED_MONITOR_PRODUCT, is_dummy: is_dummy,
is_virtual: target_monitor.product === VIRTUAL_MONITOR_PRODUCT 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'); Globals.logger.log_debug('BreezyDesktopExtension _find_supported_monitor - No supported monitor found');
return null; return null;
} catch (e) { } catch (e) {
@ -292,7 +294,9 @@ export default class BreezyDesktopExtension extends Extension {
Globals.data_stream.refresh_data(); Globals.data_stream.refresh_data();
this._overlay_content = new VirtualMonitorsActor({ this._overlay_content = new VirtualMonitorsActor({
monitors: virtualMonitors, 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, target_monitor: targetMonitor,
display_distance: this.settings.get_double('display-distance'), display_distance: this.settings.get_double('display-distance'),
toggle_display_distance_start: this.settings.get_double('toggle-display-distance-start'), 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._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._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_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._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)); 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.settings.unbind(this._distance_binding);
this._distance_binding = null; 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) { if (this._distance_connection) {
this.settings.disconnect(this._distance_connection); this.settings.disconnect(this._distance_connection);
this._distance_connection = null; 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 lookVector = [1.0, 0.0, 0.0]; // NWU vector pointing to the center of the screen
const rotatedLookVector = applyQuaternionToVector(lookVector, quaternion); const rotatedLookVector = applyQuaternionToVector(lookVector, quaternion);
// Globals.logger.log(`\t\t\tRotated look vector: ${rotatedLookVector}`);
let closestIndex = -1; let closestIndex = -1;
let closestDistance = Infinity; 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% // only switch if the closest monitor is greater than the previous closest by 25%
if (previousClosestIndex !== undefined && closestIndex !== previousClosestIndex && closestDistance * 1.25 > previousDistance) { if (previousClosestIndex !== undefined && closestIndex !== previousClosestIndex && closestDistance * 1.25 > previousDistance) {
return previousClosestIndex; return previousClosestIndex;
@ -68,10 +65,6 @@ function degreesToRadians(degrees) {
return degrees * Math.PI / 180.0; 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 * @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, GObject.ParamFlags.READWRITE,
0, 100, 0 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': GObject.ParamSpec.jsobject(
'imu-snapshots', 'imu-snapshots',
'IMU Snapshots', 'IMU Snapshots',
'Latest IMU quaternion snapshots and epoch timestamp for when it was collected', 'Latest IMU quaternion snapshots and epoch timestamp for when it was collected',
GObject.ParamFlags.READWRITE 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': GObject.ParamSpec.int(
'width', 'width',
'Width', 'Width',
@ -281,13 +273,6 @@ export const VirtualMonitorEffect = GObject.registerClass({
GObject.ParamFlags.READWRITE, GObject.ParamFlags.READWRITE,
'horizontal', ['horizontal', 'vertical', 'none'] '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': GObject.ParamSpec.int(
'focused-monitor-index', 'focused-monitor-index',
'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::display-distance', this._update_display_distance.bind(this));
this.connect('notify::focused-monitor-index', 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() { _is_focused() {
@ -361,8 +348,6 @@ export const VirtualMonitorEffect = GObject.registerClass({
this._distance_ease_timeline.stop(); 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 // if we're the focused display, we'll double the timeline and wait for the first half to let other
// displays ease out first // displays ease out first
@ -390,11 +375,28 @@ export const VirtualMonitorEffect = GObject.registerClass({
this._current_display_distance = this._distance_ease_start + this._current_display_distance = this._distance_ease_start +
progress * (this._distance_ease_target - this._distance_ease_start); progress * (this._distance_ease_target - this._distance_ease_start);
this._update_display_position_uniforms();
}).bind(this)); }).bind(this));
this._distance_ease_timeline.start(); 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) { perspective(fovDiagonalRadians, aspect, near, far) {
// compute horizontal fov given diagonal fov and aspect ratio // compute horizontal fov given diagonal fov and aspect ratio
const h = Math.sqrt(aspect * aspect + 1); const h = Math.sqrt(aspect * aspect + 1);
@ -516,22 +518,20 @@ export const VirtualMonitorEffect = GObject.registerClass({
if (!this._initialized) { if (!this._initialized) {
const aspect = this.get_actor().width / this.get_actor().height; const aspect = this.get_actor().width / this.get_actor().height;
const projection_matrix = this.perspective( const projection_matrix = this.perspective(
this.fov_degrees * Math.PI / 180.0, Globals.data_stream.device_data.displayFov * Math.PI / 180.0,
aspect, aspect,
0.0001, 0.0001,
1000.0 1000.0
); );
this.set_uniform_matrix(this.get_uniform_location("u_projection_matrix"), false, 4, projection_matrix); 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_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_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.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._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_look_ahead_ms'), 1, [lookAheadMS(this.imu_snapshots.timestamp_ms, 5)]);
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_matrix(this.get_uniform_location("u_imu_data"), false, 4, this.imu_snapshots.imu_data); this.set_uniform_matrix(this.get_uniform_location("u_imu_data"), false, 4, this.imu_snapshots.imu_data);
this.get_pipeline().set_layer_filters( this.get_pipeline().set_layer_filters(
@ -552,25 +552,45 @@ export const VirtualMonitorsActor = GObject.registerClass({
'Array of monitor indexes', 'Array of monitor indexes',
GObject.ParamFlags.READWRITE 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': GObject.ParamSpec.jsobject(
'target-monitor', 'target-monitor',
'Target Monitor', 'Target Monitor',
'Details about the monitor being used as a viewport', 'Details about the monitor being used as a viewport',
GObject.ParamFlags.READWRITE 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': GObject.ParamSpec.jsobject(
'imu-snapshots', 'imu-snapshots',
'IMU Snapshots', 'IMU Snapshots',
'Latest IMU quaternion snapshots and epoch timestamp for when it was collected', 'Latest IMU quaternion snapshots and epoch timestamp for when it was collected',
GObject.ParamFlags.READWRITE 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': GObject.ParamSpec.int(
'focused-monitor-index', 'focused-monitor-index',
'Focused Monitor Index', 'Focused Monitor Index',
@ -629,34 +649,16 @@ export const VirtualMonitorsActor = GObject.registerClass({
this.width = this.target_monitor.width; this.width = this.target_monitor.width;
this.height = this.target_monitor.height; this.height = this.target_monitor.height;
this._frametime_ms = Math.floor(1000 / (this.target_framerate ?? 60.0)); this._frametime_ms = Math.floor(1000 / (this.target_framerate ?? 60.0));
this._all_monitors = [ this._all_monitors = [
this.target_monitor, this.target_monitor,
...this.monitors ...this.monitors
]; ]
} }
renderMonitors() { renderMonitors() {
this._monitorPlacements = monitorsToPlacements( this._update_monitor_placements();
{
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'
);
// 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 = [ const actorToDisplayRatios = [
global.stage.width / this.width, global.stage.width / this.width,
global.stage.height / this.height 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}`); Globals.logger.log_debug(`\t\t\tActor to display ratios: ${actorToDisplayRatios}, offsets: ${actorToDisplayOffsets}`);
this._all_monitors.forEach(((monitor, index) => { 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}`); 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({ const containerActor = new Clutter.Actor({
width: this.width, width: this.width,
height: this.height, height: this.height,
@ -700,18 +696,18 @@ export const VirtualMonitorsActor = GObject.registerClass({
containerActor.add_child(monitorClone); containerActor.add_child(monitorClone);
const effect = new VirtualMonitorEffect({ const effect = new VirtualMonitorEffect({
imu_snapshots: this.imu_snapshots, imu_snapshots: this.imu_snapshots,
fov_degrees: this.fov_degrees,
monitor_index: index, monitor_index: index,
display_position: [-noRotationVector[1], -noRotationVector[2], -noRotationVector[0]], monitor_placements: this.monitor_placements,
display_distance: this.display_distance, display_distance: this.display_distance,
display_distance_default: Math.max(this.toggle_display_distance_start, this.toggle_display_distance_end), display_distance_default: Math.max(this.toggle_display_distance_start, this.toggle_display_distance_end),
monitor_wrapping_scheme: 'horizontal', monitor_wrapping_scheme: this.monitor_wrapping_scheme,
monitor_wrapping_rotation_radians: this._monitorPlacements[index].rotationAngleRadians,
actor_to_display_ratios: actorToDisplayRatios, actor_to_display_ratios: actorToDisplayRatios,
actor_to_display_offsets: actorToDisplayOffsets actor_to_display_offsets: actorToDisplayOffsets
}); });
containerActor.add_effect_with_name('viewport-effect', effect); containerActor.add_effect_with_name('viewport-effect', effect);
this.add_child(containerActor); 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('imu-snapshots', effect, 'imu-snapshots', GObject.BindingFlags.DEFAULT);
this.bind_property('focused-monitor-index', effect, 'focused-monitor-index', 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); this.bind_property('display-distance', effect, 'display-distance', GObject.BindingFlags.DEFAULT);
@ -729,7 +725,6 @@ export const VirtualMonitorsActor = GObject.registerClass({
this._monitorsAsNormalizedVectors, this.closestMonitorIndex 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)) { if (closestMonitorIndex !== -1 && (this.focused_monitor_index === undefined || this.focused_monitor_index !== closestMonitorIndex)) {
Globals.logger.log(`Switching to monitor ${closestMonitorIndex}`); Globals.logger.log(`Switching to monitor ${closestMonitorIndex}`);
this.focused_monitor_index = 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-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::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::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(); 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() { _handle_display_distance_properties_change() {
const distance_from_end = Math.abs(this.display_distance - this.toggle_display_distance_end); 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 The size of the display
</description> </description>
</key> </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"> <key name="curved-display" type="b">
<default> <default>
false false

View File

@ -51,6 +51,7 @@ class ConnectedDevice(Gtk.Box):
movement_look_ahead_adjustment = Gtk.Template.Child() movement_look_ahead_adjustment = Gtk.Template.Child()
text_scaling_scale = Gtk.Template.Child() text_scaling_scale = Gtk.Template.Child()
text_scaling_adjustment = Gtk.Template.Child() text_scaling_adjustment = Gtk.Template.Child()
monitor_wrapping_scheme_menu = Gtk.Template.Child()
def __init__(self): def __init__(self):
@ -66,7 +67,8 @@ class ConnectedDevice(Gtk.Box):
# self.add_virtual_display_button, # self.add_virtual_display_button,
self.set_toggle_display_distance_start_button, self.set_toggle_display_distance_start_button,
self.set_toggle_display_distance_end_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 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('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('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.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.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(), [ bind_shortcut_settings(self.get_parent(), [
[self.reassign_toggle_xr_effect_shortcut_button, self.toggle_xr_effect_shortcut_label], [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) 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): 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] 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 breezy_features_granted = len(enabled_breezy_features) > 0

View File

@ -152,6 +152,30 @@
</child> </child>
</object> </object>
</child> </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> </object>
</child> </child>
</object> </object>