qdbus

As an addendum to my comparison between DCOP and D-Bus, I recently discovered that Qt 4 apparently ships with a qdbus utility that acts like the command-line dcop utility, but using D-Bus. (I say “apparently” because Debian ships it as part of the libqt4-core package.)

For example, invoking a method:

$ qdbus org.gnome.Rhythmbox /org/gnome/Rhythmbox/Player getPlayingUri
file:///home/paul/music/Jonathon%20Coulton/Chiron%20Beta%20Prime.mp3

Or, listing the methods and signals available on an object (and ignoring the obvious instances of word wrapping below):

$ qdbus org.gnome.Rhythmbox /org/gnome/Rhythmbox/Player
method QString org.freedesktop.DBus.Introspectable.Introspect()
method QDBusVariant org.freedesktop.DBus.Properties.Get(QString interface, QString propname)
method void org.freedesktop.DBus.Properties.Set(QString interface, QString propname, QDBusVariant value)
signal void org.gnome.Rhythmbox.Player.elapsedChanged(uint)
method uint org.gnome.Rhythmbox.Player.getElapsed()
method bool org.gnome.Rhythmbox.Player.getMute()
method bool org.gnome.Rhythmbox.Player.getPlaying()
method QString org.gnome.Rhythmbox.Player.getPlayingUri()
method double org.gnome.Rhythmbox.Player.getVolume()
method void org.gnome.Rhythmbox.Player.next()
method void org.gnome.Rhythmbox.Player.playPause(bool arg0)
signal void org.gnome.Rhythmbox.Player.playingChanged(bool)
signal void org.gnome.Rhythmbox.Player.playingSongPropertyChanged(QString, QString, QDBusVariant, QDBusVariant)
signal void org.gnome.Rhythmbox.Player.playingUriChanged(QString)
method void org.gnome.Rhythmbox.Player.previous()
method void org.gnome.Rhythmbox.Player.setElapsed(uint elapsed)
method void org.gnome.Rhythmbox.Player.setMute(bool mute)
method void org.gnome.Rhythmbox.Player.setVolume(double volume)
method void org.gnome.Rhythmbox.Player.setVolumeRelative(double volume)

Maybe qdbus doesn’t have as many knobs to fiddle with as dbus-send, but in the common case it’s much more convenient.

Happy Halloween!

Um, I mean, “Merry Christmas.” I keep getting those confused, since OCT 31 = DEC 25.

*rimshot*

And for those of you who buy into the whole “War on Christmas” nonsense, this one goes out to you (possibly NSFW, as if you’re at work today anyway):

God hates Christmas trees

Thus saith the LORD, Learn not the way of the heathen, [...] For the customs of the people are vain: for one cutteth a tree out of the forest, the work of the hands of the workman, with the axe. They deck it with silver and with gold; they fasten it with nails and with hammers, that it move not.

Jer 10:2-4

Of course, not to be outdone, the Puritans hate Christmas entirely:

For preventing disorders, arising in several places within this jurisdiction by reason of some still observing such festivals as were superstitiously kept in other communities, to the great dishonor of God and offense of others: it is therefore ordered by this court and the authority thereof that whosoever shall be found observing any such day as Christmas or the like, either by forbearing of labor, feasting, or any other way, upon any such account as aforesaid, every such person so offending shall pay for every such offence five shilling as a fine to the county.

Records of the General Court, Massachusetts Bay Colony, May 11, 1659

Comments Off

My kingdom for a pango.Matrix

Why oh why does PyGTK define a pango.Matrix class to wrap a PangoMatrix struct, and provide a set of methods for manipulating them, but no way to create the blasted things in the first place?

Seriously:

>>> import pango
>>> m = pango.Matrix()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NotImplementedError: pango.Matrix is an abstract widget

Abstract widget? Abstract widget!? What’s abstract about a plain old C struct?

typedef struct _PangoMatrix    PangoMatrix;
struct _PangoMatrix
{
  double xx;
  double xy;
  double yx;
  double yy;
  double x0;
  double y0;
};

It’s not an abstract interface. It’s not even something derived from GObject, for crying out loud. It’s six doubles duct-taped together! PyGTK should be able to wrap this in its sleep. And don’t even get me started on how pango.Matrix is most certainly not a widget; it’s a smegging matrix.

And to make things worse, there doesn’t even seem to be a way to make a trivial C function that creates a PangoMatrix* and make a PyGTK-compatible wrapper for it. Since PyGTK knows about pango.Matrix, and has methods that work with them, I obviously need to tell PyGTK that my trivial C function returns one. No such luck:

Could not write function ma_matrix_new_rotate: No ArgType for PangoMatrix*

And lest you think that PyGTK wants to internally call it PyPangoMatrix_Type — which, after all, would fit with the naming scheme for everything else it wraps out of GTK — that doesn’t work either.

Apparently pango.Matrix is in some kind of weird limbo where PyGTK knows about it but won’t let you actually use it at all.

Unless anyone knows the proper voodoo to make PyGTK cooperate, it looks like I’m going to have to implement the entire widget in C instead of Python, just because I can’t smuggle a PangoMatrix into my Python code. After I’ve gone ahead and implemented the entire widget except for what I need the PangoMatrix for.

Yippee.

Comments Off

Cop versus Bus (D- edition)

[Disclaimer: Despite what you may have assumed from the title, this post does not discuss the obscure rock, paper, scissors variant “cop, bus, driver” (where driver controls bus, bus runs over cop, cop shoots driver). We apologize for any confusion.]

It has been remarked that the great thing about standards is that there’s so many to choose from. While working on adding support for Amarok to the next version of Music Applet, I ran into this first-hand.

GNOME and KDE, the two main desktop environments for Linux, each have a preferred IPC mechanism to provide a relatively easy way for programs to talk to one another. GNOME uses D-Bus, and several of the players currently supported by Music Applet use it. KDE, however, uses DCOP, and since Amarok is a KDE-based application, it provides a DCOP interface.

D-Bus and DCOP are, at a high level at least, pretty similar: they’re based on making method calls on objects exported by applications. They’re mutually incompatible with each other, of course, but they’re targeting the same niche. So, since Music Applet 2.3.0 will be using both of them to some extent, how do they shape up against each other?

I’m not going to go into the technical merits of each, or any of their implementation details. I’m only looking from the perspective of someone trying to write some Python code that interacts with other programs via D-Bus or DCOP.

Both D-Bus and DCOP provide a command-line interface for issuing individual method calls. While you wouldn’t normally use this interface in a program (unless you’re writing a shell script, of course), it is handy for quick testing, making sure the method you’re looking at does what you think it does.

Let’s say we want to ask our music player what song it’s currently playing. For Rhythmbox, which uses D-Bus, we need to do this:

$ dbus-send --print-reply --dest=org.gnome.Rhythmbox /org/gnome/Rhythmbox/Player  \
        org.gnome.Rhythmbox.Player.getPlayingUri
method return sender=:1.13 -> dest=:1.23 reply_serial=2
   string "file:///home/paul/music/Jonathon%20Coulton/Chiron%20Beta%20Prime.mp3"

You’ll note that I had to break the command across two lines to get it to fit in this column, and a lot of that command looks pretty redundant. Yes, yes, it avoids namespace pollution in case an application or an object supports multiple interfaces defined by different parties, but it’s a pain to type out. Also note that I had to include --print-reply so that the command would actually say what got returned. Also also, not pictured here, if you’re passing arguments to the method you’re calling, you have to explicitly say what the type of each argument is. Ick.

Now let’s see what the equivalent use of the DCOP command-line tool for calling the equivalent method on Amarok is:

$ dcop amarok player encodedURL
file:///home/paul/music/Jonathon%20Coulton/Chiron%20Beta%20Prime.mp3

Even though it’s saying the same sort of thing (listing an application, an object owned by that application, and a method on that object), this is much easier to type. And even better, if we leave out some of the arguments, the command will list what’s available. For example, if we instead did:

$ dcop amarok player

It would list all the methods available on the player object exported by amarok. Pretty nifty. Sure, there’s a D-Bus way to do that, but you need to remember what method to call on the org.freedesktop.whatever interface of the object — or is it a separate object that handles introspection? I’d have to look up the D-Bus documentation. But the DCOP command-line tool doesn’t make you do that.

When we turn to the Python interface, things improve a bit for D-Bus, even though we’re still stuck with those very long fully qualified names for everything. Here’s a quick Python program that does the same thing as the command-line example:

import dbus
 
bus = dbus.SessionBus ()
player_proxy = bus.get_object ("org.gnome.Rhythmbox", "/org/gnome/Rhythmbox/Player")
player = dbus.Interface (player_proxy, "org.gnome.Rhythmbox.Player")
url = player.getPlayingUri ()
print url

Of course, if we’re going to make multiple calls to the object, we only have to set things up once, and then reuse the object as many times as we want.

The DCOP equivalent once again looks simpler (at least for now):

import pcop
import pydcop
 
# Magic goes here, to be discussed later...
 
amarok = pydcop.anyAppCalled ("amarok")
url = amarok.player.encodedURL ()
print url

While this might seem like another win for DCOP, things don’t look so good once we move away from these toy examples. For starters, the Python DCOP bindings don’t provide any support for asynchronous calls. Instead, all calls are synchronous: once we make the call, the program sits there and does nothing until it receives a response. If that other program you’re talking to takes a long time to respond, that spells trouble for our program’s responsiveness to the user, especially in a GUI, where now our program won’t even be able to redraw itself.

The Python D-Bus bindings do provide a way to do asynchronous calls, by passing a function that should be invoked once a response comes back from the other program. In the meantime, our program can go on to do other things. This also lets our program issue multiple IPC calls all at once more efficiently: in the ideal case, the other program can send its responses to all our calls during a single timeslice. If we’re stuck with synchronous calls, each one gets serviced in separate timeslices, which will end up taking longer.

Technically, this lack of asynchronous calls isn’t a fault with DCOP per se, but rather with its Python bindings. The underlying C++ library supports asynchronous calls. Adding support for this for Python programs is listed in the TODO file.

However, there’s a more obnoxious problem with DCOP when compared to D-Bus. Although D-Bus is used by GNOME, it’s not actually part of GNOME; it’s a freedesktop.org project with minimal dependencies. So, if D-Bus isn’t already running (which in GNOME is pretty unlikely anyway), there isn’t that much the library has to do to start it up.

DCOP, on the other hand, is a part of KDE itself, and to assure that it’s running, it’s necessary to initialize at least the base set of KDE services. Remember that code in the Python DCOP example I didn’t show you before? Here’s what it is:

import kdecore
import sys
app = kdecore.KApplication (sys.argv, "program name")

That’s right, our program has to initialize a KDE application object. It doesn’t look like much code-wise, but that initialization will go ahead and start up DCOP and a handful of other KDE services before it returns, if they’re not already running. And if you’re running a GNOME-based desktop — and if you’re running Music Applet, you certainly are — you probably don’t have all the KDE services running yet.

On my laptop at least, this initialization takes several seconds. As a result, if you have Music Applet’s Amarok plugin enabled, this adds several seconds to start-up time before the applet appears in the panel.

To avoid this delay, it’ll be necessary to create a thread in the Amarok plugin just to do that initialization. Ick. Note that deferring initializing the plugin until the applet appears in the panel doesn’t really help, since the applet would then just become unresponsive while that initialization is taking place. And no, launching dcopserver via a call to os.system instead of initializing KDE doesn’t quite work. I mean, it does work just for DCOP, but when a proper KDE application starts, it will get confused about how only some of the KDE services are running and will start spitting out error messages and will behave a bit strangely.

Even worse, if the Python DCOP bindings try to connect to the DCOP server and fail, they will never try again. So you have to make sure the DCOP server is running before you try using it at all.

So, while DCOP does have simplicity going for it, D-Bus ends up providing a more robust Python interface and lower initialization overhead, which are both big plusses when you want your program to be small and responsive.

Fun fact: KDE4 will be ditching DCOP in favor of D-Bus. Unfortunately, I expect this will require having both DCOP and D-Bus in Music Applet’s Amarok plugin, so that it works with Amarok-for-KDE3 and Amarok-for-KDE4 seamlessly. Sort of like how, back in Music Applet’s C days, there was Rhythmbox-via-D-Bus and Rhythmbox-via-Bonobo. Thankfully no Music Applet users seem to care about compatibility with semi-ancient Rhythmbox pre-0.9 compatibility these days.

Quick PyGTK Tip

If you ever find yourself writing a custom widget using PyGTK, and can’t figure out why none of your do_something methods are ever getting called, make sure you have this line of code after your class’s definition:

gobject.type_register (YourCustomWidgetClass)

Believe me, remembering this will save you at least an hour of very confused debugging. PyGTK will never complain that you forgot to do this. Instead, you’ll just end up with a widget that doesn’t actually do anything.

Comments Off

Never invite a LARPer to your nativity play

Or else this might happen:

Just in time for Hanukkah! Or Channuah! Or anything else matching /(H|Ch)an{1,2}uk{1,2}ah?/.