Merge branch 'main' into gnome-44-max

This commit is contained in:
wheaney 2024-07-25 15:01:34 -07:00
commit 10268f638d
13 changed files with 127 additions and 54 deletions

View File

@ -13,7 +13,7 @@ There are two installations at the moment. **Note: Only install one of these at
* [Breezy Vulkan](#breezy-vulkan) primarily for gaming but would work with pretty much any application that uses Vulkan rendering.
## Breezy GNOME
Breezy GNOME is a virtual workspace solution for Linux desktops that use the GNOME desktop environment (requires GNOME 45+ on an x86_64 system); see [non-GNOME setup](#non-gnome-setup) if you want to try it without a GNOME desktop environment. It currently supports one virtual monitor and multiple physical monitors, but it will soon support multiple virtual monitors. See [upcoming features](#upcoming-features) for more improvements on the horizon.
Breezy GNOME is a virtual workspace solution for Linux desktops that use the GNOME desktop environment (supports GNOME versions 42-46 on an x86_64 system); see [non-GNOME setup](#non-gnome-setup) if you want to try it without a GNOME desktop environment. It currently supports one virtual monitor and multiple physical monitors, but it will soon support multiple virtual monitors. See [upcoming features](#upcoming-features) for more improvements on the horizon.
### GNOME Setup
@ -21,18 +21,21 @@ For the best performance, ensure you have the latest graphics drivers installed
#### Arch Linux
*Note: if you've previously installed Breezy GNOME using the setup script, you must uninstall it first: `~/.local/bin/breezy_gnome_uninstall`*
Breezy GNOME is in AUR (but not pacman, yet). To install: `yay -S breezy-desktop-gnome-git` and, once that succeeds, `systemctl --user enable --now xreal-air-driver.service`
Breezy GNOME is in AUR (but not pacman, yet). To install, run these commands from a terminal:
1. If you've previously installed Breezy GNOME using the setup script, you must uninstall it first with `breezy_gnome_uninstall`
2. `yay -S breezy-desktop-gnome-git`
3. `systemctl --user enable --now xr-driver.service`
#### All other distros
1. Download the Breezy GNOME [setup script](https://github.com/wheaney/breezy-desktop/releases/latest/download/breezy_gnome_setup) and set the execute flag (e.g. from the terminal: `chmod +x ~/Downloads/breezy_gnome_setup`)
2. Run the setup script (e.g. `~/Downloads/breezy_gnome_setup`)
2. Run the setup script:
* For **GNOME 45+**: `~/Downloads/breezy_gnome_setup`
* For **GNOME 42-44**: `~/Downloads/breezy_gnome_setup --tag gnome-44-max-beta-1`
### Non-GNOME Setup
A workable solution (with some [QoL improvements needed](#upcoming-features)) is to use your preferred desktop environment with a GNOME window open in nested mode. To do this:
1. Install `gnome-shell` using your distros package manager (e.g. apt-get, pacman, dnf, etc...). This will currently only work with GNOME Shell versions 45+, so check that using `gnome-shell --version`
1. Install `gnome-shell` using your distros package manager (e.g. apt-get, pacman, dnf, etc...). This will currently only work with GNOME Shell versions 42-46, so check that using `gnome-shell --version`
2. Run the [GNOME setup](#gnome-setup) steps. You shouldn't need to log out and back in since GNOME will be running nested.
3. Launch the nested GNOME Shell using `MUTTER_DEBUG_DUMMY_MODE_SPECS="1920x1080@60" dbus-run-session -- gnome-shell --nested`

View File

@ -73,8 +73,11 @@ pushd gnome/src
GNOME_MANIFEST_LINE=$(find -L . -type f ! -name "*.compiled" -exec sha256sum {} \; | sort | sha256sum | sed 's/ .*//')
popd
ui/bin/package $ARCH
cp ui/out/com.xronlinux.BreezyDesktop-$ARCH.flatpak $PACKAGE_DIR/com.xronlinux.BreezyDesktop.flatpak
FLATPAK_BUILD_ARTIFACT=ui/out/com.xronlinux.BreezyDesktop-$ARCH.flatpak
if [ ! -e "$FLATPAK_BUILD_ARTIFACT" ] || [ "$1" == "--rebuild-flatpak" ]; then
ui/bin/package $ARCH
fi
cp $FLATPAK_BUILD_ARTIFACT $PACKAGE_DIR/com.xronlinux.BreezyDesktop.flatpak
# create manifest file for verifying installed file checksums against the originally packaged versions
# include any file that doesn't get modified during setup (e.g. vkBasalt.json files)

View File

@ -96,24 +96,30 @@ class BreezyDesktopExtension {
var target_monitor = this._target_monitor;
var is_effect_running = this._is_effect_running;
this._running_poller_id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, (() => {
if (is_effect_running) {
this._running_poller_id = undefined;
return GLib.SOURCE_REMOVE;
}
const is_driver_running = this._check_driver_running();
if (is_driver_running && target_monitor) {
// Don't enable the effect yet if monitor updates are needed.
// _setup will be triggered again since a !ready result means it will trigger monitor changes,
// so we can remove this timeout_add no matter what.
if (this._target_monitor_ready(target_monitor)) {
Globals.logger.log('Driver is running, supported monitor connected. Enabling XR effect.');
this._effect_enable();
try {
if (is_effect_running) {
this._running_poller_id = undefined;
return GLib.SOURCE_REMOVE;
}
const is_driver_running = this._check_driver_running();
if (is_driver_running && target_monitor) {
// Don't enable the effect yet if monitor updates are needed.
// _setup will be triggered again since a !ready result means it will trigger monitor changes,
// so we can remove this timeout_add no matter what.
if (this._target_monitor_ready(target_monitor)) {
Globals.logger.log('Driver is running, supported monitor connected. Enabling XR effect.');
this._effect_enable();
}
this._running_poller_id = undefined;
return GLib.SOURCE_REMOVE;
} else {
return GLib.SOURCE_CONTINUE;
}
} catch (e) {
Globals.logger.log(`ERROR: BreezyDesktopExtension _poll_for_ready ${e.message}\n${e.stack}`);
this._running_poller_id = undefined;
return GLib.SOURCE_REMOVE;
} else {
return GLib.SOURCE_CONTINUE;
}
}).bind(this));
}
@ -334,29 +340,37 @@ class BreezyDesktopExtension {
}
_write_control(key, value) {
const file = Gio.file_new_for_path('/dev/shm/xr_driver_control');
const stream = file.replace(null, false, Gio.FileCreateFlags.NONE, null);
stream.write(`${key}=${value}`, null);
stream.close(null);
try {
const file = Gio.file_new_for_path('/dev/shm/xr_driver_control');
const stream = file.replace(null, false, Gio.FileCreateFlags.NONE, null);
stream.write(`${key}=${value}`, null);
stream.close(null);
} catch (e) {
Globals.logger.log(`ERROR: BreezyDesktopExtension _write_control ${e.message}\n${e.stack}`);
}
}
_read_state(keys) {
const state = {};
const file = Gio.file_new_for_path('/dev/shm/xr_driver_state');
if (file.query_exists(null)) {
const data = file.load_contents(null);
if (data[0]) {
const bytes = new Uint8Array(data[1]);
const decoder = new TextDecoder();
const contents = decoder.decode(bytes);
try {
const file = Gio.file_new_for_path('/dev/shm/xr_driver_state');
if (file.query_exists(null)) {
const data = file.load_contents(null);
if (data[0]) {
const bytes = new Uint8Array(data[1]);
const decoder = new TextDecoder();
const contents = decoder.decode(bytes);
const lines = contents.split('\n');
for (const line of lines) {
const [k, v] = line.split('=');
if (keys.includes(k)) state[k] = v;
const lines = contents.split('\n');
for (const line of lines) {
const [k, v] = line.split('=');
if (keys.includes(k)) state[k] = v;
}
}
}
} catch (e) {
Globals.logger.log(`ERROR: BreezyDesktopExtension _read_state ${e.message}\n${e.stack}`);
}
return state;
}
@ -378,13 +392,14 @@ class BreezyDesktopExtension {
this._write_control('sbs_mode', value ? 'enable' : 'disable');
if (!this._sbs_mode_update_timeout) {
var attempts = 0;
this._sbs_mode_update_timeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 10000, (() => {
this._sbs_mode_update_timeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 3000, (() => {
if (attempts++ < 3) {
this._write_control('sbs_mode', value ? 'enable' : 'disable');
return GLib.SOURCE_CONTINUE;
}
// the state never updated to reflect our request, revert the setting
Globals.logger.log('Failed to update sbs_mode state, reverting setting');
this.settings.set_boolean('widescreen-mode', !value);
this._sbs_mode_update_timeout = undefined;
return GLib.SOURCE_REMOVE;
@ -404,6 +419,7 @@ class BreezyDesktopExtension {
_update_widescreen_mode_from_state(effect, _pspec) {
// kill our state checker if it's running
if (this._sbs_mode_update_timeout) {
Globals.logger.log_debug('BreezyDesktopExtension _update_widescreen_mode_from_state - clearing timeout');
GLib.source_remove(this._sbs_mode_update_timeout);
this._sbs_mode_update_timeout = undefined;
}
@ -449,8 +465,9 @@ class BreezyDesktopExtension {
this._is_effect_running = false;
if (this._running_poller_id) {
GLib.source_remove(this._running_poller_id);
const poller_id = this._running_poller_id;
this._running_poller_id = undefined;
GLib.source_remove(poller_id);
}
Main.wm.removeKeybinding('recenter-display-shortcut');

View File

@ -112,6 +112,7 @@ function performOptimalModeCheck(displayConfigProxy, connectorName, headsetAsPri
let ourMonitor = undefined;
let monitorToModeIdMap = {};
let bestFitMode = undefined;
const skipScaleUpdate = !!properties['global-scale-required'];
for (let monitor of monitors) {
const [details, availableModes, monProperties] = monitor;
const [connector, vendor, product, monitorSerial] = details;
@ -160,7 +161,8 @@ function performOptimalModeCheck(displayConfigProxy, connectorName, headsetAsPri
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;
const newScale = (!skipScaleUpdate && hasOurMonitor) ? bestFitMode.bestScale : scale;
anyMonitorsChanged |= newScale !== 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
@ -168,7 +170,7 @@ function performOptimalModeCheck(displayConfigProxy, connectorName, headsetAsPri
return [
x,
y,
hasOurMonitor ? bestFitMode.bestScale : scale,
newScale,
transform,
headsetAsPrimary ? hasOurMonitor : primary,
monitors.map((monitor) => {
@ -361,6 +363,7 @@ var MonitorManager = GObject.registerClass({
}
this._asyncRequestsInFlight++;
getMonitorConfig(this._displayConfigProxy, ((result, error) => {
this._asyncRequestsInFlight--;
if (error) {
Globals.logger.log(error);
return;
@ -385,7 +388,7 @@ var MonitorManager = GObject.registerClass({
}
this._monitorProperties = monitorProperties;
if (!!this._changeHookFn) {
if (--this._asyncRequestsInFlight === 0) {
if (this._asyncRequestsInFlight === 0) {
this._changeHookFn();
} else {
Globals.logger.log_debug(`MonitorManager _on_monitors_change: ${this._asyncRequestsInFlight} requests still pending, skipping change hook`);

@ -1 +1 @@
Subproject commit b0080ca844e057d31aae0e70aa6d026059ea304f
Subproject commit 9066a1d1c6b63a71f1d39e4b946ae06cc5c673a6

View File

@ -21,11 +21,15 @@ SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
TMP_DIR=$(mktemp -d --tmpdir=$SCRIPT_DIR/.. -t .breezy-ui-flatpak-XXXXXXXXXX)
OUT_DIR=$SCRIPT_DIR/../out
rm -rf $OUT_DIR
mkdir -p $OUT_DIR
BUILD_ARTIFACT=$OUT_DIR/com.xronlinux.BreezyDesktop-$ARCH.flatpak
if [ -e "$BUILD_ARTIFACT" ]; then
rm $BUILD_ARTIFACT
fi
flatpak-builder --arch $ARCH --disable-rofiles-fuse --disable-cache --force-clean --delete-build-dirs --user $TMP_DIR/build $SCRIPT_DIR/../com.xronlinux.BreezyDesktop.json
flatpak build-export --arch $ARCH $TMP_DIR/export $TMP_DIR/build
flatpak build-bundle --arch $ARCH $TMP_DIR/export $OUT_DIR/com.xronlinux.BreezyDesktop-$ARCH.flatpak com.xronlinux.BreezyDesktop --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo
flatpak build-bundle --arch $ARCH $TMP_DIR/export $BUILD_ARTIFACT com.xronlinux.BreezyDesktop --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo
rm -rf "$TMP_DIR"

@ -1 +1 @@
Subproject commit a96fdeb1557d8cd24e73cb8e9e2559adfa46e3aa
Subproject commit 3f23409b6be154c9c9a7035c6213558a7ef6c84e

View File

@ -5,6 +5,7 @@
<file preprocess="xml-stripblanks">gtk/failed-verification.ui</file>
<file preprocess="xml-stripblanks">gtk/license-dialog.ui</file>
<file preprocess="xml-stripblanks">gtk/no-device.ui</file>
<file preprocess="xml-stripblanks">gtk/no-driver.ui</file>
<file preprocess="xml-stripblanks">gtk/no-extension.ui</file>
<file preprocess="xml-stripblanks">gtk/no-license.ui</file>
<file preprocess="xml-stripblanks">gtk/shortcut-dialog.ui</file>

24
ui/src/gtk/no-driver.ui Normal file
View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<template class="NoDriver" parent="GtkBox">
<property name="orientation">1</property>
<property name="margin-top">20</property>
<property name="margin-bottom">20</property>
<property name="margin-start">20</property>
<property name="margin-end">20</property>
<property name="spacing">20</property>
<child>
<object class="AdwStatusPage">
<property name="title" translatable="true">No driver running</property>
<property name="description" translatable="true">
If you installed via AUR, make sure you ran the recommended post-install command:
systemctl --user enable --now xr-driver.service
Otherwise, please file an issue on GitHub, or create a new thread in the #troubleshooting channel on Discord.
</property>
<property name="width-request">650</property>
</object>
</child>
</template>
</interface>

View File

@ -38,6 +38,7 @@ breezydesktop_sources = [
'licensetierrow.py',
'main.py',
'nodevice.py',
'nodriver.py',
'noextension.py',
'nolicense.py',
'settingsmanager.py',

5
ui/src/nodriver.py Normal file
View File

@ -0,0 +1,5 @@
from gi.repository import Gtk
@Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/no-driver.ui')
class NoDriver(Gtk.Box):
__gtype_name__ = "NoDriver"

View File

@ -20,6 +20,7 @@ class StateManager(GObject.GObject):
}
__gproperties__ = {
'driver-running': (bool, 'Driver Running', 'Whether the driver is running', 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),
'widescreen-mode': (bool, 'Widescreen Mode', 'Whether widescreen mode is enabled', False, GObject.ParamFlags.READWRITE),
@ -53,6 +54,7 @@ class StateManager(GObject.GObject):
def __init__(self):
GObject.GObject.__init__(self)
self.ipc = XRDriverIPC.get_instance()
self.driver_running = False
self.connected_device_name = None
self.license_action_needed = False
self.license_action_needed_seconds = 0
@ -71,6 +73,8 @@ class StateManager(GObject.GObject):
def _refresh_state(self):
self.state = self.ipc.retrieve_driver_state()
self.set_property('driver-running', self.state['ui_view'].get('driver_running'))
new_device_name = StateManager.device_name(self.state)
if self.connected_device_name != new_device_name:
self.connected_device_name = new_device_name
@ -94,12 +98,14 @@ class StateManager(GObject.GObject):
elif self.license_present:
self.set_property('license-present', False)
self.set_property('follow-mode', self.state.get('breezy_desktop_smooth_follow_enabled'))
self.set_property('widescreen-mode', self.state.get('sbs_mode_enabled'))
self.set_property('follow-mode', self.state.get('breezy_desktop_smooth_follow_enabled', False))
self.set_property('widescreen-mode', self.state.get('sbs_mode_enabled', False))
if self.running: threading.Timer(1.0, self._refresh_state).start()
def do_set_property(self, prop, value):
if prop.name == 'driver-running':
self.driver_running = value
if prop.name == 'follow-mode':
self.follow_mode = value
if prop.name == 'widescreen-mode':
@ -112,6 +118,8 @@ class StateManager(GObject.GObject):
self.enabled_features = value
def do_get_property(self, prop):
if prop.name == 'driver-running':
return self.driver_running
if prop.name == 'follow-mode':
return self.follow_mode
if prop.name == 'widescreen-mode':

View File

@ -25,6 +25,7 @@ from .statemanager import StateManager
from .connecteddevice import ConnectedDevice
from .failedverification import FailedVerification
from .nodevice import NoDevice
from .nodriver import NoDriver
from .noextension import NoExtension
from .nolicense import NoLicense
from .verify import verify_installation
@ -49,6 +50,7 @@ class BreezydesktopWindow(Gtk.ApplicationWindow):
self.connected_device = ConnectedDevice()
self.failed_verification = FailedVerification()
self.no_device = NoDevice()
self.no_driver = NoDriver()
self.no_extension = NoExtension()
self.no_license = NoLicense()
@ -77,15 +79,17 @@ class BreezydesktopWindow(Gtk.ApplicationWindow):
if not verify_installation():
self.main_content.append(self.failed_verification)
if not self.state_manager.get_property('license-present'):
if not self.state_manager.driver_running:
self.main_content.append(self.no_driver)
elif not state_manager.connected_device_name:
self.main_content.append(self.no_device)
elif not self.state_manager.license_present:
self.main_content.append(self.no_license)
elif not ExtensionsManager.get_instance().is_installed():
self.main_content.append(self.no_extension)
elif state_manager.connected_device_name:
else:
self.main_content.append(self.connected_device)
self.connected_device.set_device_name(state_manager.connected_device_name)
else:
self.main_content.append(self.no_device)
def _on_license_button_clicked(self, widget):
dialog = LicenseDialog()