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