注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

绿色圣光

~生活·自愚自乐~

 
 
 

日志

 
 

Updating GNOME Shell Extensions To Work With GNOME 3.2  

2011-12-02 23:06:55|  分类: 电脑知识 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

本文转自Musings,原文可能已有更新,请点击查看原文


This particular post will contain the knowledge that I accumulate of the next few weeks about updating GNOME 3.0 Shell extensions so that they work with the GNOME 3.2 Shell. It will be a living document for a period of about 2 months so check back frequently for updates. When the GNOME 3.4 Shell is released, it will be covered in a separate post.

As you are probably aware, GNOME Shell comes with an interactive GNOME Shell extension creator called gnome-shell-extension-tool. The utility is actually a simple Python script. It generates a simple Hello World extension using:

gnome-shell-extension-tool --create-extension

After asking you 3 questions, the utility generates three files which are placed in $HOME/.local/share/gnome-shell/extensions/<EXTENSION_UUID>/.

-rw-rw-r--. 1 fpm fpm 1488 Oct 25 21:46 extension.js
-rw-rw-r--. 1 fpm fpm 152 Oct 26 12:30 metadata.json
-rw-rw-r--. 1 fpm fpm 172 Oct 25 21:46 stylesheet.css

Here is what is generated by this utility for extension.js on Fedora 15 which uses GNOME 3.0:

const St = imports.gi.St;
const Mainloop = imports.mainloop;
const Main = imports.ui.main;

function _showHello() {
let text = new St.Label({ style_class: 'helloworld-label', text: "Hello, world!" });
let monitor = global.get_primary_monitor();
global.stage.add_actor(text);
text.set_position(Math.floor (monitor.width / 2 - text.width / 2),
Math.floor(monitor.height / 2 - text.height / 2));
Mainloop.timeout_add(3000, function () { text.destroy(); });
}

// Put your extension initialization code here
function main() {
Main.panel.actor.reactive = true;
Main.panel.actor.connect('button-release-event', _showHello);
}

Thanks to the seemingly tireless Jasper St. Pierre, here is a slightly more advanced version of a GNOME 3.0 HelloWorld Shell extension which was backported from the code generated by the same utility on Fedora 16 beta.

const St = imports.gi.St;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;

let text, button;

function _hideHello() {
Main.uiGroup.remove_actor(text);
text = null;
}

function _showHello() {
if (!text) {
text = new St.Label({ style_class: 'helloworld-label', text: "Hello, world!" });
Main.uiGroup.add_actor(text);
}

text.opacity = 255;

let monitor = Main.layout.primaryMonitor;

text.set_position(Math.floor(monitor.width / 2 - text.width / 2),
Math.floor(monitor.height / 2 - text.height / 2));

Tweener.addTween(text,
{ opacity: 0,
time: 2,
transition: 'easeOutQuad',
onComplete: _hideHello });
}

function main() {
button = new St.Bin({ style_class: 'panel-button',
reactive: true,
can_focus: true,
x_fill: true,
y_fill: false,
track_hover: true });
let icon = new St.Icon({ icon_name: 'system-run',
icon_type: St.IconType.SYMBOLIC,
style_class: 'system-status-icon' });

button.set_child(icon);
button.connect('button-press-event', _showHello);
Main.panel._rightBox.insert_actor(button, 0);
}

This extension simply adds a button to the right hand side of the (top) panel and displays a Hello World message for a number of seconds. I am going to assume that you are familiar with JavaScript and GNOME Shell extensions in general if you are reading this post – so I am not going to explain the code.

GNOME 3.2 extension image

In GNOME 3.0, all Shell extensions are enabled by default unless the Shell extension is in a blacklist, i.e. org.gnome.shell.disabled-extensions. Per-user and systemwide Shell extensions can be disabled with this GSettings key. Thus, this Shell extension is enabled by default and loaded when you restart the Shell using Alt+F2 R, gnome-shell –replace or some other means.

To gain an understanding of what changes may be required in a 3.0 Shell extension in order for the extension to work in GNOME 3.2, we examine the code generated by this utility for a 3.2 Shell extension. By the way, with Fedora 16 Beta you may get the following error when you run this utility:

Uuid [example__@ultra.xfpmurphy.com]: helloworld@example.com Created extension in '/home/fpm/.local/share/gnome-shell/extensions/helloworld@example.com' Traceback (most recent call last):   File "/usr/bin/gnome-shell-extension-tool", line 151, in      subprocess.Popen(['gnome-open', extensionjs_path]) NameError: name 'extensionjs_path' is not defined 


See this bug report for more information. The fix is simple – add the line denoted by the +.

     print "Created extension in %r" % (extension_path, ) +    extensionjs_path = os.path.join(extension_path, 'extension.js')      subprocess.Popen(['xdg-open', extensionjs_path])      sys.exit(0) 


You may notice that I also replaced gnome-open with xdg-open in subprocess.Popen. That is because gnome-open is deprecated.

Here is what is generated by the gnome-shell-extension-tool on Fedora 16 Beta:

const St = imports.gi.St;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;

let text, button;

function _hideHello() {
Main.uiGroup.remove_actor(text);
text = null;
}

function _showHello() {
if (!text) {
text = new St.Label({ style_class: 'helloworld-label', text: "Hello, world!" });
Main.uiGroup.add_actor(text);
}

text.opacity = 255;

let monitor = Main.layoutManager.primaryMonitor;

text.set_position(Math.floor(monitor.width / 2 - text.width / 2),
Math.floor(monitor.height / 2 - text.height / 2));

Tweener.addTween(text,
{ opacity: 0,
time: 2,
transition: 'easeOutQuad',
onComplete: _hideHello });
}

function init() {
button = new St.Bin({ style_class: 'panel-button',
reactive: true,
can_focus: true,
x_fill: true,
y_fill: false,
track_hover: true });
let icon = new St.Icon({ icon_name: 'system-run',
icon_type: St.IconType.SYMBOLIC,
style_class: 'system-status-icon' });

button.set_child(icon);
button.connect('button-press-event', _showHello);
}

function enable() {
Main.panel._rightBox.insert_actor(button, 0);
}

function disable() {
Main.panel._rightBox.remove_actor(button);
}

As you can see it is quite different than the backported GNOME 3.0 HelloWorld Shell extension. The basic functionality of the Shell extension is the same. There is no longer a main function; it has been replaced by init. Also two functions or methods named enable and disable are mandatory. More about these later.

In GNOME 3.2, a Shell extension is not available to you by default when the Shell is restarted because, in GNOME 3.2, Shell extensions are no longer enabled by default. The Shell extension is loaded but not enabled. Looking Glass will show the Shell extension in the Extensions pane but it will display as disabled. Any errors in the Shell extension code will be displayed in the Errors pane.

GNOME 3.2 extension image

To become enabled, a Shell extension must be specifically added to a whitelist, i.e. the GSettings org.gnome.shell.enabled-extensions key. Then the enable method or function in the Shell extension must be called. Typically this automatically happens – more about this later.

Suppose a Shell extension is named example32@example.com. One way to enable the Shell extension is to use the gsettings utility:

% gsettings get org.gnome.shell enabled-extensions[] % gsettings set org.gnome.shell enabled-extensions "['example32@example.com']" % gsettings get org.gnome.shell enabled-extensions ['example@example.com'] 


Another way to enable or disable the Shell extension is to use John Stower‘s excellent gnome-tweet-tool or the dconf-editor utility.

dconf-editor

Another way is to use a future version of gnome-shell-extension-tool, not yet released as of the date of this blog, which has options to enable or disable extensions.

$ ./gnome-shell-extension-tool --help Usage: gnome-shell-extension-tool [options]  Options:   -h, --help            show this help message and exit   -d DISABLE, --disable-extension=DISABLE                         Disable a GNOME Shell extension   -e ENABLE, --enable-extension=ENABLE                         Enable a GNOME Shell extension   -c, --create-extension                         Create a new GNOME Shell extension  $ gnome-shell-extension-tool -e example32@example.com 'example32@example.com' is now enabled. $ gnome-shell-extension-tool -d example32@example.com 'example32@example.com' is now disabled. 


A copy of this new version of gnome-shell-extension-tool is available here. Note that it does not check the validity of extension names!

By the way, while it is not specifically stated in the GNOME 3.2 Release Notes, from looking at the GNOME 3.2 Shell source code, it would appear that the org.gnome.shell.disabled-extensions key is no longer used to blacklist Shell extensions.

If you compare the 3.0 and 3.2 versions of the Shell extension code, the main function in 3.0 is replaced by three functions in 3.2, i.e. init, enable and disable. The init function is mandatory just like main was in 3.0. It does everything the main function usually does, except it cannot change any Shell state. Its purpose is to setup whatever needs to be setup, such as labels, text, icons or actors, prior to enabling the extension. It is only called once per shell session. The enable and disable functions are mandatory and are intended to be used to make a Shell extension’s UI visible (enable) or invisible (disable) to the user.

Note that while enable and disable can be empty functions and init can contain the code to enable the UI, it goes against what the GNOME Shell developers are trying to achieve, i.e. provide a way to enable or disable a Shell extension without having to reload the GNOME Shell. Remember, in GNOME Shell 3.0, every time you wished to do something with an extension you had to reload the GNOME Shell.

This design is not enforced; it cannot be. All the Shell loadExtension function, which was extensively modified in 3.2 (see extensionSystem.js), can do is to check for the existence of init, enable and disable. This is all the Shell can do to support the intent of the new design without extensive code being added to the Shell. It is up to writers of Shell extensions to comply with the spirit and intent of the design. When the proposed extensions.gnome.org materializes, a tool could probably be written to check that enable and disable actually enable or disable the Shell extension UI as part of the acceptable criteria for repository.

if (!extensionModule.init) {
logExtensionError(uuid, 'missing \'init\' function');
return;
}

try {
extensionState = extensionModule.init(meta);
} catch (e) {
if (stylesheetPath != null)
theme.unload_stylesheet(stylesheetPath);
logExtensionError(uuid, 'Failed to evaluate init function:' + e);
return;
}

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;
}

The Shell listens for a GSettings changed signal that the org.gnome.shell.enabled_extensions key has changed and invokes onEnabledExtensionsChanged which gets the list of enabled Shell extensions, compares it to the current in-memory list and enables or disables Shell extensions accordingly by calling init followed by enable for any new Shell extension added to the whitelist, enable for Shell extensions previously initialized but disabled, and disable for Shell extensions that are no longer on the whitelist.

function disableExtension(uuid) {
let meta = extensionMeta[uuid];
if (!meta)
return;

if (meta.state != ExtensionState.ENABLED)
return;

let extensionState = extensionStateObjs[uuid];

// "Rebase" the extension order by disabling and then enabling extensions
// in order to help prevent conflicts.

// Example:
// order = [A, B, C, D, E]
// user disables C
// this should: disable E, disable D, disable C, enable D, enable E

let orderIdx = extensionOrder.indexOf(uuid);
let order = extensionOrder.slice(orderIdx + 1);
let orderReversed = order.slice().reverse();

for (let i = 0; i < orderReversed.length; i++) {
let uuid = orderReversed[i];
try {
extensionStateObjs[uuid].disable();
} catch(e) {
logExtensionError(uuid, e.toString());
}
}

try {
extensionState.disable();
} catch(e) {
logExtensionError(uuid, e.toString());
return;
}

for (let i = 0; i < order.length; i++) {
let uuid = order[i];
try {
extensionStateObjs[uuid].enable();
} catch(e) {
logExtensionError(uuid, e.toString());
}
}

extensionOrder.splice(orderIdx, 1);

meta.state = ExtensionState.DISABLED;
_signals.emit('extension-state-changed', meta);
}

function enableExtension(uuid) {
let meta = extensionMeta[uuid];
if (!meta)
return;

if (meta.state == ExtensionState.INITIALIZED) {
loadExtension(meta.dir, meta.type, true);
return;
}

if (meta.state != ExtensionState.DISABLED)
return;

let extensionState = extensionStateObjs[uuid];

extensionOrder.push(uuid);

try {
extensionState.enable();
} catch(e) {
logExtensionError(uuid, e.toString());
return;
}

meta.state = ExtensionState.ENABLED;
_signals.emit('extension-state-changed', meta);
}

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;
}

Changes to the Shell UI by a Shell extension are controlled by whatever code is in the Shell extension’s enable and disable methods or functions. Typically, the Shell will call init, and then enable if the Shell extension is enabled, i.e. is in the Shell extension whitelist when the Shell starts up. The Shell may call disable and enable again during the same session. However, the Shell does not call enable or disable if a Shell extension is disabled at startup. Currently, the Shell calls init for all Shell extension including disabled Shell extensions at startup.

You can code your Shell extension so that init returns a JavaScript object with two methods called enable and disable. The object type returned does not matter. The Shell does not touch that object except to invoke either the enable or disable methods. If init does not return anything or returns a falsey (something which evaluates to FALSE) object, the Shell then assumes that enable and disable are functions defined within extension.js and acts accordingly.

Here is a modified version of the extension.js generated by the 3.2 version of gnome-shell-extension-tool in which init returns a simple object with enable and disable methods.

const Lang = imports.lang;
const St = imports.gi.St;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;

function HelloWorldExtension() {
this._init();
}

HelloWorldExtension.prototype = {
_init: function() {
this.button = new St.Bin({ style_class: 'panel-button',
reactive: true,
can_focus: true,
x_fill: true,
y_fill: false,
track_hover: true });
this.text = null;
let icon = new St.Icon({ icon_name: 'system-run',
icon_type: St.IconType.SYMBOLIC,
style_class: 'system-status-icon' });

this.button.set_child(icon);
this.button.connect('button-press-event', Lang.bind(this, this._showHello));
},

_hideHello: function() {
Main.uiGroup.remove_actor(this.text);
this.text = null;
},

_showHello: function() {
if (!this.text) {
this.text = new St.Label({ style_class: 'helloworld-label', text: "Hello, world!" });
Main.uiGroup.add_actor(this.text);
}

this.text.opacity = 255;

let monitor = Main.layout.primaryMonitor;

this.text.set_position(Math.floor(monitor.width / 2 - this.text.width / 2),
Math.floor(monitor.height / 2 - this.text.height / 2));

Tweener.addTween(this.text,
{ opacity: 0,
time: 2,
transition: 'easeOutQuad',
onComplete: Lang.bind(this, this._hideHello) });
},

enable: function() {
Main.panel._rightBox.insert_actor(this.button, 0);
},

disable: function() {
Main.panel._rightBox.remove_actor(this.button);
}
};

function init() {
return new HelloWorldExtension();
}

The key line is return new HelloWorldExtension. This is the initialized object that is passed back to the Shell. It contains the enable method which inserts the button on the panel and the disable method which removes the button from the panel.

From the above, it should be obvious that you can modify your 3.0 Shell extension in one of three ways to enable it to work with GNOME Shell 3.2.

  • BAD – You can rename the main function to init, and add empty enable and disable functions. The UI is displayed when the extension is loaded and cannot be enabled or disabled.
  • GOOD – You can rename the main function to init, and move any code that adds actors, or other UI components, to the Shell into a new enable function and add a new disable function that removes such UI components.
  • BETTER – You can rename the main function to init and modify the code in init to return an object that has enable and disable functions.

In theory, if you want your Shell extension to work with both GNOME 3.2 and GNOME 3.0, you could add a simple main function that calls init followed by enable. In practice, I have not found this to be workable unless the Shell extension is extremely simple because of the large number of differences in the names of internal variables and the workings of functions between GNOME 3.0 and GNOME 3.2.

For example, here is the GNOME 3.0 version of extension.js for my noa11y Shell extension. The purpose of that extension is to remove the accessibility (AKA a11y) button from the right-hand side of the panel.

const Panel = imports.ui.panel;

function main() {

let index = Panel.STANDARD_TRAY_ICON_ORDER.indexOf('a11y');
if (index >= 0) {
Panel.STANDARD_TRAY_ICON_ORDER.splice(index, 1);
}
delete Panel.STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION['a11y'];

}

As you can see it was a very simple extension. It simply blew away the code associated with a11y.

Here is the code for GNOME 3.2 version of the same Shell extension:

const Main = imports.ui.main;

function NoA11y() {
this._init()
}

NoA11y.prototype = {
_init: function() {
this._removed = null;
},

enable: function() {
if (this._removed == true) return;

let _a11y = Main.panel._statusArea['a11y'];

let children = Main.panel._rightBox.get_children();
for (let i = 0; i < children.length; i++) {
if (children[i]._delegate == _a11y) {
children[i].destroy();
this._removed = true;
Main.panel._statusArea['a11y'] = null;
break;
}
}
},

disable: function() {
if (this._removed == false) return;

let _index = 0;
let _volume = Main.panel._statusArea['volume'];

let children = Main.panel._rightBox.get_children();
for (let i = 0; i < children.length; i++) {
if (children[i]._delegate == _volume) {
_index = i;
break;
}
}
if (_index > 0) _index--;

let indicator = new Main.panel._status_area_shell_implementation['a11y'];
Main.panel.addToStatusArea('a11y', indicator, _index);
this._removed = false;
}

};

function init() {
return new NoA11y();
}

Most of the new code is in support of the enable and disable extension UI functionality. As you can see, supporting such functionality significantly increases the number of lines of code and complexity of the extension. I could easily make a valid argument that you need even more detailed knowledge of the internals of the 3.2 Shell than you needed for the 3.0 Shell to write Shell extensions that support enable and disable functionality.

Here is how the Linux Mint developers implemented their noa11y Shell extension:

const Main = imports.ui.main;
const Panel = imports.ui.panel;

let indicator;
let idx = null;

function init(extensionMeta) {
indicator = new Panel.STANDARD_STATUS_AREA_SHELL_IMPLEMENTATION["a11y"];
}

function enable() {
// Remove A11Y menu
for (let i = 0; i < Main.panel._rightBox.get_children().length; i++) {
if (Main.panel._statusArea['a11y'] == Main.panel._rightBox.get_children()[i]._delegate) {
Main.panel._rightBox.get_children()[i].destroy();
break;
}
}
// addToStatusArea would throw an error on disable if we don't set this to null
Main.panel._statusArea['a11y'] = null;
}

function disable() {
Main.panel.addToStatusArea("a11y", indicator, idx);
}

By the way, their MGSE (Mint GNOME Shell Extensions) repository is well worth checking out.

Monkey patching does not work the same in the 3.2 Shell as it did in the 3.0 Shell. Consider the differences between the 3.0 version of my Shell extension that replaces the SYMBOLIC icons on the panel right-hand side with FULLCOLOR icons (much nicer!)

Here is the contents of the 3.0 extension.js:

const St = imports.gi.St;
const Shell = imports.gi.Shell;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;

function main() {

// monkey patch
PanelMenu.SystemStatusButton.prototype._init = function(iconName, tooltipText) {
PanelMenu.Button.prototype._init.call(this, 0.0);
this._iconActor = new St.Icon({ icon_name: iconName,
icon_type: St.IconType.FULLCOLOR,
style_class: 'system-status-icon' });
this.actor.set_child(this._iconActor);
this.actor.set_style_class_name('panel-status-button');
this.setTooltip(tooltipText);
};

}

and here is what the 3.2 extension.js contains:

const Main = imports.ui.main;
const St = imports.gi.St;

function ColorStatusButtonsExtension() {
this._init()
}

ColorStatusButtonsExtension.prototype = {
_init: function() {
this._fullcolor = null;
},

disable: function() {
let children = Main.panel._rightBox.get_children();
for (let i = 0; i < children.length; i++) {
if (children[i] && children[i]._delegate._iconActor) {
children[i]._delegate._iconActor.icon_type = St.IconType.SYMBOLIC;
children[i]._delegate._iconActor.style_class = 'system-status-icon';
}
}
},

enable: function() {
let children = Main.panel._rightBox.get_children();
for (let i = 0; i < children.length; i++) {
if (children[i] && children[i]._delegate._iconActor) {
children[i]._delegate._iconActor.icon_type = St.IconType.FULLCOLOR;
children[i]._delegate._iconActor.style_class = 'color-status-button';
}
}
}
};

function init() {
return new ColorStatusButtonsExtension();
}

Not all of the work that you need to do to enable your Shell Extension to work with GNOME 3.2 is in extension.js. You also need to modify metadata.json to match the required version of the GNOME Shell (shell-version) and possibly also the GJS version (js-version). See the versionCheck function in extensionSystem.js. To find out the version of GNOME Shell that you are running, enter gnome-shell –version, or cat /usr/share/gnome-shell/js/misc/config.js. Currently, I set the shell-version to 3.2 in metadata.json.

By the way, if you run into trouble getting your extension to work, try the following command to invoke the GNOME Shell in debug mode:

env GJS_DEBUG_OUTPUT=stderr  gnome-shell --replace 

Also remember that Looking Glass is your friend. Get familiar with Looking Glass and you can use it to solve many a coding error. Note however that only the properties and methods of introspected objects that have been used before LG is invoked will show up.

It is nice to see that the Shell extensions got a modicum of real love in GNOME 3.2. Remember that it is not so long ago that some people were adamant that Shell extensions and themes were an evil aberration, were detracting from the purity of the one single cross-distribution GNOME Shell experience, and should be totally unsupported! With the release of GNOME 3.2, this is no longer the case. The user base won the battle. Hundreds of extensions, some useful, some quirky, had been developed since the release of GNOME 3.0 earlier this year.

  评论这张
 
阅读(1673)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017