summaryrefslogtreecommitdiffstats
path: root/js/dbusServices/extensions
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/dbusServices/extensions/css/application.css9
-rw-r--r--js/dbusServices/extensions/extensionPrefsDialog.js178
-rw-r--r--js/dbusServices/extensions/extensionsService.js161
-rw-r--r--js/dbusServices/extensions/main.js23
-rw-r--r--js/dbusServices/extensions/ui/extension-error-page.ui112
5 files changed, 483 insertions, 0 deletions
diff --git a/js/dbusServices/extensions/css/application.css b/js/dbusServices/extensions/css/application.css
new file mode 100644
index 0000000..4ef4066
--- /dev/null
+++ b/js/dbusServices/extensions/css/application.css
@@ -0,0 +1,9 @@
+.error-page preferencespage { margin: 30px; }
+
+.expander { padding: 12px; }
+.expander.expanded { border: 0 solid @borders; border-bottom-width: 1px; }
+.expander-toolbar {
+ border: 0 solid @borders;
+ border-top-width: 1px;
+ padding: 3px;
+}
diff --git a/js/dbusServices/extensions/extensionPrefsDialog.js b/js/dbusServices/extensions/extensionPrefsDialog.js
new file mode 100644
index 0000000..7155c1a
--- /dev/null
+++ b/js/dbusServices/extensions/extensionPrefsDialog.js
@@ -0,0 +1,178 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+/* exported ExtensionPrefsDialog */
+
+const { Adw, Gdk, Gio, GLib, GObject, Gtk } = imports.gi;
+
+const ExtensionUtils = imports.misc.extensionUtils;
+
+var ExtensionPrefsDialog = GObject.registerClass({
+ GTypeName: 'ExtensionPrefsDialog',
+}, class ExtensionPrefsDialog extends Adw.PreferencesWindow {
+ _init(extension) {
+ super._init({
+ title: extension.metadata.name,
+ search_enabled: false,
+ });
+
+ try {
+ ExtensionUtils.installImporter(extension);
+
+ // give extension prefs access to their own extension object
+ ExtensionUtils.setCurrentExtension(extension);
+
+ const prefsModule = extension.imports.prefs;
+ prefsModule.init(extension.metadata);
+
+ if (prefsModule.fillPreferencesWindow) {
+ prefsModule.fillPreferencesWindow(this);
+
+ if (!this.visible_page)
+ throw new Error('Extension did not provide any UI');
+ } else {
+ const widget = prefsModule.buildPrefsWidget();
+ const page = this._wrapWidget(widget);
+ this.add(page);
+ }
+ } catch (e) {
+ this._showErrorPage(e);
+ logError(e, 'Failed to open preferences');
+ }
+ }
+
+ set titlebar(w) {
+ this.set_titlebar(w);
+ }
+
+ // eslint-disable-next-line camelcase
+ set_titlebar() {
+ // intercept fatal libadwaita error, show error page instead
+ GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
+ this._showErrorPage(
+ new Error('set_titlebar() is not supported for Adw.Window'));
+ return GLib.SOURCE_REMOVE;
+ });
+ }
+
+ _showErrorPage(e) {
+ while (this.visible_page)
+ this.remove(this.visible_page);
+
+ const extension = ExtensionUtils.getCurrentExtension();
+ this.add(new ExtensionPrefsErrorPage(extension, e));
+ }
+
+ _wrapWidget(widget) {
+ if (widget instanceof Adw.PreferencesPage)
+ return widget;
+
+ const page = new Adw.PreferencesPage();
+ if (widget instanceof Adw.PreferencesGroup) {
+ page.add(widget);
+ return page;
+ }
+
+ const group = new Adw.PreferencesGroup();
+ group.add(widget);
+ page.add(group);
+
+ return page;
+ }
+});
+
+const ExtensionPrefsErrorPage = GObject.registerClass({
+ GTypeName: 'ExtensionPrefsErrorPage',
+ Template: 'resource:///org/gnome/Shell/Extensions/ui/extension-error-page.ui',
+ InternalChildren: [
+ 'expander',
+ 'expanderArrow',
+ 'revealer',
+ 'errorView',
+ ],
+}, class ExtensionPrefsErrorPage extends Adw.PreferencesPage {
+ static _classInit(klass) {
+ super._classInit(klass);
+
+ klass.install_action('page.copy-error',
+ null,
+ self => {
+ const clipboard = self.get_display().get_clipboard();
+ clipboard.set(self._errorMarkdown);
+ });
+ klass.install_action('page.show-url',
+ null,
+ self => Gtk.show_uri(self.get_root(), self._url, Gdk.CURRENT_TIME));
+
+ return klass;
+ }
+
+ _init(extension, error) {
+ super._init();
+
+ this._addCustomStylesheet();
+
+ this._uuid = extension.uuid;
+ this._url = extension.metadata.url || '';
+
+ this.action_set_enabled('page.show-url', this._url !== '');
+
+ this._gesture = new Gtk.GestureClick({
+ button: 0,
+ exclusive: true,
+ });
+ this._expander.add_controller(this._gesture);
+
+ this._gesture.connect('released', (gesture, nPress) => {
+ if (nPress === 1)
+ this._revealer.reveal_child = !this._revealer.reveal_child;
+ });
+
+ this._revealer.connect('notify::reveal-child', () => {
+ this._expanderArrow.icon_name = this._revealer.reveal_child
+ ? 'pan-down-symbolic'
+ : 'pan-end-symbolic';
+ this._syncExpandedStyle();
+ });
+ this._revealer.connect('notify::child-revealed',
+ () => this._syncExpandedStyle());
+
+ this._errorView.buffer.text = `${error}\n\nStack trace:\n`;
+ // Indent stack trace.
+ this._errorView.buffer.text +=
+ error.stack.split('\n').map(line => ` ${line}`).join('\n');
+
+ // markdown for pasting in gitlab issues
+ let lines = [
+ `The settings of extension ${this._uuid} had an error:`,
+ '```',
+ `${error}`,
+ '```',
+ '',
+ 'Stack trace:',
+ '```',
+ error.stack.replace(/\n$/, ''), // stack without trailing newline
+ '```',
+ '',
+ ];
+ this._errorMarkdown = lines.join('\n');
+ }
+
+ _syncExpandedStyle() {
+ if (this._revealer.reveal_child)
+ this._expander.add_css_class('expanded');
+ else if (!this._revealer.child_revealed)
+ this._expander.remove_css_class('expanded');
+ }
+
+ _addCustomStylesheet() {
+ let provider = new Gtk.CssProvider();
+ let uri = 'resource:///org/gnome/Shell/Extensions/css/application.css';
+ try {
+ provider.load_from_file(Gio.File.new_for_uri(uri));
+ } catch (e) {
+ logError(e, 'Failed to add application style');
+ }
+ Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(),
+ provider,
+ Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+});
diff --git a/js/dbusServices/extensions/extensionsService.js b/js/dbusServices/extensions/extensionsService.js
new file mode 100644
index 0000000..d8234d2
--- /dev/null
+++ b/js/dbusServices/extensions/extensionsService.js
@@ -0,0 +1,161 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+/* exported ExtensionsService */
+
+const { Gio, GLib, Shew } = imports.gi;
+
+const ExtensionUtils = imports.misc.extensionUtils;
+
+const { loadInterfaceXML } = imports.misc.dbusUtils;
+const { ExtensionPrefsDialog } = imports.extensionPrefsDialog;
+const { ServiceImplementation } = imports.dbusService;
+
+const ExtensionsIface = loadInterfaceXML('org.gnome.Shell.Extensions');
+const ExtensionsProxy = Gio.DBusProxy.makeProxyWrapper(ExtensionsIface);
+
+var ExtensionsService = class extends ServiceImplementation {
+ constructor() {
+ super(ExtensionsIface, '/org/gnome/Shell/Extensions');
+
+ this._proxy = new ExtensionsProxy(Gio.DBus.session,
+ 'org.gnome.Shell', '/org/gnome/Shell');
+
+ this._proxy.connectSignal('ExtensionStateChanged',
+ (proxy, sender, params) => {
+ this._dbusImpl.emit_signal('ExtensionStateChanged',
+ new GLib.Variant('(sa{sv})', params));
+ });
+
+ this._proxy.connect('g-properties-changed', () => {
+ this._dbusImpl.emit_property_changed('UserExtensionsEnabled',
+ new GLib.Variant('b', this._proxy.UserExtensionsEnabled));
+ });
+ }
+
+ get ShellVersion() {
+ return this._proxy.ShellVersion;
+ }
+
+ get UserExtensionsEnabled() {
+ return this._proxy.UserExtensionsEnabled;
+ }
+
+ set UserExtensionsEnabled(enable) {
+ this._proxy.UserExtensionsEnabled = enable;
+ }
+
+ async ListExtensionsAsync(params, invocation) {
+ try {
+ const res = await this._proxy.ListExtensionsAsync(...params);
+ invocation.return_value(new GLib.Variant('(a{sa{sv}})', res));
+ } catch (error) {
+ this._handleError(invocation, error);
+ }
+ }
+
+ async GetExtensionInfoAsync(params, invocation) {
+ try {
+ const res = await this._proxy.GetExtensionInfoAsync(...params);
+ invocation.return_value(new GLib.Variant('(a{sv})', res));
+ } catch (error) {
+ this._handleError(invocation, error);
+ }
+ }
+
+ async GetExtensionErrorsAsync(params, invocation) {
+ try {
+ const res = await this._proxy.GetExtensionErrorsAsync(...params);
+ invocation.return_value(new GLib.Variant('(as)', res));
+ } catch (error) {
+ this._handleError(invocation, error);
+ }
+ }
+
+ async InstallRemoteExtensionAsync(params, invocation) {
+ try {
+ const res = await this._proxy.InstallRemoteExtensionAsync(...params);
+ invocation.return_value(new GLib.Variant('(s)', res));
+ } catch (error) {
+ this._handleError(invocation, error);
+ }
+ }
+
+ async UninstallExtensionAsync(params, invocation) {
+ try {
+ const res = await this._proxy.UninstallExtensionAsync(...params);
+ invocation.return_value(new GLib.Variant('(b)', res));
+ } catch (error) {
+ this._handleError(invocation, error);
+ }
+ }
+
+ async EnableExtensionAsync(params, invocation) {
+ try {
+ const res = await this._proxy.EnableExtensionAsync(...params);
+ invocation.return_value(new GLib.Variant('(b)', res));
+ } catch (error) {
+ this._handleError(invocation, error);
+ }
+ }
+
+ async DisableExtensionAsync(params, invocation) {
+ try {
+ const res = await this._proxy.DisableExtensionAsync(...params);
+ invocation.return_value(new GLib.Variant('(b)', res));
+ } catch (error) {
+ this._handleError(invocation, error);
+ }
+ }
+
+ LaunchExtensionPrefsAsync([uuid], invocation) {
+ this.OpenExtensionPrefsAsync([uuid, '', {}], invocation);
+ }
+
+ async OpenExtensionPrefsAsync(params, invocation) {
+ const [uuid, parentWindow, options] = params;
+
+ try {
+ const [serialized] = await this._proxy.GetExtensionInfoAsync(uuid);
+
+ if (this._prefsDialog)
+ throw new Error('Already showing a prefs dialog');
+
+ const extension = ExtensionUtils.deserializeExtension(serialized);
+
+ this._prefsDialog = new ExtensionPrefsDialog(extension);
+ this._prefsDialog.connect('realize', () => {
+ let externalWindow = null;
+
+ if (parentWindow)
+ externalWindow = Shew.ExternalWindow.new_from_handle(parentWindow);
+
+ if (externalWindow)
+ externalWindow.set_parent_of(this._prefsDialog.get_surface());
+ });
+
+ if (options.modal)
+ this._prefsDialog.modal = options.modal.get_boolean();
+
+ this._prefsDialog.connect('close-request', () => {
+ delete this._prefsDialog;
+ this.release();
+ return false;
+ });
+ this.hold();
+
+ this._prefsDialog.show();
+
+ invocation.return_value(null);
+ } catch (error) {
+ this._handleError(invocation, error);
+ }
+ }
+
+ async CheckForUpdatesAsync(params, invocation) {
+ try {
+ await this._proxy.CheckForUpdatesAsync(...params);
+ invocation.return_value(null);
+ } catch (error) {
+ this._handleError(invocation, error);
+ }
+ }
+};
diff --git a/js/dbusServices/extensions/main.js b/js/dbusServices/extensions/main.js
new file mode 100644
index 0000000..306fe36
--- /dev/null
+++ b/js/dbusServices/extensions/main.js
@@ -0,0 +1,23 @@
+/* exported main */
+
+imports.gi.versions.Adw = '1';
+imports.gi.versions.Gdk = '4.0';
+imports.gi.versions.Gtk = '4.0';
+
+const { Adw, GObject } = imports.gi;
+const pkg = imports.package;
+
+const { DBusService } = imports.dbusService;
+const { ExtensionsService } = imports.extensionsService;
+
+function main() {
+ Adw.init();
+ pkg.initFormat();
+
+ GObject.gtypeNameBasedOnJSPath = true;
+
+ const service = new DBusService(
+ 'org.gnome.Shell.Extensions',
+ new ExtensionsService());
+ service.run();
+}
diff --git a/js/dbusServices/extensions/ui/extension-error-page.ui b/js/dbusServices/extensions/ui/extension-error-page.ui
new file mode 100644
index 0000000..5ce6a62
--- /dev/null
+++ b/js/dbusServices/extensions/ui/extension-error-page.ui
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="ExtensionPrefsErrorPage" parent="AdwPreferencesPage">
+ <style>
+ <class name="error-page"/>
+ </style>
+ <child>
+ <object class="AdwPreferencesGroup">
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Something’s gone wrong</property>
+ <style>
+ <class name="title-1"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">We’re very sorry, but there’s been a problem: the settings for this extension can’t be displayed. We recommend that you report the issue to the extension authors.</property>
+ <property name="justify">center</property>
+ <property name="wrap">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFrame">
+ <property name="margin-top">12</property>
+ <child>
+ <object class="GtkBox">
+ <property name="hexpand">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="expander">
+ <property name="spacing">6</property>
+ <style>
+ <class name="expander"/>
+ </style>
+ <child>
+ <object class="GtkImage" id="expanderArrow">
+ <property name="icon-name">pan-end-symbolic</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Technical Details</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="revealer">
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkTextView" id="errorView">
+ <property name="monospace">True</property>
+ <property name="editable">False</property>
+ <property name="wrap-mode">word</property>
+ <property name="left-margin">12</property>
+ <property name="right-margin">12</property>
+ <property name="top-margin">12</property>
+ <property name="bottom-margin">12</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <style>
+ <class name="expander-toolbar"/>
+ </style>
+ <child>
+ <object class="GtkButton">
+ <property name="receives-default">True</property>
+ <property name="action-name">page.copy-error</property>
+ <property name="has-frame">False</property>
+ <property name="icon-name">edit-copy-symbolic</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="homeButton">
+ <property name="visible"
+ bind-source="homeButton"
+ bind-property="sensitive"
+ bind-flags="sync-create"/>
+ <property name="hexpand">True</property>
+ <property name="halign">end</property>
+ <property name="label" translatable="yes">Homepage</property>
+ <property name="tooltip-text" translatable="yes">Visit extension homepage</property>
+ <property name="receives-default">True</property>
+ <property name="has-frame">False</property>
+ <property name="action-name">page.show-url</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>