1e882ba8dc
Some extensions try to destroy the provided dialog, so they can instead show the preferences in an alternative way. Unfortunately that is undetectable from the Extensions service, which still thinks the dialog is open, and will therefore block other prefs dialogs. Try to steer extensions away from that anti-pattern (or at the very least switch to `close()` which doesn't break) by overriding the window's destroy() method and show an extension error instead. Closes https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/7435 Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3209>
164 lines
4.9 KiB
JavaScript
164 lines
4.9 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
import Adw from 'gi://Adw?version=1';
|
|
import Gdk from 'gi://Gdk?version=4.0';
|
|
import Gio from 'gi://Gio';
|
|
import GLib from 'gi://GLib';
|
|
import GObject from 'gi://GObject';
|
|
import Gtk from 'gi://Gtk?version=4.0';
|
|
|
|
import {formatError} from './misc/errorUtils.js';
|
|
|
|
export const ExtensionPrefsDialog = GObject.registerClass({
|
|
GTypeName: 'ExtensionPrefsDialog',
|
|
Signals: {
|
|
'loaded': {},
|
|
},
|
|
}, class ExtensionPrefsDialog extends Adw.PreferencesWindow {
|
|
_init(extension) {
|
|
super._init({
|
|
title: extension.metadata.name,
|
|
search_enabled: false,
|
|
});
|
|
|
|
this._extension = extension;
|
|
|
|
this._loadPrefs().catch(e => {
|
|
this._showErrorPage(e);
|
|
logError(e, 'Failed to open preferences');
|
|
}).finally(() => this.emit('loaded'));
|
|
}
|
|
|
|
async _loadPrefs() {
|
|
const {dir, path, metadata} = this._extension;
|
|
|
|
const prefsJs = dir.get_child('prefs.js');
|
|
const prefsModule = await import(prefsJs.get_uri());
|
|
|
|
const prefsObj = new prefsModule.default({...metadata, dir, path});
|
|
this._extension.stateObj = prefsObj;
|
|
|
|
prefsObj.fillPreferencesWindow(this);
|
|
|
|
if (!this.visible_page)
|
|
throw new Error('Extension did not provide any UI');
|
|
}
|
|
|
|
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;
|
|
});
|
|
}
|
|
|
|
destroy() {
|
|
this._showErrorPage(
|
|
new Error('destroy() breaks tracking open dialogs, use close() if you must'));
|
|
}
|
|
|
|
_showErrorPage(e) {
|
|
while (this.visible_page)
|
|
this.remove(this.visible_page);
|
|
|
|
this.add(new ExtensionPrefsErrorPage(this._extension, e));
|
|
}
|
|
});
|
|
|
|
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());
|
|
|
|
const formattedError = formatError(error);
|
|
this._errorView.buffer.text = formattedError;
|
|
|
|
// markdown for pasting in gitlab issues
|
|
let lines = [
|
|
`The settings of extension ${this._uuid} had an error:`,
|
|
'```',
|
|
formattedError.replace(/\n$/, ''), // remove 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);
|
|
}
|
|
});
|