From 3ba8a981691efff90c8f69afcc164b06a6747f23 Mon Sep 17 00:00:00 2001
From: wheaney <42350981+wheaney@users.noreply.github.com>
Date: Thu, 11 Apr 2024 22:35:02 -0700
Subject: [PATCH] Attempt to detect the correct monitor, fix extensions
management of resources and effect state
---
.../org.gnome.Mutter.DisplayConfig.xml | 453 ++++++++++++++++++
.../breezydesktop@org.xronlinux/extension.js | 125 +++--
.../monitormanager.js | 162 +++++++
gnome/breezydesktop@org.xronlinux/xrEffect.js | 9 +-
4 files changed, 711 insertions(+), 38 deletions(-)
create mode 100644 gnome/breezydesktop@org.xronlinux/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml
create mode 100644 gnome/breezydesktop@org.xronlinux/monitormanager.js
diff --git a/gnome/breezydesktop@org.xronlinux/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml b/gnome/breezydesktop@org.xronlinux/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml
new file mode 100644
index 0000000..50b07fa
--- /dev/null
+++ b/gnome/breezydesktop@org.xronlinux/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml
@@ -0,0 +1,453 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gnome/breezydesktop@org.xronlinux/extension.js b/gnome/breezydesktop@org.xronlinux/extension.js
index 2b0c5fe..c72eedd 100644
--- a/gnome/breezydesktop@org.xronlinux/extension.js
+++ b/gnome/breezydesktop@org.xronlinux/extension.js
@@ -7,35 +7,72 @@ import St from 'gi://St';
import { CursorManager } from './cursormanager.js';
import Globals from './globals.js';
+import MonitorManager from './monitormanager.js';
import { IPC_FILE_PATH, XREffect } from './xrEffect.js';
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
+const SUPPORTED_MONITOR_PRODUCTS = [
+ 'VITURE',
+ 'Air',
+ 'MetaMonitor' // nested mode dummy monitor
+];
+
export default class BreezyDesktopExtension extends Extension {
constructor(metadata, uuid) {
super(metadata, uuid);
// Set/destroyed by enable/disable
- this._cursorManager = null;
+ this._cursor_manager = null;
+ this._monitor_manager = null;
this._xr_effect = null;
this._overlay = null;
+ this._target_monitor = null;
+ this._is_effect_running = false;
}
enable() {
- if (!this._check_driver_running()) {
- this._running_poller_id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, (() => {
- if (this._check_driver_running()) {
- this._effect_enable();
- this._running_poller_id = undefined;
- return GLib.SOURCE_REMOVE;
- } else {
- return GLib.SOURCE_CONTINUE;
- }
- }).bind(this));
- } else {
- this._effect_enable();
+ Globals.extension_dir = this.path;
+ this._monitor_manager = new MonitorManager(this.path);
+ this._monitor_manager.setChangeHook(this._monitors_changed.bind(this));
+ this._monitor_manager.enable();
+
+ this._poll_for_ready();
+ }
+
+ _poll_for_ready() {
+ this._running_poller_id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, (() => {
+ const is_driver_running = this._check_driver_running();
+ if (is_driver_running && this._target_monitor) {
+ console.log('Driver is running, supported monitor connected. Enabling XR effect.');
+ this._effect_enable();
+ this._running_poller_id = undefined;
+ return GLib.SOURCE_REMOVE;
+ } else {
+ console.log(`Not ready: driver_running ${is_driver_running}, target_monitor ${JSON.stringify(this._target_monitor)}`);
+ return GLib.SOURCE_CONTINUE;
+ }
+ }).bind(this));
+ }
+
+ _find_supported_monitor() {
+ const target_monitor_id = this._monitor_manager.getMonitorPropertiesList()
+ .find(monitor => SUPPORTED_MONITOR_PRODUCTS.includes(monitor.product))?.index;
+ if (target_monitor_id !== undefined) {
+ return this._monitor_manager.getMonitors()[target_monitor_id];
}
+
+ return null;
+ }
+
+ _monitors_changed() {
+ if (this._is_effect_running) {
+ console.log('Monitors changed, disabling effect');
+ this._effect_disable();
+ }
+ this._target_monitor = this._find_supported_monitor();
+ this._poll_for_ready();
}
_check_driver_running() {
@@ -44,14 +81,9 @@ export default class BreezyDesktopExtension extends Extension {
}
_effect_enable() {
- if (!Globals.extension_dir) Globals.extension_dir = this.metadata.path;
-
- if (!this._cursorManager) this._cursorManager = new CursorManager(Main.layoutManager.uiGroup);
- this._cursorManager.enable();
-
- if (!this._overlay) {
- const monitors = Main.layoutManager.monitors;
- this._target_monitor = monitors[monitors.length-1];
+ if (!this._is_effect_running) {
+ this._cursor_manager = new CursorManager(Main.layoutManager.uiGroup);
+ this._cursor_manager.enable();
this._overlay = new St.Bin({ style: 'background-color: rgba(0, 0, 0, 1);'});
this._overlay.opacity = 255;
@@ -60,36 +92,57 @@ export default class BreezyDesktopExtension extends Extension {
const overlayContent = new Clutter.Actor({clip_to_allocation: true});
const uiClone = new Clutter.Clone({ source: Main.layoutManager.uiGroup, clip_to_allocation: true });
+ uiClone.x = -this._target_monitor.x;
+ uiClone.y = -this._target_monitor.y;
overlayContent.add_actor(uiClone);
this._overlay.set_child(overlayContent);
global.stage.insert_child_above(this._overlay, null);
Shell.util_set_hidden_from_pick(this._overlay, true);
-
- uiClone.x = -this._target_monitor.x;
- uiClone.y = -this._target_monitor.y;
- }
-
- if (!this._xr_effect) {
+
this._xr_effect = new XREffect({
target_monitor: this._target_monitor,
target_framerate: 60
});
+
+ this._overlay.add_effect_with_name('xr-desktop', this._xr_effect);
+ Meta.disable_unredirect_for_display(global.display);
+
+ this._is_effect_running = true;
+ }
+ }
+
+ _effect_disable() {
+ if (this._running_poller_id) GLib.source_remove(this._running_poller_id);
+
+ Meta.enable_unredirect_for_display(global.display);
+ this._overlay.remove_effect_by_name('xr-desktop');
+ if (this._xr_effect) {
+ this._xr_effect.unref();
+ this._xr_effect = null;
}
- this._overlay.add_effect_with_name('xr-desktop', this._xr_effect);
- Meta.disable_unredirect_for_display(global.display);
+ if (this._overlay) {
+ global.stage.remove_child(this._overlay);
+ this._overlay.destroy();
+ this._overlay = null;
+ }
+
+ if (this._cursor_manager) {
+ this._cursor_manager.disable();
+ this._cursor_manager = null;
+ }
+
+ this._is_effect_running = false;
}
disable() {
- if (this._running_poller_id) {
- GLib.source_remove(this._running_poller_id);
- } else {
- Meta.enable_unredirect_for_display(global.display);
- this._overlay.remove_effect_by_name('xr-desktop');
- this._cursorManager.disable();
- this._cursorManager = null;
+ this._effect_disable();
+ this._target_monitor = null;
+ if (this._monitor_manager) {
+ this._monitor_manager.disable();
+ this._monitor_manager = null;
}
}
}
diff --git a/gnome/breezydesktop@org.xronlinux/monitormanager.js b/gnome/breezydesktop@org.xronlinux/monitormanager.js
new file mode 100644
index 0000000..2e979e4
--- /dev/null
+++ b/gnome/breezydesktop@org.xronlinux/monitormanager.js
@@ -0,0 +1,162 @@
+// Taken from https://github.com/jkitching/soft-brightness-plus
+//
+// Copyright (C) 2019, 2021 Philippe Troin (F-i-f on Github)
+// Copyright (C) 2023 Joel Kitching (jkitching on Github)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import Gio from 'gi://Gio';
+
+import * as Main from 'resource:///org/gnome/shell/ui/main.js';
+
+let cachedDisplayConfigProxy = null;
+
+function getDisplayConfigProxy(extPath) {
+ if (cachedDisplayConfigProxy == null) {
+ let xml = null;
+ const file = Gio.File.new_for_path(extPath + '/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml');
+ try {
+ const [ok, bytes] = file.load_contents(null);
+ if (ok) {
+ xml = new TextDecoder().decode(bytes);
+ }
+ } catch (e) {
+ console.error('failed to load DisplayConfig interface XML');
+ throw e;
+ }
+ cachedDisplayConfigProxy = Gio.DBusProxy.makeProxyWrapper(xml);
+ }
+ return cachedDisplayConfigProxy;
+}
+
+export function newDisplayConfig(extPath, callback) {
+ const DisplayConfigProxy = getDisplayConfigProxy(extPath);
+ new DisplayConfigProxy(
+ Gio.DBus.session,
+ 'org.gnome.Mutter.DisplayConfig',
+ '/org/gnome/Mutter/DisplayConfig',
+ callback
+ );
+}
+
+export function getMonitorConfig(displayConfigProxy, callback) {
+ displayConfigProxy.GetResourcesRemote((result) => {
+ if (result.length <= 2) {
+ callback(null, 'Cannot get DisplayConfig: No outputs in GetResources()');
+ } else {
+ const monitors = [];
+ for (let i = 0; i < result[2].length; i++) {
+ const output = result[2][i];
+ if (output.length <= 7) {
+ callback(null, 'Cannot get DisplayConfig: No properties on output #' + i);
+ return;
+ }
+ const props = output[7];
+ const displayName = props['display-name'].get_string()[0];
+ const connectorName = output[4];
+ if (!displayName || displayName == '') {
+ const displayName = 'Monitor on output ' + connectorName;
+ }
+ const vendor = props['vendor'].get_string()[0];
+ const product = props['product'].get_string()[0];
+ const serial = props['serial'].get_string()[0];
+ monitors.push([displayName, connectorName, vendor, product, serial]);
+ }
+ callback(monitors, null);
+ }
+ });
+}
+
+// Monitor change handling
+export default class MonitorManager {
+ constructor(extPath) {
+ this._extPath = extPath;
+
+ this._monitorsChangedConnection = null;
+ this._displayConfigProxy = null;
+ this._backendManager = null;
+ this._monitorProperties = null;
+ this._changeHookFn = null;
+ }
+
+ enable() {
+ this._backendManager = global.backend.get_monitor_manager();
+ newDisplayConfig(this._extPath, (proxy, error) => {
+ if (error) {
+ return;
+ }
+ this._displayConfigProxy = proxy;
+ this._on_monitors_change();
+ });
+
+ this._monitorsChangedConnection = Main.layoutManager.connect('monitors-changed', this._on_monitors_change.bind(this));
+ }
+
+ disable() {
+ Main.layoutManager.disconnect(this._monitorsChangedConnection);
+
+ this._monitorsChangedConnection = null;
+ this._displayConfigProxy = null;
+ this._backendManager = null;
+ this._monitorProperties = null;
+ this._changeHookFn = null;
+ }
+
+ setChangeHook(fn) {
+ this._changeHookFn = fn;
+ }
+
+ setPostCallback(callback) {
+ this._postCallback = callback;
+ }
+
+ getMonitors() {
+ return Main.layoutManager.monitors;
+ }
+
+ getMonitorPropertiesList() {
+ return this._monitorProperties;
+ }
+
+ _on_monitors_change() {
+ if (this._displayConfigProxy == null) {
+ return;
+ }
+ getMonitorConfig(this._displayConfigProxy, (result, error) => {
+ if (error) {
+ return;
+ }
+ const monitorProperties = [];
+ for (let i = 0; i < result.length; i++) {
+ const [monitorName, connectorName, vendor, product, serial] = result[i];
+ const monitorIndex = this._backendManager.get_monitor_for_connector(connectorName);
+ console.log(`\n\nFound monitor ${monitorName}, vendor ${vendor}, product ${product}, serial ${serial}, connector ${connectorName}, index ${monitorIndex}\n\n`);
+ if (monitorIndex >= 0) {
+ monitorProperties[monitorIndex] = {
+ index: monitorIndex,
+ name: monitorName,
+ vendor: vendor,
+ product: product,
+ serial: serial,
+ connector: connectorName
+ };
+ }
+ }
+ this._monitorProperties = monitorProperties;
+ if (this._changeHookFn !== null) {
+ this._changeHookFn();
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/gnome/breezydesktop@org.xronlinux/xrEffect.js b/gnome/breezydesktop@org.xronlinux/xrEffect.js
index 5900261..5fe8e33 100644
--- a/gnome/breezydesktop@org.xronlinux/xrEffect.js
+++ b/gnome/breezydesktop@org.xronlinux/xrEffect.js
@@ -204,12 +204,12 @@ export const XREffect = GObject.registerClass({
this.setIntermittentUniformVariables = setIntermittentUniformVariables.bind(this);
this.setIntermittentUniformVariables();
- GLib.timeout_add(GLib.PRIORITY_DEFAULT, this._frametime, () => {
+ this._redraw_timeout_id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, this._frametime, () => {
if ((now - lastPaint) > frametime) global.stage.queue_redraw();
return GLib.SOURCE_CONTINUE;
});
- GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, (() => {
+ this._uniforms_timeout_id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, (() => {
this.setIntermittentUniformVariables();
return GLib.SOURCE_CONTINUE;
}).bind(this));
@@ -235,4 +235,9 @@ export const XREffect = GObject.registerClass({
}
this._last_paint = now;
}
+
+ vfunc_dispose() {
+ if (this._redraw_timeout_id) GLib.source_remove(this._redraw_timeout_id);
+ if (this._uniforms_timeout_id) GLib.source_remove(this._uniforms_timeout_id);
+ }
});
\ No newline at end of file