diff --git a/data/dbus-interfaces/meson.build b/data/dbus-interfaces/meson.build index 4ba832661..c96bbbb4d 100644 --- a/data/dbus-interfaces/meson.build +++ b/data/dbus-interfaces/meson.build @@ -1,5 +1,6 @@ dbus_interfaces = [ 'org.gnome.Shell.Extensions.xml', + 'org.gnome.Shell.Introspect.xml', 'org.gnome.Shell.PadOsd.xml', 'org.gnome.Shell.Screencast.xml', 'org.gnome.Shell.Screenshot.xml', diff --git a/data/dbus-interfaces/org.gnome.Shell.Introspect.xml b/data/dbus-interfaces/org.gnome.Shell.Introspect.xml new file mode 100644 index 000000000..10c48d635 --- /dev/null +++ b/data/dbus-interfaces/org.gnome.Shell.Introspect.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + diff --git a/data/gnome-shell-dbus-interfaces.gresource.xml b/data/gnome-shell-dbus-interfaces.gresource.xml index 5183672c9..ba148ca1f 100644 --- a/data/gnome-shell-dbus-interfaces.gresource.xml +++ b/data/gnome-shell-dbus-interfaces.gresource.xml @@ -40,6 +40,7 @@ org.gnome.SettingsDaemon.Wacom.xml org.gnome.Shell.AudioDeviceSelection.xml org.gnome.Shell.Extensions.xml + org.gnome.Shell.Introspect.xml org.gnome.Shell.HotplugSniffer.xml org.gnome.Shell.PerfHelper.xml org.gnome.Shell.PortalHelper.xml diff --git a/data/org.gnome.shell.gschema.xml.in b/data/org.gnome.shell.gschema.xml.in index a7251cd06..87898a36e 100644 --- a/data/org.gnome.shell.gschema.xml.in +++ b/data/org.gnome.shell.gschema.xml.in @@ -90,6 +90,14 @@ adapter is ever seen not to have devices associated to it. + + false + Enable introspection API + + Enables a D-Bus API that allows to introspect the application state of + the shell. + + diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml index 75a6c3b12..a3dd2be08 100644 --- a/js/js-resources.gresource.xml +++ b/js/js-resources.gresource.xml @@ -16,6 +16,7 @@ misc/history.js misc/ibusManager.js misc/inputMethod.js + misc/introspect.js misc/jsParse.js misc/keyboardManager.js misc/loginManager.js diff --git a/js/misc/introspect.js b/js/misc/introspect.js new file mode 100644 index 000000000..fa0a66e00 --- /dev/null +++ b/js/misc/introspect.js @@ -0,0 +1,111 @@ +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; +const Lang = imports.lang; +const Meta = imports.gi.Meta; +const Shell = imports.gi.Shell; + +const INTROSPECT_SCHEMA = 'org.gnome.shell'; +const INTROSPECT_KEY = 'introspect'; +const APP_WHITELIST = ['org.freedesktop.impl.portal.desktop.gtk']; + +const { loadInterfaceXML } = imports.misc.fileUtils; + +const IntrospectDBusIface = loadInterfaceXML('org.gnome.Shell.Introspect'); + +var IntrospectService = new Lang.Class({ + Name: 'IntrospectService', + + _init() { + 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._appSystem = Shell.AppSystem.get_default(); + this._appSystem.connect('app-state-changed', + () => { + this._runningApplicationsDirty = true; + this._syncRunningApplications(); + }); + + this._settings = 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(); + }, + + _isStandaloneApp(app) { + let windows = app.get_windows(); + + return app.get_windows().some(w => w.transient_for == null); + }, + + _isIntrospectEnabled() { + return this._settings.get_boolean(INTROSPECT_KEY); + }, + + _isSenderWhitelisted(sender) { + return APP_WHITELIST.includes(sender); + }, + + _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(); + } + + 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; + }, + + GetRunningApplicationsAsync(params, invocation) { + if (!this._isIntrospectEnabled() && + !this._isSenderWhitelisted(invocation.get_sender())) { + 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])); + } +}); diff --git a/js/ui/main.js b/js/ui/main.js index 9e7ce8706..f87b1ad02 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -20,6 +20,7 @@ const Environment = imports.ui.environment; const ExtensionSystem = imports.ui.extensionSystem; const ExtensionDownloader = imports.ui.extensionDownloader; const InputMethod = imports.misc.inputMethod; +const Introspect = imports.misc.introspect; const Keyboard = imports.ui.keyboard; const MessageTray = imports.ui.messageTray; const ModalDialog = imports.ui.modalDialog; @@ -82,6 +83,7 @@ var keyboard = null; var layoutManager = null; var kbdA11yDialog = null; var inputMethod = null; +var introspectService = null; let _startDate; let _defaultCssStylesheet = null; let _cssStylesheet = null; @@ -187,6 +189,8 @@ function _initializeUI() { windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler(); componentManager = new Components.ComponentManager(); + introspectService = new Introspect.IntrospectService(); + layoutManager.init(); overview.init();