Panflute

Panflute is the new black Music Applet.

Let me explain.

Panflute is slated to be the successor to Music Applet. Its fundamental architectural change is the complete separation of the part that draws the panel applet from the part that figures out how to talk to the backend music player. By “complete”, I mean that Panflute makes them entirely separate programs. This opens the possibility of other software also using the Panflute backend instead of figuring out its own way to talk to a dozen different music players. A panel applet is just one possibility — you might want a desklet, or whatever GNOME 3.0 will replace panel applets with, or an alarm clock, or something else I can’t even think of.

The goal of the Panflute backend is to make everything look like it has a nice, clean MPRIS interface. MPRIS is great because it specifies a common interface for programs to talk to music players. MPRIS isn’t so great because many popular players (such as Rhythmbox and Banshee) don’t use it, and many players that do implement it either deviate from the spec in some areas (such as Audacious) or have some odd quirks about their interpretation of the spec (such as Amarok). The Panflute backend papers over all these issues, presenting a single, common, consistent interface, regardless of what player is actually running. It also adds some (clearly marked) extensions to MPRIS to provide features not available in MPRIS 1.0, such as setting metadata (particularly ratings) for the current song, or having a convenient way to get updated position information without having to resort to polling.

Panflute also provides a panel applet to replace the old Music Applet. At this time not much has changed feature-wise, but by doing a ground-up reimplementation, I’ve been able to throw out a bunch of legacy code to work with now-fairly-old libraries and to redesign things to support more flexible layout of the applet’s content, such as the oft-requested support for fat panels.

Perhaps most importantly, however, is the fact that I’m using Launchpad to host development of Panflute instead of doing things directly off of kuliniewicz.org. This means I’ve now done something I should’ve done a long time ago and started using a proper bug tracker instead of relying on e-mail. Finally.

The plan for now is to get Panflute up to feature-parity with the most recent release of Music Applet and make that a 0.something release. The 1.0 release will try to include as many feature requests from Music Applet as I can manage, as well as a testing tool to help verify that the code that handles talking to each music player works as expected, and to map out what functionality isn’t available. (Hey, that’s another thing that can exploit the applet/backend separation!) The 1.0 release should also make sure those player support modules implement as much of the MPRIS spec as possible; so far I’ve been focusing on the /Player object (which the applet makes heavy use of) and largely ignoring the /TrackList object (which it ignores completely). Obviously, for the Panflute backend to be generally useful for other developers, it needs to be full-featured.

I really ought to formalize the above paragraph a bit and get a proper blueprint for future development up in Launchpad….

Anyway, the Panflute code hosted on Launchpad is functional, with all applet features except pop-up notifications implemented and backend support for Rhythmbox, Banshee, Amarok, Audacious, Muine, and VLC implemented. There’s still some rough edges, particularly how the applet doesn’t yet bother to start the backend process, but those issues will be easy enough to fix. (And now that there’s an actual bug tracker, you can file a bug if something is amiss and/or missing.)

Music Applet 2.5.1 released

Hot on the heels of the previous version, Music Applet 2.5.1 has been released. This version is purely a bug-fix release and adds no new features. It does, however, fix a bug that could leave the applet in a hung state when the music player closes, and it fixes a bug in fetching album art from Rhythmbox.

Music Applet 2.5.0 released

Keeping my word, Music Applet 2.5.0 is now available. This release adds support for the long-awaited Amarok 2. It also fixed lots of bugs. In particular, the applet will no longer try to take over your entire panel if you play a song with a long title, and handling of metadata when playing Internet radio streams is vastly improved. There are additional bug fixes too; check the release notes for details.

Music Applet 2.4.2 released

Music Applet 2.4.2 is out. This is another bugfix-and-translation-updates-only release. In particular, it fixes the slow creep in CPU usage after each song and updates the Polish (pl) translation.

Music Applet 2.4.1 released

Music Applet 2.4.1 has been released. This is a bug-fix release, with no new features. The dependency on the deprecated python-dcop, previous used for Amarok support, has been removed. A bug in password handling for MPD has also been fixed. See the release notes for details.

Music Applet 2.4.0 released

Music Applet 2.4.0 has just been released. The main change is that it now supports the recently-released Banshee 1.0, in addition to older versions of Banshee. There’s also improved support for debugging crashes, as well as an updated Czech (cs) translation.

Unfortunately, the new version of Banshee doesn’t provide a way to manipulate song ratings. Once that gets fixed in Banshee, I’ll update Music Applet accordingly.

A few technical notes on Banshee 1.0 support: since this new version of Banshee completely changed its D-Bus interface, there’s now two plugins for Banshee: the old one and the new one. While it’s inelegant to have two plugins for what the user sees as one program, it’s the only reasonable technical solution, given that the two versions of Banshee are completely different as far as Music Applet is concerned. If you have problems with Banshee support after upgrading the applet, check to make sure the new plugin (now called “Banshee”) is enabled. The old plugin has been renamed “Banshee (pre-1.0)”. I have no plans to remove support for old versions of Banshee any time soon.

Music Applet 2.3.1 released

A new release of Music Applet, version 2.3.1, is now available. This is primary a brown-paper-bag release, fixing the crasher that, in hindsight, probably affected everyone trying to use 2.3.0. If you were one of them, try upgrading to 2.3.1 and see if that fixes things for you.

For those interested, I’ve done a write-up of what was causing the bug and why it took me so long to figure it out.

There’s also a few other bug fixes, as well as a bunch of new translations for Czech (cs), German (de), Spanish (es), French (fr_FR), and Russian (ru).

Brown paper bag

For those of you who ran into the bug in Music Applet 2.3.0 where the applet would seem to crash as soon as you started it, well, I finally figured out what was going on. It turns out to be a blatant brown-paper-bag bug, and to proper chastise myself for releasing Music Applet with it, I will now detail what its cause was and how I discovered it.

Since I have Debian’s music-applet package installed, during development I install the new code in a separate directory and run it from there. This leaves the problem of actually loading the applet from its nonstandard location. Normally, when you add an applet to a panel, Bonobo checks its database to see what program needs to be run. However, if the program is already running, it will connect to it instead of starting a new instance. So, when testing new code, I start the new applet code from the command line, and then add it to the panel.

This way, I don’t have to touch the system files to run the new code. This method also gives me an opportunity to augment the Python path with the directory where the new code’s modules are, since naturally they’re in a nonstandard location too. It furthermore has the advantage of printing the output on stdout and stderr to the console, whereas if Bonobo started the applet, they’d wind up in /dev/null, which is bad for debugging.

Now, several people reported the crash-on-startup-if-Amarok-support-is-enabled bug, but I was completely unable to reproduce it on my system. None of them had a stacktrace or core file or any other debugging output to offer. However, they were all using Ubuntu and Python 2.5, whereas Debian still uses Python 2.4 by default. It looked like the trouble might lie there.

After jumping through hoops to get the applet running on my system under Python 2.5 (complicated by the fact that Debian for some reason doesn’t have versions of the DCOP modules for Python 2.5), I still wasn’t able to reproduce the problem. Naturally, if I can’t reproduce the problem, and I can’t get any good details from the bug reports, I can’t figure out what’s causing the bug, let alone verify that any fix I come up with works.

I finally decided to go all-out and try to reproduce the exact circumstances of the crash on my machine, without completely wrecking the copy of the applet provided by Debian. I hacked the Bonobo .server file for the applet to point to the local copy of the applet, and then hacked the applet startup code to explicitly run under Python 2.5. Oddly, when the applet started, it would identify its version of 2.2.1 (the version provided in Debian) and not 2.3.0.bzr (the version installed locally), nor would it have any of the features added in 2.3.0, including the Amarok plugin.

After banging my head on this for a while, I realized that even though Bonobo was running the local copy of the applet’s startup code, that startup code was then loading the support modules installed in the system paths — the 2.2.1 code — instead of the local code. When starting the applet from Bonobo, there’s no opportunity to tweak the include path like you get from the command line. I then further hacked the applet to add the right paths to sys.path, and finally it started working, in that it stopped working and crashed like it was supposed to. Or, not supposed to, but you know what I mean.

Finally, I was getting somewhere. To track down the line causing the crash, I started putting lines like this in the code to trace the control flow:

os.system("xmessage 'About to start thread'")

This would pop up a crude dialog box with the message, which I relied on instead of printing to stdout or stderr since, well, those were going to /dev/null now. After playing around with things for a while, I tracked down the offending line of code:

self.__plugin._app = kdecore.KApplication (sys.argv, "music-applet")

Thinking it might be throwing an exception for some reason, I tried wrapping it in a try/except block, but no luck. That was definitely the line, but still no clue why it was failing so spectacularly. Thinking there might be something on stdout or stderr that could help, I realized I could point them at files on applet startup so I could read them after the fact:

sys.stdout = open ("/home/paul/music-applet/debug.stdout")
sys.stderr = open ("/home/paul/music-applet/debug.stderr")

Now I was seeing the normal debugging spew from the applet, but still no messages about the crash. It then hit me that this was only going to affect my Python code; everything else would still be using the “true” stdout and stderr. I’d have to literally dupe the system into redirecting the underlying file descriptors into writing into my debug files:

new_stdout = open ("/home/paul/music-applet/debug.stdout", "w")
new_stderr = open ("/home/paul/music-applet/debug.stderr", "w")
 
os.dup2(new_stdout.fileno(), sys.stdout.fileno())
os.dup2(new_stderr.fileno(), sys.stderr.fileno())

Aha! Now this showed up in the output right before the crash:

music-applet: Unknown option '--oaf-activate-iid=OAFIID:GNOME_Music_Applet_Factory'.
music-applet: Use --help to get a list of available command line options.

That option is one of the things that Bonobo puts on the command line when it starts an applet, and isn’t one of the things I was giving to the applet when launching it manually. Aha? Checking the source code of the KDE libraries for that error message turned up this:

void
KCmdLineArgs::usage(const QString &error)
{
    assert(KGlobal::_locale);
    QCString localError = error.local8Bit();
    if (localError[error.length()-1] == '\n')
  localError = localError.left(error.length()-1);
    fprintf(stderr, "%s: %s\n", argv[0], localError.data());
 
    QString tmp = i18n("Use --help to get a list of available command line options.");
    localError = tmp.local8Bit();
    fprintf(stderr, "%s: %s\n", argv[0], localError.data());
    exit(254);
}

Aha! The KApplication constructor indirectly calls this when it sees something on the command line it doesn’t understand. It prints a message to stderr, and them immediately exits the program! The applet wasn’t crashing; the KDE libraries were terminating it because they didn’t understand the command-line arguments Bonobo uses!

Here’s where that brown paper bag comes in: this bug would affect anyone relying on Bonobo to start the applet. In other words, everyone who isn’t me. By always starting the applet from the command line first, I never tested the applet in the same runtime configuration that everyone else on the planet would be using, and so never encountered a bug that everyone else would suffer. The whole Python version thing was a red herring.

Now that I finally knew what was happening, the fix was trivial: strip out any command-line arguments before calling the KApplication constructor:

fake_argv = sys.argv[0:1]
self.__plugin._app = kdecore.KApplication (fake_argv, "music-applet")

However, I still maintain that if there is a hell, there is a special place in it reserved for people who write libraries that call exit(), and thus do not give the user of the library an opportunity to try to recover from an error.

Needless to say, this bug is fixed in 2.3.1.

Call for help: Music Applet 2.3.0 crasher

Several users have reported that Music Applet 2.3.0 crashes for them. Unfortunately, I have been unable to reproduce this bug myself, and none of the crash reports I’ve received have contained enough detail to suggest where the problem might lie, other than hinting that it only happens with Python 2.5. Without figuring out more information about the cause of the problem, I have no way to fix it.

If you’re experiencing crashes, you can help by trying to find out what’s causing the crash. Here’s some suggestions on how you might be able to do this:

  • Run the applet from the command line before adding it to the panel, so that error messages will be printed to the console. See the FAQ for details on how to do this.
  • If the applet dumps core, use gdb to get a backtrace, and send that backtrace to me. That should at least identify the line of code where the crash occurs.
  • Try disabling all plugins by default. To do this, before running ./configure, edit the file data/music-applet.schemas.in. Look for the line that says:

    <default>[Amarok,Audacious,Banshee,Exaile,MPD,Muine,Quod Libet,Rhythmbox,VLC,XMMS,XMMS2]</default>

    And replace it with:

    <default>[]</default>

    If the applet no longer crashes on startup, try re-enabling plugins one by one until you find one that causes the crash.

  • Let me know the versions of everything the applet is using: Python, GTK, GNOME, D-Bus (and the Python bindings for it), and the player-specific libraries used by the plugins that (possibly) cause the crash.
  • Let me know exactly what you were doing with the applet when the crash happened (i.e., adding it to the panel, launching a player, clicking one of the buttons, etc.).

Without more information from someone who’s able to reproduce the crash themselves, there’s unfortunately nothing I can do to fix this problem. Thanks in advance to anyone who can help.

Music Applet 2.3.0 Released

Music Applet 2.3.0 has been released. In addition to a bunch of bug fixes, this release adds support for Amarok, Audacious, and VLC (0.9.0 or later). It also adds support for getting album art from Rhythmbox (0.11.3 or later), and the frequently requested feature of displaying song information inside the applet itself. Check the release notes for the full details.

Call for polyglots

The next release of Music Applet is approaching, with the main task remaining before 2.3.0 ships being to updating the translations.

Here’s where you can help. According to intltool-update -r, the current status of the translations is:

ar: 73 translated messages, 33 fuzzy translations, 14 untranslated messages.
de: 8 translated messages, 23 fuzzy translations, 89 untranslated messages.
es: 8 translated messages, 23 fuzzy translations, 89 untranslated messages.
fr: 8 translated messages, 23 fuzzy translations, 89 untranslated messages.
it: 38 translated messages, 51 fuzzy translations, 31 untranslated messages.
ja: 8 translated messages, 23 fuzzy translations, 89 untranslated messages.
nb: 12 translated messages, 18 fuzzy translations, 90 untranslated messages.
nl: 106 translated messages, 14 fuzzy translations.
pl: 106 translated messages, 14 fuzzy translations.
pt_BR: 97 translated messages, 18 fuzzy translations, 5 untranslated messages.
sv: 106 translated messages, 14 fuzzy translations.

Clearly, some of the translations have been unmaintained for some time now, and trying to use them would actually be worse than not having them at all. Why? The “fuzzy” translations are ones where the string that had been translated doesn’t quite match what the program is using, so the “translation” that will be used might not actually be correct anymore. With some of these languages, the translated text is literally more likely to be wrong than right.

So, unless someone volunteers new translations for German (de), Spanish (es), French (fr), Italian (it), Japanese (ja), or Norwegian (nb), I’ll drop them entirely rather than include them in the 2.3.0 release. If you’re interested in contributing, you can download the translation files and get to work.

I’ll be contacting the translators who have contributed translations for Arabic (ar), Dutch (nl), Polish (pl), Portuguese/Brazil (pt_BR), and Swedish (sv) privately to let them know about the string changes since 2.2.1 and give them an opportunity to update their translations accordingly.

I’m planning on pushing out the 2.3.0 release sometime in about a week. If you’re interested in maintaining one of the old translations, or wish to contribute a new one, but need a little more time, let me know.

Thank you to all translators, past and present, who have contributed to Music Applet. I’m still amazed at how so many people have volunteered to contribute translations, even though this is the first time I’ve ever asked for any publicly.

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.

Music Applet 2.2.1 Released

Music Applet 2.2.1 has been released! As you might guess from the version number, this is primarily a bug-fix release. In particular, this release fixes a compatibility issue with GTK+ 2.12 that prevented the song information tooltip from actually having any song information in it. In addition to a few other, more minor, bug fixes, there’s also an updated Polish translation.