diff options
Diffstat (limited to 'js/misc/introspect.js')
-rw-r--r-- | js/misc/introspect.js | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/js/misc/introspect.js b/js/misc/introspect.js new file mode 100644 index 0000000..bae7f63 --- /dev/null +++ b/js/misc/introspect.js @@ -0,0 +1,242 @@ +/* exported IntrospectService */ +const { Gio, GLib, Meta, Shell, St } = imports.gi; + +const INTROSPECT_SCHEMA = 'org.gnome.shell'; +const INTROSPECT_KEY = 'introspect'; +const APP_ALLOWLIST = ['org.freedesktop.impl.portal.desktop.gtk']; + +const INTROSPECT_DBUS_API_VERSION = 3; + +const { loadInterfaceXML } = imports.misc.fileUtils; + +const IntrospectDBusIface = loadInterfaceXML('org.gnome.Shell.Introspect'); + +var IntrospectService = class { + constructor() { + this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(IntrospectDBusIface, + this); + this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/Introspect'); + Gio.DBus.session.own_name('org.gnome.Shell.Introspect', + Gio.BusNameOwnerFlags.REPLACE, + null, null); + + this._runningApplications = {}; + this._runningApplicationsDirty = true; + this._activeApplication = null; + this._activeApplicationDirty = true; + this._animationsEnabled = true; + + this._appSystem = Shell.AppSystem.get_default(); + this._appSystem.connect('app-state-changed', + () => { + this._runningApplicationsDirty = true; + this._syncRunningApplications(); + }); + + this._introspectSettings = new Gio.Settings({ + schema_id: INTROSPECT_SCHEMA, + }); + + let tracker = Shell.WindowTracker.get_default(); + tracker.connect('notify::focus-app', + () => { + this._activeApplicationDirty = true; + this._syncRunningApplications(); + }); + + this._syncRunningApplications(); + + this._allowlistMap = new Map(); + APP_ALLOWLIST.forEach(appName => { + Gio.DBus.watch_name(Gio.BusType.SESSION, + appName, + Gio.BusNameWatcherFlags.NONE, + (conn, name, owner) => this._allowlistMap.set(name, owner), + (conn, name) => this._allowlistMap.delete(name)); + }); + + this._settings = St.Settings.get(); + this._settings.connect('notify::enable-animations', + this._syncAnimationsEnabled.bind(this)); + this._syncAnimationsEnabled(); + + const monitorManager = Meta.MonitorManager.get(); + monitorManager.connect('monitors-changed', + this._syncScreenSize.bind(this)); + this._syncScreenSize(); + } + + _isStandaloneApp(app) { + return app.get_windows().some(w => w.transient_for == null); + } + + _isIntrospectEnabled() { + return this._introspectSettings.get_boolean(INTROSPECT_KEY); + } + + _isSenderAllowed(sender) { + return [...this._allowlistMap.values()].includes(sender); + } + + _getSandboxedAppId(app) { + let ids = app.get_windows().map(w => w.get_sandboxed_app_id()); + return ids.find(id => id != null); + } + + _syncRunningApplications() { + let tracker = Shell.WindowTracker.get_default(); + let apps = this._appSystem.get_running(); + let seatName = "seat0"; + let newRunningApplications = {}; + + let newActiveApplication = null; + let focusedApp = tracker.focus_app; + + for (let app of apps) { + let appInfo = {}; + let isAppActive = focusedApp == app; + + if (!this._isStandaloneApp(app)) + continue; + + if (isAppActive) { + appInfo['active-on-seats'] = new GLib.Variant('as', [seatName]); + newActiveApplication = app.get_id(); + } + + let sandboxedAppId = this._getSandboxedAppId(app); + if (sandboxedAppId) + appInfo['sandboxed-app-id'] = new GLib.Variant('s', sandboxedAppId); + + newRunningApplications[app.get_id()] = appInfo; + } + + if (this._runningApplicationsDirty || + (this._activeApplicationDirty && + this._activeApplication != newActiveApplication)) { + this._runningApplications = newRunningApplications; + this._activeApplication = newActiveApplication; + + this._dbusImpl.emit_signal('RunningApplicationsChanged', null); + } + this._runningApplicationsDirty = false; + this._activeApplicationDirty = false; + } + + _isEligibleWindow(window) { + if (window.is_override_redirect()) + return false; + + let type = window.get_window_type(); + return type == Meta.WindowType.NORMAL || + type == Meta.WindowType.DIALOG || + type == Meta.WindowType.MODAL_DIALOG || + type == Meta.WindowType.UTILITY; + } + + _isInvocationAllowed(invocation) { + if (this._isIntrospectEnabled()) + return true; + + if (this._isSenderAllowed(invocation.get_sender())) + return true; + + return false; + } + + GetRunningApplicationsAsync(params, invocation) { + if (!this._isInvocationAllowed(invocation)) { + invocation.return_error_literal(Gio.DBusError, + Gio.DBusError.ACCESS_DENIED, + 'App introspection not allowed'); + return; + } + + invocation.return_value(new GLib.Variant('(a{sa{sv}})', [this._runningApplications])); + } + + GetWindowsAsync(params, invocation) { + let focusWindow = global.display.get_focus_window(); + let apps = this._appSystem.get_running(); + let windowsList = {}; + + if (!this._isInvocationAllowed(invocation)) { + invocation.return_error_literal(Gio.DBusError, + Gio.DBusError.ACCESS_DENIED, + 'App introspection not allowed'); + return; + } + + for (let app of apps) { + let windows = app.get_windows(); + for (let window of windows) { + + if (!this._isEligibleWindow(window)) + continue; + + let windowId = window.get_id(); + let frameRect = window.get_frame_rect(); + let title = window.get_title(); + let wmClass = window.get_wm_class(); + let sandboxedAppId = window.get_sandboxed_app_id(); + + windowsList[windowId] = { + 'app-id': GLib.Variant.new('s', app.get_id()), + 'client-type': GLib.Variant.new('u', window.get_client_type()), + 'is-hidden': GLib.Variant.new('b', window.is_hidden()), + 'has-focus': GLib.Variant.new('b', window == focusWindow), + 'width': GLib.Variant.new('u', frameRect.width), + 'height': GLib.Variant.new('u', frameRect.height), + }; + + // These properties may not be available for all windows: + if (title != null) + windowsList[windowId]['title'] = GLib.Variant.new('s', title); + + if (wmClass != null) + windowsList[windowId]['wm-class'] = GLib.Variant.new('s', wmClass); + + if (sandboxedAppId != null) { + windowsList[windowId]['sandboxed-app-id'] = + GLib.Variant.new('s', sandboxedAppId); + } + } + } + invocation.return_value(new GLib.Variant('(a{ta{sv}})', [windowsList])); + } + + _syncAnimationsEnabled() { + let wasAnimationsEnabled = this._animationsEnabled; + this._animationsEnabled = this._settings.enable_animations; + if (wasAnimationsEnabled !== this._animationsEnabled) { + let variant = new GLib.Variant('b', this._animationsEnabled); + this._dbusImpl.emit_property_changed('AnimationsEnabled', variant); + } + } + + _syncScreenSize() { + const oldScreenWidth = this._screenWidth; + const oldScreenHeight = this._screenHeight; + this._screenWidth = global.screen_width; + this._screenHeight = global.screen_height; + + if (oldScreenWidth !== this._screenWidth || + oldScreenHeight !== this._screenHeight) { + const variant = new GLib.Variant('(ii)', + [this._screenWidth, this._screenHeight]); + this._dbusImpl.emit_property_changed('ScreenSize', variant); + } + } + + get AnimationsEnabled() { + return this._animationsEnabled; + } + + get ScreenSize() { + return [this._screenWidth, this._screenHeight]; + } + + get version() { + return INTROSPECT_DBUS_API_VERSION; + } +}; |