Add a new tool, 'gnome-shell-extension-prefs', which can configure extensions

A new tool, 'gnome-shell-extension-prefs' can load a new entry point from
extensions, 'prefs.js', which has an entry point to return a GTK+ widget.
This allows extensions to have their own preferences dialog, without each
extension needing to ship its own Python script and .desktop file.

https://bugzilla.gnome.org/show_bug.cgi?id=668429
This commit is contained in:
Jasper St. Pierre 2012-01-18 21:21:56 -05:00
parent 80ff6ff797
commit b8a54faf94
8 changed files with 326 additions and 4 deletions

3
.gitignore vendored
View File

@ -18,6 +18,8 @@ config
configure
data/gnome-shell.desktop
data/gnome-shell.desktop.in
data/gnome-shell-extension-prefs.desktop
data/gnome-shell-extension-prefs.desktop.in
data/gschemas.compiled
data/org.gnome.shell.gschema.xml
data/org.gnome.shell.gschema.valid
@ -62,6 +64,7 @@ src/calendar-server/org.gnome.Shell.CalendarServer.service
src/gnome-shell
src/gnome-shell-calendar-server
src/gnome-shell-extension-tool
src/gnome-shell-extension-prefs
src/gnome-shell-hotplug-sniffer
src/gnome-shell-jhbuild
src/gnome-shell-perf-helper

View File

@ -1,5 +1,5 @@
desktopdir=$(datadir)/applications
desktop_DATA = gnome-shell.desktop
desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop
# We substitute in bindir so it works as an autostart
# file when built in a non-system prefix
@ -69,6 +69,7 @@ shaders_DATA = \
EXTRA_DIST = \
gnome-shell.desktop.in.in \
gnome-shell-extension-prefs.in.in \
$(menu_DATA) \
$(shaders_DATA) \
$(convert_DATA) \
@ -76,6 +77,7 @@ EXTRA_DIST = \
CLEANFILES = \
gnome-shell.desktop.in \
gnome-shell-extension-prefs.in \
$(desktop_DATA) \
$(gsettings_SCHEMAS) \
gschemas.compiled

View File

@ -0,0 +1,12 @@
[Desktop Entry]
Type=Application
_Name=GNOME Shell Extension Preferences
_Comment=Configure GNOME Shell Extensions
Exec=@bindir@/gnome-shell-extension-prefs %u
X-GNOME-Bugzilla-Bugzilla=GNOME
X-GNOME-Bugzilla-Product=gnome-shell
X-GNOME-Bugzilla-Component=extensions
X-GNOME-Bugzilla-Version=@VERSION@
Categories=GNOME;GTK;
OnlyShowIn=GNOME;
NoDisplay=true

View File

@ -7,6 +7,7 @@ nobase_dist_js_DATA = \
gdm/fingerprint.js \
gdm/loginDialog.js \
gdm/powerMenu.js \
extensionPrefs/main.js \
misc/config.js \
misc/docInfo.js \
misc/extensionUtils.js \

280
js/extensionPrefs/main.js Normal file
View File

@ -0,0 +1,280 @@
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 Pango = imports.gi.Pango;
const _ = Gettext.gettext;
const Config = imports.misc.config;
const Format = imports.misc.format;
const ExtensionUtils = imports.misc.extensionUtils;
const GnomeShellIface = <interface name="org.gnome.Shell">
<signal name="ExtensionStatusChanged">
<arg type="s" name="uuid"/>
<arg type="i" name="state"/>
<arg type="s" name="error"/>
</signal>
</interface>;
const GnomeShellProxy = Gio.DBusProxy.makeProxyWrapper(GnomeShellIface);
function stripPrefix(string, prefix) {
if (string.slice(0, prefix.length) == prefix)
return string.slice(prefix.length);
return string;
}
const Application = new Lang.Class({
Name: 'Application',
_init: function() {
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', Lang.bind(this, this._onActivate));
this.application.connect('command-line', Lang.bind(this, this._onCommandLine));
this.application.connect('startup', Lang.bind(this, this._onStartup));
this._extensionMetas = {};
this._extensionPrefsModules = {};
this._extensionIters = {};
},
_buildModel: function() {
this._model = new Gtk.ListStore();
this._model.set_column_types([GObject.TYPE_STRING, GObject.TYPE_STRING]);
},
_extensionAvailable: function(uuid) {
let meta = this._extensionMetas[uuid];
if (!meta)
return false;
if (ExtensionUtils.isOutOfDate(meta))
return false;
if (!meta.dir.get_child('prefs.js').query_exists(null))
return false;
return true;
},
_setExtensionInsensitive: function(layout, cell, model, iter, data) {
let uuid = model.get_value(iter, 0);
if (!this._extensionAvailable(uuid))
cell.set_sensitive(false);
},
_getExtensionPrefsModule: function(meta) {
if (this._extensionPrefsModules.hasOwnProperty(meta.uuid))
return this._extensionPrefsModules[meta.uuid];
ExtensionUtils.installImporter(meta);
let prefsModule = meta.importer.prefs;
prefsModule.init(meta);
this._extensionPrefsModules[meta.uuid] = prefsModule;
return prefsModule;
},
_selectExtension: function(uuid) {
if (!this._extensionAvailable(uuid))
return;
let meta = this._extensionMetas[uuid];
let widget;
try {
let prefsModule = this._getExtensionPrefsModule(meta);
widget = prefsModule.buildPrefsWidget(meta);
} catch (e) {
widget = this._buildErrorUI(meta, e);
}
// Destroy the current prefs widget, if it exists
if (this._extensionPrefsBin.get_child())
this._extensionPrefsBin.get_child().destroy();
this._extensionPrefsBin.add(widget);
this._extensionSelector.set_active_iter(this._extensionIters[uuid]);
},
_extensionSelected: function() {
let [success, iter] = this._extensionSelector.get_active_iter();
if (!success)
return;
let uuid = this._model.get_value(iter, 0);
this._selectExtension(uuid);
},
_buildErrorUI: function(meta, 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(meta.name)
});
box.add(label);
let errortext = '';
errortext += exc;
errortext += '\n\n';
errortext += 'Stack trace:\n';
// Indent stack trace.
errortext += exc.stack.split('\n').map(function(line) {
return ' ' + 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: function(app) {
this._window = new Gtk.ApplicationWindow({ application: app,
window_position: Gtk.WindowPosition.CENTER,
title: _("GNOME Shell Extension Preferences") });
this._window.set_size_request(600, 400);
let vbox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
this._window.add(vbox);
let toolbar = new Gtk.Toolbar();
toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR);
vbox.add(toolbar);
let toolitem;
let label = new Gtk.Label({ label: _("<b>Extension</b>"),
use_markup: true });
toolitem = new Gtk.ToolItem({ child: label });
toolbar.add(toolitem);
this._extensionSelector = new Gtk.ComboBox({ model: this._model,
margin_left: 8,
hexpand: true });
this._extensionSelector.get_style_context().add_class(Gtk.STYLE_CLASS_RAISED);
let renderer = new Gtk.CellRendererText();
this._extensionSelector.pack_start(renderer, true);
this._extensionSelector.add_attribute(renderer, 'text', 1);
this._extensionSelector.set_cell_data_func(renderer, Lang.bind(this, this._setExtensionInsensitive), null);
this._extensionSelector.connect('changed', Lang.bind(this, this._extensionSelected));
toolitem = new Gtk.ToolItem({ child: this._extensionSelector });
toolitem.set_expand(true);
toolbar.add(toolitem);
this._extensionPrefsBin = new Gtk.Frame();
vbox.add(this._extensionPrefsBin);
let label = new Gtk.Label({
label: _("Select an extension to configure using the combobox above."),
vexpand: true
});
this._extensionPrefsBin.add(label);
this._shellProxy = new GnomeShellProxy(Gio.DBus.session, 'org.gnome.Shell', '/org/gnome/Shell');
this._shellProxy.connectSignal('ExtensionStatusChanged', Lang.bind(this, function(proxy, senderName, [uuid, state, error]) {
if (!this._extensionMetas.hasOwnProperty(uuid))
this._scanExtensions();
}));
this._window.show_all();
},
_scanExtensions: function() {
ExtensionUtils.scanExtensions(Lang.bind(this, function(uuid, dir, type) {
if (this._extensionMetas.hasOwnProperty(uuid))
return;
let meta;
try {
meta = ExtensionUtils.loadMetadata(uuid, dir, type);
} catch(e) {
global.logError('' + e);
return;
}
this._extensionMetas[uuid] = meta;
let iter = this._model.append();
this._model.set(iter, [0, 1], [uuid, meta.name]);
this._extensionIters[uuid] = iter;
}));
},
_onActivate: function() {
this._window.present();
},
_onStartup: function(app) {
this._buildModel();
this._buildUI(app);
this._scanExtensions();
},
_onCommandLine: function(app, commandLine) {
app.activate();
let args = commandLine.get_arguments();
if (args.length) {
let uuid = args[0];
// Strip off "extension:///" prefix which fakes a URI, if it exists
uuid = stripPrefix(uuid, "extension:///");
if (!this._extensionAvailable(uuid))
return 1;
this._selectExtension(uuid);
}
return 0;
}
});
function initEnvironment() {
// Monkey-patch in a "global" object that fakes some Shell utilities
// that ExtensionUtils depends on.
window.global = {
log: function() {
print([].join.call(arguments, ', '));
},
logError: function(s) {
global.log('ERROR: ' + s);
},
userdatadir: GLib.build_filenamev([GLib.get_user_data_dir(), 'gnome-shell'])
};
String.prototype.format = Format.format;
}
function main(argv) {
initEnvironment();
ExtensionUtils.init();
Gettext.bindtextdomain(Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
Gettext.textdomain(Config.GETTEXT_PACKAGE);
let app = new Application();
app.application.run(argv);
}

View File

@ -10,3 +10,7 @@ const GJS_VERSION = '@GJS_VERSION@';
const HAVE_BLUETOOTH = @HAVE_BLUETOOTH@;
/* The system TLS CA list */
const SHELL_SYSTEM_CA_FILE = '@SHELL_SYSTEM_CA_FILE@';
/* gettext package */
const GETTEXT_PACKAGE = '@GETTEXT_PACKAGE@';
/* locale dir */
const LOCALEDIR = '@datadir@/locale';

View File

@ -27,8 +27,8 @@ CLEANFILES += $(service_DATA)
CLEANFILES += $(gir_DATA) $(typelib_DATA)
bin_SCRIPTS += gnome-shell-extension-tool
EXTRA_DIST += gnome-shell-extension-tool.in
bin_SCRIPTS += gnome-shell-extension-tool gnome-shell-extension-prefs
EXTRA_DIST += gnome-shell-extension-tool.in gnome-shell-extension-prefs.in
bin_PROGRAMS = gnome-shell-real
if USE_JHBUILD_WRAPPER_SCRIPT
@ -52,11 +52,13 @@ generated_script_substitutions = \
-e "s|@datadir[@]|$(datadir)|g" \
-e "s|@libexecdir[@]|$(libexecdir)|g" \
-e "s|@libdir[@]|$(libdir)|g" \
-e "s|@pkglibdir[@]|$(pkglibdir)|g" \
-e "s|@JHBUILD_TYPELIBDIR[@]|$(JHBUILD_TYPELIBDIR)|g" \
-e "s|@pkgdatadir[@]|$(pkgdatadir)|g" \
-e "s|@PYTHON[@]|$(PYTHON)|g" \
-e "s|@VERSION[@]|$(VERSION)|g" \
-e "s|@sysconfdir[@]|$(sysconfdir)|g"
-e "s|@sysconfdir[@]|$(sysconfdir)|g" \
-e "s|@GJS_CONSOLE[@]|$(GJS_CONSOLE)|g"
gnome-shell-jhbuild: gnome-shell-jhbuild.in gnome-shell-real Makefile
$(AM_V_GEN) sed $(generated_script_substitutions) $< > $@.tmp && mv $@.tmp $@ && chmod a+x $@
@ -66,6 +68,9 @@ gnome-shell-jhbuild: gnome-shell-jhbuild.in gnome-shell-real Makefile
gnome-shell-extension-tool: gnome-shell-extension-tool.in Makefile
$(AM_V_GEN) sed $(generated_script_substitutions) $< > $@.tmp && mv $@.tmp $@ && chmod a+x $@
gnome-shell-extension-prefs: gnome-shell-extension-prefs.in Makefile
$(AM_V_GEN) sed $(generated_script_substitutions) $< > $@.tmp && mv $@.tmp $@ && chmod a+x $@
CLEANFILES += gnome-shell $(bin_SCRIPTS)
include Makefile-st.am

View File

@ -0,0 +1,15 @@
#!/bin/bash
if [ -n "$GI_TYPELIB_PATH" ]; then
export GI_TYPELIB_PATH=@pkglibdir@
else
export GI_TYPELIB_PATH=@pkglibdir@:$GI_TYPELIB_PATH
fi
if [ -n "$LD_LIBRARY_PATH" ] ; then
export LD_LIBRARY_PATH=@pkglibdir@
else
export LD_LIBRARY_PATH=@pkglibdir@:$LD_LIBRARY_PATH
fi
@GJS_CONSOLE@ -I @pkgdatadir@/js -c "const Main = imports.extensionPrefs.main; Main.main(ARGV);" "$@"