From 1b856a6b31b8f8369256612cef6cc242486534a2 Mon Sep 17 00:00:00 2001 From: Raul Benencia Date: Wed, 12 Aug 2026 13:32:16 -0700 Subject: gnome: consolidate workspace-router extension --- .../extension.js | 375 --------------------- .../metadata.json | 10 - 2 files changed, 385 deletions(-) delete mode 100644 .local/share/gnome-shell/extensions/workspace-router-cli@rbenencia.name/extension.js delete mode 100644 .local/share/gnome-shell/extensions/workspace-router-cli@rbenencia.name/metadata.json (limited to '.local/share/gnome-shell/extensions/workspace-router-cli@rbenencia.name') diff --git a/.local/share/gnome-shell/extensions/workspace-router-cli@rbenencia.name/extension.js b/.local/share/gnome-shell/extensions/workspace-router-cli@rbenencia.name/extension.js deleted file mode 100644 index 8a80b8b..0000000 --- a/.local/share/gnome-shell/extensions/workspace-router-cli@rbenencia.name/extension.js +++ /dev/null @@ -1,375 +0,0 @@ -import Gio from 'gi://Gio'; -import GLib from 'gi://GLib'; -import Meta from 'gi://Meta'; -import Shell from 'gi://Shell'; - -import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js'; -import * as Main from 'resource:///org/gnome/shell/ui/main.js'; - -const BUS_NAME = 'name.rbenencia.WorkspaceRouter'; -const OBJECT_PATH = '/name/rbenencia/WorkspaceRouter'; -const INTERFACE_NAME = 'name.rbenencia.WorkspaceRouter'; - -const DBUS_XML = ` - - - - - - - - - - - - - -`; - -const ROUTABLE_WINDOW_TYPES = new Set([ - Meta.WindowType.NORMAL, - Meta.WindowType.DIALOG, - Meta.WindowType.MODAL_DIALOG, -]); - -const WINDOW_RULES = [ - { - name: 'misc', - workspace: 4, - matchAny: [ - {field: 'title', pattern: /(cisco secure client|secure client|anyconnect|vpnui|keepass)/i}, - {field: 'appId', pattern: /(cisco|secureclient|anyconnect|vpnui|keepass)/i}, - {field: 'wmClass', pattern: /(cisco|secureclient|anyconnect|vpnui|keepass)/i}, - {field: 'wmClassInstance', pattern: /(cisco|secureclient|anyconnect|vpnui|keepass)/i}, - ], - }, - { - name: 'main-frame', - workspace: 0, - matchAll: [ - {field: 'appId', pattern: /emacs/i}, - {field: 'title', pattern: /^main$/i}, - ], - }, - { - name: 'browsers', - workspace: 1, - matchAny: [ - {field: 'appId', pattern: /(firefox|chromium|chrome)/i}, - {field: 'wmClass', pattern: /(firefox|chromium|chrome)/i}, - {field: 'wmClassInstance', pattern: /(firefox|chromium|chrome)/i}, - ], - }, - { - name: 'communications-frame', - workspace: 2, - matchAll: [ - {field: 'appId', pattern: /emacs/i}, - {field: 'title', pattern: /^communications$/i}, - ], - }, - { - name: 'communications-apps', - workspace: 2, - matchAny: [ - {field: 'title', pattern: /(webex|slack|communications|notmuch|outlook|elfeed|thunderbird)/i}, - {field: 'appId', pattern: /(webex|slack|outlook|thunderbird)/i}, - {field: 'wmClass', pattern: /(webex|slack|outlook|thunderbird)/i}, - {field: 'wmClassInstance', pattern: /(webex|slack|outlook|thunderbird)/i}, - ], - }, - { - name: 'terminals-frame', - workspace: 3, - matchAll: [ - {field: 'appId', pattern: /emacs/i}, - {field: 'title', pattern: /^terminals$/i}, - ], - }, - { - name: 'terminals-apps', - workspace: 3, - matchAny: [ - {field: 'title', pattern: /(alacritty|kitty|terminal)/i}, - {field: 'appId', pattern: /(alacritty|kitty|console|terminal)/i}, - {field: 'wmClass', pattern: /(alacritty|kitty|terminal)/i}, - {field: 'wmClassInstance', pattern: /(alacritty|kitty|terminal)/i}, - ], - }, - { - name: 'teleport', - workspace: 5, - matchAny: [ - {field: 'title', pattern: /teleport/i}, - {field: 'appId', pattern: /teleport/i}, - {field: 'wmClass', pattern: /teleport/i}, - {field: 'wmClassInstance', pattern: /teleport/i}, - ], - }, - { - name: 'media', - workspace: 8, - matchAny: [ - {field: 'title', pattern: /(youtube|spotify)/i}, - {field: 'appId', pattern: /spotify/i}, - {field: 'wmClass', pattern: /spotify/i}, - {field: 'wmClassInstance', pattern: /spotify/i}, - ], - }, -]; - -function getWindowInfo(window, windowTracker) { - const app = windowTracker.get_window_app(window); - - return { - title: window.get_title() ?? '', - wmClass: window.get_wm_class() ?? '', - wmClassInstance: window.get_wm_class_instance() ?? '', - appId: app?.get_id() ?? '', - appName: app?.get_name() ?? '', - role: window.get_role() ?? '', - monitor: window.get_monitor(), - workspace: window.get_workspace()?.index() ?? null, - }; -} - -function matchCondition(info, condition) { - const value = info[condition.field] ?? ''; - return condition.pattern.test(value); -} - -function ruleMatches(info, rule) { - const matchAll = rule.matchAll ?? []; - const matchAny = rule.matchAny ?? []; - - if (!matchAll.every(condition => matchCondition(info, condition))) - return false; - - if (matchAny.length === 0) - return true; - - return matchAny.some(condition => matchCondition(info, condition)); -} - -function windowInfoForResult(window, info, rule) { - return { - id: window.get_stable_sequence(), - title: info.title, - appId: info.appId, - appName: info.appName, - wmClass: info.wmClass, - wmClassInstance: info.wmClassInstance, - role: info.role, - monitor: info.monitor, - workspace: info.workspace, - route: rule?.name ?? null, - targetWorkspace: rule?.workspace ?? null, - finalMonitor: window.get_monitor(), - finalWorkspace: window.get_workspace()?.index() ?? null, - }; -} - -function isSkipTaskbar(window) { - if (typeof window.is_skip_taskbar === 'function') - return window.is_skip_taskbar(); - - if (typeof window.skip_taskbar === 'function') - return window.skip_taskbar(); - - return Boolean(window.skip_taskbar); -} - -export default class WorkspaceRouterCliExtension extends Extension { - enable() { - this._windowTracker = Shell.WindowTracker.get_default(); - this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(DBUS_XML, this); - this._dbusImpl.export(Gio.DBus.session, OBJECT_PATH); - this._ownName = Gio.DBus.session.own_name( - BUS_NAME, - Gio.BusNameOwnerFlags.REPLACE, - () => {}, - () => log(`Workspace Router CLI lost D-Bus name ${BUS_NAME}`)); - } - - disable() { - if (this._ownName) { - Gio.DBus.session.unown_name(this._ownName); - this._ownName = null; - } - - if (this._dbusImpl) { - this._dbusImpl.unexport(); - this._dbusImpl.run_dispose(); - this._dbusImpl = null; - } - - this._windowTracker = null; - } - - RouteWindowsAsync(_params, invocation) { - try { - invocation.return_value(GLib.Variant.new('(s)', [this._routeWindows()])); - } catch (e) { - logError(e, 'Workspace Router CLI failed to route windows'); - invocation.return_dbus_error(`${INTERFACE_NAME}.Error`, e.message); - } - } - - ListWindowsAsync(_params, invocation) { - try { - invocation.return_value(GLib.Variant.new('(s)', [this._listWindows()])); - } catch (e) { - logError(e, 'Workspace Router CLI failed to list windows'); - invocation.return_dbus_error(`${INTERFACE_NAME}.Error`, e.message); - } - } - - ActivateWindowAsync(params, invocation) { - try { - const [windowId] = params.deepUnpack(); - invocation.return_value(GLib.Variant.new('(s)', [this._activateWindow(windowId)])); - } catch (e) { - logError(e, 'Workspace Router CLI failed to activate window'); - invocation.return_dbus_error(`${INTERFACE_NAME}.Error`, e.message); - } - } - - _routeWindows() { - const result = { - primaryMonitor: Main.layoutManager.primaryIndex, - movedToPrimary: 0, - routed: 0, - skipped: 0, - windows: [], - }; - - for (const window of this._collectWindows()) { - const routeResult = this._routeWindow(window); - - if (!routeResult) { - result.skipped++; - continue; - } - - if (routeResult.movedToPrimary) - result.movedToPrimary++; - - if (routeResult.routed) - result.routed++; - else - result.skipped++; - - result.windows.push(routeResult.window); - } - - return JSON.stringify(result); - } - - _listWindows() { - const windows = this._collectWindows().map(window => { - const info = getWindowInfo(window, this._windowTracker); - const rule = WINDOW_RULES.find(candidate => ruleMatches(info, candidate)); - - return windowInfoForResult(window, info, rule); - }); - - return JSON.stringify(windows); - } - - _activateWindow(windowId) { - const window = this._collectWindows().find(candidate => candidate.get_stable_sequence() === windowId); - - if (!window) - throw new Error(`Window not found: ${windowId}`); - - Main.activateWindow(window); - return JSON.stringify({activated: true, id: windowId}); - } - - _collectWindows() { - const windows = []; - const seen = new Set(); - - for (const actor of global.get_window_actors()) { - const window = actor.meta_window; - - if (!window || seen.has(window) || !this._isRoutableWindow(window)) - continue; - - seen.add(window); - windows.push(window); - } - - return windows; - } - - _routeWindow(window) { - if (!this._isRoutableWindow(window)) - return null; - - const info = getWindowInfo(window, this._windowTracker); - const rule = WINDOW_RULES.find(candidate => ruleMatches(info, candidate)); - const movedToPrimary = this._moveWindowToMonitor(window, Main.layoutManager.primaryIndex); - - if (rule) - this._moveWindowToWorkspace(window, rule.workspace); - - return { - movedToPrimary, - routed: Boolean(rule), - window: windowInfoForResult(window, info, rule), - }; - } - - _moveWindowToMonitor(window, monitorIndex) { - if (monitorIndex === null || monitorIndex === undefined) - return false; - - if (monitorIndex === window.get_monitor()) - return false; - - const wasFullscreen = window.is_fullscreen(); - const maximized = window.get_maximized(); - - if (wasFullscreen) - window.unmake_fullscreen(); - - if (maximized) - window.unmaximize(maximized); - - window.move_to_monitor(monitorIndex); - - if (maximized) - window.maximize(maximized); - - if (wasFullscreen) - window.make_fullscreen(); - - return true; - } - - _moveWindowToWorkspace(window, workspaceIndex) { - this._ensureWorkspace(workspaceIndex); - - const workspace = window.get_workspace(); - if (!workspace || workspace.index() !== workspaceIndex) - window.change_workspace_by_index(workspaceIndex, false); - } - - _ensureWorkspace(index) { - const workspaceManager = global.workspace_manager; - - while (workspaceManager.n_workspaces <= index) - workspaceManager.append_new_workspace(false, global.get_current_time()); - } - - _isRoutableWindow(window) { - if (isSkipTaskbar(window) || window.is_override_redirect()) - return false; - - if (window.is_on_all_workspaces()) - return false; - - return ROUTABLE_WINDOW_TYPES.has(window.get_window_type()); - } -} diff --git a/.local/share/gnome-shell/extensions/workspace-router-cli@rbenencia.name/metadata.json b/.local/share/gnome-shell/extensions/workspace-router-cli@rbenencia.name/metadata.json deleted file mode 100644 index c1b99b5..0000000 --- a/.local/share/gnome-shell/extensions/workspace-router-cli@rbenencia.name/metadata.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "uuid": "workspace-router-cli@rbenencia.name", - "extension-id": "workspace-router-cli", - "name": "Workspace Router CLI", - "description": "Expose Raul's GNOME window routing workflow over D-Bus.", - "shell-version": ["46", "47", "48", "49"], - "url": "https://rbenencia.name", - "session-modes": ["user"], - "version": 1 -} -- cgit v1.2.3