Compare commits
25 Commits
main
...
v0.9-sbs-2
| Author | SHA1 | Date |
|---|---|---|
|
|
3a55e139fc | |
|
|
8bf7824243 | |
|
|
5192815098 | |
|
|
707710c4c7 | |
|
|
e54bb1770f | |
|
|
d4b403fff2 | |
|
|
8ffbe4cb87 | |
|
|
2d34eb2d17 | |
|
|
a2bbb66b54 | |
|
|
0ee2a5debb | |
|
|
d8c0921576 | |
|
|
3924600130 | |
|
|
a0f84554bb | |
|
|
d565547fb8 | |
|
|
abdee1e77e | |
|
|
7607de7d1a | |
|
|
87f6dd38c3 | |
|
|
033e6a01f5 | |
|
|
cbfd64e6d4 | |
|
|
c6a35e2a94 | |
|
|
bfcf0b497f | |
|
|
cddb2e051e | |
|
|
225adbd449 | |
|
|
fcf7dcba21 | |
|
|
8e7cbbd457 |
|
|
@ -5,6 +5,7 @@
|
||||||
[submodule "modules/XRLinuxDriver"]
|
[submodule "modules/XRLinuxDriver"]
|
||||||
path = modules/XRLinuxDriver
|
path = modules/XRLinuxDriver
|
||||||
url = https://github.com/wheaney/XRLinuxDriver.git
|
url = https://github.com/wheaney/XRLinuxDriver.git
|
||||||
|
branch = breezy_sbs
|
||||||
[submodule "modules/sombrero"]
|
[submodule "modules/sombrero"]
|
||||||
path = modules/sombrero
|
path = modules/sombrero
|
||||||
url = https://github.com/wheaney/sombrero.git
|
url = https://github.com/wheaney/sombrero.git
|
||||||
|
|
|
||||||
|
|
@ -18,21 +18,23 @@ tmp_dir=$(mktemp -d -t breezy-gnome-XXXXXXXXXX)
|
||||||
pushd $tmp_dir > /dev/null
|
pushd $tmp_dir > /dev/null
|
||||||
echo "Created temp directory: ${tmp_dir}"
|
echo "Created temp directory: ${tmp_dir}"
|
||||||
|
|
||||||
# if the first argument is "-v" then the second argument is metrics version, and the third argument is binary path
|
binary_download_url="https://github.com/wheaney/breezy-desktop/releases/latest/download/breezyGNOME.tar.gz"
|
||||||
# otherwise, if the first argument is present, it's the binary path
|
|
||||||
if [ "$1" = "-v" ]
|
if [ "$1" = "-v" ]
|
||||||
then
|
then
|
||||||
metrics_version="$2"
|
metrics_version="$2"
|
||||||
binary_path_arg="$3"
|
binary_path_arg="$3"
|
||||||
|
elif [ "$1" = "--tag" ] && [ -n "$2" ]
|
||||||
|
then
|
||||||
|
binary_download_url="https://github.com/wheaney/breezy-desktop/releases/download/$2/breezyGNOME.tar.gz"
|
||||||
else
|
else
|
||||||
binary_path_arg="$1"
|
binary_path_arg="$1"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$binary_path_arg" ]
|
if [ -z "$binary_path_arg" ]
|
||||||
then
|
then
|
||||||
# download and unzip the latest driver
|
# download and unzip the binary
|
||||||
echo "Downloading latest release to: ${tmp_dir}/breezyGNOME.tar.gz"
|
echo "Downloading to: ${tmp_dir}/breezyGNOME.tar.gz"
|
||||||
curl -L -O https://github.com/wheaney/breezy-desktop/releases/latest/download/breezyGNOME.tar.gz
|
curl -L -O $binary_download_url
|
||||||
else
|
else
|
||||||
if [[ "$binary_path_arg" = /* ]]; then
|
if [[ "$binary_path_arg" = /* ]]; then
|
||||||
abs_path="$binary_path_arg"
|
abs_path="$binary_path_arg"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
|
||||||
if [ -z "$XDG_DATA_HOME" ]; then
|
if [ -z "$XDG_DATA_HOME" ]; then
|
||||||
XDG_DATA_HOME="$USER_HOME/.local/share"
|
XDG_DATA_HOME="$USER_HOME/.local/share"
|
||||||
fi
|
fi
|
||||||
|
DATA_DIR="$XDG_DATA_HOME/breezy_gnome"
|
||||||
|
|
||||||
# if $XDG_DATA_HOME/gnome-shell/extensions/breezydesktop@xronlinux.com exists
|
# if $XDG_DATA_HOME/gnome-shell/extensions/breezydesktop@xronlinux.com exists
|
||||||
extension_path="$XDG_DATA_HOME/gnome-shell/extensions/breezydesktop@xronlinux.com"
|
extension_path="$XDG_DATA_HOME/gnome-shell/extensions/breezydesktop@xronlinux.com"
|
||||||
|
|
@ -18,3 +19,11 @@ fi
|
||||||
cp -rL $SCRIPT_DIR/../../src $extension_path
|
cp -rL $SCRIPT_DIR/../../src $extension_path
|
||||||
|
|
||||||
glib-compile-schemas $extension_path/schemas
|
glib-compile-schemas $extension_path/schemas
|
||||||
|
|
||||||
|
pushd $extension_path
|
||||||
|
GNOME_MANIFEST_LINE=$(find -L . -type f ! -name "*.compiled" -exec sha256sum {} \; | sort | sha256sum | sed 's/ .*//')
|
||||||
|
popd
|
||||||
|
|
||||||
|
pushd $DATA_DIR
|
||||||
|
echo -e "$GNOME_MANIFEST_LINE breezydesktop@xronlinux.com" > manifest
|
||||||
|
popd
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import Clutter from 'gi://Clutter';
|
import Clutter from 'gi://Clutter';
|
||||||
|
import GLib from 'gi://GLib';
|
||||||
import Meta from 'gi://Meta';
|
import Meta from 'gi://Meta';
|
||||||
import * as PointerWatcher from 'resource:///org/gnome/shell/ui/pointerWatcher.js';
|
import * as PointerWatcher from 'resource:///org/gnome/shell/ui/pointerWatcher.js';
|
||||||
import { MouseSpriteContent } from './cursor.js';
|
import { MouseSpriteContent } from './cursor.js';
|
||||||
|
|
@ -6,10 +7,9 @@ import Globals from './globals.js';
|
||||||
|
|
||||||
// Taken from https://github.com/jkitching/soft-brightness-plus
|
// Taken from https://github.com/jkitching/soft-brightness-plus
|
||||||
export class CursorManager {
|
export class CursorManager {
|
||||||
constructor(mainActor) {
|
constructor(mainActor, refreshRate) {
|
||||||
this._mainActor = mainActor;
|
this._mainActor = mainActor;
|
||||||
|
this._refreshRate = refreshRate;
|
||||||
this._changeHookFn = null;
|
|
||||||
|
|
||||||
// Set/destroyed by _enableCloningMouse/_disableCloningMouse
|
// Set/destroyed by _enableCloningMouse/_disableCloningMouse
|
||||||
this._cursorWantedVisible = null;
|
this._cursorWantedVisible = null;
|
||||||
|
|
@ -26,7 +26,7 @@ export class CursorManager {
|
||||||
this._cursorWatch = null;
|
this._cursorWatch = null;
|
||||||
this._cursorChangedConnection = null;
|
this._cursorChangedConnection = null;
|
||||||
this._cursorVisibilityChangedConnection = null;
|
this._cursorVisibilityChangedConnection = null;
|
||||||
this._cursorPositionInvalidatedConnection = null;
|
this._redraw_timeline = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
enable() {
|
enable() {
|
||||||
|
|
@ -129,16 +129,31 @@ export class CursorManager {
|
||||||
} else {
|
} else {
|
||||||
this._mainActor.add_actor(this._cursorActor);
|
this._mainActor.add_actor(this._cursorActor);
|
||||||
}
|
}
|
||||||
this._cursorChangedConnection = this._cursorTracker.connect('cursor-changed', this._updateMouseSprite.bind(this));
|
this._cursorChangedConnection = this._cursorTracker.connect('cursor-changed', this._queueSpriteUpdate.bind(this));
|
||||||
this._cursorVisibilityChangedConnection = this._cursorTracker.connect('visibility-changed', this._updateMouseSprite.bind(this));
|
this._cursorVisibilityChangedConnection = this._cursorTracker.connect('visibility-changed', this._queueVisibilityUpdate.bind(this));
|
||||||
this._cursorPositionInvalidatedConnection = this._cursorTracker.connect('position-invalidated', this._updateMouseSprite.bind(this));
|
|
||||||
|
|
||||||
const interval = 1000 / 250;
|
const interval = 1000.0 / this._refreshRate;
|
||||||
this._cursorWatch = this._cursorWatcher.addWatch(interval, this._updateMousePosition.bind(this));
|
this._redraw_timeline = Clutter.Timeline.new_for_actor(this._cursorActor, interval * 10);
|
||||||
|
this._redraw_timeline.connect('new-frame', (() => {
|
||||||
|
this.handleNewFrame();
|
||||||
|
}).bind(this));
|
||||||
|
|
||||||
|
// Some elements will occasionally appear above the cursor, so we periodically reset the actor stacking.
|
||||||
|
// This could theoretically be fixed "better" by attaching to all events that might affect actor ordering,
|
||||||
|
// but finding a comprehensive list is difficult and not future proof. So this ugly solution helps us
|
||||||
|
// catch everything.
|
||||||
|
this._redraw_timeline.connect('completed', (() => {
|
||||||
|
this._periodicReset();
|
||||||
|
}).bind(this));
|
||||||
|
|
||||||
|
this._redraw_timeline.set_repeat_count(-1);
|
||||||
|
this._redraw_timeline.start();
|
||||||
|
|
||||||
|
this._cursorWatch = this._cursorWatcher.addWatch(interval, this._queuePositionUpdate.bind(this));
|
||||||
|
|
||||||
const [x, y] = global.get_pointer();
|
const [x, y] = global.get_pointer();
|
||||||
this._updateMousePosition(x, y);
|
this._queuePositionUpdate(x, y);
|
||||||
this._updateMouseSprite();
|
this._queueSpriteUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._cursorTracker.set_keep_focus_while_hidden) {
|
if (this._cursorTracker.set_keep_focus_while_hidden) {
|
||||||
|
|
@ -164,24 +179,26 @@ export class CursorManager {
|
||||||
this._cursorWatch.remove();
|
this._cursorWatch.remove();
|
||||||
this._cursorWatch = null;
|
this._cursorWatch = null;
|
||||||
|
|
||||||
|
if (this._cursorChangedConnection) {
|
||||||
this._cursorTracker.disconnect(this._cursorChangedConnection);
|
this._cursorTracker.disconnect(this._cursorChangedConnection);
|
||||||
this._cursorChangedConnection = null;
|
this._cursorChangedConnection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._cursorVisibilityChangedConnection) {
|
||||||
this._cursorTracker.disconnect(this._cursorVisibilityChangedConnection);
|
this._cursorTracker.disconnect(this._cursorVisibilityChangedConnection);
|
||||||
this._cursorVisibilityChangedConnection = null;
|
this._cursorVisibilityChangedConnection = null;
|
||||||
|
}
|
||||||
this._cursorTracker.disconnect(this._cursorPositionInvalidatedConnection);
|
|
||||||
this._cursorPositionInvalidatedConnection = null;
|
|
||||||
|
|
||||||
if (Clutter.Container === undefined) {
|
if (Clutter.Container === undefined) {
|
||||||
this._mainActor.remove_child(this._cursorActor);
|
this._mainActor.remove_child(this._cursorActor);
|
||||||
} else {
|
} else {
|
||||||
this._mainActor.remove_actor(this._cursorActor);
|
this._mainActor.remove_actor(this._cursorActor);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (this._cursorTracker.set_keep_focus_while_hidden) {
|
if (this._redraw_timeline) {
|
||||||
this._cursorTracker.set_keep_focus_while_hidden(false);
|
this._redraw_timeline.stop();
|
||||||
|
this._redraw_timeline = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._cursorUnfocusInhibited) {
|
if (this._cursorUnfocusInhibited) {
|
||||||
|
|
@ -191,11 +208,36 @@ export class CursorManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateMousePosition(x, y) {
|
_queuePositionUpdate(x, y) {
|
||||||
this._cursorActor.set_position(x, y);
|
this._queued_cursor_position = [x, y];
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateMouseSprite() {
|
_queueSpriteUpdate() {
|
||||||
|
this._queued_sprite_update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_queueVisibilityUpdate() {
|
||||||
|
this._queued_visibility_update = true;
|
||||||
|
this._cursorTrackerSetPointerVisibleBound(false);
|
||||||
|
this._queueSpriteUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// updates the stacking and other attributes that are hard to track and may periodically get out of sync
|
||||||
|
_periodicReset() {
|
||||||
|
this._queue_reset = true;
|
||||||
|
this._queueVisibilityUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNewFrame() {
|
||||||
|
let redraw = false;
|
||||||
|
if (this._queued_cursor_position) {
|
||||||
|
const [x, y] = this._queued_cursor_position;
|
||||||
|
this._cursorActor.set_position(x, y);
|
||||||
|
this._queued_cursor_position = null;
|
||||||
|
redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._queued_sprite_update) {
|
||||||
const sprite = this._cursorTracker.get_sprite();
|
const sprite = this._cursorTracker.get_sprite();
|
||||||
if (sprite) {
|
if (sprite) {
|
||||||
this._cursorSprite.content.texture = sprite;
|
this._cursorSprite.content.texture = sprite;
|
||||||
|
|
@ -209,13 +251,28 @@ export class CursorManager {
|
||||||
translation_x: -xHot,
|
translation_x: -xHot,
|
||||||
translation_y: -yHot,
|
translation_y: -yHot,
|
||||||
});
|
});
|
||||||
|
this._queued_sprite_update = false;
|
||||||
|
redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._queued_visibility_update) {
|
||||||
|
this._queued_visibility_update = false;
|
||||||
|
redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._queue_reset) {
|
||||||
|
if (this._mainActor.get_last_child() !== this._cursorActor)
|
||||||
this._mainActor.set_child_above_sibling(this._cursorActor, null);
|
this._mainActor.set_child_above_sibling(this._cursorActor, null);
|
||||||
this._cursorTrackerSetPointerVisibleBound(false);
|
|
||||||
|
|
||||||
// some other processes are uninhibiting when they shouldn't, so we need to re-inhibit here
|
// some other processes are uninhibiting when they shouldn't, so we need to re-inhibit here
|
||||||
if (!this._cursorSeat.is_unfocus_inhibited() && this._cursorUnfocusInhibited) {
|
if (!this._cursorSeat.is_unfocus_inhibited() && this._cursorUnfocusInhibited) {
|
||||||
Globals.logger.log_debug('reinhibiting');
|
Globals.logger.log_debug('reinhibiting');
|
||||||
this._cursorSeat.inhibit_unfocus();
|
this._cursorSeat.inhibit_unfocus();
|
||||||
}
|
}
|
||||||
|
this._queue_reset = false;
|
||||||
|
redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return redraw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,7 +8,8 @@ import St from 'gi://St';
|
||||||
import { CursorManager } from './cursormanager.js';
|
import { CursorManager } from './cursormanager.js';
|
||||||
import Globals from './globals.js';
|
import Globals from './globals.js';
|
||||||
import { Logger } from './logger.js';
|
import { Logger } from './logger.js';
|
||||||
import MonitorManager from './monitormanager.js';
|
import { MonitorManager } from './monitormanager.js';
|
||||||
|
import { isValidKeepAlive } from './time.js';
|
||||||
import { IPC_FILE_PATH, XREffect } from './xrEffect.js';
|
import { IPC_FILE_PATH, XREffect } from './xrEffect.js';
|
||||||
|
|
||||||
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
|
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||||
|
|
@ -40,8 +41,17 @@ export default class BreezyDesktopExtension extends Extension {
|
||||||
this._distance_binding = null;
|
this._distance_binding = null;
|
||||||
this._distance_connection = null;
|
this._distance_connection = null;
|
||||||
this._follow_threshold_connection = null;
|
this._follow_threshold_connection = null;
|
||||||
|
this._widescreen_mode_settings_connection = null;
|
||||||
|
this._widescreen_mode_effect_state_connection = null;
|
||||||
|
this._supported_device_detected_connected = null;
|
||||||
this._start_binding = null;
|
this._start_binding = null;
|
||||||
this._end_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;
|
||||||
|
|
||||||
if (!Globals.logger) {
|
if (!Globals.logger) {
|
||||||
Globals.logger = new Logger({
|
Globals.logger = new Logger({
|
||||||
|
|
@ -59,13 +69,22 @@ export default class BreezyDesktopExtension extends Extension {
|
||||||
Globals.extension_dir = this.path;
|
Globals.extension_dir = this.path;
|
||||||
this.settings.bind('debug', Globals.logger, 'debug', Gio.SettingsBindFlags.DEFAULT);
|
this.settings.bind('debug', Globals.logger, 'debug', Gio.SettingsBindFlags.DEFAULT);
|
||||||
|
|
||||||
this._monitor_manager = new MonitorManager(this.path);
|
this._monitor_manager = new MonitorManager({
|
||||||
this._monitor_manager.setChangeHook(this._setup.bind(this));
|
use_optimal_monitor_config: this.settings.get_boolean('use-optimal-monitor-config'),
|
||||||
|
headset_as_primary: this.settings.get_boolean('headset-as-primary'),
|
||||||
|
extension_path: this.path
|
||||||
|
});
|
||||||
|
this._monitor_manager.setChangeHook(this._handle_monitor_change.bind(this));
|
||||||
this._monitor_manager.enable();
|
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._setup();
|
this._setup();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Globals.logger.log(`ERROR: BreezyDesktopExtension enable ${e.message}`, e.stack);
|
Globals.logger.log(`ERROR: BreezyDesktopExtension enable ${e.message}\n${e.stack}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,12 +93,20 @@ export default class BreezyDesktopExtension extends Extension {
|
||||||
var target_monitor = this._target_monitor;
|
var target_monitor = this._target_monitor;
|
||||||
var is_effect_running = this._is_effect_running;
|
var is_effect_running = this._is_effect_running;
|
||||||
this._running_poller_id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, (() => {
|
this._running_poller_id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, (() => {
|
||||||
if (is_effect_running) return GLib.SOURCE_REMOVE;
|
if (is_effect_running) {
|
||||||
|
this._running_poller_id = undefined;
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
const is_driver_running = this._check_driver_running();
|
const is_driver_running = this._check_driver_running();
|
||||||
if (is_driver_running && target_monitor) {
|
if (is_driver_running && target_monitor) {
|
||||||
|
// don't enable the effect yet if an async optimal mode check is needed,
|
||||||
|
// _setup will be triggered after a mode change so we can remove this timeout_add
|
||||||
|
if (target_monitor.is_dummy || !this._monitor_manager.checkOptimalMode(target_monitor.connector)) {
|
||||||
Globals.logger.log('Driver is running, supported monitor connected. Enabling XR effect.');
|
Globals.logger.log('Driver is running, supported monitor connected. Enabling XR effect.');
|
||||||
this._effect_enable();
|
this._effect_enable();
|
||||||
|
}
|
||||||
|
this._running_poller_id = undefined;
|
||||||
return GLib.SOURCE_REMOVE;
|
return GLib.SOURCE_REMOVE;
|
||||||
} else {
|
} else {
|
||||||
return GLib.SOURCE_CONTINUE;
|
return GLib.SOURCE_CONTINUE;
|
||||||
|
|
@ -93,23 +120,29 @@ export default class BreezyDesktopExtension extends Extension {
|
||||||
const target_monitor = this._monitor_manager.getMonitorPropertiesList()?.find(
|
const target_monitor = this._monitor_manager.getMonitorPropertiesList()?.find(
|
||||||
monitor => SUPPORTED_MONITOR_PRODUCTS.includes(monitor.product));
|
monitor => SUPPORTED_MONITOR_PRODUCTS.includes(monitor.product));
|
||||||
if (target_monitor !== undefined) {
|
if (target_monitor !== undefined) {
|
||||||
|
Globals.logger.log_debug(`BreezyDesktopExtension _find_supported_monitor - Identified supported monitor: ${target_monitor.connector}`);
|
||||||
return {
|
return {
|
||||||
monitor: this._monitor_manager.getMonitors()[target_monitor.index],
|
monitor: this._monitor_manager.getMonitors()[target_monitor.index],
|
||||||
refreshRate: target_monitor.refreshRate,
|
connector: target_monitor.connector,
|
||||||
|
refreshRate: target_monitor.refreshRate
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.settings.get_boolean('developer-mode')) {
|
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
|
// allow testing XR devices with just USB, no video needed
|
||||||
return {
|
return {
|
||||||
monitor: this._monitor_manager.getMonitors()[0],
|
monitor: this._monitor_manager.getMonitors()[0],
|
||||||
|
connector: 'dummy',
|
||||||
refreshRate: 60,
|
refreshRate: 60,
|
||||||
|
is_dummy: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Globals.logger.log_debug('BreezyDesktopExtension _find_supported_monitor - No supported monitor found');
|
||||||
return null;
|
return null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Globals.logger.log(`ERROR: BreezyDesktopExtension _find_supported_monitor ${e.message}`, e.stack);
|
Globals.logger.log(`ERROR: BreezyDesktopExtension _find_supported_monitor ${e.message}\n${e.stack}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,33 +150,52 @@ export default class BreezyDesktopExtension extends Extension {
|
||||||
_setup() {
|
_setup() {
|
||||||
Globals.logger.log_debug('BreezyDesktopExtension _setup');
|
Globals.logger.log_debug('BreezyDesktopExtension _setup');
|
||||||
if (this._is_effect_running) {
|
if (this._is_effect_running) {
|
||||||
Globals.logger.log('Monitors changed, disabling XR effect');
|
Globals.logger.log('Reset triggered, disabling XR effect');
|
||||||
this._effect_disable();
|
this._effect_disable(true);
|
||||||
}
|
}
|
||||||
const target_monitor = this._find_supported_monitor();
|
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 isn't set, do nothing and wait for MonitorManager to call this again
|
||||||
if (target_monitor && this._running_poller_id === undefined) {
|
if (target_monitor && this._running_poller_id === undefined) {
|
||||||
this._target_monitor = target_monitor.monitor;
|
this._target_monitor = target_monitor;
|
||||||
this._refresh_rate = target_monitor.refreshRate;
|
|
||||||
|
|
||||||
if (this._check_driver_running()) {
|
if (this._check_driver_running()) {
|
||||||
|
// don't enable the effect yet if an async optimal mode check is needed,
|
||||||
|
// _setup will be triggered again after a mode change
|
||||||
|
if (target_monitor.is_dummy || !this._monitor_manager.checkOptimalMode(target_monitor.connector)) {
|
||||||
Globals.logger.log('Ready, enabling XR effect');
|
Globals.logger.log('Ready, enabling XR effect');
|
||||||
this._effect_enable();
|
this._effect_enable();
|
||||||
} else {
|
} else {
|
||||||
|
Globals.logger.log_debug('BreezyDesktopExtension _setup - driver running but optimal mode check needed');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Globals.logger.log_debug('BreezyDesktopExtension _setup - driver no running, starting poller');
|
||||||
this._poll_for_ready();
|
this._poll_for_ready();
|
||||||
}
|
}
|
||||||
|
} 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`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_check_driver_running() {
|
_check_driver_running() {
|
||||||
try {
|
try {
|
||||||
if (!Globals.ipc_file) Globals.ipc_file = Gio.file_new_for_path(IPC_FILE_PATH);
|
if (!Globals.ipc_file) Globals.ipc_file = Gio.file_new_for_path(IPC_FILE_PATH);
|
||||||
return Globals.ipc_file.query_exists(null);
|
if (Globals.ipc_file.query_exists(null)) {
|
||||||
} catch (e) {
|
const file_info = Globals.ipc_file.query_info(Gio.FILE_ATTRIBUTE_TIME_MODIFIED, Gio.FileQueryInfoFlags.NONE, null);
|
||||||
Globals.logger.log(`ERROR: BreezyDesktopExtension _check_driver_running ${e.message}`, e.stack);
|
const file_modified_time = file_info.get_attribute_uint64(Gio.FILE_ATTRIBUTE_TIME_MODIFIED);
|
||||||
return false;
|
|
||||||
|
// when the driver is running, the IMU file is updated at least 60x per second, do a strict check
|
||||||
|
return isValidKeepAlive(file_modified_time, true);
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Globals.logger.log(`ERROR: BreezyDesktopExtension _check_driver_running ${e.message}\n${e.stack}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_effect_enable() {
|
_effect_enable() {
|
||||||
|
|
@ -153,18 +205,20 @@ export default class BreezyDesktopExtension extends Extension {
|
||||||
this._is_effect_running = true;
|
this._is_effect_running = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._cursor_manager = new CursorManager(Main.layoutManager.uiGroup);
|
const targetMonitor = this._target_monitor.monitor;
|
||||||
|
const refreshRate = targetMonitor.refreshRate ?? 60;
|
||||||
|
this._cursor_manager = new CursorManager(Main.layoutManager.uiGroup, refreshRate);
|
||||||
this._cursor_manager.enable();
|
this._cursor_manager.enable();
|
||||||
|
|
||||||
this._overlay = new St.Bin({ style: 'background-color: rgba(0, 0, 0, 1);'});
|
this._overlay = new St.Bin();
|
||||||
this._overlay.opacity = 255;
|
this._overlay.opacity = 255;
|
||||||
this._overlay.set_position(this._target_monitor.x, this._target_monitor.y);
|
this._overlay.set_position(targetMonitor.x, targetMonitor.y);
|
||||||
this._overlay.set_size(this._target_monitor.width, this._target_monitor.height);
|
this._overlay.set_size(targetMonitor.width, targetMonitor.height);
|
||||||
|
|
||||||
const overlayContent = new Clutter.Actor({clip_to_allocation: true});
|
const overlayContent = new Clutter.Actor({clip_to_allocation: true});
|
||||||
const uiClone = new Clutter.Clone({ source: Main.layoutManager.uiGroup, clip_to_allocation: true });
|
const uiClone = new Clutter.Clone({ source: Main.layoutManager.uiGroup, clip_to_allocation: true });
|
||||||
uiClone.x = -this._target_monitor.x;
|
uiClone.x = -targetMonitor.x;
|
||||||
uiClone.y = -this._target_monitor.y;
|
uiClone.y = -targetMonitor.y;
|
||||||
if (Clutter.Container === undefined) {
|
if (Clutter.Container === undefined) {
|
||||||
overlayContent.add_child(uiClone);
|
overlayContent.add_child(uiClone);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -177,21 +231,31 @@ export default class BreezyDesktopExtension extends Extension {
|
||||||
Shell.util_set_hidden_from_pick(this._overlay, true);
|
Shell.util_set_hidden_from_pick(this._overlay, true);
|
||||||
|
|
||||||
this._xr_effect = new XREffect({
|
this._xr_effect = new XREffect({
|
||||||
target_monitor: this._target_monitor,
|
target_monitor: targetMonitor,
|
||||||
target_framerate: this._refresh_rate ?? 60,
|
target_framerate: refreshRate,
|
||||||
display_distance: this.settings.get_double('display-distance'),
|
display_distance: this.settings.get_double('display-distance'),
|
||||||
toggle_display_distance_start: this.settings.get_double('toggle-display-distance-start'),
|
toggle_display_distance_start: this.settings.get_double('toggle-display-distance-start'),
|
||||||
toggle_display_distance_end: this.settings.get_double('toggle-display-distance-end'),
|
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')
|
||||||
});
|
});
|
||||||
|
|
||||||
this._update_display_distance(this.settings);
|
|
||||||
this._update_follow_threshold(this.settings);
|
this._update_follow_threshold(this.settings);
|
||||||
|
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_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._distance_connection = this.settings.connect('changed::display-distance', this._update_display_distance.bind(this))
|
||||||
this._follow_threshold_connection = this.settings.connect('changed::follow-threshold', this._update_follow_threshold.bind(this))
|
this._follow_threshold_connection = this.settings.connect('changed::follow-threshold', this._update_follow_threshold.bind(this))
|
||||||
|
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._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._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._overlay.add_effect_with_name('xr-desktop', this._xr_effect);
|
this._overlay.add_effect_with_name('xr-desktop', this._xr_effect);
|
||||||
Meta.disable_unredirect_for_display(global.display);
|
Meta.disable_unredirect_for_display(global.display);
|
||||||
|
|
@ -200,7 +264,7 @@ export default class BreezyDesktopExtension extends Extension {
|
||||||
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._xr_effect._change_distance.bind(this._xr_effect));
|
||||||
this._add_settings_keybinding('toggle-follow-shortcut', this._toggle_follow_mode.bind(this));
|
this._add_settings_keybinding('toggle-follow-shortcut', this._toggle_follow_mode.bind(this));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Globals.logger.log(`ERROR: BreezyDesktopExtension _effect_enable ${e.message}`, e.stack);
|
Globals.logger.log(`ERROR: BreezyDesktopExtension _effect_enable ${e.message}\n${e.stack}`);
|
||||||
this._effect_disable();
|
this._effect_disable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -231,11 +295,11 @@ export default class BreezyDesktopExtension extends Extension {
|
||||||
bind_to_function
|
bind_to_function
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Globals.logger.log(`ERROR: BreezyDesktopExtension _add_settings_keybinding settings binding lambda ${e.message}`, e.stack);
|
Globals.logger.log(`ERROR: BreezyDesktopExtension _add_settings_keybinding settings binding lambda ${e.message}\n${e.stack}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Globals.logger.log(`ERROR: BreezyDesktopExtension _add_settings_keybinding ${e.message}`, e.stack);
|
Globals.logger.log(`ERROR: BreezyDesktopExtension _add_settings_keybinding ${e.message}\n${e.stack}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -258,6 +322,40 @@ export default class BreezyDesktopExtension extends Extension {
|
||||||
if (value !== undefined) this._write_control('breezy_desktop_follow_threshold', value);
|
if (value !== undefined) this._write_control('breezy_desktop_follow_threshold', value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_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._write_control('sbs_mode', value ? 'enable' : 'disable');
|
||||||
|
else
|
||||||
|
Globals.logger.log_debug('effect.widescreen_mode_state already matched setting');
|
||||||
|
}
|
||||||
|
|
||||||
|
_update_widescreen_mode_from_state(effect, _pspec) {
|
||||||
|
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_supported_device_change(effect, _pspec) {
|
||||||
|
const value = effect.supported_device_detected;
|
||||||
|
Globals.logger.log_debug(`BreezyDesktopExtension _handle_supported_device_change ${value}`);
|
||||||
|
|
||||||
|
// this will disable the effect and begin polling for a ready state again
|
||||||
|
if (!value && this._is_effect_running) {
|
||||||
|
Globals.logger.log('Supported device disconnected');
|
||||||
|
this._setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_recenter_display() {
|
_recenter_display() {
|
||||||
Globals.logger.log_debug('BreezyDesktopExtension _recenter_display');
|
Globals.logger.log_debug('BreezyDesktopExtension _recenter_display');
|
||||||
this._write_control('recenter_screen', 'true');
|
this._write_control('recenter_screen', 'true');
|
||||||
|
|
@ -268,12 +366,16 @@ export default class BreezyDesktopExtension extends Extension {
|
||||||
this._write_control('toggle_breezy_desktop_smooth_follow', 'true');
|
this._write_control('toggle_breezy_desktop_smooth_follow', 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
_effect_disable() {
|
// for_setup should be true if our intention is to immediately re-enable the extension
|
||||||
|
_effect_disable(for_setup = false) {
|
||||||
try {
|
try {
|
||||||
Globals.logger.log_debug('BreezyDesktopExtension _effect_disable');
|
Globals.logger.log_debug('BreezyDesktopExtension _effect_disable');
|
||||||
this._is_effect_running = false;
|
this._is_effect_running = false;
|
||||||
|
|
||||||
if (this._running_poller_id) GLib.source_remove(this._running_poller_id);
|
if (this._running_poller_id) {
|
||||||
|
GLib.source_remove(this._running_poller_id);
|
||||||
|
this._running_poller_id = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
Main.wm.removeKeybinding('recenter-display-shortcut');
|
Main.wm.removeKeybinding('recenter-display-shortcut');
|
||||||
Main.wm.removeKeybinding('toggle-display-distance-shortcut');
|
Main.wm.removeKeybinding('toggle-display-distance-shortcut');
|
||||||
|
|
@ -286,7 +388,6 @@ export default class BreezyDesktopExtension extends Extension {
|
||||||
this._overlay.destroy();
|
this._overlay.destroy();
|
||||||
this._overlay = null;
|
this._overlay = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._distance_binding) {
|
if (this._distance_binding) {
|
||||||
this.settings.unbind(this._distance_binding);
|
this.settings.unbind(this._distance_binding);
|
||||||
this._distance_binding = null;
|
this._distance_binding = null;
|
||||||
|
|
@ -299,6 +400,10 @@ export default class BreezyDesktopExtension extends Extension {
|
||||||
this.settings.disconnect(this._follow_threshold_connection);
|
this.settings.disconnect(this._follow_threshold_connection);
|
||||||
this._follow_threshold_connection = null;
|
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) {
|
if (this._start_binding) {
|
||||||
this.settings.unbind(this._start_binding);
|
this.settings.unbind(this._start_binding);
|
||||||
this._start_binding = null;
|
this._start_binding = null;
|
||||||
|
|
@ -307,17 +412,47 @@ export default class BreezyDesktopExtension extends Extension {
|
||||||
this.settings.unbind(this._end_binding);
|
this.settings.unbind(this._end_binding);
|
||||||
this._end_binding = null;
|
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._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._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.cleanup();
|
||||||
this._xr_effect = null;
|
this._xr_effect = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._cursor_manager) {
|
if (this._cursor_manager) {
|
||||||
this._cursor_manager.disable();
|
this._cursor_manager.disable();
|
||||||
this._cursor_manager = null;
|
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');
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Globals.logger.log(`ERROR: BreezyDesktopExtension _effect_disable ${e.message}`, e.stack);
|
Globals.logger.log(`ERROR: BreezyDesktopExtension _effect_disable ${e.message}\n${e.stack}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -327,11 +462,20 @@ export default class BreezyDesktopExtension extends Extension {
|
||||||
this._effect_disable();
|
this._effect_disable();
|
||||||
this._target_monitor = null;
|
this._target_monitor = null;
|
||||||
if (this._monitor_manager) {
|
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._monitor_manager.disable();
|
this._monitor_manager.disable();
|
||||||
this._monitor_manager = null;
|
this._monitor_manager = null;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Globals.logger.log(`ERROR: BreezyDesktopExtension disable ${e.message}`, e.stack);
|
Globals.logger.log(`ERROR: BreezyDesktopExtension disable ${e.message}\n${e.stack}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ export function dataViewBigUint(dataView, dataViewInfo) {
|
||||||
return Number(dataView.getBigUint64(dataViewInfo[DATA_VIEW_INFO_OFFSET_INDEX], true));
|
return Number(dataView.getBigUint64(dataViewInfo[DATA_VIEW_INFO_OFFSET_INDEX], true));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dataViewUintArray(dataView, dataViewInfo) {
|
export function dataViewUint32Array(dataView, dataViewInfo) {
|
||||||
const uintArray = []
|
const uintArray = []
|
||||||
let offset = dataViewInfo[DATA_VIEW_INFO_OFFSET_INDEX];
|
let offset = dataViewInfo[DATA_VIEW_INFO_OFFSET_INDEX];
|
||||||
for (let i = 0; i < dataViewInfo[DATA_VIEW_INFO_COUNT_INDEX]; i++) {
|
for (let i = 0; i < dataViewInfo[DATA_VIEW_INFO_COUNT_INDEX]; i++) {
|
||||||
|
|
@ -34,6 +34,16 @@ export function dataViewUintArray(dataView, dataViewInfo) {
|
||||||
return uintArray;
|
return uintArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function dataViewUint8Array(dataView, dataViewInfo) {
|
||||||
|
const uintArray = []
|
||||||
|
let offset = dataViewInfo[DATA_VIEW_INFO_OFFSET_INDEX];
|
||||||
|
for (let i = 0; i < dataViewInfo[DATA_VIEW_INFO_SIZE_INDEX] * dataViewInfo[DATA_VIEW_INFO_COUNT_INDEX]; i++) {
|
||||||
|
uintArray.push(dataView.getUint8(offset));
|
||||||
|
offset += UINT8_SIZE;
|
||||||
|
}
|
||||||
|
return uintArray;
|
||||||
|
}
|
||||||
|
|
||||||
export function dataViewFloat(dataView, dataViewInfo) {
|
export function dataViewFloat(dataView, dataViewInfo) {
|
||||||
return dataView.getFloat32(dataViewInfo[DATA_VIEW_INFO_OFFSET_INDEX], true);
|
return dataView.getFloat32(dataViewInfo[DATA_VIEW_INFO_OFFSET_INDEX], true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import Gio from 'gi://Gio';
|
import Gio from 'gi://Gio';
|
||||||
|
import GObject from 'gi://GObject';
|
||||||
|
|
||||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||||
|
|
||||||
|
|
@ -52,10 +53,10 @@ export function newDisplayConfig(extPath, callback) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMonitorConfig(displayConfigProxy, callback) {
|
function getMonitorConfig(displayConfigProxy, callback) {
|
||||||
displayConfigProxy.GetResourcesRemote((result) => {
|
displayConfigProxy.GetResourcesRemote((result, error) => {
|
||||||
if (result.length <= 2) {
|
if (error) {
|
||||||
callback(null, 'Cannot get DisplayConfig: No outputs in GetResources()');
|
callback(null, `GetResourcesRemote failed: ${error}`);
|
||||||
} else {
|
} else {
|
||||||
const monitors = [];
|
const monitors = [];
|
||||||
for (let i = 0; i < result[2].length; i++) {
|
for (let i = 0; i < result[2].length; i++) {
|
||||||
|
|
@ -84,32 +85,163 @@ export function getMonitorConfig(displayConfigProxy, callback) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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, callback) {
|
||||||
|
Globals.logger.log_debug(`monitormanager.js performOptimalModeCheck for ${connectorName}`);
|
||||||
|
displayConfigProxy.GetCurrentStateRemote((result, error) => {
|
||||||
|
if (error) {
|
||||||
|
callback(null, `GetCurrentState failed: ${error}`);
|
||||||
|
} else {
|
||||||
|
Globals.logger.log_debug(`monitormanager.js performOptimalModeCheck GetCurrentState result: ${JSON.stringify(result)}`);
|
||||||
|
const [serial, monitors, logicalMonitors, properties] = result;
|
||||||
|
|
||||||
|
// iterate over all monitors at least once, collecting the best fit mode for our monitor, and mode information
|
||||||
|
// for each monitor
|
||||||
|
let ourMonitor = undefined;
|
||||||
|
let monitorToModeIdMap = {};
|
||||||
|
let bestFitMode = undefined;
|
||||||
|
for (let monitor of monitors) {
|
||||||
|
const [details, modes, monProperties] = monitor;
|
||||||
|
const [connector, vendor, product, monitorSerial] = details;
|
||||||
|
const isOurMonitor = connector == connectorName;
|
||||||
|
if (isOurMonitor) ourMonitor = monitor;
|
||||||
|
|
||||||
|
for (let mode of modes) {
|
||||||
|
const [modeId, width, height, refreshRate, preferredScale, supportedScales, modeProperites] = mode;
|
||||||
|
const isCurrent = !!modeProperites['is-current'];
|
||||||
|
if (isCurrent) monitorToModeIdMap[connector] = modeId;
|
||||||
|
|
||||||
|
if (isOurMonitor && (!bestFitMode || (
|
||||||
|
width >= bestFitMode.width &&
|
||||||
|
height >= bestFitMode.height &&
|
||||||
|
refreshRate >= bestFitMode.refreshRate))) {
|
||||||
|
// find the scale that is closest to 1.0
|
||||||
|
const bestScale = supportedScales.reduce((prev, curr) => {
|
||||||
|
return Math.abs(curr - 1.0) < Math.abs(prev - 1.0) ? curr : prev;
|
||||||
|
});
|
||||||
|
|
||||||
|
bestFitMode = {
|
||||||
|
modeId,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
refreshRate,
|
||||||
|
bestScale,
|
||||||
|
isCurrent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!ourMonitor) {
|
||||||
|
let anyMonitorsChanged = false;
|
||||||
|
if (!!bestFitMode) {
|
||||||
|
// map from original logical monitors schema to a(iiduba(ssa{sv})) for ApplyMonitorsConfig call
|
||||||
|
const updatedLogicalMonitors = logicalMonitors.map((logicalMonitor) => {
|
||||||
|
const [x, y, scale, transform, primary, monitors, logMonProperties] = logicalMonitor;
|
||||||
|
const hasOurMonitor = !!monitors.some((monitor) => monitor[0] === connectorName);
|
||||||
|
anyMonitorsChanged |= hasOurMonitor && bestFitMode.bestScale !== scale;
|
||||||
|
|
||||||
|
// there can only be one primary monitor, so we need to set all other monitors to not primary and glasses to primary,
|
||||||
|
// if headsetAsPrimary is true
|
||||||
|
anyMonitorsChanged |= headsetAsPrimary && ((hasOurMonitor && !primary) || (!hasOurMonitor && primary));
|
||||||
|
return [
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
hasOurMonitor ? bestFitMode.bestScale : scale,
|
||||||
|
transform,
|
||||||
|
headsetAsPrimary ? hasOurMonitor : primary,
|
||||||
|
monitors.map((monitor) => {
|
||||||
|
const monitorConnector = monitor[0];
|
||||||
|
const isOurMonitor = monitorConnector === connectorName;
|
||||||
|
anyMonitorsChanged |= isOurMonitor && !bestFitMode.isCurrent;
|
||||||
|
return [
|
||||||
|
monitorConnector,
|
||||||
|
isOurMonitor ? bestFitMode.modeId : monitorToModeIdMap[monitorConnector],
|
||||||
|
{} // properties
|
||||||
|
];
|
||||||
|
})
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
// if our monitor is already properly configured, we can skip the ApplyMonitorsConfig call
|
||||||
|
if (anyMonitorsChanged) {
|
||||||
|
Globals.logger.log_debug(`monitormanager.js performOptimalModeCheck updatedLogicalMonitors: ${JSON.stringify(updatedLogicalMonitors)}`);
|
||||||
|
displayConfigProxy.ApplyMonitorsConfigRemote(
|
||||||
|
serial,
|
||||||
|
1, // "temporary" config -- "permanent" might be pointless since we always do this check
|
||||||
|
updatedLogicalMonitors,
|
||||||
|
{}, // properties
|
||||||
|
(_result, error) => {
|
||||||
|
if (error) {
|
||||||
|
callback(null, `ApplyMonitorsConfig failed: ${error}`);
|
||||||
|
} else {
|
||||||
|
callback(true, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!anyMonitorsChanged) callback(false, null);
|
||||||
|
} else {
|
||||||
|
callback(null, `Monitor ${connectorName} not found in GetCurrentState result`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Monitor change handling
|
// Monitor change handling
|
||||||
export default class MonitorManager {
|
export const MonitorManager = GObject.registerClass({
|
||||||
constructor(extPath) {
|
Properties: {
|
||||||
this._extPath = extPath;
|
'use-optimal-monitor-config': GObject.ParamSpec.boolean(
|
||||||
|
'use-optimal-monitor-config',
|
||||||
|
'Use optimal monitor configuration',
|
||||||
|
'Automatically set the optimal monitor configuration upon connection',
|
||||||
|
GObject.ParamFlags.READWRITE,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
'headset-as-primary': GObject.ParamSpec.boolean(
|
||||||
|
'headset-as-primary',
|
||||||
|
'Use headset as primary monitor',
|
||||||
|
'Automatically set the headset as the primary display upon connection',
|
||||||
|
GObject.ParamFlags.READWRITE,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
'extension-path': GObject.ParamSpec.string(
|
||||||
|
'extension-path',
|
||||||
|
'Extension path',
|
||||||
|
'Path to the extension directory',
|
||||||
|
GObject.ParamFlags.READWRITE,
|
||||||
|
''
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, class MonitorManager extends GObject.Object {
|
||||||
|
constructor(params = {}) {
|
||||||
|
super(params);
|
||||||
|
|
||||||
this._monitorsChangedConnection = null;
|
this._monitorsChangedConnection = null;
|
||||||
this._displayConfigProxy = null;
|
this._displayConfigProxy = null;
|
||||||
this._backendManager = null;
|
this._backendManager = null;
|
||||||
this._monitorProperties = null;
|
this._monitorProperties = null;
|
||||||
this._changeHookFn = null;
|
this._changeHookFn = null;
|
||||||
|
this._needsConfigCheck = this.use_optimal_monitor_config;
|
||||||
}
|
}
|
||||||
|
|
||||||
enable() {
|
enable() {
|
||||||
|
Globals.logger.log_debug('MonitorManager enable');
|
||||||
this._backendManager = global.backend.get_monitor_manager();
|
this._backendManager = global.backend.get_monitor_manager();
|
||||||
newDisplayConfig(this._extPath, (proxy, error) => {
|
newDisplayConfig(this.extension_path, ((proxy, error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._displayConfigProxy = proxy;
|
this._displayConfigProxy = proxy;
|
||||||
this._on_monitors_change();
|
this._on_monitors_change();
|
||||||
});
|
}).bind(this));
|
||||||
|
|
||||||
this._monitorsChangedConnection = Main.layoutManager.connect('monitors-changed', this._on_monitors_change.bind(this));
|
this._monitorsChangedConnection = Main.layoutManager.connect('monitors-changed', this._on_monitors_change.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
disable() {
|
disable() {
|
||||||
|
Globals.logger.log_debug('MonitorManager disable');
|
||||||
Main.layoutManager.disconnect(this._monitorsChangedConnection);
|
Main.layoutManager.disconnect(this._monitorsChangedConnection);
|
||||||
|
|
||||||
this._monitorsChangedConnection = null;
|
this._monitorsChangedConnection = null;
|
||||||
|
|
@ -123,10 +255,6 @@ export default class MonitorManager {
|
||||||
this._changeHookFn = fn;
|
this._changeHookFn = fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPostCallback(callback) {
|
|
||||||
this._postCallback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
getMonitors() {
|
getMonitors() {
|
||||||
return Main.layoutManager.monitors;
|
return Main.layoutManager.monitors;
|
||||||
}
|
}
|
||||||
|
|
@ -135,11 +263,45 @@ export default class MonitorManager {
|
||||||
return this._monitorProperties;
|
return this._monitorProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns true if a check is needed, caller should wait for the next change hook call
|
||||||
|
checkOptimalMode(monitorConnector) {
|
||||||
|
Globals.logger.log_debug(`MonitorManager checkOptimalMode: ${monitorConnector}`);
|
||||||
|
if (this._displayConfigProxy == null) {
|
||||||
|
Globals.logger.log('MonitorManager checkOptimalMode: _displayConfigProxy not set!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._needsConfigCheck) {
|
||||||
|
performOptimalModeCheck(this._displayConfigProxy, monitorConnector, this.headset_as_primary, ((configChanged, error) => {
|
||||||
|
this._needsConfigCheck = false;
|
||||||
|
if (error) {
|
||||||
|
Globals.logger.log(`Failed to switch to optimal mode for monitor ${monitorConnector}: ${error}`);
|
||||||
|
} else {
|
||||||
|
if (configChanged) {
|
||||||
|
Globals.logger.log(`Switched to optimal mode for monitor ${monitorConnector}`);
|
||||||
|
} else if (!!this._changeHookFn) {
|
||||||
|
Globals.logger.log_debug('MonitorManager checkOptimalMode: no config change');
|
||||||
|
|
||||||
|
// no config change means this won't be triggered automatically, so trigger it manually
|
||||||
|
this._changeHookFn();
|
||||||
|
} else {
|
||||||
|
Globals.logger.log('MonitorManager checkOptimalMode: can\'t trigger change hook, no hook set!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).bind(this));
|
||||||
|
} else {
|
||||||
|
Globals.logger.log_debug('MonitorManager checkOptimalMode: skipping config check');
|
||||||
|
}
|
||||||
|
return this._needsConfigCheck;
|
||||||
|
}
|
||||||
|
|
||||||
_on_monitors_change() {
|
_on_monitors_change() {
|
||||||
|
Globals.logger.log_debug('MonitorManager _on_monitors_change');
|
||||||
if (this._displayConfigProxy == null) {
|
if (this._displayConfigProxy == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getMonitorConfig(this._displayConfigProxy, (result, error) => {
|
this._needsConfigCheck = this.use_optimal_monitor_config;
|
||||||
|
getMonitorConfig(this._displayConfigProxy, ((result, error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -161,9 +323,11 @@ export default class MonitorManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._monitorProperties = monitorProperties;
|
this._monitorProperties = monitorProperties;
|
||||||
if (this._changeHookFn !== null) {
|
if (!!this._changeHookFn) {
|
||||||
this._changeHookFn();
|
this._changeHookFn();
|
||||||
|
} else {
|
||||||
|
Globals.logger.log('MonitorManager _on_monitors_change: can\'t trigger change hook, no hook set!');
|
||||||
|
}
|
||||||
|
}).bind(this));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,3 +5,7 @@ export function getEpochSec() {
|
||||||
export function toSec(milliseconds) {
|
export function toSec(milliseconds) {
|
||||||
return Math.floor(milliseconds / 1000);
|
return Math.floor(milliseconds / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isValidKeepAlive(dateSec, strictCheck = false) {
|
||||||
|
return Math.abs(toSec(Date.now()) - dateSec) <= (strictCheck ? 1 : 5);
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,8 @@ import {
|
||||||
dataViewEnd,
|
dataViewEnd,
|
||||||
dataViewUint8,
|
dataViewUint8,
|
||||||
dataViewBigUint,
|
dataViewBigUint,
|
||||||
dataViewUintArray,
|
dataViewUint32Array,
|
||||||
|
dataViewUint8Array,
|
||||||
dataViewFloat,
|
dataViewFloat,
|
||||||
dataViewFloatArray,
|
dataViewFloatArray,
|
||||||
BOOL_SIZE,
|
BOOL_SIZE,
|
||||||
|
|
@ -23,12 +24,12 @@ import {
|
||||||
} from "./ipc.js";
|
} from "./ipc.js";
|
||||||
import { degreeToRadian } from "./math.js";
|
import { degreeToRadian } from "./math.js";
|
||||||
import { getShaderSource } from "./shader.js";
|
import { getShaderSource } from "./shader.js";
|
||||||
import { toSec } from "./time.js";
|
import { isValidKeepAlive, toSec } from "./time.js";
|
||||||
|
|
||||||
export const IPC_FILE_PATH = "/dev/shm/breezy_desktop_imu";
|
export const IPC_FILE_PATH = "/dev/shm/breezy_desktop_imu";
|
||||||
|
|
||||||
// the driver should be using the same data layout version
|
// the driver should be using the same data layout version
|
||||||
const DATA_LAYOUT_VERSION = 2;
|
const DATA_LAYOUT_VERSION = 3;
|
||||||
|
|
||||||
// DataView info: [offset, size, count]
|
// DataView info: [offset, size, count]
|
||||||
const VERSION = [0, UINT8_SIZE, 1];
|
const VERSION = [0, UINT8_SIZE, 1];
|
||||||
|
|
@ -41,7 +42,8 @@ const SBS_ENABLED = [dataViewEnd(LENS_DISTANCE_RATIO), BOOL_SIZE, 1];
|
||||||
const CUSTOM_BANNER_ENABLED = [dataViewEnd(SBS_ENABLED), BOOL_SIZE, 1];
|
const CUSTOM_BANNER_ENABLED = [dataViewEnd(SBS_ENABLED), BOOL_SIZE, 1];
|
||||||
const EPOCH_MS = [dataViewEnd(CUSTOM_BANNER_ENABLED), UINT_SIZE, 2];
|
const EPOCH_MS = [dataViewEnd(CUSTOM_BANNER_ENABLED), UINT_SIZE, 2];
|
||||||
const IMU_QUAT_DATA = [dataViewEnd(EPOCH_MS), FLOAT_SIZE, 16];
|
const IMU_QUAT_DATA = [dataViewEnd(EPOCH_MS), FLOAT_SIZE, 16];
|
||||||
const DATA_VIEW_LENGTH = dataViewEnd(IMU_QUAT_DATA);
|
const IMU_PARITY_BYTE = [dataViewEnd(IMU_QUAT_DATA), UINT8_SIZE, 1];
|
||||||
|
const DATA_VIEW_LENGTH = dataViewEnd(IMU_PARITY_BYTE);
|
||||||
|
|
||||||
// cached after first retrieval
|
// cached after first retrieval
|
||||||
const shaderUniformLocations = {
|
const shaderUniformLocations = {
|
||||||
|
|
@ -50,20 +52,23 @@ const shaderUniformLocations = {
|
||||||
'imu_quat_data': null,
|
'imu_quat_data': null,
|
||||||
'look_ahead_cfg': null,
|
'look_ahead_cfg': null,
|
||||||
'look_ahead_ms': null,
|
'look_ahead_ms': null,
|
||||||
'stage_aspect_ratio': null,
|
|
||||||
'display_aspect_ratio': null,
|
|
||||||
'trim_width_percent': null,
|
'trim_width_percent': null,
|
||||||
'trim_height_percent': null,
|
'trim_height_percent': null,
|
||||||
'display_zoom': null,
|
'display_size': null,
|
||||||
'display_north_offset': null,
|
'display_north_offset': null,
|
||||||
'lens_distance_ratio': 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,
|
'sbs_enabled': null,
|
||||||
'sbs_content': null,
|
|
||||||
'custom_banner_enabled': null,
|
'custom_banner_enabled': null,
|
||||||
'half_fov_z_rads': null,
|
'half_fov_z_rads': null,
|
||||||
'half_fov_y_rads': null,
|
'half_fov_y_rads': null,
|
||||||
'screen_distance': null,
|
'fov_half_widths': null,
|
||||||
'display_res': null
|
'fov_widths': null,
|
||||||
|
'display_resolution': null,
|
||||||
|
'source_to_display_ratio': null,
|
||||||
|
'curved_display': null
|
||||||
};
|
};
|
||||||
|
|
||||||
function setUniformFloat(effect, locationName, dataViewInfo, value) {
|
function setUniformFloat(effect, locationName, dataViewInfo, value) {
|
||||||
|
|
@ -109,32 +114,55 @@ function lookAheadMS(dataView) {
|
||||||
|
|
||||||
// most uniforms don't change frequently, this function should be called periodically
|
// most uniforms don't change frequently, this function should be called periodically
|
||||||
function setIntermittentUniformVariables() {
|
function setIntermittentUniformVariables() {
|
||||||
|
try {
|
||||||
const dataView = this._dataView;
|
const dataView = this._dataView;
|
||||||
|
|
||||||
if (dataView.byteLength === DATA_VIEW_LENGTH) {
|
if (dataView.byteLength === DATA_VIEW_LENGTH) {
|
||||||
const version = dataViewUint8(dataView, VERSION);
|
const version = dataViewUint8(dataView, VERSION);
|
||||||
const imuDateMS = dataViewBigUint(dataView, EPOCH_MS);
|
const imuDateMs = dataViewBigUint(dataView, EPOCH_MS);
|
||||||
const currentDateMS = Date.now();
|
const validKeepalive = isValidKeepAlive(toSec(imuDateMs));
|
||||||
const validKeepalive = Math.abs(toSec(currentDateMS) - toSec(imuDateMS)) < 5;
|
|
||||||
const imuData = dataViewFloatArray(dataView, IMU_QUAT_DATA);
|
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 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 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) {
|
if (enabled) {
|
||||||
const displayRes = dataViewUintArray(dataView, DISPLAY_RES);
|
|
||||||
const displayFov = dataViewFloat(dataView, DISPLAY_FOV);
|
const displayFov = dataViewFloat(dataView, DISPLAY_FOV);
|
||||||
const lensDistanceRatio = dataViewFloat(dataView, LENS_DISTANCE_RATIO);
|
|
||||||
|
// TODO - drive these values from settings
|
||||||
|
const sbsContent = false;
|
||||||
|
const sbsModeStretched = true;
|
||||||
|
|
||||||
// compute these values once, they only change when the XR device changes
|
// compute these values once, they only change when the XR device changes
|
||||||
const displayAspectRatio = displayRes[0] / displayRes[1];
|
const displayAspectRatio = displayRes[0] / displayRes[1];
|
||||||
const stageAspectRatio = this.target_monitor.width / this.target_monitor.height;
|
const diagToVertRatio = Math.sqrt(Math.pow(displayAspectRatio, 2) + 1);
|
||||||
const diagToVertRatio = Math.sqrt(Math.pow(stageAspectRatio, 2) + 1);
|
|
||||||
const halfFovZRads = degreeToRadian(displayFov / diagToVertRatio) / 2;
|
const halfFovZRads = degreeToRadian(displayFov / diagToVertRatio) / 2;
|
||||||
const halfFovYRads = halfFovZRads * stageAspectRatio;
|
const halfFovYRads = halfFovZRads * displayAspectRatio;
|
||||||
const screenDistance = 1.0 - lensDistanceRatio;
|
const fovHalfWidths = [Math.tan(halfFovYRads), Math.tan(halfFovZRads)];
|
||||||
|
const fovWidths = [fovHalfWidths[0] * 2, fovHalfWidths[1] * 2];
|
||||||
|
let texcoordXLimits = [0.0, 1.0];
|
||||||
|
let texcoordXLimitsRight = [0.0, 1.0];
|
||||||
|
if (sbsEnabled) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const lensDistanceRatio = dataViewFloat(dataView, LENS_DISTANCE_RATIO);
|
||||||
|
const lensFromCenter = lensDistanceRatio / 3.0;
|
||||||
|
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
|
// our overlay doesn't quite cover the full screen texture, which allows us to see some of the real desktop
|
||||||
// underneath, so we trim two pixels around the entire edge of the texture
|
// underneath, so we trim three pixels around the entire edge of the texture
|
||||||
const trimWidthPercent = 3.0 / this.target_monitor.width;
|
const trimWidthPercent = 3.0 / this.target_monitor.width;
|
||||||
const trimHeightPercent = 3.0 / this.target_monitor.height;
|
const trimHeightPercent = 3.0 / this.target_monitor.height;
|
||||||
|
|
||||||
|
|
@ -143,33 +171,64 @@ function setIntermittentUniformVariables() {
|
||||||
transferUniformFloat(this, 'lens_distance_ratio', dataView, LENS_DISTANCE_RATIO);
|
transferUniformFloat(this, 'lens_distance_ratio', dataView, LENS_DISTANCE_RATIO);
|
||||||
|
|
||||||
// computed values with no dataViewInfo, so we set these manually
|
// computed values with no dataViewInfo, so we set these manually
|
||||||
setSingleFloat(this, 'stage_aspect_ratio', stageAspectRatio);
|
|
||||||
setSingleFloat(this, 'display_aspect_ratio', displayAspectRatio);
|
|
||||||
setSingleFloat(this, 'trim_width_percent', trimWidthPercent);
|
setSingleFloat(this, 'trim_width_percent', trimWidthPercent);
|
||||||
setSingleFloat(this, 'trim_height_percent', trimHeightPercent);
|
setSingleFloat(this, 'trim_height_percent', trimHeightPercent);
|
||||||
setSingleFloat(this, 'half_fov_z_rads', halfFovZRads);
|
setSingleFloat(this, 'half_fov_z_rads', halfFovZRads);
|
||||||
setSingleFloat(this, 'half_fov_y_rads', halfFovYRads);
|
setSingleFloat(this, 'half_fov_y_rads', halfFovYRads);
|
||||||
setSingleFloat(this, 'screen_distance', screenDistance);
|
this.set_uniform_float(shaderUniformLocations['fov_half_widths'], 2, fovHalfWidths);
|
||||||
|
this.set_uniform_float(shaderUniformLocations['fov_widths'], 2, fovWidths);
|
||||||
// TOOD - drive from settings
|
setSingleFloat(this, 'curved_display', this.curved_display ? 1.0 : 0.0);
|
||||||
setSingleFloat(this, 'display_zoom', 1.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
|
// these variables are always in play, even if enabled is false
|
||||||
setSingleFloat(this, 'enabled', enabled ? 1.0 : 0.0);
|
setSingleFloat(this, 'enabled', enabled ? 1.0 : 0.0);
|
||||||
setSingleFloat(this, 'show_banner', imuResetState ? 1.0 : 0.0);
|
setSingleFloat(this, 'show_banner', imuResetState ? 1.0 : 0.0);
|
||||||
setSingleFloat(this, 'sbs_content', 0.0); // TOOD - drive from settings
|
setSingleFloat(this, 'sbs_enabled', sbsEnabled ? 1.0 : 0.0);
|
||||||
setSingleFloat(this, 'sbs_enabled', dataViewUint8(dataView, SBS_ENABLED) !== 0 ? 1.0 : 0.0);
|
|
||||||
setSingleFloat(this, 'custom_banner_enabled', dataViewUint8(dataView, CUSTOM_BANNER_ENABLED) !== 0 ? 1.0 : 0.0);
|
setSingleFloat(this, 'custom_banner_enabled', dataViewUint8(dataView, CUSTOM_BANNER_ENABLED) !== 0 ? 1.0 : 0.0);
|
||||||
|
|
||||||
this.set_uniform_float(shaderUniformLocations['display_res'], 2, [this.target_monitor.width, this.target_monitor.height]);
|
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) {
|
} else if (dataView.byteLength !== 0) {
|
||||||
Globals.logger.log(`ERROR: Invalid dataView.byteLength: ${dataView.byteLength} !== ${DATA_VIEW_LENGTH}`)
|
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({
|
export const XREffect = GObject.registerClass({
|
||||||
Properties: {
|
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': GObject.ParamSpec.jsobject(
|
||||||
'target-monitor',
|
'target-monitor',
|
||||||
'Target Monitor',
|
'Target Monitor',
|
||||||
|
|
@ -191,6 +250,15 @@ export const XREffect = GObject.registerClass({
|
||||||
2.5,
|
2.5,
|
||||||
1.05
|
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': GObject.ParamSpec.double(
|
||||||
'toggle-display-distance-start',
|
'toggle-display-distance-start',
|
||||||
'Display distance start',
|
'Display distance start',
|
||||||
|
|
@ -208,14 +276,42 @@ export const XREffect = GObject.registerClass({
|
||||||
0.2,
|
0.2,
|
||||||
2.5,
|
2.5,
|
||||||
1.05
|
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 {
|
}, class XREffect extends Shell.GLSLEffect {
|
||||||
constructor(params = {}) {
|
constructor(params = {}) {
|
||||||
super(params);
|
super(params);
|
||||||
|
|
||||||
this._frametime = Math.floor(1000 / this.target_framerate);
|
|
||||||
|
|
||||||
this._is_display_distance_at_end = false;
|
this._is_display_distance_at_end = false;
|
||||||
this._distance_ease_timeline = null;
|
this._distance_ease_timeline = null;
|
||||||
|
|
||||||
|
|
@ -255,14 +351,11 @@ export const XREffect = GObject.registerClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
vfunc_paint_target(node, paintContext) {
|
vfunc_paint_target(node, paintContext) {
|
||||||
var now = Date.now();
|
|
||||||
var lastPaint = this._last_paint || 0;
|
|
||||||
var frametime = this._frametime;
|
|
||||||
var calibratingImage = this.calibratingImage;
|
var calibratingImage = this.calibratingImage;
|
||||||
var customBannerImage = this.customBannerImage;
|
var customBannerImage = this.customBannerImage;
|
||||||
const data = Globals.ipc_file.load_contents(null);
|
let data = Globals.ipc_file.load_contents(null);
|
||||||
if (data[0]) {
|
if (data[0]) {
|
||||||
const buffer = new Uint8Array(data[1]).buffer;
|
let buffer = new Uint8Array(data[1]).buffer;
|
||||||
this._dataView = new DataView(buffer);
|
this._dataView = new DataView(buffer);
|
||||||
if (!this._initialized) {
|
if (!this._initialized) {
|
||||||
this.set_uniform_float(this.get_uniform_location('uDesktopTexture'), 1, [0]);
|
this.set_uniform_float(this.get_uniform_location('uDesktopTexture'), 1, [0]);
|
||||||
|
|
@ -278,10 +371,12 @@ export const XREffect = GObject.registerClass({
|
||||||
this.setIntermittentUniformVariables = setIntermittentUniformVariables.bind(this);
|
this.setIntermittentUniformVariables = setIntermittentUniformVariables.bind(this);
|
||||||
this.setIntermittentUniformVariables();
|
this.setIntermittentUniformVariables();
|
||||||
|
|
||||||
this._redraw_timeout_id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, this._frametime, () => {
|
this._redraw_timeline = Clutter.Timeline.new_for_actor(this.get_actor(), 1000);
|
||||||
if ((now - lastPaint) > frametime) global.stage.queue_redraw();
|
this._redraw_timeline.connect('new-frame', (() => {
|
||||||
return GLib.SOURCE_CONTINUE;
|
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._uniforms_timeout_id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, (() => {
|
||||||
this.setIntermittentUniformVariables();
|
this.setIntermittentUniformVariables();
|
||||||
|
|
@ -291,30 +386,48 @@ export const XREffect = GObject.registerClass({
|
||||||
this._initialized = true;
|
this._initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let success = false;
|
||||||
|
let attempts = 0;
|
||||||
|
while (!success && attempts < 2) {
|
||||||
if (this._dataView.byteLength === DATA_VIEW_LENGTH) {
|
if (this._dataView.byteLength === DATA_VIEW_LENGTH) {
|
||||||
|
if (checkParityByte(this._dataView)) {
|
||||||
setSingleFloat(this, 'display_north_offset', this.display_distance);
|
setSingleFloat(this, 'display_north_offset', this.display_distance);
|
||||||
setSingleFloat(this, 'look_ahead_ms', lookAheadMS(this._dataView));
|
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);
|
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) {
|
} else if (this._dataView.byteLength !== 0) {
|
||||||
Globals.logger.log(`ERROR: Invalid dataView.byteLength: ${this._dataView.byteLength} !== ${DATA_VIEW_LENGTH}`)
|
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
|
// improves sampling quality for smooth text and edges
|
||||||
this.get_pipeline().set_layer_filters(
|
this.get_pipeline().set_layer_filters(
|
||||||
0,
|
0,
|
||||||
Cogl.PipelineFilter.LINEAR_MIPMAP_LINEAR,
|
Cogl.PipelineFilter.LINEAR_MIPMAP_LINEAR,
|
||||||
Cogl.PipelineFilter.LINEAR
|
Cogl.PipelineFilter.LINEAR
|
||||||
);
|
);
|
||||||
|
|
||||||
super.vfunc_paint_target(node, paintContext);
|
|
||||||
} else {
|
|
||||||
super.vfunc_paint_target(node, paintContext);
|
|
||||||
}
|
}
|
||||||
this._last_paint = now;
|
}
|
||||||
|
super.vfunc_paint_target(node, paintContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
if (this._redraw_timeout_id) GLib.source_remove(this._redraw_timeout_id);
|
if (this._redraw_timeline) {
|
||||||
|
this._redraw_timeline.stop();
|
||||||
|
this._redraw_timeline = null;
|
||||||
|
}
|
||||||
if (this._uniforms_timeout_id) GLib.source_remove(this._uniforms_timeout_id);
|
if (this._uniforms_timeout_id) GLib.source_remove(this._uniforms_timeout_id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit f45b8fbaa7ef0b3f824a3452a91dc74bffb6ea38
|
Subproject commit abed471a8327867f88e206b36b3555ae30111eb8
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 17ebe10ad9a006cd6a51d5705d6ceddca4464369
|
Subproject commit ec3f82aa4834847adf1f0c640eba4b87b6f3ff65
|
||||||
|
|
@ -64,6 +64,69 @@
|
||||||
End distance when using the "toggle display distance" shortcut.
|
End distance when using the "toggle display distance" shortcut.
|
||||||
</description>
|
</description>
|
||||||
</key>
|
</key>
|
||||||
|
<key name="widescreen-mode" type="b">
|
||||||
|
<default>
|
||||||
|
false
|
||||||
|
</default>
|
||||||
|
<summary>Widescreen mode</summary>
|
||||||
|
<description>
|
||||||
|
Enable widescreen/SBS mode
|
||||||
|
</description>
|
||||||
|
</key>
|
||||||
|
<key name="display-size" type="d">
|
||||||
|
<default>
|
||||||
|
1.0
|
||||||
|
</default>
|
||||||
|
<summary>Display size</summary>
|
||||||
|
<description>
|
||||||
|
The size of the display
|
||||||
|
</description>
|
||||||
|
</key>
|
||||||
|
<key name="curved-display" type="b">
|
||||||
|
<default>
|
||||||
|
false
|
||||||
|
</default>
|
||||||
|
<summary>Curved display</summary>
|
||||||
|
<description>
|
||||||
|
Enable curved display mode
|
||||||
|
</description>
|
||||||
|
</key>
|
||||||
|
<key name="look-ahead-override" type="i">
|
||||||
|
<default>
|
||||||
|
-1
|
||||||
|
</default>
|
||||||
|
<summary>Look-ahead override</summary>
|
||||||
|
<description>
|
||||||
|
Manually override the look-ahead calculation
|
||||||
|
</description>
|
||||||
|
</key>
|
||||||
|
<key name="use-optimal-monitor-config" type="b">
|
||||||
|
<default>
|
||||||
|
true
|
||||||
|
</default>
|
||||||
|
<summary>Use optimal monitor configuration</summary>
|
||||||
|
<description>
|
||||||
|
Automatically set the optimal monitor configuration upon connection
|
||||||
|
</description>
|
||||||
|
</key>
|
||||||
|
<key name="headset-as-primary" type="b">
|
||||||
|
<default>
|
||||||
|
true
|
||||||
|
</default>
|
||||||
|
<summary>Headset as primary</summary>
|
||||||
|
<description>
|
||||||
|
Automatically set the headset as the primary display upon connection
|
||||||
|
</description>
|
||||||
|
</key>
|
||||||
|
<key name="disable-anti-aliasing" type="b">
|
||||||
|
<default>
|
||||||
|
false
|
||||||
|
</default>
|
||||||
|
<summary>Disable anti-aliasing</summary>
|
||||||
|
<description>
|
||||||
|
Disable anti-aliasing
|
||||||
|
</description>
|
||||||
|
</key>
|
||||||
<key name="developer-mode" type="b">
|
<key name="developer-mode" type="b">
|
||||||
<default>
|
<default>
|
||||||
false
|
false
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 50ce02fc9e341d417785bd26abeee9f7305bee6c
|
Subproject commit 34a349d39efc02f4b65550debd193d29d95a84f9
|
||||||
|
|
@ -10,13 +10,17 @@ from .xrdriveripc import XRDriverIPC
|
||||||
class ConnectedDevice(Gtk.Box):
|
class ConnectedDevice(Gtk.Box):
|
||||||
__gtype_name__ = "ConnectedDevice"
|
__gtype_name__ = "ConnectedDevice"
|
||||||
|
|
||||||
|
device_label = Gtk.Template.Child()
|
||||||
effect_enable_switch = Gtk.Template.Child()
|
effect_enable_switch = Gtk.Template.Child()
|
||||||
display_distance_scale = Gtk.Template.Child()
|
display_distance_scale = Gtk.Template.Child()
|
||||||
display_distance_adjustment = Gtk.Template.Child()
|
display_distance_adjustment = Gtk.Template.Child()
|
||||||
|
display_size_scale = Gtk.Template.Child()
|
||||||
|
display_size_adjustment = Gtk.Template.Child()
|
||||||
follow_threshold_scale = Gtk.Template.Child()
|
follow_threshold_scale = Gtk.Template.Child()
|
||||||
follow_threshold_adjustment = Gtk.Template.Child()
|
follow_threshold_adjustment = Gtk.Template.Child()
|
||||||
follow_mode_switch = Gtk.Template.Child()
|
follow_mode_switch = Gtk.Template.Child()
|
||||||
device_label = Gtk.Template.Child()
|
widescreen_mode_switch = Gtk.Template.Child()
|
||||||
|
curved_display_switch = Gtk.Template.Child()
|
||||||
set_toggle_display_distance_start_button = Gtk.Template.Child()
|
set_toggle_display_distance_start_button = Gtk.Template.Child()
|
||||||
set_toggle_display_distance_end_button = Gtk.Template.Child()
|
set_toggle_display_distance_end_button = Gtk.Template.Child()
|
||||||
reassign_recenter_display_shortcut_button = Gtk.Template.Child()
|
reassign_recenter_display_shortcut_button = Gtk.Template.Child()
|
||||||
|
|
@ -25,19 +29,30 @@ class ConnectedDevice(Gtk.Box):
|
||||||
toggle_display_distance_shortcut_label = Gtk.Template.Child()
|
toggle_display_distance_shortcut_label = Gtk.Template.Child()
|
||||||
reassign_toggle_follow_shortcut_button = Gtk.Template.Child()
|
reassign_toggle_follow_shortcut_button = Gtk.Template.Child()
|
||||||
toggle_follow_shortcut_label = Gtk.Template.Child()
|
toggle_follow_shortcut_label = Gtk.Template.Child()
|
||||||
|
headset_as_primary_switch = Gtk.Template.Child()
|
||||||
|
use_optimal_monitor_config_switch = Gtk.Template.Child()
|
||||||
|
movement_look_ahead_scale = Gtk.Template.Child()
|
||||||
|
movement_look_ahead_adjustment = Gtk.Template.Child()
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(Gtk.Box, self).__init__()
|
super(Gtk.Box, self).__init__()
|
||||||
self.init_template()
|
self.init_template()
|
||||||
self.all_enabled_state_inputs = [
|
self.all_enabled_state_inputs = [
|
||||||
self.display_distance_scale,
|
self.display_distance_scale,
|
||||||
|
self.display_size_scale,
|
||||||
self.follow_mode_switch,
|
self.follow_mode_switch,
|
||||||
self.follow_threshold_scale,
|
self.follow_threshold_scale,
|
||||||
|
self.widescreen_mode_switch,
|
||||||
|
self.curved_display_switch,
|
||||||
self.set_toggle_display_distance_start_button,
|
self.set_toggle_display_distance_start_button,
|
||||||
self.set_toggle_display_distance_end_button,
|
self.set_toggle_display_distance_end_button,
|
||||||
self.reassign_recenter_display_shortcut_button,
|
self.reassign_recenter_display_shortcut_button,
|
||||||
self.reassign_toggle_display_distance_shortcut_button,
|
self.reassign_toggle_display_distance_shortcut_button,
|
||||||
self.reassign_toggle_follow_shortcut_button
|
self.reassign_toggle_follow_shortcut_button,
|
||||||
|
self.headset_as_primary_switch,
|
||||||
|
self.use_optimal_monitor_config_switch,
|
||||||
|
self.movement_look_ahead_scale
|
||||||
]
|
]
|
||||||
|
|
||||||
self.settings = SettingsManager.get_instance().settings
|
self.settings = SettingsManager.get_instance().settings
|
||||||
|
|
@ -45,7 +60,13 @@ class ConnectedDevice(Gtk.Box):
|
||||||
self.extensions_manager = ExtensionsManager.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-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('follow-threshold', self.follow_threshold_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('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('look-ahead-override', self.movement_look_ahead_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT)
|
||||||
|
|
||||||
bind_shortcut_settings(self.get_parent(), [
|
bind_shortcut_settings(self.get_parent(), [
|
||||||
[self.reassign_recenter_display_shortcut_button, self.recenter_display_shortcut_label],
|
[self.reassign_recenter_display_shortcut_button, self.recenter_display_shortcut_label],
|
||||||
|
|
@ -68,8 +89,11 @@ class ConnectedDevice(Gtk.Box):
|
||||||
self.effect_enable_switch.set_active(self._is_config_enabled(self.ipc.retrieve_config()) and self.extensions_manager.is_enabled())
|
self.effect_enable_switch.set_active(self._is_config_enabled(self.ipc.retrieve_config()) and self.extensions_manager.is_enabled())
|
||||||
self.effect_enable_switch.connect('notify::active', self._refresh_inputs_for_enabled_state)
|
self.effect_enable_switch.connect('notify::active', self._refresh_inputs_for_enabled_state)
|
||||||
|
|
||||||
|
self.use_optimal_monitor_config_switch.connect('notify::active', self._refresh_use_optimal_monitor_config)
|
||||||
|
|
||||||
self._handle_enabled_features(self.state_manager, None)
|
self._handle_enabled_features(self.state_manager, None)
|
||||||
self._refresh_inputs_for_enabled_state(self.effect_enable_switch, None)
|
self._refresh_inputs_for_enabled_state(self.effect_enable_switch, None)
|
||||||
|
self._refresh_use_optimal_monitor_config(self.use_optimal_monitor_config_switch, None)
|
||||||
self.extensions_manager.bind_property('breezy-enabled', self.effect_enable_switch, 'active', GObject.BindingFlags.BIDIRECTIONAL)
|
self.extensions_manager.bind_property('breezy-enabled', self.effect_enable_switch, 'active', GObject.BindingFlags.BIDIRECTIONAL)
|
||||||
|
|
||||||
self.connect("destroy", self._on_widget_destroy)
|
self.connect("destroy", self._on_widget_destroy)
|
||||||
|
|
@ -98,7 +122,8 @@ class ConnectedDevice(Gtk.Box):
|
||||||
for widget in self.all_enabled_state_inputs:
|
for widget in self.all_enabled_state_inputs:
|
||||||
widget.set_sensitive(requesting_enabled)
|
widget.set_sensitive(requesting_enabled)
|
||||||
|
|
||||||
if requesting_enabled: self._refresh_follow_mode(self.follow_mode_switch, None)
|
if requesting_enabled:
|
||||||
|
self._refresh_follow_mode(self.follow_mode_switch, None)
|
||||||
|
|
||||||
def _refresh_follow_mode(self, switch, param):
|
def _refresh_follow_mode(self, switch, param):
|
||||||
self.follow_threshold_scale.set_sensitive(switch.get_active())
|
self.follow_threshold_scale.set_sensitive(switch.get_active())
|
||||||
|
|
@ -109,6 +134,11 @@ class ConnectedDevice(Gtk.Box):
|
||||||
'enable_breezy_desktop_smooth_follow': switch.get_active()
|
'enable_breezy_desktop_smooth_follow': switch.get_active()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def _refresh_use_optimal_monitor_config(self, switch, param):
|
||||||
|
self.headset_as_primary_switch.set_sensitive(switch.get_active())
|
||||||
|
if not switch.get_active():
|
||||||
|
self.headset_as_primary_switch.set_active(False)
|
||||||
|
|
||||||
def set_device_name(self, name):
|
def set_device_name(self, name):
|
||||||
self.device_label.set_markup(f"<b>{name}</b>")
|
self.device_label.set_markup(f"<b>{name}</b>")
|
||||||
|
|
||||||
|
|
@ -120,7 +150,9 @@ class ConnectedDevice(Gtk.Box):
|
||||||
def _on_widget_destroy(self, widget):
|
def _on_widget_destroy(self, widget):
|
||||||
self.state_manager.unbind_property('follow-mode', self.follow_mode_switch, 'active')
|
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-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('follow-threshold', self.follow_threshold_adjustment, 'value')
|
||||||
|
self.settings.unbind('widescreen-mode', self.widescreen_mode_switch, 'active')
|
||||||
self.extensions_manager.unbind_property('breezy-enabled', self.effect_enable_switch, 'active')
|
self.extensions_manager.unbind_property('breezy-enabled', self.effect_enable_switch, 'active')
|
||||||
|
|
||||||
def reload_display_distance_toggle_button(widget):
|
def reload_display_distance_toggle_button(widget):
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,26 @@
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="AdwViewStack" id="stack">
|
||||||
|
<child>
|
||||||
|
<object class="AdwViewStackPage">
|
||||||
|
<property name="name">general</property>
|
||||||
|
<property name="title">General Settings</property>
|
||||||
|
<property name="icon-name">applications-system-symbolic</property>
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="orientation">1</property>
|
||||||
|
<property name="margin-start">20</property>
|
||||||
|
<property name="margin-end">20</property>
|
||||||
|
<property name="spacing">20</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwPreferencesGroup">
|
<object class="AdwPreferencesGroup">
|
||||||
<property name="title" translatable="true">Settings</property>
|
<property name="title" translatable="true">Features</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow">
|
||||||
<property name="title" translatable="true">Effect enabled</property>
|
<property name="title" translatable="true">XR effect</property>
|
||||||
<property name="subtitle" translatable="true">Turn on or off the XR desktop effect</property>
|
<property name="subtitle" translatable="true">Enables the Breezy Desktop XR effect.</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkSwitch" id="effect_enable_switch">
|
<object class="GtkSwitch" id="effect_enable_switch">
|
||||||
<property name="valign">3</property>
|
<property name="valign">3</property>
|
||||||
|
|
@ -39,7 +52,48 @@
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow">
|
||||||
|
<property name="title" translatable="true">Widescreen mode</property>
|
||||||
|
<property name="subtitle" translatable="true">Switches your glasses into side-by-side mode and doubles the width of the display.</property>
|
||||||
|
<property name="valign">2</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSwitch" id="widescreen_mode_switch">
|
||||||
|
<property name="valign">3</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="AdwActionRow">
|
||||||
|
<property name="title" translatable="true">Follow mode</property>
|
||||||
|
<property name="subtitle" translatable="true">Keep the virtual display near the center of your view.</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSwitch" id="follow_mode_switch">
|
||||||
|
<property name="valign">3</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="AdwActionRow">
|
||||||
|
<property name="title" translatable="true">Curved display</property>
|
||||||
|
<property name="subtitle" translatable="true">Switch between flat and curved displays.</property>
|
||||||
|
<property name="valign">2</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSwitch" id="curved_display_switch">
|
||||||
|
<property name="valign">3</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="AdwPreferencesGroup">
|
||||||
|
<property name="title" translatable="true">Adjustments</property>
|
||||||
|
<child>
|
||||||
|
<object class="AdwActionRow" id="display_distance_row">
|
||||||
<property name="title" translatable="true">Display distance</property>
|
<property name="title" translatable="true">Display distance</property>
|
||||||
|
<property name="subtitle" translatable="true">Closer appears larger, further appears smaller. Controls depth when in widescreen mode.</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkScale" id="display_distance_scale">
|
<object class="GtkScale" id="display_distance_scale">
|
||||||
<property name="valign">3</property>
|
<property name="valign">3</property>
|
||||||
|
|
@ -67,11 +121,55 @@
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow">
|
||||||
<property name="title" translatable="true">Follow enabled</property>
|
<property name="title" translatable="true">Display size</property>
|
||||||
<property name="subtitle" translatable="true">Keep the virtual display near the center of your view</property>
|
<property name="subtitle" translatable="true">Combine with display distance to achieve a comfortable level of depth and size.</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkSwitch" id="follow_mode_switch">
|
<object class="GtkScale" id="display_size_scale">
|
||||||
<property name="valign">3</property>
|
<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="true">Display toggle distances</property>
|
||||||
|
<property name="subtitle" translatable="true">Use the buttons to capture the current display distance for use with the keyboard shortcut.</property>
|
||||||
|
<property name="valign">2</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="spacing">30</property>
|
||||||
|
<property name="width-request">150</property>
|
||||||
|
<property name="margin-start">30</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="set_toggle_display_distance_start_button">
|
||||||
|
<property name="name">toggle-display-distance-start</property>
|
||||||
|
<property name="valign">3</property>
|
||||||
|
</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>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
@ -79,7 +177,7 @@
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow">
|
||||||
<property name="title" translatable="true">Follow threshold</property>
|
<property name="title" translatable="true">Follow threshold</property>
|
||||||
<property name="subtitle" translatable="true">How far away you can look before the display follows</property>
|
<property name="subtitle" translatable="true">How far away you can look before the display follows.</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkScale" id="follow_threshold_scale">
|
<object class="GtkScale" id="follow_threshold_scale">
|
||||||
<property name="valign">3</property>
|
<property name="valign">3</property>
|
||||||
|
|
@ -102,14 +200,24 @@
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="AdwViewStackPage">
|
||||||
|
<property name="name">shortcuts</property>
|
||||||
|
<property name="title">Keyboard Shortcuts</property>
|
||||||
|
<property name="icon-name">preferences-desktop-keyboard-shortcuts-symbolic</property>
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkBox">
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwPreferencesGroup">
|
<object class="AdwPreferencesGroup">
|
||||||
<property name="title" translatable="true">Shortcuts</property>
|
<property name="title" translatable="true">Keyboard Shortcuts</property>
|
||||||
<property name="description" translatable="true">Modify keyboard shortcuts and how they work</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow">
|
||||||
<property name="title" translatable="true">Re-center display shortcut</property>
|
<property name="title" translatable="true">Re-center display shortcut</property>
|
||||||
<property name="subtitle" translatable="true">Pin the virtual display to the current position</property>
|
<property name="subtitle" translatable="true">Pin the virtual display to the current position.</property>
|
||||||
<property name="valign">2</property>
|
<property name="valign">2</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
|
|
@ -138,7 +246,7 @@
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow">
|
||||||
<property name="title" translatable="true">Display distance shortcut</property>
|
<property name="title" translatable="true">Display distance shortcut</property>
|
||||||
<property name="subtitle" translatable="true">Quickly toggle between two predefined distances</property>
|
<property name="subtitle" translatable="true">Quickly toggle between two predefined distances.</property>
|
||||||
<property name="valign">2</property>
|
<property name="valign">2</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
|
|
@ -164,36 +272,10 @@
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
|
||||||
<object class="AdwActionRow">
|
|
||||||
<property name="title" translatable="true">Display distance start and end</property>
|
|
||||||
<property name="subtitle" translatable="true">Use the buttons to capture the current display distance as start and end points.</property>
|
|
||||||
<property name="valign">2</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="spacing">30</property>
|
|
||||||
<property name="width-request">150</property>
|
|
||||||
<property name="margin-start">30</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton" id="set_toggle_display_distance_start_button">
|
|
||||||
<property name="name">toggle-display-distance-start</property>
|
|
||||||
<property name="valign">3</property>
|
|
||||||
</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>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow">
|
||||||
<property name="title" translatable="true">Toggle follow mode shortcut</property>
|
<property name="title" translatable="true">Toggle follow mode shortcut</property>
|
||||||
<property name="subtitle" translatable="true">Quickly toggle follow mode</property>
|
<property name="subtitle" translatable="true">Quickly toggle follow mode.</property>
|
||||||
<property name="valign">2</property>
|
<property name="valign">2</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
|
|
@ -221,5 +303,86 @@
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="AdwViewStackPage">
|
||||||
|
<property name="name">advanced</property>
|
||||||
|
<property name="title">Advanced Settings</property>
|
||||||
|
<property name="icon-name">applications-system-symbolic</property>
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkBox">
|
||||||
|
<child>
|
||||||
|
<object class="AdwPreferencesGroup">
|
||||||
|
<property name="title" translatable="true">Advanced Settings</property>
|
||||||
|
<child>
|
||||||
|
<object class="AdwActionRow">
|
||||||
|
<property name="title" translatable="true">Find optimal display config</property>
|
||||||
|
<property name="subtitle" translatable="true">Automatically modify the glasses display configuration for maximum resolution and best scaling when plugged in.</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSwitch" id="use_optimal_monitor_config_switch">
|
||||||
|
<property name="valign">3</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="AdwActionRow">
|
||||||
|
<property name="title" translatable="true">Always primary display</property>
|
||||||
|
<property name="subtitle" translatable="true">Automatically set the glasses as the primary display when plugged in.</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSwitch" id="headset_as_primary_switch">
|
||||||
|
<property name="valign">3</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="AdwActionRow">
|
||||||
|
<property name="title" translatable="true">Movement look-ahead</property>
|
||||||
|
<property name="subtitle" translatable="true">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.</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScale" id="movement_look_ahead_scale">
|
||||||
|
<property name="valign">3</property>
|
||||||
|
<property name="draw-value">false</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="movement_look_ahead_adjustment">
|
||||||
|
<property name="lower">-1</property>
|
||||||
|
<property name="upper">40</property>
|
||||||
|
<property name="step-increment">1</property>
|
||||||
|
<property name="value">-1</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
<marks>
|
||||||
|
<mark value="-1" position="bottom">Default</mark>
|
||||||
|
<mark value="10" position="bottom">10ms</mark>
|
||||||
|
<mark value="20" position="bottom">20ms</mark>
|
||||||
|
<mark value="30" position="bottom">30ms</mark>
|
||||||
|
<mark value="40" position="bottom">40ms</mark>
|
||||||
|
</marks>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="AdwViewSwitcher" id="switcher">
|
||||||
|
<property name="stack">stack</property>
|
||||||
|
<property name="policy">wide</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
</template>
|
</template>
|
||||||
</interface>
|
</interface>
|
||||||
|
|
|
||||||
|
|
@ -57,11 +57,11 @@
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox" id="tiers">
|
<object class="GtkBox" id="features">
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox" id="features">
|
<object class="GtkBox" id="tiers">
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,10 @@
|
||||||
<attribute name="label" translatable="yes">License Details</attribute>
|
<attribute name="label" translatable="yes">License Details</attribute>
|
||||||
<attribute name="action">app.license</attribute>
|
<attribute name="action">app.license</attribute>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Force Reset</attribute>
|
||||||
|
<attribute name="action">app.reset_driver</attribute>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes">_About BreezyDesktop</attribute>
|
<attribute name="label" translatable="yes">_About BreezyDesktop</attribute>
|
||||||
<attribute name="action">app.about</attribute>
|
<attribute name="action">app.about</attribute>
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ from gi.repository import Adw
|
||||||
from .time import time_remaining_text
|
from .time import time_remaining_text
|
||||||
|
|
||||||
FEATURE_NAMES = {
|
FEATURE_NAMES = {
|
||||||
'sbs': 'Side-by-side mode (for gaming)',
|
'sbs': 'Side-by-side mode (gaming)',
|
||||||
'smooth_follow': 'Smooth Follow',
|
'smooth_follow': 'Smooth Follow (gaming)',
|
||||||
'productivity_basic': 'Breezy Desktop',
|
'productivity_basic': 'Breezy Desktop (productivity)',
|
||||||
'productivity_pro': 'Breezy Desktop w/ multiple monitors',
|
'productivity_pro': 'Breezy Desktop Pro (productivity)',
|
||||||
}
|
}
|
||||||
|
|
||||||
class LicenseFeatureRow(Adw.ActionRow):
|
class LicenseFeatureRow(Adw.ActionRow):
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ class LicenseTierRow(Adw.ExpanderRow):
|
||||||
elif active_period is not None:
|
elif active_period is not None:
|
||||||
amount_text += " to upgrade"
|
amount_text += " to upgrade"
|
||||||
elif active_period is not None and PERIOD_RANKS[period] >= PERIOD_RANKS[active_period]:
|
elif active_period is not None and PERIOD_RANKS[period] >= PERIOD_RANKS[active_period]:
|
||||||
amount_text = "Ready to auto-renew"
|
amount_text = "Paid through next renewal period"
|
||||||
|
|
||||||
if amount_text is not None:
|
if amount_text is not None:
|
||||||
row_widget = Adw.ActionRow(title=period.capitalize())
|
row_widget = Adw.ActionRow(title=period.capitalize())
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ class BreezydesktopApplication(Adw.Application):
|
||||||
self.create_action('quit', self.on_quit_action, ['<primary>q'])
|
self.create_action('quit', self.on_quit_action, ['<primary>q'])
|
||||||
self.create_action('about', self.on_about_action)
|
self.create_action('about', self.on_about_action)
|
||||||
self.create_action('license', self.on_license_action)
|
self.create_action('license', self.on_license_action)
|
||||||
|
self.create_action('reset_driver', self.on_reset_driver_action)
|
||||||
|
|
||||||
def do_activate(self):
|
def do_activate(self):
|
||||||
"""Called when the application is activated.
|
"""Called when the application is activated.
|
||||||
|
|
@ -94,6 +95,11 @@ class BreezydesktopApplication(Adw.Application):
|
||||||
dialog.set_transient_for(self.props.active_window)
|
dialog.set_transient_for(self.props.active_window)
|
||||||
dialog.present()
|
dialog.present()
|
||||||
|
|
||||||
|
def on_reset_driver_action(self, widget, _):
|
||||||
|
XRDriverIPC.get_instance().write_control_flags({
|
||||||
|
'force_quit': True
|
||||||
|
})
|
||||||
|
|
||||||
def create_action(self, name, callback, shortcuts=None):
|
def create_action(self, name, callback, shortcuts=None):
|
||||||
"""Add an application action.
|
"""Add an application action.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ class StateManager(GObject.GObject):
|
||||||
__gproperties__ = {
|
__gproperties__ = {
|
||||||
'follow-mode': (bool, 'Follow Mode', 'Whether the follow mode is enabled', False, GObject.ParamFlags.READWRITE),
|
'follow-mode': (bool, 'Follow Mode', 'Whether the follow mode is enabled', False, GObject.ParamFlags.READWRITE),
|
||||||
'follow-threshold': (float, 'Follow Threshold', 'The follow threshold', 1.0, 45.0, 15.0, GObject.ParamFlags.READWRITE),
|
'follow-threshold': (float, 'Follow Threshold', 'The follow threshold', 1.0, 45.0, 15.0, GObject.ParamFlags.READWRITE),
|
||||||
|
'widescreen-mode': (bool, 'Widescreen Mode', 'Whether widescreen mode is enabled', False, GObject.ParamFlags.READWRITE),
|
||||||
'license-action-needed': (bool, 'License Action Needed', 'Whether the license needs attention', False, GObject.ParamFlags.READWRITE),
|
'license-action-needed': (bool, 'License Action Needed', 'Whether the license needs attention', False, GObject.ParamFlags.READWRITE),
|
||||||
'license-present': (bool, 'License Present', 'Whether a license is present', False, GObject.ParamFlags.READWRITE),
|
'license-present': (bool, 'License Present', 'Whether a license is present', False, GObject.ParamFlags.READWRITE),
|
||||||
'enabled-features-list': (object, 'Enabled Features List', 'A list of the enabled features', GObject.ParamFlags.READWRITE),
|
'enabled-features-list': (object, 'Enabled Features List', 'A list of the enabled features', GObject.ParamFlags.READWRITE),
|
||||||
|
|
@ -94,12 +95,15 @@ class StateManager(GObject.GObject):
|
||||||
self.set_property('license-present', False)
|
self.set_property('license-present', False)
|
||||||
|
|
||||||
self.set_property('follow-mode', self.state.get('breezy_desktop_smooth_follow_enabled'))
|
self.set_property('follow-mode', self.state.get('breezy_desktop_smooth_follow_enabled'))
|
||||||
|
self.set_property('widescreen-mode', self.state.get('sbs_mode_enabled'))
|
||||||
|
|
||||||
if self.running: threading.Timer(1.0, self._refresh_state).start()
|
if self.running: threading.Timer(1.0, self._refresh_state).start()
|
||||||
|
|
||||||
def do_set_property(self, prop, value):
|
def do_set_property(self, prop, value):
|
||||||
if prop.name == 'follow-mode':
|
if prop.name == 'follow-mode':
|
||||||
self.follow_mode = value
|
self.follow_mode = value
|
||||||
|
if prop.name == 'widescreen-mode':
|
||||||
|
self.widescreen_mode = value
|
||||||
if prop.name == 'license-action-needed':
|
if prop.name == 'license-action-needed':
|
||||||
self.license_action_needed = value
|
self.license_action_needed = value
|
||||||
if prop.name == 'license-present':
|
if prop.name == 'license-present':
|
||||||
|
|
@ -110,6 +114,8 @@ class StateManager(GObject.GObject):
|
||||||
def do_get_property(self, prop):
|
def do_get_property(self, prop):
|
||||||
if prop.name == 'follow-mode':
|
if prop.name == 'follow-mode':
|
||||||
return self.follow_mode
|
return self.follow_mode
|
||||||
|
if prop.name == 'widescreen-mode':
|
||||||
|
return self.widescreen_mode
|
||||||
if prop.name == 'license-action-needed':
|
if prop.name == 'license-action-needed':
|
||||||
return self.license_action_needed
|
return self.license_action_needed
|
||||||
if prop.name == 'license-present':
|
if prop.name == 'license-present':
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue