From b8a54faf941976cfd59ae5d9f922d9828f3cd06c Mon Sep 17 00:00:00 2001 From: "Jasper St. Pierre" Date: Wed, 18 Jan 2012 21:21:56 -0500 Subject: [PATCH] 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 --- .gitignore | 3 + data/Makefile.am | 4 +- .../gnome-shell-extension-prefs.desktop.in.in | 12 + js/Makefile.am | 1 + js/extensionPrefs/main.js | 280 ++++++++++++++++++ js/misc/config.js.in | 4 + src/Makefile.am | 11 +- src/gnome-shell-extension-prefs.in | 15 + 8 files changed, 326 insertions(+), 4 deletions(-) create mode 100644 data/gnome-shell-extension-prefs.desktop.in.in create mode 100644 js/extensionPrefs/main.js create mode 100644 src/gnome-shell-extension-prefs.in diff --git a/.gitignore b/.gitignore index 4ec7bad8b..f6dc70b02 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/data/Makefile.am b/data/Makefile.am index 04c8f4f6a..c077aa22d 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -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 diff --git a/data/gnome-shell-extension-prefs.desktop.in.in b/data/gnome-shell-extension-prefs.desktop.in.in new file mode 100644 index 000000000..9f8bbf405 --- /dev/null +++ b/data/gnome-shell-extension-prefs.desktop.in.in @@ -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 diff --git a/js/Makefile.am b/js/Makefile.am index f7ce7cd69..4c41bffee 100644 --- a/js/Makefile.am +++ b/js/Makefile.am @@ -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 \ diff --git a/js/extensionPrefs/main.js b/js/extensionPrefs/main.js new file mode 100644 index 000000000..82b67b4b1 --- /dev/null +++ b/js/extensionPrefs/main.js @@ -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 = + + + + + +; + +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: _("Extension"), + 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); +} diff --git a/js/misc/config.js.in b/js/misc/config.js.in index a35fe28b2..6cbf9324d 100644 --- a/js/misc/config.js.in +++ b/js/misc/config.js.in @@ -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'; diff --git a/src/Makefile.am b/src/Makefile.am index a46d779b4..464831a76 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/gnome-shell-extension-prefs.in b/src/gnome-shell-extension-prefs.in new file mode 100644 index 000000000..fa3725457 --- /dev/null +++ b/src/gnome-shell-extension-prefs.in @@ -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);" "$@"