More Extension API breaks/improvements

Since GNOME Shell Extensions launched, we’ve been overwhelmed at the response from the community. You guys are awesome, and I’m constantly impressed at what things you guys are doing with the Shell. I’ve been a bit behind on reviewing extensions and adding requested features to the website (I still need to get started on search and investigating some pretty big bugs), and I apologize. I recently landed a bunch of features to the review system to make it easier for me to review your extensions, such as rewriting the diff system. Hopefully things will go a lot faster now.

When Owen Taylor first suggested the project, we worked out some basic sketches and built an overview for the site, but we didn’t want to change the extension API until we knew what extensions were going to need or want to do. As I’ve said before, I felt that we needed a co-operative live enable/disable system for asthetic reasons: the Shell isn’t the fastest thing at restarting, and a user having to spend ten or fifteen seconds to revert your system to the previous state after deciding that an extension sucks makes for a pretty poor experience. That was the only API break that I inserted into the extension system for 3.2, and it was purely to make sure that your extensions were presented in the best way possible.

Multi-file extensions

The extension system in the shell really wasn’t designed for extensions that had multiple files. The many people that built the system had no idea that this clever hack would be commonplace. While I hate to break API, I’d say it’s for the better. Introducing the “extension object”. Previous to now, we would construct a meta object from your metadata.json, install some random things into it and pass it to you, the extension, through the init method. Over time, we inserted more and more on the metadata object where it was this jumbled bag of things that the extension system added, and things that were in the metadata.json file. While adding a few more keys for the prefs tool below, I finally decided it was worth a redesign.

Here’s an example of the new system in action:

const Gio = imports.gi.Gio;

let extension = imports.misc.extensionUtils.getCurrentExtension();
let convenience = extension.imports.convenience;

Gio.app_info_launch_default_for_uri(extension.dir.get_uri(), global.create_app_launch_context());

let metadata = extension.metadata;
Gio.app_info_launch_default_for_uri(metadata.url, global.create_app_launch_context());

function init() {}
function enable() {}
function disable() {}

Of course, you shouldn’t do these things outside of any user interaction, but a concise, imperfect example is better than one that misses the point by building an StButton, sticking it somewhere on enable(), and then removing it on disable().

You might notice that you don’t get the extension object passed to you – you go out and grab it. If you’re curious as to how we know what extension you are, there’s some clever trickery involved.

What’s on this object? A bunch of stuff that used to be on the metadata object: state, type, dir, path, etc. When we introduce new APIs specifically designed for the ease of extension, this is that place we will put them. metadata is now a pristine and untampered representation of your metadata.json file, and we’re going to keep it that way.

Preferences

One thing that we don’t really have an area for in the current 3.2 API is preferences. Unfortunately, GSettings was incompatible with extensions installed into your home directory (like extensions installed from GNOME Shell Extensions do). We couldn’t get this fixed in time for 3.2, but Ryan Lortie spent a lot of his personal time fixing this, and I thank him graciously. We now have a usable API for settings from extensions. It requires a bit more code, as you can see in gnome-shell-extensions’s convenience.js.

I spend a lot of my time looking at the code for extensions. Extensions have been shipping their own Python or gjs scripts that stick up a GTK+ dialog. Someitimes they built a configuration UI inside the extension with St. Sometimes they installed a .desktop file so that they would show up in the Applications section of the Shell. Sometimes they would insert a button to launch the script, or maybe they made users right-click on their item in the toolbar.

When Owen and I sat down to discuss this, we both decided we need some consistent way for extensions to be configured. Introducing the “GNOME Shell Extension Preferences” tool. It’s a new entry point to your extension – prefs.js. Here’s a simple prefs.js file adding support to the Alternate Tab extension.

... and here's a screenshot!

I didn’t make it too fancy, and it’s entirely possible that Giovanni may want to make a better UI.

Your entry point is a function labeled buildPrefsWidget(), and it should return some sort of GTK+ widget. Whatever you return from there will get inserted into preferences widget.

The combobox is for switching between extensions, and it’s ours. Otherwise, go crazy… the world below is your canvas. As usual, try to make it useful and pretty, but the reviewers on the extensions repo can’t and won’t discriminate against ugly UIs. There is still a policy for this, though: the widgets that you put in the preferences UI have to be related to preferences (I think I’ll allow version strings and other things though), and you should not walk up the GTK+ widget tree beyond your preferences widget (to adjust the combobox or manipulate any UI that is not yours). I’m looking forward to the neat things that you guys do with this!

“But how do I launch it?”

Oh, right. The tool is marked NoDisplay, so it won’t show up in the Applications menu. This is intentional. I’m currently working on a patch to SweetTooth so you can launch it directly from the website, provided the browser-plugin is installed correctly.

“Something broke, and I don’t think it’s my fault!”

I’m human. I make mistakes. You can let me know I was or am being a moron by filing a bug for GNOME Shell, or filing a bug for SweetTooth. While I try to respond to everyone’s mail and read all blog comments, I regularly check my task list on these two things, so this is a better system for me.

“How do I try it out?”

Providing everything goes well, GNOME Shell 3.3.5 should be released tonight, which should have all this new awesomeness. If you want to test with the new browser-plugin stuff, you’ll need to copy the browser plugin from gnome-shell/browser-plugin/.libs/libgnome-shell-browser-plugin.so to ~/.mozilla/plugins, and then restart your browser.

GJS Improvements

Myself, Giovanni Campagna as well as Colin Walters have all been working hard trying to make GJS somewhat of a competitor to PyGObject, being a full introspection stack for the GNOME Desktop Environment. Rather than give you a bunch of history, let me just give you a quick taste. There’s much more to the landing than this, such as implementing your own signals, properties, as well as implementing interfaces, but it will take me a few days to come up with an exciting example that fully showcases the power that you now have.

const Lang = imports.lang;

const Cogl = imports.gi.Cogl;
const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;

const MyClutterActor = new Lang.Class({
    Name: 'MyClutterActor',
    Extends: Clutter.Actor,

    vfunc_get_preferred_width: function(actor, forHeight) {
        return [100, 100];
    },

    vfunc_get_preferred_height: function(actor, forWidth) {
        return [100, 100];
    },

    vfunc_paint: function(actor) {
        let alloc = this.get_allocation_box();
        Cogl.set_source_color4ub(255, 0, 0, 255);
        Cogl.rectangle(alloc.x1, alloc.y1, alloc.x2, alloc.y2);
    }
});

const MyClutterEffect = new Lang.Class({
    Name: 'MyClutterEffect',
    Extends: Clutter.DeformEffect,

    vfunc_deform_vertex: function(effect, width, height, vertex) {
        vertex.x += Math.random() * 20 - 10;
        vertex.y += Math.random() * 20 - 10;
    }
});

let actor = new MyClutterActor();
let stage = new Clutter.Stage();
actor.animatev(Clutter.AnimationMode.EASE_IN_OUT_CUBIC, 2000, ['x', 'y'], [200, 200]);
actor.add_effect(new MyClutterEffect());
stage.add_actor(actor);
stage.show_all();

Clutter.main();

We’ll do it live!

Live extension enabling and disabling has landed! This allows users to do this!

I could do that all day. It’s quite fun.

But there’s one little problem. Extension developers? We need to talk.

“What did I do?”

Nothing bad, I promise. You just have a new responsibility: in case the user doesn’t like the extension, you need to undo all the changes that you ever made to the shell. Make it look like your code never touched the place. Otherwise: that video above? The giant illusion will drop to the ground and crack into a thousand pieces. Like that China I broke last weekend.

“Uh, OK. How?”

If your extension looked like this before, you need to make it look like either this, or this.

Put simply: main(), uh, how do I say this? main() has been, uh, let go. Sure. Don’t worry though, he’s been replaced with init(), enable() and disable(). They’re just as cool. I promise. The in-depth obitua — uh, termination notice can be read here. init()‘s best friend, the “helper” object couldn’t make it for this trip. He’ll be here next year.

There’s three simple rules to take into account:

  • There used to be one required method: main(). This has been removed. It has been replaced with a new hook, init(). You should use init() to initialize any state. Do not modify the Shell inside of this function.
  • The init() may return an object, called the “state object”. If you do not return an object, the module is considered the state object. This object is supposed to have two new methods: enable() and disable(). These should modify Shell in the appropriate way. For lack of better vocabulary, extensions which use the module (return nothing from init()) as their state object are called “stateless” objects, and those which don’t are “stateful”.
  • init() is guaranteed to be called at most once per shell session. enable() and disable() may be called at any time. If your extension is enabled at the start of the session, enable() will be called. If your extension is disabled at the start of the session, disable() will not be called, and there is no guarantee that init() will be called.

Additionally, keep in mind that Shell extensions are versioned, so only extensions that are targeted for 3.2 should port to the new API. Some extensions that want to support both 3.0 and 3.2. Fortunately there is an easy way to do exactly that. Just run init(), and then enable():

function main() { init(); enable(); }

… Stateful extensions just need to run enable() on their state object.

function main() { init().enable(); }

Oh, and one last thing. Thank you. You don’t have to believe it, but I’m doing this for you. I’m trying to make it easier for users to safely try out your awesome work. There’s more awesome stuff for you guys on the way. I promise.

One is the loneliest border…

Today is an exciting day for GNOME 3.0! Why? Invisible borders have landed! What does this mean for you? One pixel borders are no longer! No more pixel hunting! The last remnants of the cursor accuracy kingdom have been tore down! Pointer precision, you are —

“What?”

Let me draw you a picture.

Motivation

In GNOME3 before today, if I wanted to conveniently resize a window, I had to hunt for one-pixel borders. I would finally get my mouse positioned right on the border, and then carefully click…

and then miss… So, it’s not surprising people are upset about this. Now, you can go outside the actual visible part of the window, and still resize it!

Now, you have as much space in the world to resize your windows: we extend the resizable area to outside of the actual window. Additionally, the mount of area that is available to resize is a user preference, and completely customizable! That is, the green area in the picture below is completely customizable by a gconf setting.

Implementation

The way I did this was by making every X window a little larger, and use Owen’s existing shaping code to hide the excess area around the borders. This means that toolkits that use the parent window’s size to find their visible extents are now going to break. Use the _NET_FRAME_EXTENTS X atom as a replacement.

Oh, and since I’m using Owen’s existing shaping code, and that uses an 8-bit mask to hide the shaped region, this made it quite a bit easier to add a feature that people have been begging for for a little while: antialiased borders. This hasn’t landed quite yet, but it should be coming soon enough!

Next time, I’ll talk about SweetTooth some more!

Shell-like Switch Widget

A recent SweetTooth screenshot

I’ve been tinkering with web development a lot lately as part of my work with SweetTooth, the GNOME Shell extensions repository I’m working on for GNOME.

At the suggestion of Vinicius Depizzol, who’s been doing a wonderful job redesigning the GNOME web sites, I tried my hand at creating a Shell-like switch widget to allow a user to click a button to turn extensions on and off. Using some precious combination of JS and CSS, I’ve finally made a switch widget I think looks nice. Go ahead: grab it, click on it, fling it around, try and break it! It should just feel “right”.

Figuring out whether the widget is activated and setting the appropriate style classes, the drag logic, and figuring out where to put the switch handle are the only responsibilities of the JS code: everything else is done with CSS, using a combination of border-radius, position: absolute, floats. The triple-line on the grab handle is “drawn” with CSS and manipulates the border styling properties to do its bidding. The animations to fade the background-color and slider position are all CSS3 transitions.

There’s still one unsolved problem, which is the flash and fade of the background color when loading the page, and as much as I tried, I couldn’t solve it. It’s a fallout from CSS thinking it has transitioned from one class to another, but in actuality, it’s just jQuery adding a style class after the node has been constructed. Feel free to yell at me I’m being stupid.

The unnecessary details of my adventure

As usual, the hard part in concocting something like this is compatibility. The web truly isn’t meant for something pixel-perfect like this, and it shows. Browsers like IE, where compatibility is usually a pain, weren’t involved at all here (I haven’t bothered to test them — again, yell at me). Through poking people on IRC, I found that OS X has different font metrics for the “ON” and “OFF” labels

(A mini rant to the CSS WG: why can’t I create a new flow root/block formatting context to contain floats by asking for one, instead having to rely on the side effects of other properties?)

Originally, the DOM looked like this image to the left, if you forget about the switch handle. That is, there were two sub elements that were absolutely positioned inside the switch: the switch was hardcoded to a 64px width. Here, everything worked fine and the code was relatively simple.

The problem was that on some browsers and platforms, the text metrics are inconsistent. Unfortunately, the HTML specification gives no guarantee about text measurement, explicitly stating it to be agent-specific. I could deal with this, but ti would be nice if CSS had relative units for glyph widths or JS let you get at some arbitrary measurements of text. <canvas> lets you get at a TextMetrics object, but the only current attribute there is width. For instance, even though I included a web font, WebKit on Mac (which uses CoreGraphics) looked terrible.

Just to make me more irritated, a normal font-weight made it work perfectly, and was almost pixel-perfect to the way bold looked on every other browser/OS. “Go eat a ham”, said Apple. So, obviously, I can’t rely on things which aren’t meant to be relied on.

Welp. So, using some complicated trickery using width: 50%, floats, float containment, I finally got a DOM I wanted, and should hopefully be a bit more flexible. This one should make the box stretch in accordance to font weights and such: float containment means that the parent will expand to the container, and width: 50% means that the children will expand to fill half their container. Perfect.

I simultaneously love and hate web development because of things like this.