Merge branch 'main' into docker
This commit is contained in:
commit
c0a04e29f7
21
README.md
21
README.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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'))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>")
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue