Merge branch 'main' into docker

This commit is contained in:
Wayne Heaney 2024-07-18 17:37:41 -07:00 committed by GitHub
commit c0a04e29f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 142 additions and 28 deletions

View File

@ -16,11 +16,17 @@ There are two installations at the moment. **Note: Only install one of these at
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.
### GNOME Setup
1. Ensure you have the latest graphics drivers installed for your distro.
2. 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`)
3. Run the setup script (e.g. `~/Downloads/breezy_gnome_setup`)
4. You'll have an application called `Breezy Desktop` installed. Launch that and follow any instructions. You will need to log out and back in at least once to get the GNOME extension working.
5. For a double-wide screen, enable "widescreen mode" using the toggle in the Breezy Desktop application. **Note: this can be significantly more resource intensive than non-widescreen, you may notice performance dips on older hardware**
For the best performance, ensure you have the latest graphics drivers installed for your distro.
#### Arch Linux
Breezy GNOME is in AUR (but not pacman, yet). To install: `yay -S breezy-desktop-gnome-git`
#### 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`)
### 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:
@ -29,7 +35,10 @@ A workable solution (with some [QoL improvements needed](#upcoming-features)) is
3. Launch the nested GNOME Shell using `MUTTER_DEBUG_DUMMY_MODE_SPECS="1920x1080@60" dbus-run-session -- gnome-shell --nested`
### Breezy GNOME Usage
All controls are provided through the Breezy Desktop application. You can also configure keyboard shortcuts for the most common toggle actions. The Breezy Desktop app doesn't have to be running to use the virtual desktop or the keyboard shortcuts once you've configured everything to your liking.
After setup, you'll have an application called `Breezy Desktop` installed. Launch that and follow any instructions. You will need to log out and back in at least once to get the GNOME extension working. You can also configure keyboard shortcuts for the most common toggle actions. The Breezy Desktop app doesn't have to be running to use the virtual desktop or the keyboard shortcuts once you've configured everything to your liking.
For a double-wide screen, enable "widescreen mode" using the toggle in the Breezy Desktop application. **Note: this can be significantly more resource intensive than non-widescreen, you may notice performance dips on older hardware.**
### Upcoming Features
1. Port to GNOME 43/44

View File

@ -1,5 +1,4 @@
import Clutter from 'gi://Clutter';
import GLib from 'gi://GLib';
import Meta from 'gi://Meta';
import * as PointerWatcher from 'resource:///org/gnome/shell/ui/pointerWatcher.js';
import { MouseSpriteContent } from './cursor.js';

View File

@ -71,6 +71,7 @@ export default class BreezyDesktopExtension extends Extension {
this._monitor_manager = new MonitorManager({
use_optimal_monitor_config: this.settings.get_boolean('use-optimal-monitor-config'),
headset_as_primary: this.settings.get_boolean('headset-as-primary'),
use_highest_refresh_rate: this.settings.get_boolean('use-highest-refresh-rate'),
extension_path: this.path
});
this._monitor_manager.setChangeHook(this._handle_monitor_change.bind(this));
@ -120,7 +121,7 @@ export default class BreezyDesktopExtension extends Extension {
const target_monitor = this._monitor_manager.getMonitorPropertiesList()?.find(
monitor => SUPPORTED_MONITOR_PRODUCTS.includes(monitor.product));
if (target_monitor !== undefined) {
Globals.logger.log_debug(`BreezyDesktopExtension _find_supported_monitor - Identified supported monitor: ${target_monitor.connector}`);
Globals.logger.log(`Identified supported monitor: ${target_monitor.product} on ${target_monitor.connector}`);
return {
monitor: this._monitor_manager.getMonitors()[target_monitor.index],
connector: target_monitor.connector,
@ -151,8 +152,11 @@ export default class BreezyDesktopExtension extends Extension {
// A false result means we'll expect _handle_monitor_change to be triggered, so active polling
// can be disabled.
_target_monitor_ready(target_monitor) {
return target_monitor.is_dummy ||
!this._monitor_manager.needsOptimalModeCheck(target_monitor.connector);
if (target_monitor.is_dummy) return true;
const needs_sbs_mode_switch = this.settings.get_boolean('fast-sbs-mode-switching') &&
this._needs_widescreen_monitor_update();
return !needs_sbs_mode_switch && !this._monitor_manager.needsOptimalModeCheck(target_monitor.connector);
}
_setup() {
@ -213,7 +217,7 @@ export default class BreezyDesktopExtension extends Extension {
const widescreen_setting_enabled = this.settings.get_boolean('widescreen-mode');
if (widescreen_setting_enabled !== sbs_enabled) {
Globals.logger.log_debug('BreezyDesktopExtension _needs_widescreen_monitor_update - true');
this._write_control('sbs_mode', widescreen_setting_enabled ? 'enable' : 'disable');
this._request_sbs_mode_change(widescreen_setting_enabled);
return true;
}
@ -259,7 +263,10 @@ export default class BreezyDesktopExtension extends Extension {
});
this._update_follow_threshold(this.settings);
this._update_widescreen_mode_from_settings(this.settings);
// this gets triggered before _effect_enable if in fast-sbs-mode-switching mode
if (!this.settings.get_boolean('fast-sbs-mode-switching'))
this._update_widescreen_mode_from_settings(this.settings);
this._widescreen_mode_effect_state_connection = this._xr_effect.connect('notify::widescreen-mode-state', this._update_widescreen_mode_from_state.bind(this));
this._supported_device_detected_connected = this._xr_effect.connect('notify::supported-device-detected', this._handle_supported_device_change.bind(this));
@ -362,16 +369,41 @@ export default class BreezyDesktopExtension extends Extension {
if (value !== undefined) this._write_control('breezy_desktop_follow_threshold', value);
}
// requests sbs_mode change and monitors to ensure the state reflects the setting
_request_sbs_mode_change(value) {
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, (() => {
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
this.settings.set_boolean('widescreen-mode', !value);
this._sbs_mode_update_timeout = undefined;
return GLib.SOURCE_REMOVE;
}).bind(this));
}
}
_update_widescreen_mode_from_settings(settings, event) {
const value = settings.get_boolean('widescreen-mode');
Globals.logger.log_debug(`BreezyDesktopExtension _update_widescreen_mode_from_settings ${value}`);
if (value !== undefined && value !== this._xr_effect.widescreen_mode_state)
this._write_control('sbs_mode', value ? 'enable' : 'disable');
else
if (value !== undefined && value !== this._xr_effect.widescreen_mode_state) {
this._request_sbs_mode_change(value);
} else
Globals.logger.log_debug('effect.widescreen_mode_state already matched setting');
}
_update_widescreen_mode_from_state(effect, _pspec) {
// kill our state checker if it's running
if (this._sbs_mode_update_timeout) {
GLib.source_remove(this._sbs_mode_update_timeout);
this._sbs_mode_update_timeout = undefined;
}
const value = effect.widescreen_mode_state;
Globals.logger.log_debug(`BreezyDesktopExtension _update_widescreen_mode_from_state ${value}`);
if (value !== this.settings.get_boolean('widescreen-mode'))

View File

@ -86,7 +86,7 @@ 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) {
function performOptimalModeCheck(displayConfigProxy, connectorName, headsetAsPrimary, useHighestRefreshRate, callback) {
Globals.logger.log_debug(`monitormanager.js performOptimalModeCheck for ${connectorName}`);
displayConfigProxy.GetCurrentStateRemote((result, error) => {
if (error) {
@ -101,10 +101,19 @@ function performOptimalModeCheck(displayConfigProxy, connectorName, headsetAsPri
let monitorToModeIdMap = {};
let bestFitMode = undefined;
for (let monitor of monitors) {
const [details, modes, monProperties] = monitor;
const [details, availableModes, monProperties] = monitor;
const [connector, vendor, product, monitorSerial] = details;
const isOurMonitor = connector == connectorName;
if (isOurMonitor) ourMonitor = monitor;
let modes = availableModes;
if (isOurMonitor) {
ourMonitor = monitor;
if (!useHighestRefreshRate) {
const currentMode = modes.find((mode) => !!mode[6]['is-current']);
// filter modes to only include the current refresh rate
modes = availableModes.filter((mode) => mode[3] === currentMode[3]);
}
}
for (let mode of modes) {
const [modeId, width, height, refreshRate, preferredScale, supportedScales, modeProperites] = mode;
@ -199,6 +208,13 @@ export const MonitorManager = GObject.registerClass({
GObject.ParamFlags.READWRITE,
true
),
'use-highest-refresh-rate': GObject.ParamSpec.boolean(
'use-highest-refresh-rate',
'Use highest refresh rate',
'Set the highest refresh rate which choosing optimal configs',
GObject.ParamFlags.READWRITE,
true
),
'headset-as-primary': GObject.ParamSpec.boolean(
'headset-as-primary',
'Use headset as primary monitor',
@ -272,7 +288,7 @@ export const MonitorManager = GObject.registerClass({
}
if (this._needsConfigCheck) {
performOptimalModeCheck(this._displayConfigProxy, monitorConnector, this.headset_as_primary, ((configChanged, error) => {
performOptimalModeCheck(this._displayConfigProxy, monitorConnector, this.headset_as_primary, this.use_highest_refresh_rate, ((configChanged, error) => {
this._needsConfigCheck = false;
if (error) {
Globals.logger.log(`Failed to switch to optimal mode for monitor ${monitorConnector}: ${error}`);
@ -309,7 +325,7 @@ export const MonitorManager = GObject.registerClass({
for (let i = 0; i < result.length; i++) {
const [monitorName, connectorName, vendor, product, serial, refreshRate] = result[i];
const monitorIndex = this._backendManager.get_monitor_for_connector(connectorName);
Globals.logger.log(`Found monitor ${monitorName}, vendor ${vendor}, product ${product}, serial ${serial}, connector ${connectorName}, index ${monitorIndex}`);
Globals.logger.log_debug(`Found monitor ${monitorName}, vendor ${vendor}, product ${product}, serial ${serial}, connector ${connectorName}, index ${monitorIndex}`);
if (monitorIndex >= 0) {
monitorProperties[monitorIndex] = {
index: monitorIndex,

View File

@ -118,6 +118,24 @@
Automatically set the headset as the primary display upon connection
</description>
</key>
<key name="use-highest-refresh-rate" type="b">
<default>
true
</default>
<summary>Use highest refresh rate</summary>
<description>
Automatically set the highest refresh rate upon connection
</description>
</key>
<key name="fast-sbs-mode-switching" type="b">
<default>
true
</default>
<summary>Fast SBS mode switching</summary>
<description>
Enable fast SBS mode switching
</description>
</key>
<key name="disable-anti-aliasing" type="b">
<default>
false

View File

@ -31,6 +31,8 @@ class ConnectedDevice(Gtk.Box):
toggle_follow_shortcut_label = Gtk.Template.Child()
headset_as_primary_switch = Gtk.Template.Child()
use_optimal_monitor_config_switch = Gtk.Template.Child()
use_highest_refresh_rate_switch = Gtk.Template.Child()
fast_sbs_mode_switch = Gtk.Template.Child()
movement_look_ahead_scale = Gtk.Template.Child()
movement_look_ahead_adjustment = Gtk.Template.Child()
@ -52,6 +54,8 @@ class ConnectedDevice(Gtk.Box):
self.reassign_toggle_follow_shortcut_button,
self.headset_as_primary_switch,
self.use_optimal_monitor_config_switch,
self.use_highest_refresh_rate_switch,
self.fast_sbs_mode_switch,
self.movement_look_ahead_scale
]
@ -66,6 +70,8 @@ class ConnectedDevice(Gtk.Box):
self.settings.bind('curved-display', self.curved_display_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
self.settings.bind('headset-as-primary', self.headset_as_primary_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
self.settings.bind('use-optimal-monitor-config', self.use_optimal_monitor_config_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
self.settings.bind('use-highest-refresh-rate', self.use_highest_refresh_rate_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
self.settings.bind('fast-sbs-mode-switching', self.fast_sbs_mode_switch, 'active', Gio.SettingsBindFlags.DEFAULT)
self.settings.bind('look-ahead-override', self.movement_look_ahead_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT)
bind_shortcut_settings(self.get_parent(), [
@ -136,8 +142,10 @@ class ConnectedDevice(Gtk.Box):
def _refresh_use_optimal_monitor_config(self, switch, param):
self.headset_as_primary_switch.set_sensitive(switch.get_active())
self.use_highest_refresh_rate_switch.set_sensitive(switch.get_active())
if not switch.get_active():
self.headset_as_primary_switch.set_active(False)
self.use_highest_refresh_rate_switch.set_active(False)
def set_device_name(self, name):
self.device_label.set_markup(f"<b>{name}</b>")

View File

@ -328,6 +328,17 @@
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="true">Use highest refresh rate</property>
<property name="subtitle" translatable="true">Refresh rate may affect performance, disable this to set it manually.</property>
<child>
<object class="GtkSwitch" id="use_highest_refresh_rate_switch">
<property name="valign">3</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="true">Always primary display</property>
@ -339,6 +350,17 @@
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="true">Fast SBS mode switching</property>
<property name="subtitle" translatable="true">Switches glasses to SBS mode immediately when plugged in, if widescreen mode is on. May cause instability.</property>
<child>
<object class="GtkSwitch" id="fast_sbs_mode_switch">
<property name="valign">3</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="true">Movement look-ahead</property>

View File

@ -21,6 +21,7 @@ import gi
import logging
import os
import sys
import argparse
from logging.handlers import TimedRotatingFileHandler
@ -58,13 +59,14 @@ XRDriverIPC.set_instance(XRDriverIPC(logger))
class BreezydesktopApplication(Adw.Application):
"""The main application singleton class."""
def __init__(self):
def __init__(self, skip_verification):
super().__init__(application_id='com.xronlinux.BreezyDesktop',
flags=Gio.ApplicationFlags.DEFAULT_FLAGS)
self.create_action('quit', self.on_quit_action, ['<primary>q'])
self.create_action('about', self.on_about_action)
self.create_action('license', self.on_license_action)
self.create_action('reset_driver', self.on_reset_driver_action)
self._skip_verification = skip_verification
def do_activate(self):
"""Called when the application is activated.
@ -74,7 +76,7 @@ class BreezydesktopApplication(Adw.Application):
"""
win = self.props.active_window
if not win:
win = BreezydesktopWindow(application=self)
win = BreezydesktopWindow(self._skip_verification, application=self)
win.connect('close-request', lambda *_: self.on_quit_action())
win.connect('destroy', lambda *_: self.on_quit_action())
win.present()
@ -125,5 +127,9 @@ class BreezydesktopApplication(Adw.Application):
def main(version):
app = BreezydesktopApplication()
return app.run(sys.argv)
parser = argparse.ArgumentParser()
parser.add_argument("-sv", "--skip-verification", action="store_true")
args = parser.parse_args()
app = BreezydesktopApplication(args.skip_verification)
return app.run(None)

View File

@ -37,7 +37,7 @@ class BreezydesktopWindow(Gtk.ApplicationWindow):
license_action_needed_banner = Gtk.Template.Child()
missing_breezy_features_banner = Gtk.Template.Child()
def __init__(self, **kwargs):
def __init__(self, skip_verification, **kwargs):
super().__init__(**kwargs)
self.state_manager = StateManager.get_instance()
@ -57,6 +57,8 @@ class BreezydesktopWindow(Gtk.ApplicationWindow):
self._handle_state_update(self.state_manager, None)
self._skip_verification = skip_verification
self.connect("destroy", self._on_window_destroy)
def _handle_state_update(self, state_manager, val):
@ -71,9 +73,11 @@ class BreezydesktopWindow(Gtk.ApplicationWindow):
for child in self.main_content:
self.main_content.remove(child)
if not verify_installation():
self.main_content.append(self.failed_verification)
elif not self.state_manager.get_property('license-present'):
if not self._skip_verification:
if not verify_installation():
self.main_content.append(self.failed_verification)
if not self.state_manager.get_property('license-present'):
self.main_content.append(self.no_license)
elif not ExtensionsManager.get_instance().is_installed():
self.main_content.append(self.no_extension)