3b1330880f
When not using arrow notation with anonymous functions, we use Lang.bind() to bind `this` to named callbacks. However since ES5, this functionality is already provided by Function.prototype.bind() - in fact, Lang.bind() itself uses it when no extra arguments are specified. Just use the built-in function directly where possible, and use arrow notation in the few places where we pass additional arguments. https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/23
399 lines
13 KiB
JavaScript
399 lines
13 KiB
JavaScript
|
|
const Lang = imports.lang;
|
|
const Gettext = imports.gettext;
|
|
const GLib = imports.gi.GLib;
|
|
const GObject = imports.gi.GObject;
|
|
const Gio = imports.gi.Gio;
|
|
const Gtk = imports.gi.Gtk;
|
|
const Gdk = imports.gi.Gdk;
|
|
const Pango = imports.gi.Pango;
|
|
const Format = imports.format;
|
|
|
|
const _ = Gettext.gettext;
|
|
|
|
const Config = imports.misc.config;
|
|
const ExtensionUtils = imports.misc.extensionUtils;
|
|
|
|
const GnomeShellIface = '<node> \
|
|
<interface name="org.gnome.Shell.Extensions"> \
|
|
<signal name="ExtensionStatusChanged"> \
|
|
<arg type="s" name="uuid"/> \
|
|
<arg type="i" name="state"/> \
|
|
<arg type="s" name="error"/> \
|
|
</signal> \
|
|
</interface> \
|
|
</node>';
|
|
|
|
const GnomeShellProxy = Gio.DBusProxy.makeProxyWrapper(GnomeShellIface);
|
|
|
|
function stripPrefix(string, prefix) {
|
|
if (string.slice(0, prefix.length) == prefix)
|
|
return string.slice(prefix.length);
|
|
return string;
|
|
}
|
|
|
|
var Application = new Lang.Class({
|
|
Name: 'Application',
|
|
_init() {
|
|
GLib.set_prgname('gnome-shell-extension-prefs');
|
|
this.application = new Gtk.Application({
|
|
application_id: 'org.gnome.shell.ExtensionPrefs',
|
|
flags: Gio.ApplicationFlags.HANDLES_COMMAND_LINE
|
|
});
|
|
|
|
this.application.connect('activate', this._onActivate.bind(this));
|
|
this.application.connect('command-line', this._onCommandLine.bind(this));
|
|
this.application.connect('startup', this._onStartup.bind(this));
|
|
|
|
this._extensionPrefsModules = {};
|
|
|
|
this._startupUuid = null;
|
|
this._loaded = false;
|
|
this._skipMainWindow = false;
|
|
},
|
|
|
|
_extensionAvailable(uuid) {
|
|
let extension = ExtensionUtils.extensions[uuid];
|
|
|
|
if (!extension)
|
|
return false;
|
|
|
|
if (!extension.dir.get_child('prefs.js').query_exists(null))
|
|
return false;
|
|
|
|
return true;
|
|
},
|
|
|
|
_getExtensionPrefsModule(extension) {
|
|
let uuid = extension.metadata.uuid;
|
|
|
|
if (this._extensionPrefsModules.hasOwnProperty(uuid))
|
|
return this._extensionPrefsModules[uuid];
|
|
|
|
ExtensionUtils.installImporter(extension);
|
|
|
|
let prefsModule = extension.imports.prefs;
|
|
prefsModule.init(extension.metadata);
|
|
|
|
this._extensionPrefsModules[uuid] = prefsModule;
|
|
return prefsModule;
|
|
},
|
|
|
|
_selectExtension(uuid) {
|
|
if (!this._extensionAvailable(uuid))
|
|
return;
|
|
|
|
let extension = ExtensionUtils.extensions[uuid];
|
|
let widget;
|
|
|
|
try {
|
|
let prefsModule = this._getExtensionPrefsModule(extension);
|
|
widget = prefsModule.buildPrefsWidget();
|
|
} catch (e) {
|
|
widget = this._buildErrorUI(extension, e);
|
|
}
|
|
|
|
let dialog = new Gtk.Window({ modal: !this._skipMainWindow,
|
|
type_hint: Gdk.WindowTypeHint.DIALOG });
|
|
dialog.set_titlebar(new Gtk.HeaderBar({ show_close_button: true,
|
|
title: extension.metadata.name,
|
|
visible: true }));
|
|
|
|
if (this._skipMainWindow) {
|
|
this.application.add_window(dialog);
|
|
if (this._window)
|
|
this._window.destroy();
|
|
this._window = dialog;
|
|
this._window.window_position = Gtk.WindowPosition.CENTER;
|
|
} else {
|
|
dialog.transient_for = this._window;
|
|
}
|
|
|
|
dialog.set_default_size(600, 400);
|
|
dialog.add(widget);
|
|
dialog.show();
|
|
},
|
|
|
|
_buildErrorUI(extension, exc) {
|
|
let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
|
|
let label = new Gtk.Label({
|
|
label: _("There was an error loading the preferences dialog for %s:").format(extension.metadata.name)
|
|
});
|
|
box.add(label);
|
|
|
|
let errortext = '';
|
|
errortext += exc;
|
|
errortext += '\n\n';
|
|
errortext += 'Stack trace:\n';
|
|
|
|
// Indent stack trace.
|
|
errortext += exc.stack.split('\n').map(line => ' ' + line).join('\n');
|
|
|
|
let scroll = new Gtk.ScrolledWindow({ vexpand: true });
|
|
let buffer = new Gtk.TextBuffer({ text: errortext });
|
|
let textview = new Gtk.TextView({ buffer: buffer });
|
|
textview.override_font(Pango.font_description_from_string('monospace'));
|
|
scroll.add(textview);
|
|
box.add(scroll);
|
|
|
|
box.show_all();
|
|
return box;
|
|
},
|
|
|
|
_buildUI(app) {
|
|
this._window = new Gtk.ApplicationWindow({ application: app,
|
|
window_position: Gtk.WindowPosition.CENTER });
|
|
|
|
this._window.set_default_size(800, 500);
|
|
|
|
this._titlebar = new Gtk.HeaderBar({ show_close_button: true,
|
|
title: _("Shell Extensions") });
|
|
this._window.set_titlebar(this._titlebar);
|
|
|
|
let killSwitch = new Gtk.Switch({ valign: Gtk.Align.CENTER });
|
|
this._titlebar.pack_end(killSwitch);
|
|
|
|
this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell' });
|
|
this._settings.bind('disable-user-extensions', killSwitch, 'active',
|
|
Gio.SettingsBindFlags.DEFAULT |
|
|
Gio.SettingsBindFlags.INVERT_BOOLEAN);
|
|
|
|
let scroll = new Gtk.ScrolledWindow({ hscrollbar_policy: Gtk.PolicyType.NEVER });
|
|
this._window.add(scroll);
|
|
|
|
this._extensionSelector = new Gtk.ListBox({ selection_mode: Gtk.SelectionMode.NONE });
|
|
this._extensionSelector.set_sort_func(this._sortList.bind(this));
|
|
this._extensionSelector.set_header_func(this._updateHeader.bind(this));
|
|
|
|
scroll.add(this._extensionSelector);
|
|
|
|
|
|
this._shellProxy = new GnomeShellProxy(Gio.DBus.session, 'org.gnome.Shell', '/org/gnome/Shell');
|
|
this._shellProxy.connectSignal('ExtensionStatusChanged', (proxy, senderName, [uuid, state, error]) => {
|
|
if (ExtensionUtils.extensions[uuid] !== undefined)
|
|
this._scanExtensions();
|
|
});
|
|
|
|
this._window.show_all();
|
|
},
|
|
|
|
_sortList(row1, row2) {
|
|
let name1 = ExtensionUtils.extensions[row1.uuid].metadata.name;
|
|
let name2 = ExtensionUtils.extensions[row2.uuid].metadata.name;
|
|
return name1.localeCompare(name2);
|
|
},
|
|
|
|
_updateHeader(row, before) {
|
|
if (!before || row.get_header())
|
|
return;
|
|
|
|
let sep = new Gtk.Separator({ orientation: Gtk.Orientation.HORIZONTAL });
|
|
row.set_header(sep);
|
|
},
|
|
|
|
_scanExtensions() {
|
|
let finder = new ExtensionUtils.ExtensionFinder();
|
|
finder.connect('extension-found', this._extensionFound.bind(this));
|
|
finder.scanExtensions();
|
|
this._extensionsLoaded();
|
|
},
|
|
|
|
_extensionFound(finder, extension) {
|
|
let row = new ExtensionRow(extension.uuid);
|
|
|
|
row.prefsButton.visible = this._extensionAvailable(row.uuid);
|
|
row.prefsButton.connect('clicked', () => {
|
|
this._selectExtension(row.uuid);
|
|
});
|
|
|
|
row.show_all();
|
|
this._extensionSelector.add(row);
|
|
},
|
|
|
|
_extensionsLoaded() {
|
|
if (this._startupUuid && this._extensionAvailable(this._startupUuid))
|
|
this._selectExtension(this._startupUuid);
|
|
this._startupUuid = null;
|
|
this._skipMainWindow = false;
|
|
this._loaded = true;
|
|
},
|
|
|
|
_onActivate() {
|
|
this._window.present();
|
|
},
|
|
|
|
_onStartup(app) {
|
|
this._buildUI(app);
|
|
this._scanExtensions();
|
|
},
|
|
|
|
_onCommandLine(app, commandLine) {
|
|
app.activate();
|
|
let args = commandLine.get_arguments();
|
|
|
|
if (args.length) {
|
|
let uuid = args[0];
|
|
|
|
this._skipMainWindow = true;
|
|
|
|
// Strip off "extension:///" prefix which fakes a URI, if it exists
|
|
uuid = stripPrefix(uuid, "extension:///");
|
|
|
|
if (this._extensionAvailable(uuid))
|
|
this._selectExtension(uuid);
|
|
else if (!this._loaded)
|
|
this._startupUuid = uuid;
|
|
else
|
|
this._skipMainWindow = false;
|
|
}
|
|
return 0;
|
|
}
|
|
});
|
|
|
|
var DescriptionLabel = new Lang.Class({
|
|
Name: 'DescriptionLabel',
|
|
Extends: Gtk.Label,
|
|
|
|
vfunc_get_preferred_height_for_width(width) {
|
|
// Hack: Request the maximum height allowed by the line limit
|
|
if (this.lines > 0)
|
|
return this.parent(0);
|
|
return this.parent(width);
|
|
}
|
|
});
|
|
|
|
var ExtensionRow = new Lang.Class({
|
|
Name: 'ExtensionRow',
|
|
Extends: Gtk.ListBoxRow,
|
|
|
|
_init(uuid) {
|
|
this.parent();
|
|
|
|
this.uuid = uuid;
|
|
|
|
this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell' });
|
|
this._settings.connect('changed::enabled-extensions', () => {
|
|
this._switch.state = this._isEnabled();
|
|
});
|
|
this._settings.connect('changed::disable-extension-version-validation',
|
|
() => {
|
|
this._switch.sensitive = this._canEnable();
|
|
});
|
|
this._settings.connect('changed::disable-user-extensions',
|
|
() => {
|
|
this._switch.sensitive = this._canEnable();
|
|
});
|
|
|
|
this._buildUI();
|
|
},
|
|
|
|
_buildUI() {
|
|
let extension = ExtensionUtils.extensions[this.uuid];
|
|
|
|
let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL,
|
|
hexpand: true, margin_end: 24, spacing: 24,
|
|
margin: 12 });
|
|
this.add(hbox);
|
|
|
|
let vbox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL,
|
|
spacing: 6, hexpand: true });
|
|
hbox.add(vbox);
|
|
|
|
let name = GLib.markup_escape_text(extension.metadata.name, -1);
|
|
let label = new Gtk.Label({ label: '<b>' + name + '</b>',
|
|
use_markup: true,
|
|
halign: Gtk.Align.START });
|
|
vbox.add(label);
|
|
|
|
let desc = extension.metadata.description.split('\n')[0];
|
|
label = new DescriptionLabel({ label: desc, wrap: true, lines: 2,
|
|
ellipsize: Pango.EllipsizeMode.END,
|
|
xalign: 0, yalign: 0 });
|
|
vbox.add(label);
|
|
|
|
let button = new Gtk.Button({ valign: Gtk.Align.CENTER,
|
|
no_show_all: true });
|
|
button.add(new Gtk.Image({ icon_name: 'emblem-system-symbolic',
|
|
icon_size: Gtk.IconSize.BUTTON,
|
|
visible: true }));
|
|
button.get_style_context().add_class('circular');
|
|
hbox.add(button);
|
|
|
|
this.prefsButton = button;
|
|
|
|
this._switch = new Gtk.Switch({ valign: Gtk.Align.CENTER,
|
|
sensitive: this._canEnable(),
|
|
state: this._isEnabled() });
|
|
this._switch.connect('notify::active', () => {
|
|
if (this._switch.active)
|
|
this._enable();
|
|
else
|
|
this._disable();
|
|
});
|
|
this._switch.connect('state-set', () => true);
|
|
hbox.add(this._switch);
|
|
},
|
|
|
|
_canEnable() {
|
|
let extension = ExtensionUtils.extensions[this.uuid];
|
|
let checkVersion = !this._settings.get_boolean('disable-extension-version-validation');
|
|
|
|
return !this._settings.get_boolean('disable-user-extensions') &&
|
|
!(checkVersion && ExtensionUtils.isOutOfDate(extension));
|
|
},
|
|
|
|
_isEnabled() {
|
|
let extensions = this._settings.get_strv('enabled-extensions');
|
|
return extensions.indexOf(this.uuid) != -1;
|
|
},
|
|
|
|
_enable() {
|
|
let extensions = this._settings.get_strv('enabled-extensions');
|
|
if (extensions.indexOf(this.uuid) != -1)
|
|
return;
|
|
|
|
extensions.push(this.uuid);
|
|
this._settings.set_strv('enabled-extensions', extensions);
|
|
},
|
|
|
|
_disable() {
|
|
let extensions = this._settings.get_strv('enabled-extensions');
|
|
let pos = extensions.indexOf(this.uuid);
|
|
if (pos == -1)
|
|
return;
|
|
do {
|
|
extensions.splice(pos, 1);
|
|
pos = extensions.indexOf(this.uuid);
|
|
} while (pos != -1);
|
|
this._settings.set_strv('enabled-extensions', extensions);
|
|
}
|
|
});
|
|
|
|
function initEnvironment() {
|
|
// Monkey-patch in a "global" object that fakes some Shell utilities
|
|
// that ExtensionUtils depends on.
|
|
window.global = {
|
|
log() {
|
|
print([].join.call(arguments, ', '));
|
|
},
|
|
|
|
logError(s) {
|
|
log('ERROR: ' + s);
|
|
},
|
|
|
|
userdatadir: GLib.build_filenamev([GLib.get_user_data_dir(), 'gnome-shell'])
|
|
};
|
|
|
|
String.prototype.format = Format.format;
|
|
}
|
|
|
|
function main(argv) {
|
|
initEnvironment();
|
|
|
|
Gettext.bindtextdomain(Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
|
|
Gettext.textdomain(Config.GETTEXT_PACKAGE);
|
|
|
|
let app = new Application();
|
|
app.application.run(argv);
|
|
}
|