Add multi-monitor support (#89)

Rewrite of Breezy Desktop to support multiple virtual displays
This commit is contained in:
Wayne Heaney 2025-03-05 15:58:07 -08:00 committed by GitHub
parent 07bf239433
commit 8a2926890d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 6248 additions and 2147 deletions

1
.gitmodules vendored
View File

@ -11,3 +11,4 @@
[submodule "ui/modules/PyXRLinuxDriverIPC"]
path = ui/modules/PyXRLinuxDriverIPC
url = https://github.com/wheaney/PyXRLinuxDriverIPC.git
branch = v1.1

View File

@ -1 +1 @@
1.1.7
2.0.0

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@ -1 +1 @@
Subproject commit 53002afde8406eef7fe309206268ad6dc39e2da6
Subproject commit a66c9aee82989ea7e98da5a05fde621ffb47af83

View File

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

View File

@ -91,6 +91,42 @@
The size of the display
</description>
</key>
<key name="viewport-offset-x" type="d">
<default>
0.0
</default>
<summary>Viewport offset x</summary>
<description>
How far to offset the viewport from the target monitor in the x direction
</description>
</key>
<key name="viewport-offset-y" type="d">
<default>
0.0
</default>
<summary>Viewport offset y</summary>
<description>
How far to offset the viewport from the target monitor in the y direction
</description>
</key>
<key name="monitor-wrapping-scheme" type="s">
<default>
"automatic"
</default>
<summary>Monitor wrapping scheme</summary>
<description>
How the monitors are wrapped around the viewport
</description>
</key>
<key name="monitor-spacing" type="i">
<default>
0
</default>
<summary>Monitor spacing</summary>
<description>
How far apart the monitors are visually (not logically)
</description>
</key>
<key name="curved-display" type="b">
<default>
false
@ -120,7 +156,7 @@
</key>
<key name="headset-as-primary" type="b">
<default>
true
false
</default>
<summary>Headset as primary</summary>
<description>
@ -154,6 +190,15 @@
Disable anti-aliasing
</description>
</key>
<key name="disable-physical-displays" type="b">
<default>
true
</default>
<summary>Disable physical displays</summary>
<description>
Disable physical displays when XR effect is enabled
</description>
</key>
<key name="developer-mode" type="b">
<default>
false
@ -172,6 +217,15 @@
Log debug messages
</description>
</key>
<key name="debug-no-device" type="b">
<default>
false
</default>
<summary>Debug no device</summary>
<description>
Debug no device
</description>
</key>
<key name="custom-monitor-product" type="s">
<default>
""
@ -180,6 +234,15 @@
<description>
Custom monitor product
</description>
</key>
<key name="framerate-cap" type="d">
<default>
0.0
</default>
<summary>Framerate cap</summary>
<description>
Framerate cap
</description>
</key>
</schema>
</schemalist>

@ -1 +1 @@
Subproject commit c3b0ddab302e11e517b57b621ff334faf03173de
Subproject commit b01239646d7ea6f78a823f591946855b880f0484

View File

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@ -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: <wayne@xronlinux.com>\n"
"Language-Team: German <translation-team-de@lists.sourceforge.net>\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"

View File

@ -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: <wayne@xronlinux.com>\n"
"Language-Team: Spanish <es@tp.org.es>\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."

View File

@ -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: <wayne@xronlinux.com>\n"
"Language-Team: French <traduc@traduc.org>\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."

View File

@ -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: <wayne@xronlinux.com>\n"
"Language-Team: Italian <tp@lists.linux.it>\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."

View File

@ -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: <wayne@xronlinux.com>\n"
"Language-Team: Japanese <translation-team-ja@lists.sourceforge.net>\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 ""
#~ "ワイドスクリーンモードはすべてのメガネで対応しているわけではありません。"

View File

@ -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: <wayne@xronlinux.com>\n"
"Language-Team: Polish <translation-team-pl@lists.sourceforge.net>\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 ""

View File

@ -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: <wayne@xronlinux.com>\n"
"Language-Team: Brazilian Portuguese <ldpbr-translation@lists.sourceforge."
"net>\n"
"Language-Team: Brazilian Portuguese <ldpbr-"
"translation@lists.sourceforge.net>\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"

View File

@ -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: <wayne@xronlinux.com>\n"
"Language-Team: Russian <gnu@d07.ru>\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 "Режим широкого экрана"

View File

@ -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: <wayne@xronlinux.com>\n"
"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\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."

View File

@ -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: <wayne@xronlinux.com>\n"
"Language-Team: Ukrainian <trans-uk@lists.fedoraproject.org>\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 "Режим широкого екрану"

View File

@ -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: <wayne@xronlinux.com>\n"
"Language-Team: Chinese (simplified) <i18n-zh@googlegroups.com>\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眼睛"

View File

@ -2,6 +2,8 @@
<gresources>
<gresource prefix="/com/xronlinux/BreezyDesktop">
<file preprocess="xml-stripblanks">gtk/connected-device.ui</file>
<file preprocess="xml-stripblanks">gtk/display-distance-dialog.ui</file>
<file preprocess="xml-stripblanks">gtk/display-distance-dialog-content.ui</file>
<file preprocess="xml-stripblanks">gtk/failed-verification.ui</file>
<file preprocess="xml-stripblanks">gtk/license-dialog.ui</file>
<file preprocess="xml-stripblanks">gtk/license-dialog-content.ui</file>
@ -10,6 +12,7 @@
<file preprocess="xml-stripblanks">gtk/no-extension.ui</file>
<file preprocess="xml-stripblanks">gtk/no-license.ui</file>
<file preprocess="xml-stripblanks">gtk/shortcut-dialog.ui</file>
<file preprocess="xml-stripblanks">gtk/virtual-display-row.ui</file>
<file preprocess="xml-stripblanks">gtk/window.ui</file>
</gresource>
</gresources>

View File

@ -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
return self.breezy_desktop_enabled
elif prop.name == 'multi-tap-enabled':
return self.multi_tap_enabled

View File

@ -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"<b>{name}</b>")
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")}: <b>{distance}</b>")
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")}: <b>{distance}</b>")
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())

View File

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

View File

@ -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"""<span size="small">{subtitle}</span>""")
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())

View File

@ -37,7 +37,7 @@
<property name="margin-end">20</property>
<property name="spacing">20</property>
<child>
<object class="AdwPreferencesGroup">
<object class="AdwPreferencesGroup" id="top_features_group">
<property name="title" translatable="yes"><!-- section heading for switches that enable certain features -->Features</property>
<child>
<object class="AdwActionRow">
@ -51,11 +51,14 @@
</object>
</child>
<child>
<object class="AdwActionRow" id="widescreen_mode_row">
<property name="title" translatable="yes"><!-- feature switch -->Widescreen mode</property>
<property name="valign">2</property>
<object class="AdwActionRow">
<property name="title" translatable="yes"><!-- feature switch -->Zoom on focus</property>
<property name="subtitle" translatable="yes"><!--
-->Automatically move a display closer when you look at it.
<!-- -->Set your preferred focused and unfocused distances in the Adjustments section.
</property>
<child>
<object class="GtkSwitch" id="widescreen_mode_switch">
<object class="GtkSwitch" id="display_zoom_on_focus_switch">
<property name="valign">3</property>
</object>
</child>
@ -64,7 +67,7 @@
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes"><!-- feature switch -->Follow mode</property>
<property name="subtitle" translatable="yes">Keep the virtual display near the center of your view.</property>
<property name="subtitle" translatable="yes">Keep the focused display near the center of your view.</property>
<child>
<object class="GtkSwitch" id="follow_mode_switch">
<property name="valign">3</property>
@ -74,12 +77,58 @@
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes"><!-- feature switch -->Curved display</property>
<property name="subtitle" translatable="yes">Switch between flat and curved displays.</property>
<property name="title" translatable="yes"><!-- feature switch -->Disable physical displays</property>
<property name="subtitle" translatable="yes">Automatically disable all physical displays when the XR effect is enabled.</property>
<child>
<object class="GtkSwitch" id="disable_physical_displays_switch">
<property name="valign">3</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow" id="virtual_displays_row">
<property name="title" translatable="yes">Virtual displays</property>
<property name="valign">2</property>
<child>
<object class="GtkSwitch" id="curved_display_switch">
<object class="GtkBox">
<property name="spacing">30</property>
<property name="width-request">150</property>
<property name="margin-start">30</property>
<child>
<object class="GtkButton" id="launch_display_settings_button">
<property name="visible">0</property>
<property name="name">launch-display-settings</property>
<property name="valign">3</property>
<property name="label" translatable="yes">Rearrange displays</property>
</object>
</child>
<child>
<object class="GtkBox">
<property name="valign">3</property>
<style>
<class name="flat"/>
</style>
<child>
<object class="GtkComboBoxText" id="add_virtual_display_menu">
<items>
<item translatable="yes" id="1080p">1080p</item>
<item translatable="yes" id="1440p">1440p</item>
</items>
</object>
</child>
</object>
</child>
</object>
</child>
<child type="suffix">
<object class="GtkButton" id="add_virtual_display_button">
<property name="name">add-virtual-display</property>
<property name="icon-name">list-add-symbolic</property>
<property name="valign">3</property>
<style>
<class name="flat"/>
</style>
</object>
</child>
</object>
@ -91,82 +140,54 @@
<property name="title" translatable="yes"><!-- Section containing sliders for adjusting the XR effect -->Adjustments</property>
<child>
<object class="AdwActionRow" id="display_distance_row">
<property name="title" translatable="yes"><!-- adjustment slider -->Display distance</property>
<property name="subtitle" translatable="yes">Closer appears larger, further appears smaller. Controls depth when in widescreen mode.</property>
<child>
<object class="GtkScale" id="display_distance_scale">
<property name="valign">3</property>
<property name="draw-value">true</property>
<property name="value-pos">0</property>
<property name="digits">2</property>
<property name="width-request">350</property>
<property name="has-origin">false</property>
<property name="adjustment">
<object class="GtkAdjustment" id="display_distance_adjustment">
<property name="lower">0.2</property>
<property name="upper">2.5</property>
<property name="step-increment">0.01</property>
<property name="value">1.05</property>
</object>
</property>
<marks>
<mark value="0.2" position="bottom"></mark>
<mark value="1.0" position="bottom"></mark>
<mark value="2.5" position="bottom"></mark>
</marks>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes"><!-- adjustment slider -->Display size</property>
<property name="subtitle" translatable="yes">Combine with display distance to achieve a comfortable level of depth and size.</property>
<child>
<object class="GtkScale" id="display_size_scale">
<property name="valign">3</property>
<property name="draw-value">true</property>
<property name="value-pos">0</property>
<property name="digits">2</property>
<property name="width-request">350</property>
<property name="has-origin">false</property>
<property name="adjustment">
<object class="GtkAdjustment" id="display_size_adjustment">
<property name="lower">0.2</property>
<property name="upper">2.5</property>
<property name="step-increment">0.01</property>
<property name="value">1.0</property>
</object>
</property>
<marks>
<mark value="0.2" position="bottom"></mark>
<mark value="1.0" position="bottom"></mark>
<mark value="2.5" position="bottom"></mark>
</marks>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes"><!-- adjustment slider -->Display toggle distances</property>
<property name="subtitle" translatable="yes">Use the buttons to capture the current display distance for use with the keyboard shortcut.</property>
<property name="valign">2</property>
<property name="title" translatable="yes"><!-- adjustment slider -->Display distances</property>
<property name="subtitle" translatable="yes">Set how close you want displays to appear.</property>
<child>
<object class="GtkBox">
<property name="spacing">30</property>
<property name="width-request">150</property>
<property name="margin-start">30</property>
<property name="spacing">25</property>
<property name="homogeneous">1</property>
<property name="margin-top">15</property>
<property name="margin-bottom">15</property>
<child>
<object class="GtkButton" id="set_toggle_display_distance_start_button">
<property name="name">toggle-display-distance-start</property>
<property name="valign">3</property>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel" id="focused_display_distance_label">
<property name="label" translatable="yes"></property>
<property name="valign">3</property>
</object>
</child>
<child>
<object class="GtkButton" id="change_focused_display_distance_button">
<property name="name">set-all-displays-distance</property>
<property name="valign">3</property>
<property name="label">Change</property>
<property name="margin-start">20</property>
<property name="margin-end">20</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="set_toggle_display_distance_end_button">
<property name="name">toggle-display-distance-end</property>
<property name="valign">3</property>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel" id="all_displays_distance_label">
<property name="label" translatable="yes"></property>
<property name="valign">3</property>
</object>
</child>
<child>
<object class="GtkButton" id="change_all_displays_distance_button">
<property name="name">set-all-displays-distance</property>
<property name="valign">3</property>
<property name="label">Change</property>
<property name="margin-start">20</property>
<property name="margin-end">20</property>
</object>
</child>
</object>
</child>
</object>
@ -197,6 +218,125 @@
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes"><!-- dropdown menu -->Display angling</property>
<property name="subtitle" translatable="yes">When there are multiple displays, choose how they should angle towards you.</property>
<property name="valign">2</property>
<child>
<object class="GtkBox">
<property name="spacing">30</property>
<property name="width-request">150</property>
<property name="margin-start">30</property>
<property name="valign">3</property>
<style>
<class name="flat"/>
</style>
<child>
<object class="GtkComboBoxText" id="monitor_wrapping_scheme_menu">
<items>
<item translatable="yes" id="automatic">Automatic</item>
<item translatable="yes" id="horizontal">Side-angled</item>
<item translatable="yes" id="vertical">Top-angled</item>
<item translatable="yes" id="none">Flat</item>
</items>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes"><!-- adjustment slider -->Display spacing</property>
<property name="subtitle" translatable="yes">Put empty space between displays, when there are multiple.</property>
<child>
<object class="GtkScale" id="monitor_spacing_scale">
<property name="valign">3</property>
<property name="draw-value">true</property>
<property name="value-pos">0</property>
<property name="digits">0</property>
<property name="width-request">350</property>
<property name="has-origin">false</property>
<property name="adjustment">
<object class="GtkAdjustment" id="monitor_spacing_adjustment">
<property name="lower">0</property>
<property name="upper">100</property>
<property name="step-increment">1</property>
<property name="value">0</property>
</object>
</property>
<marks>
<mark value="0" position="bottom">0</mark>
<mark value="25" position="bottom"></mark>
<mark value="50" position="bottom">50</mark>
<mark value="75" position="bottom"></mark>
<mark value="100" position="bottom">100</mark>
</marks>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes"><!-- adjustment slider -->Viewport horizontal offset</property>
<property name="subtitle" translatable="yes">By default, the viewport will center on the primary display. Use this slider to move the viewport to the left or right.</property>
<child>
<object class="GtkScale" id="viewport_offset_x_scale">
<property name="valign">3</property>
<property name="value-pos">0</property>
<property name="digits">1</property>
<property name="width-request">350</property>
<property name="has-origin">false</property>
<property name="adjustment">
<object class="GtkAdjustment" id="viewport_offset_x_adjustment">
<property name="lower">-2.5</property>
<property name="upper">2.5</property>
<property name="step-increment">0.1</property>
<property name="value">0.0</property>
</object>
</property>
<marks>
<mark value="-2.0" position="bottom">left</mark>
<mark value="-1.0" position="bottom"></mark>
<mark value="0.0" position="bottom">center</mark>
<mark value="1.0" position="bottom"></mark>
<mark value="2.0" position="bottom">right</mark>
</marks>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes"><!-- adjustment slider -->Viewport vertical offset</property>
<property name="subtitle" translatable="yes">By default, the viewport will center on the primary display. Use this slider to move the viewport up or down.</property>
<child>
<object class="GtkScale" id="viewport_offset_y_scale">
<property name="valign">3</property>
<property name="value-pos">0</property>
<property name="digits">1</property>
<property name="width-request">350</property>
<property name="has-origin">false</property>
<property name="adjustment">
<object class="GtkAdjustment" id="viewport_offset_y_adjustment">
<property name="lower">-2.5</property>
<property name="upper">2.5</property>
<property name="step-increment">0.1</property>
<property name="value">0.0</property>
</object>
</property>
<marks>
<mark value="-2.0" position="bottom">down</mark>
<mark value="-1.0" position="bottom"></mark>
<mark value="0.0" position="bottom">center</mark>
<mark value="1.0" position="bottom"></mark>
<mark value="2.0" position="bottom">up</mark>
</marks>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
@ -273,8 +413,8 @@
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Display distance shortcut</property>
<property name="subtitle" translatable="yes">Quickly toggle between two predefined distances.</property>
<property name="title" translatable="yes">Toggle zoom on focus shortcut</property>
<property name="subtitle" translatable="yes">Quickly toggle zoom on focus mode.</property>
<property name="valign">2</property>
<child>
<object class="GtkBox">
@ -378,17 +518,6 @@
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Fast SBS mode switching</property>
<property name="subtitle" translatable="yes">Switches glasses to SBS mode immediately when plugged in, if widescreen mode is on. May cause instability.</property>
<child>
<object class="GtkSwitch" id="fast_sbs_mode_switch">
<property name="valign">3</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Movement look-ahead</property>
@ -451,6 +580,17 @@
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Enable multi-tap detection</property>
<property name="subtitle" translatable="yes">Enables double-tap to recenter and triple-tap to recalibrate.</property>
<child>
<object class="GtkSwitch" id="enable_multi_tap_switch">
<property name="valign">3</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<template class="DisplayDistanceDialogContent" parent="GtkBox">
<property name="orientation">vertical</property>
<property name="valign">3</property>
<child>
<object class="GtkLabel" id="display_distance_subtitle">
<property name="wrap">true</property>
<property name="margin-top">20</property>
<property name="margin-start">20</property>
<property name="margin-end">20</property>
</object>
</child>
<child>
<object class="GtkScale" id="display_distance_scale">
<property name="valign">3</property>
<property name="margin-top">20</property>
<property name="margin-start">20</property>
<property name="margin-end">20</property>
<property name="draw-value">true</property>
<property name="value-pos">0</property>
<property name="digits">2</property>
<property name="width-request">350</property>
<property name="has-origin">false</property>
<property name="adjustment">
<object class="GtkAdjustment" id="display_distance_adjustment">
<property name="lower">0.2</property>
<property name="upper">2.5</property>
<property name="step-increment">0.01</property>
<property name="value">1.05</property>
</object>
</property>
</object>
</child>
</template>
</interface>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<template class="DisplayDistanceDialog" parent="GtkDialog">
<property name="modal">1</property>
<property name="use-header-bar">1</property>
<child type="action">
<object class="GtkButton" id="show_full_scale_button">
<property name="label" translatable="yes">Show full range</property>
<property name="margin-top">10</property>
<property name="margin-bottom">10</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
</object>
</child>
<child type="action">
<object class="GtkButton" id="save_button">
<property name="label" translatable="yes">Done</property>
<property name="margin-top">10</property>
<property name="margin-bottom">10</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
</object>
</child>
</template>
</interface>

View File

@ -30,12 +30,11 @@
</object>
</child>
<child>
<object class="AdwActionRow" id="widescreen_mode_row">
<property name="title" translatable="yes"><!-- feature switch -->Start in widescreen mode</property>
<property name="subtitle" translatable="yes">Widescreen mode is not supported for all glasses.</property>
<property name="valign">2</property>
<object class="AdwActionRow">
<property name="title" translatable="yes"><!-- feature switch -->Disable physical displays</property>
<property name="subtitle" translatable="yes">Automatically disable all physical displays when the XR effect is enabled.</property>
<child>
<object class="GtkSwitch" id="widescreen_mode_switch">
<object class="GtkSwitch" id="disable_physical_displays_switch">
<property name="valign">3</property>
</object>
</child>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<template class="VirtualDisplayRow" parent="AdwActionRow">
<property name="subtitle"></property>
<child type="suffix">
<object class="GtkButton" id="remove_virtual_display_button">
<property name="name">remove-virtual-display</property>
<property name="icon-name">user-trash-symbolic</property>
<property name="valign">center</property>
<style>
<class name="flat"/>
</style>
</object>
</child>
</template>
</interface>

View File

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

View File

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

View File

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

107
ui/src/virtualdisplay.in Executable file
View File

@ -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 <https://www.gnu.org/licenses/>.
#
# 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)

100
ui/src/virtualdisplay.py Normal file
View File

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

View File

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

View File

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

View File

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