Add support for smooth follow of a single display
This commit is contained in:
parent
66c035bd14
commit
7a91b2e39b
|
|
@ -21,7 +21,7 @@ const IPC_FILE_PATH = "/dev/shm/breezy_desktop_imu";
|
||||||
const KEEPALIVE_REFRESH_INTERVAL_SEC = 1;
|
const KEEPALIVE_REFRESH_INTERVAL_SEC = 1;
|
||||||
|
|
||||||
// the driver should be using the same data layout version
|
// the driver should be using the same data layout version
|
||||||
const DATA_LAYOUT_VERSION = 3;
|
const DATA_LAYOUT_VERSION = 4;
|
||||||
|
|
||||||
// DataView info: [offset, size, count]
|
// DataView info: [offset, size, count]
|
||||||
const VERSION = [0, UINT8_SIZE, 1];
|
const VERSION = [0, UINT8_SIZE, 1];
|
||||||
|
|
@ -32,7 +32,9 @@ const DISPLAY_FOV = [dataViewEnd(DISPLAY_RES), FLOAT_SIZE, 1];
|
||||||
const LENS_DISTANCE_RATIO = [dataViewEnd(DISPLAY_FOV), FLOAT_SIZE, 1];
|
const LENS_DISTANCE_RATIO = [dataViewEnd(DISPLAY_FOV), FLOAT_SIZE, 1];
|
||||||
const SBS_ENABLED = [dataViewEnd(LENS_DISTANCE_RATIO), BOOL_SIZE, 1];
|
const SBS_ENABLED = [dataViewEnd(LENS_DISTANCE_RATIO), BOOL_SIZE, 1];
|
||||||
const CUSTOM_BANNER_ENABLED = [dataViewEnd(SBS_ENABLED), BOOL_SIZE, 1];
|
const CUSTOM_BANNER_ENABLED = [dataViewEnd(SBS_ENABLED), BOOL_SIZE, 1];
|
||||||
const EPOCH_MS = [dataViewEnd(CUSTOM_BANNER_ENABLED), UINT_SIZE, 2];
|
const SMOOTH_FOLLOW_ENABLED = [dataViewEnd(CUSTOM_BANNER_ENABLED), BOOL_SIZE, 1];
|
||||||
|
const SMOOTH_FOLLOW_ORIGIN_DATA = [dataViewEnd(SMOOTH_FOLLOW_ENABLED), FLOAT_SIZE, 16];
|
||||||
|
const EPOCH_MS = [dataViewEnd(SMOOTH_FOLLOW_ORIGIN_DATA), UINT_SIZE, 2];
|
||||||
const IMU_QUAT_DATA = [dataViewEnd(EPOCH_MS), FLOAT_SIZE, 16];
|
const IMU_QUAT_DATA = [dataViewEnd(EPOCH_MS), FLOAT_SIZE, 16];
|
||||||
const IMU_PARITY_BYTE = [dataViewEnd(IMU_QUAT_DATA), UINT8_SIZE, 1];
|
const IMU_PARITY_BYTE = [dataViewEnd(IMU_QUAT_DATA), UINT8_SIZE, 1];
|
||||||
const DATA_VIEW_LENGTH = dataViewEnd(IMU_PARITY_BYTE);
|
const DATA_VIEW_LENGTH = dataViewEnd(IMU_PARITY_BYTE);
|
||||||
|
|
@ -95,6 +97,13 @@ export const DeviceDataStream = GObject.registerClass({
|
||||||
'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
|
||||||
),
|
),
|
||||||
|
'smooth-follow-enabled': GObject.ParamSpec.boolean(
|
||||||
|
'smooth-follow-enabled',
|
||||||
|
'Smooth follow enabled',
|
||||||
|
'Whether smooth follow is enabled',
|
||||||
|
GObject.ParamFlags.READWRITE,
|
||||||
|
false
|
||||||
|
),
|
||||||
'show-banner': GObject.ParamSpec.boolean(
|
'show-banner': GObject.ParamSpec.boolean(
|
||||||
'show-banner',
|
'show-banner',
|
||||||
'Show banner',
|
'Show banner',
|
||||||
|
|
@ -182,6 +191,8 @@ export const DeviceDataStream = GObject.registerClass({
|
||||||
const version = dataViewUint8(dataView, VERSION);
|
const version = dataViewUint8(dataView, VERSION);
|
||||||
const enabled = dataViewUint8(dataView, ENABLED) !== 0 && version === DATA_LAYOUT_VERSION && validData;
|
const enabled = dataViewUint8(dataView, ENABLED) !== 0 && version === DATA_LAYOUT_VERSION && validData;
|
||||||
let imuData = dataViewFloatArray(dataView, IMU_QUAT_DATA);
|
let imuData = dataViewFloatArray(dataView, IMU_QUAT_DATA);
|
||||||
|
let smoothFollowEnabled = dataViewUint8(dataView, SMOOTH_FOLLOW_ENABLED) !== 0;
|
||||||
|
let smoothFollowOrigin = dataViewFloatArray(dataView, SMOOTH_FOLLOW_ORIGIN_DATA);
|
||||||
const imuResetState = enabled && validData && imuData[0] === 0.0 && imuData[1] === 0.0 && imuData[2] === 0.0 && imuData[3] === 1.0;
|
const imuResetState = enabled && validData && imuData[0] === 0.0 && imuData[1] === 0.0 && imuData[2] === 0.0 && imuData[3] === 1.0;
|
||||||
const customBannerEnabled = dataViewUint8(dataView, CUSTOM_BANNER_ENABLED) !== 0;
|
const customBannerEnabled = dataViewUint8(dataView, CUSTOM_BANNER_ENABLED) !== 0;
|
||||||
const sbsEnabled = dataViewUint8(dataView, SBS_ENABLED) !== 0;
|
const sbsEnabled = dataViewUint8(dataView, SBS_ENABLED) !== 0;
|
||||||
|
|
@ -215,15 +226,24 @@ export const DeviceDataStream = GObject.registerClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (smoothFollowEnabled !== this.smooth_follow_enabled) {
|
||||||
|
Globals.logger.log_debug(`Smooth follow enabled: ${smoothFollowEnabled}`);
|
||||||
|
this.smooth_follow_enabled = smoothFollowEnabled;
|
||||||
|
}
|
||||||
|
this.imu_snapshots = {
|
||||||
|
...(this.imu_snapshots ?? {}),
|
||||||
|
smooth_follow_origin: smoothFollowOrigin
|
||||||
|
}
|
||||||
|
|
||||||
let attempts = 0;
|
let attempts = 0;
|
||||||
while (!success && attempts < 3) {
|
while (!success && attempts < 2) {
|
||||||
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.imuDateMs = imuDateMs;
|
|
||||||
this.imu_snapshots = {
|
this.imu_snapshots = {
|
||||||
imu_data: imuData,
|
imu_data: imuData,
|
||||||
timestamp_ms: imuDateMs
|
timestamp_ms: imuDateMs,
|
||||||
|
smooth_follow_origin: smoothFollowOrigin
|
||||||
};
|
};
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
|
|
@ -231,7 +251,7 @@ export const DeviceDataStream = GObject.registerClass({
|
||||||
Globals.logger.log(`[ERROR] Invalid dataView.byteLength: ${dataView.byteLength} !== ${DATA_VIEW_LENGTH}`)
|
Globals.logger.log(`[ERROR] Invalid dataView.byteLength: ${dataView.byteLength} !== ${DATA_VIEW_LENGTH}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!success && ++attempts < 3) {
|
if (!success && ++attempts < 2) {
|
||||||
data = this._ipc_file.load_contents(null);
|
data = this._ipc_file.load_contents(null);
|
||||||
if (data[0]) {
|
if (data[0]) {
|
||||||
buffer = new Uint8Array(data[1]).buffer;
|
buffer = new Uint8Array(data[1]).buffer;
|
||||||
|
|
@ -277,19 +297,17 @@ export const DeviceDataStream = GObject.registerClass({
|
||||||
...imuDataFirst,
|
...imuDataFirst,
|
||||||
2.0, 1.0, 0.0, 0.0
|
2.0, 1.0, 0.0, 0.0
|
||||||
]
|
]
|
||||||
const imuDateMs = Date.now();
|
|
||||||
this.device_data.imuData = imuData;
|
|
||||||
this.device_data.imuDateMs = imuDateMs;
|
|
||||||
this.imu_snapshots = {
|
this.imu_snapshots = {
|
||||||
imu_data: imuData,
|
imu_data: imuData,
|
||||||
timestamp_ms: imuDateMs
|
timestamp_ms: Date.now(),
|
||||||
|
smooth_follow_origin: [0.0, 0.0, 0.0, 1.0]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
this.breezy_desktop_running = true;
|
this.breezy_desktop_running = true;
|
||||||
} else if (this.breezy_desktop_running !== this.breezy_desktop_actually_running) {
|
} else if (this.breezy_desktop_running !== this.breezy_desktop_actually_running) {
|
||||||
// update the breezy_desktop_running property if the state changes to trigger "notify::" events
|
// update the breezy_desktop_running property if the state changes to trigger "notify::" events
|
||||||
this.breezy_desktop_running = this.breezy_desktop_actually_running;
|
this.breezy_desktop_running = this.breezy_desktop_actually_running;
|
||||||
if (!this.breezy_desktop_running) {
|
if (!this.breezy_desktop_running && keepalive_only) {
|
||||||
this.device_data = null;
|
this.device_data = null;
|
||||||
this.imu_snapshots = null;
|
this.imu_snapshots = null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -260,7 +260,8 @@ export default class BreezyDesktopExtension extends Extension {
|
||||||
|
|
||||||
this._data_stream_bindings = [
|
this._data_stream_bindings = [
|
||||||
'show-banner',
|
'show-banner',
|
||||||
'custom-banner-enabled'
|
'custom-banner-enabled',
|
||||||
|
'smooth-follow-enabled'
|
||||||
].map(data_stream_key =>
|
].map(data_stream_key =>
|
||||||
Globals.data_stream.bind_property(data_stream_key, this._virtual_displays_actor, data_stream_key, Gio.SettingsBindFlags.DEFAULT)
|
Globals.data_stream.bind_property(data_stream_key, this._virtual_displays_actor, data_stream_key, Gio.SettingsBindFlags.DEFAULT)
|
||||||
);
|
);
|
||||||
|
|
@ -508,6 +509,7 @@ export default class BreezyDesktopExtension extends Extension {
|
||||||
|
|
||||||
_toggle_follow_mode() {
|
_toggle_follow_mode() {
|
||||||
Globals.logger.log_debug('BreezyDesktopExtension _toggle_follow_mode');
|
Globals.logger.log_debug('BreezyDesktopExtension _toggle_follow_mode');
|
||||||
|
this._virtual_displays_actor.set_property('smooth-follow-toggle-epoch-ms', Date.now());
|
||||||
this._write_control('toggle_breezy_desktop_smooth_follow', 'true');
|
this._write_control('toggle_breezy_desktop_smooth_follow', 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,23 @@
|
||||||
import Clutter from 'gi://Clutter'
|
import Clutter from 'gi://Clutter'
|
||||||
import Cogl from 'gi://Cogl';
|
import Cogl from 'gi://Cogl';
|
||||||
|
import GLib from 'gi://GLib';
|
||||||
import GObject from 'gi://GObject';
|
import GObject from 'gi://GObject';
|
||||||
import Shell from 'gi://Shell';
|
import Shell from 'gi://Shell';
|
||||||
|
|
||||||
import Globals from './globals.js';
|
import Globals from './globals.js';
|
||||||
import { degreeToRadian, diagonalToCrossFOVs } from './math.js';
|
import { degreeToRadian, diagonalToCrossFOVs } from './math.js';
|
||||||
|
|
||||||
|
|
||||||
|
// these need to mirror the values in XRLinuxDriver
|
||||||
|
// https://github.com/wheaney/XRLinuxDriver/blob/main/src/plugins/smooth_follow.c#L31
|
||||||
|
export const SMOOTH_FOLLOW_SLERP_TIMELINE_MS = 1000;
|
||||||
|
const SMOOTH_FOLLOW_SLERP_FACTOR = Math.pow(1-0.99, 1/SMOOTH_FOLLOW_SLERP_TIMELINE_MS);
|
||||||
|
|
||||||
|
// this mirror's how the driver's slerp function progresses so our effect will match it
|
||||||
|
function smoothFollowSlerpProgress(elapsedMs) {
|
||||||
|
return 1 - Math.pow(SMOOTH_FOLLOW_SLERP_FACTOR, elapsedMs);
|
||||||
|
}
|
||||||
|
|
||||||
// how far to look ahead is how old the IMU data is plus a constant that is either the default for this device or an override
|
// how far to look ahead is how old the IMU data is plus a constant that is either the default for this device or an override
|
||||||
function lookAheadMS(imuDateMs, lookAheadCfg, override) {
|
function lookAheadMS(imuDateMs, lookAheadCfg, override) {
|
||||||
// how stale the imu data is
|
// how stale the imu data is
|
||||||
|
|
@ -41,6 +53,20 @@ export const VirtualDisplayEffect = GObject.registerClass({
|
||||||
'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
|
||||||
),
|
),
|
||||||
|
'smooth-follow-enabled': GObject.ParamSpec.boolean(
|
||||||
|
'smooth-follow-enabled',
|
||||||
|
'Smooth follow enabled',
|
||||||
|
'Whether smooth follow is enabled',
|
||||||
|
GObject.ParamFlags.READWRITE,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
'smooth-follow-toggle-epoch-ms': GObject.ParamSpec.uint64(
|
||||||
|
'smooth-follow-toggle-epoch-ms',
|
||||||
|
'Smooth follow toggle epoch time',
|
||||||
|
'ms since epoch when smooth follow was toggled',
|
||||||
|
GObject.ParamFlags.READWRITE,
|
||||||
|
0, Number.MAX_SAFE_INTEGER, 0
|
||||||
|
),
|
||||||
'width': GObject.ParamSpec.int(
|
'width': GObject.ParamSpec.int(
|
||||||
'width',
|
'width',
|
||||||
'Width',
|
'Width',
|
||||||
|
|
@ -78,12 +104,6 @@ export const VirtualDisplayEffect = GObject.registerClass({
|
||||||
2.5,
|
2.5,
|
||||||
1.0
|
1.0
|
||||||
),
|
),
|
||||||
'display-position': GObject.ParamSpec.jsobject(
|
|
||||||
'display-position',
|
|
||||||
'Display Position',
|
|
||||||
'Position of the display in COGL (ESU) coordinates',
|
|
||||||
GObject.ParamFlags.READWRITE
|
|
||||||
),
|
|
||||||
'display-distance-default': GObject.ParamSpec.double(
|
'display-distance-default': GObject.ParamSpec.double(
|
||||||
'display-distance-default',
|
'display-distance-default',
|
||||||
'Display distance default',
|
'Display distance default',
|
||||||
|
|
@ -148,12 +168,15 @@ export const VirtualDisplayEffect = GObject.registerClass({
|
||||||
|
|
||||||
this._current_display_distance = this._is_focused() ? this.display_distance : this.display_distance_default;
|
this._current_display_distance = this._is_focused() ? this.display_distance : this.display_distance_default;
|
||||||
this.no_distance_ease = false;
|
this.no_distance_ease = false;
|
||||||
|
this._current_follow_ease_progress = 0.0;
|
||||||
|
this._use_smooth_follow_origin = false;
|
||||||
|
|
||||||
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-placements', this._update_display_position_uniforms.bind(this));
|
||||||
this.connect('notify::monitor-wrapping-scheme', this._update_display_position_uniforms.bind(this));
|
this.connect('notify::monitor-wrapping-scheme', this._update_display_position_uniforms.bind(this));
|
||||||
this.connect('notify::show-banner', this._handle_banner_update.bind(this));
|
this.connect('notify::show-banner', this._handle_banner_update.bind(this));
|
||||||
|
this.connect('notify::smooth-follow-enabled', this._handle_smooth_follow_enabled_update.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
_is_focused() {
|
_is_focused() {
|
||||||
|
|
@ -212,8 +235,67 @@ export const VirtualDisplayEffect = GObject.registerClass({
|
||||||
}).bind(this));
|
}).bind(this));
|
||||||
|
|
||||||
this._distance_ease_timeline.start();
|
this._distance_ease_timeline.start();
|
||||||
|
|
||||||
|
if (this.smooth_follow_enabled) this._handle_smooth_follow_enabled_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handle_smooth_follow_enabled_update() {
|
||||||
|
// we'll re-trigger this once a monitor becomes focused
|
||||||
|
if (this.focused_monitor_index === -1) return;
|
||||||
|
|
||||||
|
this._use_smooth_follow_origin = false;
|
||||||
|
|
||||||
|
if (this._follow_ease_timeline?.is_playing()) this._follow_ease_timeline.stop();
|
||||||
|
|
||||||
|
const from = this._current_follow_ease_progress;
|
||||||
|
const to = this.smooth_follow_enabled && this._is_focused() ? 1.0 : 0.0;
|
||||||
|
const toggleTime = this.smooth_follow_toggle_epoch_ms === 0 ? Date.now() : this.smooth_follow_toggle_epoch_ms;
|
||||||
|
|
||||||
|
// would have been a slight delay between request and slerp actually starting
|
||||||
|
const toggleDelayMs = (Date.now() - toggleTime) * 0.75;
|
||||||
|
const slerpStartTime = toggleTime + toggleDelayMs;
|
||||||
|
|
||||||
|
const dataAge = Date.now() - this.imu_snapshots.timestamp_ms;
|
||||||
|
if (to !== from) {
|
||||||
|
this._follow_ease_timeline = Clutter.Timeline.new_for_actor(
|
||||||
|
this.get_actor(),
|
||||||
|
SMOOTH_FOLLOW_SLERP_TIMELINE_MS - toggleDelayMs
|
||||||
|
);
|
||||||
|
this._follow_ease_timeline.connect('new-frame', ((timeline, elapsed_ms) => {
|
||||||
|
const toggleTimeOffsetMs = Date.now() - slerpStartTime;
|
||||||
|
|
||||||
|
// this relies on the slerp function tuned to reach 100% in about 1 second
|
||||||
|
const progress = smoothFollowSlerpProgress(toggleTimeOffsetMs);
|
||||||
|
this._current_follow_ease_progress = from + (to - from) * progress;
|
||||||
|
this._update_display_position_uniforms();
|
||||||
|
}).bind(this));
|
||||||
|
|
||||||
|
this._follow_ease_timeline.connect('completed', (() => {
|
||||||
|
this._current_follow_ease_progress = to;
|
||||||
|
this._use_smooth_follow_origin = false;
|
||||||
|
this.smooth_follow_toggle_epoch_ms = 0;
|
||||||
|
this._update_display_position_uniforms();
|
||||||
|
}).bind(this));
|
||||||
|
|
||||||
|
this._follow_ease_timeline.start();
|
||||||
|
} else if (!this.smooth_follow_enabled) {
|
||||||
|
// smooth follow has been turned off and this screen wasn't the focus,
|
||||||
|
// continue to use the smooth_follow_origin data for 1 more second
|
||||||
|
this._use_smooth_follow_origin = true;
|
||||||
|
GLib.timeout_add(
|
||||||
|
GLib.PRIORITY_DEFAULT,
|
||||||
|
SMOOTH_FOLLOW_SLERP_TIMELINE_MS - toggleDelayMs,
|
||||||
|
(() => {
|
||||||
|
this._use_smooth_follow_origin = false;
|
||||||
|
this.smooth_follow_toggle_epoch_ms = 0;
|
||||||
|
this._current_follow_ease_progress = to;
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
}).bind(this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// follow_ease transitions this from a rotated display (0.0) to a centered/focused display (1.0)
|
||||||
_update_display_position_uniforms() {
|
_update_display_position_uniforms() {
|
||||||
// this is in NWU coordinates
|
// this is in NWU coordinates
|
||||||
const monitorPlacement = this.monitor_placements[this.monitor_index];
|
const monitorPlacement = this.monitor_placements[this.monitor_index];
|
||||||
|
|
@ -225,12 +307,24 @@ export const VirtualDisplayEffect = GObject.registerClass({
|
||||||
const noRotationVector = monitorPlacement.topLeftNoRotate.map((coord, index) => coord - distanceDelta[index]);
|
const noRotationVector = monitorPlacement.topLeftNoRotate.map((coord, index) => coord - distanceDelta[index]);
|
||||||
|
|
||||||
// convert to CoGL's east-down-south coordinates and apply display distance
|
// convert to CoGL's east-down-south coordinates and apply display distance
|
||||||
this.set_uniform_float(this.get_uniform_location("u_display_position"), 3,
|
const inverse_follow_ease = 1.0 - this._current_follow_ease_progress;
|
||||||
[-noRotationVector[1], -noRotationVector[2], -noRotationVector[0]]);
|
if (this._current_follow_ease_progress === 0.0) {
|
||||||
|
this.set_uniform_float(this.get_uniform_location("u_display_position"), 3,
|
||||||
|
[-noRotationVector[1], -noRotationVector[2], -noRotationVector[0]]);
|
||||||
|
} else {
|
||||||
|
const focusDistanceNorth = monitorPlacement.centerOrigin[0] * inverseAppliedDistance;
|
||||||
|
const centerOriginVector = {...monitorPlacement.centerOrigin};
|
||||||
|
centerOriginVector[0] -= focusDistanceNorth;
|
||||||
|
|
||||||
|
// slerp from the rotated display to the centered display
|
||||||
|
const followVector = noRotationVector.map((coord, index) => coord * inverse_follow_ease + centerOriginVector[index] * this._current_follow_ease_progress);
|
||||||
|
this.set_uniform_float(this.get_uniform_location("u_display_position"), 3,
|
||||||
|
[-followVector[1], -followVector[2], -followVector[0]]);
|
||||||
|
}
|
||||||
|
|
||||||
const rotation_radians = this.monitor_placements[this.monitor_index].rotationAngleRadians;
|
const rotation_radians = this.monitor_placements[this.monitor_index].rotationAngleRadians;
|
||||||
this.set_uniform_float(this.get_uniform_location("u_rotation_x_radians"), 1, [rotation_radians.x]);
|
this.set_uniform_float(this.get_uniform_location("u_rotation_x_radians"), 1, [rotation_radians.x * inverse_follow_ease]);
|
||||||
this.set_uniform_float(this.get_uniform_location("u_rotation_y_radians"), 1, [rotation_radians.y]);
|
this.set_uniform_float(this.get_uniform_location("u_rotation_y_radians"), 1, [rotation_radians.y * inverse_follow_ease]);
|
||||||
}
|
}
|
||||||
|
|
||||||
_handle_banner_update() {
|
_handle_banner_update() {
|
||||||
|
|
@ -350,7 +444,7 @@ export const VirtualDisplayEffect = GObject.registerClass({
|
||||||
vec3 velocity_t0 = rateOfChange(rotated_vector_t0, rotated_vector_t1, delta_time_t0);
|
vec3 velocity_t0 = rateOfChange(rotated_vector_t0, rotated_vector_t1, delta_time_t0);
|
||||||
|
|
||||||
// compute the capped look ahead with scanline adjustments
|
// compute the capped look ahead with scanline adjustments
|
||||||
float look_ahead_scanline_ms = vectorToScanline(u_fov_vertical_radians, rotated_vector_t0) * u_look_ahead_cfg[2];
|
float look_ahead_scanline_ms = u_look_ahead_ms == 0.0 ? 0.0 : vectorToScanline(u_fov_vertical_radians, rotated_vector_t0) * u_look_ahead_cfg[2];
|
||||||
float effective_look_ahead_ms = min(min(u_look_ahead_ms, look_ahead_ms_cap), u_look_ahead_cfg[3]) + look_ahead_scanline_ms;
|
float effective_look_ahead_ms = min(min(u_look_ahead_ms, look_ahead_ms_cap), u_look_ahead_cfg[3]) + look_ahead_scanline_ms;
|
||||||
|
|
||||||
vec3 look_ahead_vector = applyLookAhead(rotated_vector_t0, velocity_t0, effective_look_ahead_ms);
|
vec3 look_ahead_vector = applyLookAhead(rotated_vector_t0, velocity_t0, effective_look_ahead_ms);
|
||||||
|
|
@ -402,8 +496,20 @@ export const VirtualDisplayEffect = GObject.registerClass({
|
||||||
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, Globals.data_stream.device_data.lookAheadCfg, this.look_ahead_override)]);
|
let lookAheadSet = false;
|
||||||
this.set_uniform_matrix(this.get_uniform_location("u_imu_data"), false, 4, this.imu_snapshots.imu_data);
|
if (!this._use_smooth_follow_origin && (this._is_focused() || this._current_follow_ease_progress > 0.0 || !this.smooth_follow_enabled)) {
|
||||||
|
if (this._current_follow_ease_progress > 0.0 && this._current_follow_ease_progress < 1.0) {
|
||||||
|
// don't apply look-ahead while the display is slerping
|
||||||
|
this.set_uniform_float(this.get_uniform_location('u_look_ahead_ms'), 1, [0.0]);
|
||||||
|
lookAheadSet = true;
|
||||||
|
}
|
||||||
|
this.set_uniform_matrix(this.get_uniform_location("u_imu_data"), false, 4, this.imu_snapshots.imu_data);
|
||||||
|
} else {
|
||||||
|
this.set_uniform_matrix(this.get_uniform_location("u_imu_data"), false, 4, this.imu_snapshots.smooth_follow_origin);
|
||||||
|
}
|
||||||
|
if (!lookAheadSet) {
|
||||||
|
this.set_uniform_float(this.get_uniform_location('u_look_ahead_ms'), 1, [lookAheadMS(this.imu_snapshots.timestamp_ms, Globals.data_stream.device_data.lookAheadCfg, this.look_ahead_override)]);
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.disable_anti_aliasing) {
|
if (!this.disable_anti_aliasing) {
|
||||||
// improves sampling quality for smooth text and edges
|
// improves sampling quality for smooth text and edges
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import Mtk from 'gi://Mtk';
|
||||||
import Shell from 'gi://Shell';
|
import Shell from 'gi://Shell';
|
||||||
import St from 'gi://St';
|
import St from 'gi://St';
|
||||||
|
|
||||||
import { VirtualDisplayEffect } from './virtualdisplayeffect.js';
|
import { VirtualDisplayEffect, SMOOTH_FOLLOW_SLERP_TIMELINE_MS } from './virtualdisplayeffect.js';
|
||||||
import { degreeToRadian, diagonalToCrossFOVs } from './math.js';
|
import { degreeToRadian, diagonalToCrossFOVs } from './math.js';
|
||||||
|
|
||||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||||
|
|
@ -31,7 +31,7 @@ function applyQuaternionToVector(vector, quaternion) {
|
||||||
const FOCUS_THRESHOLD = 0.95 / 2.0;
|
const FOCUS_THRESHOLD = 0.95 / 2.0;
|
||||||
|
|
||||||
// if we leave the monitor with some margin, unfocus even if no other monitor is in focus
|
// if we leave the monitor with some margin, unfocus even if no other monitor is in focus
|
||||||
const UNFOCUS_THRESHOLD = 1.1 / 2.0;
|
const UNFOCUS_THRESHOLD = 1.2 / 2.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the vector in the array that's closest to the quaternion rotation
|
* Find the vector in the array that's closest to the quaternion rotation
|
||||||
|
|
@ -40,11 +40,12 @@ const UNFOCUS_THRESHOLD = 1.1 / 2.0;
|
||||||
* @param {number[][]} monitorVectors - Array of monitor vectors [x, y, z] to search from
|
* @param {number[][]} monitorVectors - Array of monitor vectors [x, y, z] to search from
|
||||||
* @param {number} currentFocusedIndex - Index of the currently focused monitor
|
* @param {number} currentFocusedIndex - Index of the currently focused monitor
|
||||||
* @param {number} focusedMonitorDistance - Distance to the focused monitor, < 1.0 if zoomed in
|
* @param {number} focusedMonitorDistance - Distance to the focused monitor, < 1.0 if zoomed in
|
||||||
|
* @param {boolean} smoothFollowEnabled - If true, always keep the current monitor in focus or choose the closest
|
||||||
* @param {Object} fovDetails - Contains reference widthPixels, heightPixels, horizontal and vertical radians, and pixel distance to the center of the screen
|
* @param {Object} fovDetails - Contains reference widthPixels, heightPixels, horizontal and vertical radians, and pixel distance to the center of the screen
|
||||||
* @param {Object[]} monitorsDetails - Contains x, y, width, height (coordinates from top-left) for each monitor
|
* @param {Object[]} monitorsDetails - Contains x, y, width, height (coordinates from top-left) for each monitor
|
||||||
* @returns {number} Index of the closest vector, if it surpasses the previous closest index by a certain margin, otherwise the previous index
|
* @returns {number} Index of the closest vector, if it surpasses the previous closest index by a certain margin, otherwise the previous index
|
||||||
*/
|
*/
|
||||||
function findFocusedMonitor(quaternion, monitorVectors, currentFocusedIndex, focusedMonitorDistance, fovDetails, monitorsDetails) {
|
function findFocusedMonitor(quaternion, monitorVectors, currentFocusedIndex, focusedMonitorDistance, smoothFollowEnabled, fovDetails, monitorsDetails) {
|
||||||
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);
|
||||||
|
|
||||||
|
|
@ -85,6 +86,7 @@ function findFocusedMonitor(quaternion, monitorVectors, currentFocusedIndex, foc
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO - this assumes the display is facing towards us, need to account for looking in the "flat" direction
|
||||||
const distanceFromCenterPixels = fovDetails.completeScreenDistancePixels * Math.tan(distance);
|
const distanceFromCenterPixels = fovDetails.completeScreenDistancePixels * Math.tan(distance);
|
||||||
const distanceFromCenterSizeRatio = distanceFromCenterPixels / monitor.width;
|
const distanceFromCenterSizeRatio = distanceFromCenterPixels / monitor.width;
|
||||||
|
|
||||||
|
|
@ -98,9 +100,9 @@ function findFocusedMonitor(quaternion, monitorVectors, currentFocusedIndex, foc
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const keepCurrent = currentFocusedIndex !== -1 && currentFocusedDistance < UNFOCUS_THRESHOLD;
|
const keepCurrent = currentFocusedIndex !== -1 && (smoothFollowEnabled || currentFocusedDistance < UNFOCUS_THRESHOLD);
|
||||||
if (!keepCurrent) {
|
if (!keepCurrent) {
|
||||||
if (closestDistance < FOCUS_THRESHOLD) return closestIndex;
|
if (smoothFollowEnabled || closestDistance < FOCUS_THRESHOLD) return closestIndex;
|
||||||
|
|
||||||
// neither the current nor the closest will take focus, unfocus all displays
|
// neither the current nor the closest will take focus, unfocus all displays
|
||||||
return -1;
|
return -1;
|
||||||
|
|
@ -200,14 +202,19 @@ function monitorsToPlacements(fovDetails, monitorDetailsList, monitorWrappingSch
|
||||||
const monitorWrapDetails = monitorWrap(cachedMonitorRadians, sideEdgeRadius, monitorSpacingPixels, monitorDetails.x, monitorDetails.width);
|
const monitorWrapDetails = monitorWrap(cachedMonitorRadians, sideEdgeRadius, monitorSpacingPixels, monitorDetails.x, monitorDetails.width);
|
||||||
const monitorCenterRadius = Math.sqrt(Math.pow(sideEdgeRadius, 2) - Math.pow(monitorDetails.width / 2, 2));
|
const monitorCenterRadius = Math.sqrt(Math.pow(sideEdgeRadius, 2) - Math.pow(monitorDetails.width / 2, 2));
|
||||||
const upTopPixels = monitorDetails.y + (monitorDetails.y / fovDetails.heightPixels) * monitorSpacingPixels;
|
const upTopPixels = monitorDetails.y + (monitorDetails.y / fovDetails.heightPixels) * monitorSpacingPixels;
|
||||||
const upCenterPixels = upTopPixels + monitorDetails.height / 2 - fovDetails.heightPixels / 2;
|
|
||||||
|
// how to place the monitors at the origin (0, 0)
|
||||||
|
const westCenterOriginPixels = (monitorDetails.width - fovDetails.widthPixels) / 2;
|
||||||
|
const upCenterOriginPixels = (monitorDetails.height - fovDetails.heightPixels) / 2;
|
||||||
|
|
||||||
|
const upCenterPixels = upTopPixels + upCenterOriginPixels;
|
||||||
|
|
||||||
monitorPlacements.push({
|
monitorPlacements.push({
|
||||||
topLeftNoRotate: [
|
topLeftNoRotate: [
|
||||||
monitorCenterRadius,
|
monitorCenterRadius,
|
||||||
|
|
||||||
// west stays aligned with (0, 0), will apply rotationAngleRadians value during rendering
|
// west stays aligned with the origin, will apply rotationAngleRadians value during rendering
|
||||||
-(monitorDetails.width - fovDetails.widthPixels) / 2,
|
-westCenterOriginPixels,
|
||||||
|
|
||||||
// up is flat when wrapping horizontally, apply it here as a constant, not touched by rendering
|
// up is flat when wrapping horizontally, apply it here as a constant, not touched by rendering
|
||||||
-upTopPixels
|
-upTopPixels
|
||||||
|
|
@ -221,7 +228,12 @@ function monitorsToPlacements(fovDetails, monitorDetailsList, monitorWrappingSch
|
||||||
// up is flat when wrapping horizontally
|
// up is flat when wrapping horizontally
|
||||||
-upCenterPixels
|
-upCenterPixels
|
||||||
],
|
],
|
||||||
center: [
|
centerOrigin: [
|
||||||
|
monitorCenterRadius,
|
||||||
|
-westCenterOriginPixels,
|
||||||
|
upCenterOriginPixels
|
||||||
|
],
|
||||||
|
centerLook: [
|
||||||
// north is adjacent where radius is the hypotenuse, using monitorWrapDetails.center as the radians
|
// north is adjacent where radius is the hypotenuse, using monitorWrapDetails.center as the radians
|
||||||
monitorCenterRadius * Math.cos(monitorWrapDetails.center),
|
monitorCenterRadius * Math.cos(monitorWrapDetails.center),
|
||||||
|
|
||||||
|
|
@ -249,7 +261,12 @@ function monitorsToPlacements(fovDetails, monitorDetailsList, monitorWrappingSch
|
||||||
const monitorWrapDetails = monitorWrap(cachedMonitorRadians, topEdgeRadius, monitorSpacingPixels, monitorDetails.y, monitorDetails.height);
|
const monitorWrapDetails = monitorWrap(cachedMonitorRadians, topEdgeRadius, monitorSpacingPixels, monitorDetails.y, monitorDetails.height);
|
||||||
const monitorCenterRadius = Math.sqrt(Math.pow(topEdgeRadius, 2) - Math.pow(monitorDetails.height / 2, 2));
|
const monitorCenterRadius = Math.sqrt(Math.pow(topEdgeRadius, 2) - Math.pow(monitorDetails.height / 2, 2));
|
||||||
const westPixels = monitorDetails.x + (monitorDetails.x / fovDetails.widthPixels) * monitorSpacingPixels;
|
const westPixels = monitorDetails.x + (monitorDetails.x / fovDetails.widthPixels) * monitorSpacingPixels;
|
||||||
const westCenterPixels = westPixels + monitorDetails.width / 2 - fovDetails.widthPixels / 2;
|
|
||||||
|
// how to place the monitors at the origin (0, 0)
|
||||||
|
const westCenterOriginPixels = (monitorDetails.width - fovDetails.widthPixels) / 2;
|
||||||
|
const upCenterOriginPixels = (monitorDetails.height - fovDetails.heightPixels) / 2;
|
||||||
|
|
||||||
|
const westCenterPixels = westPixels + westCenterOriginPixels;
|
||||||
|
|
||||||
monitorPlacements.push({
|
monitorPlacements.push({
|
||||||
topLeftNoRotate: [
|
topLeftNoRotate: [
|
||||||
|
|
@ -258,8 +275,8 @@ function monitorsToPlacements(fovDetails, monitorDetailsList, monitorWrappingSch
|
||||||
// west is flat when wrapping vertically, apply it here as a constant, not touched by rendering
|
// west is flat when wrapping vertically, apply it here as a constant, not touched by rendering
|
||||||
westPixels,
|
westPixels,
|
||||||
|
|
||||||
// up stays aligned with (0, 0), will apply rotationAngleRadians value during rendering
|
// up stays aligned with the origin, will apply rotationAngleRadians value during rendering
|
||||||
(monitorDetails.height - fovDetails.heightPixels) / 2
|
upCenterOriginPixels
|
||||||
],
|
],
|
||||||
centerNoRotate: [
|
centerNoRotate: [
|
||||||
monitorCenterRadius,
|
monitorCenterRadius,
|
||||||
|
|
@ -270,7 +287,12 @@ function monitorsToPlacements(fovDetails, monitorDetailsList, monitorWrappingSch
|
||||||
// west centered about the FOV center
|
// west centered about the FOV center
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
center: [
|
centerOrigin: [
|
||||||
|
monitorCenterRadius,
|
||||||
|
-westCenterOriginPixels,
|
||||||
|
upCenterOriginPixels
|
||||||
|
],
|
||||||
|
centerLook: [
|
||||||
// north is adjacent where radius is the hypotenuse, using monitorWrapDetails.center as the radians
|
// north is adjacent where radius is the hypotenuse, using monitorWrapDetails.center as the radians
|
||||||
monitorCenterRadius * Math.cos(monitorWrapDetails.center),
|
monitorCenterRadius * Math.cos(monitorWrapDetails.center),
|
||||||
|
|
||||||
|
|
@ -293,8 +315,14 @@ function monitorsToPlacements(fovDetails, monitorDetailsList, monitorWrappingSch
|
||||||
monitorDetailsList.forEach(monitorDetails => {
|
monitorDetailsList.forEach(monitorDetails => {
|
||||||
const upPixels = monitorDetails.y + (monitorDetails.y / fovDetails.heightPixels) * monitorSpacingPixels;
|
const upPixels = monitorDetails.y + (monitorDetails.y / fovDetails.heightPixels) * monitorSpacingPixels;
|
||||||
const westPixels = monitorDetails.x + (monitorDetails.x / fovDetails.widthPixels) * monitorSpacingPixels;
|
const westPixels = monitorDetails.x + (monitorDetails.x / fovDetails.widthPixels) * monitorSpacingPixels;
|
||||||
const westCenterPixels = westPixels + monitorDetails.width / 2 - fovDetails.widthPixels / 2;
|
|
||||||
const upCenterPixels = upPixels + monitorDetails.height / 2 - fovDetails.heightPixels / 2;
|
// how to place the monitors at the origin (0, 0)
|
||||||
|
const westCenterOriginPixels = (monitorDetails.width - fovDetails.widthPixels) / 2;
|
||||||
|
const upCenterOriginPixels = (monitorDetails.height - fovDetails.heightPixels) / 2;
|
||||||
|
|
||||||
|
const westCenterPixels = westPixels + westCenterOriginPixels;
|
||||||
|
const upCenterPixels = upPixels + upCenterOriginPixels;
|
||||||
|
|
||||||
monitorPlacements.push({
|
monitorPlacements.push({
|
||||||
topLeftNoRotate: [
|
topLeftNoRotate: [
|
||||||
fovDetails.completeScreenDistancePixels,
|
fovDetails.completeScreenDistancePixels,
|
||||||
|
|
@ -306,7 +334,12 @@ function monitorsToPlacements(fovDetails, monitorDetailsList, monitorWrappingSch
|
||||||
westCenterPixels,
|
westCenterPixels,
|
||||||
-upCenterPixels
|
-upCenterPixels
|
||||||
],
|
],
|
||||||
center: [
|
centerOrigin: [
|
||||||
|
fovDetails.completeScreenDistancePixels,
|
||||||
|
-westCenterOriginPixels,
|
||||||
|
upCenterOriginPixels
|
||||||
|
],
|
||||||
|
centerLook: [
|
||||||
fovDetails.completeScreenDistancePixels,
|
fovDetails.completeScreenDistancePixels,
|
||||||
-westCenterPixels,
|
-westCenterPixels,
|
||||||
-upCenterPixels
|
-upCenterPixels
|
||||||
|
|
@ -384,6 +417,20 @@ export const VirtualDisplaysActor = GObject.registerClass({
|
||||||
'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
|
||||||
),
|
),
|
||||||
|
'smooth-follow-enabled': GObject.ParamSpec.boolean(
|
||||||
|
'smooth-follow-enabled',
|
||||||
|
'Smooth follow enabled',
|
||||||
|
'Whether smooth follow is enabled',
|
||||||
|
GObject.ParamFlags.READWRITE,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
'smooth-follow-toggle-epoch-ms': GObject.ParamSpec.uint64(
|
||||||
|
'smooth-follow-toggle-epoch-ms',
|
||||||
|
'Smooth follow toggle epoch time',
|
||||||
|
'ms since epoch when smooth follow was toggled',
|
||||||
|
GObject.ParamFlags.READWRITE,
|
||||||
|
0, Number.MAX_SAFE_INTEGER, 0
|
||||||
|
),
|
||||||
'show-banner': GObject.ParamSpec.boolean(
|
'show-banner': GObject.ParamSpec.boolean(
|
||||||
'show-banner',
|
'show-banner',
|
||||||
'Show banner',
|
'Show banner',
|
||||||
|
|
@ -541,6 +588,7 @@ export const VirtualDisplaysActor = GObject.registerClass({
|
||||||
notifyToFunction('show-banner', this._handle_banner_update);
|
notifyToFunction('show-banner', this._handle_banner_update);
|
||||||
notifyToFunction('custom-banner-enabled', this._handle_banner_update);
|
notifyToFunction('custom-banner-enabled', this._handle_banner_update);
|
||||||
notifyToFunction('framerate-cap', this._handle_frame_rate_cap_change);
|
notifyToFunction('framerate-cap', this._handle_frame_rate_cap_change);
|
||||||
|
notifyToFunction('smooth-follow-enabled', this._handle_smooth_follow_enabled_change);
|
||||||
this._handle_display_distance_properties_change();
|
this._handle_display_distance_properties_change();
|
||||||
this._handle_frame_rate_cap_change();
|
this._handle_frame_rate_cap_change();
|
||||||
|
|
||||||
|
|
@ -612,6 +660,8 @@ export const VirtualDisplaysActor = GObject.registerClass({
|
||||||
[
|
[
|
||||||
'monitor-placements',
|
'monitor-placements',
|
||||||
'imu-snapshots',
|
'imu-snapshots',
|
||||||
|
'smooth-follow-enabled',
|
||||||
|
'smooth-follow-toggle-epoch-ms',
|
||||||
'focused-monitor-index',
|
'focused-monitor-index',
|
||||||
'lens-vector',
|
'lens-vector',
|
||||||
'look-ahead-override',
|
'look-ahead-override',
|
||||||
|
|
@ -650,12 +700,19 @@ export const VirtualDisplaysActor = GObject.registerClass({
|
||||||
|
|
||||||
if (this.show_banner) {
|
if (this.show_banner) {
|
||||||
this.focused_monitor_index = -1;
|
this.focused_monitor_index = -1;
|
||||||
} else if (this.imu_snapshots) {
|
} else if (this.imu_snapshots && (!this._smooth_follow_slerping || this.focused_monitor_index === -1)) {
|
||||||
|
// if smooth follow is enabled, use the origin IMU data to inform the initial focused monitor
|
||||||
|
// since it reflects where the user is looking in relation to the original monitor positions
|
||||||
|
const currentPoseQuat = this.smooth_follow_enabled ?
|
||||||
|
this.imu_snapshots.smooth_follow_origin.splice(0, 4) :
|
||||||
|
this.imu_snapshots.imu_data.splice(0, 4);
|
||||||
|
|
||||||
const focusedMonitorIndex = findFocusedMonitor(
|
const focusedMonitorIndex = findFocusedMonitor(
|
||||||
this.imu_snapshots.imu_data.splice(0, 4),
|
currentPoseQuat,
|
||||||
this._monitorsAsNormalizedVectors,
|
this._monitorsAsNormalizedVectors,
|
||||||
this.focused_monitor_index,
|
this.focused_monitor_index,
|
||||||
this.display_distance / this._display_distance_default(),
|
this.display_distance / this._display_distance_default(),
|
||||||
|
this.smooth_follow_enabled,
|
||||||
this._fov_details(),
|
this._fov_details(),
|
||||||
this._sorted_monitors
|
this._sorted_monitors
|
||||||
);
|
);
|
||||||
|
|
@ -773,7 +830,7 @@ export const VirtualDisplaysActor = GObject.registerClass({
|
||||||
|
|
||||||
// normalize the center vectors
|
// normalize the center vectors
|
||||||
this._monitorsAsNormalizedVectors = this.monitor_placements.map(monitorVectors => {
|
this._monitorsAsNormalizedVectors = this.monitor_placements.map(monitorVectors => {
|
||||||
const vector = monitorVectors.center;
|
const vector = monitorVectors.centerLook;
|
||||||
const length = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]);
|
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];
|
return [vector[0] / length, vector[1] / length, vector[2] / length];
|
||||||
});
|
});
|
||||||
|
|
@ -801,6 +858,17 @@ export const VirtualDisplaysActor = GObject.registerClass({
|
||||||
this._cap_frametime_ms = this.framerate_cap === 0 ? 0.0 : Math.floor(1000 * frametime_margin / this.framerate_cap);
|
this._cap_frametime_ms = this.framerate_cap === 0 ? 0.0 : Math.floor(1000 * frametime_margin / this.framerate_cap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handle_smooth_follow_enabled_change() {
|
||||||
|
if (this._smooth_follow_timeout_id !== undefined) GLib.source_remove(this._smooth_follow_timeout_id);
|
||||||
|
|
||||||
|
this._smooth_follow_slerping = true;
|
||||||
|
this._smooth_follow_timeout_id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, SMOOTH_FOLLOW_SLERP_TIMELINE_MS, (() => {
|
||||||
|
this._smooth_follow_slerping = false;
|
||||||
|
this._smooth_follow_timeout_id = undefined;
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
}).bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
_change_distance() {
|
_change_distance() {
|
||||||
this.display_distance = this._is_display_distance_at_end ?
|
this.display_distance = this._is_display_distance_at_end ?
|
||||||
this.toggle_display_distance_start : this.toggle_display_distance_end;
|
this.toggle_display_distance_start : this.toggle_display_distance_end;
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit a274fc23c385b3f039fe6baba0a138fe31c7ad35
|
Subproject commit d6b02234553afe9bea2df9e70c5376bd0526a8a9
|
||||||
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-02 10:59-0800\n"
|
"POT-Creation-Date: 2025-03-04 15:46-0800\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
|
@ -215,7 +215,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:66
|
#: src/gtk/connected-device.ui:66
|
||||||
msgid "Zoom on focus mode"
|
msgid "Zoom on focus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:67
|
#: src/gtk/connected-device.ui:67
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-02 10:59-0800\n"
|
"POT-Creation-Date: 2025-03-04 15:46-0800\n"
|
||||||
"PO-Revision-Date: 2024-08-02 20:54-0700\n"
|
"PO-Revision-Date: 2024-08-02 20:54-0700\n"
|
||||||
"Last-Translator: <wayne@xronlinux.com>\n"
|
"Last-Translator: <wayne@xronlinux.com>\n"
|
||||||
"Language-Team: German <translation-team-de@lists.sourceforge.net>\n"
|
"Language-Team: German <translation-team-de@lists.sourceforge.net>\n"
|
||||||
|
|
@ -221,7 +221,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:66
|
#: src/gtk/connected-device.ui:66
|
||||||
msgid "Zoom on focus mode"
|
msgid "Zoom on focus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:67
|
#: src/gtk/connected-device.ui:67
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-02 10:59-0800\n"
|
"POT-Creation-Date: 2025-03-04 15:46-0800\n"
|
||||||
"PO-Revision-Date: 2024-08-02 20:55-0700\n"
|
"PO-Revision-Date: 2024-08-02 20:55-0700\n"
|
||||||
"Last-Translator: <wayne@xronlinux.com>\n"
|
"Last-Translator: <wayne@xronlinux.com>\n"
|
||||||
"Language-Team: Spanish <es@tp.org.es>\n"
|
"Language-Team: Spanish <es@tp.org.es>\n"
|
||||||
|
|
@ -220,7 +220,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:66
|
#: src/gtk/connected-device.ui:66
|
||||||
msgid "Zoom on focus mode"
|
msgid "Zoom on focus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:67
|
#: src/gtk/connected-device.ui:67
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-02 10:59-0800\n"
|
"POT-Creation-Date: 2025-03-04 15:46-0800\n"
|
||||||
"PO-Revision-Date: 2024-08-02 20:54-0700\n"
|
"PO-Revision-Date: 2024-08-02 20:54-0700\n"
|
||||||
"Last-Translator: <wayne@xronlinux.com>\n"
|
"Last-Translator: <wayne@xronlinux.com>\n"
|
||||||
"Language-Team: French <traduc@traduc.org>\n"
|
"Language-Team: French <traduc@traduc.org>\n"
|
||||||
|
|
@ -223,7 +223,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:66
|
#: src/gtk/connected-device.ui:66
|
||||||
msgid "Zoom on focus mode"
|
msgid "Zoom on focus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:67
|
#: src/gtk/connected-device.ui:67
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-02 10:59-0800\n"
|
"POT-Creation-Date: 2025-03-04 15:46-0800\n"
|
||||||
"PO-Revision-Date: 2024-08-02 21:14-0700\n"
|
"PO-Revision-Date: 2024-08-02 21:14-0700\n"
|
||||||
"Last-Translator: <wayne@xronlinux.com>\n"
|
"Last-Translator: <wayne@xronlinux.com>\n"
|
||||||
"Language-Team: Italian <tp@lists.linux.it>\n"
|
"Language-Team: Italian <tp@lists.linux.it>\n"
|
||||||
|
|
@ -221,7 +221,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:66
|
#: src/gtk/connected-device.ui:66
|
||||||
msgid "Zoom on focus mode"
|
msgid "Zoom on focus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:67
|
#: src/gtk/connected-device.ui:67
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-02 10:59-0800\n"
|
"POT-Creation-Date: 2025-03-04 15:46-0800\n"
|
||||||
"PO-Revision-Date: 2024-08-02 20:55-0700\n"
|
"PO-Revision-Date: 2024-08-02 20:55-0700\n"
|
||||||
"Last-Translator: <wayne@xronlinux.com>\n"
|
"Last-Translator: <wayne@xronlinux.com>\n"
|
||||||
"Language-Team: Japanese <translation-team-ja@lists.sourceforge.net>\n"
|
"Language-Team: Japanese <translation-team-ja@lists.sourceforge.net>\n"
|
||||||
|
|
@ -223,7 +223,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:66
|
#: src/gtk/connected-device.ui:66
|
||||||
msgid "Zoom on focus mode"
|
msgid "Zoom on focus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:67
|
#: src/gtk/connected-device.ui:67
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-02 10:59-0800\n"
|
"POT-Creation-Date: 2025-03-04 15:46-0800\n"
|
||||||
"PO-Revision-Date: 2024-08-16 10:26-0700\n"
|
"PO-Revision-Date: 2024-08-16 10:26-0700\n"
|
||||||
"Last-Translator: <wayne@xronlinux.com>\n"
|
"Last-Translator: <wayne@xronlinux.com>\n"
|
||||||
"Language-Team: Polish <translation-team-pl@lists.sourceforge.net>\n"
|
"Language-Team: Polish <translation-team-pl@lists.sourceforge.net>\n"
|
||||||
|
|
@ -216,7 +216,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:66
|
#: src/gtk/connected-device.ui:66
|
||||||
msgid "Zoom on focus mode"
|
msgid "Zoom on focus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:67
|
#: src/gtk/connected-device.ui:67
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-02 10:59-0800\n"
|
"POT-Creation-Date: 2025-03-04 15:46-0800\n"
|
||||||
"PO-Revision-Date: 2024-08-19 09:39-0700\n"
|
"PO-Revision-Date: 2024-08-19 09:39-0700\n"
|
||||||
"Last-Translator: <wayne@xronlinux.com>\n"
|
"Last-Translator: <wayne@xronlinux.com>\n"
|
||||||
"Language-Team: Brazilian Portuguese <ldpbr-"
|
"Language-Team: Brazilian Portuguese <ldpbr-"
|
||||||
|
|
@ -222,7 +222,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:66
|
#: src/gtk/connected-device.ui:66
|
||||||
msgid "Zoom on focus mode"
|
msgid "Zoom on focus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:67
|
#: src/gtk/connected-device.ui:67
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-02 10:59-0800\n"
|
"POT-Creation-Date: 2025-03-04 15:46-0800\n"
|
||||||
"PO-Revision-Date: 2024-08-17 09:39-0700\n"
|
"PO-Revision-Date: 2024-08-17 09:39-0700\n"
|
||||||
"Last-Translator: <wayne@xronlinux.com>\n"
|
"Last-Translator: <wayne@xronlinux.com>\n"
|
||||||
"Language-Team: Russian <gnu@d07.ru>\n"
|
"Language-Team: Russian <gnu@d07.ru>\n"
|
||||||
|
|
@ -221,7 +221,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:66
|
#: src/gtk/connected-device.ui:66
|
||||||
msgid "Zoom on focus mode"
|
msgid "Zoom on focus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:67
|
#: src/gtk/connected-device.ui:67
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-02 10:59-0800\n"
|
"POT-Creation-Date: 2025-03-04 15:46-0800\n"
|
||||||
"PO-Revision-Date: 2024-08-16 10:31-0700\n"
|
"PO-Revision-Date: 2024-08-16 10:31-0700\n"
|
||||||
"Last-Translator: <wayne@xronlinux.com>\n"
|
"Last-Translator: <wayne@xronlinux.com>\n"
|
||||||
"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
|
"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
|
||||||
|
|
@ -221,7 +221,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:66
|
#: src/gtk/connected-device.ui:66
|
||||||
msgid "Zoom on focus mode"
|
msgid "Zoom on focus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:67
|
#: src/gtk/connected-device.ui:67
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-02 10:59-0800\n"
|
"POT-Creation-Date: 2025-03-04 15:46-0800\n"
|
||||||
"PO-Revision-Date: 2024-08-17 10:08-0700\n"
|
"PO-Revision-Date: 2024-08-17 10:08-0700\n"
|
||||||
"Last-Translator: <wayne@xronlinux.com>\n"
|
"Last-Translator: <wayne@xronlinux.com>\n"
|
||||||
"Language-Team: Ukrainian <trans-uk@lists.fedoraproject.org>\n"
|
"Language-Team: Ukrainian <trans-uk@lists.fedoraproject.org>\n"
|
||||||
|
|
@ -220,7 +220,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:66
|
#: src/gtk/connected-device.ui:66
|
||||||
msgid "Zoom on focus mode"
|
msgid "Zoom on focus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:67
|
#: src/gtk/connected-device.ui:67
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-02 10:59-0800\n"
|
"POT-Creation-Date: 2025-03-04 15:46-0800\n"
|
||||||
"PO-Revision-Date: 2024-08-02 20:55-0700\n"
|
"PO-Revision-Date: 2024-08-02 20:55-0700\n"
|
||||||
"Last-Translator: <wayne@xronlinux.com>\n"
|
"Last-Translator: <wayne@xronlinux.com>\n"
|
||||||
"Language-Team: Chinese (simplified) <i18n-zh@googlegroups.com>\n"
|
"Language-Team: Chinese (simplified) <i18n-zh@googlegroups.com>\n"
|
||||||
|
|
@ -218,7 +218,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:66
|
#: src/gtk/connected-device.ui:66
|
||||||
msgid "Zoom on focus mode"
|
msgid "Zoom on focus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/gtk/connected-device.ui:67
|
#: src/gtk/connected-device.ui:67
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow">
|
||||||
<property name="title" translatable="yes"><!-- feature switch -->Zoom on focus mode</property>
|
<property name="title" translatable="yes"><!-- feature switch -->Zoom on focus</property>
|
||||||
<property name="subtitle" translatable="yes"><!--
|
<property name="subtitle" translatable="yes"><!--
|
||||||
-->Automatically move a display closer when you look at it.
|
-->Automatically move a display closer when you look at it.
|
||||||
<!-- -->Set your preferred focused and unfocused distances in the Adjustments section.
|
<!-- -->Set your preferred focused and unfocused distances in the Adjustments section.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue