diff --git a/.gitmodules b/.gitmodules index e7a2906..32df75b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,4 @@ [submodule "ui/modules/PyXRLinuxDriverIPC"] path = ui/modules/PyXRLinuxDriverIPC url = https://github.com/wheaney/PyXRLinuxDriverIPC.git + branch = v1.1 diff --git a/VERSION b/VERSION index a5ba932..359a5b9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.7 \ No newline at end of file +2.0.0 \ No newline at end of file diff --git a/gnome/bin/dev/use_local_extension.sh b/gnome/bin/dev/use_local_extension.sh index f959f13..12465b3 100755 --- a/gnome/bin/dev/use_local_extension.sh +++ b/gnome/bin/dev/use_local_extension.sh @@ -7,6 +7,7 @@ if [ -z "$XDG_DATA_HOME" ]; then XDG_DATA_HOME="$USER_HOME/.local/share" fi DATA_DIR="$XDG_DATA_HOME/breezy_gnome" +mkdir -p $DATA_DIR # if $XDG_DATA_HOME/gnome-shell/extensions/breezydesktop@xronlinux.com exists extension_path="$XDG_DATA_HOME/gnome-shell/extensions/breezydesktop@xronlinux.com" diff --git a/gnome/src/cursormanager.js b/gnome/src/cursormanager.js index 52d1341..b8470d1 100644 --- a/gnome/src/cursormanager.js +++ b/gnome/src/cursormanager.js @@ -4,11 +4,10 @@ import * as PointerWatcher from 'resource:///org/gnome/shell/ui/pointerWatcher.j import { MouseSpriteContent } from './cursor.js'; import Globals from './globals.js'; - // Taken from https://github.com/jkitching/soft-brightness-plus export class CursorManager { - constructor(overlay, refreshRate) { - this._overlay = overlay; + constructor(targetMonitors, refreshRate) { + this._targetMonitors = targetMonitors; this._refreshRate = refreshRate; // Set/destroyed by _enableCloningMouse/_disableCloningMouse @@ -53,7 +52,7 @@ export class CursorManager { // and will trigger _startCloningMouse when the cursor should be shown _enableCloningMouse() { Globals.logger.log_debug('CursorManager _enableCloningMouse'); - this._cursorTracker = Meta.CursorTracker.get_for_display(global.display); + this._cursorTracker = global.backend.get_cursor_tracker?.() ?? Meta.CursorTracker.get_for_display(global.display); this._mouseSprite = new Clutter.Actor({ request_mode: Clutter.RequestMode.CONTENT_SIZE }); this._mouseSprite.content = new MouseSpriteContent(); @@ -62,13 +61,17 @@ export class CursorManager { this._cursorRoot.add_child(this._mouseSprite); } + _backend() { + return global.stage.get_context?.().get_backend() ?? Clutter.get_default_backend(); + } + _hideSystemCursor() { this._systemCursorShown = false; this._cursorRoot.show(); if (!this._cursorUnfocusInhibited) { - Clutter.get_default_backend().get_default_seat().inhibit_unfocus(); + this._backend().get_default_seat().inhibit_unfocus(); this._cursorUnfocusInhibited = true; } @@ -111,20 +114,16 @@ export class CursorManager { // prereqs: setup in _enableCloningMouse _startCloningMouse() { Globals.logger.log_debug('CursorManager _startCloningMouse'); - this._overlay.mainActor().add_child(this._cursorRoot); this._updateMouseSprite(); this._cursorTracker.connectObject('cursor-changed', this._updateMouseSprite.bind(this), this); - Meta.disable_unredirect_for_display(global.display); + Meta.Compositor?.disable_unredirect?.() ?? Meta.disable_unredirect_for_display(global.display); // cap the refresh rate for performance reasons const interval = 1000.0 / Math.min(this._refreshRate, 60); this._cursorWatch = PointerWatcher.getPointerWatcher().addWatch(interval, this._updateMousePosition.bind(this)); this._updateMousePosition(); - - const [xMouse, yMouse] = global.get_pointer(); - if (this._overlay.isWithinBounds(xMouse, yMouse)) this._hideSystemCursor(); } // After this: @@ -142,9 +141,8 @@ export class CursorManager { if (this._cursorTracker) this._cursorTracker.disconnectObject(this); if (this._mouseSprite?.content?.texture) this._mouseSprite.content.texture = null; - Meta.enable_unredirect_for_display(global.display); + Meta.Compositor?.enable_unredirect?.() ?? Meta.enable_unredirect_for_display(global.display); - if (this._cursorRoot) this._overlay.mainActor().remove_child(this._cursorRoot); if (!this._systemCursorShown) this._showSystemCursor(); } @@ -154,7 +152,7 @@ export class CursorManager { if (this._cursorRoot) this._cursorRoot.hide(); if (this._cursorUnfocusInhibited) { - Clutter.get_default_backend().get_default_seat().uninhibit_unfocus(); + this._backend().get_default_seat().uninhibit_unfocus(); this._cursorUnfocusInhibited = false; } @@ -168,23 +166,56 @@ export class CursorManager { _updateMousePosition(...args) { const [xMouse, yMouse] = args.length ? args : global.get_pointer(); - const inBounds = this._overlay.isWithinBounds(xMouse, yMouse); - const [xRel, yRel] = this._overlay.getRelativePosition(xMouse, yMouse); + let onMonitorIndex; + let xRel; + let yRel; - if (xRel === this.xMouse && yRel === this.yMouse) - return; + const inBoundsCheck = (monitorObj, index) => { + const inBoundsCoordinates = this._getInBoundsCoordinates(xMouse, yMouse, monitorObj.monitor); + if (inBoundsCoordinates) { + onMonitorIndex = index; + xRel = inBoundsCoordinates.xRel; + yRel = inBoundsCoordinates.yRel; + return true; + } + return false; + } - if (inBounds) { + // check the previously in-bounds monitor first to avoid iterating over the whole list in the likely case that the cursor + // is still on the same monitor + if (this.onMonitorIndex === undefined || !inBoundsCheck(this._targetMonitors[this.onMonitorIndex], this.onMonitorIndex)) { + for (let i = 0; i < this._targetMonitors.length; i++) { + if (this.onMonitorIndex === i) continue; + if (inBoundsCheck(this._targetMonitors[i], i)) break; + } + } + + if (this.onMonitorIndex !== onMonitorIndex) { + try { + if (this.onMonitorIndex !== undefined) this._targetMonitors[this.onMonitorIndex].actor.remove_child(this._cursorRoot); + + this.onMonitorIndex = onMonitorIndex; + if (this.onMonitorIndex !== undefined) { + const actor = this._targetMonitors[this.onMonitorIndex].actor; + actor.add_child(this._cursorRoot); + actor.set_child_above_sibling(this._cursorRoot, null); + } + } catch (e) { + Globals.logger.log_debug(e); + } + } + + if (this.onMonitorIndex !== undefined) { if (this._systemCursorShown) this._hideSystemCursor(); this._cursorRoot.set_position(xRel, yRel); - } else if (!this._systemCursorShown && !inBounds) { + } else if (!this._systemCursorShown) { this._showSystemCursor(); } - this.xMouse = xRel; - this.yMouse = yRel; + this.xRel = xRel; + this.xRel = xRel; - const seat = Clutter.get_default_backend().get_default_seat(); + const seat = this._backend().get_default_seat(); if (this._cursorUnfocusInhibited && !seat.is_unfocus_inhibited()) { Globals.logger.log_debug('reinhibiting'); seat.inhibit_unfocus(); @@ -210,4 +241,17 @@ export class CursorManager { this._mouseSprite.hide(); } } + + _getInBoundsCoordinates(x, y, monitor) { + const xRel = x - monitor.x; + const yRel = y - monitor.y; + if (xRel >= 0 && xRel < monitor.width && yRel >= 0 && yRel < monitor.height) { + return { + xRel, + yRel, + } + } + + return null; + } } \ No newline at end of file diff --git a/gnome/src/devicedatastream.js b/gnome/src/devicedatastream.js new file mode 100644 index 0000000..aae3706 --- /dev/null +++ b/gnome/src/devicedatastream.js @@ -0,0 +1,316 @@ +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; + +import Globals from './globals.js'; +import { + dataViewEnd, + dataViewUint8, + dataViewBigUint, + dataViewUint32Array, + dataViewUint8Array, + dataViewFloat, + dataViewFloatArray, + BOOL_SIZE, + FLOAT_SIZE, + UINT_SIZE, + UINT8_SIZE +} from "./ipc.js"; +import { isValidKeepAlive, getEpochSec, toSec } from "./time.js"; + +const IPC_FILE_PATH = "/dev/shm/breezy_desktop_imu"; +const KEEPALIVE_REFRESH_INTERVAL_SEC = 1; + +// the driver should be using the same data layout version +const DATA_LAYOUT_VERSION = 4; + +// DataView info: [offset, size, count] +const VERSION = [0, UINT8_SIZE, 1]; +const ENABLED = [dataViewEnd(VERSION), BOOL_SIZE, 1]; +const LOOK_AHEAD_CFG = [dataViewEnd(ENABLED), FLOAT_SIZE, 4]; +const DISPLAY_RES = [dataViewEnd(LOOK_AHEAD_CFG), UINT_SIZE, 2]; +const DISPLAY_FOV = [dataViewEnd(DISPLAY_RES), FLOAT_SIZE, 1]; +const LENS_DISTANCE_RATIO = [dataViewEnd(DISPLAY_FOV), FLOAT_SIZE, 1]; +const SBS_ENABLED = [dataViewEnd(LENS_DISTANCE_RATIO), BOOL_SIZE, 1]; +const CUSTOM_BANNER_ENABLED = [dataViewEnd(SBS_ENABLED), BOOL_SIZE, 1]; +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_PARITY_BYTE = [dataViewEnd(IMU_QUAT_DATA), UINT8_SIZE, 1]; +const DATA_VIEW_LENGTH = dataViewEnd(IMU_PARITY_BYTE); + +function checkParityByte(dataView) { + const parityByte = dataViewUint8(dataView, IMU_PARITY_BYTE); + let parity = 0; + const epochUint8 = dataViewUint8Array(dataView, EPOCH_MS); + const imuDataUint8 = dataViewUint8Array(dataView, IMU_QUAT_DATA); + for (let i = 0; i < epochUint8.length; i++) { + parity ^= epochUint8[i]; + } + for (let i = 0; i < imuDataUint8.length; i++) { + parity ^= imuDataUint8[i]; + } + return parityByte === parity; +} + +const COUNTER_MAX = 300; +function nextDebugIMUQuaternion(counter) { + const angle = counter / COUNTER_MAX * 2 * Math.PI; + const yaw = 10 * Math.PI / 180 * Math.cos(angle); + const roll = 0; + const pitch = 10 * Math.PI / 180 * Math.sin(angle); + + const cy = Math.cos(yaw * 0.5); + const sy = Math.sin(yaw * 0.5); + const cp = Math.cos(pitch * 0.5); + const sp = Math.sin(pitch * 0.5); + const cr = Math.cos(roll * 0.5); + const sr = Math.sin(roll * 0.5); + + const w = cr * cp * cy + sr * sp * sy; + const x = sr * cp * cy - cr * sp * sy; + const y = cr * sp * cy + sr * cp * sy; + const z = cr * cp * sy - sr * sp * cy; + + return [x, y, z, w]; +} + +export const DeviceDataStream = GObject.registerClass({ + Properties: { + 'breezy-desktop-running': GObject.ParamSpec.boolean( + 'breezy-desktop-running', + 'Breezy Desktop running', + 'Whether Breezy Desktop mode is enabled in xr_driver and supported glasses are connected', + GObject.ParamFlags.READWRITE, + false + ), + 'widescreen-mode-state': GObject.ParamSpec.boolean( + 'widescreen-mode-state', + 'Widescreen mode state', + 'The state of widescreen mode from the perspective of the driver', + GObject.ParamFlags.READWRITE, + false + ), + 'imu-snapshots': GObject.ParamSpec.jsobject( + 'imu-snapshots', + 'IMU Snapshots', + 'Latest IMU quaternion snapshots and epoch timestamp for when it was collected', + 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', + 'Show banner', + 'Whether the banner should be displayed', + GObject.ParamFlags.READWRITE, + false + ), + 'custom-banner-enabled': GObject.ParamSpec.boolean( + 'custom-banner-enabled', + 'Custom banner enabled', + 'Whether the custom banner should be displayed', + GObject.ParamFlags.READWRITE, + false + ), + 'debug-no-device': GObject.ParamSpec.boolean( + 'debug-no-device', + 'Debug without device', + 'Debug mode that allows for testing with moving IMU values without a device connected', + GObject.ParamFlags.READWRITE, + false + ) + } +}, class DeviceDataStream extends GObject.Object { + constructor(params = {}) { + super(params); + + // this may be set true if debug_no_device is set, so it doesn't mean a device is actually connected + this.breezy_desktop_running = false; + + // use this property to set whether breezy is being operated by a real device + this.breezy_desktop_actually_running = false; + + this._ipc_file = Gio.file_new_for_path(IPC_FILE_PATH); + this._running = false; + this.device_data = null; + } + + start() { + this._running = true; + this._poll(); + } + + stop() { + this._running = false; + this.device_data = null; + this.imu_snapshots = null; + } + + // polling is just intended to keep breezy_desktop_running current, anything needing up-to-date imu data should + // trigger a refresh with the default flag + _poll() { + if (this._running) { + this.refresh_data(true); + setTimeout(this._poll.bind(this), 1000); + } + } + + // Refresh the data from the IPC file. if keepalive_only is true, we'll only check and update breezy_desktop_running if it + // hasn't been checked within KEEPALIVE_REFRESH_INTERVAL_SEC. + refresh_data(keepalive_only = false) { + if (!this.debug_no_device && this.was_debug_no_device) { + this.was_debug_no_device = false; + if (!this.breezy_desktop_actually_running) { + this.device_data = null; + this.breezy_desktop_running = false; + this.breezy_desktop_actually_running = false; + this.imu_snapshots = null; + } + } + + if (this._ipc_file.query_exists(null) && ( + !this.device_data?.imuData || + !keepalive_only || + getEpochSec() - toSec(this.device_data?.imuDateMs ?? 0) > KEEPALIVE_REFRESH_INTERVAL_SEC + )) { + let data = this._ipc_file.load_contents(null); + if (data[0]) { + let buffer = new Uint8Array(data[1]).buffer; + let dataView = new DataView(buffer); + if (dataView.byteLength === DATA_VIEW_LENGTH) { + let imuDateMs = dataViewBigUint(dataView, EPOCH_MS); + const displayFov = dataViewFloat(dataView, DISPLAY_FOV); + const validKeepAlive = isValidKeepAlive(toSec(imuDateMs)); + const validData = validKeepAlive && displayFov !== 0.0; + const version = dataViewUint8(dataView, VERSION); + const enabled = dataViewUint8(dataView, ENABLED) !== 0 && version === DATA_LAYOUT_VERSION && validData; + 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 customBannerEnabled = dataViewUint8(dataView, CUSTOM_BANNER_ENABLED) !== 0; + const sbsEnabled = dataViewUint8(dataView, SBS_ENABLED) !== 0; + + if (validKeepAlive && !validData) Globals.logger.log('[ERROR] Received invalid device data'); + + // trigger "notify::" events for properties we want to check on every cycle + if (enabled && this.widescreen_mode_state !== sbsEnabled) this.widescreen_mode_state = sbsEnabled; + if (this.show_banner !== imuResetState) this.show_banner = imuResetState; + if (this.custom_banner_enabled !== customBannerEnabled) this.custom_banner_enabled = customBannerEnabled; + + let success = keepalive_only; + if (validData) { + if (!this.device_data) { + this.device_data = { + version, + enabled, + imuResetState, + displayRes: dataViewUint32Array(dataView, DISPLAY_RES), + sbsEnabled, + displayFov, + lookAheadCfg: dataViewFloatArray(dataView, LOOK_AHEAD_CFG), + lensDistanceRatio: dataViewFloat(dataView, LENS_DISTANCE_RATIO) + }; + } else if (keepalive_only) { + this.device_data = { + ...this.device_data, + imuResetState, + enabled, + sbsEnabled + } + } + + 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; + while (!success && attempts < 2) { + if (dataView.byteLength === DATA_VIEW_LENGTH) { + if (checkParityByte(dataView)) { + + this.imu_snapshots = { + imu_data: imuData, + timestamp_ms: imuDateMs, + smooth_follow_origin: smoothFollowOrigin + }; + success = true; + } + } else if (dataView.byteLength !== 0) { + Globals.logger.log(`[ERROR] Invalid dataView.byteLength: ${dataView.byteLength} !== ${DATA_VIEW_LENGTH}`) + } + + if (!success && ++attempts < 2) { + data = this._ipc_file.load_contents(null); + if (data[0]) { + buffer = new Uint8Array(data[1]).buffer; + dataView = new DataView(buffer); + imuDateMs = dataViewBigUint(dataView, EPOCH_MS); + imuData = dataViewFloatArray(dataView, IMU_QUAT_DATA); + } + } + } + } + + this.breezy_desktop_actually_running = success && enabled && validData; + } else { + this.breezy_desktop_actually_running = false; + } + } else { + this.breezy_desktop_actually_running = false; + } + } + + if (this.debug_no_device && !this.breezy_desktop_actually_running) { + if (!this.device_data || !this.was_debug_no_device) { + this.device_data = { + version: 1.0, + enabled: true, + imuResetState: false, + displayRes: [1920.0, 1080.0], + sbsEnabled: false, + displayFov: 46.0, + lookAheadCfg: [0.0, 0.0, 0.0, 0.0], + lensDistanceRatio: 0.05 + } + } + this.was_debug_no_device = true; + + if (!keepalive_only) { + this._counter = ((this._counter ?? -1)+1)%COUNTER_MAX; + + const imuDataFirst = nextDebugIMUQuaternion(this._counter); + const imuData = [ + ...imuDataFirst, + ...imuDataFirst, + ...imuDataFirst, + 2.0, 1.0, 0.0, 0.0 + ] + this.imu_snapshots = { + imu_data: imuData, + timestamp_ms: Date.now(), + smooth_follow_origin: [0.0, 0.0, 0.0, 1.0] + }; + } + this.breezy_desktop_running = true; + } 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 + this.breezy_desktop_running = this.breezy_desktop_actually_running; + if (!this.breezy_desktop_running && keepalive_only) { + this.device_data = null; + this.imu_snapshots = null; + } + } + } +}); \ No newline at end of file diff --git a/gnome/src/extension.js b/gnome/src/extension.js index 08c4c5d..ea30261 100644 --- a/gnome/src/extension.js +++ b/gnome/src/extension.js @@ -1,34 +1,19 @@ -import Clutter from 'gi://Clutter' import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import Meta from 'gi://Meta'; import Shell from 'gi://Shell'; +import St from 'gi://St'; import { CursorManager } from './cursormanager.js'; +import { DeviceDataStream } from './devicedatastream.js'; import Globals from './globals.js'; import { Logger } from './logger.js'; -import { MonitorManager } from './monitormanager.js'; -import { Overlay } from './overlay.js'; -import { isValidKeepAlive } from './time.js'; -import { IPC_FILE_PATH, XREffect } from './xrEffect.js'; +import { MonitorManager, NESTED_MONITOR_PRODUCT, SUPPORTED_MONITOR_PRODUCTS, VIRTUAL_MONITOR_PRODUCT } from './monitormanager.js'; +import { VirtualDisplaysActor } from './virtualdisplaysactor.js'; import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; -const NESTED_MONITOR_PRODUCT = 'MetaMonitor'; -const SUPPORTED_MONITOR_PRODUCTS = [ - 'VITURE', - 'nreal air', - 'Air', - 'Air 2', - 'Air 2 Pro', - 'Air 2 Ultra', - 'SmartGlasses', // TCL/RayNeo - 'Rokid Max', - 'Rokid Air', - NESTED_MONITOR_PRODUCT -]; - const BIN_HOME = GLib.getenv('XDG_BIN_HOME') || GLib.build_filenamev([GLib.get_home_dir(), '.local', 'bin']); const XDG_CLI_PATH = GLib.build_filenamev([BIN_HOME, 'xr_driver_cli']); const ALT_CLI_PATH = '/usr/bin/xr_driver_cli'; @@ -42,26 +27,17 @@ export default class BreezyDesktopExtension extends Extension { // Set/destroyed by enable/disable this._cursor_manager = null; this._monitor_manager = null; - this._xr_effect = null; - this._overlay = null; + this._virtual_displays_actor = null; + this._virtual_displays_overlay = null; this._target_monitor = null; this._is_effect_running = false; - this._distance_binding = null; + this._effect_settings_bindings = []; + this._data_stream_bindings = []; + this._show_banner_connection = null; this._distance_connection = null; + this._focused_monitor_distance_connection = null; this._follow_threshold_connection = null; - this._widescreen_mode_settings_connection = null; - this._widescreen_mode_effect_state_connection = null; - this._supported_device_detected_connected = null; - this._start_binding = null; - this._end_binding = null; - this._curved_display_binding = null; - this._display_size_binding = null; - this._look_ahead_override_binding = null; - this._disable_anti_aliasing_binding = null; - this._optimal_monitor_config_binding = null; - this._headset_as_primary_binding = null; - this._actor_added_connection = null; - this._actor_removed_connection = null; + this._breezy_desktop_running_connection = null; if (!Globals.logger) { Globals.logger = new Logger({ @@ -70,6 +46,12 @@ export default class BreezyDesktopExtension extends Extension { }); Globals.logger.logVersion(); } + + if (!Globals.data_stream) { + Globals.data_stream = new DeviceDataStream({ + debug_no_device: this.settings.get_boolean('debug-no-device') + }); + } } enable() { @@ -77,21 +59,27 @@ export default class BreezyDesktopExtension extends Extension { try { Globals.extension_dir = this.path; - this.settings.bind('debug', Globals.logger, 'debug', Gio.SettingsBindFlags.DEFAULT); + + Globals.data_stream.start(); this._monitor_manager = new MonitorManager({ use_optimal_monitor_config: this.settings.get_boolean('use-optimal-monitor-config'), headset_as_primary: this.settings.get_boolean('headset-as-primary'), use_highest_refresh_rate: this.settings.get_boolean('use-highest-refresh-rate'), + disable_physical_displays: this.settings.get_boolean('disable-physical-displays'), extension_path: this.path }); this._monitor_manager.setChangeHook(this._handle_monitor_change.bind(this)); this._monitor_manager.enable(); - this._optimal_monitor_config_binding = this.settings.bind('use-optimal-monitor-config', - this._monitor_manager, 'use-optimal-monitor-config', Gio.SettingsBindFlags.DEFAULT); - this._headset_as_primary_binding = this.settings.bind('headset-as-primary', - this._monitor_manager, 'headset-as-primary', Gio.SettingsBindFlags.DEFAULT); + this.settings.bind('debug', Globals.logger, 'debug', Gio.SettingsBindFlags.DEFAULT); + this.settings.bind('use-optimal-monitor-config',this._monitor_manager, 'use-optimal-monitor-config', Gio.SettingsBindFlags.DEFAULT); + this.settings.bind('headset-as-primary', this._monitor_manager, 'headset-as-primary', Gio.SettingsBindFlags.DEFAULT); + this.settings.bind('disable-physical-displays', this._monitor_manager, 'disable-physical-displays', Gio.SettingsBindFlags.DEFAULT); + this.settings.bind('debug-no-device', Globals.data_stream, 'debug-no-device', Gio.SettingsBindFlags.DEFAULT); + + this._breezy_desktop_running_connection = Globals.data_stream.connect('notify::breezy-desktop-running', + this._handle_breezy_desktop_running_change.bind(this)); this._cli_file = Gio.file_new_for_path(XDG_CLI_PATH); if (!this._cli_file.query_exists(null)) { @@ -108,62 +96,52 @@ export default class BreezyDesktopExtension extends Extension { } } - _poll_for_ready() { - Globals.logger.log_debug('BreezyDesktopExtension _poll_for_ready'); - var target_monitor = this._target_monitor; - var is_effect_running = this._is_effect_running; - this._running_poller_id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, (() => { - try { - if (is_effect_running) { - this._running_poller_id = undefined; - return GLib.SOURCE_REMOVE; - } - - if (this._check_driver_running() && target_monitor) { - // Don't enable the effect yet if monitor updates are needed. - // _setup will be triggered again since a !ready result means it will trigger monitor changes, - // so we can remove this timeout_add no matter what. - if (this._target_monitor_ready(target_monitor)) { - Globals.logger.log('Driver is running, supported monitor connected. Enabling XR effect.'); - this._effect_enable(); - } - this._running_poller_id = undefined; - return GLib.SOURCE_REMOVE; - } else { - return GLib.SOURCE_CONTINUE; - } - } catch (e) { - Globals.logger.log(`[ERROR] BreezyDesktopExtension _poll_for_ready ${e.message}\n${e.stack}`); - this._running_poller_id = undefined; - return GLib.SOURCE_REMOVE; + _find_virtual_monitors() { + try { + Globals.logger.log_debug('BreezyDesktopExtension _find_virtual_monitors'); + const virtual_monitors = this._monitor_manager.getMonitorPropertiesList()?.filter( + monitor => monitor && monitor.product === VIRTUAL_MONITOR_PRODUCT); + if (virtual_monitors.length > 0) { + Globals.logger.log(`Found ${virtual_monitors.length} virtual monitors`); + return virtual_monitors.map(monitor => { + return this._monitor_manager.getMonitors()[monitor.index]; + }); } - }).bind(this)); + + Globals.logger.log_debug('BreezyDesktopExtension _find_virtual_monitors - No virtual monitors found'); + } catch (e) { + Globals.logger.log(`[ERROR] BreezyDesktopExtension _find_virtual_monitors ${e.message}\n${e.stack}`) + } + + return []; } _find_supported_monitor() { + if (!this._monitor_manager.getMonitorPropertiesList()) return null; + try { Globals.logger.log_debug('BreezyDesktopExtension _find_supported_monitor'); - const target_monitor = this._monitor_manager.getMonitorPropertiesList()?.find( - monitor => SUPPORTED_MONITOR_PRODUCTS.includes(monitor.product) || - this.settings.get_string('custom-monitor-product') === monitor.product); + 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 - }; - } - - 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 + is_dummy: is_dummy, + is_virtual: target_monitor.product === VIRTUAL_MONITOR_PRODUCT }; } @@ -176,8 +154,7 @@ export default class BreezyDesktopExtension extends Extension { } // Assumes target_monitor is set, and was returned by _find_supported_monitor. - // A false result means we'll expect _handle_monitor_change to be triggered, so active polling - // can be disabled. + // A false result means we'll expect _handle_monitor_change to be triggered _target_monitor_ready(target_monitor) { if (target_monitor.is_dummy) return true; @@ -194,49 +171,26 @@ export default class BreezyDesktopExtension extends Extension { Globals.logger.log('Reset triggered, disabling XR effect'); this._effect_disable(!for_disable); } - const target_monitor = this._find_supported_monitor(); - // if target_monitor isn't set, do nothing and wait for MonitorManager to call this again - if (target_monitor && this._running_poller_id === undefined) { - this._target_monitor = target_monitor; - - if (this._check_driver_running()) { + this._target_monitor = this._find_supported_monitor(); + if (this._target_monitor) { + if (Globals.data_stream.breezy_desktop_running) { // Don't enable the effect yet if monitor updates are needed. // _setup will be triggered again since a !ready result means it will trigger monitor changes - if (this._target_monitor_ready(target_monitor)) { + if (this._target_monitor_ready(this._target_monitor)) { Globals.logger.log('Ready, enabling XR effect'); this._effect_enable(); } else { - Globals.logger.log_debug('BreezyDesktopExtension _setup - driver running but async monitor action needed'); + Globals.logger.log_debug('BreezyDesktopExtension _setup - breezy desktop enabled, but async monitor action needed'); } } else { - Globals.logger.log_debug('BreezyDesktopExtension _setup - driver not running, starting poller'); - this._poll_for_ready(); + Globals.logger.log_debug('BreezyDesktopExtension _setup - Doing nothing, target monitor found, but device stream not being received'); } } else { - if (!target_monitor) { - Globals.logger.log_debug(`BreezyDesktopExtension _setup - Doing nothing, no supported monitor found`); - } else { - Globals.logger.log_debug(`BreezyDesktopExtension _setup - Doing nothing, target monitor found, waiting for poller to pick it up`); - } + Globals.logger.log_debug(`BreezyDesktopExtension _setup - Doing nothing, no supported monitor found, breezy_desktop_running: ${Globals.data_stream.breezy_desktop_running}`); } } - _check_driver_running() { - try { - if (!Globals.ipc_file) Globals.ipc_file = Gio.file_new_for_path(IPC_FILE_PATH); - if (Globals.ipc_file.query_exists(null)) { - const file_info = Globals.ipc_file.query_info(Gio.FILE_ATTRIBUTE_TIME_MODIFIED, Gio.FileQueryInfoFlags.NONE, null); - const file_modified_time = file_info.get_attribute_uint64(Gio.FILE_ATTRIBUTE_TIME_MODIFIED); - return isValidKeepAlive(file_modified_time); - } - } catch (e) { - Globals.logger.log(`[ERROR] BreezyDesktopExtension _check_driver_running ${e.message}\n${e.stack}`); - } - - return false; - } - _needs_widescreen_monitor_update() { Globals.logger.log_debug('BreezyDesktopExtension _needs_widescreen_monitor_update'); const state = this._read_state(); @@ -253,79 +207,105 @@ export default class BreezyDesktopExtension extends Extension { _effect_enable() { Globals.logger.log_debug('BreezyDesktopExtension _effect_enable'); - this._running_poller_id = undefined; if (!this._is_effect_running) { this._is_effect_running = true; try { const targetMonitor = this._target_monitor.monitor; + const virtualMonitors = this._find_virtual_monitors(); const refreshRate = targetMonitor.refreshRate ?? 60; - this._overlay = new Overlay(targetMonitor); - this._cursor_manager = new CursorManager(this._overlay, refreshRate); - this._cursor_manager.enable(); + // use rgba(255, 4, 144, 1) for chroma key background + this._virtual_displays_overlay = new St.Bin({ style: 'background-color: rgba(0, 0, 0, 1);', clip_to_allocation: true }); + this._virtual_displays_overlay.opacity = 255; + this._virtual_displays_overlay.set_position(targetMonitor.x, targetMonitor.y); + this._virtual_displays_overlay.set_size(targetMonitor.width, targetMonitor.height); - // In GS 45, use of "actor" was renamed to "child". - const clutterContainer = Clutter.Container !== undefined; - this._actor_added_connection = global.stage.connect( - clutterContainer ? 'actor-added' : 'child-added', - this._handle_sibling_update.bind(this), - ); - this._actor_removed_connection = global.stage.connect( - clutterContainer ? 'actor-removed' : 'child-removed', - this._handle_sibling_update.bind(this), - ); - - this._xr_effect = new XREffect({ + Globals.data_stream.refresh_data(); + this._virtual_displays_actor = new VirtualDisplaysActor({ + width: targetMonitor.width, + height: targetMonitor.height, target_monitor: targetMonitor, - target_framerate: refreshRate, + virtual_monitors: virtualMonitors, + monitor_wrapping_scheme: this.settings.get_string('monitor-wrapping-scheme'), + monitor_spacing: this.settings.get_int('monitor-spacing'), + viewport_offset_x: this.settings.get_double('viewport-offset-x'), + viewport_offset_y: this.settings.get_double('viewport-offset-y'), display_distance: this.settings.get_double('display-distance'), toggle_display_distance_start: this.settings.get_double('toggle-display-distance-start'), toggle_display_distance_end: this.settings.get_double('toggle-display-distance-end'), - look_ahead_override: this.settings.get_int('look-ahead-override'), - disable_anti_aliasing: this.settings.get_boolean('disable-anti-aliasing') + framerate_cap: this.settings.get_double('framerate-cap'), + imu_snapshots: Globals.data_stream.imu_snapshots, + show_banner: Globals.data_stream.show_banner, + custom_banner_enabled: Globals.data_stream.custom_banner_enabled }); + this._virtual_displays_overlay.set_child(this._virtual_displays_actor); + this._virtual_displays_actor.renderMonitors(); + + Shell.util_set_hidden_from_pick(this._virtual_displays_overlay, true); + global.stage.add_child(this._virtual_displays_overlay); + + const cursor_manager_monitor_objs = this._virtual_displays_actor.monitor_actors.map(monitor => { + return { + monitor: monitor.monitorDetails, + actor: monitor.containerActor + }; + }); + + this._cursor_manager = new CursorManager(cursor_manager_monitor_objs, refreshRate); + this._cursor_manager.enable(); + this._update_follow_threshold(this.settings); - // this gets triggered before _effect_enable if in fast-sbs-mode-switching mode - if (!this.settings.get_boolean('fast-sbs-mode-switching')) - this._update_widescreen_mode_from_settings(this.settings); - - this._widescreen_mode_effect_state_connection = this._xr_effect.connect('notify::widescreen-mode-state', this._update_widescreen_mode_from_state.bind(this)); - this._supported_device_detected_connected = this._xr_effect.connect('notify::supported-device-detected', this._handle_supported_device_change.bind(this)); - - this._distance_binding = this.settings.bind('display-distance', this._xr_effect, '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)) + this._data_stream_bindings = [ + 'show-banner', + 'custom-banner-enabled', + 'smooth-follow-enabled' + ].map(data_stream_key => + Globals.data_stream.bind_property(data_stream_key, this._virtual_displays_actor, data_stream_key, Gio.SettingsBindFlags.DEFAULT) + ); - this._widescreen_mode_settings_connection = this.settings.connect('changed::widescreen-mode', this._update_widescreen_mode_from_settings.bind(this)) - this._start_binding = this.settings.bind('toggle-display-distance-start', this._xr_effect, 'toggle-display-distance-start', Gio.SettingsBindFlags.DEFAULT) - this._end_binding = this.settings.bind('toggle-display-distance-end', this._xr_effect, 'toggle-display-distance-end', Gio.SettingsBindFlags.DEFAULT) - this._curved_display_binding = this.settings.bind('curved-display', this._xr_effect, 'curved-display', Gio.SettingsBindFlags.DEFAULT) - this._display_size_binding = this.settings.bind('display-size', this._xr_effect, 'display-size', Gio.SettingsBindFlags.DEFAULT); - this._look_ahead_override_binding = this.settings.bind('look-ahead-override', this._xr_effect, 'look-ahead-override', Gio.SettingsBindFlags.DEFAULT); - this._disable_anti_aliasing_binding = this.settings.bind('disable-anti-aliasing', this._xr_effect, 'disable-anti-aliasing', Gio.SettingsBindFlags.DEFAULT); + this._show_banner_connection = Globals.data_stream.connect('notify::show-banner', this._handle_show_banner_update.bind(this)); + this._was_show_banner = Globals.data_stream.show_banner; + if (!this._was_show_banner) this._recenter_display(); - this._overlay.mainActor().add_effect_with_name('xr-desktop', this._xr_effect); - Meta.disable_unredirect_for_display(global.display); + this._effect_settings_bindings = [ + 'monitor-wrapping-scheme', + 'viewport-offset-x', + 'viewport-offset-y', + 'monitor-spacing', + 'display-distance', + 'toggle-display-distance-start', + 'toggle-display-distance-end', + 'display-size', + 'framerate-cap', + 'look-ahead-override', + 'disable-anti-aliasing' + ] + this._effect_settings_bindings.forEach(settings_key => + this.settings.bind(settings_key, this._virtual_displays_actor, settings_key, Gio.SettingsBindFlags.DEFAULT) + ); + + this._distance_connection = this.settings.connect('changed::display-distance', this._update_display_distance.bind(this)); + this._focused_monitor_distance_connection = + this._virtual_displays_actor.connect('notify::focused-monitor-details', this._update_display_distance.bind(this)); + this._follow_threshold_connection = this.settings.connect('changed::follow-threshold', this._update_follow_threshold.bind(this)); + + Meta.Compositor?.disable_unredirect?.() ?? Meta.disable_unredirect_for_display(global.display); this._add_settings_keybinding('toggle-xr-effect-shortcut', this._toggle_xr_effect.bind(this)); this._add_settings_keybinding('recenter-display-shortcut', this._recenter_display.bind(this)); - this._add_settings_keybinding('toggle-display-distance-shortcut', this._xr_effect._change_distance.bind(this._xr_effect)); + this._add_settings_keybinding('toggle-display-distance-shortcut', this._virtual_displays_actor._change_distance.bind(this._virtual_displays_actor)); this._add_settings_keybinding('toggle-follow-shortcut', this._toggle_follow_mode.bind(this)); } catch (e) { + Globals.logger.log(`[ERROR] BreezyDesktopExtension _effect_enable ${e.message}\n${e.stack}`); Globals.logger.log(`[ERROR] BreezyDesktopExtension _effect_enable ${e.message}\n${e.stack}`); this._effect_disable(); } } } - _handle_sibling_update() { - Globals.logger.log_debug('BreezyDesktopExtension _handle_sibling_update()'); - global.stage.set_child_above_sibling(this._overlay.mainActor(), null); - } - _add_settings_keybinding(settings_key, bind_to_function) { try { Main.wm.addKeybinding( @@ -361,10 +341,15 @@ export default class BreezyDesktopExtension extends Extension { _write_control(key, value) { try { - const file = Gio.file_new_for_path('/dev/shm/xr_driver_control'); - const stream = file.replace(null, false, Gio.FileCreateFlags.NONE, null); - stream.write(`${key}=${value}`, null); - stream.close(null); + Globals.logger.log_debug(`BreezyDesktopExtension _write_control ${key} ${value}`); + let proc = Gio.Subprocess.new( + ['bash', '-c', `echo "${key}=${value}" > /dev/shm/xr_driver_control`], + Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE + ); + + let [success, stdout, stderr] = proc.communicate_utf8(null, null); + if (!success || !!stderr) + throw new Error(`Failed to write control: ${stderr}`); } catch (e) { Globals.logger.log(`[ERROR] BreezyDesktopExtension _write_control ${e.message}\n${e.stack}`); } @@ -395,10 +380,19 @@ export default class BreezyDesktopExtension extends Extension { return state; } - _update_display_distance(settings, event) { - const value = settings.get_double('display-distance'); + _update_display_distance(object, event) { + const value = this.settings.get_double('display-distance'); Globals.logger.log_debug(`BreezyDesktopExtension _update_display_distance ${value}`); - if (value !== undefined) this._write_control('breezy_desktop_display_distance', value); + if (value !== undefined) { + let focusedMonitorSizeAdjustment = 1.0; + if (this._virtual_displays_actor?.focused_monitor_details && this._target_monitor) { + const fovMonitor = this._target_monitor.monitor; + const focusedMonitor = this._virtual_displays_actor.focused_monitor_details; + focusedMonitorSizeAdjustment = + Math.max(focusedMonitor.width / fovMonitor.width, focusedMonitor.height / fovMonitor.height); + } + this._write_control('breezy_desktop_display_distance', value / focusedMonitorSizeAdjustment); + } } _update_follow_threshold(settings, event) { @@ -421,7 +415,7 @@ export default class BreezyDesktopExtension extends Extension { this._sbs_mode_update_timeout = undefined; if (this.settings.get_boolean('fast-sbs-mode-switching')) { - // setup and polling were halted if this is enabled, so we have to re-trigger setup + // setup was halted if this is enabled, so we have to re-trigger it now this._setup(); } @@ -443,12 +437,12 @@ export default class BreezyDesktopExtension extends Extension { } _update_widescreen_mode_from_settings(settings, event) { - const value = settings.get_boolean('widescreen-mode'); - Globals.logger.log_debug(`BreezyDesktopExtension _update_widescreen_mode_from_settings ${value}`); - if (value !== undefined && value !== this._xr_effect.widescreen_mode_state) { - this._request_sbs_mode_change(value); - } else - Globals.logger.log_debug('effect.widescreen_mode_state already matched setting'); + // const value = settings.get_boolean('widescreen-mode'); + // Globals.logger.log_debug(`BreezyDesktopExtension _update_widescreen_mode_from_settings ${value}`); + // if (value !== undefined && value !== this._xr_effect.widescreen_mode_state) { + // this._request_sbs_mode_change(value); + // } else + // Globals.logger.log_debug('effect.widescreen_mode_state already matched setting'); } _update_widescreen_mode_from_state(effect, _pspec) { @@ -472,17 +466,22 @@ export default class BreezyDesktopExtension extends Extension { this._setup(); } - _handle_supported_device_change(effect, _pspec) { - const device_connected = effect.supported_device_detected; - Globals.logger.log_debug(`BreezyDesktopExtension _handle_supported_device_change ${device_connected}`); + _handle_breezy_desktop_running_change(datastream, _pspec) { + Globals.logger.log_debug(`BreezyDesktopExtension _handle_breezy_desktop_running_change ${datastream.breezy_desktop_running}`); - // this will disable the effect and begin polling for a ready state again - if (!device_connected && this._is_effect_running) { - Globals.logger.log('Supported device disconnected'); - this._setup(true); + if (datastream.breezy_desktop_running !== this._is_effect_running) { + if (!datastream.breezy_desktop_running) Globals.logger.log('Breezy desktop disabled'); + this._setup(!datastream.breezy_desktop_running); } } + _handle_show_banner_update(datastream, _pspec) { + Globals.logger.log_debug(`BreezyDesktopExtension _handle_show_banner_update ${datastream.show_banner}`); + if (this._was_show_banner && !datastream.show_banner) this._recenter_display(); + + this._was_show_banner = datastream.show_banner; + } + _toggle_xr_effect() { if (!this._cli_file) return; @@ -521,6 +520,7 @@ export default class BreezyDesktopExtension extends Extension { _toggle_follow_mode() { Globals.logger.log_debug('BreezyDesktopExtension _toggle_follow_mode'); + if (!!this._virtual_displays_actor) this._virtual_displays_actor.set_property('smooth-follow-toggle-epoch-ms', Date.now()); this._write_control('toggle_breezy_desktop_smooth_follow', 'true'); } @@ -530,85 +530,51 @@ export default class BreezyDesktopExtension extends Extension { Globals.logger.log_debug('BreezyDesktopExtension _effect_disable'); this._is_effect_running = false; - if (this._running_poller_id) { - const poller_id = this._running_poller_id; - this._running_poller_id = undefined; - GLib.source_remove(poller_id); - } + if (Globals.data_stream.smooth_follow_enabled) this._toggle_follow_mode(); Main.wm.removeKeybinding('recenter-display-shortcut'); Main.wm.removeKeybinding('toggle-display-distance-shortcut'); Main.wm.removeKeybinding('toggle-follow-shortcut'); - Meta.enable_unredirect_for_display(global.display); + Meta.Compositor?.enable_unredirect?.() ?? Meta.enable_unredirect_for_display(global.display); - if (this._actor_added_connection) { - global.stage.disconnect(this._actor_added_connection); - this._actor_added_connection = null; + for (let settings_key of this._effect_settings_bindings) { + Gio.Settings.unbind(this.settings, settings_key); } - if (this._actor_removed_connection) { - global.stage.disconnect(this._actor_removed_connection); - this._actor_removed_connection = null; - } - if (this._distance_binding) { - this.settings.unbind(this._distance_binding); - this._distance_binding = null; + this._effect_settings_bindings = []; + this._data_stream_bindings.forEach(binding => binding.unbind()); + this._data_stream_bindings = []; + + if (this._show_banner_connection) { + Globals.data_stream.disconnect(this._show_banner_connection); + this._show_banner_connection = null; } if (this._distance_connection) { this.settings.disconnect(this._distance_connection); this._distance_connection = null; } + if (this._focused_monitor_distance_connection) { + this._virtual_displays_actor.disconnect(this._focused_monitor_distance_connection); + this._focused_monitor_distance_connection = null; + } if (this._follow_threshold_connection) { this.settings.disconnect(this._follow_threshold_connection); this._follow_threshold_connection = null; } - if (this._widescreen_mode_settings_connection) { - this.settings.disconnect(this._widescreen_mode_settings_connection); - this._widescreen_mode_settings_connection = null; - } - if (this._start_binding) { - this.settings.unbind(this._start_binding); - this._start_binding = null; - } - if (this._end_binding) { - this.settings.unbind(this._end_binding); - this._end_binding = null; - } - if (this._curved_display_binding) { - this.settings.unbind(this._curved_display_binding); - this._curved_display_binding = null; - } - if (this._display_size_binding) { - this.settings.unbind(this._display_size_binding); - this._display_size_binding = null; - } - if (this._look_ahead_override_binding) { - this.settings.unbind(this._look_ahead_override_binding); - this._look_ahead_override_binding = null; - } - if (this._disable_anti_aliasing_binding) { - this.settings.unbind(this._disable_anti_aliasing_binding); - this._disable_anti_aliasing_binding = null; - } - if (this._xr_effect) { - if (this._widescreen_mode_effect_state_connection) { - this._xr_effect.disconnect(this._widescreen_mode_effect_state_connection); - this._widescreen_mode_effect_state_connection = null; + if (this._virtual_displays_overlay) { + if (this._virtual_displays_actor) { + this._virtual_displays_overlay.remove_child(this._virtual_displays_actor); + this._virtual_displays_actor.destroy(); + this._virtual_displays_actor = null; } - if (this._supported_device_detected_connected) { - this._xr_effect.disconnect(this._supported_device_detected_connected); - this._supported_device_detected_connected = null; - } - this._xr_effect.cleanup(); - this._xr_effect = null; + + global.stage.remove_child(this._virtual_displays_overlay); + this._virtual_displays_overlay.destroy(); + this._virtual_displays_overlay = null; } if (this._cursor_manager) { this._cursor_manager.disable(); this._cursor_manager = null; } - if (this._overlay) { - this._overlay.mainActor().remove_effect_by_name('xr-desktop'); - this._overlay.destroy(); - } // this should always be done at the end of this function after the widescreen settings binding is removed, // so it doesn't reset the setting to false @@ -624,18 +590,22 @@ export default class BreezyDesktopExtension extends Extension { disable() { try { Globals.logger.log_debug('BreezyDesktopExtension disable'); - this._effect_disable(); - this._target_monitor = null; - if (this._monitor_manager) { - if (this._optimal_monitor_config_binding) { - this.settings.unbind(this._optimal_monitor_config_binding); - this._optimal_monitor_config_binding = null - } - if (this._headset_as_primary_binding) { - this.settings.unbind(this._headset_as_primary_binding); - this._headset_as_primary_binding = null; - } + this._effect_disable(); + Globals.data_stream.stop(); + this._target_monitor = null; + + if (this._breezy_desktop_running_connection) { + Globals.data_stream.disconnect(this._breezy_desktop_running_connection); + this._breezy_desktop_running_connection = null; + } + Gio.Settings.unbind(this.settings, 'debug'); + Gio.Settings.unbind(this.settings, 'use-optimal-monitor-config'); + Gio.Settings.unbind(this.settings, 'headset-as-primary'); + Gio.Settings.unbind(this.settings, 'disable-physical-displays'); + Gio.Settings.unbind(this.settings, 'debug-no-device'); + + if (this._monitor_manager) { this._monitor_manager.disable(); this._monitor_manager = null; } diff --git a/gnome/src/globals.js b/gnome/src/globals.js index 124d2e1..f11451c 100644 --- a/gnome/src/globals.js +++ b/gnome/src/globals.js @@ -1,6 +1,7 @@ const Globals = { logger: null, ipc_file: null, // Gio.File instance, file exists if set - extension_dir: null // string path + extension_dir: null, // string path + data_stream: null, // DeviceDataStream instance } export default Globals; \ No newline at end of file diff --git a/gnome/src/math.js b/gnome/src/math.js index 497274e..f1c9983 100644 --- a/gnome/src/math.js +++ b/gnome/src/math.js @@ -1,3 +1,20 @@ export function degreeToRadian(degree) { return degree * Math.PI / 180; +} + +// FOV in radians is spherical, so doesn't follow Pythagoras' theorem +export function diagonalToCrossFOVs(diagonalFOVRadians, aspectRatio) { + // first convert from a spherical FOV to a diagonal FOV on a flat plane at a generic distance of 1.0 + const flatDiagonalFOV = 2 * Math.tan(diagonalFOVRadians / 2); + + // then convert to flat plane horizontal and vertical FOVs + const flatVerticalFOV = flatDiagonalFOV / Math.sqrt(1 + aspectRatio * aspectRatio); + const flatHorizontalFOV = flatVerticalFOV * aspectRatio; + + // then convert back to spherical FOV + return { + diagonal: diagonalFOVRadians, + horizontal: 2 * Math.atan(Math.tan(flatHorizontalFOV / 2)), + vertical: 2 * Math.atan(Math.tan(flatVerticalFOV / 2)) + } } \ No newline at end of file diff --git a/gnome/src/monitormanager.js b/gnome/src/monitormanager.js index 6cf5532..4c57171 100644 --- a/gnome/src/monitormanager.js +++ b/gnome/src/monitormanager.js @@ -23,6 +23,21 @@ import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import Globals from './globals.js'; +export const NESTED_MONITOR_PRODUCT = 'MetaMonitor'; +export const VIRTUAL_MONITOR_PRODUCT = 'Virtual remote monitor'; +export const SUPPORTED_MONITOR_PRODUCTS = [ + 'VITURE', + 'nreal air', + 'Air', + 'Air 2', + 'Air 2 Pro', + 'Air 2 Ultra', + 'SmartGlasses', // TCL/RayNeo + 'Rokid Max', + 'Rokid Air', + NESTED_MONITOR_PRODUCT +]; + let cachedDisplayConfigProxy = null; function getDisplayConfigProxy(extPath) { @@ -35,7 +50,7 @@ function getDisplayConfigProxy(extPath) { xml = new TextDecoder().decode(bytes); } } catch (e) { - Globals.logger.log('ERROR: failed to load DisplayConfig interface XML'); + Globals.logger.log('[ERROR] failed to load DisplayConfig interface XML'); throw e; } cachedDisplayConfigProxy = Gio.DBusProxy.makeProxyWrapper(xml); @@ -54,40 +69,35 @@ export function newDisplayConfig(extPath, callback) { } function getMonitorConfig(displayConfigProxy, callback) { - displayConfigProxy.GetResourcesRemote((result, error) => { + displayConfigProxy.GetCurrentStateRemote((result, error) => { if (error) { - callback(null, `GetResourcesRemote failed: ${error}`); + callback(null, `GetCurrentState failed: ${error}`); } else { - const monitors = []; - for (let i = 0; i < result[2].length; i++) { - const output = result[2][i]; - if (output.length <= 7) { - callback(null, 'Cannot get DisplayConfig: No properties on output #' + i); - return; - } - const props = output[7]; - const displayName = props['display-name'].get_string()[0]; - const connectorName = output[4]; - if (!displayName || displayName == '') { - const displayName = 'Monitor on output ' + connectorName; - } - const vendor = props['vendor'].get_string()[0]; - const product = props['product'].get_string()[0]; - const serial = props['serial'].get_string()[0]; + Globals.logger.log_debug(`monitormanager.js getMonitorConfig GetCurrentState result: ${JSON.stringify(result)}`); + + const allMonitors = []; + const [serial, monitors, logicalMonitors, properties] = result; + for (let monitor of monitors) { + const [details, modes, monProperties] = monitor; + const [connector, vendor, product, monitorSerial] = details; + const displayName = monProperties['display-name'].get_string()[0]; - // grab refresh rate from the modes array - const refreshRate = result[3][i][4]; - - monitors.push([displayName, connectorName, vendor, product, serial, refreshRate]); + for (let mode of modes) { + const [modeId, width, height, refreshRate, preferredScale, supportedScales, modeProperites] = mode; + const isCurrent = !!modeProperites['is-current']; + if (isCurrent) { + allMonitors.push([displayName, connector, vendor, product, serial, refreshRate]); + } + } } - callback(monitors, null); + callback(allMonitors, null); } }); } // triggers callback with true result if an an async monitor config change was triggered, false if no config change needed function performOptimalModeCheck(displayConfigProxy, connectorName, headsetAsPrimary, useHighestRefreshRate, - callback, allowConfigUpdateFn) { + disablePhysicalDisplays, callback, allowConfigUpdateFn) { Globals.logger.log_debug(`monitormanager.js performOptimalModeCheck for ${connectorName}`); displayConfigProxy.GetCurrentStateRemote((result, error) => { @@ -162,9 +172,11 @@ function performOptimalModeCheck(displayConfigProxy, connectorName, headsetAsPri logicalMonitors.sort((a, b) => a[0] - b[0]); // map from original logical monitors schema to a(iiduba(ssa{sv})) for ApplyMonitorsConfig call - const updatedLogicalMonitors = logicalMonitors.map((logicalMonitor) => { + const removeMonitorIndexes = []; + const updatedLogicalMonitors = logicalMonitors.map((logicalMonitor, index) => { const [x, y, scale, transform, primary, monitors, logMonProperties] = logicalMonitor; const hasOurMonitor = !!monitors.some((monitor) => monitor[0] === connectorName); + const hasVirtualMonitor = monitors.some((monitor) => monitor[2] === VIRTUAL_MONITOR_PRODUCT); const newScale = (!skipScaleUpdate && hasOurMonitor) ? bestFitMode.bestScale : scale; anyMonitorsChanged |= newScale !== scale; @@ -172,6 +184,11 @@ function performOptimalModeCheck(displayConfigProxy, connectorName, headsetAsPri // if headsetAsPrimary is true anyMonitorsChanged |= headsetAsPrimary && ((hasOurMonitor && !primary) || (!hasOurMonitor && primary)); + if (disablePhysicalDisplays && !hasVirtualMonitor && !hasOurMonitor) { + removeMonitorIndexes.push(index); + anyMonitorsChanged = true; + } + // we need to figure out if the deltaX applies to this logical monitor, // i.e. if it is within the same row as our monitor and to the right of it let thisDeltaX = deltaX; @@ -218,6 +235,112 @@ function performOptimalModeCheck(displayConfigProxy, connectorName, headsetAsPri // if our monitor is already properly configured, we can skip the ApplyMonitorsConfig call if (anyMonitorsChanged) { + if (removeMonitorIndexes.length > 0) { + let removedPrimary = false; + + // remove monitors that are not virtual or our monitor + removeMonitorIndexes.reverse().forEach((index) => { + const [x, y, scale, transform, primary, monitors, logMonProperties] = updatedLogicalMonitors[index]; + if (primary) removedPrimary = true; + updatedLogicalMonitors.splice(index, 1); + }); + + // collect sizes based on modes of attached monitors + const logicalMonitorsWithSizes = updatedLogicalMonitors.map((logicalMonitor) => { + const [x, y, scale, transform, primary, monitors, logMonProperties] = logicalMonitor; + const {width, height} = monitors.reduce(({width, height}, monitor) => { + const monitorConnector = monitor[0]; + const currentMode = monitorToCurrentModeMap[monitorConnector]; + const currentWidth = currentMode[1]; + const currentHeight = currentMode[2]; + return { + width: Math.max(width, currentWidth), + height: Math.max(height, currentHeight) + }; + }, {width: 0, height: 0}); + + return { + logicalMonitor, + width, + height, + xEnd: x + width, + yEnd: y + height + } + }); + logicalMonitorsWithSizes.sort((a, b) => a.x === b.x ? a.y - b.y : a.x - b.x); + + // this array will track monitors we've already corrected, but we'll toss it out since we're modifying the + // objects in the original array + const processedLogicalMonitors = []; + + // make sure all monitors have a monitor adjacent + for (let i = 0; i < logicalMonitorsWithSizes.length; i++) { + const thisMonitor = logicalMonitorsWithSizes[i]; + const [x, y, scale, transform, primary, monitors, logMonProperties] = thisMonitor.logicalMonitor; + const {xEnd, yEnd} = thisMonitor; + + const hasOurMonitor = !!monitors.some((monitor) => monitor[0] === connectorName); + if (removedPrimary && hasOurMonitor) { + // if we removed the primary monitor, we need to set the glasses monitor as the new primary + thisMonitor.logicalMonitor[4] = true; + } + + if (logicalMonitorsWithSizes.some((monitor, index) => { + if (index === i) return false; + + const [monX, monY, monScale, monTransform, monPrimary, monMonitors, monLogMonProperties] = monitor.logicalMonitor; + const monXEnd = monitor.xEnd; + const monYEnd = monitor.yEnd; + const xOverlap = x < monXEnd && xEnd > monX; + const yOverlap = y < monYEnd && yEnd > monY; + + // use top or left sides to determine if it's already adjacent + return (x === monXEnd && yOverlap) || (y === monYEnd && xOverlap); + })) { + // this monitor is already adjacent to another monitor, leave it as-is + processedLogicalMonitors.push(thisMonitor); + } else { + let newX = undefined; + let newY = undefined; + + // move the monitor left until it runs into one + const procMonitorsByXEndDesc = [...processedLogicalMonitors].sort((a, b) => b.xEnd - a.xEnd); + for (let j = 0; j < procMonitorsByXEndDesc.length; j++) { + const procMonitor = procMonitorsByXEndDesc[j]; + const [procX, procY, procScale, procTransform, procPrimary, procMonitors, procLogMonProperties] = procMonitor.logicalMonitor; + if (procMonitor.xEnd <= x && procY < yEnd && procMonitor.yEnd > y) { + newX = procMonitor.xEnd; + newY = y; + break; + } + } + + if (newX === undefined) { + newX = 0; + + // we didn't find an adjacent monitor to the left, now move it up until it runs into one + const procMonitorsByYEndDesc = [...processedLogicalMonitors].sort((a, b) => b.yEnd - a.yEnd); + for (let j = 0; j < procMonitorsByYEndDesc.length; j++) { + const procMonitor = procMonitorsByYEndDesc[j]; + const [procX, procY, procScale, procTransform, procPrimary, procMonitors, procLogMonProperties] = procMonitor.logicalMonitor; + if (procMonitor.yEnd <= y && procX < thisMonitor.width && procMonitor.xEnd > 0) { + newY = procMonitor.yEnd; + break; + } + } + + // if nothing found, put at origin + if (newY === undefined) newY = 0; + } + thisMonitor.logicalMonitor[0] = newX; + thisMonitor.logicalMonitor[1] = newY; + thisMonitor.xEnd = newX + thisMonitor.width; + thisMonitor.yEnd = newY + thisMonitor.height; + processedLogicalMonitors.push(thisMonitor); + } + } + } + Globals.logger.log_debug(`monitormanager.js performOptimalModeCheck updatedLogicalMonitors: ${JSON.stringify(updatedLogicalMonitors)}`); displayConfigProxy.ApplyMonitorsConfigRemote( serial, @@ -264,6 +387,13 @@ export const MonitorManager = GObject.registerClass({ 'Use headset as primary monitor', 'Automatically set the headset as the primary display upon connection', GObject.ParamFlags.READWRITE, + false + ), + 'disable-physical-displays': GObject.ParamSpec.boolean( + 'disable-physical-displays', + 'Disable physical displays', + 'Disable physical displays when a virtual display is connected', + GObject.ParamFlags.READWRITE, true ), 'extension-path': GObject.ParamSpec.string( @@ -282,11 +412,12 @@ export const MonitorManager = GObject.registerClass({ this._displayConfigProxy = null; this._monitorProperties = null; this._changeHookFn = null; - this._needsConfigCheck = this.use_optimal_monitor_config; + this._needsConfigCheck = this.use_optimal_monitor_config || this.headset_as_primary || this.disable_physical_displays; // help prevent certain actions from taking place multiple times in the event of rapid monitor updates this._asyncRequestsInFlight = 0; this._configCheckRequestsCount = 0; + this._enabled = false; } enable() { @@ -300,12 +431,17 @@ export const MonitorManager = GObject.registerClass({ }).bind(this)); this._monitorsChangedConnection = Main.layoutManager.connect('monitors-changed', this._on_monitors_change.bind(this)); + this._disable_physical_displays_connection = this.connect('notify::disable-physical-displays', this._on_disable_physical_displays_change.bind(this)); + this._enabled = true; } disable() { Globals.logger.log_debug('MonitorManager disable'); + this.disconnect(this._disable_physical_displays_connection); Main.layoutManager.disconnect(this._monitorsChangedConnection); + this._enabled = false; + this._disable_physical_displays_connection = null; this._monitorsChangedConnection = null; this._displayConfigProxy = null; this._monitorProperties = null; @@ -343,7 +479,7 @@ export const MonitorManager = GObject.registerClass({ return this._asyncRequestsInFlight === 1 && this._configCheckRequestsCount === configCheckCountSnapshot; }).bind(this); - performOptimalModeCheck(this._displayConfigProxy, monitorConnector, this.headset_as_primary, this.use_highest_refresh_rate, ((configChanged, error) => { + performOptimalModeCheck(this._displayConfigProxy, monitorConnector, this.headset_as_primary, this.use_highest_refresh_rate, this.disable_physical_displays, ((configChanged, error) => { if (--this._asyncRequestsInFlight > 0) { Globals.logger.log_debug(`MonitorManager needsOptimalModeCheck: ${this._asyncRequestsInFlight} async requests still pending, skipping change hook`); return; @@ -354,7 +490,7 @@ export const MonitorManager = GObject.registerClass({ this._needsConfigCheck = false; if (error) { - Globals.logger.log(`Failed to switch to optimal mode for monitor ${monitorConnector}: ${error}`); + Globals.logger.log(`[ERROR] Failed to switch to optimal mode for monitor ${monitorConnector}: ${error}`); // tell the extension to proceed, this should result in another config check this._changeHookFn(); @@ -380,11 +516,13 @@ export const MonitorManager = GObject.registerClass({ } _on_monitors_change() { + if (!this._enabled) return; + Globals.logger.log_debug('MonitorManager _on_monitors_change'); if (this._displayConfigProxy == null) { return; } - if (this.use_optimal_monitor_config) { + if (this.use_optimal_monitor_config || this.headset_as_primary || this.disable_physical_displays) { this._needsConfigCheck = true; this._configCheckRequestsCount++; } @@ -392,7 +530,7 @@ export const MonitorManager = GObject.registerClass({ getMonitorConfig(this._displayConfigProxy, ((result, error) => { this._asyncRequestsInFlight--; if (error) { - Globals.logger.log(error); + Globals.logger.log(`[ERROR] Failed _on_monitors_change getMonitorConfig: ${error}`); return; } @@ -425,4 +563,13 @@ export const MonitorManager = GObject.registerClass({ } }).bind(this)); } + + _on_disable_physical_displays_change() { + if (this._enabled && this.disable_physical_displays && !!this._changeHookFn) { + Globals.logger.log_debug('MonitorManager _on_disable_physical_displays_change triggering change hook'); + this._needsConfigCheck = true; + this._configCheckRequestsCount++; + this._changeHookFn(); + } + } }); \ No newline at end of file diff --git a/gnome/src/overlay.js b/gnome/src/overlay.js deleted file mode 100644 index c42af7c..0000000 --- a/gnome/src/overlay.js +++ /dev/null @@ -1,49 +0,0 @@ -import Clutter from 'gi://Clutter' -import Shell from 'gi://Shell'; -import St from 'gi://St'; - -import { SystemBackground } from './systembackground.js'; -import * as Main from 'resource:///org/gnome/shell/ui/main.js'; - -export class Overlay { - constructor(targetMonitor) { - this._overlayContent = new Clutter.Actor({clip_to_allocation: true}); - this._overlay = new St.Bin({ - child: this._overlayContent - }); - this._overlay.set_position(targetMonitor.x, targetMonitor.y); - this._overlay.set_size(targetMonitor.width, targetMonitor.height); - - global.stage.add_child(this._overlay); - Shell.util_set_hidden_from_pick(this._overlay, true); - - this._background = new SystemBackground(); - this._overlayContent.add_child(this._background); - - this._uiClone = new Clutter.Clone({ source: Main.layoutManager.uiGroup, clip_to_allocation: true }); - this._uiClone.x = -targetMonitor.x; - this._uiClone.y = -targetMonitor.y; - this._overlayContent.add_child(this._uiClone); - - this._targetMonitor = targetMonitor; - } - - isWithinBounds(x, y) { - return x >= this._targetMonitor.x && x < this._targetMonitor.x + this._targetMonitor.width && - y >= this._targetMonitor.y && y < this._targetMonitor.y + this._targetMonitor.height; - } - - getRelativePosition(x, y) { - return [x - this._targetMonitor.x, y - this._targetMonitor.y]; - } - - mainActor() { - return this._overlayContent; - } - - destroy() { - global.stage.remove_child(this._overlay); - this._overlay.destroy(); - this._overlay = null; - } -} \ No newline at end of file diff --git a/gnome/src/systembackground.js b/gnome/src/systembackground.js deleted file mode 100644 index cf7a3bb..0000000 --- a/gnome/src/systembackground.js +++ /dev/null @@ -1,32 +0,0 @@ -import Clutter from 'gi://Clutter'; -import Cogl from 'gi://Cogl'; -import GLib from 'gi://GLib'; -import GObject from 'gi://GObject'; -import Meta from 'gi://Meta'; - -const DEFAULT_BACKGROUND_COLOR = Clutter.Color?.from_pixel(0x2e3436ff) || new Cogl.Color({red: 40, green: 40, blue: 40, alpha: 255}); - -let _systemBackground; - -export const SystemBackground = GObject.registerClass({ - Signals: {'loaded': {}}, -}, class SystemBackground extends Meta.BackgroundActor { - _init() { - if (_systemBackground == null) { - _systemBackground = new Meta.Background({meta_display: global.display}); - _systemBackground.set_color(DEFAULT_BACKGROUND_COLOR); - } - - super._init({ - meta_display: global.display, - monitor: 0, - }); - this.content.background = _systemBackground; - - let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { - this.emit('loaded'); - return GLib.SOURCE_REMOVE; - }); - GLib.Source.set_name_by_id(id, '[gnome-shell] SystemBackground.loaded'); - } -}); diff --git a/gnome/src/virtualdisplayeffect.js b/gnome/src/virtualdisplayeffect.js new file mode 100644 index 0000000..7fb013b --- /dev/null +++ b/gnome/src/virtualdisplayeffect.js @@ -0,0 +1,523 @@ +import Clutter from 'gi://Clutter' +import Cogl from 'gi://Cogl'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Shell from 'gi://Shell'; + +import Globals from './globals.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.999, 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 +function lookAheadMS(imuDateMs, lookAheadCfg, override) { + // how stale the imu data is + const dataAge = Date.now() - imuDateMs; + + return (override === -1 ? lookAheadCfg[0] : override) + dataAge; +} + +export const VirtualDisplayEffect = GObject.registerClass({ + Properties: { + 'monitor-index': GObject.ParamSpec.int( + 'monitor-index', + 'Monitor Index', + 'Index of the monitor that this effect is applied to', + 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 + ), + 'target-monitor': GObject.ParamSpec.jsobject( + 'target-monitor', + 'Target Monitor', + 'Details about the monitor being used as a viewport', + 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 + ), + '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', + 'Width', + 'Width of the viewport', + GObject.ParamFlags.READWRITE, + 1, 10000, 1920 + ), + 'height': GObject.ParamSpec.int( + 'height', + 'Height', + 'Height of the viewport', + GObject.ParamFlags.READWRITE, + 1, 10000, 1080 + ), + 'focused-monitor-index': GObject.ParamSpec.int( + 'focused-monitor-index', + 'Focused Monitor Index', + 'Index of the monitor that is currently focused', + GObject.ParamFlags.READWRITE, + -1, 100, -1 + ), + 'display-zoom-on-focus': GObject.ParamSpec.boolean( + 'display-zoom-on-focus', + 'Display zoom on focus', + 'Automatically move a display closer when it becomes focused.', + GObject.ParamFlags.READWRITE, + true + ), + 'display-distance': GObject.ParamSpec.double( + 'display-distance', + 'Display Distance', + 'Distance of the display from the camera', + GObject.ParamFlags.READWRITE, + 0.0, + 2.5, + 1.0 + ), + 'display-distance-default': GObject.ParamSpec.double( + 'display-distance-default', + 'Display distance default', + 'Distance to use when not explicitly set, or when reset', + GObject.ParamFlags.READWRITE, + 0.2, + 2.5, + 1.0 + ), + 'show-banner': GObject.ParamSpec.boolean( + 'show-banner', + 'Show banner', + 'Whether the banner should be displayed', + GObject.ParamFlags.READWRITE, + false + ), + 'lens-vector': GObject.ParamSpec.jsobject( + 'lens-vector', + 'Lens Vector', + 'Vector representing the offset of the lens from the pivot point', + GObject.ParamFlags.READWRITE + ), + 'actor-to-display-ratios': GObject.ParamSpec.jsobject( + 'actor-to-display-ratios', + 'Actor to Display Ratios', + 'Ratios to convert actor coordinates to display coordinates', + GObject.ParamFlags.READWRITE + ), + 'actor-to-display-offsets': GObject.ParamSpec.jsobject( + 'actor-to-display-offsets', + 'Actor to Display Offsets', + 'Offsets to convert actor coordinates to display coordinates', + GObject.ParamFlags.READWRITE + ), + 'is-closest': GObject.ParamSpec.boolean( + 'is-closest', + 'Is Closest', + 'Whether this monitor is the closest to the camera', + GObject.ParamFlags.READWRITE, + false + ), + 'disable-anti-aliasing': GObject.ParamSpec.boolean( + 'disable-anti-aliasing', + 'Disable anti-aliasing', + 'Disable anti-aliasing for the effect', + GObject.ParamFlags.READWRITE, + false + ), + 'look-ahead-override': GObject.ParamSpec.int( + 'look-ahead-override', + 'Look ahead override', + 'Override the look ahead value', + GObject.ParamFlags.READWRITE, + -1, + 45, + -1 + ), + } +}, class VirtualDisplayEffect extends Shell.GLSLEffect { + constructor(params = {}) { + super(params); + + this._current_display_distance = this._is_focused() ? this.display_distance : this.display_distance_default; + 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::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)); + 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() { + return this.focused_monitor_index === this.monitor_index; + } + + _update_display_distance() { + const desired_distance = this._is_focused() ? this.display_distance : this.display_distance_default; + if (this._distance_ease_timeline?.is_playing()) { + // we're already easing towards the desired distance, do nothing + if (this._distance_ease_target === desired_distance) return; + + this._distance_ease_timeline.stop(); + } + + if (this.no_distance_ease) { + this._current_display_distance = desired_distance; + this._update_display_position_uniforms(); + this.no_distance_ease = false; + return; + } + + // if we're the focused display, we'll double the timeline and wait for the first half to let other + // displays ease out first + this._distance_ease_focus = this._is_focused(); + const ease_out_timeline_ms = 150; + const pause_ms = 50; + const ease_in_timeline_ms = 500; // includes ease out and pause + const ease_in_begin_pct = (ease_out_timeline_ms + pause_ms) / ease_in_timeline_ms; + const timeline_ms = this._distance_ease_focus ? + ease_in_timeline_ms : + ease_out_timeline_ms; + + this._distance_ease_start = this._current_display_distance; + this._distance_ease_timeline = Clutter.Timeline.new_for_actor(this.get_actor(), timeline_ms); + + this._distance_ease_target = desired_distance; + this._distance_ease_timeline.connect('new-frame', (() => { + let progress = this._distance_ease_timeline.get_progress(); + if (this._distance_ease_focus) { + // if we're the focused display, wait for the first half of the timeline to pass + if (progress < ease_in_begin_pct) return; + + // treat the second half of the timeline as its own full progression + progress = (progress - ease_in_begin_pct) / (1 - ease_in_begin_pct); + + // put this display in front as it starts to easy in + this.is_closest = true; + } else { + this.is_closest = false; + } + + this._current_display_distance = this._distance_ease_start + + (1 - Math.cos(progress * Math.PI)) / 2 * (this._distance_ease_target - this._distance_ease_start); + this._update_display_position_uniforms(); + }).bind(this)); + + 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; + + 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() { + // this is in NWU coordinates + const monitorPlacement = this.monitor_placements[this.monitor_index]; + + // use the center vector with the distance applied to determine how much to move each coordinate, so they all move uniformly + const inverseAppliedDistance = 1.0 - this._current_display_distance / this.display_distance_default; + const distanceDelta = monitorPlacement.centerNoRotate.map(coord => coord * inverseAppliedDistance); + const noRotationVector = monitorPlacement.topLeftNoRotate.map((coord, index) => coord - distanceDelta[index]); + + // convert to CoGL's east-down-south coordinates and apply display distance + const inverse_follow_ease = 1.0 - this._current_follow_ease_progress; + 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; + 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 * inverse_follow_ease]); + } + + _handle_banner_update() { + this.set_uniform_float(this.get_uniform_location("u_show_banner"), 1, [this.show_banner ? 1.0 : 0.0]); + } + + perspective(fovHorizontalRadians, aspect, near, far) { + const f = 1.0 / Math.tan(fovHorizontalRadians / 2.0); + const range = far - near; + + return [ + f / aspect, 0, 0, 0, + 0, f, 0, 0, + 0, 0, - (far + near) / range, -1, + 0, 0, - (2.0 * near * far) / range, 0 + ]; + } + + vfunc_build_pipeline() { + const declarations = ` + uniform bool u_show_banner; + uniform mat4 u_imu_data; + uniform float u_look_ahead_ms; + uniform vec4 u_look_ahead_cfg; + uniform mat4 u_projection_matrix; + uniform float u_fov_vertical_radians; + uniform vec3 u_display_position; + uniform float u_rotation_x_radians; + uniform float u_rotation_y_radians; + uniform vec2 u_display_resolution; + uniform vec3 u_lens_vector; + + // vector positions are relative to the width and height of the entire stage + uniform vec2 u_actor_to_display_ratios; + uniform vec2 u_actor_to_display_offsets; + + // discovered through trial and error, no idea the significance + float cogl_position_mystery_factor = 29.09 * 2; + + float look_ahead_ms_cap = 45.0; + + vec4 quatConjugate(vec4 q) { + return vec4(-q.xyz, q.w); + } + + vec3 applyQuaternionToVector(vec3 v, vec4 q) { + vec3 t = 2.0 * cross(q.xyz, v); + return v + q.w * t + cross(q.xyz, t); + } + + vec3 applyXRotationToVector(vec3 v, float angle) { + float c = cos(angle); + float s = sin(angle); + return vec3(v.x, v.y * c - v.z * s, v.y * s + v.z * c); + } + + vec3 applyYRotationToVector(vec3 v, float angle) { + float c = cos(angle); + float s = sin(angle); + return vec3(v.x * c + v.z * s, v.y, v.z * c - v.x * s); + } + + vec4 nwuToESU(vec4 v) { + return vec4(-v.y, v.z, -v.x, v.w); + } + + // returns the rate of change between the two vectors, in same time units as delta_time + // e.g. if delta_time is in ms, then the rate of change is "per ms" + vec3 rateOfChange(vec3 v1, vec3 v2, float delta_time) { + return (v1-v2) / delta_time; + } + + // attempt to figure out where the current position should be based on previous position and velocity. + // velocity and time values should use the same time units (secs, ms, etc...) + vec3 applyLookAhead(vec3 position, vec3 velocity, float look_ahead_ms) { + return position + velocity * look_ahead_ms; + } + + // project the vector onto a flat surface, return it's vertical position relative to the vertical fov, where 0.0 is + // the top and 1.0 is the bottom. vectors that project outside the vertical range of the display will have values + // outside this range, but capped + float vectorToScanline(float fovVerticalRadians, vec3 v) { + return clamp(1.0 - (-v.y / (tan(fovVerticalRadians / 2.0) * v.z) + 1.0) / 2.0, -1.5, 2.5); + } + `; + + const main = ` + vec4 world_pos = cogl_position_in; + + if (!u_show_banner) { + float aspect_ratio = u_display_resolution.x / u_display_resolution.y; + + float cogl_position_width = cogl_position_mystery_factor * aspect_ratio / u_actor_to_display_ratios.y; + float cogl_position_height = cogl_position_width / aspect_ratio; + + float pos_z_factor = aspect_ratio / u_actor_to_display_ratios.y; + vec3 pos_factors = vec3( + cogl_position_width / u_display_resolution.x, + cogl_position_height / u_display_resolution.y, + cogl_position_mystery_factor * pos_z_factor / u_display_resolution.x + ); + world_pos.x -= u_display_position.x * pos_factors.x; + world_pos.y -= u_display_position.y * pos_factors.y; + world_pos.z = u_display_position.z * pos_factors.z; + + // if the perspective includes more than just our viewport actor, move vertices towards the center of the perspective so they'll be properly rotated + world_pos.x += u_actor_to_display_offsets.x * cogl_position_width / 2; + world_pos.y -= u_actor_to_display_offsets.y * cogl_position_height / 2; + + vec3 complete_vector = applyXRotationToVector(world_pos.xyz, u_rotation_x_radians); + complete_vector = applyYRotationToVector(complete_vector, u_rotation_y_radians); + + vec4 quat_t0 = nwuToESU(quatConjugate(u_imu_data[0])); + vec3 rotated_vector_t0 = applyQuaternionToVector(complete_vector, quat_t0); + vec3 rotated_vector_t1 = applyQuaternionToVector(complete_vector, nwuToESU(quatConjugate(u_imu_data[1]))); + float delta_time_t0 = u_imu_data[3][0] - u_imu_data[3][1]; + vec3 velocity_t0 = rateOfChange(rotated_vector_t0, rotated_vector_t1, delta_time_t0); + + // compute the capped look ahead with scanline adjustments + 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; + + vec3 look_ahead_vector = applyLookAhead(rotated_vector_t0, velocity_t0, effective_look_ahead_ms); + + vec3 adjusted_lens_vector = u_lens_vector * pos_factors; + world_pos = vec4(look_ahead_vector - adjusted_lens_vector, world_pos.w); + + world_pos.z /= pos_z_factor; + + world_pos.x *= u_actor_to_display_ratios.y / u_actor_to_display_ratios.x; + + world_pos = u_projection_matrix * world_pos; + + // if the perspective includes more than just our viewport actor, move the vertices back to just the area we can see. + // this needs to be done after the projection matrix multiplication so it will be projected as if centered in our vision + world_pos.x -= (u_actor_to_display_offsets.x / u_actor_to_display_ratios.x) * world_pos.w; + world_pos.y += (u_actor_to_display_offsets.y / u_actor_to_display_ratios.y) * world_pos.w; + } else { + world_pos = cogl_modelview_matrix * world_pos; + world_pos = cogl_projection_matrix * world_pos; + } + + cogl_position_out = world_pos; + cogl_tex_coord_out[0] = cogl_tex_coord_in; + ` + + this.add_glsl_snippet(Shell.SnippetHook.VERTEX, declarations, main, false); + } + + vfunc_paint_target(node, paintContext) { + if (!this._initialized) { + const aspect = this.target_monitor.width / this.target_monitor.height; + const fovRadians = diagonalToCrossFOVs(degreeToRadian(Globals.data_stream.device_data.displayFov), aspect); + const projection_matrix = this.perspective( + fovRadians.horizontal, + 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_fov_vertical_radians"), 1, [fovRadians.vertical]); + this.set_uniform_float(this.get_uniform_location("u_display_resolution"), 2, [this.target_monitor.width, this.target_monitor.height]); + this.set_uniform_float(this.get_uniform_location("u_look_ahead_cfg"), 4, Globals.data_stream.device_data.lookAheadCfg); + 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_lens_vector"), 3, this.lens_vector); + this._update_display_position_uniforms(); + this._handle_banner_update(); + this._initialized = true; + } + + let lookAheadSet = false; + if (!this._use_smooth_follow_origin && (!this.smooth_follow_enabled || this._is_focused() || this._current_follow_ease_progress > 0.0)) { + 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) { + // improves sampling quality for smooth text and edges + this.get_pipeline().set_layer_filters( + 0, + Cogl.PipelineFilter.LINEAR_MIPMAP_LINEAR, + Cogl.PipelineFilter.LINEAR + ); + } + + super.vfunc_paint_target(node, paintContext); + } +}); \ No newline at end of file diff --git a/gnome/src/virtualdisplaysactor.js b/gnome/src/virtualdisplaysactor.js new file mode 100644 index 0000000..cab3c47 --- /dev/null +++ b/gnome/src/virtualdisplaysactor.js @@ -0,0 +1,907 @@ +import Clutter from 'gi://Clutter' +import Cogl from 'gi://Cogl'; +import GdkPixbuf from 'gi://GdkPixbuf'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Mtk from 'gi://Mtk'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + +import { VirtualDisplayEffect, SMOOTH_FOLLOW_SLERP_TIMELINE_MS } from './virtualdisplayeffect.js'; +import { degreeToRadian, diagonalToCrossFOVs } from './math.js'; + +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; + +import Globals from './globals.js'; + +function applyQuaternionToVector(vector, quaternion) { + const t = [ + 2.0 * (quaternion[1] * vector[2] - quaternion[2] * vector[1]), + 2.0 * (quaternion[2] * vector[0] - quaternion[0] * vector[2]), + 2.0 * (quaternion[0] * vector[1] - quaternion[1] * vector[0]) + ]; + return [ + vector[0] + quaternion[3] * t[0] + quaternion[1] * t[2] - quaternion[2] * t[1], + vector[1] + quaternion[3] * t[1] + quaternion[2] * t[0] - quaternion[0] * t[2], + vector[2] + quaternion[3] * t[2] + quaternion[0] * t[1] - quaternion[1] * t[0] + ]; +} + +// if nothing is in focus, take it as soon as it crosses into the monitor's bounds +const FOCUS_THRESHOLD = 0.95 / 2.0; + +// if we leave the monitor with some margin, unfocus even if no other monitor is in focus +const UNFOCUS_THRESHOLD = 1.2 / 2.0; + +/** + * Find the vector in the array that's closest to the quaternion rotation + * + * @param {number[]} quaternion - Reference quaternion [x, y, z, w] + * @param {number[][]} monitorVectors - Array of monitor vectors [x, y, z] to search from + * @param {number} currentFocusedIndex - Index of the currently focused monitor + * @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[]} 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 + */ +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 rotatedLookVector = applyQuaternionToVector(lookVector, quaternion); + + const xzMagnitude = Math.sqrt(rotatedLookVector[0]*rotatedLookVector[0] + rotatedLookVector[2]*rotatedLookVector[2]); + const lookUpTheta = Math.atan2(rotatedLookVector[2], rotatedLookVector[0]); + + let closestIndex = -1; + let closestDistance = Infinity; + let currentFocusedDistance = Infinity; + + // find the vector closest to the rotated look vector + monitorVectors.forEach((vector, index) => { + const monitor = monitorsDetails[index]; + const monitorAspectRatio = monitor.width / monitor.height; + + // weight the rotation about the y-axis between the two vectors, by the aspect ratio + const vectorUpTheta = Math.atan2(vector[2], vector[0]); + const upDelta = lookUpTheta - vectorUpTheta; + const newLookUpTheta = Math.tan(Math.max( + -Math.PI, + Math.min( + Math.PI, + upDelta * monitorAspectRatio + vectorUpTheta + ) + )); + const weightedLookVector = [ + xzMagnitude * Math.cos(newLookUpTheta), + rotatedLookVector[1], + xzMagnitude * Math.sin(newLookUpTheta) + ]; + + // find the distance between the monitor vector and weighted look vector + const distance = Math.acos( + Math.min(1.0, Math.max(-1.0, + vector[0] * weightedLookVector[0] + + vector[1] * weightedLookVector[1] + + vector[2] * weightedLookVector[2] + )) + ); + + // 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 distanceFromCenterSizeRatio = distanceFromCenterPixels / monitor.width; + + if (currentFocusedIndex === index) { + currentFocusedDistance = distanceFromCenterSizeRatio * focusedMonitorDistance; + } + + if (distanceFromCenterSizeRatio < closestDistance) { + closestIndex = index; + closestDistance = distanceFromCenterSizeRatio; + } + }); + + const keepCurrent = currentFocusedIndex !== -1 && (smoothFollowEnabled || currentFocusedDistance < UNFOCUS_THRESHOLD); + if (!keepCurrent) { + if (smoothFollowEnabled || closestDistance < FOCUS_THRESHOLD) return closestIndex; + + // neither the current nor the closest will take focus, unfocus all displays + return -1; + } + + return currentFocusedIndex; +} + +/*** + * @returns {Object} - containing `begin`, `center`, and `end` radians for rotating the given monitor + */ +function monitorWrap(cachedMonitorRadians, radiusPixels, monitorSpacingPixels, monitorBeginPixel, monitorLengthPixels) { + let closestWrapPixel = monitorBeginPixel; + let closestWrap = cachedMonitorRadians[monitorBeginPixel]; + if (closestWrap === undefined) { + closestWrapPixel = Object.keys(cachedMonitorRadians).reduce((previousPixel, currentPixel) => { + if (previousPixel === undefined) return currentPixel; + + const currentDelta = currentPixel - monitorBeginPixel; + const previousDelta = previousPixel - monitorBeginPixel; + + // always prefer an exact monitor width match + if (previousDelta % monitorLengthPixels !== 0) { + if (currentDelta % monitorLengthPixels === 0) return currentPixel; + + // prefer placing a monitor to the right or below, even if there's a closer placement to the left or above + if (previousDelta < 0 && currentDelta > 0) return currentPixel; + + // otherwise, just prefer the closest one + if (Math.abs(currentDelta) < Math.abs(previousDelta)) return currentPixel; + } + + return previousPixel; + }, undefined); + closestWrap = cachedMonitorRadians[closestWrapPixel]; + } + + const spacingRadians = Math.asin(monitorSpacingPixels / 2 / radiusPixels) * 2; + if (closestWrapPixel !== monitorBeginPixel) { + // there's a gap between the cached wrap value and this one + const gapPixels = monitorBeginPixel - closestWrapPixel; + const gapHalfRadians = Math.asin(gapPixels / 2 / radiusPixels); + const gapRadians = gapHalfRadians * 2; + + // use Math.floor so if it's negative (this monitor is to the left of or above the closest) it will always + // compenstate for the spacing that's needed at the right/bottom + const appliedSpacingRadians = Math.floor(gapPixels / monitorLengthPixels) * spacingRadians; + + // update the closestWrap value and cache it + closestWrap = closestWrap + gapRadians + appliedSpacingRadians; + closestWrapPixel = monitorBeginPixel; + cachedMonitorRadians[closestWrapPixel] = closestWrap; + } + + const monitorHalfRadians = Math.asin(monitorLengthPixels / 2 / radiusPixels); + const centerRadians = closestWrap + monitorHalfRadians; + const endRadians = centerRadians + monitorHalfRadians; + + // since we're computing the end values for this monitor, cache them too in case they line up with a future monitor + const nextMonitorPixel = monitorBeginPixel + monitorLengthPixels; + if (cachedMonitorRadians[nextMonitorPixel] === undefined) + cachedMonitorRadians[nextMonitorPixel] = endRadians + spacingRadians; + + return { + begin: closestWrap, + center: centerRadians, + end: endRadians + } +} + +/** + * Convert the given monitor details into NWU vectors describing the center of the fully placed monitor, + * and the top-left of the partially placed monitor (minus only a single-axis rotation) + * + * @param {Object} fovDetails - contains reference widthPixels, heightPixels, horizontal and vertical radians, +* and distance to the center of the screen + * @param {Object[]} monitorDetailsList - contains x, y, width, height (coordinates from top-left) + * @param {string} monitorWrappingScheme - horizontal, vertical, none + * @returns {Object[]} - contains NWU vectors pointing to `topLeftNoRotate` and `center` of each monitor + * and a `rotation` angle for the given wrapping scheme + */ +function monitorsToPlacements(fovDetails, monitorDetailsList, monitorWrappingScheme, monitorSpacing) { + const monitorPlacements = []; + const cachedMonitorRadians = {}; + + Globals.logger.log_debug(`\t\t\tFOV Details: ${JSON.stringify(fovDetails)}, Monitor Wrapping Scheme: ${monitorWrappingScheme}`); + + if (monitorWrappingScheme === 'horizontal') { + // monitors wrap around us horizontally + + // distance to a horizontal edge is the hypothenuse of the triangle where the opposite side is half the width of the reference fov screen + const sideEdgeRadius = Math.sqrt(Math.pow(fovDetails.widthPixels / 2, 2) + Math.pow(fovDetails.completeScreenDistancePixels, 2)); + const monitorSpacingPixels = monitorSpacing * fovDetails.widthPixels; + + cachedMonitorRadians[0] = -fovDetails.defaultDistanceHorizontalRadians / 2; + monitorDetailsList.forEach(monitorDetails => { + 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 upTopPixels = monitorDetails.y + (monitorDetails.y / fovDetails.heightPixels) * monitorSpacingPixels; + + // 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({ + topLeftNoRotate: [ + monitorCenterRadius, + + // west stays aligned with the origin, will apply rotationAngleRadians value during rendering + -westCenterOriginPixels, + + // up is flat when wrapping horizontally, apply it here as a constant, not touched by rendering + -upTopPixels + ], + centerNoRotate: [ + monitorCenterRadius, + + // west centered about the FOV center + 0, + + // up is flat when wrapping horizontally + -upCenterPixels + ], + centerOrigin: [ + monitorCenterRadius, + -westCenterOriginPixels, + upCenterOriginPixels + ], + centerLook: [ + // north is adjacent where radius is the hypotenuse, using monitorWrapDetails.center as the radians + monitorCenterRadius * Math.cos(monitorWrapDetails.center), + + // west is opposite where radius is the hypotenuse, using monitorWrapDetails.center as the radians + -monitorCenterRadius * Math.sin(monitorWrapDetails.center), + + // up is flat when wrapping horizontally + -upCenterPixels + ], + rotationAngleRadians: { + x: 0, + y: -monitorWrapDetails.center + } + }); + }); + } else if (monitorWrappingScheme === 'vertical') { + // monitors wrap around us vertically + + // distance to the top edge is the hypothenuse of the triangle where the opposite side is half the height of the reference fov screen + const topEdgeRadius = Math.sqrt(Math.pow(fovDetails.heightPixels / 2, 2) + Math.pow(fovDetails.completeScreenDistancePixels, 2)); + const monitorSpacingPixels = monitorSpacing * fovDetails.heightPixels; + + cachedMonitorRadians[0] = -fovDetails.defaultDistanceVerticalRadians / 2; + monitorDetailsList.forEach(monitorDetails => { + 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 westPixels = monitorDetails.x + (monitorDetails.x / fovDetails.widthPixels) * monitorSpacingPixels; + + // 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({ + topLeftNoRotate: [ + monitorCenterRadius, + + // west is flat when wrapping vertically, apply it here as a constant, not touched by rendering + westPixels, + + // up stays aligned with the origin, will apply rotationAngleRadians value during rendering + upCenterOriginPixels + ], + centerNoRotate: [ + monitorCenterRadius, + + // west is flat when wrapping horizontally + westCenterPixels, + + // west centered about the FOV center + 0 + ], + centerOrigin: [ + monitorCenterRadius, + -westCenterOriginPixels, + upCenterOriginPixels + ], + centerLook: [ + // north is adjacent where radius is the hypotenuse, using monitorWrapDetails.center as the radians + monitorCenterRadius * Math.cos(monitorWrapDetails.center), + + // west is flat when wrapping vertically + -westCenterPixels, + + // up is opposite where radius is the hypotenuse, using monitorWrapDetails.center as the radians + -monitorCenterRadius * Math.sin(monitorWrapDetails.center) + ], + rotationAngleRadians: { + x: -monitorWrapDetails.center, + y: 0 + } + }); + }); + } else { + const monitorSpacingPixels = monitorSpacing * fovDetails.widthPixels; + + // monitors make a flat wall in front of us, no wrapping + monitorDetailsList.forEach(monitorDetails => { + const upPixels = monitorDetails.y + (monitorDetails.y / fovDetails.heightPixels) * monitorSpacingPixels; + const westPixels = monitorDetails.x + (monitorDetails.x / fovDetails.widthPixels) * monitorSpacingPixels; + + // 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({ + topLeftNoRotate: [ + fovDetails.completeScreenDistancePixels, + westPixels, + -upPixels + ], + centerNoRotate: [ + fovDetails.completeScreenDistancePixels, + westCenterPixels, + -upCenterPixels + ], + centerOrigin: [ + fovDetails.completeScreenDistancePixels, + -westCenterOriginPixels, + upCenterOriginPixels + ], + centerLook: [ + fovDetails.completeScreenDistancePixels, + -westCenterPixels, + -upCenterPixels + ], + rotationAngleRadians: { + x: 0, + y: 0 + } + }); + }); + } + + Globals.logger.log_debug(`\t\t\tMonitor placements: ${JSON.stringify(monitorPlacements)}, cached values: ${JSON.stringify(cachedMonitorRadians)}`); + + return monitorPlacements; +} + +export const VirtualDisplaysActor = GObject.registerClass({ + Properties: { + 'target-monitor': GObject.ParamSpec.jsobject( + 'target-monitor', + 'Target Monitor', + 'Details about the monitor being used as a viewport', + GObject.ParamFlags.READWRITE + ), + 'virtual-monitors': GObject.ParamSpec.jsobject( + 'virtual-monitors', + 'Virtual Monitors', + 'Details about the virtual monitors', + 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'] + ), + 'monitor-spacing': GObject.ParamSpec.int( + 'monitor-spacing', + 'Monitor Spacing', + 'Visual spacing between monitors, units are 0.001 of the viewport width', + GObject.ParamFlags.READWRITE, + 0, 100, 0 + ), + '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 + ), + 'monitor-actors': GObject.ParamSpec.jsobject( + 'monitor-actors', + 'Monitor Actors', + 'Tracking actors and details for each monitor', + 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 + ), + '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', + 'Show banner', + 'Whether the banner should be displayed', + GObject.ParamFlags.READWRITE, + false + ), + 'custom-banner-enabled': GObject.ParamSpec.boolean( + 'custom-banner-enabled', + 'Custom banner enabled', + 'Whether the custom banner should be displayed', + GObject.ParamFlags.READWRITE, + false + ), + 'focused-monitor-index': GObject.ParamSpec.int( + 'focused-monitor-index', + 'Focused Monitor Index', + 'Index of the monitor that is currently focused', + GObject.ParamFlags.READWRITE, + -1, 100, -1 + ), + 'focused-monitor-details': GObject.ParamSpec.jsobject( + 'focused-monitor-details', + 'Focused Monitor Details', + 'Details about the monitor that is currently focused', + GObject.ParamFlags.READWRITE + ), + 'display-size': GObject.ParamSpec.double( + 'display-size', + 'Display size', + 'Size of the display', + GObject.ParamFlags.READWRITE, + 0.2, + 2.5, + 1.0 + ), + 'display-zoom-on-focus': GObject.ParamSpec.boolean( + 'display-zoom-on-focus', + 'Display zoom on focus', + 'Automatically move a display closer when it becomes focused.', + GObject.ParamFlags.READWRITE, + true + ), + 'display-distance': GObject.ParamSpec.double( + 'display-distance', + 'Display Distance', + 'Distance of the display from the camera', + GObject.ParamFlags.READWRITE, + 0.2, + 2.5, + 1.05 + ), + 'lens-vector': GObject.ParamSpec.jsobject( + 'lens-vector', + 'Lens Vector', + 'Vector representing the offset of the lens from the pivot point', + GObject.ParamFlags.READWRITE + ), + 'toggle-display-distance-start': GObject.ParamSpec.double( + 'toggle-display-distance-start', + 'Display distance start', + 'Start distance when using the "change distance" shortcut.', + GObject.ParamFlags.READWRITE, + 0.2, + 2.5, + 1.05 + ), + 'toggle-display-distance-end': GObject.ParamSpec.double( + 'toggle-display-distance-end', + 'Display distance end', + 'End distance when using the "change distance" shortcut.', + GObject.ParamFlags.READWRITE, + 0.2, + 2.5, + 1.05 + ), + 'framerate-cap': GObject.ParamSpec.double( + 'framerate-cap', + 'Framerate Cap', + 'Maximum framerate to render at', + GObject.ParamFlags.READWRITE, + 0.0, 240.0, 0.0 + ), + 'look-ahead-override': GObject.ParamSpec.int( + 'look-ahead-override', + 'Look ahead override', + 'Override the look ahead value', + GObject.ParamFlags.READWRITE, + -1, + 45, + -1 + ), + 'disable-anti-aliasing': GObject.ParamSpec.boolean( + 'disable-anti-aliasing', + 'Disable anti-aliasing', + 'Disable anti-aliasing for the effect', + GObject.ParamFlags.READWRITE, + false + ) + } +}, class VirtualDisplaysActor extends Clutter.Actor { + constructor(params = {}) { + super(params); + + this._all_monitors = [ + this.target_monitor, + ...this.virtual_monitors + ] + + const bannerTextureClippingRect = new Mtk.Rectangle({ + x: 0, + y: 0, + width: 800, + height: 200 + }); + + const calibratingBanner = GdkPixbuf.Pixbuf.new_from_file(`${Globals.extension_dir}/textures/calibrating.png`); + const calibratingImage = new Clutter.Image(); + calibratingImage.set_data(calibratingBanner.get_pixels(), Cogl.PixelFormat.RGB_888, + calibratingBanner.width, calibratingBanner.height, calibratingBanner.rowstride); + this.bannerContent = Clutter.TextureContent.new_from_texture(calibratingImage.get_texture(), bannerTextureClippingRect); + + const customBanner = GdkPixbuf.Pixbuf.new_from_file(`${Globals.extension_dir}/textures/custom_banner.png`); + const customBannerImage = new Clutter.Image(); + customBannerImage.set_data(customBanner.get_pixels(), Cogl.PixelFormat.RGB_888, + customBanner.width, customBanner.height, customBanner.rowstride); + this.customBannerContent = Clutter.TextureContent.new_from_texture(customBannerImage.get_texture(), bannerTextureClippingRect); + + this.bannerActor = new Clutter.Actor({ + width: calibratingBanner.width, + height: calibratingBanner.height, + reactive: false + }); + this.bannerActor.set_position( + (this.target_monitor.width - this.bannerActor.width) / 2, + this.target_monitor.height * 0.75 - this.bannerActor.height / 2 + ); + this.bannerActor.set_content(this.custom_banner_enabled ? this.customBannerContent : this.bannerContent); + this.bannerActor.hide(); + + this.monitor_actors = []; + } + + renderMonitors() { + // collect bindings and connections to clean up on dispose + this._property_bindings = []; + this._property_connections = []; + + const notifyToFunction = ((property, fn) => { + this._property_connections.push(this.connect(`notify::${property}`, fn.bind(this))); + }).bind(this); + + this._distance_ease_timeline = null; + notifyToFunction('toggle-display-distance-start', this._handle_display_distance_properties_change); + notifyToFunction('toggle-display-distance-end', this._handle_display_distance_properties_change); + notifyToFunction('display-distance', this._handle_display_distance_properties_change); + notifyToFunction('monitor-wrapping-scheme', this._update_monitor_placements); + notifyToFunction('monitor-spacing', this._update_monitor_placements); + notifyToFunction('viewport-offset-x', this._update_monitor_placements); + notifyToFunction('viewport-offset-y', this._update_monitor_placements); + notifyToFunction('show-banner', this._handle_banner_update); + notifyToFunction('custom-banner-enabled', this._handle_banner_update); + 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_frame_rate_cap_change(); + + const actorToDisplayRatios = [ + global.stage.width / this.target_monitor.width, + global.stage.height / this.target_monitor.height + ]; + + // how far this viewport actor's center is from the center of the whole stage + const actorMidX = this.target_monitor.x + this.target_monitor.width / 2; + const actorMidY = this.target_monitor.y + this.target_monitor.height / 2; + const actorToDisplayOffsets = [ + (global.stage.width / 2 - (actorMidX - global.stage.x)) * 2 / this.target_monitor.width, + (global.stage.height / 2 - (actorMidY - global.stage.y)) * 2 / this.target_monitor.height + ]; + + Globals.logger.log_debug(`\t\t\tActor to display ratios: ${actorToDisplayRatios}, offsets: ${actorToDisplayOffsets}`); + + this._sorted_monitors.forEach(((monitor, index) => { + Globals.logger.log_debug(`\t\t\tMonitor ${index}: ${monitor.x}, ${monitor.y}, ${monitor.width}, ${monitor.height}`); + + const containerActor = new Clutter.Actor({ + clip_to_allocation: true + }); + const viewport = new St.Bin({ + child: containerActor, + width: monitor.width, + height: monitor.height + }); + + // Create a clone of the stage content for this monitor + const monitorClone = new Clutter.Clone({ + source: Main.layoutManager.uiGroup, + clip_to_allocation: true, + x: -monitor.x, + y: -monitor.y + }); + + // Add the monitor actor to the scene + containerActor.add_child(monitorClone); + const effect = new VirtualDisplayEffect({ + focused_monitor_index: this.focused_monitor_index, + imu_snapshots: this.imu_snapshots, + monitor_index: index, + monitor_placements: this.monitor_placements, + target_monitor: this.target_monitor, + display_distance: this.display_distance, + display_distance_default: this._display_distance_default(), + actor_to_display_ratios: actorToDisplayRatios, + actor_to_display_offsets: actorToDisplayOffsets, + lens_vector: this.lens_vector, + show_banner: this.show_banner + }); + viewport.add_effect_with_name('viewport-effect', effect); + this.add_child(viewport); + Shell.util_set_hidden_from_pick(viewport, true); + + this.monitor_actors.push({ + viewport, + containerActor, + monitorClone, + effect, + monitorDetails: monitor + }); + + // do this so the primary monitor is always on top at first, before the focused monitor logic comes into play + this.set_child_below_sibling(viewport, null); + + [ + 'monitor-placements', + 'imu-snapshots', + 'smooth-follow-enabled', + 'smooth-follow-toggle-epoch-ms', + 'focused-monitor-index', + 'lens-vector', + 'look-ahead-override', + 'disable-anti-aliasing', + 'show-banner' + ].forEach((property => { + this._property_bindings.push(this.bind_property(property, effect, property, GObject.BindingFlags.DEFAULT)); + })); + + const updateEffectDistanceDefault = (() => { + effect.no_distance_ease = Math.abs(this.display_distance - effect.display_distance) <= 0.05; + effect.display_distance = this.display_distance; + effect.display_distance_default = this._display_distance_default(); + }).bind(this); + this._property_connections.push(this.connect('notify::display-distance', updateEffectDistanceDefault)); + this._property_connections.push(this.connect('notify::toggle-display-distance-start', updateEffectDistanceDefault)); + this._property_connections.push(this.connect('notify::toggle-display-distance-end', updateEffectDistanceDefault)); + + // in addition to rendering distance properly in the shader, the parent actor determines overlap based on child ordering + effect.connect('notify::is-closest', ((actor, _pspec) => { + if (!this._is_disposed && actor.is_closest) { + this.set_child_above_sibling(viewport, null); + if (this.show_banner) this.set_child_above_sibling(this.bannerActor, null); + } + }).bind(this)); + }).bind(this)); + + this.add_child(this.bannerActor); + if (this.show_banner) { + this.set_child_above_sibling(this.bannerActor, null); + this.bannerActor.show(); + } + + GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, (() => { + if (this._is_disposed) return GLib.SOURCE_REMOVE; + + if (this.show_banner) { + this.focused_monitor_index = -1; + this.focused_monitor_details = null; + } 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( + currentPoseQuat, + this._monitorsAsNormalizedVectors, + this.focused_monitor_index, + this.display_distance / this._display_distance_default(), + this.smooth_follow_enabled, + this._fov_details(), + this._sorted_monitors + ); + + if (this.focused_monitor_index !== focusedMonitorIndex) { + Globals.logger.log_debug(`Switching to monitor ${focusedMonitorIndex}`); + this.focused_monitor_index = focusedMonitorIndex; + this.focused_monitor_details = this._sorted_monitors[focusedMonitorIndex]; + } + } + + return GLib.SOURCE_CONTINUE; + }).bind(this)); + + this._redraw_timeline = Clutter.Timeline.new_for_actor(global.stage, 1000); + this._redraw_timeline.connect('new-frame', (() => { + // let's try to cap the forced redraw rate + if (this._is_disposed || this._last_redraw !== undefined && Date.now() - this._last_redraw < this._cap_frametime_ms) return; + + Globals.data_stream.refresh_data(); + this.imu_snapshots = Globals.data_stream.imu_snapshots; + this.monitor_actors.forEach(({ monitorClone }) => monitorClone.queue_redraw()); + this._last_redraw = Date.now(); + }).bind(this)); + this._redraw_timeline.set_repeat_count(-1); + this._redraw_timeline.start(); + } + + _display_distance_default() { + return Math.max(this.display_distance, this.toggle_display_distance_start, this.toggle_display_distance_end); + } + + _fov_details() { + const aspect = this.target_monitor.width / this.target_monitor.height; + const fovRadians = diagonalToCrossFOVs(degreeToRadian(Globals.data_stream.device_data.displayFov), aspect); + const defaultDistanceVerticalRadians = 2 * Math.atan(Math.tan(fovRadians.vertical / 2) / this._display_distance_default()); + const defaultDistanceHorizontalRadians = 2 * Math.atan(Math.tan(fovRadians.horizontal / 2) / this._display_distance_default()); + + // distance needed for the FOV-sized monitor to fill up the screen + const fullScreenDistance = this.target_monitor.height / 2 / Math.tan(fovRadians.vertical / 2); + const lensDistancePixels = fullScreenDistance / (1.0 - Globals.data_stream.device_data.lensDistanceRatio) - fullScreenDistance; + + // distance of a display at the default (most zoomed out) distance, plus the lens distance constant + const lensToScreenDistance = this.target_monitor.height / 2 / Math.tan(defaultDistanceVerticalRadians / 2); + const completeScreenDistancePixels = lensToScreenDistance + lensDistancePixels; + + return { + widthPixels: this.target_monitor.width, + heightPixels: this.target_monitor.height, + defaultDistanceVerticalRadians, + defaultDistanceHorizontalRadians, + lensDistancePixels, + completeScreenDistancePixels + }; + } + + _horizontal_monitor_sort() { + return [...this._all_monitors].sort((a, b) => { + // First compare by y-coordinate to form rows (top to bottom) + if (a.y !== b.y) { + return a.y - b.y; + } + // Then compare by x-coordinate within the same row (left to right) + return a.x - b.x; + }); + } + + _vertical_monitor_sort() { + return [...this._all_monitors].sort((a, b) => { + // First compare by x-coordinate to form columns (left to right) + if (a.x !== b.x) { + return a.x - b.x; + } + // Then compare by y-coordinate within the same column (top to bottom) + return a.y - b.y; + }); + } + + _update_monitor_placements() { + // collect minimum and maximum x and y values of monitors + let actualWrapScheme = this.monitor_wrapping_scheme; + if (actualWrapScheme === 'automatic') { + const minX = Math.min(...this._all_monitors.map(monitor => monitor.x)); + const minY = Math.min(...this._all_monitors.map(monitor => monitor.y)); + const maxX = Math.max(...this._all_monitors.map(monitor => monitor.x + monitor.width)); + const maxY = Math.max(...this._all_monitors.map(monitor => monitor.y + monitor.height)); + + // check if there are more monitors in the horizontal or vertical direction, prefer horizontal if equal + if ((maxX - minX) / this.target_monitor.width >= (maxY - minY) / this.target_monitor.height) { + actualWrapScheme = 'horizontal'; + } else { + actualWrapScheme = 'vertical'; + } + } + + // use horizontal in all cases but vertical wrapping + this._sorted_monitors = actualWrapScheme === 'vertical' ? + this._vertical_monitor_sort() : + this._horizontal_monitor_sort(); + + const fovDetails = this._fov_details(); + this.lens_vector = [0.0, 0.0, -fovDetails.lensDistancePixels]; + this.monitor_placements = monitorsToPlacements( + fovDetails, + + // shift all monitors so they center around the target monitor, then adjusted by the offsets + this._sorted_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 + })), + actualWrapScheme, + this.monitor_spacing / 1000.0 + ); + + // normalize the center vectors + this._monitorsAsNormalizedVectors = this.monitor_placements.map(monitorVectors => { + const vector = monitorVectors.centerLook; + 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); + const distance_from_start = Math.abs(this.display_distance - this.toggle_display_distance_start); + this._is_display_distance_at_end = distance_from_end < distance_from_start; + this._update_monitor_placements(); + } + + _handle_banner_update() { + if (this.show_banner) { + this.bannerActor.set_content(this.custom_banner_enabled ? this.customBannerContent : this.bannerContent); + this.bannerActor.show(); + } else { + this.bannerActor.hide(); + } + } + + _handle_frame_rate_cap_change() { + // add a margin to the cap time so we don't cut off frames that come in close + const frametime_margin = 0.75; + 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() { + this.display_distance = this._is_display_distance_at_end ? + this.toggle_display_distance_start : this.toggle_display_distance_end; + } + + vfunc_dispose() { + Globals.logger.log_debug(`Disposing VirtualMonitorsActor`); + this._is_disposed = true; + + if (this._redraw_timeline) { + this._redraw_timeline.stop(); + this._redraw_timeline = null; + } + + this.monitor_actors.forEach(({ viewport, containerActor, monitorClone, effect }) => { + viewport.remove_effect(effect); + containerActor.remove_child(monitorClone); + viewport.remove_child(containerActor); + this.remove_child(viewport); + }); + this.monitor_actors = []; + + this._property_bindings.forEach(binding => binding.unbind()); + this._property_bindings = []; + + this._property_connections.forEach(connection => this.disconnect(connection)); + } +}); \ No newline at end of file diff --git a/gnome/src/xrEffect.js b/gnome/src/xrEffect.js deleted file mode 100644 index 19533f1..0000000 --- a/gnome/src/xrEffect.js +++ /dev/null @@ -1,453 +0,0 @@ -import Clutter from 'gi://Clutter'; -import Cogl from 'gi://Cogl'; -import GdkPixbuf from 'gi://GdkPixbuf'; -import GLib from 'gi://GLib'; -import GObject from 'gi://GObject'; -import Shell from 'gi://Shell'; - -import Globals from './globals.js'; - -import { - dataViewEnd, - dataViewUint8, - dataViewBigUint, - dataViewUint32Array, - dataViewUint8Array, - dataViewFloat, - dataViewFloatArray, - BOOL_SIZE, - DATA_VIEW_INFO_COUNT_INDEX, - DATA_VIEW_INFO_OFFSET_INDEX, - FLOAT_SIZE, - UINT_SIZE, - UINT8_SIZE -} from "./ipc.js"; -import { degreeToRadian } from "./math.js"; -import { getShaderSource } from "./shader.js"; -import { isValidKeepAlive, toSec } from "./time.js"; - -export const IPC_FILE_PATH = "/dev/shm/breezy_desktop_imu"; - -// the driver should be using the same data layout version -const DATA_LAYOUT_VERSION = 3; - -// DataView info: [offset, size, count] -const VERSION = [0, UINT8_SIZE, 1]; -const ENABLED = [dataViewEnd(VERSION), BOOL_SIZE, 1]; -const LOOK_AHEAD_CFG = [dataViewEnd(ENABLED), FLOAT_SIZE, 4]; -const DISPLAY_RES = [dataViewEnd(LOOK_AHEAD_CFG), UINT_SIZE, 2]; -const DISPLAY_FOV = [dataViewEnd(DISPLAY_RES), FLOAT_SIZE, 1]; -const LENS_DISTANCE_RATIO = [dataViewEnd(DISPLAY_FOV), FLOAT_SIZE, 1]; -const SBS_ENABLED = [dataViewEnd(LENS_DISTANCE_RATIO), BOOL_SIZE, 1]; -const CUSTOM_BANNER_ENABLED = [dataViewEnd(SBS_ENABLED), BOOL_SIZE, 1]; -const EPOCH_MS = [dataViewEnd(CUSTOM_BANNER_ENABLED), UINT_SIZE, 2]; -const IMU_QUAT_DATA = [dataViewEnd(EPOCH_MS), FLOAT_SIZE, 16]; -const IMU_PARITY_BYTE = [dataViewEnd(IMU_QUAT_DATA), UINT8_SIZE, 1]; -const DATA_VIEW_LENGTH = dataViewEnd(IMU_PARITY_BYTE); - -// cached after first retrieval -const shaderUniformLocations = { - 'virtual_display_enabled': null, - 'show_banner': null, - 'imu_quat_data': null, - 'look_ahead_cfg': null, - 'look_ahead_ms': null, - 'trim_percent': null, - 'display_size': null, - 'display_north_offset': null, - 'lens_vector': null, - 'lens_vector_r': null, // only used if sbs_enabled is true - 'texcoord_x_limits': null, // index 0: min; index 1: max - 'texcoord_x_limits_r': null, // only used if sbs_enabled is true - 'sbs_enabled': null, - 'custom_banner_enabled': null, - 'half_fov_z_rads': null, - 'half_fov_y_rads': null, - 'fov_half_widths': null, - 'fov_widths': null, - 'display_resolution': null, - 'source_to_display_ratio': null, - 'curved_display': null, - - // only used by the reshade integration, but needs to be set to a default value by this effect - 'frametime': null, - 'sideview_enabled': null, - 'sideview_position': null, - 'sideview_display_size': null -}; - -function setUniformFloat(effect, locationName, dataViewInfo, value) { - effect.set_uniform_float(shaderUniformLocations[locationName], dataViewInfo[DATA_VIEW_INFO_COUNT_INDEX], value); -} - -function transferUniformFloat(effect, locationName, dataView, dataViewInfo) { - setUniformFloat(effect, locationName, dataViewInfo, dataViewFloatArray(dataView, dataViewInfo)); -} - -function setSingleFloat(effect, locationName, value) { - effect.set_uniform_float(shaderUniformLocations[locationName], 1, [value]); -} - -function setUniformMatrix(effect, locationName, components, dataView, dataViewInfo) { - const numValues = dataViewInfo[DATA_VIEW_INFO_COUNT_INDEX]; - if (numValues / components !== components) { - throw new Error('Invalid matrix size'); - } - - const floatArray = [].fill(0, 0, numValues); - let offset = dataViewInfo[DATA_VIEW_INFO_OFFSET_INDEX]; - for (let i = 0; i < numValues; i++) { - // GLSL uses column-major order, so we need to transpose the matrix - const row = i % components; - const column = Math.floor(i / components); - - floatArray[row * components + column] = dataView.getFloat32(offset, true); - offset += FLOAT_SIZE; - } - effect.set_uniform_matrix(shaderUniformLocations[locationName], true, components, floatArray); -} - -function lookAheadMS(dataView) { - const lookAheadCfg = dataViewFloatArray(dataView, LOOK_AHEAD_CFG); - const imuDateMS = dataViewBigUint(dataView, EPOCH_MS); - - // how stale the imu data is - const dataAge = Date.now() - imuDateMS; - - return lookAheadCfg[0] + dataAge; -} - -// most uniforms don't change frequently, this function should be called periodically -function setIntermittentUniformVariables() { - try { - const dataView = this._dataView; - - if (dataView.byteLength === DATA_VIEW_LENGTH) { - const version = dataViewUint8(dataView, VERSION); - const imuDateMs = dataViewBigUint(dataView, EPOCH_MS); - const validKeepalive = isValidKeepAlive(toSec(imuDateMs)); - const imuData = dataViewFloatArray(dataView, IMU_QUAT_DATA); - const imuResetState = validKeepalive && imuData[0] === 0.0 && imuData[1] === 0.0 && imuData[2] === 0.0 && imuData[3] === 1.0; - const enabled = dataViewUint8(dataView, ENABLED) !== 0 && version === DATA_LAYOUT_VERSION && validKeepalive; - const displayRes = dataViewUint32Array(dataView, DISPLAY_RES); - const sbsEnabled = dataViewUint8(dataView, SBS_ENABLED) !== 0; - - if (enabled) { - const displayFov = dataViewFloat(dataView, DISPLAY_FOV); - - // TODO - drive these values from settings - const sbsContent = false; - const sbsModeStretched = true; - - // compute these values once, they only change when the XR device changes - const displayAspectRatio = displayRes[0] / displayRes[1]; - const diagToVertRatio = Math.sqrt(Math.pow(displayAspectRatio, 2) + 1); - const halfFovZRads = degreeToRadian(displayFov / diagToVertRatio) / 2; - const halfFovYRads = halfFovZRads * displayAspectRatio; - const fovHalfWidths = [Math.tan(halfFovYRads), Math.tan(halfFovZRads)]; - const fovWidths = [fovHalfWidths[0] * 2, fovHalfWidths[1] * 2]; - const lensDistanceRatio = dataViewFloat(dataView, LENS_DISTANCE_RATIO); - let lensFromCenter = 0.0; - let texcoordXLimits = [0.0, 1.0]; - let texcoordXLimitsRight = [0.0, 1.0]; - if (sbsEnabled) { - lensFromCenter = lensDistanceRatio / 3.0; - if (sbsContent) { - texcoordXLimits[1] = 0.5; - texcoordXLimitsRight[0] = 0.5; - if (!sbsModeStretched) { - texcoordXLimits[0] = 0.25; - texcoordXLimitsRight[1] = 0.75; - } - } else if (!sbsModeStretched) { - texcoordXLimits[0] = 0.25; - texcoordXLimits[1] = 0.75; - texcoordXLimitsRight[0] = 0.25; - texcoordXLimitsRight[1] = 0.75; - } - } - const lensVector = [lensDistanceRatio, lensFromCenter, 0.0]; - const lensVectorRight = [lensDistanceRatio, -lensFromCenter, 0.0]; - - // our overlay doesn't quite cover the full screen texture, which allows us to see some of the real desktop - // underneath, so we trim three pixels around the entire edge of the texture - const trimWidthPercent = 3.0 / this.target_monitor.width; - const trimHeightPercent = 3.0 / this.target_monitor.height; - - // all these values are transferred directly, unmodified from the driver - transferUniformFloat(this, 'look_ahead_cfg', dataView, LOOK_AHEAD_CFG); - transferUniformFloat(this, 'lens_distance_ratio', dataView, LENS_DISTANCE_RATIO); - - // computed values with no dataViewInfo, so we set these manually - this.set_uniform_float(shaderUniformLocations['trim_percent'], 2, [trimWidthPercent, trimHeightPercent]); - setSingleFloat(this, 'half_fov_z_rads', halfFovZRads); - setSingleFloat(this, 'half_fov_y_rads', halfFovYRads); - this.set_uniform_float(shaderUniformLocations['fov_half_widths'], 2, fovHalfWidths); - this.set_uniform_float(shaderUniformLocations['fov_widths'], 2, fovWidths); - setSingleFloat(this, 'curved_display', this.curved_display ? 1.0 : 0.0); - this.set_uniform_float(shaderUniformLocations['texcoord_x_limits'], 2, texcoordXLimits); - this.set_uniform_float(shaderUniformLocations['texcoord_x_limits_r'], 2, texcoordXLimitsRight); - this.set_uniform_float(shaderUniformLocations['lens_vector'], 3, lensVector); - this.set_uniform_float(shaderUniformLocations['lens_vector_r'], 3, lensVectorRight); - } - - // update the supported device detected property if the state changes, trigger "notify::" events - if (this.supported_device_detected !== validKeepalive) this.supported_device_detected = validKeepalive; - - // 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; - - // these variables are always in play, even if enabled is false - setSingleFloat(this, 'virtual_display_enabled', enabled ? 1.0 : 0.0); - setSingleFloat(this, 'show_banner', imuResetState ? 1.0 : 0.0); - setSingleFloat(this, 'sbs_enabled', sbsEnabled ? 1.0 : 0.0); - setSingleFloat(this, 'custom_banner_enabled', dataViewUint8(dataView, CUSTOM_BANNER_ENABLED) !== 0 ? 1.0 : 0.0); - setSingleFloat(this, 'frametime', 0.0); - - setSingleFloat(this, 'sideview_enabled', 0.0); - setSingleFloat(this, 'sideview_position', 0.0); - setSingleFloat(this, 'sideview_display_size', 1.0); - - this.set_uniform_float(shaderUniformLocations['display_resolution'], 2, displayRes); - this.set_uniform_float(shaderUniformLocations['source_to_display_ratio'], 2, [this.target_monitor.width/displayRes[0], this.target_monitor.height/displayRes[1]]); - } else if (dataView.byteLength !== 0) { - throw new Error(`Invalid dataView.byteLength: ${dataView.byteLength} !== ${DATA_VIEW_LENGTH}`); - } - } catch (e) { - Globals.logger.log(`[ERROR] xrEffect.js setIntermittentUniformVariables ${e.message}\n${e.stack}`); - } -} - -function checkParityByte(dataView) { - const parityByte = dataViewUint8(dataView, IMU_PARITY_BYTE); - let parity = 0; - const epochUint8 = dataViewUint8Array(dataView, EPOCH_MS); - const imuDataUint8 = dataViewUint8Array(dataView, IMU_QUAT_DATA); - for (let i = 0; i < epochUint8.length; i++) { - parity ^= epochUint8[i]; - } - for (let i = 0; i < imuDataUint8.length; i++) { - parity ^= imuDataUint8[i]; - } - return parityByte === parity; -} - -export const XREffect = GObject.registerClass({ - Properties: { - 'supported-device-detected': GObject.ParamSpec.boolean( - 'supported-device-detected', - 'Supported device detected', - 'Whether a supported device is connected', - GObject.ParamFlags.READWRITE, - false - ), - 'target-monitor': GObject.ParamSpec.jsobject( - 'target-monitor', - 'Target Monitor', - 'Geometry of the target monitor for this effect', - GObject.ParamFlags.READWRITE - ), - 'target-framerate': GObject.ParamSpec.uint( - 'target-framerate', - 'Target Framerate', - 'Target framerate for this effect', - GObject.ParamFlags.READWRITE, 30, 240, 60 - ), - 'display-distance': GObject.ParamSpec.double( - 'display-distance', - 'Display Distance', - 'How far away the display appears', - GObject.ParamFlags.READWRITE, - 0.2, - 2.5, - 1.05 - ), - 'display-size': GObject.ParamSpec.double( - 'display-size', - 'Display size', - 'Size of the display', - GObject.ParamFlags.READWRITE, - 0.2, - 2.5, - 1.0 - ), - 'toggle-display-distance-start': GObject.ParamSpec.double( - 'toggle-display-distance-start', - 'Display distance start', - 'Start distance when using the "change distance" shortcut.', - GObject.ParamFlags.READWRITE, - 0.2, - 2.5, - 1.05 - ), - 'toggle-display-distance-end': GObject.ParamSpec.double( - 'toggle-display-distance-end', - 'Display distance end', - 'End distance when using the "change distance" shortcut.', - GObject.ParamFlags.READWRITE, - 0.2, - 2.5, - 1.05 - ), - 'curved-display': GObject.ParamSpec.boolean( - 'curved-display', - 'Curved Display', - 'Whether the display is curved', - GObject.ParamFlags.READWRITE, - false - ), - 'widescreen-mode-state': GObject.ParamSpec.boolean( - 'widescreen-mode-state', - 'Widescreen mode state', - 'The state of widescreen mode from the perspective of the driver', - GObject.ParamFlags.READWRITE, - false - ), - 'look-ahead-override': GObject.ParamSpec.int( - 'look-ahead-override', - 'Look ahead override', - 'Override the look ahead value', - GObject.ParamFlags.READWRITE, - -1, - 45, - -1 - ), - 'disable-anti-aliasing': GObject.ParamSpec.boolean( - 'disable-anti-aliasing', - 'Disable anti-aliasing', - 'Disable anti-aliasing for the effect', - GObject.ParamFlags.READWRITE, - false - ) - } -}, class XREffect extends Shell.GLSLEffect { - constructor(params = {}) { - super(params); - - this._distance_ease_timeline = null; - 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._handle_display_distance_properties_change(); - - const calibrating = GdkPixbuf.Pixbuf.new_from_file(`${Globals.extension_dir}/textures/calibrating.png`); - this.calibratingImage = new Clutter.Image(); - this.calibratingImage.set_data(calibrating.get_pixels(), Cogl.PixelFormat.RGB_888, - calibrating.width, calibrating.height, calibrating.rowstride); - - const customBanner = GdkPixbuf.Pixbuf.new_from_file(`${Globals.extension_dir}/textures/custom_banner.png`); - this.customBannerImage = new Clutter.Image(); - this.customBannerImage.set_data(customBanner.get_pixels(), Cogl.PixelFormat.RGB_888, - customBanner.width, customBanner.height, customBanner.rowstride); - } - - _handle_display_distance_properties_change() { - const distance_from_end = Math.abs(this.display_distance - this.toggle_display_distance_end); - const distance_from_start = Math.abs(this.display_distance - this.toggle_display_distance_start); - this._is_display_distance_at_end = distance_from_end < distance_from_start; - } - - _change_distance() { - if (this._distance_ease_timeline?.is_playing()) this._distance_ease_timeline.stop(); - - this._distance_ease_start = this.display_distance; - this._distance_ease_timeline = Clutter.Timeline.new_for_actor(this.get_actor(), 250); - - const toggle_display_distance_target = this._is_display_distance_at_end ? - this.toggle_display_distance_start : this.toggle_display_distance_end; - this._distance_ease_timeline.connect('new-frame', () => { - this.display_distance = this._distance_ease_start + - this._distance_ease_timeline.get_progress() * - (toggle_display_distance_target - this._distance_ease_start); - }); - - this._distance_ease_timeline.start(); - } - - vfunc_build_pipeline() { - const code = getShaderSource(`${Globals.extension_dir}/Sombrero.frag`); - const main = 'PS_Sombrero(virtual_display_enabled, false, source_to_display_ratio, show_banner, cogl_tex_coord_in[0].xy, cogl_color_out);'; - this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, code, main, false); - } - - vfunc_paint_target(node, paintContext) { - var calibratingImage = this.calibratingImage; - var customBannerImage = this.customBannerImage; - let data = Globals.ipc_file.load_contents(null); - if (data[0]) { - let buffer = new Uint8Array(data[1]).buffer; - this._dataView = new DataView(buffer); - if (!this._initialized) { - this.set_uniform_float(this.get_uniform_location('screenTexture'), 1, [0]); - - this.get_pipeline().set_layer_texture(1, calibratingImage.get_texture()); - this.get_pipeline().set_layer_texture(2, customBannerImage.get_texture()); - this.get_pipeline().set_uniform_1i(this.get_uniform_location('calibratingTexture'), 1); - this.get_pipeline().set_uniform_1i(this.get_uniform_location('customBannerTexture'), 2); - - for (let key in shaderUniformLocations) { - shaderUniformLocations[key] = this.get_uniform_location(key); - } - this.setIntermittentUniformVariables = setIntermittentUniformVariables.bind(this); - this.setIntermittentUniformVariables(); - - this._redraw_timeline = Clutter.Timeline.new_for_actor(this.get_actor(), 1000); - this._redraw_timeline.connect('new-frame', (() => { - this.queue_repaint(); - }).bind(this)); - this._redraw_timeline.set_repeat_count(-1); - this._redraw_timeline.start(); - - this._uniforms_timeout_id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, (() => { - this.setIntermittentUniformVariables(); - return GLib.SOURCE_CONTINUE; - }).bind(this)); - - this._initialized = true; - } - - let success = false; - let attempts = 0; - while (!success && attempts < 2) { - if (this._dataView.byteLength === DATA_VIEW_LENGTH) { - if (checkParityByte(this._dataView)) { - setSingleFloat(this, 'display_north_offset', this.display_distance); - setSingleFloat(this, 'look_ahead_ms', - this.look_ahead_override === -1 ? lookAheadMS(this._dataView) : this.look_ahead_override); - setUniformMatrix(this, 'imu_quat_data', 4, this._dataView, IMU_QUAT_DATA); - setSingleFloat(this, 'display_size', this.display_size); - success = true; - } - } else if (this._dataView.byteLength !== 0) { - Globals.logger.log(`[ERROR] Invalid dataView.byteLength: ${this._dataView.byteLength} !== ${DATA_VIEW_LENGTH}`) - } - - if (!success && ++attempts < 3) { - data = Globals.ipc_file.load_contents(null); - if (data[0]) { - buffer = new Uint8Array(data[1]).buffer; - this._dataView = new DataView(buffer); - } - } - } - - if (!this.disable_anti_aliasing) { - // improves sampling quality for smooth text and edges - this.get_pipeline().set_layer_filters( - 0, - Cogl.PipelineFilter.LINEAR_MIPMAP_LINEAR, - Cogl.PipelineFilter.LINEAR - ); - } - } - super.vfunc_paint_target(node, paintContext); - } - - cleanup() { - if (this._redraw_timeline) { - this._redraw_timeline.stop(); - this._redraw_timeline = null; - } - if (this._uniforms_timeout_id) GLib.source_remove(this._uniforms_timeout_id); - } -}); \ No newline at end of file diff --git a/modules/XRLinuxDriver b/modules/XRLinuxDriver index 53002af..a66c9ae 160000 --- a/modules/XRLinuxDriver +++ b/modules/XRLinuxDriver @@ -1 +1 @@ -Subproject commit 53002afde8406eef7fe309206268ad6dc39e2da6 +Subproject commit a66c9aee82989ea7e98da5a05fde621ffb47af83 diff --git a/ui/bin/package b/ui/bin/package index d66e731..f41fe2d 100755 --- a/ui/bin/package +++ b/ui/bin/package @@ -43,6 +43,7 @@ cp $UI_BUILD_PATH/src/breezydesktop.gresource $PACKAGE_BREEZY_DIR cp -r po/mo/* $PACKAGE_LOCALE_DIR cp data/com.xronlinux.BreezyDesktop.gschema.xml $PACKAGE_SCHEMAS_DIR cp $UI_BUILD_PATH/src/breezydesktop $PACKAGE_BIN_DIR +cp $UI_BUILD_PATH/src/virtualdisplay $PACKAGE_BIN_DIR cp $UI_BUILD_PATH/data/com.xronlinux.BreezyDesktop.desktop $PACKAGE_APPS_DIR mkdir -p $PACKAGE_ICONS_DIR/64x64/apps diff --git a/ui/data/com.xronlinux.BreezyDesktop.gschema.xml b/ui/data/com.xronlinux.BreezyDesktop.gschema.xml index b5aafdd..77ebb59 100644 --- a/ui/data/com.xronlinux.BreezyDesktop.gschema.xml +++ b/ui/data/com.xronlinux.BreezyDesktop.gschema.xml @@ -91,6 +91,42 @@ The size of the display + + + 0.0 + + Viewport offset x + + How far to offset the viewport from the target monitor in the x direction + + + + + 0.0 + + Viewport offset y + + How far to offset the viewport from the target monitor in the y direction + + + + + "automatic" + + Monitor wrapping scheme + + How the monitors are wrapped around the viewport + + + + + 0 + + Monitor spacing + + How far apart the monitors are visually (not logically) + + false @@ -120,7 +156,7 @@ - true + false Headset as primary @@ -154,6 +190,15 @@ Disable anti-aliasing + + + true + + Disable physical displays + + Disable physical displays when XR effect is enabled + + false @@ -172,6 +217,15 @@ Log debug messages + + + false + + Debug no device + + Debug no device + + "" @@ -180,6 +234,15 @@ Custom monitor product + + + + 0.0 + + Framerate cap + + Framerate cap + diff --git a/ui/modules/PyXRLinuxDriverIPC b/ui/modules/PyXRLinuxDriverIPC index c3b0dda..b012396 160000 --- a/ui/modules/PyXRLinuxDriverIPC +++ b/ui/modules/PyXRLinuxDriverIPC @@ -1 +1 @@ -Subproject commit c3b0ddab302e11e517b57b621ff334faf03173de +Subproject commit b01239646d7ea6f78a823f591946855b880f0484 diff --git a/ui/po/breezydesktop.pot b/ui/po/breezydesktop.pot index 9e6475c..9534c35 100644 --- a/ui/po/breezydesktop.pot +++ b/ui/po/breezydesktop.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-23 11:32-0700\n" +"POT-Creation-Date: 2025-03-05 12:28-0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,16 +17,58 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: src/connecteddevice.py:17 +#: src/connecteddevice.py:23 msgid "" "Switches your glasses into side-by-side mode and doubles the width of the " "display." msgstr "" -#: src/connecteddevice.py:18 +#: src/connecteddevice.py:24 msgid "This feature is not currently supported for your device." msgstr "" +#: src/connecteddevice.py:124 +msgid "Set Focused Display Distance" +msgstr "" + +#: src/connecteddevice.py:125 +msgid "Use a closer value so the display zooms in when you look at it." +msgstr "" + +#: src/connecteddevice.py:132 +msgid "Set All Displays Distance" +msgstr "" + +#: src/connecteddevice.py:133 +msgid "Use a farther value so the displays are zoomed out when you look away." +msgstr "" + +#: src/connecteddevice.py:231 +msgid "" +"Unable to add virtual displays on this machine. xdg-desktop-portal is " +"required." +msgstr "" + +#: src/connecteddevice.py:265 +msgid "Focused display" +msgstr "" + +#: src/connecteddevice.py:271 +msgid "All displays" +msgstr "" + +#: src/displaydistancedialogcontent.py:41 +msgid "closer" +msgstr "" + +#: src/displaydistancedialogcontent.py:45 +msgid "fullscreen" +msgstr "" + +#: src/displaydistancedialogcontent.py:48 +msgid "farther" +msgstr "" + #: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "" @@ -35,7 +77,7 @@ msgstr "" msgid "Feature Availability" msgstr "" -#: src/licensefeaturerow.py:15 src/shortcutdialog.py:107 +#: src/licensefeaturerow.py:15 src/shortcutdialog.py:104 msgid "Disabled" msgstr "" @@ -164,173 +206,235 @@ msgid "Enables the Breezy Desktop XR effect." msgstr "" #: src/gtk/connected-device.ui:55 -msgid "Widescreen mode" +msgid "Zoom on focus" msgstr "" -#: src/gtk/connected-device.ui:66 +#: src/gtk/connected-device.ui:56 +msgid "" +"Automatically move a display closer when you look at it.\n" +"Set your preferred focused and unfocused distances in the Adjustments " +"section.\n" +" " +msgstr "" + +#: src/gtk/connected-device.ui:69 msgid "Follow mode" msgstr "" -#: src/gtk/connected-device.ui:67 -msgid "Keep the virtual display near the center of your view." +#: src/gtk/connected-device.ui:70 +msgid "Keep the focused display near the center of your view." msgstr "" -#: src/gtk/connected-device.ui:77 -msgid "Curved display" +#: src/gtk/connected-device.ui:80 src/gtk/no-device.ui:34 +msgid "Disable physical displays" msgstr "" -#: src/gtk/connected-device.ui:78 -msgid "Switch between flat and curved displays." +#: src/gtk/connected-device.ui:81 src/gtk/no-device.ui:35 +msgid "" +"Automatically disable all physical displays when the XR effect is enabled." msgstr "" #: src/gtk/connected-device.ui:91 +msgid "Virtual displays" +msgstr "" + +#: src/gtk/connected-device.ui:103 +msgid "Rearrange displays" +msgstr "" + +#: src/gtk/connected-device.ui:115 +msgid "1080p" +msgstr "" + +#: src/gtk/connected-device.ui:116 +msgid "1440p" +msgstr "" + +#: src/gtk/connected-device.ui:140 msgid "Adjustments" msgstr "" -#: src/gtk/connected-device.ui:94 -msgid "Display distance" +#: src/gtk/connected-device.ui:143 +msgid "Display distances" msgstr "" -#: src/gtk/connected-device.ui:95 -msgid "" -"Closer appears larger, further appears smaller. Controls depth when in " -"widescreen mode." +#: src/gtk/connected-device.ui:144 +msgid "Set how close you want displays to appear." msgstr "" -#: src/gtk/connected-device.ui:123 -msgid "Display size" -msgstr "" - -#: src/gtk/connected-device.ui:124 -msgid "" -"Combine with display distance to achieve a comfortable level of depth and " -"size." -msgstr "" - -#: src/gtk/connected-device.ui:152 -msgid "Display toggle distances" -msgstr "" - -#: src/gtk/connected-device.ui:153 -msgid "" -"Use the buttons to capture the current display distance for use with the " -"keyboard shortcut." -msgstr "" - -#: src/gtk/connected-device.ui:178 +#: src/gtk/connected-device.ui:199 msgid "Follow threshold" msgstr "" -#: src/gtk/connected-device.ui:179 +#: src/gtk/connected-device.ui:200 msgid "How far away you can look before the display follows." msgstr "" -#: src/gtk/connected-device.ui:209 src/gtk/connected-device.ui:215 +#: src/gtk/connected-device.ui:223 +msgid "Display angling" +msgstr "" + +#: src/gtk/connected-device.ui:224 +msgid "" +"When there are multiple displays, choose how they should angle towards you." +msgstr "" + +#: src/gtk/connected-device.ui:238 +msgid "Automatic" +msgstr "" + +#: src/gtk/connected-device.ui:239 +msgid "Side-angled" +msgstr "" + +#: src/gtk/connected-device.ui:240 +msgid "Top-angled" +msgstr "" + +#: src/gtk/connected-device.ui:241 +msgid "Flat" +msgstr "" + +#: src/gtk/connected-device.ui:251 +msgid "Display spacing" +msgstr "" + +#: src/gtk/connected-device.ui:252 +msgid "Put empty space between displays, when there are multiple." +msgstr "" + +#: src/gtk/connected-device.ui:282 +msgid "Viewport horizontal offset" +msgstr "" + +#: src/gtk/connected-device.ui:283 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport to the left or right." +msgstr "" + +#: src/gtk/connected-device.ui:312 +msgid "Viewport vertical offset" +msgstr "" + +#: src/gtk/connected-device.ui:313 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport up or down." +msgstr "" + +#: src/gtk/connected-device.ui:349 src/gtk/connected-device.ui:355 msgid "Keyboard Shortcuts" msgstr "" -#: src/gtk/connected-device.ui:218 +#: src/gtk/connected-device.ui:358 msgid "XR Effect on/off shortcut" msgstr "" -#: src/gtk/connected-device.ui:219 +#: src/gtk/connected-device.ui:359 msgid "" "Quickly enable or disable the XR Effect. You may need to enable the effect " "manually once in order to enable the shortcut." msgstr "" -#: src/gtk/connected-device.ui:238 src/gtk/connected-device.ui:267 -#: src/gtk/connected-device.ui:296 src/gtk/connected-device.ui:325 +#: src/gtk/connected-device.ui:378 src/gtk/connected-device.ui:407 +#: src/gtk/connected-device.ui:436 src/gtk/connected-device.ui:465 msgid "Change" msgstr "" -#: src/gtk/connected-device.ui:247 +#: src/gtk/connected-device.ui:387 msgid "Re-center display shortcut" msgstr "" -#: src/gtk/connected-device.ui:248 +#: src/gtk/connected-device.ui:388 msgid "Pin the virtual display to the current position." msgstr "" -#: src/gtk/connected-device.ui:276 -msgid "Display distance shortcut" +#: src/gtk/connected-device.ui:416 +msgid "Toggle zoom on focus shortcut" msgstr "" -#: src/gtk/connected-device.ui:277 -msgid "Quickly toggle between two predefined distances." +#: src/gtk/connected-device.ui:417 +msgid "Quickly toggle zoom on focus mode." msgstr "" -#: src/gtk/connected-device.ui:305 +#: src/gtk/connected-device.ui:445 msgid "Toggle follow mode shortcut" msgstr "" -#: src/gtk/connected-device.ui:306 +#: src/gtk/connected-device.ui:446 msgid "Quickly toggle follow mode." msgstr "" -#: src/gtk/connected-device.ui:341 src/gtk/connected-device.ui:347 +#: src/gtk/connected-device.ui:481 src/gtk/connected-device.ui:487 msgid "Advanced Settings" msgstr "" -#: src/gtk/connected-device.ui:350 +#: src/gtk/connected-device.ui:490 msgid "Find optimal display config" msgstr "" -#: src/gtk/connected-device.ui:351 +#: src/gtk/connected-device.ui:491 msgid "" "Automatically modify the glasses display configuration for maximum " "resolution and best scaling when plugged in." msgstr "" -#: src/gtk/connected-device.ui:361 +#: src/gtk/connected-device.ui:501 msgid "Use highest refresh rate" msgstr "" -#: src/gtk/connected-device.ui:362 +#: src/gtk/connected-device.ui:502 msgid "Refresh rate may affect performance, disable this to set it manually." msgstr "" -#: src/gtk/connected-device.ui:372 +#: src/gtk/connected-device.ui:512 msgid "Always primary display" msgstr "" -#: src/gtk/connected-device.ui:373 +#: src/gtk/connected-device.ui:513 msgid "Automatically set the glasses as the primary display when plugged in." msgstr "" -#: src/gtk/connected-device.ui:383 -msgid "Fast SBS mode switching" -msgstr "" - -#: src/gtk/connected-device.ui:384 -msgid "" -"Switches glasses to SBS mode immediately when plugged in, if widescreen mode " -"is on. May cause instability." -msgstr "" - -#: src/gtk/connected-device.ui:394 +#: src/gtk/connected-device.ui:523 msgid "Movement look-ahead" msgstr "" -#: src/gtk/connected-device.ui:395 +#: src/gtk/connected-device.ui:524 msgid "" "Counteracts input lag by predicting head-tracking position ahead of render " "time. Stick with default unless virtual display drags behind your head " "movements, jumps ahead, or is very shaky." msgstr "" -#: src/gtk/connected-device.ui:413 +#: src/gtk/connected-device.ui:542 msgid "Default" msgstr "" -#: src/gtk/connected-device.ui:425 +#: src/gtk/connected-device.ui:554 msgid "Text Scaling" msgstr "" -#: src/gtk/connected-device.ui:426 +#: src/gtk/connected-device.ui:555 msgid "Scaling text below 1.0 will simulate a higher resolution display" msgstr "" +#: src/gtk/connected-device.ui:585 +msgid "Enable multi-tap detection" +msgstr "" + +#: src/gtk/connected-device.ui:586 +msgid "Enables double-tap to recenter and triple-tap to recalibrate." +msgstr "" + +#: src/gtk/display-distance-dialog.ui:9 +msgid "Show full range" +msgstr "" + +#: src/gtk/display-distance-dialog.ui:18 +msgid "Done" +msgstr "" + #: src/gtk/failed-verification.ui:13 msgid "Breezy Desktop GNOME invalid setup" msgstr "" @@ -375,14 +479,6 @@ msgid "" "connected." msgstr "" -#: src/gtk/no-device.ui:34 -msgid "Start in widescreen mode" -msgstr "" - -#: src/gtk/no-device.ui:35 -msgid "Widescreen mode is not supported for all glasses." -msgstr "" - #: src/gtk/no-driver.ui:13 msgid "No driver running" msgstr "" diff --git a/ui/po/de.po b/ui/po/de.po index 7bec3f2..14d88e9 100644 --- a/ui/po/de.po +++ b/ui/po/de.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-30 09:36-0700\n" +"POT-Creation-Date: 2025-03-05 12:28-0800\n" "PO-Revision-Date: 2024-08-02 20:54-0700\n" "Last-Translator: \n" "Language-Team: German \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: src/connecteddevice.py:17 +#: src/connecteddevice.py:23 msgid "" "Switches your glasses into side-by-side mode and doubles the width of the " "display." @@ -25,10 +25,56 @@ msgstr "" "Schaltet Ihre Brille in den Side-by-Side-Modus und verdoppelt die Breite des " "Displays." -#: src/connecteddevice.py:18 +#: src/connecteddevice.py:24 msgid "This feature is not currently supported for your device." msgstr "Diese Funktion wird von Ihrem Gerät derzeit nicht unterstützt." +#: src/connecteddevice.py:124 +#, fuzzy +msgid "Set Focused Display Distance" +msgstr "Display-Entfernung" + +#: src/connecteddevice.py:125 +msgid "Use a closer value so the display zooms in when you look at it." +msgstr "" + +#: src/connecteddevice.py:132 +#, fuzzy +msgid "Set All Displays Distance" +msgstr "Display-Entfernung" + +#: src/connecteddevice.py:133 +msgid "Use a farther value so the displays are zoomed out when you look away." +msgstr "" + +#: src/connecteddevice.py:231 +msgid "" +"Unable to add virtual displays on this machine. xdg-desktop-portal is " +"required." +msgstr "" + +#: src/connecteddevice.py:265 +#, fuzzy +msgid "Focused display" +msgstr "Display-Entfernung" + +#: src/connecteddevice.py:271 +#, fuzzy +msgid "All displays" +msgstr "Gebogenes Display" + +#: src/displaydistancedialogcontent.py:41 +msgid "closer" +msgstr "" + +#: src/displaydistancedialogcontent.py:45 +msgid "fullscreen" +msgstr "" + +#: src/displaydistancedialogcontent.py:48 +msgid "farther" +msgstr "" + #: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "Bezahlter Tarifstatus" @@ -37,7 +83,7 @@ msgstr "Bezahlter Tarifstatus" msgid "Feature Availability" msgstr "Funktionsverfügbarkeit" -#: src/licensefeaturerow.py:15 src/shortcutdialog.py:107 +#: src/licensefeaturerow.py:15 src/shortcutdialog.py:104 msgid "Disabled" msgstr "Deaktiviert" @@ -166,82 +212,138 @@ msgid "Enables the Breezy Desktop XR effect." msgstr "Aktiviert den Breezy Desktop XR-Effekt." #: src/gtk/connected-device.ui:55 -msgid "Widescreen mode" -msgstr "Breitbildmodus" +msgid "Zoom on focus" +msgstr "" -#: src/gtk/connected-device.ui:66 +#: src/gtk/connected-device.ui:56 +msgid "" +"Automatically move a display closer when you look at it.\n" +"Set your preferred focused and unfocused distances in the Adjustments " +"section.\n" +" " +msgstr "" + +#: src/gtk/connected-device.ui:69 msgid "Follow mode" msgstr "Folgemodus" -#: src/gtk/connected-device.ui:67 -msgid "Keep the virtual display near the center of your view." +#: src/gtk/connected-device.ui:70 +#, fuzzy +msgid "Keep the focused display near the center of your view." msgstr "Halten Sie das virtuelle Display Nahe der Mitte Ihres Blickfelds." -#: src/gtk/connected-device.ui:77 -msgid "Curved display" -msgstr "Gebogenes Display" +#: src/gtk/connected-device.ui:80 src/gtk/no-device.ui:34 +msgid "Disable physical displays" +msgstr "" -#: src/gtk/connected-device.ui:78 -msgid "Switch between flat and curved displays." -msgstr "Zwischen flachen und gebogenen Displays wechseln." +#: src/gtk/connected-device.ui:81 src/gtk/no-device.ui:35 +msgid "" +"Automatically disable all physical displays when the XR effect is enabled." +msgstr "" #: src/gtk/connected-device.ui:91 +#, fuzzy +msgid "Virtual displays" +msgstr "Gebogenes Display" + +#: src/gtk/connected-device.ui:103 +#, fuzzy +msgid "Rearrange displays" +msgstr "Gebogenes Display" + +#: src/gtk/connected-device.ui:115 +msgid "1080p" +msgstr "" + +#: src/gtk/connected-device.ui:116 +msgid "1440p" +msgstr "" + +#: src/gtk/connected-device.ui:140 msgid "Adjustments" msgstr "Einstellungen" -#: src/gtk/connected-device.ui:94 -msgid "Display distance" +#: src/gtk/connected-device.ui:143 +#, fuzzy +msgid "Display distances" msgstr "Display-Entfernung" -#: src/gtk/connected-device.ui:95 -msgid "" -"Closer appears larger, further appears smaller. Controls depth when in " -"widescreen mode." +#: src/gtk/connected-device.ui:144 +msgid "Set how close you want displays to appear." msgstr "" -"Näher erscheint größer, weiter erscheint kleiner. Steuert die Tiefe, wenn im " -"Breitbildmodus." -#: src/gtk/connected-device.ui:123 -msgid "Display size" -msgstr "Display-Größe" - -#: src/gtk/connected-device.ui:124 -msgid "" -"Combine with display distance to achieve a comfortable level of depth and " -"size." -msgstr "" -"Kombinieren Sie mit der Display-Entfernung, um eine bequeme Ebene der Tiefe " -"und Größe zu erreichen." - -#: src/gtk/connected-device.ui:152 -msgid "Display toggle distances" -msgstr "Umschalten der Display-Entfernungen" - -#: src/gtk/connected-device.ui:153 -msgid "" -"Use the buttons to capture the current display distance for use with the " -"keyboard shortcut." -msgstr "" -"Verwenden Sie die Schaltflächen, um die aktuelle Display-Entfernung für die " -"Verwendung mit der Tastenkombination aufzufangen." - -#: src/gtk/connected-device.ui:178 +#: src/gtk/connected-device.ui:199 msgid "Follow threshold" msgstr "Folgemodus-Schwelle" -#: src/gtk/connected-device.ui:179 +#: src/gtk/connected-device.ui:200 msgid "How far away you can look before the display follows." msgstr "Wie weit Sie wegschauen können, bevor das Display folgt." -#: src/gtk/connected-device.ui:209 src/gtk/connected-device.ui:215 +#: src/gtk/connected-device.ui:223 +#, fuzzy +msgid "Display angling" +msgstr "Display-Größe" + +#: src/gtk/connected-device.ui:224 +msgid "" +"When there are multiple displays, choose how they should angle towards you." +msgstr "" + +#: src/gtk/connected-device.ui:238 +msgid "Automatic" +msgstr "" + +#: src/gtk/connected-device.ui:239 +msgid "Side-angled" +msgstr "" + +#: src/gtk/connected-device.ui:240 +msgid "Top-angled" +msgstr "" + +#: src/gtk/connected-device.ui:241 +msgid "Flat" +msgstr "" + +#: src/gtk/connected-device.ui:251 +#, fuzzy +msgid "Display spacing" +msgstr "Display-Größe" + +#: src/gtk/connected-device.ui:252 +msgid "Put empty space between displays, when there are multiple." +msgstr "" + +#: src/gtk/connected-device.ui:282 +msgid "Viewport horizontal offset" +msgstr "" + +#: src/gtk/connected-device.ui:283 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport to the left or right." +msgstr "" + +#: src/gtk/connected-device.ui:312 +msgid "Viewport vertical offset" +msgstr "" + +#: src/gtk/connected-device.ui:313 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport up or down." +msgstr "" + +#: src/gtk/connected-device.ui:349 src/gtk/connected-device.ui:355 msgid "Keyboard Shortcuts" msgstr "Tastenkombinationen" -#: src/gtk/connected-device.ui:218 +#: src/gtk/connected-device.ui:358 msgid "XR Effect on/off shortcut" msgstr "XR-Effekt an/aus Tastenkombination" -#: src/gtk/connected-device.ui:219 +#: src/gtk/connected-device.ui:359 msgid "" "Quickly enable or disable the XR Effect. You may need to enable the effect " "manually once in order to enable the shortcut." @@ -250,44 +352,46 @@ msgstr "" "Effekt einmalig manuell aktiviert werden, um die Tastenkombination zu " "aktivieren." -#: src/gtk/connected-device.ui:238 src/gtk/connected-device.ui:267 -#: src/gtk/connected-device.ui:296 src/gtk/connected-device.ui:325 +#: src/gtk/connected-device.ui:378 src/gtk/connected-device.ui:407 +#: src/gtk/connected-device.ui:436 src/gtk/connected-device.ui:465 msgid "Change" msgstr "Ändern" -#: src/gtk/connected-device.ui:247 +#: src/gtk/connected-device.ui:387 msgid "Re-center display shortcut" msgstr "Display-Zentrierung-Tastenkombination" -#: src/gtk/connected-device.ui:248 +#: src/gtk/connected-device.ui:388 msgid "Pin the virtual display to the current position." msgstr "Heften Sie das virtuelle Display an die aktuelle Position an." -#: src/gtk/connected-device.ui:276 -msgid "Display distance shortcut" -msgstr "Display-Entfernung-Tastenkombination" +#: src/gtk/connected-device.ui:416 +#, fuzzy +msgid "Toggle zoom on focus shortcut" +msgstr "Folgemodus-Tastenkombination umschalten" -#: src/gtk/connected-device.ui:277 -msgid "Quickly toggle between two predefined distances." -msgstr "Schnell zwischen zwei vordefinierten Entfernungen wechseln." +#: src/gtk/connected-device.ui:417 +#, fuzzy +msgid "Quickly toggle zoom on focus mode." +msgstr "Schnell den Folgemodus wechseln." -#: src/gtk/connected-device.ui:305 +#: src/gtk/connected-device.ui:445 msgid "Toggle follow mode shortcut" msgstr "Folgemodus-Tastenkombination umschalten" -#: src/gtk/connected-device.ui:306 +#: src/gtk/connected-device.ui:446 msgid "Quickly toggle follow mode." msgstr "Schnell den Folgemodus wechseln." -#: src/gtk/connected-device.ui:341 src/gtk/connected-device.ui:347 +#: src/gtk/connected-device.ui:481 src/gtk/connected-device.ui:487 msgid "Advanced Settings" msgstr "Erweiterte Einstellungen" -#: src/gtk/connected-device.ui:350 +#: src/gtk/connected-device.ui:490 msgid "Find optimal display config" msgstr "Optimale Display-Konfiguration finden" -#: src/gtk/connected-device.ui:351 +#: src/gtk/connected-device.ui:491 msgid "" "Automatically modify the glasses display configuration for maximum " "resolution and best scaling when plugged in." @@ -295,43 +399,31 @@ msgstr "" "Ändern Sie die Display-Konfiguration der Brille automatisch zur maximalen " "Auflösung und besten Skalierung, wenn sie angeschlossen ist." -#: src/gtk/connected-device.ui:361 +#: src/gtk/connected-device.ui:501 msgid "Use highest refresh rate" msgstr "Höchste Bildwiederholrate verwenden" -#: src/gtk/connected-device.ui:362 +#: src/gtk/connected-device.ui:502 msgid "Refresh rate may affect performance, disable this to set it manually." msgstr "" "Die Bildwiederholrate kann die Leistung beeinflussen, deaktivieren Sie dies, " "um sie manuell festzulegen." -#: src/gtk/connected-device.ui:372 +#: src/gtk/connected-device.ui:512 msgid "Always primary display" msgstr "Immer primäres Display" -#: src/gtk/connected-device.ui:373 +#: src/gtk/connected-device.ui:513 msgid "Automatically set the glasses as the primary display when plugged in." msgstr "" "Setzen Sie die Brille automatisch als primäres Display, wenn sie " "angeschlossen ist." -#: src/gtk/connected-device.ui:383 -msgid "Fast SBS mode switching" -msgstr "Schnelles SBS-Modus-Wechseln" - -#: src/gtk/connected-device.ui:384 -msgid "" -"Switches glasses to SBS mode immediately when plugged in, if widescreen mode " -"is on. May cause instability." -msgstr "" -"Schaltet die Brille sofort in den SBS-Modus, wenn sie angeschlossen ist, " -"wenn der Breitbildmodus aktiviert ist. Kann zu Instabilität führen." - -#: src/gtk/connected-device.ui:394 +#: src/gtk/connected-device.ui:523 msgid "Movement look-ahead" msgstr "Bewegungsvorausschau" -#: src/gtk/connected-device.ui:395 +#: src/gtk/connected-device.ui:524 msgid "" "Counteracts input lag by predicting head-tracking position ahead of render " "time. Stick with default unless virtual display drags behind your head " @@ -342,18 +434,34 @@ msgstr "" "es sei denn, das virtuelle Display hängt hinter Ihren Kopfbewegungen " "hinterher, springt vor oder ist sehr wackelig." -#: src/gtk/connected-device.ui:413 +#: src/gtk/connected-device.ui:542 msgid "Default" msgstr "Standard" -#: src/gtk/connected-device.ui:425 +#: src/gtk/connected-device.ui:554 msgid "Text Scaling" msgstr "Textskalierung" -#: src/gtk/connected-device.ui:426 +#: src/gtk/connected-device.ui:555 msgid "Scaling text below 1.0 will simulate a higher resolution display" msgstr "Text unter 1.0 skalieren simuliert ein höher aufgelöstes Display" +#: src/gtk/connected-device.ui:585 +msgid "Enable multi-tap detection" +msgstr "" + +#: src/gtk/connected-device.ui:586 +msgid "Enables double-tap to recenter and triple-tap to recalibrate." +msgstr "" + +#: src/gtk/display-distance-dialog.ui:9 +msgid "Show full range" +msgstr "" + +#: src/gtk/display-distance-dialog.ui:18 +msgid "Done" +msgstr "" + #: src/gtk/failed-verification.ui:13 msgid "Breezy Desktop GNOME invalid setup" msgstr "Ungültige Breezy Desktop GNOME-Installation" @@ -403,14 +511,6 @@ msgstr "" "Automatisch den Breezy Desktop XR-Effekt aktivieren, wenn unterstützte " "Gläser verbunden werden" -#: src/gtk/no-device.ui:34 -msgid "Start in widescreen mode" -msgstr "Im Breitbildmodus starten" - -#: src/gtk/no-device.ui:35 -msgid "Widescreen mode is not supported for all glasses." -msgstr "Breitbildmodus wird nicht für alle Gläsern unterstützt" - #: src/gtk/no-driver.ui:13 msgid "No driver running" msgstr "Kein Treiber wird ausgeführt" @@ -522,3 +622,59 @@ msgstr "Zurrücksetzung erwzingen" #: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "Über BreezyDesktop" + +#~ msgid "Fast SBS mode switching" +#~ msgstr "Schnelles SBS-Modus-Wechseln" + +#~ msgid "" +#~ "Switches glasses to SBS mode immediately when plugged in, if widescreen " +#~ "mode is on. May cause instability." +#~ msgstr "" +#~ "Schaltet die Brille sofort in den SBS-Modus, wenn sie angeschlossen ist, " +#~ "wenn der Breitbildmodus aktiviert ist. Kann zu Instabilität führen." + +#, fuzzy +#~ msgid "Set Unfocused Displays Distance" +#~ msgstr "Display-Entfernung" + +#~ msgid "Widescreen mode" +#~ msgstr "Breitbildmodus" + +#~ msgid "Switch between flat and curved displays." +#~ msgstr "Zwischen flachen und gebogenen Displays wechseln." + +#~ msgid "" +#~ "Closer appears larger, further appears smaller. Controls depth when in " +#~ "widescreen mode." +#~ msgstr "" +#~ "Näher erscheint größer, weiter erscheint kleiner. Steuert die Tiefe, wenn " +#~ "im Breitbildmodus." + +#~ msgid "" +#~ "Combine with display distance to achieve a comfortable level of depth and " +#~ "size." +#~ msgstr "" +#~ "Kombinieren Sie mit der Display-Entfernung, um eine bequeme Ebene der " +#~ "Tiefe und Größe zu erreichen." + +#~ msgid "Display toggle distances" +#~ msgstr "Umschalten der Display-Entfernungen" + +#~ msgid "" +#~ "Use the buttons to capture the current display distance for use with the " +#~ "keyboard shortcut." +#~ msgstr "" +#~ "Verwenden Sie die Schaltflächen, um die aktuelle Display-Entfernung für " +#~ "die Verwendung mit der Tastenkombination aufzufangen." + +#~ msgid "Display distance shortcut" +#~ msgstr "Display-Entfernung-Tastenkombination" + +#~ msgid "Quickly toggle between two predefined distances." +#~ msgstr "Schnell zwischen zwei vordefinierten Entfernungen wechseln." + +#~ msgid "Start in widescreen mode" +#~ msgstr "Im Breitbildmodus starten" + +#~ msgid "Widescreen mode is not supported for all glasses." +#~ msgstr "Breitbildmodus wird nicht für alle Gläsern unterstützt" diff --git a/ui/po/es.po b/ui/po/es.po index 1ab354e..b3f84bb 100644 --- a/ui/po/es.po +++ b/ui/po/es.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-22 13:27-0700\n" +"POT-Creation-Date: 2025-03-05 12:28-0800\n" "PO-Revision-Date: 2024-08-02 20:55-0700\n" "Last-Translator: \n" "Language-Team: Spanish \n" @@ -17,17 +17,63 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: src/connecteddevice.py:17 +#: src/connecteddevice.py:23 msgid "" "Switches your glasses into side-by-side mode and doubles the width of the " "display." msgstr "" "Cambia tus gafas al modo lado a lado y duplica el ancho de la pantalla." -#: src/connecteddevice.py:18 +#: src/connecteddevice.py:24 msgid "This feature is not currently supported for your device." msgstr "Esta función no es compatible con tu dispositivo en este momento." +#: src/connecteddevice.py:124 +#, fuzzy +msgid "Set Focused Display Distance" +msgstr "Distancia de la pantalla" + +#: src/connecteddevice.py:125 +msgid "Use a closer value so the display zooms in when you look at it." +msgstr "" + +#: src/connecteddevice.py:132 +#, fuzzy +msgid "Set All Displays Distance" +msgstr "Distancia de la pantalla" + +#: src/connecteddevice.py:133 +msgid "Use a farther value so the displays are zoomed out when you look away." +msgstr "" + +#: src/connecteddevice.py:231 +msgid "" +"Unable to add virtual displays on this machine. xdg-desktop-portal is " +"required." +msgstr "" + +#: src/connecteddevice.py:265 +#, fuzzy +msgid "Focused display" +msgstr "Distancia de la pantalla" + +#: src/connecteddevice.py:271 +#, fuzzy +msgid "All displays" +msgstr "Pantalla curvada" + +#: src/displaydistancedialogcontent.py:41 +msgid "closer" +msgstr "" + +#: src/displaydistancedialogcontent.py:45 +msgid "fullscreen" +msgstr "" + +#: src/displaydistancedialogcontent.py:48 +msgid "farther" +msgstr "" + #: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "Estado del Nivel de Membresía Pagada" @@ -36,7 +82,7 @@ msgstr "Estado del Nivel de Membresía Pagada" msgid "Feature Availability" msgstr "Disponibilidad de Características" -#: src/licensefeaturerow.py:15 src/shortcutdialog.py:107 +#: src/licensefeaturerow.py:15 src/shortcutdialog.py:104 msgid "Disabled" msgstr "Deshabilitado" @@ -165,127 +211,185 @@ msgid "Enables the Breezy Desktop XR effect." msgstr "Habilita el efecto XR de Breezy Desktop." #: src/gtk/connected-device.ui:55 -msgid "Widescreen mode" -msgstr "Modo pantalla ancha" +msgid "Zoom on focus" +msgstr "" -#: src/gtk/connected-device.ui:66 +#: src/gtk/connected-device.ui:56 +msgid "" +"Automatically move a display closer when you look at it.\n" +"Set your preferred focused and unfocused distances in the Adjustments " +"section.\n" +" " +msgstr "" + +#: src/gtk/connected-device.ui:69 msgid "Follow mode" msgstr "Modo de seguimiento" -#: src/gtk/connected-device.ui:67 -msgid "Keep the virtual display near the center of your view." +#: src/gtk/connected-device.ui:70 +#, fuzzy +msgid "Keep the focused display near the center of your view." msgstr "Mantenga la pantalla virtual cerca del centro de su vista." -#: src/gtk/connected-device.ui:77 -msgid "Curved display" -msgstr "Pantalla curvada" +#: src/gtk/connected-device.ui:80 src/gtk/no-device.ui:34 +msgid "Disable physical displays" +msgstr "" -#: src/gtk/connected-device.ui:78 -msgid "Switch between flat and curved displays." -msgstr "Cambiar entre pantallas planas y curvadas." +#: src/gtk/connected-device.ui:81 src/gtk/no-device.ui:35 +msgid "" +"Automatically disable all physical displays when the XR effect is enabled." +msgstr "" #: src/gtk/connected-device.ui:91 +#, fuzzy +msgid "Virtual displays" +msgstr "Pantalla curvada" + +#: src/gtk/connected-device.ui:103 +#, fuzzy +msgid "Rearrange displays" +msgstr "Pantalla curvada" + +#: src/gtk/connected-device.ui:115 +msgid "1080p" +msgstr "" + +#: src/gtk/connected-device.ui:116 +msgid "1440p" +msgstr "" + +#: src/gtk/connected-device.ui:140 msgid "Adjustments" msgstr "Ajustes" -#: src/gtk/connected-device.ui:94 -msgid "Display distance" +#: src/gtk/connected-device.ui:143 +#, fuzzy +msgid "Display distances" msgstr "Distancia de la pantalla" -#: src/gtk/connected-device.ui:95 -msgid "" -"Closer appears larger, further appears smaller. Controls depth when in " -"widescreen mode." +#: src/gtk/connected-device.ui:144 +msgid "Set how close you want displays to appear." msgstr "" -"Más cerca parece más grande, más lejos parece más pequeño. Controla la " -"profundidad cuando está en modo de pantalla ancha." -#: src/gtk/connected-device.ui:123 -msgid "Display size" -msgstr "Tamaño de la pantalla" - -#: src/gtk/connected-device.ui:124 -msgid "" -"Combine with display distance to achieve a comfortable level of depth and " -"size." -msgstr "" -"Combínalo con la distancia de la pantalla para lograr un nivel cómodo de " -"profundidad y tamaño." - -#: src/gtk/connected-device.ui:152 -msgid "Display toggle distances" -msgstr "Distancias de alternancia de pantalla" - -#: src/gtk/connected-device.ui:153 -msgid "" -"Use the buttons to capture the current display distance for use with the " -"keyboard shortcut." -msgstr "" -"Utilice los botones para capturar la distancia actual de la pantalla para su " -"uso con el método abreviado de teclado." - -#: src/gtk/connected-device.ui:178 +#: src/gtk/connected-device.ui:199 msgid "Follow threshold" msgstr "Umbral de seguimiento" -#: src/gtk/connected-device.ui:179 +#: src/gtk/connected-device.ui:200 msgid "How far away you can look before the display follows." msgstr "Hasta qué punto puedes mirar antes de que la pantalla siga." -#: src/gtk/connected-device.ui:209 src/gtk/connected-device.ui:215 +#: src/gtk/connected-device.ui:223 +#, fuzzy +msgid "Display angling" +msgstr "Tamaño de la pantalla" + +#: src/gtk/connected-device.ui:224 +msgid "" +"When there are multiple displays, choose how they should angle towards you." +msgstr "" + +#: src/gtk/connected-device.ui:238 +msgid "Automatic" +msgstr "" + +#: src/gtk/connected-device.ui:239 +msgid "Side-angled" +msgstr "" + +#: src/gtk/connected-device.ui:240 +msgid "Top-angled" +msgstr "" + +#: src/gtk/connected-device.ui:241 +msgid "Flat" +msgstr "" + +#: src/gtk/connected-device.ui:251 +#, fuzzy +msgid "Display spacing" +msgstr "Tamaño de la pantalla" + +#: src/gtk/connected-device.ui:252 +msgid "Put empty space between displays, when there are multiple." +msgstr "" + +#: src/gtk/connected-device.ui:282 +msgid "Viewport horizontal offset" +msgstr "" + +#: src/gtk/connected-device.ui:283 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport to the left or right." +msgstr "" + +#: src/gtk/connected-device.ui:312 +msgid "Viewport vertical offset" +msgstr "" + +#: src/gtk/connected-device.ui:313 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport up or down." +msgstr "" + +#: src/gtk/connected-device.ui:349 src/gtk/connected-device.ui:355 msgid "Keyboard Shortcuts" msgstr "Atajos de teclado" -#: src/gtk/connected-device.ui:218 +#: src/gtk/connected-device.ui:358 msgid "XR Effect on/off shortcut" msgstr "Atajo encendido/apagado Efecto XR" -#: src/gtk/connected-device.ui:219 +#: src/gtk/connected-device.ui:359 msgid "" "Quickly enable or disable the XR Effect. You may need to enable the effect " "manually once in order to enable the shortcut." msgstr "" -"Activa o desactiva rápidamente el Efecto XR. Es posible que necesites activar " -"el efecto manualmente una vez para habilitar el atajo." +"Activa o desactiva rápidamente el Efecto XR. Es posible que necesites " +"activar el efecto manualmente una vez para habilitar el atajo." -#: src/gtk/connected-device.ui:238 src/gtk/connected-device.ui:267 -#: src/gtk/connected-device.ui:296 src/gtk/connected-device.ui:325 +#: src/gtk/connected-device.ui:378 src/gtk/connected-device.ui:407 +#: src/gtk/connected-device.ui:436 src/gtk/connected-device.ui:465 msgid "Change" msgstr "Cambiar" -#: src/gtk/connected-device.ui:247 +#: src/gtk/connected-device.ui:387 msgid "Re-center display shortcut" msgstr "Atajo para recentrar la pantalla" -#: src/gtk/connected-device.ui:248 +#: src/gtk/connected-device.ui:388 msgid "Pin the virtual display to the current position." msgstr "Fije la pantalla virtual en la posición actual." -#: src/gtk/connected-device.ui:276 -msgid "Display distance shortcut" -msgstr "Atajo de distancia de pantalla" +#: src/gtk/connected-device.ui:416 +#, fuzzy +msgid "Toggle zoom on focus shortcut" +msgstr "Atajo para alternar el modo de seguimiento" -#: src/gtk/connected-device.ui:277 -msgid "Quickly toggle between two predefined distances." -msgstr "Cambiar rápidamente entre dos distancias predefinidas." +#: src/gtk/connected-device.ui:417 +#, fuzzy +msgid "Quickly toggle zoom on focus mode." +msgstr "Activar el modo de seguimiento." -#: src/gtk/connected-device.ui:305 +#: src/gtk/connected-device.ui:445 msgid "Toggle follow mode shortcut" msgstr "Atajo para alternar el modo de seguimiento" -#: src/gtk/connected-device.ui:306 +#: src/gtk/connected-device.ui:446 msgid "Quickly toggle follow mode." msgstr "Activar el modo de seguimiento." -#: src/gtk/connected-device.ui:341 src/gtk/connected-device.ui:347 +#: src/gtk/connected-device.ui:481 src/gtk/connected-device.ui:487 msgid "Advanced Settings" msgstr "Configuración Avanzada" -#: src/gtk/connected-device.ui:350 +#: src/gtk/connected-device.ui:490 msgid "Find optimal display config" msgstr "Encuentre la configuración de pantalla óptima" -#: src/gtk/connected-device.ui:351 +#: src/gtk/connected-device.ui:491 msgid "" "Automatically modify the glasses display configuration for maximum " "resolution and best scaling when plugged in." @@ -294,43 +398,30 @@ msgstr "" "obtener la máxima resolución y la mejor escalabilidad cuando estén " "enchufadas." -#: src/gtk/connected-device.ui:361 +#: src/gtk/connected-device.ui:501 msgid "Use highest refresh rate" msgstr "Utilizar la frecuencia de actualización más alta" -#: src/gtk/connected-device.ui:362 +#: src/gtk/connected-device.ui:502 msgid "Refresh rate may affect performance, disable this to set it manually." msgstr "" "La frecuencia de actualización puede afectar el rendimiento, deshabilite " "esto para configurarlo manualmente." -#: src/gtk/connected-device.ui:372 +#: src/gtk/connected-device.ui:512 msgid "Always primary display" msgstr "Siempre como pantalla principal" -#: src/gtk/connected-device.ui:373 +#: src/gtk/connected-device.ui:513 msgid "Automatically set the glasses as the primary display when plugged in." msgstr "" "Automáticamente configurar las gafas como pantalla principal al enchufarse." -#: src/gtk/connected-device.ui:383 -msgid "Fast SBS mode switching" -msgstr "Cambio rápido de modo SBS" - -#: src/gtk/connected-device.ui:384 -msgid "" -"Switches glasses to SBS mode immediately when plugged in, if widescreen mode " -"is on. May cause instability." -msgstr "" -"Cambia las gafas al modo SBS (lado a lado) inmediatamente cuando se " -"enchufan, si el modo de pantalla ancha está activado. Puede causar " -"inestabilidad." - -#: src/gtk/connected-device.ui:394 +#: src/gtk/connected-device.ui:523 msgid "Movement look-ahead" msgstr "Anticipación de movimiento" -#: src/gtk/connected-device.ui:395 +#: src/gtk/connected-device.ui:524 msgid "" "Counteracts input lag by predicting head-tracking position ahead of render " "time. Stick with default unless virtual display drags behind your head " @@ -341,17 +432,35 @@ msgstr "" "predeterminado a menos que la pantalla virtual se retrase detrás de los " "movimientos de la cabeza, se adelante o sea muy inestable." -#: src/gtk/connected-device.ui:413 +#: src/gtk/connected-device.ui:542 msgid "Default" msgstr "Predeterminado" -#: src/gtk/connected-device.ui:425 +#: src/gtk/connected-device.ui:554 msgid "Text Scaling" msgstr "Escalado de Texto" -#: src/gtk/connected-device.ui:426 +#: src/gtk/connected-device.ui:555 msgid "Scaling text below 1.0 will simulate a higher resolution display" -msgstr "Escalando el texto por debajo de 1.0 simulará una pantalla de mayor resolución" +msgstr "" +"Escalando el texto por debajo de 1.0 simulará una pantalla de mayor " +"resolución" + +#: src/gtk/connected-device.ui:585 +msgid "Enable multi-tap detection" +msgstr "" + +#: src/gtk/connected-device.ui:586 +msgid "Enables double-tap to recenter and triple-tap to recalibrate." +msgstr "" + +#: src/gtk/display-distance-dialog.ui:9 +msgid "Show full range" +msgstr "" + +#: src/gtk/display-distance-dialog.ui:18 +msgid "Done" +msgstr "" #: src/gtk/failed-verification.ui:13 msgid "Breezy Desktop GNOME invalid setup" @@ -398,16 +507,8 @@ msgid "" "Automatically enable the Breezy Desktop XR effect when supported glasses are " "connected." msgstr "" -"Habilita automáticamente el efecto Breezy Desktop XR cuando se conectan gafas " -"compatibles." - -#: src/gtk/no-device.ui:34 -msgid "Start in widescreen mode" -msgstr "Empezar en modo pantalla ancha" - -#: src/gtk/no-device.ui:35 -msgid "Widescreen mode is not supported for all glasses." -msgstr "El modo pantalla ancha no está soportado en todas las gafas." +"Habilita automáticamente el efecto Breezy Desktop XR cuando se conectan " +"gafas compatibles." #: src/gtk/no-driver.ui:13 msgid "No driver running" @@ -518,3 +619,60 @@ msgstr "Reinicio forzoso" #: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "Acerca de BreezyDesktop" + +#~ msgid "Fast SBS mode switching" +#~ msgstr "Cambio rápido de modo SBS" + +#~ msgid "" +#~ "Switches glasses to SBS mode immediately when plugged in, if widescreen " +#~ "mode is on. May cause instability." +#~ msgstr "" +#~ "Cambia las gafas al modo SBS (lado a lado) inmediatamente cuando se " +#~ "enchufan, si el modo de pantalla ancha está activado. Puede causar " +#~ "inestabilidad." + +#, fuzzy +#~ msgid "Set Unfocused Displays Distance" +#~ msgstr "Distancia de la pantalla" + +#~ msgid "Widescreen mode" +#~ msgstr "Modo pantalla ancha" + +#~ msgid "Switch between flat and curved displays." +#~ msgstr "Cambiar entre pantallas planas y curvadas." + +#~ msgid "" +#~ "Closer appears larger, further appears smaller. Controls depth when in " +#~ "widescreen mode." +#~ msgstr "" +#~ "Más cerca parece más grande, más lejos parece más pequeño. Controla la " +#~ "profundidad cuando está en modo de pantalla ancha." + +#~ msgid "" +#~ "Combine with display distance to achieve a comfortable level of depth and " +#~ "size." +#~ msgstr "" +#~ "Combínalo con la distancia de la pantalla para lograr un nivel cómodo de " +#~ "profundidad y tamaño." + +#~ msgid "Display toggle distances" +#~ msgstr "Distancias de alternancia de pantalla" + +#~ msgid "" +#~ "Use the buttons to capture the current display distance for use with the " +#~ "keyboard shortcut." +#~ msgstr "" +#~ "Utilice los botones para capturar la distancia actual de la pantalla para " +#~ "su uso con el método abreviado de teclado." + +#~ msgid "Display distance shortcut" +#~ msgstr "Atajo de distancia de pantalla" + +#~ msgid "Quickly toggle between two predefined distances." +#~ msgstr "Cambiar rápidamente entre dos distancias predefinidas." + +#~ msgid "Start in widescreen mode" +#~ msgstr "Empezar en modo pantalla ancha" + +#~ msgid "Widescreen mode is not supported for all glasses." +#~ msgstr "El modo pantalla ancha no está soportado en todas las gafas." diff --git a/ui/po/fr.po b/ui/po/fr.po index b743075..102df1c 100644 --- a/ui/po/fr.po +++ b/ui/po/fr.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-22 13:27-0700\n" +"POT-Creation-Date: 2025-03-05 12:28-0800\n" "PO-Revision-Date: 2024-08-02 20:54-0700\n" "Last-Translator: \n" "Language-Team: French \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: src/connecteddevice.py:17 +#: src/connecteddevice.py:23 msgid "" "Switches your glasses into side-by-side mode and doubles the width of the " "display." @@ -25,12 +25,58 @@ msgstr "" "Basculez vos lunettes en mode Side-By-Side (Côte-à-Côte) et doublez la " "largeur de l'écran." -#: src/connecteddevice.py:18 +#: src/connecteddevice.py:24 msgid "This feature is not currently supported for your device." msgstr "" "Cette fonctionnalité n'est actuellement pas prise en charge par votre " "appareil." +#: src/connecteddevice.py:124 +#, fuzzy +msgid "Set Focused Display Distance" +msgstr "Distance d'affichage" + +#: src/connecteddevice.py:125 +msgid "Use a closer value so the display zooms in when you look at it." +msgstr "" + +#: src/connecteddevice.py:132 +#, fuzzy +msgid "Set All Displays Distance" +msgstr "Distance d'affichage" + +#: src/connecteddevice.py:133 +msgid "Use a farther value so the displays are zoomed out when you look away." +msgstr "" + +#: src/connecteddevice.py:231 +msgid "" +"Unable to add virtual displays on this machine. xdg-desktop-portal is " +"required." +msgstr "" + +#: src/connecteddevice.py:265 +#, fuzzy +msgid "Focused display" +msgstr "Distance d'affichage" + +#: src/connecteddevice.py:271 +#, fuzzy +msgid "All displays" +msgstr "Affichage incurvé" + +#: src/displaydistancedialogcontent.py:41 +msgid "closer" +msgstr "" + +#: src/displaydistancedialogcontent.py:45 +msgid "fullscreen" +msgstr "" + +#: src/displaydistancedialogcontent.py:48 +msgid "farther" +msgstr "" + #: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "Statut de l'abonnement payant" @@ -39,7 +85,7 @@ msgstr "Statut de l'abonnement payant" msgid "Feature Availability" msgstr "Disponibilité des fonctionnalités" -#: src/licensefeaturerow.py:15 src/shortcutdialog.py:107 +#: src/licensefeaturerow.py:15 src/shortcutdialog.py:104 msgid "Disabled" msgstr "Désactivé" @@ -168,126 +214,185 @@ msgid "Enables the Breezy Desktop XR effect." msgstr "Active l'effet Breezy Desktop XR." #: src/gtk/connected-device.ui:55 -msgid "Widescreen mode" -msgstr "Mode grand écran" +msgid "Zoom on focus" +msgstr "" -#: src/gtk/connected-device.ui:66 +#: src/gtk/connected-device.ui:56 +msgid "" +"Automatically move a display closer when you look at it.\n" +"Set your preferred focused and unfocused distances in the Adjustments " +"section.\n" +" " +msgstr "" + +#: src/gtk/connected-device.ui:69 msgid "Follow mode" msgstr "Mode de suivi" -#: src/gtk/connected-device.ui:67 -msgid "Keep the virtual display near the center of your view." +#: src/gtk/connected-device.ui:70 +#, fuzzy +msgid "Keep the focused display near the center of your view." msgstr "Gardez l'affichage virtuel près du centre de votre champ de vision." -#: src/gtk/connected-device.ui:77 -msgid "Curved display" -msgstr "Affichage incurvé" +#: src/gtk/connected-device.ui:80 src/gtk/no-device.ui:34 +msgid "Disable physical displays" +msgstr "" -#: src/gtk/connected-device.ui:78 -msgid "Switch between flat and curved displays." -msgstr "Basculez entre les affichages plats et incurvés." +#: src/gtk/connected-device.ui:81 src/gtk/no-device.ui:35 +msgid "" +"Automatically disable all physical displays when the XR effect is enabled." +msgstr "" #: src/gtk/connected-device.ui:91 +#, fuzzy +msgid "Virtual displays" +msgstr "Affichage incurvé" + +#: src/gtk/connected-device.ui:103 +#, fuzzy +msgid "Rearrange displays" +msgstr "Affichage incurvé" + +#: src/gtk/connected-device.ui:115 +msgid "1080p" +msgstr "" + +#: src/gtk/connected-device.ui:116 +msgid "1440p" +msgstr "" + +#: src/gtk/connected-device.ui:140 msgid "Adjustments" msgstr "Réglages" -#: src/gtk/connected-device.ui:94 -msgid "Display distance" +#: src/gtk/connected-device.ui:143 +#, fuzzy +msgid "Display distances" msgstr "Distance d'affichage" -#: src/gtk/connected-device.ui:95 -msgid "" -"Closer appears larger, further appears smaller. Controls depth when in " -"widescreen mode." +#: src/gtk/connected-device.ui:144 +msgid "Set how close you want displays to appear." msgstr "" -"Plus proche apparaît plus grand, plus éloigné apparaît plus petit. Contrôle " -"la profondeur lorsque vous êtes en mode grand écran." -#: src/gtk/connected-device.ui:123 -msgid "Display size" -msgstr "Taille de l'affichage" - -#: src/gtk/connected-device.ui:124 -msgid "" -"Combine with display distance to achieve a comfortable level of depth and " -"size." -msgstr "" -"Combinez avec la distance d'affichage pour obtenir un niveau de profondeur " -"et de taille confortable." - -#: src/gtk/connected-device.ui:152 -msgid "Display toggle distances" -msgstr "Distances de basculement de l'affichage" - -#: src/gtk/connected-device.ui:153 -msgid "" -"Use the buttons to capture the current display distance for use with the " -"keyboard shortcut." -msgstr "" -"Utilisez les boutons pour capturer la distance d'affichage actuelle pour une " -"utilisation avec le raccourci clavier." - -#: src/gtk/connected-device.ui:178 +#: src/gtk/connected-device.ui:199 msgid "Follow threshold" msgstr "Seuil de suivi" -#: src/gtk/connected-device.ui:179 +#: src/gtk/connected-device.ui:200 msgid "How far away you can look before the display follows." msgstr "Distance jusqu'où vous pouvez regarder avant que l'affichage ne suive." -#: src/gtk/connected-device.ui:209 src/gtk/connected-device.ui:215 +#: src/gtk/connected-device.ui:223 +#, fuzzy +msgid "Display angling" +msgstr "Taille de l'affichage" + +#: src/gtk/connected-device.ui:224 +msgid "" +"When there are multiple displays, choose how they should angle towards you." +msgstr "" + +#: src/gtk/connected-device.ui:238 +msgid "Automatic" +msgstr "" + +#: src/gtk/connected-device.ui:239 +msgid "Side-angled" +msgstr "" + +#: src/gtk/connected-device.ui:240 +msgid "Top-angled" +msgstr "" + +#: src/gtk/connected-device.ui:241 +msgid "Flat" +msgstr "" + +#: src/gtk/connected-device.ui:251 +#, fuzzy +msgid "Display spacing" +msgstr "Taille de l'affichage" + +#: src/gtk/connected-device.ui:252 +msgid "Put empty space between displays, when there are multiple." +msgstr "" + +#: src/gtk/connected-device.ui:282 +msgid "Viewport horizontal offset" +msgstr "" + +#: src/gtk/connected-device.ui:283 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport to the left or right." +msgstr "" + +#: src/gtk/connected-device.ui:312 +msgid "Viewport vertical offset" +msgstr "" + +#: src/gtk/connected-device.ui:313 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport up or down." +msgstr "" + +#: src/gtk/connected-device.ui:349 src/gtk/connected-device.ui:355 msgid "Keyboard Shortcuts" msgstr "Raccourcis clavier" -#: src/gtk/connected-device.ui:218 +#: src/gtk/connected-device.ui:358 msgid "XR Effect on/off shortcut" msgstr "Raccourci on/off de l'effet XR" -#: src/gtk/connected-device.ui:219 +#: src/gtk/connected-device.ui:359 msgid "" "Quickly enable or disable the XR Effect. You may need to enable the effect " "manually once in order to enable the shortcut." -msgstr "Activez ou désactivez rapidement l'effet XR. Il se peut que vous deviez " +msgstr "" +"Activez ou désactivez rapidement l'effet XR. Il se peut que vous deviez " "activer l'effet manuellement une fois pour activer le raccourci." -#: src/gtk/connected-device.ui:238 src/gtk/connected-device.ui:267 -#: src/gtk/connected-device.ui:296 src/gtk/connected-device.ui:325 +#: src/gtk/connected-device.ui:378 src/gtk/connected-device.ui:407 +#: src/gtk/connected-device.ui:436 src/gtk/connected-device.ui:465 msgid "Change" msgstr "Changer" -#: src/gtk/connected-device.ui:247 +#: src/gtk/connected-device.ui:387 msgid "Re-center display shortcut" msgstr "Raccourci de recentrage de l'affichage" -#: src/gtk/connected-device.ui:248 +#: src/gtk/connected-device.ui:388 msgid "Pin the virtual display to the current position." msgstr "Fixez l'affichage virtuel à la position actuelle." -#: src/gtk/connected-device.ui:276 -msgid "Display distance shortcut" -msgstr "Raccourci de distance d'affichage" +#: src/gtk/connected-device.ui:416 +#, fuzzy +msgid "Toggle zoom on focus shortcut" +msgstr "Raccourci de basculement du mode de suivi" -#: src/gtk/connected-device.ui:277 -msgid "Quickly toggle between two predefined distances." -msgstr "Basculez rapidement entre deux distances prédéfinies." +#: src/gtk/connected-device.ui:417 +#, fuzzy +msgid "Quickly toggle zoom on focus mode." +msgstr "Basculer rapidement le mode de suivi." -#: src/gtk/connected-device.ui:305 +#: src/gtk/connected-device.ui:445 msgid "Toggle follow mode shortcut" msgstr "Raccourci de basculement du mode de suivi" -#: src/gtk/connected-device.ui:306 +#: src/gtk/connected-device.ui:446 msgid "Quickly toggle follow mode." msgstr "Basculer rapidement le mode de suivi." -#: src/gtk/connected-device.ui:341 src/gtk/connected-device.ui:347 +#: src/gtk/connected-device.ui:481 src/gtk/connected-device.ui:487 msgid "Advanced Settings" msgstr "Paramètres avancés" -#: src/gtk/connected-device.ui:350 +#: src/gtk/connected-device.ui:490 msgid "Find optimal display config" msgstr "Trouver la configuration d'affichage optimale" -#: src/gtk/connected-device.ui:351 +#: src/gtk/connected-device.ui:491 msgid "" "Automatically modify the glasses display configuration for maximum " "resolution and best scaling when plugged in." @@ -296,43 +401,31 @@ msgstr "" "résolution maximale et une meilleure mise à l'échelle lorsque elles sont " "branchées." -#: src/gtk/connected-device.ui:361 +#: src/gtk/connected-device.ui:501 msgid "Use highest refresh rate" msgstr "Utiliser le taux de rafraîchissement le plus élevé" -#: src/gtk/connected-device.ui:362 +#: src/gtk/connected-device.ui:502 msgid "Refresh rate may affect performance, disable this to set it manually." msgstr "" "Un taux de rafraîchissement élevé peut affecter les performances, désactivez-" "le pour le définir manuellement." -#: src/gtk/connected-device.ui:372 +#: src/gtk/connected-device.ui:512 msgid "Always primary display" msgstr "Affichage principal en permanence" -#: src/gtk/connected-device.ui:373 +#: src/gtk/connected-device.ui:513 msgid "Automatically set the glasses as the primary display when plugged in." msgstr "" "Définissez automatiquement les lunettes comme affichage principal " "lorsqu'elles sont branchées." -#: src/gtk/connected-device.ui:383 -msgid "Fast SBS mode switching" -msgstr "Bascule rapide vers le mode SBS" - -#: src/gtk/connected-device.ui:384 -msgid "" -"Switches glasses to SBS mode immediately when plugged in, if widescreen mode " -"is on. May cause instability." -msgstr "" -"Bascule les lunettes vers le mode SBS immédiatement lorsqu'elles sont " -"branchées si le mode grand écran est activé. Peut provoquer des instabilités." - -#: src/gtk/connected-device.ui:394 +#: src/gtk/connected-device.ui:523 msgid "Movement look-ahead" msgstr "Prédiction de mouvement" -#: src/gtk/connected-device.ui:395 +#: src/gtk/connected-device.ui:524 msgid "" "Counteracts input lag by predicting head-tracking position ahead of render " "time. Stick with default unless virtual display drags behind your head " @@ -342,20 +435,36 @@ msgstr "" "le temps de rendu. Restez sur la valeur par défaut à moins que l'affichage " "virtuel ne soit lent, ne saute pas ou ne soit très instable." -#: src/gtk/connected-device.ui:413 +#: src/gtk/connected-device.ui:542 msgid "Default" msgstr "Par défaut" -#: src/gtk/connected-device.ui:425 +#: src/gtk/connected-device.ui:554 msgid "Text Scaling" msgstr "Mise à l'échelle du texte" -#: src/gtk/connected-device.ui:426 +#: src/gtk/connected-device.ui:555 msgid "Scaling text below 1.0 will simulate a higher resolution display" msgstr "" "Une mise à l'échelle du texte en dessous de 1.0 simulera un affichage de " "plus haute résolution" +#: src/gtk/connected-device.ui:585 +msgid "Enable multi-tap detection" +msgstr "" + +#: src/gtk/connected-device.ui:586 +msgid "Enables double-tap to recenter and triple-tap to recalibrate." +msgstr "" + +#: src/gtk/display-distance-dialog.ui:9 +msgid "Show full range" +msgstr "" + +#: src/gtk/display-distance-dialog.ui:18 +msgid "Done" +msgstr "" + #: src/gtk/failed-verification.ui:13 msgid "Breezy Desktop GNOME invalid setup" msgstr "Configuration Breezy Desktop GNOME non valide" @@ -401,17 +510,10 @@ msgstr "Activation automatique de l'effet XR" msgid "" "Automatically enable the Breezy Desktop XR effect when supported glasses are " "connected." -msgstr "Activer automatiquement l'effet Breezy Desktop XR lorsque des lunettes " +msgstr "" +"Activer automatiquement l'effet Breezy Desktop XR lorsque des lunettes " "compatibles sont connectées." -#: src/gtk/no-device.ui:34 -msgid "Start in widescreen mode" -msgstr "Démarer en mode grand écran" - -#: src/gtk/no-device.ui:35 -msgid "Widescreen mode is not supported for all glasses." -msgstr "Le mode écran large n'est pas compatible avec toutes les lunettes." - #: src/gtk/no-driver.ui:13 msgid "No driver running" msgstr "Aucun pilote en cours d'exécution" @@ -521,3 +623,60 @@ msgstr "Réinitialiser" #: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "À propos de BreezyDesktop" + +#~ msgid "Fast SBS mode switching" +#~ msgstr "Bascule rapide vers le mode SBS" + +#~ msgid "" +#~ "Switches glasses to SBS mode immediately when plugged in, if widescreen " +#~ "mode is on. May cause instability." +#~ msgstr "" +#~ "Bascule les lunettes vers le mode SBS immédiatement lorsqu'elles sont " +#~ "branchées si le mode grand écran est activé. Peut provoquer des " +#~ "instabilités." + +#, fuzzy +#~ msgid "Set Unfocused Displays Distance" +#~ msgstr "Distance d'affichage" + +#~ msgid "Widescreen mode" +#~ msgstr "Mode grand écran" + +#~ msgid "Switch between flat and curved displays." +#~ msgstr "Basculez entre les affichages plats et incurvés." + +#~ msgid "" +#~ "Closer appears larger, further appears smaller. Controls depth when in " +#~ "widescreen mode." +#~ msgstr "" +#~ "Plus proche apparaît plus grand, plus éloigné apparaît plus petit. " +#~ "Contrôle la profondeur lorsque vous êtes en mode grand écran." + +#~ msgid "" +#~ "Combine with display distance to achieve a comfortable level of depth and " +#~ "size." +#~ msgstr "" +#~ "Combinez avec la distance d'affichage pour obtenir un niveau de " +#~ "profondeur et de taille confortable." + +#~ msgid "Display toggle distances" +#~ msgstr "Distances de basculement de l'affichage" + +#~ msgid "" +#~ "Use the buttons to capture the current display distance for use with the " +#~ "keyboard shortcut." +#~ msgstr "" +#~ "Utilisez les boutons pour capturer la distance d'affichage actuelle pour " +#~ "une utilisation avec le raccourci clavier." + +#~ msgid "Display distance shortcut" +#~ msgstr "Raccourci de distance d'affichage" + +#~ msgid "Quickly toggle between two predefined distances." +#~ msgstr "Basculez rapidement entre deux distances prédéfinies." + +#~ msgid "Start in widescreen mode" +#~ msgstr "Démarer en mode grand écran" + +#~ msgid "Widescreen mode is not supported for all glasses." +#~ msgstr "Le mode écran large n'est pas compatible avec toutes les lunettes." diff --git a/ui/po/it.po b/ui/po/it.po index a52e46a..39c70d1 100644 --- a/ui/po/it.po +++ b/ui/po/it.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-22 13:27-0700\n" +"POT-Creation-Date: 2025-03-05 12:28-0800\n" "PO-Revision-Date: 2024-08-02 21:14-0700\n" "Last-Translator: \n" "Language-Team: Italian \n" @@ -17,17 +17,64 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: src/connecteddevice.py:17 +#: src/connecteddevice.py:23 msgid "" "Switches your glasses into side-by-side mode and doubles the width of the " "display." msgstr "" -"Passa i tuoi occhiali in modalità side-by-side e raddoppia la larghezza del display." +"Passa i tuoi occhiali in modalità side-by-side e raddoppia la larghezza del " +"display." -#: src/connecteddevice.py:18 +#: src/connecteddevice.py:24 msgid "This feature is not currently supported for your device." msgstr "Questa funzione non è attualmente supportata sul tuo dispositivo." +#: src/connecteddevice.py:124 +#, fuzzy +msgid "Set Focused Display Distance" +msgstr "Distanza del display" + +#: src/connecteddevice.py:125 +msgid "Use a closer value so the display zooms in when you look at it." +msgstr "" + +#: src/connecteddevice.py:132 +#, fuzzy +msgid "Set All Displays Distance" +msgstr "Distanza del display" + +#: src/connecteddevice.py:133 +msgid "Use a farther value so the displays are zoomed out when you look away." +msgstr "" + +#: src/connecteddevice.py:231 +msgid "" +"Unable to add virtual displays on this machine. xdg-desktop-portal is " +"required." +msgstr "" + +#: src/connecteddevice.py:265 +#, fuzzy +msgid "Focused display" +msgstr "Distanza del display" + +#: src/connecteddevice.py:271 +#, fuzzy +msgid "All displays" +msgstr "Display curvo" + +#: src/displaydistancedialogcontent.py:41 +msgid "closer" +msgstr "" + +#: src/displaydistancedialogcontent.py:45 +msgid "fullscreen" +msgstr "" + +#: src/displaydistancedialogcontent.py:48 +msgid "farther" +msgstr "" + #: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "Stato del livello a pagamento" @@ -36,7 +83,7 @@ msgstr "Stato del livello a pagamento" msgid "Feature Availability" msgstr "Disponibilità delle funzionalità" -#: src/licensefeaturerow.py:15 src/shortcutdialog.py:107 +#: src/licensefeaturerow.py:15 src/shortcutdialog.py:104 msgid "Disabled" msgstr "Disabilitato" @@ -165,178 +212,257 @@ msgid "Enables the Breezy Desktop XR effect." msgstr "Abilita l'effetto XR di Breezy Desktop." #: src/gtk/connected-device.ui:55 -msgid "Widescreen mode" -msgstr "Modalità widescreen" +msgid "Zoom on focus" +msgstr "" -#: src/gtk/connected-device.ui:66 +#: src/gtk/connected-device.ui:56 +msgid "" +"Automatically move a display closer when you look at it.\n" +"Set your preferred focused and unfocused distances in the Adjustments " +"section.\n" +" " +msgstr "" + +#: src/gtk/connected-device.ui:69 msgid "Follow mode" msgstr "Modalità di inseguimento" -#: src/gtk/connected-device.ui:67 -msgid "Keep the virtual display near the center of your view." +#: src/gtk/connected-device.ui:70 +#, fuzzy +msgid "Keep the focused display near the center of your view." msgstr "Mantieni il display virtuale vicino al centro del tuo campo visivo." -#: src/gtk/connected-device.ui:77 -msgid "Curved display" -msgstr "Display curvo" +#: src/gtk/connected-device.ui:80 src/gtk/no-device.ui:34 +msgid "Disable physical displays" +msgstr "" -#: src/gtk/connected-device.ui:78 -msgid "Switch between flat and curved displays." -msgstr "Passa da display piatti a curvi." +#: src/gtk/connected-device.ui:81 src/gtk/no-device.ui:35 +msgid "" +"Automatically disable all physical displays when the XR effect is enabled." +msgstr "" #: src/gtk/connected-device.ui:91 +#, fuzzy +msgid "Virtual displays" +msgstr "Display curvo" + +#: src/gtk/connected-device.ui:103 +#, fuzzy +msgid "Rearrange displays" +msgstr "Display curvo" + +#: src/gtk/connected-device.ui:115 +msgid "1080p" +msgstr "" + +#: src/gtk/connected-device.ui:116 +msgid "1440p" +msgstr "" + +#: src/gtk/connected-device.ui:140 msgid "Adjustments" msgstr "Regolazioni" -#: src/gtk/connected-device.ui:94 -msgid "Display distance" +#: src/gtk/connected-device.ui:143 +#, fuzzy +msgid "Display distances" msgstr "Distanza del display" -#: src/gtk/connected-device.ui:95 -msgid "" -"Closer appears larger, further appears smaller. Controls depth when in " -"widescreen mode." +#: src/gtk/connected-device.ui:144 +msgid "Set how close you want displays to appear." msgstr "" -"Più vicino appare più grande, più lontano appare più piccolo. Controlla la profondità quando sei in modalità widescreen." -#: src/gtk/connected-device.ui:123 -msgid "Display size" -msgstr "Dimensione del display" - -#: src/gtk/connected-device.ui:124 -msgid "" -"Combine with display distance to achieve a comfortable level of depth and " -"size." -msgstr "" -"Da combinare con la distanza del display per ottenere un livello confortevole di profondità e dimensione." - -#: src/gtk/connected-device.ui:152 -msgid "Display toggle distances" -msgstr "Distanze di attivazione del cambio display" - -#: src/gtk/connected-device.ui:153 -msgid "" -"Use the buttons to capture the current display distance for use with the " -"keyboard shortcut." -msgstr "" -"Usa i pulsanti per acquisire la distanza attuale del display da utilizzare con la scorciatoia da tastiera." - -#: src/gtk/connected-device.ui:178 +#: src/gtk/connected-device.ui:199 msgid "Follow threshold" msgstr "Soglia di inseguimento" -#: src/gtk/connected-device.ui:179 +#: src/gtk/connected-device.ui:200 msgid "How far away you can look before the display follows." msgstr "Quanto lontano puoi guardare prima che il display ti segua." -#: src/gtk/connected-device.ui:209 src/gtk/connected-device.ui:215 +#: src/gtk/connected-device.ui:223 +#, fuzzy +msgid "Display angling" +msgstr "Dimensione del display" + +#: src/gtk/connected-device.ui:224 +msgid "" +"When there are multiple displays, choose how they should angle towards you." +msgstr "" + +#: src/gtk/connected-device.ui:238 +msgid "Automatic" +msgstr "" + +#: src/gtk/connected-device.ui:239 +msgid "Side-angled" +msgstr "" + +#: src/gtk/connected-device.ui:240 +msgid "Top-angled" +msgstr "" + +#: src/gtk/connected-device.ui:241 +msgid "Flat" +msgstr "" + +#: src/gtk/connected-device.ui:251 +#, fuzzy +msgid "Display spacing" +msgstr "Dimensione del display" + +#: src/gtk/connected-device.ui:252 +msgid "Put empty space between displays, when there are multiple." +msgstr "" + +#: src/gtk/connected-device.ui:282 +msgid "Viewport horizontal offset" +msgstr "" + +#: src/gtk/connected-device.ui:283 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport to the left or right." +msgstr "" + +#: src/gtk/connected-device.ui:312 +msgid "Viewport vertical offset" +msgstr "" + +#: src/gtk/connected-device.ui:313 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport up or down." +msgstr "" + +#: src/gtk/connected-device.ui:349 src/gtk/connected-device.ui:355 msgid "Keyboard Shortcuts" msgstr "Scorciatoie da tastiera" -#: src/gtk/connected-device.ui:218 +#: src/gtk/connected-device.ui:358 msgid "XR Effect on/off shortcut" msgstr "Scorciatoia per on/off dell'effetto XR" -#: src/gtk/connected-device.ui:219 +#: src/gtk/connected-device.ui:359 msgid "" "Quickly enable or disable the XR Effect. You may need to enable the effect " "manually once in order to enable the shortcut." -msgstr "Abilitazione/disabilitazione rapida dell'effetto XR. Può essere necessario abilitare l'effetto manualmente almeno una volta per abilitare la scorciatoia da tastiera." +msgstr "" +"Abilitazione/disabilitazione rapida dell'effetto XR. Può essere necessario " +"abilitare l'effetto manualmente almeno una volta per abilitare la " +"scorciatoia da tastiera." -#: src/gtk/connected-device.ui:238 src/gtk/connected-device.ui:267 -#: src/gtk/connected-device.ui:296 src/gtk/connected-device.ui:325 +#: src/gtk/connected-device.ui:378 src/gtk/connected-device.ui:407 +#: src/gtk/connected-device.ui:436 src/gtk/connected-device.ui:465 msgid "Change" msgstr "Cambia" -#: src/gtk/connected-device.ui:247 +#: src/gtk/connected-device.ui:387 msgid "Re-center display shortcut" msgstr "Scorciatoia per ricentrare il display" -#: src/gtk/connected-device.ui:248 +#: src/gtk/connected-device.ui:388 msgid "Pin the virtual display to the current position." msgstr "Fissa il display virtuale alla posizione attuale." -#: src/gtk/connected-device.ui:276 -msgid "Display distance shortcut" -msgstr "Scorciatoia per la distanza del display" +#: src/gtk/connected-device.ui:416 +#, fuzzy +msgid "Toggle zoom on focus shortcut" +msgstr "Scorciatoia per attivare/disattivare la modalità di inseguimento" -#: src/gtk/connected-device.ui:277 -msgid "Quickly toggle between two predefined distances." -msgstr "Scambio rapido tra due distanze predefinite." +#: src/gtk/connected-device.ui:417 +#, fuzzy +msgid "Quickly toggle zoom on focus mode." +msgstr "Attivazione/disattivazione rapida della modalità di inseguimento." -#: src/gtk/connected-device.ui:305 +#: src/gtk/connected-device.ui:445 msgid "Toggle follow mode shortcut" msgstr "Scorciatoia per attivare/disattivare la modalità di inseguimento" -#: src/gtk/connected-device.ui:306 +#: src/gtk/connected-device.ui:446 msgid "Quickly toggle follow mode." msgstr "Attivazione/disattivazione rapida della modalità di inseguimento." -#: src/gtk/connected-device.ui:341 src/gtk/connected-device.ui:347 +#: src/gtk/connected-device.ui:481 src/gtk/connected-device.ui:487 msgid "Advanced Settings" msgstr "Impostazioni avanzate" -#: src/gtk/connected-device.ui:350 +#: src/gtk/connected-device.ui:490 msgid "Find optimal display config" msgstr "Trova la configurazione ottimale del display" -#: src/gtk/connected-device.ui:351 +#: src/gtk/connected-device.ui:491 msgid "" "Automatically modify the glasses display configuration for maximum " "resolution and best scaling when plugged in." msgstr "" -"Modifica automaticamente la configurazione del display degli occhiali per ottenere la massima risoluzione e la migliore scalabilità quando collegati." +"Modifica automaticamente la configurazione del display degli occhiali per " +"ottenere la massima risoluzione e la migliore scalabilità quando collegati." -#: src/gtk/connected-device.ui:361 +#: src/gtk/connected-device.ui:501 msgid "Use highest refresh rate" msgstr "Usa la frequenza di aggiornamento più elevata" -#: src/gtk/connected-device.ui:362 +#: src/gtk/connected-device.ui:502 msgid "Refresh rate may affect performance, disable this to set it manually." -msgstr "La frequenza di aggiornamento potrebbe influire sulle prestazioni, disabilitala per impostarla manualmente." +msgstr "" +"La frequenza di aggiornamento potrebbe influire sulle prestazioni, " +"disabilitala per impostarla manualmente." -#: src/gtk/connected-device.ui:372 +#: src/gtk/connected-device.ui:512 msgid "Always primary display" msgstr "Imposta sempre come display primario" -#: src/gtk/connected-device.ui:373 +#: src/gtk/connected-device.ui:513 msgid "Automatically set the glasses as the primary display when plugged in." -msgstr "Imposta automaticamente gli occhiali come display primario quando sono collegati." - -#: src/gtk/connected-device.ui:383 -msgid "Fast SBS mode switching" -msgstr "Passaggio rapido alla modalità SBS" - -#: src/gtk/connected-device.ui:384 -msgid "" -"Switches glasses to SBS mode immediately when plugged in, if widescreen mode " -"is on. May cause instability." msgstr "" -"Passa immediatamente gli occhiali alla modalità SBS quando vengono collegati, se la modalità widescreen è attiva. Potrebbe causare instabilità." +"Imposta automaticamente gli occhiali come display primario quando sono " +"collegati." -#: src/gtk/connected-device.ui:394 +#: src/gtk/connected-device.ui:523 msgid "Movement look-ahead" msgstr "Anticipo del movimento" -#: src/gtk/connected-device.ui:395 +#: src/gtk/connected-device.ui:524 msgid "" "Counteracts input lag by predicting head-tracking position ahead of render " "time. Stick with default unless virtual display drags behind your head " "movements, jumps ahead, or is very shaky." msgstr "" -"Compensa il ritardo di input prevedendo la posizione del tracciamento della testa prima del tempo di rendering. Rimani con il valore predefinito a meno che il display virtuale non rimanga indietro rispetto ai tuoi movimenti, non salti in avanti o sia molto tremolante." +"Compensa il ritardo di input prevedendo la posizione del tracciamento della " +"testa prima del tempo di rendering. Rimani con il valore predefinito a meno " +"che il display virtuale non rimanga indietro rispetto ai tuoi movimenti, non " +"salti in avanti o sia molto tremolante." -#: src/gtk/connected-device.ui:413 +#: src/gtk/connected-device.ui:542 msgid "Default" msgstr "Predefinito" -#: src/gtk/connected-device.ui:425 +#: src/gtk/connected-device.ui:554 msgid "Text Scaling" msgstr "Ridimensionamento del testo" -#: src/gtk/connected-device.ui:426 +#: src/gtk/connected-device.ui:555 msgid "Scaling text below 1.0 will simulate a higher resolution display" -msgstr "Ridimensionando il testo sotto a 1.0 si simula una maggiore risoluzione del display" +msgstr "" +"Ridimensionando il testo sotto a 1.0 si simula una maggiore risoluzione del " +"display" + +#: src/gtk/connected-device.ui:585 +msgid "Enable multi-tap detection" +msgstr "" + +#: src/gtk/connected-device.ui:586 +msgid "Enables double-tap to recenter and triple-tap to recalibrate." +msgstr "" + +#: src/gtk/display-distance-dialog.ui:9 +msgid "Show full range" +msgstr "" + +#: src/gtk/display-distance-dialog.ui:18 +msgid "Done" +msgstr "" #: src/gtk/failed-verification.ui:13 msgid "Breezy Desktop GNOME invalid setup" @@ -347,7 +473,9 @@ msgid "" "Your Breezy GNOME setup is invalid or incomplete. Please re-run the setup " "script. Report this issue if it persists." msgstr "" -"La tua configurazione di Breezy GNOME non è valida o è incompleta. Ripeti l'esecuzione dello script di configurazione. Segnala questo problema se persiste." +"La tua configurazione di Breezy GNOME non è valida o è incompleta. Ripeti " +"l'esecuzione dello script di configurazione. Segnala questo problema se " +"persiste." #: src/gtk/license-dialog-content.ui:15 msgid "Donate" @@ -381,15 +509,9 @@ msgstr "Abilitazione automatica effetto XR" msgid "" "Automatically enable the Breezy Desktop XR effect when supported glasses are " "connected." -msgstr "Abilita automaticamente l'effetto XR di Breezy Desktop quando vengono connessi degli occhiali supportati" - -#: src/gtk/no-device.ui:34 -msgid "Start in widescreen mode" -msgstr "Si attiva in modalità widescreen" - -#: src/gtk/no-device.ui:35 -msgid "Widescreen mode is not supported for all glasses." -msgstr "Breezy Desktop non è riuscito a rilevare alcun dispositivo XR supportato." +msgstr "" +"Abilita automaticamente l'effetto XR di Breezy Desktop quando vengono " +"connessi degli occhiali supportati" #: src/gtk/no-driver.ui:13 msgid "No driver running" @@ -407,10 +529,12 @@ msgid "" " " msgstr "" "\n" -" Se hai installato tramite AUR, assicurati di aver eseguito il comando di post-installazione consigliato:\n" +" Se hai installato tramite AUR, assicurati di aver eseguito il " +"comando di post-installazione consigliato:\n" " systemctl --user enable --now xr-driver.service\n" "\n" -" In caso contrario, segnala il problema su GitHub o crea un nuovo thread nel canale #troubleshooting su Discord.\n" +" In caso contrario, segnala il problema su GitHub o crea un nuovo " +"thread nel canale #troubleshooting su Discord.\n" " " #: src/gtk/no-extension.ui:13 @@ -422,7 +546,9 @@ msgid "" "If you have just run the setup, then you may need to log out and back in to " "use it. Otherwise, please follow the Breezy GNOME setup instructions." msgstr "" -"Se hai appena eseguito la configurazione, potrebbe essere necessario disconnettersi e riconnettersi per utilizzarla. In caso contrario, segui le istruzioni di configurazione di Breezy GNOME." +"Se hai appena eseguito la configurazione, potrebbe essere necessario " +"disconnettersi e riconnettersi per utilizzarla. In caso contrario, segui le " +"istruzioni di configurazione di Breezy GNOME." #: src/gtk/no-license.ui:13 msgid "No license file was found" @@ -444,11 +570,16 @@ msgid "" " " msgstr "" "\n" -" La prima volta che utilizzi Breezy Desktop, è necessaria una connessione Internet per recuperare la licenza del tuo dispositivo.\n" +" La prima volta che utilizzi Breezy Desktop, è necessaria una " +"connessione Internet per recuperare la licenza del tuo dispositivo.\n" " \n" -" Non preoccuparti, non è obbligatorio donare adesso. Se non lo hai già fatto, ti verrà fornita una licenza di prova in modo da poter decidere se Breezy Desktop soddisfa le tue esigenze.\n" +" Non preoccuparti, non è obbligatorio donare adesso. Se non lo hai " +"già fatto, ti verrà fornita una licenza di prova in modo da poter decidere " +"se Breezy Desktop soddisfa le tue esigenze.\n" " \n" -" Una volta ottenuta una licenza, di prova o altra, puoi utilizzare Breezy Desktop offline fino alla scadenza delle funzionalità (o a tempo indeterminato, se hai scelto l'accesso perpetuo).\n" +" Una volta ottenuta una licenza, di prova o altra, puoi utilizzare " +"Breezy Desktop offline fino alla scadenza delle funzionalità (o a tempo " +"indeterminato, se hai scelto l'accesso perpetuo).\n" " " #: src/gtk/no-license.ui:27 @@ -490,3 +621,61 @@ msgstr "Reset forzato" #: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "Informazioni su BreezyDesktop" + +#~ msgid "Fast SBS mode switching" +#~ msgstr "Passaggio rapido alla modalità SBS" + +#~ msgid "" +#~ "Switches glasses to SBS mode immediately when plugged in, if widescreen " +#~ "mode is on. May cause instability." +#~ msgstr "" +#~ "Passa immediatamente gli occhiali alla modalità SBS quando vengono " +#~ "collegati, se la modalità widescreen è attiva. Potrebbe causare " +#~ "instabilità." + +#, fuzzy +#~ msgid "Set Unfocused Displays Distance" +#~ msgstr "Distanza del display" + +#~ msgid "Widescreen mode" +#~ msgstr "Modalità widescreen" + +#~ msgid "Switch between flat and curved displays." +#~ msgstr "Passa da display piatti a curvi." + +#~ msgid "" +#~ "Closer appears larger, further appears smaller. Controls depth when in " +#~ "widescreen mode." +#~ msgstr "" +#~ "Più vicino appare più grande, più lontano appare più piccolo. Controlla " +#~ "la profondità quando sei in modalità widescreen." + +#~ msgid "" +#~ "Combine with display distance to achieve a comfortable level of depth and " +#~ "size." +#~ msgstr "" +#~ "Da combinare con la distanza del display per ottenere un livello " +#~ "confortevole di profondità e dimensione." + +#~ msgid "Display toggle distances" +#~ msgstr "Distanze di attivazione del cambio display" + +#~ msgid "" +#~ "Use the buttons to capture the current display distance for use with the " +#~ "keyboard shortcut." +#~ msgstr "" +#~ "Usa i pulsanti per acquisire la distanza attuale del display da " +#~ "utilizzare con la scorciatoia da tastiera." + +#~ msgid "Display distance shortcut" +#~ msgstr "Scorciatoia per la distanza del display" + +#~ msgid "Quickly toggle between two predefined distances." +#~ msgstr "Scambio rapido tra due distanze predefinite." + +#~ msgid "Start in widescreen mode" +#~ msgstr "Si attiva in modalità widescreen" + +#~ msgid "Widescreen mode is not supported for all glasses." +#~ msgstr "" +#~ "Breezy Desktop non è riuscito a rilevare alcun dispositivo XR supportato." diff --git a/ui/po/ja.po b/ui/po/ja.po index 5f735f9..05b152d 100644 --- a/ui/po/ja.po +++ b/ui/po/ja.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-22 13:27-0700\n" +"POT-Creation-Date: 2025-03-05 12:28-0800\n" "PO-Revision-Date: 2024-08-02 20:55-0700\n" "Last-Translator: \n" "Language-Team: Japanese \n" @@ -21,16 +21,62 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: src/connecteddevice.py:17 +#: src/connecteddevice.py:23 msgid "" "Switches your glasses into side-by-side mode and doubles the width of the " "display." msgstr "メガネを3Dモードに切り替え、表示の幅を2倍にします。" -#: src/connecteddevice.py:18 +#: src/connecteddevice.py:24 msgid "This feature is not currently supported for your device." msgstr "現在接続されているデバイスはこの機能に対応していません。" +#: src/connecteddevice.py:124 +#, fuzzy +msgid "Set Focused Display Distance" +msgstr "ディスプレイ距離" + +#: src/connecteddevice.py:125 +msgid "Use a closer value so the display zooms in when you look at it." +msgstr "" + +#: src/connecteddevice.py:132 +#, fuzzy +msgid "Set All Displays Distance" +msgstr "ディスプレイ距離" + +#: src/connecteddevice.py:133 +msgid "Use a farther value so the displays are zoomed out when you look away." +msgstr "" + +#: src/connecteddevice.py:231 +msgid "" +"Unable to add virtual displays on this machine. xdg-desktop-portal is " +"required." +msgstr "" + +#: src/connecteddevice.py:265 +#, fuzzy +msgid "Focused display" +msgstr "ディスプレイ距離" + +#: src/connecteddevice.py:271 +#, fuzzy +msgid "All displays" +msgstr "曲面ディスプレイ" + +#: src/displaydistancedialogcontent.py:41 +msgid "closer" +msgstr "" + +#: src/displaydistancedialogcontent.py:45 +msgid "fullscreen" +msgstr "" + +#: src/displaydistancedialogcontent.py:48 +msgid "farther" +msgstr "" + #: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "有料ティアの状態" @@ -39,7 +85,7 @@ msgstr "有料ティアの状態" msgid "Feature Availability" msgstr "利用できる機能" -#: src/licensefeaturerow.py:15 src/shortcutdialog.py:107 +#: src/licensefeaturerow.py:15 src/shortcutdialog.py:104 msgid "Disabled" msgstr "無効" @@ -168,82 +214,138 @@ msgid "Enables the Breezy Desktop XR effect." msgstr "Breezy DesktopのXRエフェクトを有効にします。" #: src/gtk/connected-device.ui:55 -msgid "Widescreen mode" -msgstr "ワイドスクリーンモード" +msgid "Zoom on focus" +msgstr "" -#: src/gtk/connected-device.ui:66 +#: src/gtk/connected-device.ui:56 +msgid "" +"Automatically move a display closer when you look at it.\n" +"Set your preferred focused and unfocused distances in the Adjustments " +"section.\n" +" " +msgstr "" + +#: src/gtk/connected-device.ui:69 msgid "Follow mode" msgstr "フォローモード" -#: src/gtk/connected-device.ui:67 -msgid "Keep the virtual display near the center of your view." +#: src/gtk/connected-device.ui:70 +#, fuzzy +msgid "Keep the focused display near the center of your view." msgstr "仮想ディスプレイを視界の中心近くに保ちます。" -#: src/gtk/connected-device.ui:77 -msgid "Curved display" -msgstr "曲面ディスプレイ" +#: src/gtk/connected-device.ui:80 src/gtk/no-device.ui:34 +msgid "Disable physical displays" +msgstr "" -#: src/gtk/connected-device.ui:78 -msgid "Switch between flat and curved displays." -msgstr "平面と曲面ディスプレイを切り替えます。" +#: src/gtk/connected-device.ui:81 src/gtk/no-device.ui:35 +msgid "" +"Automatically disable all physical displays when the XR effect is enabled." +msgstr "" #: src/gtk/connected-device.ui:91 +#, fuzzy +msgid "Virtual displays" +msgstr "曲面ディスプレイ" + +#: src/gtk/connected-device.ui:103 +#, fuzzy +msgid "Rearrange displays" +msgstr "曲面ディスプレイ" + +#: src/gtk/connected-device.ui:115 +msgid "1080p" +msgstr "" + +#: src/gtk/connected-device.ui:116 +msgid "1440p" +msgstr "" + +#: src/gtk/connected-device.ui:140 msgid "Adjustments" msgstr "調整" -#: src/gtk/connected-device.ui:94 -msgid "Display distance" +#: src/gtk/connected-device.ui:143 +#, fuzzy +msgid "Display distances" msgstr "ディスプレイ距離" -#: src/gtk/connected-device.ui:95 -msgid "" -"Closer appears larger, further appears smaller. Controls depth when in " -"widescreen mode." +#: src/gtk/connected-device.ui:144 +msgid "Set how close you want displays to appear." msgstr "" -"近づけると大きく見え、遠ざけると小さく見えます。\n" -"ワイドスクリーンモードの奥行きを制御します。" -#: src/gtk/connected-device.ui:123 -msgid "Display size" -msgstr "ディスプレイサイズ" - -#: src/gtk/connected-device.ui:124 -msgid "" -"Combine with display distance to achieve a comfortable level of depth and " -"size." -msgstr "" -"ディスプレイ距離と組み合わせて、\n" -"快適な奥行きとサイズを実現します。" - -#: src/gtk/connected-device.ui:152 -msgid "Display toggle distances" -msgstr "ディスプレイ距離の切り替え" - -#: src/gtk/connected-device.ui:153 -msgid "" -"Use the buttons to capture the current display distance for use with the " -"keyboard shortcut." -msgstr "" -"2個のボタンを使用して、キーボードショートカットで使用する現在のディスプレイ距" -"離をそれぞれ設定します。" - -#: src/gtk/connected-device.ui:178 +#: src/gtk/connected-device.ui:199 msgid "Follow threshold" msgstr "フォローしきい値" -#: src/gtk/connected-device.ui:179 +#: src/gtk/connected-device.ui:200 msgid "How far away you can look before the display follows." msgstr "ディスプレイがフォロー(追従)を開始するまでの距離" -#: src/gtk/connected-device.ui:209 src/gtk/connected-device.ui:215 +#: src/gtk/connected-device.ui:223 +#, fuzzy +msgid "Display angling" +msgstr "ディスプレイサイズ" + +#: src/gtk/connected-device.ui:224 +msgid "" +"When there are multiple displays, choose how they should angle towards you." +msgstr "" + +#: src/gtk/connected-device.ui:238 +msgid "Automatic" +msgstr "" + +#: src/gtk/connected-device.ui:239 +msgid "Side-angled" +msgstr "" + +#: src/gtk/connected-device.ui:240 +msgid "Top-angled" +msgstr "" + +#: src/gtk/connected-device.ui:241 +msgid "Flat" +msgstr "" + +#: src/gtk/connected-device.ui:251 +#, fuzzy +msgid "Display spacing" +msgstr "ディスプレイサイズ" + +#: src/gtk/connected-device.ui:252 +msgid "Put empty space between displays, when there are multiple." +msgstr "" + +#: src/gtk/connected-device.ui:282 +msgid "Viewport horizontal offset" +msgstr "" + +#: src/gtk/connected-device.ui:283 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport to the left or right." +msgstr "" + +#: src/gtk/connected-device.ui:312 +msgid "Viewport vertical offset" +msgstr "" + +#: src/gtk/connected-device.ui:313 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport up or down." +msgstr "" + +#: src/gtk/connected-device.ui:349 src/gtk/connected-device.ui:355 msgid "Keyboard Shortcuts" msgstr "キーボードショートカット" -#: src/gtk/connected-device.ui:218 +#: src/gtk/connected-device.ui:358 msgid "XR Effect on/off shortcut" msgstr "XRエフェクトの切り替え" -#: src/gtk/connected-device.ui:219 +#: src/gtk/connected-device.ui:359 msgid "" "Quickly enable or disable the XR Effect. You may need to enable the effect " "manually once in order to enable the shortcut." @@ -251,44 +353,46 @@ msgstr "" "XRエフェクトの有効・無効を切り替えます。このショートカットを有効にするために" "手動で1回XRエフェクトを有効にする必要があります。" -#: src/gtk/connected-device.ui:238 src/gtk/connected-device.ui:267 -#: src/gtk/connected-device.ui:296 src/gtk/connected-device.ui:325 +#: src/gtk/connected-device.ui:378 src/gtk/connected-device.ui:407 +#: src/gtk/connected-device.ui:436 src/gtk/connected-device.ui:465 msgid "Change" msgstr "変更" -#: src/gtk/connected-device.ui:247 +#: src/gtk/connected-device.ui:387 msgid "Re-center display shortcut" msgstr "ディスプレイを中央へ移動" -#: src/gtk/connected-device.ui:248 +#: src/gtk/connected-device.ui:388 msgid "Pin the virtual display to the current position." msgstr "仮想ディスプレイを現在の視点に固定します。" -#: src/gtk/connected-device.ui:276 -msgid "Display distance shortcut" -msgstr "ディスプレイ距離の切り替え" +#: src/gtk/connected-device.ui:416 +#, fuzzy +msgid "Toggle zoom on focus shortcut" +msgstr "フォローモードの切り替え" -#: src/gtk/connected-device.ui:277 -msgid "Quickly toggle between two predefined distances." -msgstr "設定してある2個の距離をすばやく切り替えます。" +#: src/gtk/connected-device.ui:417 +#, fuzzy +msgid "Quickly toggle zoom on focus mode." +msgstr "フォローモードのオン/オフをすばやく切り替えます。" -#: src/gtk/connected-device.ui:305 +#: src/gtk/connected-device.ui:445 msgid "Toggle follow mode shortcut" msgstr "フォローモードの切り替え" -#: src/gtk/connected-device.ui:306 +#: src/gtk/connected-device.ui:446 msgid "Quickly toggle follow mode." msgstr "フォローモードのオン/オフをすばやく切り替えます。" -#: src/gtk/connected-device.ui:341 src/gtk/connected-device.ui:347 +#: src/gtk/connected-device.ui:481 src/gtk/connected-device.ui:487 msgid "Advanced Settings" msgstr "詳細設定" -#: src/gtk/connected-device.ui:350 +#: src/gtk/connected-device.ui:490 msgid "Find optimal display config" msgstr "ディスプレイ設定を最適化する" -#: src/gtk/connected-device.ui:351 +#: src/gtk/connected-device.ui:491 msgid "" "Automatically modify the glasses display configuration for maximum " "resolution and best scaling when plugged in." @@ -296,41 +400,29 @@ msgstr "" "メガネ接続時、最大解像度と最適なスケーリングのためにディスプレイ設定を自動的" "に変更します。" -#: src/gtk/connected-device.ui:361 +#: src/gtk/connected-device.ui:501 msgid "Use highest refresh rate" msgstr "最大のリフレッシュレートを使用する" -#: src/gtk/connected-device.ui:362 +#: src/gtk/connected-device.ui:502 msgid "Refresh rate may affect performance, disable this to set it manually." msgstr "" "リフレッシュレートはパフォーマンスに影響を与える可能性があります。手動で設定" "する場合は無効にしてください。" -#: src/gtk/connected-device.ui:372 +#: src/gtk/connected-device.ui:512 msgid "Always primary display" msgstr "常にプライマリディスプレイにする" -#: src/gtk/connected-device.ui:373 +#: src/gtk/connected-device.ui:513 msgid "Automatically set the glasses as the primary display when plugged in." msgstr "メガネ接続時、自動的にプライマリディスプレイにします。" -#: src/gtk/connected-device.ui:383 -msgid "Fast SBS mode switching" -msgstr "高速SBSモード切り替え" - -#: src/gtk/connected-device.ui:384 -msgid "" -"Switches glasses to SBS mode immediately when plugged in, if widescreen mode " -"is on. May cause instability." -msgstr "" -"ワイドスクリーンモードがオンになっている場合、メガネ接続時にたたちにSBSモード" -"に切り替えます。不安定になる可能性があります。" - -#: src/gtk/connected-device.ui:394 +#: src/gtk/connected-device.ui:523 msgid "Movement look-ahead" msgstr "動きの先読み" -#: src/gtk/connected-device.ui:395 +#: src/gtk/connected-device.ui:524 msgid "" "Counteracts input lag by predicting head-tracking position ahead of render " "time. Stick with default unless virtual display drags behind your head " @@ -340,20 +432,36 @@ msgstr "" "ます。仮想ディスプレイが頭の動きに遅れたり、先に進んだり、非常に揺れたりする" "場合を除き、デフォルトのままで問題ありません。" -#: src/gtk/connected-device.ui:413 +#: src/gtk/connected-device.ui:542 msgid "Default" msgstr "デフォルト" -#: src/gtk/connected-device.ui:425 +#: src/gtk/connected-device.ui:554 msgid "Text Scaling" msgstr "テキストスケーリング" -#: src/gtk/connected-device.ui:426 +#: src/gtk/connected-device.ui:555 msgid "Scaling text below 1.0 will simulate a higher resolution display" msgstr "" "テキストを1.0未満にスケーリングすると、高解像度ディスプレイをシミュレートしま" "す。" +#: src/gtk/connected-device.ui:585 +msgid "Enable multi-tap detection" +msgstr "" + +#: src/gtk/connected-device.ui:586 +msgid "Enables double-tap to recenter and triple-tap to recalibrate." +msgstr "" + +#: src/gtk/display-distance-dialog.ui:9 +msgid "Show full range" +msgstr "" + +#: src/gtk/display-distance-dialog.ui:18 +msgid "Done" +msgstr "" + #: src/gtk/failed-verification.ui:13 msgid "Breezy Desktop GNOME invalid setup" msgstr "Breezy Desktop GNOMEの無効なセットアップ" @@ -398,15 +506,8 @@ msgstr "XRエフェクトの自動有効化" msgid "" "Automatically enable the Breezy Desktop XR effect when supported glasses are " "connected." -msgstr "対応メガネを接続するとBreezy DesktopのXRエフェクトを自動的に開始します。" - -#: src/gtk/no-device.ui:34 -msgid "Start in widescreen mode" -msgstr "ワイドスクリーンモードで起動" - -#: src/gtk/no-device.ui:35 -msgid "Widescreen mode is not supported for all glasses." -msgstr "ワイドスクリーンモードはすべてのメガネで対応しているわけではありません。" +msgstr "" +"対応メガネを接続するとBreezy DesktopのXRエフェクトを自動的に開始します。" #: src/gtk/no-driver.ui:13 msgid "No driver running" @@ -517,3 +618,60 @@ msgstr "強制リセット" #: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "Breezy Desktopについて" + +#~ msgid "Fast SBS mode switching" +#~ msgstr "高速SBSモード切り替え" + +#~ msgid "" +#~ "Switches glasses to SBS mode immediately when plugged in, if widescreen " +#~ "mode is on. May cause instability." +#~ msgstr "" +#~ "ワイドスクリーンモードがオンになっている場合、メガネ接続時にたたちにSBS" +#~ "モードに切り替えます。不安定になる可能性があります。" + +#, fuzzy +#~ msgid "Set Unfocused Displays Distance" +#~ msgstr "ディスプレイ距離" + +#~ msgid "Widescreen mode" +#~ msgstr "ワイドスクリーンモード" + +#~ msgid "Switch between flat and curved displays." +#~ msgstr "平面と曲面ディスプレイを切り替えます。" + +#~ msgid "" +#~ "Closer appears larger, further appears smaller. Controls depth when in " +#~ "widescreen mode." +#~ msgstr "" +#~ "近づけると大きく見え、遠ざけると小さく見えます。\n" +#~ "ワイドスクリーンモードの奥行きを制御します。" + +#~ msgid "" +#~ "Combine with display distance to achieve a comfortable level of depth and " +#~ "size." +#~ msgstr "" +#~ "ディスプレイ距離と組み合わせて、\n" +#~ "快適な奥行きとサイズを実現します。" + +#~ msgid "Display toggle distances" +#~ msgstr "ディスプレイ距離の切り替え" + +#~ msgid "" +#~ "Use the buttons to capture the current display distance for use with the " +#~ "keyboard shortcut." +#~ msgstr "" +#~ "2個のボタンを使用して、キーボードショートカットで使用する現在のディスプレ" +#~ "イ距離をそれぞれ設定します。" + +#~ msgid "Display distance shortcut" +#~ msgstr "ディスプレイ距離の切り替え" + +#~ msgid "Quickly toggle between two predefined distances." +#~ msgstr "設定してある2個の距離をすばやく切り替えます。" + +#~ msgid "Start in widescreen mode" +#~ msgstr "ワイドスクリーンモードで起動" + +#~ msgid "Widescreen mode is not supported for all glasses." +#~ msgstr "" +#~ "ワイドスクリーンモードはすべてのメガネで対応しているわけではありません。" diff --git a/ui/po/mo/de/LC_MESSAGES/breezydesktop.mo b/ui/po/mo/de/LC_MESSAGES/breezydesktop.mo index b0e713e..812e950 100644 Binary files a/ui/po/mo/de/LC_MESSAGES/breezydesktop.mo and b/ui/po/mo/de/LC_MESSAGES/breezydesktop.mo differ diff --git a/ui/po/mo/es/LC_MESSAGES/breezydesktop.mo b/ui/po/mo/es/LC_MESSAGES/breezydesktop.mo index e63ac9c..10a23bb 100644 Binary files a/ui/po/mo/es/LC_MESSAGES/breezydesktop.mo and b/ui/po/mo/es/LC_MESSAGES/breezydesktop.mo differ diff --git a/ui/po/mo/fr/LC_MESSAGES/breezydesktop.mo b/ui/po/mo/fr/LC_MESSAGES/breezydesktop.mo index d2edf14..c8d36cb 100644 Binary files a/ui/po/mo/fr/LC_MESSAGES/breezydesktop.mo and b/ui/po/mo/fr/LC_MESSAGES/breezydesktop.mo differ diff --git a/ui/po/mo/it/LC_MESSAGES/breezydesktop.mo b/ui/po/mo/it/LC_MESSAGES/breezydesktop.mo index 0485f79..56522b1 100644 Binary files a/ui/po/mo/it/LC_MESSAGES/breezydesktop.mo and b/ui/po/mo/it/LC_MESSAGES/breezydesktop.mo differ diff --git a/ui/po/mo/ja/LC_MESSAGES/breezydesktop.mo b/ui/po/mo/ja/LC_MESSAGES/breezydesktop.mo index b039e79..2cc0d3a 100644 Binary files a/ui/po/mo/ja/LC_MESSAGES/breezydesktop.mo and b/ui/po/mo/ja/LC_MESSAGES/breezydesktop.mo differ diff --git a/ui/po/mo/pt_BR/LC_MESSAGES/breezydesktop.mo b/ui/po/mo/pt_BR/LC_MESSAGES/breezydesktop.mo index 18c9d92..3b1c11c 100644 Binary files a/ui/po/mo/pt_BR/LC_MESSAGES/breezydesktop.mo and b/ui/po/mo/pt_BR/LC_MESSAGES/breezydesktop.mo differ diff --git a/ui/po/mo/ru/LC_MESSAGES/breezydesktop.mo b/ui/po/mo/ru/LC_MESSAGES/breezydesktop.mo index 17b4db3..337a833 100644 Binary files a/ui/po/mo/ru/LC_MESSAGES/breezydesktop.mo and b/ui/po/mo/ru/LC_MESSAGES/breezydesktop.mo differ diff --git a/ui/po/mo/sv/LC_MESSAGES/breezydesktop.mo b/ui/po/mo/sv/LC_MESSAGES/breezydesktop.mo index 2629fc1..aafc036 100644 Binary files a/ui/po/mo/sv/LC_MESSAGES/breezydesktop.mo and b/ui/po/mo/sv/LC_MESSAGES/breezydesktop.mo differ diff --git a/ui/po/mo/uk_UA/LC_MESSAGES/breezydesktop.mo b/ui/po/mo/uk_UA/LC_MESSAGES/breezydesktop.mo index d795448..f9f6b67 100644 Binary files a/ui/po/mo/uk_UA/LC_MESSAGES/breezydesktop.mo and b/ui/po/mo/uk_UA/LC_MESSAGES/breezydesktop.mo differ diff --git a/ui/po/mo/zh_CN/LC_MESSAGES/breezydesktop.mo b/ui/po/mo/zh_CN/LC_MESSAGES/breezydesktop.mo index 9a1698a..2373235 100644 Binary files a/ui/po/mo/zh_CN/LC_MESSAGES/breezydesktop.mo and b/ui/po/mo/zh_CN/LC_MESSAGES/breezydesktop.mo differ diff --git a/ui/po/pl.po b/ui/po/pl.po index 0b599c8..2bb7084 100644 --- a/ui/po/pl.po +++ b/ui/po/pl.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-22 13:27-0700\n" +"POT-Creation-Date: 2025-03-05 12:28-0800\n" "PO-Revision-Date: 2024-08-16 10:26-0700\n" "Last-Translator: \n" "Language-Team: Polish \n" @@ -18,16 +18,58 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" -#: src/connecteddevice.py:17 +#: src/connecteddevice.py:23 msgid "" "Switches your glasses into side-by-side mode and doubles the width of the " "display." msgstr "" -#: src/connecteddevice.py:18 +#: src/connecteddevice.py:24 msgid "This feature is not currently supported for your device." msgstr "" +#: src/connecteddevice.py:124 +msgid "Set Focused Display Distance" +msgstr "" + +#: src/connecteddevice.py:125 +msgid "Use a closer value so the display zooms in when you look at it." +msgstr "" + +#: src/connecteddevice.py:132 +msgid "Set All Displays Distance" +msgstr "" + +#: src/connecteddevice.py:133 +msgid "Use a farther value so the displays are zoomed out when you look away." +msgstr "" + +#: src/connecteddevice.py:231 +msgid "" +"Unable to add virtual displays on this machine. xdg-desktop-portal is " +"required." +msgstr "" + +#: src/connecteddevice.py:265 +msgid "Focused display" +msgstr "" + +#: src/connecteddevice.py:271 +msgid "All displays" +msgstr "" + +#: src/displaydistancedialogcontent.py:41 +msgid "closer" +msgstr "" + +#: src/displaydistancedialogcontent.py:45 +msgid "fullscreen" +msgstr "" + +#: src/displaydistancedialogcontent.py:48 +msgid "farther" +msgstr "" + #: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "" @@ -36,7 +78,7 @@ msgstr "" msgid "Feature Availability" msgstr "" -#: src/licensefeaturerow.py:15 src/shortcutdialog.py:107 +#: src/licensefeaturerow.py:15 src/shortcutdialog.py:104 msgid "Disabled" msgstr "" @@ -165,173 +207,235 @@ msgid "Enables the Breezy Desktop XR effect." msgstr "" #: src/gtk/connected-device.ui:55 -msgid "Widescreen mode" +msgid "Zoom on focus" msgstr "" -#: src/gtk/connected-device.ui:66 +#: src/gtk/connected-device.ui:56 +msgid "" +"Automatically move a display closer when you look at it.\n" +"Set your preferred focused and unfocused distances in the Adjustments " +"section.\n" +" " +msgstr "" + +#: src/gtk/connected-device.ui:69 msgid "Follow mode" msgstr "" -#: src/gtk/connected-device.ui:67 -msgid "Keep the virtual display near the center of your view." +#: src/gtk/connected-device.ui:70 +msgid "Keep the focused display near the center of your view." msgstr "" -#: src/gtk/connected-device.ui:77 -msgid "Curved display" +#: src/gtk/connected-device.ui:80 src/gtk/no-device.ui:34 +msgid "Disable physical displays" msgstr "" -#: src/gtk/connected-device.ui:78 -msgid "Switch between flat and curved displays." +#: src/gtk/connected-device.ui:81 src/gtk/no-device.ui:35 +msgid "" +"Automatically disable all physical displays when the XR effect is enabled." msgstr "" #: src/gtk/connected-device.ui:91 +msgid "Virtual displays" +msgstr "" + +#: src/gtk/connected-device.ui:103 +msgid "Rearrange displays" +msgstr "" + +#: src/gtk/connected-device.ui:115 +msgid "1080p" +msgstr "" + +#: src/gtk/connected-device.ui:116 +msgid "1440p" +msgstr "" + +#: src/gtk/connected-device.ui:140 msgid "Adjustments" msgstr "" -#: src/gtk/connected-device.ui:94 -msgid "Display distance" +#: src/gtk/connected-device.ui:143 +msgid "Display distances" msgstr "" -#: src/gtk/connected-device.ui:95 -msgid "" -"Closer appears larger, further appears smaller. Controls depth when in " -"widescreen mode." +#: src/gtk/connected-device.ui:144 +msgid "Set how close you want displays to appear." msgstr "" -#: src/gtk/connected-device.ui:123 -msgid "Display size" -msgstr "" - -#: src/gtk/connected-device.ui:124 -msgid "" -"Combine with display distance to achieve a comfortable level of depth and " -"size." -msgstr "" - -#: src/gtk/connected-device.ui:152 -msgid "Display toggle distances" -msgstr "" - -#: src/gtk/connected-device.ui:153 -msgid "" -"Use the buttons to capture the current display distance for use with the " -"keyboard shortcut." -msgstr "" - -#: src/gtk/connected-device.ui:178 +#: src/gtk/connected-device.ui:199 msgid "Follow threshold" msgstr "" -#: src/gtk/connected-device.ui:179 +#: src/gtk/connected-device.ui:200 msgid "How far away you can look before the display follows." msgstr "" -#: src/gtk/connected-device.ui:209 src/gtk/connected-device.ui:215 +#: src/gtk/connected-device.ui:223 +msgid "Display angling" +msgstr "" + +#: src/gtk/connected-device.ui:224 +msgid "" +"When there are multiple displays, choose how they should angle towards you." +msgstr "" + +#: src/gtk/connected-device.ui:238 +msgid "Automatic" +msgstr "" + +#: src/gtk/connected-device.ui:239 +msgid "Side-angled" +msgstr "" + +#: src/gtk/connected-device.ui:240 +msgid "Top-angled" +msgstr "" + +#: src/gtk/connected-device.ui:241 +msgid "Flat" +msgstr "" + +#: src/gtk/connected-device.ui:251 +msgid "Display spacing" +msgstr "" + +#: src/gtk/connected-device.ui:252 +msgid "Put empty space between displays, when there are multiple." +msgstr "" + +#: src/gtk/connected-device.ui:282 +msgid "Viewport horizontal offset" +msgstr "" + +#: src/gtk/connected-device.ui:283 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport to the left or right." +msgstr "" + +#: src/gtk/connected-device.ui:312 +msgid "Viewport vertical offset" +msgstr "" + +#: src/gtk/connected-device.ui:313 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport up or down." +msgstr "" + +#: src/gtk/connected-device.ui:349 src/gtk/connected-device.ui:355 msgid "Keyboard Shortcuts" msgstr "" -#: src/gtk/connected-device.ui:218 +#: src/gtk/connected-device.ui:358 msgid "XR Effect on/off shortcut" msgstr "" -#: src/gtk/connected-device.ui:219 +#: src/gtk/connected-device.ui:359 msgid "" "Quickly enable or disable the XR Effect. You may need to enable the effect " "manually once in order to enable the shortcut." msgstr "" -#: src/gtk/connected-device.ui:238 src/gtk/connected-device.ui:267 -#: src/gtk/connected-device.ui:296 src/gtk/connected-device.ui:325 +#: src/gtk/connected-device.ui:378 src/gtk/connected-device.ui:407 +#: src/gtk/connected-device.ui:436 src/gtk/connected-device.ui:465 msgid "Change" msgstr "" -#: src/gtk/connected-device.ui:247 +#: src/gtk/connected-device.ui:387 msgid "Re-center display shortcut" msgstr "" -#: src/gtk/connected-device.ui:248 +#: src/gtk/connected-device.ui:388 msgid "Pin the virtual display to the current position." msgstr "" -#: src/gtk/connected-device.ui:276 -msgid "Display distance shortcut" +#: src/gtk/connected-device.ui:416 +msgid "Toggle zoom on focus shortcut" msgstr "" -#: src/gtk/connected-device.ui:277 -msgid "Quickly toggle between two predefined distances." +#: src/gtk/connected-device.ui:417 +msgid "Quickly toggle zoom on focus mode." msgstr "" -#: src/gtk/connected-device.ui:305 +#: src/gtk/connected-device.ui:445 msgid "Toggle follow mode shortcut" msgstr "" -#: src/gtk/connected-device.ui:306 +#: src/gtk/connected-device.ui:446 msgid "Quickly toggle follow mode." msgstr "" -#: src/gtk/connected-device.ui:341 src/gtk/connected-device.ui:347 +#: src/gtk/connected-device.ui:481 src/gtk/connected-device.ui:487 msgid "Advanced Settings" msgstr "" -#: src/gtk/connected-device.ui:350 +#: src/gtk/connected-device.ui:490 msgid "Find optimal display config" msgstr "" -#: src/gtk/connected-device.ui:351 +#: src/gtk/connected-device.ui:491 msgid "" "Automatically modify the glasses display configuration for maximum " "resolution and best scaling when plugged in." msgstr "" -#: src/gtk/connected-device.ui:361 +#: src/gtk/connected-device.ui:501 msgid "Use highest refresh rate" msgstr "" -#: src/gtk/connected-device.ui:362 +#: src/gtk/connected-device.ui:502 msgid "Refresh rate may affect performance, disable this to set it manually." msgstr "" -#: src/gtk/connected-device.ui:372 +#: src/gtk/connected-device.ui:512 msgid "Always primary display" msgstr "" -#: src/gtk/connected-device.ui:373 +#: src/gtk/connected-device.ui:513 msgid "Automatically set the glasses as the primary display when plugged in." msgstr "" -#: src/gtk/connected-device.ui:383 -msgid "Fast SBS mode switching" -msgstr "" - -#: src/gtk/connected-device.ui:384 -msgid "" -"Switches glasses to SBS mode immediately when plugged in, if widescreen mode " -"is on. May cause instability." -msgstr "" - -#: src/gtk/connected-device.ui:394 +#: src/gtk/connected-device.ui:523 msgid "Movement look-ahead" msgstr "" -#: src/gtk/connected-device.ui:395 +#: src/gtk/connected-device.ui:524 msgid "" "Counteracts input lag by predicting head-tracking position ahead of render " "time. Stick with default unless virtual display drags behind your head " "movements, jumps ahead, or is very shaky." msgstr "" -#: src/gtk/connected-device.ui:413 +#: src/gtk/connected-device.ui:542 msgid "Default" msgstr "" -#: src/gtk/connected-device.ui:425 +#: src/gtk/connected-device.ui:554 msgid "Text Scaling" msgstr "" -#: src/gtk/connected-device.ui:426 +#: src/gtk/connected-device.ui:555 msgid "Scaling text below 1.0 will simulate a higher resolution display" msgstr "" +#: src/gtk/connected-device.ui:585 +msgid "Enable multi-tap detection" +msgstr "" + +#: src/gtk/connected-device.ui:586 +msgid "Enables double-tap to recenter and triple-tap to recalibrate." +msgstr "" + +#: src/gtk/display-distance-dialog.ui:9 +msgid "Show full range" +msgstr "" + +#: src/gtk/display-distance-dialog.ui:18 +msgid "Done" +msgstr "" + #: src/gtk/failed-verification.ui:13 msgid "Breezy Desktop GNOME invalid setup" msgstr "" @@ -376,14 +480,6 @@ msgid "" "connected." msgstr "" -#: src/gtk/no-device.ui:34 -msgid "Start in widescreen mode" -msgstr "" - -#: src/gtk/no-device.ui:35 -msgid "Widescreen mode is not supported for all glasses." -msgstr "" - #: src/gtk/no-driver.ui:13 msgid "No driver running" msgstr "" diff --git a/ui/po/pt_BR.po b/ui/po/pt_BR.po index 1ca89cc..c13a71b 100644 --- a/ui/po/pt_BR.po +++ b/ui/po/pt_BR.po @@ -7,18 +7,18 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-22 13:27-0700\n" +"POT-Creation-Date: 2025-03-05 12:28-0800\n" "PO-Revision-Date: 2024-08-19 09:39-0700\n" "Last-Translator: \n" -"Language-Team: Brazilian Portuguese \n" +"Language-Team: Brazilian Portuguese \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: src/connecteddevice.py:17 +#: src/connecteddevice.py:23 msgid "" "Switches your glasses into side-by-side mode and doubles the width of the " "display." @@ -26,10 +26,56 @@ msgstr "" "Altera o modo dos óculos para lado a lado (SBS) e dobra a largura da tela " "(ultrawide)." -#: src/connecteddevice.py:18 +#: src/connecteddevice.py:24 msgid "This feature is not currently supported for your device." msgstr "Este recurso não é atualmente suportado para o seu dispositivo." +#: src/connecteddevice.py:124 +#, fuzzy +msgid "Set Focused Display Distance" +msgstr "Distância da tela" + +#: src/connecteddevice.py:125 +msgid "Use a closer value so the display zooms in when you look at it." +msgstr "" + +#: src/connecteddevice.py:132 +#, fuzzy +msgid "Set All Displays Distance" +msgstr "Distância da tela" + +#: src/connecteddevice.py:133 +msgid "Use a farther value so the displays are zoomed out when you look away." +msgstr "" + +#: src/connecteddevice.py:231 +msgid "" +"Unable to add virtual displays on this machine. xdg-desktop-portal is " +"required." +msgstr "" + +#: src/connecteddevice.py:265 +#, fuzzy +msgid "Focused display" +msgstr "Distância da tela" + +#: src/connecteddevice.py:271 +#, fuzzy +msgid "All displays" +msgstr "Tela curva" + +#: src/displaydistancedialogcontent.py:41 +msgid "closer" +msgstr "" + +#: src/displaydistancedialogcontent.py:45 +msgid "fullscreen" +msgstr "" + +#: src/displaydistancedialogcontent.py:48 +msgid "farther" +msgstr "" + #: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "Status do Nível Pago" @@ -38,7 +84,7 @@ msgstr "Status do Nível Pago" msgid "Feature Availability" msgstr "Disponibilidade de Recursos" -#: src/licensefeaturerow.py:15 src/shortcutdialog.py:107 +#: src/licensefeaturerow.py:15 src/shortcutdialog.py:104 msgid "Disabled" msgstr "Desabilitado" @@ -167,125 +213,183 @@ msgid "Enables the Breezy Desktop XR effect." msgstr "Habilita o efeito XR do Breezy Desktop." #: src/gtk/connected-device.ui:55 -msgid "Widescreen mode" -msgstr "Modo Ultrawide" +msgid "Zoom on focus" +msgstr "" -#: src/gtk/connected-device.ui:66 +#: src/gtk/connected-device.ui:56 +msgid "" +"Automatically move a display closer when you look at it.\n" +"Set your preferred focused and unfocused distances in the Adjustments " +"section.\n" +" " +msgstr "" + +#: src/gtk/connected-device.ui:69 msgid "Follow mode" msgstr "Modo de acompanhamento" -#: src/gtk/connected-device.ui:67 -msgid "Keep the virtual display near the center of your view." +#: src/gtk/connected-device.ui:70 +#, fuzzy +msgid "Keep the focused display near the center of your view." msgstr "Mantenha a tela virtual próxima ao centro da sua visão." -#: src/gtk/connected-device.ui:77 -msgid "Curved display" -msgstr "Tela curva" +#: src/gtk/connected-device.ui:80 src/gtk/no-device.ui:34 +msgid "Disable physical displays" +msgstr "" -#: src/gtk/connected-device.ui:78 -msgid "Switch between flat and curved displays." -msgstr "Alterne entre tela plana e tela curva." +#: src/gtk/connected-device.ui:81 src/gtk/no-device.ui:35 +msgid "" +"Automatically disable all physical displays when the XR effect is enabled." +msgstr "" #: src/gtk/connected-device.ui:91 +#, fuzzy +msgid "Virtual displays" +msgstr "Tela curva" + +#: src/gtk/connected-device.ui:103 +#, fuzzy +msgid "Rearrange displays" +msgstr "Tela curva" + +#: src/gtk/connected-device.ui:115 +msgid "1080p" +msgstr "" + +#: src/gtk/connected-device.ui:116 +msgid "1440p" +msgstr "" + +#: src/gtk/connected-device.ui:140 msgid "Adjustments" msgstr "Ajustes" -#: src/gtk/connected-device.ui:94 -msgid "Display distance" +#: src/gtk/connected-device.ui:143 +#, fuzzy +msgid "Display distances" msgstr "Distância da tela" -#: src/gtk/connected-device.ui:95 -msgid "" -"Closer appears larger, further appears smaller. Controls depth when in " -"widescreen mode." +#: src/gtk/connected-device.ui:144 +msgid "Set how close you want displays to appear." msgstr "" -"Mais perto parece maior, mais longe parece menor. Controla a profundidade no " -"modo ultrawide." -#: src/gtk/connected-device.ui:123 -msgid "Display size" -msgstr "Tamanho da tela" - -#: src/gtk/connected-device.ui:124 -msgid "" -"Combine with display distance to achieve a comfortable level of depth and " -"size." -msgstr "" -"Combine com a distância da tela para alcançar um nível confortável de " -"profundidade e tamanho." - -#: src/gtk/connected-device.ui:152 -msgid "Display toggle distances" -msgstr "Alternância das distâncias de tela" - -#: src/gtk/connected-device.ui:153 -msgid "" -"Use the buttons to capture the current display distance for use with the " -"keyboard shortcut." -msgstr "" -"Use os botões para obter a distância atual da tela para depois usar com o " -"atalho de teclado." - -#: src/gtk/connected-device.ui:178 +#: src/gtk/connected-device.ui:199 msgid "Follow threshold" msgstr "Sensibilidade do modo de acompanhamento" -#: src/gtk/connected-device.ui:179 +#: src/gtk/connected-device.ui:200 msgid "How far away you can look before the display follows." msgstr "Quanto você pode mover a cabeça antes que a tela te acompanhe." -#: src/gtk/connected-device.ui:209 src/gtk/connected-device.ui:215 +#: src/gtk/connected-device.ui:223 +#, fuzzy +msgid "Display angling" +msgstr "Tamanho da tela" + +#: src/gtk/connected-device.ui:224 +msgid "" +"When there are multiple displays, choose how they should angle towards you." +msgstr "" + +#: src/gtk/connected-device.ui:238 +msgid "Automatic" +msgstr "" + +#: src/gtk/connected-device.ui:239 +msgid "Side-angled" +msgstr "" + +#: src/gtk/connected-device.ui:240 +msgid "Top-angled" +msgstr "" + +#: src/gtk/connected-device.ui:241 +msgid "Flat" +msgstr "" + +#: src/gtk/connected-device.ui:251 +#, fuzzy +msgid "Display spacing" +msgstr "Tamanho da tela" + +#: src/gtk/connected-device.ui:252 +msgid "Put empty space between displays, when there are multiple." +msgstr "" + +#: src/gtk/connected-device.ui:282 +msgid "Viewport horizontal offset" +msgstr "" + +#: src/gtk/connected-device.ui:283 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport to the left or right." +msgstr "" + +#: src/gtk/connected-device.ui:312 +msgid "Viewport vertical offset" +msgstr "" + +#: src/gtk/connected-device.ui:313 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport up or down." +msgstr "" + +#: src/gtk/connected-device.ui:349 src/gtk/connected-device.ui:355 msgid "Keyboard Shortcuts" msgstr "Atalhos de teclado" -#: src/gtk/connected-device.ui:218 +#: src/gtk/connected-device.ui:358 msgid "XR Effect on/off shortcut" msgstr "" -#: src/gtk/connected-device.ui:219 +#: src/gtk/connected-device.ui:359 msgid "" "Quickly enable or disable the XR Effect. You may need to enable the effect " "manually once in order to enable the shortcut." msgstr "" -#: src/gtk/connected-device.ui:238 src/gtk/connected-device.ui:267 -#: src/gtk/connected-device.ui:296 src/gtk/connected-device.ui:325 +#: src/gtk/connected-device.ui:378 src/gtk/connected-device.ui:407 +#: src/gtk/connected-device.ui:436 src/gtk/connected-device.ui:465 msgid "Change" msgstr "Alterar" -#: src/gtk/connected-device.ui:247 +#: src/gtk/connected-device.ui:387 msgid "Re-center display shortcut" msgstr "Atalho para recentralizar a tela" -#: src/gtk/connected-device.ui:248 +#: src/gtk/connected-device.ui:388 msgid "Pin the virtual display to the current position." msgstr "Traga a tela virtual para onde você está olhando." -#: src/gtk/connected-device.ui:276 -msgid "Display distance shortcut" -msgstr "Atalho de distância da tela" +#: src/gtk/connected-device.ui:416 +#, fuzzy +msgid "Toggle zoom on focus shortcut" +msgstr "Atalho para alternar o modo de acompanhamento" -#: src/gtk/connected-device.ui:277 -msgid "Quickly toggle between two predefined distances." -msgstr "Alterne rapidamente entre as duas distâncias predefinidas." +#: src/gtk/connected-device.ui:417 +#, fuzzy +msgid "Quickly toggle zoom on focus mode." +msgstr "Alterne rapidamente o modo de acompanhamento." -#: src/gtk/connected-device.ui:305 +#: src/gtk/connected-device.ui:445 msgid "Toggle follow mode shortcut" msgstr "Atalho para alternar o modo de acompanhamento" -#: src/gtk/connected-device.ui:306 +#: src/gtk/connected-device.ui:446 msgid "Quickly toggle follow mode." msgstr "Alterne rapidamente o modo de acompanhamento." -#: src/gtk/connected-device.ui:341 src/gtk/connected-device.ui:347 +#: src/gtk/connected-device.ui:481 src/gtk/connected-device.ui:487 msgid "Advanced Settings" msgstr "Configurações Avançadas" -#: src/gtk/connected-device.ui:350 +#: src/gtk/connected-device.ui:490 msgid "Find optimal display config" msgstr "Encontre a configuração de tela ideal" -#: src/gtk/connected-device.ui:351 +#: src/gtk/connected-device.ui:491 msgid "" "Automatically modify the glasses display configuration for maximum " "resolution and best scaling when plugged in." @@ -293,42 +397,30 @@ msgstr "" "Modifique automaticamente a configuração de exibição dos óculos para máxima " "resolução e melhor dimensionamento quando conectado." -#: src/gtk/connected-device.ui:361 +#: src/gtk/connected-device.ui:501 msgid "Use highest refresh rate" msgstr "Use a taxa de atualização mais alta" -#: src/gtk/connected-device.ui:362 +#: src/gtk/connected-device.ui:502 msgid "Refresh rate may affect performance, disable this to set it manually." msgstr "" "A taxa de atualização pode afetar o desempenho, desative-a para defini-la " "manualmente." -#: src/gtk/connected-device.ui:372 +#: src/gtk/connected-device.ui:512 msgid "Always primary display" msgstr "Sempre tela principal" -#: src/gtk/connected-device.ui:373 +#: src/gtk/connected-device.ui:513 msgid "Automatically set the glasses as the primary display when plugged in." msgstr "" "Defina automaticamente os óculos como a tela primária quando conectados." -#: src/gtk/connected-device.ui:383 -msgid "Fast SBS mode switching" -msgstr "Alternância rápida do modo SBS" - -#: src/gtk/connected-device.ui:384 -msgid "" -"Switches glasses to SBS mode immediately when plugged in, if widescreen mode " -"is on. May cause instability." -msgstr "" -"Altera os óculos para o modo SBS imediatamente quando conectados, se o modo " -"ultrawide estiver ativado. Pode causar instabilidade." - -#: src/gtk/connected-device.ui:394 +#: src/gtk/connected-device.ui:523 msgid "Movement look-ahead" msgstr "Antecipação de movimento" -#: src/gtk/connected-device.ui:395 +#: src/gtk/connected-device.ui:524 msgid "" "Counteracts input lag by predicting head-tracking position ahead of render " "time. Stick with default unless virtual display drags behind your head " @@ -339,19 +431,35 @@ msgstr "" "virtual tenha atrasos, avance ou seja muito instável em relação aos " "movimentos da cabeça " -#: src/gtk/connected-device.ui:413 +#: src/gtk/connected-device.ui:542 msgid "Default" msgstr "Padrão" -#: src/gtk/connected-device.ui:425 +#: src/gtk/connected-device.ui:554 msgid "Text Scaling" msgstr "Redimensionamento de Texto" -#: src/gtk/connected-device.ui:426 +#: src/gtk/connected-device.ui:555 msgid "Scaling text below 1.0 will simulate a higher resolution display" msgstr "" "Redimensionar o texto abaixo de 1.0 simulará uma tela de resolução mais alta" +#: src/gtk/connected-device.ui:585 +msgid "Enable multi-tap detection" +msgstr "" + +#: src/gtk/connected-device.ui:586 +msgid "Enables double-tap to recenter and triple-tap to recalibrate." +msgstr "" + +#: src/gtk/display-distance-dialog.ui:9 +msgid "Show full range" +msgstr "" + +#: src/gtk/display-distance-dialog.ui:18 +msgid "Done" +msgstr "" + #: src/gtk/failed-verification.ui:13 msgid "Breezy Desktop GNOME invalid setup" msgstr "Configuração inválida do Breezy Desktop GNOME" @@ -401,15 +509,6 @@ msgid "" "connected." msgstr "" -#: src/gtk/no-device.ui:34 -#, fuzzy -msgid "Start in widescreen mode" -msgstr "Modo Ultrawide" - -#: src/gtk/no-device.ui:35 -msgid "Widescreen mode is not supported for all glasses." -msgstr "" - #: src/gtk/no-driver.ui:13 msgid "No driver running" msgstr "Nenhum driver em execução" @@ -516,3 +615,57 @@ msgstr "Forçar redefinição" #: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "Sobre o BreezyDesktop" + +#~ msgid "Fast SBS mode switching" +#~ msgstr "Alternância rápida do modo SBS" + +#~ msgid "" +#~ "Switches glasses to SBS mode immediately when plugged in, if widescreen " +#~ "mode is on. May cause instability." +#~ msgstr "" +#~ "Altera os óculos para o modo SBS imediatamente quando conectados, se o " +#~ "modo ultrawide estiver ativado. Pode causar instabilidade." + +#, fuzzy +#~ msgid "Set Unfocused Displays Distance" +#~ msgstr "Distância da tela" + +#~ msgid "Widescreen mode" +#~ msgstr "Modo Ultrawide" + +#~ msgid "Switch between flat and curved displays." +#~ msgstr "Alterne entre tela plana e tela curva." + +#~ msgid "" +#~ "Closer appears larger, further appears smaller. Controls depth when in " +#~ "widescreen mode." +#~ msgstr "" +#~ "Mais perto parece maior, mais longe parece menor. Controla a profundidade " +#~ "no modo ultrawide." + +#~ msgid "" +#~ "Combine with display distance to achieve a comfortable level of depth and " +#~ "size." +#~ msgstr "" +#~ "Combine com a distância da tela para alcançar um nível confortável de " +#~ "profundidade e tamanho." + +#~ msgid "Display toggle distances" +#~ msgstr "Alternância das distâncias de tela" + +#~ msgid "" +#~ "Use the buttons to capture the current display distance for use with the " +#~ "keyboard shortcut." +#~ msgstr "" +#~ "Use os botões para obter a distância atual da tela para depois usar com o " +#~ "atalho de teclado." + +#~ msgid "Display distance shortcut" +#~ msgstr "Atalho de distância da tela" + +#~ msgid "Quickly toggle between two predefined distances." +#~ msgstr "Alterne rapidamente entre as duas distâncias predefinidas." + +#, fuzzy +#~ msgid "Start in widescreen mode" +#~ msgstr "Modo Ultrawide" diff --git a/ui/po/ru.po b/ui/po/ru.po index da7815e..2df8733 100644 --- a/ui/po/ru.po +++ b/ui/po/ru.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-22 13:27-0700\n" +"POT-Creation-Date: 2025-03-05 12:28-0800\n" "PO-Revision-Date: 2024-08-17 09:39-0700\n" "Last-Translator: \n" "Language-Team: Russian \n" @@ -18,17 +18,63 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -#: src/connecteddevice.py:17 +#: src/connecteddevice.py:23 msgid "" "Switches your glasses into side-by-side mode and doubles the width of the " "display." msgstr "" "Переключает очки в режим «сторона к стороне» и удваивает ширину дисплея." -#: src/connecteddevice.py:18 +#: src/connecteddevice.py:24 msgid "This feature is not currently supported for your device." msgstr "Эта функция в настоящее время не поддерживается для вашего устройства." +#: src/connecteddevice.py:124 +#, fuzzy +msgid "Set Focused Display Distance" +msgstr "Расстояние дисплея" + +#: src/connecteddevice.py:125 +msgid "Use a closer value so the display zooms in when you look at it." +msgstr "" + +#: src/connecteddevice.py:132 +#, fuzzy +msgid "Set All Displays Distance" +msgstr "Расстояние дисплея" + +#: src/connecteddevice.py:133 +msgid "Use a farther value so the displays are zoomed out when you look away." +msgstr "" + +#: src/connecteddevice.py:231 +msgid "" +"Unable to add virtual displays on this machine. xdg-desktop-portal is " +"required." +msgstr "" + +#: src/connecteddevice.py:265 +#, fuzzy +msgid "Focused display" +msgstr "Расстояние дисплея" + +#: src/connecteddevice.py:271 +#, fuzzy +msgid "All displays" +msgstr "Изогнутый дисплей" + +#: src/displaydistancedialogcontent.py:41 +msgid "closer" +msgstr "" + +#: src/displaydistancedialogcontent.py:45 +msgid "fullscreen" +msgstr "" + +#: src/displaydistancedialogcontent.py:48 +msgid "farther" +msgstr "" + #: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "Статус платного уровня" @@ -37,7 +83,7 @@ msgstr "Статус платного уровня" msgid "Feature Availability" msgstr "Статус функций" -#: src/licensefeaturerow.py:15 src/shortcutdialog.py:107 +#: src/licensefeaturerow.py:15 src/shortcutdialog.py:104 msgid "Disabled" msgstr "Отключено" @@ -166,126 +212,184 @@ msgid "Enables the Breezy Desktop XR effect." msgstr "Включает эффект Breezy Desktop XR." #: src/gtk/connected-device.ui:55 -msgid "Widescreen mode" -msgstr "Режим широкого экрана" +msgid "Zoom on focus" +msgstr "" -#: src/gtk/connected-device.ui:66 +#: src/gtk/connected-device.ui:56 +msgid "" +"Automatically move a display closer when you look at it.\n" +"Set your preferred focused and unfocused distances in the Adjustments " +"section.\n" +" " +msgstr "" + +#: src/gtk/connected-device.ui:69 msgid "Follow mode" msgstr "Режим следования" -#: src/gtk/connected-device.ui:67 -msgid "Keep the virtual display near the center of your view." +#: src/gtk/connected-device.ui:70 +#, fuzzy +msgid "Keep the focused display near the center of your view." msgstr "Поддерживает виртуальный дисплей вблизи центра вашего зрения." -#: src/gtk/connected-device.ui:77 -msgid "Curved display" -msgstr "Изогнутый дисплей" +#: src/gtk/connected-device.ui:80 src/gtk/no-device.ui:34 +msgid "Disable physical displays" +msgstr "" -#: src/gtk/connected-device.ui:78 -msgid "Switch between flat and curved displays." -msgstr "Переключается между плоскими и изогнутыми дисплеями." +#: src/gtk/connected-device.ui:81 src/gtk/no-device.ui:35 +msgid "" +"Automatically disable all physical displays when the XR effect is enabled." +msgstr "" #: src/gtk/connected-device.ui:91 +#, fuzzy +msgid "Virtual displays" +msgstr "Изогнутый дисплей" + +#: src/gtk/connected-device.ui:103 +#, fuzzy +msgid "Rearrange displays" +msgstr "Изогнутый дисплей" + +#: src/gtk/connected-device.ui:115 +msgid "1080p" +msgstr "" + +#: src/gtk/connected-device.ui:116 +msgid "1440p" +msgstr "" + +#: src/gtk/connected-device.ui:140 msgid "Adjustments" msgstr "Настройки" -#: src/gtk/connected-device.ui:94 -msgid "Display distance" +#: src/gtk/connected-device.ui:143 +#, fuzzy +msgid "Display distances" msgstr "Расстояние дисплея" -#: src/gtk/connected-device.ui:95 -msgid "" -"Closer appears larger, further appears smaller. Controls depth when in " -"widescreen mode." +#: src/gtk/connected-device.ui:144 +msgid "Set how close you want displays to appear." msgstr "" -"Чем ближе, тем больше. Чем дальше, тем меньше. Управляет глубиной при " -"использовании режима широкого экрана." -#: src/gtk/connected-device.ui:123 -msgid "Display size" -msgstr "Размер дисплея" - -#: src/gtk/connected-device.ui:124 -msgid "" -"Combine with display distance to achieve a comfortable level of depth and " -"size." -msgstr "" -"Комбинируйте с расстоянием дисплея, чтобы достичь комфортного уровня глубины " -"и размера." - -#: src/gtk/connected-device.ui:152 -msgid "Display toggle distances" -msgstr "Переключения расстояние дисплея" - -#: src/gtk/connected-device.ui:153 -msgid "" -"Use the buttons to capture the current display distance for use with the " -"keyboard shortcut." -msgstr "" -"Используйте кнопки, чтобы зафиксировать текущее расстояние дисплея для " -"использования с сочетанием клавиш." - -#: src/gtk/connected-device.ui:178 +#: src/gtk/connected-device.ui:199 msgid "Follow threshold" msgstr "Порог следования" -#: src/gtk/connected-device.ui:179 +#: src/gtk/connected-device.ui:200 msgid "How far away you can look before the display follows." msgstr "" "Как далеко вы можете отвернуть взгляд, прежде чем дисплей последует за вами." -#: src/gtk/connected-device.ui:209 src/gtk/connected-device.ui:215 +#: src/gtk/connected-device.ui:223 +#, fuzzy +msgid "Display angling" +msgstr "Размер дисплея" + +#: src/gtk/connected-device.ui:224 +msgid "" +"When there are multiple displays, choose how they should angle towards you." +msgstr "" + +#: src/gtk/connected-device.ui:238 +msgid "Automatic" +msgstr "" + +#: src/gtk/connected-device.ui:239 +msgid "Side-angled" +msgstr "" + +#: src/gtk/connected-device.ui:240 +msgid "Top-angled" +msgstr "" + +#: src/gtk/connected-device.ui:241 +msgid "Flat" +msgstr "" + +#: src/gtk/connected-device.ui:251 +#, fuzzy +msgid "Display spacing" +msgstr "Размер дисплея" + +#: src/gtk/connected-device.ui:252 +msgid "Put empty space between displays, when there are multiple." +msgstr "" + +#: src/gtk/connected-device.ui:282 +msgid "Viewport horizontal offset" +msgstr "" + +#: src/gtk/connected-device.ui:283 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport to the left or right." +msgstr "" + +#: src/gtk/connected-device.ui:312 +msgid "Viewport vertical offset" +msgstr "" + +#: src/gtk/connected-device.ui:313 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport up or down." +msgstr "" + +#: src/gtk/connected-device.ui:349 src/gtk/connected-device.ui:355 msgid "Keyboard Shortcuts" msgstr "Сочетания клавиш" -#: src/gtk/connected-device.ui:218 +#: src/gtk/connected-device.ui:358 msgid "XR Effect on/off shortcut" msgstr "" -#: src/gtk/connected-device.ui:219 +#: src/gtk/connected-device.ui:359 msgid "" "Quickly enable or disable the XR Effect. You may need to enable the effect " "manually once in order to enable the shortcut." msgstr "" -#: src/gtk/connected-device.ui:238 src/gtk/connected-device.ui:267 -#: src/gtk/connected-device.ui:296 src/gtk/connected-device.ui:325 +#: src/gtk/connected-device.ui:378 src/gtk/connected-device.ui:407 +#: src/gtk/connected-device.ui:436 src/gtk/connected-device.ui:465 msgid "Change" msgstr "Изменить" -#: src/gtk/connected-device.ui:247 +#: src/gtk/connected-device.ui:387 msgid "Re-center display shortcut" msgstr "Сочетание клавиш для перецентровки дисплея" -#: src/gtk/connected-device.ui:248 +#: src/gtk/connected-device.ui:388 msgid "Pin the virtual display to the current position." msgstr "Фиксировать виртуальный дисплей в текущем положении." -#: src/gtk/connected-device.ui:276 -msgid "Display distance shortcut" -msgstr "Сочетание клавиш для расстояния дисплея" +#: src/gtk/connected-device.ui:416 +#, fuzzy +msgid "Toggle zoom on focus shortcut" +msgstr "Сочетание клавиш для переключения режима следования" -#: src/gtk/connected-device.ui:277 -msgid "Quickly toggle between two predefined distances." -msgstr "Быстро переключаться между двумя предопределенными расстояниями." +#: src/gtk/connected-device.ui:417 +#, fuzzy +msgid "Quickly toggle zoom on focus mode." +msgstr "Быстро переключать режим следования." -#: src/gtk/connected-device.ui:305 +#: src/gtk/connected-device.ui:445 msgid "Toggle follow mode shortcut" msgstr "Сочетание клавиш для переключения режима следования" -#: src/gtk/connected-device.ui:306 +#: src/gtk/connected-device.ui:446 msgid "Quickly toggle follow mode." msgstr "Быстро переключать режим следования." -#: src/gtk/connected-device.ui:341 src/gtk/connected-device.ui:347 +#: src/gtk/connected-device.ui:481 src/gtk/connected-device.ui:487 msgid "Advanced Settings" msgstr "Дополнительные настройки" -#: src/gtk/connected-device.ui:350 +#: src/gtk/connected-device.ui:490 msgid "Find optimal display config" msgstr "Найти оптимальную конфигурацию дисплея" -#: src/gtk/connected-device.ui:351 +#: src/gtk/connected-device.ui:491 msgid "" "Automatically modify the glasses display configuration for maximum " "resolution and best scaling when plugged in." @@ -293,43 +397,31 @@ msgstr "" "Автоматически изменять конфигурацию дисплея очков для максимального " "разрешения и лучшей масштабирования при подключении." -#: src/gtk/connected-device.ui:361 +#: src/gtk/connected-device.ui:501 msgid "Use highest refresh rate" msgstr "Использовать высшую частоту обновления" -#: src/gtk/connected-device.ui:362 +#: src/gtk/connected-device.ui:502 msgid "Refresh rate may affect performance, disable this to set it manually." msgstr "" "Частота обновления может повлиять на производительность, отключите это, " "чтобы установить ее вручную." -#: src/gtk/connected-device.ui:372 +#: src/gtk/connected-device.ui:512 msgid "Always primary display" msgstr "Всегда основной дисплей" -#: src/gtk/connected-device.ui:373 +#: src/gtk/connected-device.ui:513 msgid "Automatically set the glasses as the primary display when plugged in." msgstr "" "Автоматически устанавливать очки в качестве основного дисплея при " "подключении." -#: src/gtk/connected-device.ui:383 -msgid "Fast SBS mode switching" -msgstr "Быстрое переключение режима SBS" - -#: src/gtk/connected-device.ui:384 -msgid "" -"Switches glasses to SBS mode immediately when plugged in, if widescreen mode " -"is on. May cause instability." -msgstr "" -"Переключает очки в режим SBS сразу при подключении, если режим широкого " -"экрана включен. Может вызвать нестабильность." - -#: src/gtk/connected-device.ui:394 +#: src/gtk/connected-device.ui:523 msgid "Movement look-ahead" msgstr "Прогнозирование движения" -#: src/gtk/connected-device.ui:395 +#: src/gtk/connected-device.ui:524 msgid "" "Counteracts input lag by predicting head-tracking position ahead of render " "time. Stick with default unless virtual display drags behind your head " @@ -340,18 +432,34 @@ msgstr "" "виртуальный дисплей не отстает от движений вашей головы, не опережает или не " "очень трясётся." -#: src/gtk/connected-device.ui:413 +#: src/gtk/connected-device.ui:542 msgid "Default" msgstr "По умолчанию" -#: src/gtk/connected-device.ui:425 +#: src/gtk/connected-device.ui:554 msgid "Text Scaling" msgstr "" -#: src/gtk/connected-device.ui:426 +#: src/gtk/connected-device.ui:555 msgid "Scaling text below 1.0 will simulate a higher resolution display" msgstr "" +#: src/gtk/connected-device.ui:585 +msgid "Enable multi-tap detection" +msgstr "" + +#: src/gtk/connected-device.ui:586 +msgid "Enables double-tap to recenter and triple-tap to recalibrate." +msgstr "" + +#: src/gtk/display-distance-dialog.ui:9 +msgid "Show full range" +msgstr "" + +#: src/gtk/display-distance-dialog.ui:18 +msgid "Done" +msgstr "" + #: src/gtk/failed-verification.ui:13 msgid "Breezy Desktop GNOME invalid setup" msgstr "Недействительная настройка Breezy Desktop GNOME" @@ -400,15 +508,6 @@ msgid "" "connected." msgstr "" -#: src/gtk/no-device.ui:34 -#, fuzzy -msgid "Start in widescreen mode" -msgstr "Режим широкого экрана" - -#: src/gtk/no-device.ui:35 -msgid "Widescreen mode is not supported for all glasses." -msgstr "" - #: src/gtk/no-driver.ui:13 msgid "No driver running" msgstr "Драйвер не запущен" @@ -517,3 +616,57 @@ msgstr "Сброс" #: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "О BreezyDesktop" + +#~ msgid "Fast SBS mode switching" +#~ msgstr "Быстрое переключение режима SBS" + +#~ msgid "" +#~ "Switches glasses to SBS mode immediately when plugged in, if widescreen " +#~ "mode is on. May cause instability." +#~ msgstr "" +#~ "Переключает очки в режим SBS сразу при подключении, если режим широкого " +#~ "экрана включен. Может вызвать нестабильность." + +#, fuzzy +#~ msgid "Set Unfocused Displays Distance" +#~ msgstr "Расстояние дисплея" + +#~ msgid "Widescreen mode" +#~ msgstr "Режим широкого экрана" + +#~ msgid "Switch between flat and curved displays." +#~ msgstr "Переключается между плоскими и изогнутыми дисплеями." + +#~ msgid "" +#~ "Closer appears larger, further appears smaller. Controls depth when in " +#~ "widescreen mode." +#~ msgstr "" +#~ "Чем ближе, тем больше. Чем дальше, тем меньше. Управляет глубиной при " +#~ "использовании режима широкого экрана." + +#~ msgid "" +#~ "Combine with display distance to achieve a comfortable level of depth and " +#~ "size." +#~ msgstr "" +#~ "Комбинируйте с расстоянием дисплея, чтобы достичь комфортного уровня " +#~ "глубины и размера." + +#~ msgid "Display toggle distances" +#~ msgstr "Переключения расстояние дисплея" + +#~ msgid "" +#~ "Use the buttons to capture the current display distance for use with the " +#~ "keyboard shortcut." +#~ msgstr "" +#~ "Используйте кнопки, чтобы зафиксировать текущее расстояние дисплея для " +#~ "использования с сочетанием клавиш." + +#~ msgid "Display distance shortcut" +#~ msgstr "Сочетание клавиш для расстояния дисплея" + +#~ msgid "Quickly toggle between two predefined distances." +#~ msgstr "Быстро переключаться между двумя предопределенными расстояниями." + +#, fuzzy +#~ msgid "Start in widescreen mode" +#~ msgstr "Режим широкого экрана" diff --git a/ui/po/sv.po b/ui/po/sv.po index dd5fc39..07fc116 100644 --- a/ui/po/sv.po +++ b/ui/po/sv.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-22 13:27-0700\n" +"POT-Creation-Date: 2025-03-05 12:28-0800\n" "PO-Revision-Date: 2024-08-16 10:31-0700\n" "Last-Translator: \n" "Language-Team: Swedish \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: src/connecteddevice.py:17 +#: src/connecteddevice.py:23 msgid "" "Switches your glasses into side-by-side mode and doubles the width of the " "display." @@ -25,10 +25,56 @@ msgstr "" "Växlar dina glasögon till side-by-side-läget och dubblerar bredden på " "visningen." -#: src/connecteddevice.py:18 +#: src/connecteddevice.py:24 msgid "This feature is not currently supported for your device." msgstr "Din enhet stöder inte den här funktionen för tillfället." +#: src/connecteddevice.py:124 +#, fuzzy +msgid "Set Focused Display Distance" +msgstr "Avstånd till skärmen" + +#: src/connecteddevice.py:125 +msgid "Use a closer value so the display zooms in when you look at it." +msgstr "" + +#: src/connecteddevice.py:132 +#, fuzzy +msgid "Set All Displays Distance" +msgstr "Avstånd till skärmen" + +#: src/connecteddevice.py:133 +msgid "Use a farther value so the displays are zoomed out when you look away." +msgstr "" + +#: src/connecteddevice.py:231 +msgid "" +"Unable to add virtual displays on this machine. xdg-desktop-portal is " +"required." +msgstr "" + +#: src/connecteddevice.py:265 +#, fuzzy +msgid "Focused display" +msgstr "Avstånd till skärmen" + +#: src/connecteddevice.py:271 +#, fuzzy +msgid "All displays" +msgstr "Böjd skärm" + +#: src/displaydistancedialogcontent.py:41 +msgid "closer" +msgstr "" + +#: src/displaydistancedialogcontent.py:45 +msgid "fullscreen" +msgstr "" + +#: src/displaydistancedialogcontent.py:48 +msgid "farther" +msgstr "" + #: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "Betalningsstatus" @@ -37,7 +83,7 @@ msgstr "Betalningsstatus" msgid "Feature Availability" msgstr "Funktions tillgänglighet" -#: src/licensefeaturerow.py:15 src/shortcutdialog.py:107 +#: src/licensefeaturerow.py:15 src/shortcutdialog.py:104 msgid "Disabled" msgstr "Inaktiverad" @@ -166,82 +212,138 @@ msgid "Enables the Breezy Desktop XR effect." msgstr "Aktiverar Breezy Desktop XR-effekten." #: src/gtk/connected-device.ui:55 -msgid "Widescreen mode" -msgstr "Bredbildsläge" +msgid "Zoom on focus" +msgstr "" -#: src/gtk/connected-device.ui:66 +#: src/gtk/connected-device.ui:56 +msgid "" +"Automatically move a display closer when you look at it.\n" +"Set your preferred focused and unfocused distances in the Adjustments " +"section.\n" +" " +msgstr "" + +#: src/gtk/connected-device.ui:69 msgid "Follow mode" msgstr "Följningsläge" -#: src/gtk/connected-device.ui:67 -msgid "Keep the virtual display near the center of your view." +#: src/gtk/connected-device.ui:70 +#, fuzzy +msgid "Keep the focused display near the center of your view." msgstr "Håll den virtuella skärmen nära mitten av din syn." -#: src/gtk/connected-device.ui:77 -msgid "Curved display" -msgstr "Böjd skärm" +#: src/gtk/connected-device.ui:80 src/gtk/no-device.ui:34 +msgid "Disable physical displays" +msgstr "" -#: src/gtk/connected-device.ui:78 -msgid "Switch between flat and curved displays." -msgstr "Växla mellan platt eller böjd skärm." +#: src/gtk/connected-device.ui:81 src/gtk/no-device.ui:35 +msgid "" +"Automatically disable all physical displays when the XR effect is enabled." +msgstr "" #: src/gtk/connected-device.ui:91 +#, fuzzy +msgid "Virtual displays" +msgstr "Böjd skärm" + +#: src/gtk/connected-device.ui:103 +#, fuzzy +msgid "Rearrange displays" +msgstr "Böjd skärm" + +#: src/gtk/connected-device.ui:115 +msgid "1080p" +msgstr "" + +#: src/gtk/connected-device.ui:116 +msgid "1440p" +msgstr "" + +#: src/gtk/connected-device.ui:140 msgid "Adjustments" msgstr "Justeringar" -#: src/gtk/connected-device.ui:94 -msgid "Display distance" +#: src/gtk/connected-device.ui:143 +#, fuzzy +msgid "Display distances" msgstr "Avstånd till skärmen" -#: src/gtk/connected-device.ui:95 -msgid "" -"Closer appears larger, further appears smaller. Controls depth when in " -"widescreen mode." +#: src/gtk/connected-device.ui:144 +msgid "Set how close you want displays to appear." msgstr "" -"Närmare verkar större, längre verkar mindre. Kontrollerar djupet när du är i " -"bredbildsläge." -#: src/gtk/connected-device.ui:123 -msgid "Display size" -msgstr "Skärmens storlek" - -#: src/gtk/connected-device.ui:124 -msgid "" -"Combine with display distance to achieve a comfortable level of depth and " -"size." -msgstr "" -"Kombinera med skärmens avstånd för att uppnå en bekväm nivå av djup och " -"storlek." - -#: src/gtk/connected-device.ui:152 -msgid "Display toggle distances" -msgstr "Växla skärm-avstånd" - -#: src/gtk/connected-device.ui:153 -msgid "" -"Use the buttons to capture the current display distance for use with the " -"keyboard shortcut." -msgstr "" -"Använd knapparna för att fanga nuvarande skärmavståndet för användning med " -"tangentbordsgenvägar." - -#: src/gtk/connected-device.ui:178 +#: src/gtk/connected-device.ui:199 msgid "Follow threshold" msgstr "Automatisk följtröskel" -#: src/gtk/connected-device.ui:179 +#: src/gtk/connected-device.ui:200 msgid "How far away you can look before the display follows." msgstr "Huvudrörelsetröskel för automatisk efterföljning." -#: src/gtk/connected-device.ui:209 src/gtk/connected-device.ui:215 +#: src/gtk/connected-device.ui:223 +#, fuzzy +msgid "Display angling" +msgstr "Skärmens storlek" + +#: src/gtk/connected-device.ui:224 +msgid "" +"When there are multiple displays, choose how they should angle towards you." +msgstr "" + +#: src/gtk/connected-device.ui:238 +msgid "Automatic" +msgstr "" + +#: src/gtk/connected-device.ui:239 +msgid "Side-angled" +msgstr "" + +#: src/gtk/connected-device.ui:240 +msgid "Top-angled" +msgstr "" + +#: src/gtk/connected-device.ui:241 +msgid "Flat" +msgstr "" + +#: src/gtk/connected-device.ui:251 +#, fuzzy +msgid "Display spacing" +msgstr "Skärmens storlek" + +#: src/gtk/connected-device.ui:252 +msgid "Put empty space between displays, when there are multiple." +msgstr "" + +#: src/gtk/connected-device.ui:282 +msgid "Viewport horizontal offset" +msgstr "" + +#: src/gtk/connected-device.ui:283 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport to the left or right." +msgstr "" + +#: src/gtk/connected-device.ui:312 +msgid "Viewport vertical offset" +msgstr "" + +#: src/gtk/connected-device.ui:313 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport up or down." +msgstr "" + +#: src/gtk/connected-device.ui:349 src/gtk/connected-device.ui:355 msgid "Keyboard Shortcuts" msgstr "Tangentbordsgenvägar" -#: src/gtk/connected-device.ui:218 +#: src/gtk/connected-device.ui:358 msgid "XR Effect on/off shortcut" msgstr "XR-Effekt genväg på/av" -#: src/gtk/connected-device.ui:219 +#: src/gtk/connected-device.ui:359 msgid "" "Quickly enable or disable the XR Effect. You may need to enable the effect " "manually once in order to enable the shortcut." @@ -249,44 +351,46 @@ msgstr "" "Skifta snabbt mellan att slå av eller på XR-Effekt. Du kan behöva slå på den " "manuellt en gång för att genvägen ska fungera." -#: src/gtk/connected-device.ui:238 src/gtk/connected-device.ui:267 -#: src/gtk/connected-device.ui:296 src/gtk/connected-device.ui:325 +#: src/gtk/connected-device.ui:378 src/gtk/connected-device.ui:407 +#: src/gtk/connected-device.ui:436 src/gtk/connected-device.ui:465 msgid "Change" msgstr "Ändra" -#: src/gtk/connected-device.ui:247 +#: src/gtk/connected-device.ui:387 msgid "Re-center display shortcut" msgstr "Om-centrering tangentbordsgenväg" -#: src/gtk/connected-device.ui:248 +#: src/gtk/connected-device.ui:388 msgid "Pin the virtual display to the current position." msgstr "Fäst den virtuella skärmen i den nuvarande positionen." -#: src/gtk/connected-device.ui:276 -msgid "Display distance shortcut" -msgstr "Skärm avstånd genväg" +#: src/gtk/connected-device.ui:416 +#, fuzzy +msgid "Toggle zoom on focus shortcut" +msgstr "Växla följ läge kort" -#: src/gtk/connected-device.ui:277 -msgid "Quickly toggle between two predefined distances." -msgstr "Växla snabbt mellan två fördefinierade avstånd." +#: src/gtk/connected-device.ui:417 +#, fuzzy +msgid "Quickly toggle zoom on focus mode." +msgstr "Växla snabbt följ läge." -#: src/gtk/connected-device.ui:305 +#: src/gtk/connected-device.ui:445 msgid "Toggle follow mode shortcut" msgstr "Växla följ läge kort" -#: src/gtk/connected-device.ui:306 +#: src/gtk/connected-device.ui:446 msgid "Quickly toggle follow mode." msgstr "Växla snabbt följ läge." -#: src/gtk/connected-device.ui:341 src/gtk/connected-device.ui:347 +#: src/gtk/connected-device.ui:481 src/gtk/connected-device.ui:487 msgid "Advanced Settings" msgstr "Avancerade inställningar" -#: src/gtk/connected-device.ui:350 +#: src/gtk/connected-device.ui:490 msgid "Find optimal display config" msgstr "Hitta optimal konfiguration till skärmen" -#: src/gtk/connected-device.ui:351 +#: src/gtk/connected-device.ui:491 msgid "" "Automatically modify the glasses display configuration for maximum " "resolution and best scaling when plugged in." @@ -294,41 +398,29 @@ msgstr "" "Ändrar automatisk glasögonens skärmkonfiguration för maximal upplösning och " "bästa skälning när den är ansluten." -#: src/gtk/connected-device.ui:361 +#: src/gtk/connected-device.ui:501 msgid "Use highest refresh rate" msgstr "Använd högsta uppdateringsfrekvens" -#: src/gtk/connected-device.ui:362 +#: src/gtk/connected-device.ui:502 msgid "Refresh rate may affect performance, disable this to set it manually." msgstr "" "Uppdateringsfrekvens kan påverka prestanda, inaktivera detta för att ställa " "in det manuellt." -#: src/gtk/connected-device.ui:372 +#: src/gtk/connected-device.ui:512 msgid "Always primary display" msgstr "Alltid primär skärm" -#: src/gtk/connected-device.ui:373 +#: src/gtk/connected-device.ui:513 msgid "Automatically set the glasses as the primary display when plugged in." msgstr "Ställer automatisk glasögon som primär skärm när den är ansluten." -#: src/gtk/connected-device.ui:383 -msgid "Fast SBS mode switching" -msgstr "Snabb SBS läge växling" - -#: src/gtk/connected-device.ui:384 -msgid "" -"Switches glasses to SBS mode immediately when plugged in, if widescreen mode " -"is on. May cause instability." -msgstr "" -"Växlar glasögon till SBS läge omedelbart när den är ansluten, om bredbildsläge är " -"på. Kan orsaka instabilitet." - -#: src/gtk/connected-device.ui:394 +#: src/gtk/connected-device.ui:523 msgid "Movement look-ahead" msgstr "Rörs förväntning" -#: src/gtk/connected-device.ui:395 +#: src/gtk/connected-device.ui:524 msgid "" "Counteracts input lag by predicting head-tracking position ahead of render " "time. Stick with default unless virtual display drags behind your head " @@ -337,18 +429,34 @@ msgstr "" "Motverkar ingångsfördröjning genom förutsägelse av huvudrörelser.Behåll " "standardinställningen om inte skärmen skakar mycket eller rörsig konstigt." -#: src/gtk/connected-device.ui:413 +#: src/gtk/connected-device.ui:542 msgid "Default" msgstr "Standard" -#: src/gtk/connected-device.ui:425 +#: src/gtk/connected-device.ui:554 msgid "Text Scaling" msgstr "Textskalning" -#: src/gtk/connected-device.ui:426 +#: src/gtk/connected-device.ui:555 msgid "Scaling text below 1.0 will simulate a higher resolution display" msgstr "Textskalning under 1.0 kommer att simulera en högre skärmupplösning" +#: src/gtk/connected-device.ui:585 +msgid "Enable multi-tap detection" +msgstr "" + +#: src/gtk/connected-device.ui:586 +msgid "Enables double-tap to recenter and triple-tap to recalibrate." +msgstr "" + +#: src/gtk/display-distance-dialog.ui:9 +msgid "Show full range" +msgstr "" + +#: src/gtk/display-distance-dialog.ui:18 +msgid "Done" +msgstr "" + #: src/gtk/failed-verification.ui:13 msgid "Breezy Desktop GNOME invalid setup" msgstr "Breezy Desktop GNOME ogiltig inställning" @@ -397,14 +505,6 @@ msgstr "" "Aktivera Breezy Desktop XR-effekten automatiskt när glasögon som stöds är " "anslutna." -#: src/gtk/no-device.ui:34 -msgid "Start in widescreen mode" -msgstr "Starta i bredbildsläge" - -#: src/gtk/no-device.ui:35 -msgid "Widescreen mode is not supported for all glasses." -msgstr "Bredbildsläge stöds inte av alla glasögon." - #: src/gtk/no-driver.ui:13 msgid "No driver running" msgstr "Inget drivrutin köres" @@ -513,3 +613,59 @@ msgstr "Tvinga Reset" #: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "Om BreezyDesktop" + +#~ msgid "Fast SBS mode switching" +#~ msgstr "Snabb SBS läge växling" + +#~ msgid "" +#~ "Switches glasses to SBS mode immediately when plugged in, if widescreen " +#~ "mode is on. May cause instability." +#~ msgstr "" +#~ "Växlar glasögon till SBS läge omedelbart när den är ansluten, om " +#~ "bredbildsläge är på. Kan orsaka instabilitet." + +#, fuzzy +#~ msgid "Set Unfocused Displays Distance" +#~ msgstr "Avstånd till skärmen" + +#~ msgid "Widescreen mode" +#~ msgstr "Bredbildsläge" + +#~ msgid "Switch between flat and curved displays." +#~ msgstr "Växla mellan platt eller böjd skärm." + +#~ msgid "" +#~ "Closer appears larger, further appears smaller. Controls depth when in " +#~ "widescreen mode." +#~ msgstr "" +#~ "Närmare verkar större, längre verkar mindre. Kontrollerar djupet när du " +#~ "är i bredbildsläge." + +#~ msgid "" +#~ "Combine with display distance to achieve a comfortable level of depth and " +#~ "size." +#~ msgstr "" +#~ "Kombinera med skärmens avstånd för att uppnå en bekväm nivå av djup och " +#~ "storlek." + +#~ msgid "Display toggle distances" +#~ msgstr "Växla skärm-avstånd" + +#~ msgid "" +#~ "Use the buttons to capture the current display distance for use with the " +#~ "keyboard shortcut." +#~ msgstr "" +#~ "Använd knapparna för att fanga nuvarande skärmavståndet för användning " +#~ "med tangentbordsgenvägar." + +#~ msgid "Display distance shortcut" +#~ msgstr "Skärm avstånd genväg" + +#~ msgid "Quickly toggle between two predefined distances." +#~ msgstr "Växla snabbt mellan två fördefinierade avstånd." + +#~ msgid "Start in widescreen mode" +#~ msgstr "Starta i bredbildsläge" + +#~ msgid "Widescreen mode is not supported for all glasses." +#~ msgstr "Bredbildsläge stöds inte av alla glasögon." diff --git a/ui/po/uk_UA.po b/ui/po/uk_UA.po index 4189b2f..40acb60 100644 --- a/ui/po/uk_UA.po +++ b/ui/po/uk_UA.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-22 13:27-0700\n" +"POT-Creation-Date: 2025-03-05 12:28-0800\n" "PO-Revision-Date: 2024-08-17 10:08-0700\n" "Last-Translator: \n" "Language-Team: Ukrainian \n" @@ -18,16 +18,62 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -#: src/connecteddevice.py:17 +#: src/connecteddevice.py:23 msgid "" "Switches your glasses into side-by-side mode and doubles the width of the " "display." msgstr "Переключає окуляри в режим «бок о бок» і подвоює ширину дисплея." -#: src/connecteddevice.py:18 +#: src/connecteddevice.py:24 msgid "This feature is not currently supported for your device." msgstr "Ця функція наразі не підтримується на вашому пристрої." +#: src/connecteddevice.py:124 +#, fuzzy +msgid "Set Focused Display Distance" +msgstr "Відстань дисплея" + +#: src/connecteddevice.py:125 +msgid "Use a closer value so the display zooms in when you look at it." +msgstr "" + +#: src/connecteddevice.py:132 +#, fuzzy +msgid "Set All Displays Distance" +msgstr "Відстань дисплея" + +#: src/connecteddevice.py:133 +msgid "Use a farther value so the displays are zoomed out when you look away." +msgstr "" + +#: src/connecteddevice.py:231 +msgid "" +"Unable to add virtual displays on this machine. xdg-desktop-portal is " +"required." +msgstr "" + +#: src/connecteddevice.py:265 +#, fuzzy +msgid "Focused display" +msgstr "Відстань дисплея" + +#: src/connecteddevice.py:271 +#, fuzzy +msgid "All displays" +msgstr "Викривлений дисплей" + +#: src/displaydistancedialogcontent.py:41 +msgid "closer" +msgstr "" + +#: src/displaydistancedialogcontent.py:45 +msgid "fullscreen" +msgstr "" + +#: src/displaydistancedialogcontent.py:48 +msgid "farther" +msgstr "" + #: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "Статус платного рівня" @@ -36,7 +82,7 @@ msgstr "Статус платного рівня" msgid "Feature Availability" msgstr "Статус функцій" -#: src/licensefeaturerow.py:15 src/shortcutdialog.py:107 +#: src/licensefeaturerow.py:15 src/shortcutdialog.py:104 msgid "Disabled" msgstr "Вимкнено" @@ -165,127 +211,185 @@ msgid "Enables the Breezy Desktop XR effect." msgstr "Включає ефект Breezy Desktop XR." #: src/gtk/connected-device.ui:55 -msgid "Widescreen mode" -msgstr "Режим широкого екрану" +msgid "Zoom on focus" +msgstr "" -#: src/gtk/connected-device.ui:66 +#: src/gtk/connected-device.ui:56 +msgid "" +"Automatically move a display closer when you look at it.\n" +"Set your preferred focused and unfocused distances in the Adjustments " +"section.\n" +" " +msgstr "" + +#: src/gtk/connected-device.ui:69 msgid "Follow mode" msgstr "Режим слідування" -#: src/gtk/connected-device.ui:67 -msgid "Keep the virtual display near the center of your view." +#: src/gtk/connected-device.ui:70 +#, fuzzy +msgid "Keep the focused display near the center of your view." msgstr "Зберігае віртуальний дисплей поблизу центру вашого зору." -#: src/gtk/connected-device.ui:77 -msgid "Curved display" -msgstr "Викривлений дисплей" +#: src/gtk/connected-device.ui:80 src/gtk/no-device.ui:34 +msgid "Disable physical displays" +msgstr "" -#: src/gtk/connected-device.ui:78 -msgid "Switch between flat and curved displays." -msgstr "Переключается між плоскими і викривленими дисплеями." +#: src/gtk/connected-device.ui:81 src/gtk/no-device.ui:35 +msgid "" +"Automatically disable all physical displays when the XR effect is enabled." +msgstr "" #: src/gtk/connected-device.ui:91 +#, fuzzy +msgid "Virtual displays" +msgstr "Викривлений дисплей" + +#: src/gtk/connected-device.ui:103 +#, fuzzy +msgid "Rearrange displays" +msgstr "Викривлений дисплей" + +#: src/gtk/connected-device.ui:115 +msgid "1080p" +msgstr "" + +#: src/gtk/connected-device.ui:116 +msgid "1440p" +msgstr "" + +#: src/gtk/connected-device.ui:140 msgid "Adjustments" msgstr "Налаштування" -#: src/gtk/connected-device.ui:94 -msgid "Display distance" +#: src/gtk/connected-device.ui:143 +#, fuzzy +msgid "Display distances" msgstr "Відстань дисплея" -#: src/gtk/connected-device.ui:95 -msgid "" -"Closer appears larger, further appears smaller. Controls depth when in " -"widescreen mode." +#: src/gtk/connected-device.ui:144 +msgid "Set how close you want displays to appear." msgstr "" -"Чим ближче, тим більший. Чим далі, тим менший. Керує глибиною при " -"використанні режиму широкого екрана." -#: src/gtk/connected-device.ui:123 -msgid "Display size" -msgstr "Розмір дисплея" - -#: src/gtk/connected-device.ui:124 -msgid "" -"Combine with display distance to achieve a comfortable level of depth and " -"size." -msgstr "" -"Поєднуйте з відстанню дисплея, щоб досягти комфортного рівня глибини і " -"розміру." - -#: src/gtk/connected-device.ui:152 -msgid "Display toggle distances" -msgstr "Перемикання відстані дисплея" - -#: src/gtk/connected-device.ui:153 -msgid "" -"Use the buttons to capture the current display distance for use with the " -"keyboard shortcut." -msgstr "" -"Використовуйте кнопки, щоб зафіксувати поточну відстань дисплея для " -"використання з сполученням клавіш." - -#: src/gtk/connected-device.ui:178 +#: src/gtk/connected-device.ui:199 msgid "Follow threshold" msgstr "Поріг слідування" -#: src/gtk/connected-device.ui:179 +#: src/gtk/connected-device.ui:200 msgid "How far away you can look before the display follows." msgstr "" "Як далеко ви можете відвернути ваш взор, перш ніж дисплей почне слідувати за " "вами." -#: src/gtk/connected-device.ui:209 src/gtk/connected-device.ui:215 +#: src/gtk/connected-device.ui:223 +#, fuzzy +msgid "Display angling" +msgstr "Розмір дисплея" + +#: src/gtk/connected-device.ui:224 +msgid "" +"When there are multiple displays, choose how they should angle towards you." +msgstr "" + +#: src/gtk/connected-device.ui:238 +msgid "Automatic" +msgstr "" + +#: src/gtk/connected-device.ui:239 +msgid "Side-angled" +msgstr "" + +#: src/gtk/connected-device.ui:240 +msgid "Top-angled" +msgstr "" + +#: src/gtk/connected-device.ui:241 +msgid "Flat" +msgstr "" + +#: src/gtk/connected-device.ui:251 +#, fuzzy +msgid "Display spacing" +msgstr "Розмір дисплея" + +#: src/gtk/connected-device.ui:252 +msgid "Put empty space between displays, when there are multiple." +msgstr "" + +#: src/gtk/connected-device.ui:282 +msgid "Viewport horizontal offset" +msgstr "" + +#: src/gtk/connected-device.ui:283 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport to the left or right." +msgstr "" + +#: src/gtk/connected-device.ui:312 +msgid "Viewport vertical offset" +msgstr "" + +#: src/gtk/connected-device.ui:313 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport up or down." +msgstr "" + +#: src/gtk/connected-device.ui:349 src/gtk/connected-device.ui:355 msgid "Keyboard Shortcuts" msgstr "Сполучення клавіш" -#: src/gtk/connected-device.ui:218 +#: src/gtk/connected-device.ui:358 msgid "XR Effect on/off shortcut" msgstr "" -#: src/gtk/connected-device.ui:219 +#: src/gtk/connected-device.ui:359 msgid "" "Quickly enable or disable the XR Effect. You may need to enable the effect " "manually once in order to enable the shortcut." msgstr "" -#: src/gtk/connected-device.ui:238 src/gtk/connected-device.ui:267 -#: src/gtk/connected-device.ui:296 src/gtk/connected-device.ui:325 +#: src/gtk/connected-device.ui:378 src/gtk/connected-device.ui:407 +#: src/gtk/connected-device.ui:436 src/gtk/connected-device.ui:465 msgid "Change" msgstr "Змінити" -#: src/gtk/connected-device.ui:247 +#: src/gtk/connected-device.ui:387 msgid "Re-center display shortcut" msgstr "Сполучення клавіш для центрування дисплея" -#: src/gtk/connected-device.ui:248 +#: src/gtk/connected-device.ui:388 msgid "Pin the virtual display to the current position." msgstr "Фіксувати віртуальний дисплей у поточній позиції." -#: src/gtk/connected-device.ui:276 -msgid "Display distance shortcut" -msgstr "Сполучення клавіш для відстані дисплея" +#: src/gtk/connected-device.ui:416 +#, fuzzy +msgid "Toggle zoom on focus shortcut" +msgstr "Сполучення клавіш для перемикання режиму слідування" -#: src/gtk/connected-device.ui:277 -msgid "Quickly toggle between two predefined distances." -msgstr "Швидко перемикатися між двома попередньо визначеними відстанями." +#: src/gtk/connected-device.ui:417 +#, fuzzy +msgid "Quickly toggle zoom on focus mode." +msgstr "Швидко перемикати режим слідування." -#: src/gtk/connected-device.ui:305 +#: src/gtk/connected-device.ui:445 msgid "Toggle follow mode shortcut" msgstr "Сполучення клавіш для перемикання режиму слідування" -#: src/gtk/connected-device.ui:306 +#: src/gtk/connected-device.ui:446 msgid "Quickly toggle follow mode." msgstr "Швидко перемикати режим слідування." -#: src/gtk/connected-device.ui:341 src/gtk/connected-device.ui:347 +#: src/gtk/connected-device.ui:481 src/gtk/connected-device.ui:487 msgid "Advanced Settings" msgstr "Додаткові налаштування" -#: src/gtk/connected-device.ui:350 +#: src/gtk/connected-device.ui:490 msgid "Find optimal display config" msgstr "Знайти оптимальну конфігурацію дисплея" -#: src/gtk/connected-device.ui:351 +#: src/gtk/connected-device.ui:491 msgid "" "Automatically modify the glasses display configuration for maximum " "resolution and best scaling when plugged in." @@ -293,41 +397,29 @@ msgstr "" "Автоматично змінює конфігурацію дисплея окулярів для максимальної роздільної " "здатності і найкращого масштабування при підключенні." -#: src/gtk/connected-device.ui:361 +#: src/gtk/connected-device.ui:501 msgid "Use highest refresh rate" msgstr "Використовувати найвищу частоту оновлення" -#: src/gtk/connected-device.ui:362 +#: src/gtk/connected-device.ui:502 msgid "Refresh rate may affect performance, disable this to set it manually." msgstr "" "Частота оновлення може вплинути на продуктивність, вимкніть це, щоб " "встановити її вручну." -#: src/gtk/connected-device.ui:372 +#: src/gtk/connected-device.ui:512 msgid "Always primary display" msgstr "Завжди основний дисплей" -#: src/gtk/connected-device.ui:373 +#: src/gtk/connected-device.ui:513 msgid "Automatically set the glasses as the primary display when plugged in." msgstr "Автоматично встановлює окуляри як основний дисплей при підключенні." -#: src/gtk/connected-device.ui:383 -msgid "Fast SBS mode switching" -msgstr "Швидке перемикання режиму SBS" - -#: src/gtk/connected-device.ui:384 -msgid "" -"Switches glasses to SBS mode immediately when plugged in, if widescreen mode " -"is on. May cause instability." -msgstr "" -"Переключає окуляри в режим SBS відразу після підключення, якщо режим " -"широкого екрана увімкнено. Може викликати нестабільність." - -#: src/gtk/connected-device.ui:394 +#: src/gtk/connected-device.ui:523 msgid "Movement look-ahead" msgstr "Прогнозування руху" -#: src/gtk/connected-device.ui:395 +#: src/gtk/connected-device.ui:524 msgid "" "Counteracts input lag by predicting head-tracking position ahead of render " "time. Stick with default unless virtual display drags behind your head " @@ -338,18 +430,34 @@ msgstr "" "віртуальний дисплей відстає від рухів вашої голови, випереджає або дуже " "тремтить." -#: src/gtk/connected-device.ui:413 +#: src/gtk/connected-device.ui:542 msgid "Default" msgstr "За замовчуванням" -#: src/gtk/connected-device.ui:425 +#: src/gtk/connected-device.ui:554 msgid "Text Scaling" msgstr "" -#: src/gtk/connected-device.ui:426 +#: src/gtk/connected-device.ui:555 msgid "Scaling text below 1.0 will simulate a higher resolution display" msgstr "" +#: src/gtk/connected-device.ui:585 +msgid "Enable multi-tap detection" +msgstr "" + +#: src/gtk/connected-device.ui:586 +msgid "Enables double-tap to recenter and triple-tap to recalibrate." +msgstr "" + +#: src/gtk/display-distance-dialog.ui:9 +msgid "Show full range" +msgstr "" + +#: src/gtk/display-distance-dialog.ui:18 +msgid "Done" +msgstr "" + #: src/gtk/failed-verification.ui:13 msgid "Breezy Desktop GNOME invalid setup" msgstr "Невірна настройка Breezy Desktop GNOME" @@ -397,15 +505,6 @@ msgid "" "connected." msgstr "" -#: src/gtk/no-device.ui:34 -#, fuzzy -msgid "Start in widescreen mode" -msgstr "Режим широкого екрану" - -#: src/gtk/no-device.ui:35 -msgid "Widescreen mode is not supported for all glasses." -msgstr "" - #: src/gtk/no-driver.ui:13 msgid "No driver running" msgstr "Жоден драйвер не запущений" @@ -514,3 +613,57 @@ msgstr "Скинути" #: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "Про BreezyDesktop" + +#~ msgid "Fast SBS mode switching" +#~ msgstr "Швидке перемикання режиму SBS" + +#~ msgid "" +#~ "Switches glasses to SBS mode immediately when plugged in, if widescreen " +#~ "mode is on. May cause instability." +#~ msgstr "" +#~ "Переключає окуляри в режим SBS відразу після підключення, якщо режим " +#~ "широкого екрана увімкнено. Може викликати нестабільність." + +#, fuzzy +#~ msgid "Set Unfocused Displays Distance" +#~ msgstr "Відстань дисплея" + +#~ msgid "Widescreen mode" +#~ msgstr "Режим широкого екрану" + +#~ msgid "Switch between flat and curved displays." +#~ msgstr "Переключается між плоскими і викривленими дисплеями." + +#~ msgid "" +#~ "Closer appears larger, further appears smaller. Controls depth when in " +#~ "widescreen mode." +#~ msgstr "" +#~ "Чим ближче, тим більший. Чим далі, тим менший. Керує глибиною при " +#~ "використанні режиму широкого екрана." + +#~ msgid "" +#~ "Combine with display distance to achieve a comfortable level of depth and " +#~ "size." +#~ msgstr "" +#~ "Поєднуйте з відстанню дисплея, щоб досягти комфортного рівня глибини і " +#~ "розміру." + +#~ msgid "Display toggle distances" +#~ msgstr "Перемикання відстані дисплея" + +#~ msgid "" +#~ "Use the buttons to capture the current display distance for use with the " +#~ "keyboard shortcut." +#~ msgstr "" +#~ "Використовуйте кнопки, щоб зафіксувати поточну відстань дисплея для " +#~ "використання з сполученням клавіш." + +#~ msgid "Display distance shortcut" +#~ msgstr "Сполучення клавіш для відстані дисплея" + +#~ msgid "Quickly toggle between two predefined distances." +#~ msgstr "Швидко перемикатися між двома попередньо визначеними відстанями." + +#, fuzzy +#~ msgid "Start in widescreen mode" +#~ msgstr "Режим широкого екрану" diff --git a/ui/po/zh_CN.po b/ui/po/zh_CN.po index 0f14f64..3faee5c 100644 --- a/ui/po/zh_CN.po +++ b/ui/po/zh_CN.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-22 13:27-0700\n" +"POT-Creation-Date: 2025-03-05 12:28-0800\n" "PO-Revision-Date: 2024-08-02 20:55-0700\n" "Last-Translator: \n" "Language-Team: Chinese (simplified) \n" @@ -16,16 +16,62 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: src/connecteddevice.py:17 +#: src/connecteddevice.py:23 msgid "" "Switches your glasses into side-by-side mode and doubles the width of the " "display." msgstr "切换到并排模式,并将显示宽度翻倍。" -#: src/connecteddevice.py:18 +#: src/connecteddevice.py:24 msgid "This feature is not currently supported for your device." msgstr "您的设备目前不支援此功能。" +#: src/connecteddevice.py:124 +#, fuzzy +msgid "Set Focused Display Distance" +msgstr "显示距离" + +#: src/connecteddevice.py:125 +msgid "Use a closer value so the display zooms in when you look at it." +msgstr "" + +#: src/connecteddevice.py:132 +#, fuzzy +msgid "Set All Displays Distance" +msgstr "显示距离" + +#: src/connecteddevice.py:133 +msgid "Use a farther value so the displays are zoomed out when you look away." +msgstr "" + +#: src/connecteddevice.py:231 +msgid "" +"Unable to add virtual displays on this machine. xdg-desktop-portal is " +"required." +msgstr "" + +#: src/connecteddevice.py:265 +#, fuzzy +msgid "Focused display" +msgstr "显示距离" + +#: src/connecteddevice.py:271 +#, fuzzy +msgid "All displays" +msgstr "曲面显示" + +#: src/displaydistancedialogcontent.py:41 +msgid "closer" +msgstr "" + +#: src/displaydistancedialogcontent.py:45 +msgid "fullscreen" +msgstr "" + +#: src/displaydistancedialogcontent.py:48 +msgid "farther" +msgstr "" + #: src/licensedialogcontent.py:63 msgid "Paid Tier Status" msgstr "订阅状态" @@ -34,7 +80,7 @@ msgstr "订阅状态" msgid "Feature Availability" msgstr "功能状态" -#: src/licensefeaturerow.py:15 src/shortcutdialog.py:107 +#: src/licensefeaturerow.py:15 src/shortcutdialog.py:104 msgid "Disabled" msgstr "已禁用" @@ -163,176 +209,245 @@ msgid "Enables the Breezy Desktop XR effect." msgstr "启用 Breezy Desktop XR 效果。" #: src/gtk/connected-device.ui:55 -msgid "Widescreen mode" -msgstr "宽屏模式" +msgid "Zoom on focus" +msgstr "" -#: src/gtk/connected-device.ui:66 +#: src/gtk/connected-device.ui:56 +msgid "" +"Automatically move a display closer when you look at it.\n" +"Set your preferred focused and unfocused distances in the Adjustments " +"section.\n" +" " +msgstr "" + +#: src/gtk/connected-device.ui:69 msgid "Follow mode" msgstr "跟随模式" -#: src/gtk/connected-device.ui:67 -msgid "Keep the virtual display near the center of your view." +#: src/gtk/connected-device.ui:70 +#, fuzzy +msgid "Keep the focused display near the center of your view." msgstr "虚拟显示保持在视野中心附近。" -#: src/gtk/connected-device.ui:77 -msgid "Curved display" -msgstr "曲面显示" +#: src/gtk/connected-device.ui:80 src/gtk/no-device.ui:34 +msgid "Disable physical displays" +msgstr "" -#: src/gtk/connected-device.ui:78 -msgid "Switch between flat and curved displays." -msgstr "平板和曲面显示模式之间切换。" +#: src/gtk/connected-device.ui:81 src/gtk/no-device.ui:35 +msgid "" +"Automatically disable all physical displays when the XR effect is enabled." +msgstr "" #: src/gtk/connected-device.ui:91 +#, fuzzy +msgid "Virtual displays" +msgstr "曲面显示" + +#: src/gtk/connected-device.ui:103 +#, fuzzy +msgid "Rearrange displays" +msgstr "曲面显示" + +#: src/gtk/connected-device.ui:115 +msgid "1080p" +msgstr "" + +#: src/gtk/connected-device.ui:116 +msgid "1440p" +msgstr "" + +#: src/gtk/connected-device.ui:140 msgid "Adjustments" msgstr "调整" -#: src/gtk/connected-device.ui:94 -msgid "Display distance" +#: src/gtk/connected-device.ui:143 +#, fuzzy +msgid "Display distances" msgstr "显示距离" -#: src/gtk/connected-device.ui:95 -msgid "" -"Closer appears larger, further appears smaller. Controls depth when in " -"widescreen mode." -msgstr "距离近看起来大,距离远看起来小。调整使用宽屏模式时的深度。" +#: src/gtk/connected-device.ui:144 +msgid "Set how close you want displays to appear." +msgstr "" -#: src/gtk/connected-device.ui:123 -msgid "Display size" -msgstr "显示大小" - -#: src/gtk/connected-device.ui:124 -msgid "" -"Combine with display distance to achieve a comfortable level of depth and " -"size." -msgstr "可以跟显示距离一起配合来实现舒适的深度和大小。" - -#: src/gtk/connected-device.ui:152 -msgid "Display toggle distances" -msgstr "显示切换距离" - -#: src/gtk/connected-device.ui:153 -msgid "" -"Use the buttons to capture the current display distance for use with the " -"keyboard shortcut." -msgstr "使用按钮记住当时的显示距离,以设成键盘快捷键。" - -#: src/gtk/connected-device.ui:178 +#: src/gtk/connected-device.ui:199 msgid "Follow threshold" msgstr "跟随触发值" -#: src/gtk/connected-device.ui:179 +#: src/gtk/connected-device.ui:200 msgid "How far away you can look before the display follows." msgstr "在显示内容跟随前您可以看多远。" -#: src/gtk/connected-device.ui:209 src/gtk/connected-device.ui:215 +#: src/gtk/connected-device.ui:223 +#, fuzzy +msgid "Display angling" +msgstr "显示大小" + +#: src/gtk/connected-device.ui:224 +msgid "" +"When there are multiple displays, choose how they should angle towards you." +msgstr "" + +#: src/gtk/connected-device.ui:238 +msgid "Automatic" +msgstr "" + +#: src/gtk/connected-device.ui:239 +msgid "Side-angled" +msgstr "" + +#: src/gtk/connected-device.ui:240 +msgid "Top-angled" +msgstr "" + +#: src/gtk/connected-device.ui:241 +msgid "Flat" +msgstr "" + +#: src/gtk/connected-device.ui:251 +#, fuzzy +msgid "Display spacing" +msgstr "显示大小" + +#: src/gtk/connected-device.ui:252 +msgid "Put empty space between displays, when there are multiple." +msgstr "" + +#: src/gtk/connected-device.ui:282 +msgid "Viewport horizontal offset" +msgstr "" + +#: src/gtk/connected-device.ui:283 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport to the left or right." +msgstr "" + +#: src/gtk/connected-device.ui:312 +msgid "Viewport vertical offset" +msgstr "" + +#: src/gtk/connected-device.ui:313 +msgid "" +"By default, the viewport will center on the primary display. Use this slider " +"to move the viewport up or down." +msgstr "" + +#: src/gtk/connected-device.ui:349 src/gtk/connected-device.ui:355 msgid "Keyboard Shortcuts" msgstr "键盘快捷键" -#: src/gtk/connected-device.ui:218 +#: src/gtk/connected-device.ui:358 msgid "XR Effect on/off shortcut" msgstr "XR效果 开/关 快捷键" -#: src/gtk/connected-device.ui:219 +#: src/gtk/connected-device.ui:359 msgid "" "Quickly enable or disable the XR Effect. You may need to enable the effect " "manually once in order to enable the shortcut." msgstr "快速启用或禁用 XR 效果。您可能需要先手动启用一次该效果,才能设此快捷键" -#: src/gtk/connected-device.ui:238 src/gtk/connected-device.ui:267 -#: src/gtk/connected-device.ui:296 src/gtk/connected-device.ui:325 +#: src/gtk/connected-device.ui:378 src/gtk/connected-device.ui:407 +#: src/gtk/connected-device.ui:436 src/gtk/connected-device.ui:465 msgid "Change" msgstr "更改" -#: src/gtk/connected-device.ui:247 +#: src/gtk/connected-device.ui:387 msgid "Re-center display shortcut" msgstr "重新居中显示快捷键" -#: src/gtk/connected-device.ui:248 +#: src/gtk/connected-device.ui:388 msgid "Pin the virtual display to the current position." msgstr "将虚拟显示固定在当前位置。" -#: src/gtk/connected-device.ui:276 -msgid "Display distance shortcut" -msgstr "显示距离快捷键" +#: src/gtk/connected-device.ui:416 +#, fuzzy +msgid "Toggle zoom on focus shortcut" +msgstr "切换跟随模式快捷键" -#: src/gtk/connected-device.ui:277 -msgid "Quickly toggle between two predefined distances." -msgstr "快速地在两个预定的距离之间切换。" +#: src/gtk/connected-device.ui:417 +#, fuzzy +msgid "Quickly toggle zoom on focus mode." +msgstr "快速切换跟随模式。" -#: src/gtk/connected-device.ui:305 +#: src/gtk/connected-device.ui:445 msgid "Toggle follow mode shortcut" msgstr "切换跟随模式快捷键" -#: src/gtk/connected-device.ui:306 +#: src/gtk/connected-device.ui:446 msgid "Quickly toggle follow mode." msgstr "快速切换跟随模式。" -#: src/gtk/connected-device.ui:341 src/gtk/connected-device.ui:347 +#: src/gtk/connected-device.ui:481 src/gtk/connected-device.ui:487 msgid "Advanced Settings" msgstr "高级设定" -#: src/gtk/connected-device.ui:350 +#: src/gtk/connected-device.ui:490 msgid "Find optimal display config" msgstr "寻找最佳显示设定" -#: src/gtk/connected-device.ui:351 +#: src/gtk/connected-device.ui:491 msgid "" "Automatically modify the glasses display configuration for maximum " "resolution and best scaling when plugged in." msgstr "连接时,可以自动修改眼镜显示设定以表现出最大解析度和最佳的对比。" -#: src/gtk/connected-device.ui:361 +#: src/gtk/connected-device.ui:501 msgid "Use highest refresh rate" msgstr "使用最高刷新率" -#: src/gtk/connected-device.ui:362 +#: src/gtk/connected-device.ui:502 msgid "Refresh rate may affect performance, disable this to set it manually." msgstr "刷新率可能会影响性能,禁用此功能即可手动设置。" -#: src/gtk/connected-device.ui:372 +#: src/gtk/connected-device.ui:512 msgid "Always primary display" msgstr "每次设置为主要显示" -#: src/gtk/connected-device.ui:373 +#: src/gtk/connected-device.ui:513 msgid "Automatically set the glasses as the primary display when plugged in." msgstr "连接时,自动将眼镜设置为主要显示。" -#: src/gtk/connected-device.ui:383 -msgid "Fast SBS mode switching" -msgstr "快速并排模式切换" - -#: src/gtk/connected-device.ui:384 -msgid "" -"Switches glasses to SBS mode immediately when plugged in, if widescreen mode " -"is on. May cause instability." -msgstr "" -"连接时,如果宽屏模式开启,眼镜会立即切换到 并排模式。这可能会导致不稳定。" - -#: src/gtk/connected-device.ui:394 +#: src/gtk/connected-device.ui:523 msgid "Movement look-ahead" msgstr "移动预测" -#: src/gtk/connected-device.ui:395 +#: src/gtk/connected-device.ui:524 msgid "" "Counteracts input lag by predicting head-tracking position ahead of render " "time. Stick with default unless virtual display drags behind your head " "movements, jumps ahead, or is very shaky." msgstr "" -"透过预测头部追踪位置,提前于渲染时间进行预测来抵消输入延迟。" -"除非虚拟显示滞后于头部,提前跳动或非常抖动,请尽量使用默认设置。" +"透过预测头部追踪位置,提前于渲染时间进行预测来抵消输入延迟。除非虚拟显示滞后" +"于头部,提前跳动或非常抖动,请尽量使用默认设置。" -#: src/gtk/connected-device.ui:413 +#: src/gtk/connected-device.ui:542 msgid "Default" msgstr "默认" -#: src/gtk/connected-device.ui:425 +#: src/gtk/connected-device.ui:554 msgid "Text Scaling" msgstr "字体大小比例" -#: src/gtk/connected-device.ui:426 +#: src/gtk/connected-device.ui:555 msgid "Scaling text below 1.0 will simulate a higher resolution display" msgstr "字体缩放小于1.0,将模拟解析度更高的显示效果" +#: src/gtk/connected-device.ui:585 +msgid "Enable multi-tap detection" +msgstr "" + +#: src/gtk/connected-device.ui:586 +msgid "Enables double-tap to recenter and triple-tap to recalibrate." +msgstr "" + +#: src/gtk/display-distance-dialog.ui:9 +msgid "Show full range" +msgstr "" + +#: src/gtk/display-distance-dialog.ui:18 +msgid "Done" +msgstr "" + #: src/gtk/failed-verification.ui:13 msgid "Breezy Desktop GNOME invalid setup" msgstr "Breezy Desktop GNOME 无效设置" @@ -379,14 +494,6 @@ msgid "" "connected." msgstr "当支援的设备已连接,将BREEZY DESKTOP XR效果自动启动" -#: src/gtk/no-device.ui:34 -msgid "Start in widescreen mode" -msgstr "使用宽屏模式以启动软件" - -#: src/gtk/no-device.ui:35 -msgid "Widescreen mode is not supported for all glasses." -msgstr "宽屏模式不支援每一个型号的XR眼睛" - #: src/gtk/no-driver.ui:13 msgid "No driver running" msgstr "未运行驱动程序" @@ -490,3 +597,52 @@ msgstr "强制重置" #: src/gtk/window.ui:99 msgid "About BreezyDesktop" msgstr "关于 Breezy Desktop" + +#~ msgid "Fast SBS mode switching" +#~ msgstr "快速并排模式切换" + +#~ msgid "" +#~ "Switches glasses to SBS mode immediately when plugged in, if widescreen " +#~ "mode is on. May cause instability." +#~ msgstr "" +#~ "连接时,如果宽屏模式开启,眼镜会立即切换到 并排模式。这可能会导致不稳定。" + +#, fuzzy +#~ msgid "Set Unfocused Displays Distance" +#~ msgstr "显示距离" + +#~ msgid "Widescreen mode" +#~ msgstr "宽屏模式" + +#~ msgid "Switch between flat and curved displays." +#~ msgstr "平板和曲面显示模式之间切换。" + +#~ msgid "" +#~ "Closer appears larger, further appears smaller. Controls depth when in " +#~ "widescreen mode." +#~ msgstr "距离近看起来大,距离远看起来小。调整使用宽屏模式时的深度。" + +#~ msgid "" +#~ "Combine with display distance to achieve a comfortable level of depth and " +#~ "size." +#~ msgstr "可以跟显示距离一起配合来实现舒适的深度和大小。" + +#~ msgid "Display toggle distances" +#~ msgstr "显示切换距离" + +#~ msgid "" +#~ "Use the buttons to capture the current display distance for use with the " +#~ "keyboard shortcut." +#~ msgstr "使用按钮记住当时的显示距离,以设成键盘快捷键。" + +#~ msgid "Display distance shortcut" +#~ msgstr "显示距离快捷键" + +#~ msgid "Quickly toggle between two predefined distances." +#~ msgstr "快速地在两个预定的距离之间切换。" + +#~ msgid "Start in widescreen mode" +#~ msgstr "使用宽屏模式以启动软件" + +#~ msgid "Widescreen mode is not supported for all glasses." +#~ msgstr "宽屏模式不支援每一个型号的XR眼睛" diff --git a/ui/src/breezydesktop.gresource.xml b/ui/src/breezydesktop.gresource.xml index 843487c..37c33ba 100644 --- a/ui/src/breezydesktop.gresource.xml +++ b/ui/src/breezydesktop.gresource.xml @@ -2,6 +2,8 @@ gtk/connected-device.ui + gtk/display-distance-dialog.ui + gtk/display-distance-dialog-content.ui gtk/failed-verification.ui gtk/license-dialog.ui gtk/license-dialog-content.ui @@ -10,6 +12,7 @@ gtk/no-extension.ui gtk/no-license.ui gtk/shortcut-dialog.ui + gtk/virtual-display-row.ui gtk/window.ui diff --git a/ui/src/configmanager.py b/ui/src/configmanager.py index 37c6b18..983d4bc 100644 --- a/ui/src/configmanager.py +++ b/ui/src/configmanager.py @@ -6,6 +6,7 @@ from .xrdriveripc import XRDriverIPC class ConfigManager(GObject.GObject): __gproperties__ = { 'breezy-desktop-enabled': (bool, 'Breezy Desktop Enabled', 'Whether Breezy Desktop is enabled', False, GObject.ParamFlags.READWRITE), + 'multi-tap-enabled': (bool, 'Multi-Tap Enabled', 'Whether Multi-Tap is enabled', False, GObject.ParamFlags.READWRITE), } _instance = None @@ -27,6 +28,7 @@ class ConfigManager(GObject.GObject): GObject.GObject.__init__(self) self.ipc = XRDriverIPC.get_instance() self.breezy_desktop_enabled = None + self.multi_tap_enabled = None self._running = True self._refresh_config() @@ -38,6 +40,9 @@ class ConfigManager(GObject.GObject): if self._is_breezy_desktop_enabled() != self.breezy_desktop_enabled: self.set_property('breezy-desktop-enabled', self._is_breezy_desktop_enabled()) + if self.config['multi_tap_enabled'] != self.multi_tap_enabled: + self.set_property('multi-tap-enabled', self.config['multi_tap_enabled']) + if self._running: threading.Timer(1.0, self._refresh_config).start() def _is_breezy_desktop_enabled(self): @@ -48,17 +53,26 @@ class ConfigManager(GObject.GObject): self.config['disabled'] = False self.config['output_mode'] = 'external_only' self.config['external_mode'] = ['breezy_desktop'] - self.ipc.write_config(self.config) else: self.config['external_mode'] = [] - self.ipc.write_config(self.config) + self.ipc.write_config(self.config) self.breezy_desktop_enabled = value + def _set_multi_tap_enabled(self, value): + if self.multi_tap_enabled != value: + self.config['multi_tap_enabled'] = value + self.ipc.write_config(self.config) + self.multi_tap_enabled = value + def do_set_property(self, prop, value): if prop.name == 'breezy-desktop-enabled': self._set_breezy_desktop_enabled(value) + elif prop.name == 'multi-tap-enabled': + self._set_multi_tap_enabled(value) def do_get_property(self, prop): if prop.name == 'breezy-desktop-enabled': - return self.breezy_desktop_enabled \ No newline at end of file + return self.breezy_desktop_enabled + elif prop.name == 'multi-tap-enabled': + return self.multi_tap_enabled \ No newline at end of file diff --git a/ui/src/connecteddevice.py b/ui/src/connecteddevice.py index efe638e..acb867d 100644 --- a/ui/src/connecteddevice.py +++ b/ui/src/connecteddevice.py @@ -1,14 +1,20 @@ -from gi.repository import Gio, Gtk, GObject +from gi.repository import Gio, GLib, Gtk, GObject from .configmanager import ConfigManager from .extensionsmanager import ExtensionsManager +from .displaydistancedialog import DisplayDistanceDialog from .license import BREEZY_GNOME_FEATURES from .settingsmanager import SettingsManager from .shortcutdialog import bind_shortcut_settings from .statemanager import StateManager +from .virtualdisplaymanager import VirtualDisplayManager +from .virtualdisplay import is_screencast_available +from .virtualdisplayrow import VirtualDisplayRow from .xrdriveripc import XRDriverIPC import gettext +import logging _ = gettext.gettext +logger = logging.getLogger('breezy_ui') @Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/connected-device.ui') class ConnectedDevice(Gtk.Box): @@ -19,18 +25,20 @@ class ConnectedDevice(Gtk.Box): device_label = Gtk.Template.Child() effect_enable_switch = Gtk.Template.Child() - display_distance_scale = Gtk.Template.Child() - display_distance_adjustment = Gtk.Template.Child() - display_size_scale = Gtk.Template.Child() - display_size_adjustment = Gtk.Template.Child() + disable_physical_displays_switch = Gtk.Template.Child() + display_zoom_on_focus_switch = Gtk.Template.Child() follow_threshold_scale = Gtk.Template.Child() follow_threshold_adjustment = Gtk.Template.Child() follow_mode_switch = Gtk.Template.Child() - widescreen_mode_switch = Gtk.Template.Child() - widescreen_mode_row = Gtk.Template.Child() - curved_display_switch = Gtk.Template.Child() - set_toggle_display_distance_start_button = Gtk.Template.Child() - set_toggle_display_distance_end_button = Gtk.Template.Child() + top_features_group = Gtk.Template.Child() + virtual_displays_row = Gtk.Template.Child() + add_virtual_display_menu = Gtk.Template.Child() + add_virtual_display_button = Gtk.Template.Child() + launch_display_settings_button = Gtk.Template.Child() + all_displays_distance_label = Gtk.Template.Child() + change_all_displays_distance_button = Gtk.Template.Child() + focused_display_distance_label = Gtk.Template.Child() + change_focused_display_distance_button = Gtk.Template.Child() reassign_toggle_xr_effect_shortcut_button = Gtk.Template.Child() toggle_xr_effect_shortcut_label = Gtk.Template.Child() reassign_recenter_display_shortcut_button = Gtk.Template.Child() @@ -42,44 +50,65 @@ class ConnectedDevice(Gtk.Box): headset_as_primary_switch = Gtk.Template.Child() use_optimal_monitor_config_switch = Gtk.Template.Child() use_highest_refresh_rate_switch = Gtk.Template.Child() - fast_sbs_mode_switch = Gtk.Template.Child() movement_look_ahead_scale = Gtk.Template.Child() movement_look_ahead_adjustment = Gtk.Template.Child() text_scaling_scale = Gtk.Template.Child() text_scaling_adjustment = Gtk.Template.Child() - + enable_multi_tap_switch = Gtk.Template.Child() + monitor_wrapping_scheme_menu = Gtk.Template.Child() + monitor_spacing_scale = Gtk.Template.Child() + monitor_spacing_adjustment = Gtk.Template.Child() + viewport_offset_x_scale = Gtk.Template.Child() + viewport_offset_x_adjustment = Gtk.Template.Child() + viewport_offset_y_scale = Gtk.Template.Child() + viewport_offset_y_adjustment = Gtk.Template.Child() def __init__(self): super(Gtk.Box, self).__init__() self.init_template() self.active = True self.all_enabled_state_inputs = [ - self.display_distance_scale, - self.display_size_scale, + self.display_zoom_on_focus_switch, + # self.display_size_scale, self.follow_mode_switch, self.follow_threshold_scale, - self.curved_display_switch, - self.set_toggle_display_distance_start_button, - self.set_toggle_display_distance_end_button, - self.movement_look_ahead_scale + # self.curved_display_switch, + self.add_virtual_display_menu, + self.add_virtual_display_button, + self.change_all_displays_distance_button, + self.change_focused_display_distance_button, + self.movement_look_ahead_scale, + self.monitor_wrapping_scheme_menu, + self.monitor_spacing_scale, + self.viewport_offset_x_scale, + self.viewport_offset_y_scale ] self.settings = SettingsManager.get_instance().settings self.desktop_settings = SettingsManager.get_instance().desktop_settings self.ipc = XRDriverIPC.get_instance() + self.virtual_display_manager = VirtualDisplayManager.get_instance() self.extensions_manager = ExtensionsManager.get_instance() - self.settings.bind('display-distance', self.display_distance_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT) - self.settings.bind('display-size', self.display_size_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT) + self.settings.bind('disable-physical-displays', self.disable_physical_displays_switch, 'active', Gio.SettingsBindFlags.DEFAULT) + self.settings.connect('changed::display-distance', self._handle_display_distance) + # self.settings.bind('display-size', self.display_size_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT) self.settings.bind('follow-threshold', self.follow_threshold_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT) - self.settings.bind('widescreen-mode', self.widescreen_mode_switch, 'active', Gio.SettingsBindFlags.DEFAULT) - self.settings.bind('curved-display', self.curved_display_switch, 'active', Gio.SettingsBindFlags.DEFAULT) + # self.settings.bind('widescreen-mode', self.widescreen_mode_switch, 'active', Gio.SettingsBindFlags.DEFAULT) + # self.settings.bind('curved-display', self.curved_display_switch, 'active', Gio.SettingsBindFlags.DEFAULT) self.settings.bind('headset-as-primary', self.headset_as_primary_switch, 'active', Gio.SettingsBindFlags.DEFAULT) self.settings.bind('use-optimal-monitor-config', self.use_optimal_monitor_config_switch, 'active', Gio.SettingsBindFlags.DEFAULT) self.settings.bind('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('monitor-spacing', self.monitor_spacing_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT) + self.settings.bind('viewport-offset-x', self.viewport_offset_x_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT) + self.settings.bind('viewport-offset-y', self.viewport_offset_y_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.display_zoom_on_focus_switch.connect('notify::active', self._handle_zoom_on_focus_switch_changed) + self.monitor_wrapping_scheme_menu.connect('changed', self._handle_monitor_wrapping_scheme_menu_changed) + self._handle_monitor_wrapping_scheme_setting_changed(self.settings, self.settings.get_string('monitor-wrapping-scheme')) bind_shortcut_settings(self.get_parent(), [ [self.reassign_toggle_xr_effect_shortcut_button, self.toggle_xr_effect_shortcut_label], @@ -88,10 +117,28 @@ class ConnectedDevice(Gtk.Box): [self.reassign_toggle_follow_shortcut_button, self.toggle_follow_shortcut_label] ]) - self.bind_set_distance_toggle([ - self.set_toggle_display_distance_start_button, - self.set_toggle_display_distance_end_button - ]) + self.change_focused_display_distance_button.connect('clicked', + self._on_display_distance_preset_change_button_clicked, + 'toggle-display-distance-start', + self._on_set_focused_display_distance, + _('Set Focused Display Distance'), + _('Use a closer value so the display zooms in when you look at it.'), + 0.2, 1.0 + ) + self.change_all_displays_distance_button.connect('clicked', + self._on_display_distance_preset_change_button_clicked, + 'toggle-display-distance-end', + self._on_set_all_displays_distance, + _('Set All Displays Distance'), + _('Use a farther value so the displays are zoomed out when you look away.'), + 1.0, 2.5 + ) + self._set_all_displays_distance(self.settings.get_double('toggle-display-distance-end')) + self._set_focused_display_distance(self.settings.get_double('toggle-display-distance-start')) + + self.add_virtual_display_menu.set_active_id('1080p') + self.add_virtual_display_button.connect('clicked', self._on_add_virtual_display) + self.launch_display_settings_button.connect('clicked', self._launch_display_settings) self.state_manager = StateManager.get_instance() self.state_manager.bind_property('follow-mode', self.follow_mode_switch, 'active', GObject.BindingFlags.DEFAULT) @@ -104,16 +151,46 @@ class ConnectedDevice(Gtk.Box): self.config_manager = ConfigManager.get_instance() self.config_manager.connect('notify::breezy-desktop-enabled', self._handle_enabled_config) + self.config_manager.bind_property('multi-tap-enabled', self.enable_multi_tap_switch, 'active', Gio.SettingsBindFlags.DEFAULT) + self.enable_multi_tap_switch.connect('notify::active', lambda widget, param: self.config_manager.set_property('multi-tap-enabled', widget.get_active())) self.use_optimal_monitor_config_switch.connect('notify::active', self._refresh_use_optimal_monitor_config) + self._handle_switch_enabled_state(self.effect_enable_switch, None) + self._handle_display_distance(self.settings, self.settings.get_double('display-distance')) self._handle_enabled_features(self.state_manager, None) self._handle_device_supports_sbs(self.state_manager, None) self._handle_enabled_config(None, None) self._refresh_use_optimal_monitor_config(self.use_optimal_monitor_config_switch, None) self.extensions_manager.connect('notify::breezy-enabled', self._handle_enabled_config) - self.connect("destroy", self._on_widget_destroy) + self.virtual_display_manager.connect('notify::displays', self._on_virtual_displays_update) + self._on_virtual_displays_update(self.virtual_display_manager, None) + + self.virtual_displays_by_pid = {} + + self._settings_displays_app_info = None + + for appinfo in Gio.AppInfo.get_all(): + if appinfo.get_id() == 'gnome-display-panel.desktop': + self._settings_displays_app_info = appinfo + break + + def _handle_zoom_on_focus_switch_changed(self, widget, param): + display_distance = self.settings.get_double('display-distance') + toggle_display_distance_end = self.settings.get_double('toggle-display-distance-end') + toggle_display_distance_start = self.settings.get_double('toggle-display-distance-start') + is_zoom_on_focus_already_enabled = display_distance < toggle_display_distance_end + if widget.get_active() and not is_zoom_on_focus_already_enabled: + self.settings.set_double('display-distance', toggle_display_distance_start) + elif not widget.get_active() and is_zoom_on_focus_already_enabled: + self.settings.set_double('display-distance', toggle_display_distance_end) + + def _handle_monitor_wrapping_scheme_setting_changed(self, settings, val): + self.monitor_wrapping_scheme_menu.set_active_id(val) + + 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] @@ -125,9 +202,9 @@ class ConnectedDevice(Gtk.Box): def _handle_device_supports_sbs(self, state_manager, val): if not state_manager.get_property('device-supports-sbs'): self.settings.set_boolean('widescreen-mode', False) - self.widescreen_mode_switch.set_sensitive(state_manager.get_property('device-supports-sbs')) - subtitle = self.widescreen_mode_subtitle if state_manager.get_property('device-supports-sbs') else self.widescreen_mode_not_supported_subtitle - self.widescreen_mode_row.set_subtitle(subtitle) + # self.widescreen_mode_switch.set_sensitive(state_manager.get_property('device-supports-sbs')) + # subtitle = self.widescreen_mode_subtitle if state_manager.get_property('device-supports-sbs') else self.widescreen_mode_not_supported_subtitle + # self.widescreen_mode_row.set_subtitle(subtitle) def _handle_enabled_config(self, object, val): enabled = self.config_manager.get_property('breezy-desktop-enabled') and self.extensions_manager.get_property('breezy-enabled') @@ -135,6 +212,9 @@ class ConnectedDevice(Gtk.Box): self.effect_enable_switch.set_active(enabled) def _handle_switch_enabled_state(self, switch, param): + GLib.idle_add(self._handle_switch_enabled_state_gui, switch, param) + + def _handle_switch_enabled_state_gui(self, switch, param): requesting_enabled = switch.get_active() # never turn off the extension, disabling the effect is done via configs only @@ -145,12 +225,17 @@ class ConnectedDevice(Gtk.Box): for widget in self.all_enabled_state_inputs: widget.set_sensitive(requesting_enabled) + + if not is_screencast_available(): + self.virtual_displays_row.set_subtitle( + _("Unable to add virtual displays on this machine. xdg-desktop-portal is required.")) + self.add_virtual_display_button.set_sensitive(False) + self.add_virtual_display_menu.set_sensitive(False) if requesting_enabled: self._refresh_follow_mode(self.follow_mode_switch, None) def _refresh_follow_mode(self, switch, param): - self.follow_threshold_scale.set_sensitive(switch.get_active()) if (self.state_manager.get_property('follow-mode') == switch.get_active()): return @@ -167,18 +252,90 @@ class ConnectedDevice(Gtk.Box): def set_device_name(self, name): self.device_label.set_markup(f"{name}") + + def _handle_display_distance(self, *args): + display_distance = self.settings.get_double('display-distance') + toggle_display_distance_end = self.settings.get_double('toggle-display-distance-end') + + should_zoom_on_focus_be_enabled = display_distance < toggle_display_distance_end + if self.display_zoom_on_focus_switch.get_active() != should_zoom_on_focus_be_enabled: + self.display_zoom_on_focus_switch.set_active(should_zoom_on_focus_be_enabled) + + def _set_focused_display_distance(self, distance): + self.focused_display_distance_label.set_markup(f"{_("Focused display")}: {distance}") + self.settings.set_double('toggle-display-distance-start', distance) + + self.display_zoom_on_focus_switch.set_sensitive(distance != self.settings.get_double('toggle-display-distance-end')) + + def _set_all_displays_distance(self, distance): + self.all_displays_distance_label.set_markup(f"{_("All displays")}: {distance}") + self.settings.set_double('toggle-display-distance-end', distance) + self.display_zoom_on_focus_switch.set_active(False) + self.display_zoom_on_focus_switch.set_sensitive(distance != self.settings.get_double('toggle-display-distance-start')) + + def _on_display_distance_preset_change_button_clicked(self, widget, settings_key, on_save_callback, title, subtitle, lower_limit, upper_limit): + dialog = DisplayDistanceDialog(settings_key, on_save_callback, title, subtitle, lower_limit, upper_limit) + dialog.set_transient_for(widget.get_ancestor(Gtk.Window)) + dialog.present() - def bind_set_distance_toggle(self, widgets): - for widget in widgets: - widget.connect('clicked', lambda *args, widget=widget: on_set_display_distance_toggle(widget)) - reload_display_distance_toggle_button(widget) - - def _on_widget_destroy(self, widget): - self.state_manager.unbind_property('follow-mode', self.follow_mode_switch, 'active') - self.settings.unbind('display-distance', self.display_distance_adjustment, 'value') - self.settings.unbind('display-size', self.display_size_adjustment, 'value') - self.settings.unbind('follow-threshold', self.follow_threshold_adjustment, 'value') - self.settings.unbind('widescreen-mode', self.widescreen_mode_switch, 'active') + def _on_set_all_displays_distance(self, prev_distance, distance): + focused_display_distance = self.settings.get_double('toggle-display-distance-start') + all_displays_distance = self.settings.get_double('toggle-display-distance-end') + if (distance < focused_display_distance): + self._set_focused_display_distance(distance) + + self._set_all_displays_distance(distance) + + if prev_distance == focused_display_distance: + self.settings.set_double('display-distance', prev_distance) + + def _on_set_focused_display_distance(self, prev_distance, distance): + focused_display_distance = self.settings.get_double('toggle-display-distance-start') + all_displays_distance = self.settings.get_double('toggle-display-distance-end') + if (distance > all_displays_distance): + self._set_all_displays_distance(distance) + + self._set_focused_display_distance(distance) + + def _on_add_virtual_display(self, *args): + resolution = self.add_virtual_display_menu.get_active_id() + logger.info(f"Adding virtual display {resolution}") + + width = 1920 + height = 1080 + if resolution == '1440p': + width = 2560 + height = 1440 + + self.virtual_display_manager.create_virtual_display(width, height, 60) + + def _on_virtual_displays_update(self, virtual_display_manager, val): + GLib.idle_add(self._on_virtual_displays_update_gui, virtual_display_manager) + + def _on_virtual_displays_update_gui(self, virtual_display_manager): + effect_enabled = self.effect_enable_switch.get_active() + virtual_displays_present = len(virtual_display_manager.displays) > 0 + self.launch_display_settings_button.set_visible( + self._settings_displays_app_info is not None and virtual_displays_present + ) + self.monitor_wrapping_scheme_menu.set_sensitive(effect_enabled and virtual_displays_present) + self.monitor_spacing_scale.set_sensitive(effect_enabled and virtual_displays_present) + + for pid, child in self.virtual_displays_by_pid.items(): + self.top_features_group.remove(child) + + new_displays_by_pid = {} + for display in virtual_display_manager.displays: + child = self.virtual_displays_by_pid.get( + display['pid'], + VirtualDisplayRow(display['pid'], display['width'], display['height'], 60)) + self.top_features_group.add(child) + new_displays_by_pid[display['pid']] = child + + self.virtual_displays_by_pid = new_displays_by_pid + + def _launch_display_settings(self, *args): + self._settings_displays_app_info.launch() def reload_display_distance_toggle_button(widget): distance = SettingsManager.get_instance().settings.get_double(widget.get_name()) diff --git a/ui/src/displaydistancedialog.py b/ui/src/displaydistancedialog.py new file mode 100644 index 0000000..9f02baa --- /dev/null +++ b/ui/src/displaydistancedialog.py @@ -0,0 +1,28 @@ +from gi.repository import Gtk, Gio +from .displaydistancedialogcontent import DisplayDistanceDialogContent + +@Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/display-distance-dialog.ui') +class DisplayDistanceDialog(Gtk.Dialog): + __gtype_name__ = 'DisplayDistanceDialog' + + show_full_scale_button = Gtk.Template.Child() + save_button = Gtk.Template.Child() + + def __init__(self, settings_key, on_save_callback, title, subtitle, lower_limit, upper_limit): + super(Gtk.Dialog, self).__init__() + self.init_template() + + self.on_save_callback = on_save_callback + self.set_title(title) + + self.content = DisplayDistanceDialogContent(settings_key, self.show_full_scale_button, self.save_button, self._on_save_callback, subtitle, lower_limit, upper_limit) + self.get_content_area().append(self.content) + + self.show_full_scale_button.connect('clicked', self._on_show_full_scale_button_clicked) + + def _on_show_full_scale_button_clicked(self, button): + self.show_full_scale_button.set_visible(False) + + def _on_save_callback(self, prev_distance, distance): + self.on_save_callback(prev_distance, distance) + self.close() \ No newline at end of file diff --git a/ui/src/displaydistancedialogcontent.py b/ui/src/displaydistancedialogcontent.py new file mode 100644 index 0000000..3b81e72 --- /dev/null +++ b/ui/src/displaydistancedialogcontent.py @@ -0,0 +1,58 @@ +from gi.repository import Gtk, Gio +from .settingsmanager import SettingsManager + +import gettext + +_ = gettext.gettext + + +@Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/display-distance-dialog-content.ui') +class DisplayDistanceDialogContent(Gtk.Box): + __gtype_name__ = 'DisplayDistanceDialogContent' + + display_distance_subtitle = Gtk.Template.Child() + display_distance_scale = Gtk.Template.Child() + display_distance_adjustment = Gtk.Template.Child() + + def __init__(self, settings_key, show_full_scale_button, save_button, on_save_callback, subtitle, lower_limit, upper_limit): + super(Gtk.Box, self).__init__() + self.init_template() + + self.display_distance_subtitle.set_markup(f"""{subtitle}""") + + self.on_save_callback = on_save_callback + self.settings = SettingsManager.get_instance().settings + self.prev_distance = self.settings.get_double('display-distance') + + self.lower_limit_orig = self.display_distance_adjustment.get_lower() + self.upper_limit_orig = self.display_distance_adjustment.get_upper() + + self._add_marks(lower_limit, upper_limit) + + self.settings.bind('display-distance', self.display_distance_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT) + + show_full_scale_button.connect('clicked', self._on_show_full_scale_button_clicked) + save_button.connect('clicked', self._on_save_button_clicked) + + def _add_marks(self, lower_limit, upper_limit): + self.display_distance_scale.clear_marks() + + if self.lower_limit_orig == lower_limit: + self.display_distance_scale.add_mark(self.lower_limit_orig, Gtk.PositionType.BOTTOM, _("closer")) + else: + self.display_distance_adjustment.set_lower(lower_limit) + + self.display_distance_scale.add_mark(1.0, Gtk.PositionType.BOTTOM, _("fullscreen")) + + if self.upper_limit_orig == upper_limit: + self.display_distance_scale.add_mark(self.upper_limit_orig, Gtk.PositionType.BOTTOM, _("farther")) + else: + self.display_distance_adjustment.set_upper(upper_limit) + + def _on_show_full_scale_button_clicked(self, button): + self._add_marks(self.lower_limit_orig, self.upper_limit_orig) + self.display_distance_adjustment.set_lower(self.lower_limit_orig) + self.display_distance_adjustment.set_upper(self.upper_limit_orig) + + def _on_save_button_clicked(self, button): + self.on_save_callback(self.prev_distance, self.display_distance_adjustment.get_value()) \ No newline at end of file diff --git a/ui/src/gtk/connected-device.ui b/ui/src/gtk/connected-device.ui index 26f7063..2849ef6 100644 --- a/ui/src/gtk/connected-device.ui +++ b/ui/src/gtk/connected-device.ui @@ -37,7 +37,7 @@ 20 20 - + Features @@ -51,11 +51,14 @@ - - Widescreen mode - 2 + + Zoom on focus + Automatically move a display closer when you look at it. +Set your preferred focused and unfocused distances in the Adjustments section. + - + 3 @@ -64,7 +67,7 @@ Follow mode - Keep the virtual display near the center of your view. + Keep the focused display near the center of your view. 3 @@ -74,12 +77,58 @@ - Curved display - Switch between flat and curved displays. + Disable physical displays + Automatically disable all physical displays when the XR effect is enabled. + + + 3 + + + + + + + Virtual displays 2 - + + 30 + 150 + 30 + + + 0 + launch-display-settings + 3 + Rearrange displays + + + + + 3 + + + + + 1080p + 1440p + + + + + + + + + + add-virtual-display + list-add-symbolic 3 + @@ -91,82 +140,54 @@ Adjustments - Display distance - Closer appears larger, further appears smaller. Controls depth when in widescreen mode. - - - 3 - true - 0 - 2 - 350 - false - - - 0.2 - 2.5 - 0.01 - 1.05 - - - - - - - - - - - - - - Display size - Combine with display distance to achieve a comfortable level of depth and size. - - - 3 - true - 0 - 2 - 350 - false - - - 0.2 - 2.5 - 0.01 - 1.0 - - - - - - - - - - - - - - Display toggle distances - Use the buttons to capture the current display distance for use with the keyboard shortcut. - 2 + Display distances + Set how close you want displays to appear. - 30 - 150 - 30 + 25 + 1 + 15 + 15 - - toggle-display-distance-start - 3 + + vertical + 10 + + + + 3 + + + + + set-all-displays-distance + 3 + Change + 20 + 20 + + - - toggle-display-distance-end - 3 + + vertical + 10 + + + + 3 + + + + + set-all-displays-distance + 3 + Change + 20 + 20 + + @@ -197,6 +218,125 @@ + + + Display angling + When there are multiple displays, choose how they should angle towards you. + 2 + + + 30 + 150 + 30 + 3 + + + + + Automatic + Side-angled + Top-angled + Flat + + + + + + + + + + Display spacing + Put empty space between displays, when there are multiple. + + + 3 + true + 0 + 0 + 350 + false + + + 0 + 100 + 1 + 0 + + + + 0 + + 50 + + 100 + + + + + + + + Viewport horizontal offset + By default, the viewport will center on the primary display. Use this slider to move the viewport to the left or right. + + + 3 + 0 + 1 + 350 + false + + + -2.5 + 2.5 + 0.1 + 0.0 + + + + left + + center + + right + + + + + + + + Viewport vertical offset + By default, the viewport will center on the primary display. Use this slider to move the viewport up or down. + + + 3 + 0 + 1 + 350 + false + + + -2.5 + 2.5 + 0.1 + 0.0 + + + + down + + center + + up + + + + + @@ -273,8 +413,8 @@ - Display distance shortcut - Quickly toggle between two predefined distances. + Toggle zoom on focus shortcut + Quickly toggle zoom on focus mode. 2 @@ -378,17 +518,6 @@ - - - Fast SBS mode switching - Switches glasses to SBS mode immediately when plugged in, if widescreen mode is on. May cause instability. - - - 3 - - - - Movement look-ahead @@ -451,6 +580,17 @@ + + + Enable multi-tap detection + Enables double-tap to recenter and triple-tap to recalibrate. + + + 3 + + + + diff --git a/ui/src/gtk/display-distance-dialog-content.ui b/ui/src/gtk/display-distance-dialog-content.ui new file mode 100644 index 0000000..704b9c4 --- /dev/null +++ b/ui/src/gtk/display-distance-dialog-content.ui @@ -0,0 +1,37 @@ + + + + + \ No newline at end of file diff --git a/ui/src/gtk/display-distance-dialog.ui b/ui/src/gtk/display-distance-dialog.ui new file mode 100644 index 0000000..1ac5219 --- /dev/null +++ b/ui/src/gtk/display-distance-dialog.ui @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/ui/src/gtk/no-device.ui b/ui/src/gtk/no-device.ui index c7d6cad..4ce401d 100644 --- a/ui/src/gtk/no-device.ui +++ b/ui/src/gtk/no-device.ui @@ -30,12 +30,11 @@ - - Start in widescreen mode - Widescreen mode is not supported for all glasses. - 2 + + Disable physical displays + Automatically disable all physical displays when the XR effect is enabled. - + 3 diff --git a/ui/src/gtk/virtual-display-row.ui b/ui/src/gtk/virtual-display-row.ui new file mode 100644 index 0000000..acb2c2f --- /dev/null +++ b/ui/src/gtk/virtual-display-row.ui @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/ui/src/meson.build b/ui/src/meson.build index 0a2b141..447c0d6 100644 --- a/ui/src/meson.build +++ b/ui/src/meson.build @@ -26,12 +26,23 @@ configure_file( install_mode: 'r-xr-xr-x' ) +configure_file( + input: 'virtualdisplay.in', + output: 'virtualdisplay', + configuration: conf, + install: true, + install_dir: get_option('bindir'), + install_mode: 'r-xr-xr-x' +) + breezydesktop_sources = [ '../modules/PyXRLinuxDriverIPC/xrdriveripc.py', '__init__.py', 'configmanager.py', 'connecteddevice.py', 'extensionsmanager.py', + 'displaydistancedialog.py', + 'displaydistancedialogcontent.py', 'failedverification.py', 'license.py', 'licensedialog.py', @@ -47,8 +58,11 @@ breezydesktop_sources = [ 'shortcutdialog.py', 'statemanager.py', 'time.py', + 'virtualdisplay.py', + 'virtualdisplaymanager.py', 'verify.py', 'window.py' ] install_data(breezydesktop_sources, install_dir: moduledir) +install_subdir('../lib', install_dir: moduledir) \ No newline at end of file diff --git a/ui/src/nodevice.py b/ui/src/nodevice.py index b36c656..fa56bfe 100644 --- a/ui/src/nodevice.py +++ b/ui/src/nodevice.py @@ -10,7 +10,8 @@ class NoDevice(Gtk.Box): __gtype_name__ = "NoDevice" effect_enable_switch = Gtk.Template.Child() - widescreen_mode_switch = Gtk.Template.Child() + disable_physical_displays_switch = Gtk.Template.Child() + # widescreen_mode_switch = Gtk.Template.Child() def __init__(self): super(Gtk.Box, self).__init__() @@ -23,7 +24,8 @@ class NoDevice(Gtk.Box): self.config_manager.connect('notify::breezy-desktop-enabled', self._handle_enabled_config) self.effect_enable_switch.connect('notify::active', self._handle_switch_enabled_state) - self.settings.bind('widescreen-mode', self.widescreen_mode_switch, 'active', Gio.SettingsBindFlags.DEFAULT) + # self.settings.bind('widescreen-mode', self.widescreen_mode_switch, 'active', Gio.SettingsBindFlags.DEFAULT) + self.settings.bind('disable-physical-displays', self.disable_physical_displays_switch, 'active', Gio.SettingsBindFlags.DEFAULT) self._handle_enabled_config(self.config_manager, None) @@ -41,5 +43,5 @@ class NoDevice(Gtk.Box): self.config_manager.set_property('breezy-desktop-enabled', requesting_enabled) - def _on_widget_destroy(self, widget): - self.settings.unbind('widescreen-mode', self.widescreen_mode_switch, 'active') + # def _on_widget_destroy(self, widget): + # self.settings.unbind('widescreen-mode', self.widescreen_mode_switch, 'active') diff --git a/ui/src/shortcutdialog.py b/ui/src/shortcutdialog.py index 356c74e..4bb1e3b 100644 --- a/ui/src/shortcutdialog.py +++ b/ui/src/shortcutdialog.py @@ -36,9 +36,6 @@ class ShortcutDialog(Gtk.Dialog): keycode, state ) - label = Gtk.accelerator_get_label(keyval, state) - - # hacky way to store the label, causes warnings from the WM SettingsManager.get_instance().settings.set_strv(self.settings_key, [binding]) self.close() diff --git a/ui/src/virtualdisplay.in b/ui/src/virtualdisplay.in new file mode 100755 index 0000000..882b148 --- /dev/null +++ b/ui/src/virtualdisplay.in @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 + +# virtualdisplay.in +# +# Copyright 2024 Unknown +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# SPDX-License-Identifier: GPL-3.0-or-later + +import logging +import os +import sys + +lib_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'lib') +sys.path.insert(0, lib_dir) + +from logging.handlers import TimedRotatingFileHandler + +config_home = os.environ.get('XDG_CONFIG_HOME', '~/.config') +config_dir = os.path.expanduser(config_home) +state_home = os.environ.get('XDG_STATE_HOME', '~/.local/state') +state_dir = os.path.expanduser(state_home) +breezy_state_dir = os.path.join(state_dir, 'breezy_gnome') +log_dir = os.path.join(breezy_state_dir, 'logs/ui') +os.makedirs(log_dir, exist_ok=True) + +logger = logging.getLogger('breezy_ui') +logger.setLevel(logging.INFO) +logname = os.path.join(log_dir, "breezy_desktop.log") +handler = TimedRotatingFileHandler(logname, when="midnight", backupCount=30) +handler.suffix = "%Y%m%d" +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +handler.setFormatter(formatter) +logger.addHandler(handler) + +import sys +import argparse +import signal +import time + +VERSION = '@VERSION@' + +xdg_data_home = os.getenv('XDG_DATA_HOME', os.path.join(os.path.expanduser('~'), '.local', 'share')) +appdir = os.getenv('APPDIR', xdg_data_home) +locale_dir = os.path.join(appdir, 'locale') +pkgdatadir = os.path.join(appdir, 'breezydesktop') +sys.path.insert(1, pkgdatadir) + +import gi + +gi.require_version('GLib', '2.0') + +from gi.repository import GLib + +def graceful_shutdown(signum, frame): + global virtual_display_instance + global loop + + if virtual_display_instance is not None: + virtual_display_instance.terminate() + +def _on_display_closed(): + global loop + loop.quit() + +def create_display(width, height, framerate): + global virtual_display_instance + + virtual_display_instance = VirtualDisplay(width, height, framerate, _on_display_closed) + virtual_display_instance.create() + +if __name__ == "__main__": + from breezydesktop import virtualdisplay + from breezydesktop.virtualdisplay import VirtualDisplay + + global virtual_display_instance + global loop + + parser = argparse.ArgumentParser(description="Virtual display arguments") + parser.add_argument("--height", type=int, required=True, help="Height of the display") + parser.add_argument("--width", type=int, required=True, help="Width of the display") + parser.add_argument("--framerate", type=int, default=60, help="Framerate of the display") + args = parser.parse_args() + + signal.signal(signal.SIGTERM, graceful_shutdown) + signal.signal(signal.SIGINT, graceful_shutdown) + + loop = GLib.MainLoop() + + try: + GLib.idle_add(create_display, args.width, args.height, args.framerate) + loop.run() + except Exception as e: + logger.error(f"Error in main loop: {e}") + sys.exit(1) \ No newline at end of file diff --git a/ui/src/virtualdisplay.py b/ui/src/virtualdisplay.py new file mode 100644 index 0000000..64eb478 --- /dev/null +++ b/ui/src/virtualdisplay.py @@ -0,0 +1,100 @@ +#!/usr/bin/python3 + +import argparse +import logging +import sys +import signal +from .lib import pydbus +import gi +import time + +gi.require_version('Gst', '1.0') +from gi.repository import GLib, GObject, Gst + +logger = logging.getLogger('breezy_ui') + +screen_cast_iface = 'org.gnome.Mutter.ScreenCast' +screen_cast_session_iface = 'org.gnome.Mutter.ScreenCast.Session' +screen_cast_stream_iface = 'org.gnome.Mutter.ScreenCast.Session' +gst_pipeline_format = "pipewiresrc path=%u ! video/x-raw,max-framerate=%d/1,width=%d,height=%d ! fakesink sync=false" + +class VirtualDisplay: + def __init__(self, width, height, framerate, on_closed_cb): + self.width = width + self.height = height + self.framerate = framerate + self.on_closed_cb = on_closed_cb + + Gst.init(None) + + def _screen_cast_session(self): + bus = pydbus.SessionBus() + screen_cast = bus.get(screen_cast_iface, '/org/gnome/Mutter/ScreenCast') + session_path = screen_cast.CreateSession([]) + screen_cast_session = bus.get(screen_cast_iface, session_path) + + return screen_cast_session + + def _on_session_closed(self): + self.stream = None + self.terminate() + + def create(self): + session = self._screen_cast_session() + session.onClosed = self._on_session_closed + stream_path = session.RecordVirtual({ + 'is-platform': GLib.Variant.new_boolean(True), + }) + bus = pydbus.SessionBus() + self.stream = bus.get(screen_cast_iface, stream_path) + + self.stream.onPipeWireStreamAdded = self._on_pipewire_stream_added + + session.Start() + + def terminate(self): + try: + if self.stream is not None: + self.stream.Stop() + except Exception as e: + logger.error("Failed to stop stream: %s" % e) + + try: + if self.pipeline is not None: + self.pipeline.send_event(Gst.Event.new_eos()) + self.pipeline.set_state(Gst.State.NULL) + except Exception as e: + logger.error("Failed to stop pipeline: %s" % e) + + self.on_closed_cb() + + def _on_message(self, bus, message): + type = message.type + logger.info("message type: %s" % type) + if type == Gst.MessageType.EOS: + self.pipeline = None + self.terminate() + elif type == Gst.MessageType.ERROR: + err, debug = message.parse_error() + logger.error("Error: %s" % err) + logger.error("Debug: %s" % debug) + self.terminate() + + def _on_pipewire_stream_added(self, node_id): + self.pipeline = Gst.parse_launch(gst_pipeline_format % (node_id, self.framerate, self.width, self.height)) + self.pipeline.set_state(Gst.State.PLAYING) + self.pipeline.get_bus().connect('message', self._on_message) + self.pipeline.set_state(Gst.State.PAUSED) + +def is_screencast_available(): + try: + bus = pydbus.SessionBus() + # Try to get the ScreenCast interface + screen_cast = bus.get(screen_cast_iface, '/org/gnome/Mutter/ScreenCast') + return True + except Exception as e: + logger.warning(f"ScreenCast portal not available: {e}") + return False + + + diff --git a/ui/src/virtualdisplaymanager.py b/ui/src/virtualdisplaymanager.py new file mode 100644 index 0000000..dd02bc1 --- /dev/null +++ b/ui/src/virtualdisplaymanager.py @@ -0,0 +1,122 @@ +import gi +import json +import os +import signal +import subprocess +import time +from pathlib import Path + +import logging +logger = logging.getLogger('breezy_ui') + +gi.require_version('GLib', '2.0') +from gi.repository import GLib, GObject + +xdg_bin_home = os.getenv('XDG_BIN_HOME', os.path.join(os.path.expanduser('~'), '.local', 'bin')) +bindir = os.getenv('BINDIR', xdg_bin_home) + +class VirtualDisplayManager(GObject.GObject): + __gproperties__ = { + 'displays': (object, 'Displays', 'A list of the displays', GObject.ParamFlags.READWRITE) + } + _instance = None + + @staticmethod + def get_instance(): + if not VirtualDisplayManager._instance: + VirtualDisplayManager._instance = VirtualDisplayManager() + + return VirtualDisplayManager._instance + + def __init__(self): + GObject.GObject.__init__(self) + + self.shm_path = Path("/dev/shm/breezy_virtual_displays.json") + self._load_displays() + self._prune_dead_display_processes() + + GLib.timeout_add_seconds(15, self._prune_dead_display_processes) + + def _process_dead(self, pid): + if (not os.path.exists(f"/proc/{pid}")): + return True + + try: + if (os.waitpid(pid, os.WNOHANG) == (pid, 0)): + return True + except ChildProcessError: + # process isn't tied to the current process, it's not dead if it's still open + return False + + return False + + def _prune_dead_display_processes(self): + new_displays = [disp for disp in self.displays if not self._process_dead(disp['pid'])] + if new_displays != self.displays: + self.set_property('displays', new_displays) + self._save_processes() + + return GLib.SOURCE_CONTINUE + + def create_virtual_display(self, width, height, framerate): + try: + process = subprocess.Popen( + [f"{bindir}/virtualdisplay", "--width", str(width), "--height", str(height), "--framerate", str(framerate)], + start_new_session=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + if process.returncode is not None: + logger.error(f"Failed to create virtual display: {process.stderr.read()}") + return + + self.displays.append({ + 'pid': process.pid, + 'width': width, + 'height': height + }) + self.set_property('displays', self.displays) + self._save_processes() + except Exception as e: + logger.error(f"Failed to create virtual display: {e}") + + def destroy_virtual_display(self, pid: str) -> bool: + try: + # Send SIGTERM to allow graceful shutdown + os.killpg(pid, signal.SIGTERM) + self.set_property('displays', [disp for disp in self.displays if disp['pid'] != pid]) + self._save_processes() + return True + except ProcessLookupError: + # Process already gone, delete pid from list + self.set_property('displays', [disp for disp in self.displays if disp['pid'] != pid]) + self._save_processes() + return True + except Exception as e: + print(f"Failed to kill process {pid}: {e}") + return False + + def _save_processes(self): + with open(self.shm_path, 'w') as f: + json.dump(self.displays, f) + + def _load_displays(self): + displays = [] + if self.shm_path.exists(): + try: + with open(self.shm_path, 'r') as f: + displays = json.load(f) + except Exception: + displays = [] + + self.set_property('displays', displays) + + def do_set_property(self, prop, value): + if prop.name == 'displays': + self.displays = value + + def do_get_property(self, prop): + if prop.name == 'displays': + return self.displays \ No newline at end of file diff --git a/ui/src/virtualdisplayrow.py b/ui/src/virtualdisplayrow.py new file mode 100644 index 0000000..ef4593a --- /dev/null +++ b/ui/src/virtualdisplayrow.py @@ -0,0 +1,31 @@ +from gi.repository import Adw, Gtk +from .virtualdisplaymanager import VirtualDisplayManager + +import gettext + +_ = gettext.gettext + +@Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/virtual-display-row.ui') +class VirtualDisplayRow(Adw.ActionRow): + __gtype_name__ = "VirtualDisplayRow" + + remove_virtual_display_button = Gtk.Template.Child() + + def __init__(self, pid, width, height, framerate): + super(Adw.ActionRow, self).__init__() + self.init_template() + self.pid = pid + + icon = Gtk.Image.new_from_icon_name("video-display-symbolic") + + # padding around the icon + self.add_prefix(Gtk.Label(label=" ")) + self.add_prefix(icon) + self.add_prefix(Gtk.Label(label=" ")) + + self.set_subtitle(f"{width} x {height}") + + self.remove_virtual_display_button.connect('clicked', self._remove_virtual_display) + + def _remove_virtual_display(self, widget): + VirtualDisplayManager.get_instance().destroy_virtual_display(self.pid) \ No newline at end of file diff --git a/ui/src/window.py b/ui/src/window.py index 502f7dc..330e743 100644 --- a/ui/src/window.py +++ b/ui/src/window.py @@ -22,6 +22,7 @@ from .extensionsmanager import ExtensionsManager from .license import BREEZY_GNOME_FEATURES from .licensedialog import LicenseDialog from .statemanager import StateManager +from .settingsmanager import SettingsManager from .connecteddevice import ConnectedDevice from .failedverification import FailedVerification from .nodevice import NoDevice @@ -42,14 +43,6 @@ class BreezydesktopWindow(Gtk.ApplicationWindow): def __init__(self, skip_verification, **kwargs): super().__init__(**kwargs) - - self._skip_verification = skip_verification - - self.state_manager = StateManager.get_instance() - self.state_manager.connect('device-update', self._handle_state_update) - self.state_manager.connect('notify::license-action-needed', self._handle_state_update) - self.state_manager.connect('notify::license-present', self._handle_state_update) - self.state_manager.connect('notify::enabled-features-list', self._handle_state_update) self.connected_device = ConnectedDevice() self.failed_verification = FailedVerification() @@ -57,6 +50,16 @@ class BreezydesktopWindow(Gtk.ApplicationWindow): self.no_driver = NoDriver() self.no_extension = NoExtension() self.no_license = NoLicense() + + self._skip_verification = skip_verification + + self.settings = SettingsManager.get_instance().settings + self.state_manager = StateManager.get_instance() + self.state_manager.connect('device-update', self._handle_state_update) + self.state_manager.connect('notify::license-action-needed', self._handle_state_update) + self.state_manager.connect('notify::license-present', self._handle_state_update) + self.state_manager.connect('notify::enabled-features-list', self._handle_state_update) + self.settings.connect('changed::debug-no-device', self._handle_settings_update) self.license_action_needed_button.connect('clicked', self._on_license_button_clicked) self.missing_breezy_features_button.connect('clicked', self._on_license_button_clicked) @@ -67,6 +70,9 @@ class BreezydesktopWindow(Gtk.ApplicationWindow): self.connect("destroy", self._on_window_destroy) + def _handle_settings_update(self, settings_manager, key): + self._handle_state_update(self.state_manager, None) + def _handle_state_update(self, state_manager, val): GLib.idle_add(self._handle_state_update_gui, state_manager) @@ -79,7 +85,10 @@ class BreezydesktopWindow(Gtk.ApplicationWindow): for child in self.main_content: self.main_content.remove(child) - if not self._skip_verification and not verify_installation(): + if self.settings.get_boolean('debug-no-device'): + self.main_content.append(self.connected_device) + self.connected_device.set_device_name('Fake device') + elif not self._skip_verification and not verify_installation(): self.main_content.append(self.failed_verification) elif not ExtensionsManager.get_instance().is_installed(): self.main_content.append(self.no_extension)