664 lines
31 KiB
JavaScript
664 lines
31 KiB
JavaScript
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, 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 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';
|
|
|
|
export default class BreezyDesktopExtension extends Extension {
|
|
constructor(metadata, uuid) {
|
|
super(metadata, uuid);
|
|
|
|
this.settings = this.getSettings();
|
|
|
|
// Set/destroyed by enable/disable
|
|
this._cursor_manager = null;
|
|
this._monitor_manager = null;
|
|
this._virtual_displays_actor = null;
|
|
this._virtual_displays_overlay = null;
|
|
this._target_monitor = null;
|
|
this._is_effect_running = false;
|
|
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._breezy_desktop_running_connection = null;
|
|
|
|
// "fresh" means the effect hasn't been enabled since breezy-desktop-running became true
|
|
this._fresh_session = true;
|
|
|
|
if (!Globals.logger) {
|
|
Globals.logger = new Logger({
|
|
title: 'breezydesktop',
|
|
debug: this.settings.get_boolean('debug')
|
|
});
|
|
Globals.logger.logVersion();
|
|
}
|
|
|
|
if (!Globals.data_stream) {
|
|
Globals.data_stream = new DeviceDataStream({
|
|
debug_no_device: this.settings.get_boolean('debug-no-device')
|
|
});
|
|
}
|
|
}
|
|
|
|
enable() {
|
|
Globals.logger.log_debug('BreezyDesktopExtension enable');
|
|
|
|
try {
|
|
Globals.extension_dir = this.path;
|
|
|
|
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.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('legacy-follow-mode', Globals.data_stream, 'legacy-follow-mode', 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)) {
|
|
this._cli_file = Gio.file_new_for_path(ALT_CLI_PATH);
|
|
if (!this._cli_file.query_exists(null)) {
|
|
this._cli_file = null;
|
|
Globals.logger.log('[ERROR] BreezyDesktopExtension enable - xr_driver_cli not found');
|
|
}
|
|
}
|
|
|
|
this._setup();
|
|
} catch (e) {
|
|
Globals.logger.log(`[ERROR] BreezyDesktopExtension enable ${e.message}\n${e.stack}`);
|
|
}
|
|
}
|
|
|
|
_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];
|
|
});
|
|
}
|
|
|
|
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');
|
|
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: is_dummy,
|
|
is_virtual: target_monitor.product === VIRTUAL_MONITOR_PRODUCT
|
|
};
|
|
}
|
|
|
|
Globals.logger.log_debug('BreezyDesktopExtension _find_supported_monitor - No supported monitor found');
|
|
return null;
|
|
} catch (e) {
|
|
Globals.logger.log(`[ERROR] BreezyDesktopExtension _find_supported_monitor ${e.message}\n${e.stack}`);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// 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
|
|
_target_monitor_ready(target_monitor) {
|
|
if (target_monitor.is_dummy) return true;
|
|
|
|
const needs_sbs_mode_switch = this.settings.get_boolean('fast-sbs-mode-switching') &&
|
|
this._needs_widescreen_monitor_update();
|
|
return !needs_sbs_mode_switch && !this._monitor_manager.needsOptimalModeCheck(target_monitor.connector);
|
|
}
|
|
|
|
// for_disable should be true if we're using this function to disable the
|
|
// effect without anticipating an immediate re-enable
|
|
_setup(for_disable = false) {
|
|
Globals.logger.log_debug('BreezyDesktopExtension _setup');
|
|
if (this._is_effect_running) {
|
|
Globals.logger.log('Reset triggered, disabling XR effect');
|
|
this._effect_disable(!for_disable);
|
|
}
|
|
|
|
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(this._target_monitor)) {
|
|
Globals.logger.log('Ready, enabling XR effect');
|
|
this._effect_enable();
|
|
} else {
|
|
Globals.logger.log_debug('BreezyDesktopExtension _setup - breezy desktop enabled, but async monitor action needed');
|
|
}
|
|
} else {
|
|
Globals.logger.log_debug('BreezyDesktopExtension _setup - Doing nothing, target monitor found, but device stream not being received');
|
|
}
|
|
} else {
|
|
Globals.logger.log_debug(`BreezyDesktopExtension _setup - Doing nothing, no supported monitor found, breezy_desktop_running: ${Globals.data_stream.breezy_desktop_running}`);
|
|
}
|
|
}
|
|
|
|
_needs_widescreen_monitor_update() {
|
|
Globals.logger.log_debug('BreezyDesktopExtension _needs_widescreen_monitor_update');
|
|
const state = this._read_state();
|
|
const sbs_enabled = state['sbs_mode_enabled'] === 'true';
|
|
const widescreen_setting_enabled = this.settings.get_boolean('widescreen-mode');
|
|
if (widescreen_setting_enabled !== sbs_enabled) {
|
|
Globals.logger.log_debug('BreezyDesktopExtension _needs_widescreen_monitor_update - true');
|
|
this._request_sbs_mode_change(widescreen_setting_enabled);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
_effect_enable() {
|
|
Globals.logger.log_debug('BreezyDesktopExtension _effect_enable');
|
|
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;
|
|
|
|
// 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);
|
|
|
|
Globals.data_stream.refresh_data();
|
|
this._virtual_displays_actor = new VirtualDisplaysActor({
|
|
width: targetMonitor.width,
|
|
height: targetMonitor.height,
|
|
target_monitor: targetMonitor,
|
|
virtual_monitors: virtualMonitors,
|
|
monitor_wrapping_scheme: this.settings.get_string('monitor-wrapping-scheme'),
|
|
monitor_spacing: this.settings.get_int('monitor-spacing'),
|
|
headset_display_as_viewport_center: this.settings.get_boolean('headset-display-as-viewport-center'),
|
|
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'),
|
|
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._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._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._fresh_session) this._recenter_display();
|
|
|
|
this._effect_settings_bindings = [
|
|
'monitor-wrapping-scheme',
|
|
'headset-display-as-viewport-center',
|
|
'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));
|
|
|
|
if (global.compositor?.disable_unredirect) {
|
|
global.compositor.disable_unredirect();
|
|
} else {
|
|
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._virtual_displays_actor._change_distance.bind(this._virtual_displays_actor));
|
|
this._add_settings_keybinding('toggle-follow-shortcut', this._toggle_follow_mode.bind(this));
|
|
this._add_settings_keybinding('cursor-to-focused-display-shortcut', this._cursor_to_focused_display.bind(this));
|
|
|
|
this._fresh_session = false;
|
|
} 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();
|
|
}
|
|
}
|
|
}
|
|
|
|
_add_settings_keybinding(settings_key, bind_to_function) {
|
|
try {
|
|
Main.wm.addKeybinding(
|
|
settings_key,
|
|
this.settings,
|
|
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
|
|
Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW | Shell.ActionMode.POPUP,
|
|
bind_to_function
|
|
);
|
|
|
|
// Connect to the 'changed' signal for the keybinding property
|
|
this.settings.connect(`changed::${settings_key}`, () => {
|
|
try {
|
|
// Remove the old keybinding
|
|
Main.wm.removeKeybinding(settings_key);
|
|
|
|
// Add the updated keybinding
|
|
Main.wm.addKeybinding(
|
|
settings_key,
|
|
this.settings,
|
|
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
|
|
Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW | Shell.ActionMode.POPUP,
|
|
bind_to_function
|
|
);
|
|
} catch (e) {
|
|
Globals.logger.log(`[ERROR] BreezyDesktopExtension _add_settings_keybinding settings binding lambda ${e.message}\n${e.stack}`);
|
|
}
|
|
});
|
|
} catch (e) {
|
|
Globals.logger.log(`[ERROR] BreezyDesktopExtension _add_settings_keybinding ${e.message}\n${e.stack}`);
|
|
}
|
|
}
|
|
|
|
_write_control(key, value) {
|
|
try {
|
|
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}`);
|
|
}
|
|
}
|
|
|
|
_read_state() {
|
|
const state = {};
|
|
try {
|
|
const file = Gio.file_new_for_path('/dev/shm/xr_driver_state');
|
|
if (file.query_exists(null)) {
|
|
const data = file.load_contents(null);
|
|
|
|
if (data[0]) {
|
|
const bytes = new Uint8Array(data[1]);
|
|
const decoder = new TextDecoder();
|
|
const contents = decoder.decode(bytes);
|
|
|
|
const lines = contents.split('\n');
|
|
for (const line of lines) {
|
|
const [k, v] = line.split('=');
|
|
state[k] = v;
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
Globals.logger.log(`[ERROR] BreezyDesktopExtension _read_state ${e.message}\n${e.stack}`);
|
|
}
|
|
return state;
|
|
}
|
|
|
|
_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) {
|
|
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) {
|
|
const value = settings.get_double('follow-threshold');
|
|
Globals.logger.log_debug(`BreezyDesktopExtension _update_follow_threshold ${value}`);
|
|
if (value !== undefined) this._write_control('breezy_desktop_follow_threshold', value);
|
|
}
|
|
|
|
// requests sbs_mode change and monitors to ensure the state reflects the setting
|
|
_request_sbs_mode_change(value) {
|
|
Globals.logger.log_debug(`BreezyDesktopExtension _request_sbs_mode_change ${value}`);
|
|
this._write_control('sbs_mode', value ? 'enable' : 'disable');
|
|
if (!this._sbs_mode_update_timeout) {
|
|
var attempts = 0;
|
|
this._sbs_mode_update_timeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 3000, (() => {
|
|
const state = this._read_state();
|
|
const sbs_enabled = state['sbs_mode_enabled'] === 'true';
|
|
if (sbs_enabled === value) {
|
|
Globals.logger.log_debug('BreezyDesktopExtension _request_sbs_mode_change - successfully updated');
|
|
this._sbs_mode_update_timeout = undefined;
|
|
|
|
if (this.settings.get_boolean('fast-sbs-mode-switching')) {
|
|
// setup was halted if this is enabled, so we have to re-trigger it now
|
|
this._setup();
|
|
}
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
}
|
|
|
|
if (attempts++ < 3) {
|
|
this._write_control('sbs_mode', value ? 'enable' : 'disable');
|
|
return GLib.SOURCE_CONTINUE;
|
|
}
|
|
|
|
// the state never updated to reflect our request, revert the setting
|
|
Globals.logger.log('Failed to update sbs_mode state, reverting setting');
|
|
this.settings.set_boolean('widescreen-mode', !value);
|
|
this._sbs_mode_update_timeout = undefined;
|
|
return GLib.SOURCE_REMOVE;
|
|
}).bind(this));
|
|
}
|
|
}
|
|
|
|
_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');
|
|
}
|
|
|
|
_update_widescreen_mode_from_state(effect, _pspec) {
|
|
// kill our state checker if it's running
|
|
if (this._sbs_mode_update_timeout) {
|
|
Globals.logger.log_debug('BreezyDesktopExtension _update_widescreen_mode_from_state - clearing timeout');
|
|
GLib.source_remove(this._sbs_mode_update_timeout);
|
|
this._sbs_mode_update_timeout = undefined;
|
|
}
|
|
|
|
const value = effect.widescreen_mode_state;
|
|
Globals.logger.log_debug(`BreezyDesktopExtension _update_widescreen_mode_from_state ${value}`);
|
|
if (value !== this.settings.get_boolean('widescreen-mode'))
|
|
this.settings.set_boolean('widescreen-mode', value);
|
|
else
|
|
Globals.logger.log_debug('settings.widescreen-mode already matched state');
|
|
}
|
|
|
|
_handle_monitor_change() {
|
|
Globals.logger.log('Monitor change detected');
|
|
this._setup();
|
|
}
|
|
|
|
_handle_breezy_desktop_running_change(datastream, _pspec) {
|
|
Globals.logger.log_debug(`BreezyDesktopExtension _handle_breezy_desktop_running_change ${datastream.breezy_desktop_running}`);
|
|
|
|
if (datastream.breezy_desktop_running !== this._is_effect_running) {
|
|
if (!datastream.breezy_desktop_running) Globals.logger.log('Breezy desktop disabled');
|
|
this._fresh_session = datastream.breezy_desktop_running;
|
|
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;
|
|
|
|
Globals.logger.log_debug('BreezyDesktopExtension _toggle_xr_effect');
|
|
|
|
let proc = Gio.Subprocess.new(
|
|
['bash', '-c', `${this._cli_file.get_path()} --external-mode`],
|
|
Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE
|
|
);
|
|
|
|
let [success, stdout, stderr] = proc.communicate_utf8(null, null);
|
|
if (!success || !!stderr || !stdout) {
|
|
Globals.logger.log(`[ERROR] Failed to get driver status: ${stderr}`);
|
|
return;
|
|
}
|
|
|
|
Globals.logger.log_debug(`BreezyDesktopExtension _toggle_xr_effect external_mode: ${stdout}`);
|
|
const enabled = stdout.trim() === 'breezy_desktop';
|
|
|
|
// use the CLI to change the external mode, avoid using disable/enable, otherwise the driver will
|
|
// shut down and recalibrate each time
|
|
proc = Gio.Subprocess.new(
|
|
['bash', '-c', `${this._cli_file.get_path()} --${enabled ? 'disable-external' : 'breezy-desktop'}`],
|
|
Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE
|
|
);
|
|
[success, stdout, stderr] = proc.communicate_utf8(null, null);
|
|
if (!success || !!stderr) {
|
|
Globals.logger.log(`[ERROR] Failed to toggle driver: ${stderr}`);
|
|
}
|
|
}
|
|
|
|
_recenter_display() {
|
|
Globals.logger.log_debug('BreezyDesktopExtension _recenter_display');
|
|
this._write_control('recenter_screen', 'true');
|
|
}
|
|
|
|
_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');
|
|
}
|
|
|
|
_cursor_to_focused_display() {
|
|
Globals.logger.log_debug('BreezyDesktopExtension _cursor_to_focused_display');
|
|
if (this._virtual_displays_actor?.focused_monitor_details) {
|
|
const monitorDetails = this._virtual_displays_actor.focused_monitor_details;
|
|
const xMid = monitorDetails.x + monitorDetails.width / 2;
|
|
const yMid = monitorDetails.y + monitorDetails.height / 2;
|
|
this._cursor_manager.moveCursorTo(xMid, yMid);
|
|
}
|
|
}
|
|
|
|
// for_setup should be true if our intention is to immediately re-enable the extension
|
|
_effect_disable(for_setup = false) {
|
|
try {
|
|
Globals.logger.log_debug('BreezyDesktopExtension _effect_disable');
|
|
this._is_effect_running = false;
|
|
|
|
if (Globals.data_stream.smooth_follow_enabled) this._toggle_follow_mode();
|
|
|
|
Main.wm.removeKeybinding('toggle-xr-effect-shortcut');
|
|
Main.wm.removeKeybinding('recenter-display-shortcut');
|
|
Main.wm.removeKeybinding('toggle-display-distance-shortcut');
|
|
Main.wm.removeKeybinding('toggle-follow-shortcut');
|
|
Main.wm.removeKeybinding('cursor-to-focused-display-shortcut');
|
|
|
|
if (global.compositor?.enable_unredirect) {
|
|
global.compositor.enable_unredirect();
|
|
} else {
|
|
Meta.enable_unredirect_for_display(global.display);
|
|
}
|
|
|
|
for (let settings_key of this._effect_settings_bindings) {
|
|
Gio.Settings.unbind(this.settings, settings_key);
|
|
}
|
|
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._virtual_displays_overlay) {
|
|
if (this._virtual_displays_actor) {
|
|
this._virtual_displays_overlay.set_child(null);
|
|
this._virtual_displays_actor.destroy();
|
|
this._virtual_displays_actor = 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;
|
|
}
|
|
|
|
// 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
|
|
if (!for_setup && this.settings.get_boolean('widescreen-mode')) {
|
|
Globals.logger.log('Disabling SBS mode due to disabling effect');
|
|
this._write_control('sbs_mode', 'disable');
|
|
}
|
|
|
|
if (!for_setup && this.settings.get_boolean('remove-virtual-displays-on-disable')) {
|
|
this._remove_virtual_displays();
|
|
}
|
|
} catch (e) {
|
|
Globals.logger.log(`[ERROR] BreezyDesktopExtension _effect_disable ${e.message}\n${e.stack}`);
|
|
}
|
|
}
|
|
|
|
_remove_virtual_displays() {
|
|
try {
|
|
GLib.spawn_command_line_sync(`pkill -f "/virtualdisplay( |$)"`);
|
|
} catch (e) {
|
|
Globals.logger.log(`[ERROR] BreezyDesktopExtension _remove_virtual_displays ${e.message}\n${e.stack}`);
|
|
}
|
|
}
|
|
|
|
disable() {
|
|
try {
|
|
Globals.logger.log_debug('BreezyDesktopExtension disable');
|
|
|
|
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;
|
|
}
|
|
} catch (e) {
|
|
Globals.logger.log(`[ERROR] BreezyDesktopExtension disable ${e.message}\n${e.stack}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function init() {
|
|
return new Extension();
|
|
}
|