edd34c50d9
With the previous preparations in place, it is time to take the plunge. As both the app and the portal use the same small library for handling external windows, port everything at once to avoid the hassle of building and installing two versions of the library. With the portal using GTK4 now, all extensions must port their preference widgets as well. Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1495>
284 lines
8.8 KiB
JavaScript
284 lines
8.8 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
/* exported ExtensionsService */
|
|
|
|
const { Gdk, Gio, GLib, GObject, Gtk, Shew } = imports.gi;
|
|
|
|
const ExtensionUtils = imports.misc.extensionUtils;
|
|
|
|
const { loadInterfaceXML } = imports.misc.fileUtils;
|
|
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;
|
|
}
|
|
|
|
ListExtensionsAsync(params, invocation) {
|
|
this._proxy.ListExtensionsRemote(...params, (res, error) => {
|
|
if (this._handleError(invocation, error))
|
|
return;
|
|
|
|
invocation.return_value(new GLib.Variant('(a{sa{sv}})', res));
|
|
});
|
|
}
|
|
|
|
GetExtensionInfoAsync(params, invocation) {
|
|
this._proxy.GetExtensionInfoRemote(...params, (res, error) => {
|
|
if (this._handleError(invocation, error))
|
|
return;
|
|
|
|
invocation.return_value(new GLib.Variant('(a{sv})', res));
|
|
});
|
|
}
|
|
|
|
GetExtensionErrorsAsync(params, invocation) {
|
|
this._proxy.GetExtensionErrorsRemote(...params, (res, error) => {
|
|
if (this._handleError(invocation, error))
|
|
return;
|
|
|
|
invocation.return_value(new GLib.Variant('(as)', res));
|
|
});
|
|
}
|
|
|
|
InstallRemoteExtensionAsync(params, invocation) {
|
|
this._proxy.InstallRemoteExtensionRemote(...params, (res, error) => {
|
|
if (this._handleError(invocation, error))
|
|
return;
|
|
|
|
invocation.return_value(new GLib.Variant('(s)', res));
|
|
});
|
|
}
|
|
|
|
UninstallExtensionAsync(params, invocation) {
|
|
this._proxy.UninstallExtensionRemote(...params, (res, error) => {
|
|
if (this._handleError(invocation, error))
|
|
return;
|
|
|
|
invocation.return_value(new GLib.Variant('(b)', res));
|
|
});
|
|
}
|
|
|
|
EnableExtensionAsync(params, invocation) {
|
|
this._proxy.EnableExtensionRemote(...params, (res, error) => {
|
|
if (this._handleError(invocation, error))
|
|
return;
|
|
|
|
invocation.return_value(new GLib.Variant('(b)', res));
|
|
});
|
|
}
|
|
|
|
DisableExtensionAsync(params, invocation) {
|
|
this._proxy.DisableExtensionRemote(...params, (res, error) => {
|
|
if (this._handleError(invocation, error))
|
|
return;
|
|
|
|
invocation.return_value(new GLib.Variant('(b)', res));
|
|
});
|
|
}
|
|
|
|
LaunchExtensionPrefsAsync([uuid], invocation) {
|
|
this.OpenExtensionPrefsAsync([uuid, '', {}], invocation);
|
|
}
|
|
|
|
OpenExtensionPrefsAsync(params, invocation) {
|
|
const [uuid, parentWindow, options] = params;
|
|
|
|
this._proxy.GetExtensionInfoRemote(uuid, (res, error) => {
|
|
if (this._handleError(invocation, error))
|
|
return;
|
|
|
|
const [serialized] = res;
|
|
const extension = ExtensionUtils.deserializeExtension(serialized);
|
|
|
|
const window = new ExtensionPrefsDialog(extension);
|
|
window.realize();
|
|
|
|
let externalWindow = null;
|
|
|
|
if (parentWindow)
|
|
externalWindow = Shew.ExternalWindow.new_from_handle(parentWindow);
|
|
|
|
if (externalWindow)
|
|
externalWindow.set_parent_of(window.get_surface());
|
|
|
|
if (options.modal)
|
|
window.modal = options.modal.get_boolean();
|
|
|
|
window.connect('destroy', () => this.release());
|
|
this.hold();
|
|
|
|
window.show();
|
|
|
|
invocation.return_value(null);
|
|
});
|
|
}
|
|
|
|
CheckForUpdatesAsync(params, invocation) {
|
|
this._proxy.CheckForUpdatesRemote(...params, (res, error) => {
|
|
if (this._handleError(invocation, error))
|
|
return;
|
|
|
|
invocation.return_value(null);
|
|
});
|
|
}
|
|
};
|
|
|
|
var ExtensionPrefsDialog = GObject.registerClass({
|
|
GTypeName: 'ExtensionPrefsDialog',
|
|
Template: 'resource:///org/gnome/Shell/Extensions/ui/extension-prefs-dialog.ui',
|
|
InternalChildren: [
|
|
'stack',
|
|
'expander',
|
|
'expanderArrow',
|
|
'revealer',
|
|
'errorView',
|
|
],
|
|
}, class ExtensionPrefsDialog extends Gtk.Window {
|
|
_init(extension) {
|
|
super._init({
|
|
title: extension.metadata.name,
|
|
});
|
|
|
|
this._uuid = extension.uuid;
|
|
this._url = extension.metadata.url || '';
|
|
|
|
this._actionGroup = new Gio.SimpleActionGroup();
|
|
this.insert_action_group('win', this._actionGroup);
|
|
|
|
this._initActions();
|
|
this._addCustomStylesheet();
|
|
|
|
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());
|
|
|
|
try {
|
|
ExtensionUtils.installImporter(extension);
|
|
|
|
// give extension prefs access to their own extension object
|
|
ExtensionUtils.getCurrentExtension = () => extension;
|
|
|
|
const prefsModule = extension.imports.prefs;
|
|
prefsModule.init(extension.metadata);
|
|
|
|
const widget = prefsModule.buildPrefsWidget();
|
|
this._stack.add_named(widget, 'prefs');
|
|
this._stack.visible_child = widget;
|
|
} catch (e) {
|
|
this._setError(e);
|
|
}
|
|
}
|
|
|
|
_syncExpandedStyle() {
|
|
if (this._revealer.reveal_child)
|
|
this._expander.add_css_class('expanded');
|
|
else if (!this._revealer.child_revealed)
|
|
this._expander.remove_css_class('expanded');
|
|
}
|
|
|
|
_setError(exc) {
|
|
this._errorView.buffer.text = `${exc}\n\nStack trace:\n`;
|
|
// Indent stack trace.
|
|
this._errorView.buffer.text +=
|
|
exc.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:`,
|
|
'```',
|
|
`${exc}`,
|
|
'```',
|
|
'',
|
|
'Stack trace:',
|
|
'```',
|
|
exc.stack.replace(/\n$/, ''), // stack without trailing newline
|
|
'```',
|
|
'',
|
|
];
|
|
this._errorMarkdown = lines.join('\n');
|
|
this._actionGroup.lookup('copy-error').enabled = true;
|
|
}
|
|
|
|
_initActions() {
|
|
let action;
|
|
|
|
action = new Gio.SimpleAction({
|
|
name: 'copy-error',
|
|
enabled: false,
|
|
});
|
|
action.connect('activate', () => {
|
|
const clipboard = Gtk.Clipboard.get_default(this.get_display());
|
|
clipboard.set_text(this._errorMarkdown, -1);
|
|
});
|
|
this._actionGroup.add_action(action);
|
|
|
|
action = new Gio.SimpleAction({
|
|
name: 'show-url',
|
|
enabled: this._url !== '',
|
|
});
|
|
action.connect('activate', () => {
|
|
Gio.AppInfo.launch_default_for_uri(this._url,
|
|
this.get_display().get_app_launch_context());
|
|
});
|
|
this._actionGroup.add_action(action);
|
|
}
|
|
|
|
_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);
|
|
}
|
|
});
|