extensionSystem: Implement new live enable/disable system

The rough sketches of the system are outlined here:

http://mail.gnome.org/archives/gnome-shell-list/2011-June/msg00283.html

Additionally, enable/disable extensions when the 'enabled-extensions' setting
changes. Two new DBus methods, "EnableExtension" and "DisableExtension" allow
users to manipulate this setting quite easily.

https://bugzilla.gnome.org/show_bug.cgi?id=654770
This commit is contained in:
Jasper St. Pierre 2011-06-22 21:56:24 -04:00
parent 6d3434f3a5
commit 2d813cbdd8
3 changed files with 134 additions and 21 deletions

View File

@ -14,7 +14,11 @@ const ExtensionState = {
ENABLED: 1, ENABLED: 1,
DISABLED: 2, DISABLED: 2,
ERROR: 3, ERROR: 3,
OUT_OF_DATE: 4 OUT_OF_DATE: 4,
// Used as an error state for operations on unknown extensions,
// should never be in a real extensionMeta object.
UNINSTALLED: 99
}; };
const ExtensionType = { const ExtensionType = {
@ -26,6 +30,8 @@ const ExtensionType = {
const extensionMeta = {}; const extensionMeta = {};
// Maps uuid -> importer object (extension directory tree) // Maps uuid -> importer object (extension directory tree)
const extensions = {}; const extensions = {};
// Maps uuid -> extension state object (returned from init())
const extensionStateObjs = {};
// Arrays of uuids // Arrays of uuids
var enabledExtensions; var enabledExtensions;
// GFile for user extensions // GFile for user extensions
@ -75,6 +81,46 @@ function versionCheck(required, current) {
return false; return false;
} }
function disableExtension(uuid) {
let meta = extensionMeta[uuid];
if (!meta)
return;
if (meta.state != ExtensionState.ENABLED)
return;
let extensionState = extensionStateObjs[uuid];
try {
extensionState.disable();
} catch(e) {
logExtensionError(uuid, e.toString());
return;
}
meta.state = ExtensionState.DISABLED;
}
function enableExtension(uuid) {
let meta = extensionMeta[uuid];
if (!meta)
return;
if (meta.state != ExtensionState.DISABLED)
return;
let extensionState = extensionStateObjs[uuid];
try {
extensionState.enable();
} catch(e) {
logExtensionError(uuid, e.toString());
return;
}
meta.state = ExtensionState.ENABLED;
}
function logExtensionError(uuid, message) { function logExtensionError(uuid, message) {
if (!errors[uuid]) errors[uuid] = []; if (!errors[uuid]) errors[uuid] = [];
errors[uuid].push(message); errors[uuid].push(message);
@ -136,16 +182,12 @@ function loadExtension(dir, enabled, type) {
return; return;
} }
extensionMeta[meta.uuid] = meta; extensionMeta[uuid] = meta;
extensionMeta[meta.uuid].type = type; meta.type = type;
extensionMeta[meta.uuid].path = dir.get_path(); meta.path = dir.get_path();
if (!enabled) {
extensionMeta[meta.uuid].state = ExtensionState.DISABLED;
return;
}
// Default to error, we set success as the last step // Default to error, we set success as the last step
extensionMeta[meta.uuid].state = ExtensionState.ERROR; meta.state = ExtensionState.ERROR;
let extensionJs = dir.get_child('extension.js'); let extensionJs = dir.get_child('extension.js');
if (!extensionJs.query_exists(null)) { if (!extensionJs.query_exists(null)) {
@ -166,6 +208,7 @@ function loadExtension(dir, enabled, type) {
} }
let extensionModule; let extensionModule;
let extensionState = null;
try { try {
global.add_extension_importer('imports.ui.extensionSystem.extensions', meta.uuid, dir.get_path()); global.add_extension_importer('imports.ui.extensionSystem.extensions', meta.uuid, dir.get_path());
extensionModule = extensions[meta.uuid].extension; extensionModule = extensions[meta.uuid].extension;
@ -175,24 +218,65 @@ function loadExtension(dir, enabled, type) {
logExtensionError(uuid, e); logExtensionError(uuid, e);
return; return;
} }
if (!extensionModule.main) {
logExtensionError(uuid, 'missing \'main\' function'); if (!extensionModule.init) {
logExtensionError(uuid, 'missing \'init\' function');
return; return;
} }
try { try {
extensionModule.main(meta); extensionState = extensionModule.init(meta);
} catch (e) { } catch (e) {
if (stylesheetPath != null) if (stylesheetPath != null)
theme.unload_stylesheet(stylesheetPath); theme.unload_stylesheet(stylesheetPath);
logExtensionError(uuid, 'Failed to evaluate init function:' + e); logExtensionError(uuid, 'Failed to evaluate init function:' + e);
return; return;
} }
extensionMeta[meta.uuid].state = ExtensionState.ENABLED;
if (!extensionState)
extensionState = extensionModule;
extensionStateObjs[uuid] = extensionState;
if (!extensionState.enable) {
logExtensionError(uuid, 'missing \'enable\' function');
return;
}
if (!extensionState.disable) {
logExtensionError(uuid, 'missing \'disable\' function');
return;
}
meta.state = ExtensionState.DISABLED;
if (enabled)
enableExtension(uuid);
_signals.emit('extension-loaded', meta.uuid); _signals.emit('extension-loaded', meta.uuid);
global.log('Loaded extension ' + meta.uuid); global.log('Loaded extension ' + meta.uuid);
} }
function onEnabledExtensionsChanged() {
let newEnabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
// Find and enable all the newly enabled extensions: UUIDs found in the
// new setting, but not in the old one.
newEnabledExtensions.filter(function(uuid) {
return enabledExtensions.indexOf(uuid) == -1;
}).forEach(function(uuid) {
enableExtension(uuid);
});
// Find and disable all the newly disabled extensions: UUIDs found in the
// old setting, but not in the new one.
enabledExtensions.filter(function(item) {
return newEnabledExtensions.indexOf(item) == -1;
}).forEach(function(uuid) {
disableExtension(uuid);
});
enabledExtensions = newEnabledExtensions;
}
function init() { function init() {
let userExtensionsPath = GLib.build_filenamev([global.userdatadir, 'extensions']); let userExtensionsPath = GLib.build_filenamev([global.userdatadir, 'extensions']);
userExtensionsDir = Gio.file_new_for_path(userExtensionsPath); userExtensionsDir = Gio.file_new_for_path(userExtensionsPath);
@ -202,6 +286,7 @@ function init() {
global.logError('' + e); global.logError('' + e);
} }
global.settings.connect('changed::' + ENABLED_EXTENSIONS_KEY, onEnabledExtensionsChanged);
enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY); enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
} }

View File

@ -35,6 +35,14 @@ const GnomeShellIface = {
{ name: 'Screenshot', { name: 'Screenshot',
inSignature: 's', inSignature: 's',
outSignature: 'b' outSignature: 'b'
},
{ name: 'EnableExtension',
inSignature: 's',
outSignature: ''
},
{ name: 'DisableExtension',
inSignature: 's',
outSignature: ''
} }
], ],
signals: [], signals: [],
@ -144,6 +152,20 @@ GnomeShell.prototype = {
return ExtensionSystem.errors[uuid] || []; return ExtensionSystem.errors[uuid] || [];
}, },
EnableExtension: function(uuid) {
let enabledExtensions = global.settings.get_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY);
if (enabledExtensions.indexOf(uuid) == -1)
enabledExtensions.push(uuid);
global.settings.set_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY, enabledExtensions);
},
DisableExtension: function(uuid) {
let enabledExtensions = global.settings.get_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY);
while (enabledExtensions.indexOf(uuid) != -1)
enabledExtensions.splice(enabledExtensions.indexOf(uuid), 1);
global.settings.set_strv(ExtensionSystem.ENABLED_EXTENSIONS_KEY, enabledExtensions);
},
get OverviewActive() { get OverviewActive() {
return Main.overview.visible; return Main.overview.visible;
}, },

View File

@ -33,7 +33,7 @@ const St = imports.gi.St;
const Main = imports.ui.main; const Main = imports.ui.main;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
let text; let text, button;
function _hideHello() { function _hideHello() {
Main.uiGroup.remove_actor(text); Main.uiGroup.remove_actor(text);
@ -60,22 +60,28 @@ function _showHello() {
onComplete: _hideHello }); onComplete: _hideHello });
} }
function main() { function init() {
let button = new St.Bin({ style_class: 'panel-button', button = new St.Bin({ style_class: 'panel-button',
reactive: true, reactive: true,
can_focus: true, can_focus: true,
x_fill: true, x_fill: true,
y_fill: false, y_fill: false,
track_hover: true }); track_hover: true });
let icon = new St.Icon({ icon_name: 'system-run', let icon = new St.Icon({ icon_name: 'system-run',
icon_type: St.IconType.SYMBOLIC, icon_type: St.IconType.SYMBOLIC,
style_class: 'system-status-icon' }); style_class: 'system-status-icon' });
button.set_child(icon); button.set_child(icon);
button.connect('button-press-event', _showHello); button.connect('button-press-event', _showHello);
}
function enable() {
Main.panel._rightBox.insert_actor(button, 0); Main.panel._rightBox.insert_actor(button, 0);
} }
function disable() {
Main.panel._rightBox.remove_actor(button);
}
""", """,
"stylesheet.css": """ "stylesheet.css": """