g_variant_equal() and dictionaries

For anyone using g_variant_equal() with a type containing a dictionary, you should the aware that, somewhat unexpectedly in my opinion, g_variant_equal() only returns true if (and only if) the keys in the dictionary are in the same order (GVariant implements dictionaries as an array of key-value pairs).

You can resolve this by using a function that recursively checks for semantic equivalence (such as this one).

I’m sure I’m not going to be the only person to find this useful. I’ve filed this as bug #622590.

Empathy master ported to GSettings

GLib 2.25.9 just came out, which included a GSettings bugfix I needed, so I’ve just merged the GSettings port branch I wrote for Empathy into master. Unfortunately this does not come with a fancy screenshot. It uses the DConf backend by default, if you do not have DConf, your settings won’t be preserved between instances.

This work includes a .convert file that can be used by gsettings-data-convert to convert from GConf to DConf. I got kinda bored of maintaining this file by hand, so I wrote an XSL script to generate it from the schema XML.

Bugs in GNOME Bugzilla please 🙂

Also, to the Americans and Canadians who enjoy Australian, female folk singers: my favourite band is touring your countries for the next three months (you have to click Next on the page to see every gig). You should go and see them!

empathy: the future's gonna be awesome

So we've been doing a bit of work on Empathy lately (but too late for GNOME 2.30 I'm afraid), and trying out some crazy new ideas.

One of these Favourite Contacts. You can now mark contacts in your contact list as favourites, and they will always been shown at the top of your roster (as well as in any groups they're a part of).

Another is Frequent Contacts. This information is generated by the new telepathy-logger service, and shows which of your contacts (on your contact list) message you most frequently.

The telepathy-logger is a new desktop wide logging service in development that applications (such as Zeitgeist) will be able to query to retrieve conversations that take place via Telepathy. As well as recording the actual messages you send and receive it also generates statistics, such as your most frequent contacts. This means information such as your favourite and recent contacts are also available to other Telepathy clients (perhaps they could appear integrated into GNOME Shell along with current conversations).

We're also considering adding Recent Contacts, so you can quickly return to a conversation you have been recently having after you close the window.

a threaded processing queue in PyGTK

I'm currently writing a PyGTK client that needs to make network requests using a library that doesn't integrate with the GLib mainloop (python-suds), so I found myself wanting to be able to make network requests without blocking the mainloop, and getting callbacks in my main thread when operations were done. The pattern to use is clearly having a dedicated network thread. In C I might have used GAsyncQueue, however I've found myself quite liking queue.Queue.

The following is a fairly generic class for queuing asynchronous requests. Calling the add_request() method from the main thread queues a function to be run in the worker thread. If the callback or error keywords are provided, these will then be called from the GLib mainloop in the main thread (queued via g_idle_add).

from threading import Thread
from Queue import Queue

import gobject

class ThreadQueue(object):
    def __init__(self):
        self.q = Queue()

        t = Thread(target=self._thread_worker)

    def add_request(self, func, *args, **kwargs):
        """Add a request to the queue. Pass callback= and/or error= as
           keyword arguments to receive return from functions or exceptions.

        self.q.put((func, args, kwargs))

    def _thread_worker(self):
        while True:
            request = self.q.get()

    def do_request(self, (func, args, kwargs)):
        if 'callback' in kwargs:
            callback = kwargs['callback']
            del kwargs['callback']
            callback = None

        if 'error' in kwargs:
            error = kwargs['error']
            del kwargs['error']
            error = None

            r = func(*args, **kwargs)
            if not isinstance(r, tuple): r = (r,)
            if callback: self.do_callback(callback, *r)
        except Exception, e:
            if error: self.do_callback(error, e)
            else: print "Unhandled error:", e

    def do_callback(self, callback, *args):
        def _callback(callback, args):
            return False

        gobject.idle_add(_callback, callback, args)

We can then inherit this class to provide setup for our specific application:

class WebService(ThreadQueue):
    def __init__(self, guid=None, **kwargs):
        """Initialise the service. If guid is not provided, one will be
           requested (returned in the callback). Pass callback= or error=
           to receive notification of readiness."""

        self.guid = guid
        self.add_request(self._setup_client, **kwargs)

    def _setup_client(self):
        print "Setting up client"


        return self.guid

Which we call from our program like this:

class Client(object):
    def __init__(self):
        self.w = WebService(guid=guid, callback=self.client_ready)

    def client_ready(self, guid):
        print "client ready:", guid




What's really cool though is adding methods to the API that are called asynchronously for you. Python makes this possible through the power of decorators. Add the following decorator to a method, and it instead of it being called directly, it will be added to the processing queue.

def async_method(func):
    """Makes the given method asynchronous, meaning when it is called it
       will be queued with add_request.

    def bound_func(obj, *args, **kwargs):
        obj.add_request(func, obj, *args, **kwargs)

    return bound_func

class WebService(ThreadQueue):

    def GetStopInformation(self, stopNo):
        print "Requesting information for stop", stopNo


And that's it! If you can't follow it, don't worry too much. This is possibly the most Pythonesque bit of code I've ever written, but I've tried to make it generic enough that other people can use it for whatever they need. It's currently part of my app that's beginning to take shape, but the source is here.

Incidently, Maemo people: are there Glade definition files allowing me to use Hildon widgets, GtkBuild and Glade 3? That would be super awesome if there were.

GTK+ is crushing my spirit

I want a widget that is the combination of GtkComboBoxEntry and GtkEntryCompletion that can display a tree of options in a nice, indented way without the expanders (but with the rows expanded). Basically, a search box with a drop-down and hierarchical entries. I think I'm going to have to write my own, thought it sounds like something that might be more widely useful than just my application.

penguin in the tree
Visited '[info]'nixwilliams and '[info]'daniel_bethany for a cup of tea, which become lunch (which was delicious) which became more tea, which became waiting out the rain and watching Nicholas Crane trip over a lot. Got wet going home anyway. Now waiting out the rain again before going to the shops. It's a good thing that I like rain; though mostly I like wearing a giant, warm jumper while it rains outside.

status update on Clutter-GTK

Some time ago I wrote about my work on Clutter-GTK. Well, I finally got around to rebasing this branch so it could be merged and ebassi merged it for me last night.

There are still bugs in the code that I've not fixed, but many eyes make all bugs shallow, right?

The code includes a number of examples of how to do things.

If you're going to use these classes, you should understand how they work. There are caveats. Certainly I wouldn't base your entire toolkit around the interchanged use of GtkClutterActor and GtkClutterStandin.

GtkClutterActor takes a GtkWidget, rendered offscreen to an XPixmap, and paints it onto a GL texture which it draws on the Clutter stage. As a result, it can draw that texture anywhere, at any stacking level, and do things like taking events and sending them back to GTK+. Updating of the offscreen pixmap is done using Damage events, and things are generally peachy.

GtkClutterActor Implementation
Things basically work like you expect. Although there are some performance issues where widgets that draw using two expose events will sometimes have a delay between the two Damage events that I've not yet explained, which can cause a visual rip.

If GtkClutterActor lets you embed a GtkWidget into a Clutter stage, then GtkClutterStandin goes the other way around, allowing a ClutterActor to be placed within a GtkContainer; but the implementation is different. The ClutterActor is not rendered into offscreen memory, and then copied into an XPixmap and blitted into GTK+, this would not allow for all of those exciting animations you want to do.

Instead what happens is that GtkClutterStandin reserves space within a GtkContainer for the actor it proxies and then places that actor on top of the texture containing the widget contents.

Bugs should be filed in the Opened Hand Bugzilla.

and now for something from the school of pure evil (setting a background on a GtkTextView)

This is something that comes up from time to time, someone wants to set some attractive background on a GtkTextView. Well, unfortunately for you, the theme engine isn't able to help you here because GtkTextView never calls gtk_paint_box() or friends. Still, today I stared at the code for a bit and came up with a particularly hacky solution: see for yourself. You have to handle everything yourself, resizing (hook into size-allocate), scaling whatever is needed. You could probably cheat though and call something like gtk_paint_box() and then let the theme engine do that bit of the work for you… I've not tried.

Just to prove it works:

This depends on what is basically an implementation detail of the widget, and I make no promises that it will continue working in the future. With that warning in mind, do what you will with the code.

P.S. the background image is a photo of the art in my hotel room. If I was Google, I'd totally sue.

quick and dirty icon theme viewer

So very often I find myself trying to find the name of an icon in the icon theme, and I end up having to search through my icon theme with a combination of find and eog. Usually I miss the icon on my first pass through.

Today I cracked, and decided to hammer together a quick and dirty viewer and searcher for the icon theme which looks something like this:

icon theme viewer
It's written in PyGTK and so far only has the single feature of 'Search' (this is my 90% case). It could be a lot more advanced if people wanted to hack on it: it could select themes, show whether an icon is in this theme or a parent theme, what sizes are available, etc. I will certainly accept patches!

Hopefully other people find this useful too. Source code is here.

GTK+ client-side-windows and threading

Some people (including me) had noticed that recent Empathy's video calling was breaking with recent GTK+ builds. Some people (not including me) were smart enough to work out the bug was related to client-side-windows.

In CSW-enabled GTK+, calls to GDK_WINDOW_XID() — the function with returns the XID of a GdkWindow — implicitly makes a call to gdk_window_ensure_native() before retrieving the XID. It does this so that there is a window on the X server to return the XID for, and thus ideally no one's application will break under CSW. The problem in Empathy was that the first call to GDK_WINDOW_XID() happened in a thread, which in the olden days would have been harmless, but now it can magically result in your window being allocated server side.

The fix for Empathy (and maybe your program to) is to call gdk_window_ensure_native() as soon as the widget is realized. In Empathy we're calling GDK_WINDOW_XID(), because we don't want to depend on GTK+ 2.18, which looks a lot like this.

Creative Commons Attribution-ShareAlike 2.5 Australia
This work by Danielle Madeley is licensed under a Creative Commons Attribution-ShareAlike 2.5 Australia.