diff --git a/src/dbus/client.ts b/src/dbus/client.ts deleted file mode 100644 index 30373c8..0000000 --- a/src/dbus/client.ts +++ /dev/null @@ -1,47 +0,0 @@ -// imports.gi -import Gio from 'gi://Gio'; - -// --------------------------------------------------------------- [end imports] - -const connect = Gio.DBus.session; -const bus_name = 'org.gnome.Shell'; -const iface_name = 'org.gnome.Shell.Extensions.RoundedWindowCorners'; -const obj_path = '/org/gnome/shell/extensions/RoundedWindowCorners'; - -/** - * Call pick() of DBus service, it will open Inspector from gnome-shell to - * Pick actor on stage. - */ -export function pick() { - connect.call( - bus_name, - obj_path, - iface_name, - 'pick', - null, - null, - Gio.DBusCallFlags.NO_AUTO_START, - -1, - null, - null, - ); -} - -/** - * Connect to 'picked' signal, it will be emit when window is picked - */ -export function on_picked(cb: (wm_instance_class: string) => void) { - const id = connect.signal_subscribe( - bus_name, - iface_name, - 'picked', - obj_path, - null, - Gio.DBusSignalFlags.NONE, - (_conn, _sender, _obj_path, _iface, _signal, params) => { - const val = params.get_child_value(0); - cb(val.get_string()[0]); - connect.signal_unsubscribe(id); - }, - ); -} diff --git a/src/dbus/services.ts b/src/dbus/services.ts deleted file mode 100644 index 9d369ec..0000000 --- a/src/dbus/services.ts +++ /dev/null @@ -1,94 +0,0 @@ -// imports.gi -import type Clutter from 'gi://Clutter'; -import GLib from 'gi://GLib'; -import Gio from 'gi://Gio'; -import Meta from 'gi://Meta'; - -// gnome modules -import {Inspector} from 'resource:///org/gnome/shell/ui/lookingGlass.js'; -import * as Main from 'resource:///org/gnome/shell/ui/main.js'; - -// local modules -import {loadFile} from '../utils/io.js'; -import {_log} from '../utils/log.js'; - -// --------------------------------------------------------------- [end imports] - -const iface = loadFile(import.meta.url, 'iface.xml'); - -export class Services { - DBusImpl = Gio.DBusExportedObject.wrapJSObject(iface, this); - - /** Pick Window for Preferences Page, export to DBus client */ - pick() { - /** Emit `picked` signal, send wm_instance_class of got */ - const _send_wm_class_instance = (wm_instance_class: string) => { - this.DBusImpl.emit_signal( - 'picked', - new GLib.Variant('(s)', [wm_instance_class]), - ); - }; - - // A very interesting way to pick a window: - // 1. Open LookingGlass to mask all event handles of window - // 2. Use inspector to pick window, thats is also lookingGlass do - // 3. Close LookingGlass when done - // It will restore event handles of window - - // Open then hide LookingGlass - const looking_class = Main.createLookingGlass(); - looking_class.open(); - looking_class.hide(); - - // Inspect window now - const inspector = new Inspector(Main.createLookingGlass()); - inspector.connect('target', (me, target, x, y) => { - _log(`${me}: pick ${target} in ${x}, ${y}`); - - // Remove border effect when window is picked. - const effect_name = 'lookingGlass_RedBorderEffect'; - for (const effect of target.get_effects()) { - if (effect.toString().includes(effect_name)) { - target.remove_effect(effect); - } - } - - let actor: Clutter.Actor | null = target; - - // User will pick to a Meta.SurfaceActor in most time, let's find the - // associate Meta.WindowActor - for (let i = 0; i < 2; i++) { - if (actor == null || actor instanceof Meta.WindowActor) { - break; - } - // If picked actor is not a Meta.WindowActor, search it's parent - actor = actor.get_parent(); - } - - if (!(actor instanceof Meta.WindowActor)) { - _send_wm_class_instance('window-not-found'); - return; - } - - _send_wm_class_instance( - actor.metaWindow.get_wm_class_instance() ?? 'window-not-found', - ); - }); - inspector.connect('closed', () => { - // Close LookingGlass When we done - looking_class.close(); - }); - } - - export() { - this.DBusImpl.export( - Gio.DBus.session, - '/org/gnome/shell/extensions/RoundedWindowCorners', - ); - _log('DBus Services exported'); - } - - unexport() { - this.DBusImpl.unexport(); - } -} diff --git a/src/extension.ts b/src/extension.ts index e0d9907..45c1744 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -12,7 +12,7 @@ import {WindowPreview} from 'resource:///org/gnome/shell/ui/windowPreview.js'; import {WorkspaceAnimationController} from 'resource:///org/gnome/shell/ui/workspaceAnimation.js'; // local modules -import {Services} from './dbus/services.js'; +import {WindowPicker} from './preferences/window_picker/service.js'; import {LinearFilterEffect} from './effect/linear_filter_effect.js'; import {disableEffect, enableEffect} from './manager/event_manager.js'; import {connections} from './utils/connections.js'; @@ -32,7 +32,7 @@ export default class RoundedWindowCornersReborn extends Extension { private _orig_prep_workspace_swt!: (workspaceIndices: number[]) => void; private _orig_finish_workspace_swt!: typeof WorkspaceAnimationController.prototype._finishWorkspaceSwitch; - private _services: Services | null = null; + private _windowPicker: WindowPicker | null = null; enable() { init_settings(this.getSettings()); @@ -45,9 +45,9 @@ export default class RoundedWindowCornersReborn extends Extension { this._orig_finish_workspace_swt = WorkspaceAnimationController.prototype._finishWorkspaceSwitch; - this._services = new Services(); + this._windowPicker = new WindowPicker(); - this._services.export(); + this._windowPicker.export(); // Enable rounded corners effects when gnome-shell is ready // @@ -291,7 +291,7 @@ export default class RoundedWindowCornersReborn extends Extension { // Remove the item to open preferences page in background menu UI.RestoreBackgroundMenu(); - this._services?.unexport(); + this._windowPicker?.unexport(); disableEffect(); // Disconnect all signals in global connections.get() @@ -299,7 +299,7 @@ export default class RoundedWindowCornersReborn extends Extension { connections.del(); // Set all props to null - this._services = null; + this._windowPicker = null; _log('Disabled'); diff --git a/src/preferences/widgets/app_row.ts b/src/preferences/widgets/app_row.ts index 99fc9c7..e9b7ea2 100644 --- a/src/preferences/widgets/app_row.ts +++ b/src/preferences/widgets/app_row.ts @@ -3,7 +3,7 @@ import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; import {gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; -import {on_picked, pick} from '../../dbus/client.js'; +import {onPicked, pick} from '../window_picker/client.js'; import {connections} from '../../utils/connections.js'; export class AppRowClass extends Adw.ExpanderRow { @@ -84,7 +84,7 @@ export class AppRowClass extends Adw.ExpanderRow { } on_pick(entry: Adw.EntryRow) { - on_picked(wm_instance_class => { + onPicked(wm_instance_class => { if (wm_instance_class === 'window-not-found') { const win = this.root as unknown as Adw.PreferencesDialog; win.add_toast( diff --git a/src/preferences/window_picker/README.md b/src/preferences/window_picker/README.md new file mode 100644 index 0000000..769f2c2 --- /dev/null +++ b/src/preferences/window_picker/README.md @@ -0,0 +1,21 @@ +# `window_picker` + +The extensions preferences window runs in a separate isolated process which has +no access to GNOME Shell methods. However, the window picking functionality is +based on GNOME Shell's Looking Glass. + +To allow the preferences window to communicate with the main process, the code +in this directory creates a DBus service, which has a method to open the window +picker and a signal to transmit the class of the selected window. + +## `iface.xml` + +Defines the DBus interface for the window picker. + +## `service.ts` + +Contains the implementation of the DBus interface. + +## `client.ts` + +Provides wrapper JavaScript functions around the DBus method calls. diff --git a/src/preferences/window_picker/client.ts b/src/preferences/window_picker/client.ts new file mode 100644 index 0000000..29483be --- /dev/null +++ b/src/preferences/window_picker/client.ts @@ -0,0 +1,49 @@ +/** + * @file This file provides wrapper functions around the DBus window picker + * interface. + */ + +import Gio from 'gi://Gio'; + +const connection = Gio.DBus.session; +const busName = 'org.gnome.Shell'; +const interfaceName = 'org.gnome.Shell.Extensions.RoundedWindowCorners'; +const objectPath = '/org/gnome/shell/extensions/RoundedWindowCorners'; + +/** Open the window picker and select a window. */ +export function pick() { + connection.call( + busName, + objectPath, + interfaceName, + 'pick', + null, + null, + Gio.DBusCallFlags.NO_AUTO_START, + -1, + null, + null, + ); +} + +/** + * Connect a callback to the `picked` signal, which is emitted when a window + * is picked. + * + * @param callback - The function to execute when the window is picked. + */ +export function onPicked(callback: (wmInstanceClass: string) => void) { + const id = connection.signal_subscribe( + busName, + interfaceName, + 'picked', + objectPath, + null, + Gio.DBusSignalFlags.NONE, + (_conn, _sender, _objectPath, _iface, _signal, params) => { + const val = params.get_child_value(0); + callback(val.get_string()[0]); + connection.signal_unsubscribe(id); + }, + ); +} diff --git a/src/dbus/iface.xml b/src/preferences/window_picker/iface.xml similarity index 50% rename from src/dbus/iface.xml rename to src/preferences/window_picker/iface.xml index 4441551..6cb7756 100644 --- a/src/dbus/iface.xml +++ b/src/preferences/window_picker/iface.xml @@ -1,10 +1,8 @@ - - - + diff --git a/src/preferences/window_picker/service.ts b/src/preferences/window_picker/service.ts new file mode 100644 index 0000000..753d581 --- /dev/null +++ b/src/preferences/window_picker/service.ts @@ -0,0 +1,91 @@ +/** + * @file This file contains the implementation of the DBus interface for the + * window picker. See the {@link WindowPicker} class for more information. + */ + +import GLib from 'gi://GLib'; +import Gio from 'gi://Gio'; +import Meta from 'gi://Meta'; + +import {Inspector} from 'resource:///org/gnome/shell/ui/lookingGlass.js'; +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; + +import {loadFile} from '../../utils/io.js'; +import {_log} from '../../utils/log.js'; + +const iface = loadFile(import.meta.url, 'iface.xml'); + +/** + * This class provides the implementation of the DBus interface for the window + * picker. It implements a single method - `pick` - which opens the window picker + * and allows the user to select a window. + */ +export class WindowPicker { + #dbus = Gio.DBusExportedObject.wrapJSObject(iface, this); + + /** Emit the wm_class of the picked window to the `picked` signal. */ + #sendPickedWindow(wmClass: string) { + this.#dbus.emit_signal('picked', new GLib.Variant('(s)', [wmClass])); + } + + /** + * Open the window picker and select a window. + * + * This uses the window picker from GNOME's Looking Glass. This is the + * easiest way to pick a window, and this is also what's used by other + * extensions such as Blur my Shell. + */ + pick() { + const lookingGlass = Main.createLookingGlass(); + const inspector = new Inspector(lookingGlass); + + inspector.connect('target', (me, target, x, y) => { + _log(`${me}: pick ${target} in ${x}, ${y}`); + + // Remove the red border effect when the window is picked. + const effectName = 'lookingGlass_RedBorderEffect'; + for (const effect of target.get_effects()) { + if (effect.toString().includes(effectName)) { + target.remove_effect(effect); + } + } + + let actor = target; + + // If the picked actor is not a Meta.WindowActor, which happens + // often since it's usually a Meta.SurfaceActor, try to find its + // parent which is a Meta.WindowActor. + for (let i = 0; i < 2; i++) { + if (actor == null || actor instanceof Meta.WindowActor) { + break; + } + actor = actor.get_parent(); + } + + if (!(actor instanceof Meta.WindowActor)) { + this.#sendPickedWindow('window-not-found'); + return; + } + + this.#sendPickedWindow( + actor.metaWindow.get_wm_class_instance() ?? 'window-not-found', + ); + }); + + inspector.connect('closed', () => { + lookingGlass.close(); + }); + } + + export() { + this.#dbus.export( + Gio.DBus.session, + '/org/gnome/shell/extensions/RoundedWindowCorners', + ); + _log('DBus Service exported'); + } + + unexport() { + this.#dbus.unexport(); + } +}