Tuesday 19 November 2013

Qt, MPRIS2 and Clementine


I have been using Clementine in a daily basis for some months now and I finally feel that I have a solid music player for my Ubuntu box. Clementine is a Qt-based media player that is inspired by Amarok 1.4 and the maintainers have done a great job supporting and extending this project while building a solid and dedicated community about it (follow on Facebook, on Twitter, on Last.fm).

Lately I noticed a little quirk (~where quirk is a low importance abnormality for usual people - but not for my state of mind :P) in Clementine's interaction with the Sound Menu widget in Ubuntu's taskbar (Issue 3962). The quirk was that the open playlists in Clementine weren't reflected at the same time in this widget. This initially led to Arnaud fixing a crash when adding a new playlist, re-opening Clementine, removing the playlist from Clementine and selecting it from Ubuntu's sound menu. But my initial obsession still hadn't been satisfied, so I kept looking for a way to trigger the rebinding of the Sound Menu with the playlist collection, upon each playlist change. I learned several things over the last weekend that I'd like to share but I was also reminded that a lot of IPC is text-based at the user-level and progress is a matter of text-based specifications that won't fully express the semantics of underlying processes.

 

Playing with D-Bus


The Sound Menu communicates with Clementine via the MPRIS2 D-Bus interface specification (mpris2 is the latest specification for communication with media players). Regarding the message-bus for the graphical system of Ubuntu, I knew nothing so I experimented a little bit. However, I could not find a way to refresh the Sound Menu. How was it populated at the initialization phase and why we couldn't find a way to re-populate it on demand? The specification isn't clear about this if you study it in detail. A certain bug reported back in 2011, however shed light to our case:
Sound Menu should re-read playlists from MPRIS apps when PropertiesChanged is posted
the description of which was:
At the moment, the sound menu only ever calls GetPlaylists for an MPRIS app once, immediately after it appears on the bus. If an app's playlists change, there's no way for the app to notify the sound menu that the playlists should be re-read.
As you see from the fix, from there on you could get an update by posting an arbitrary PropertiesChanged to the Sound Menu in order for it to trigger calling the GetPlaylists again. Below, you can see the output of dbus-monitor that captured the traffic of the GetPlaylists method invocation (from the sound panel to Clementine).
method call sender=:1.45 -> dest=org.mpris.MediaPlayer2.clementine serial=425 path=/org/mpris/MediaPlayer2; interface=org.mpris.MediaPlayer2.Playlists; member=GetPlaylists
   uint32 0
   uint32 100
   string "Alphabetical"
   boolean false
method return sender=:1.121 -> dest=:1.45 reply_serial=425
   array [
      struct {
         object path "/org/mpris/MediaPlayer2/Playlists/29"
         string "Playlist 29"
         string ""
      }
      struct {
         object path "/org/mpris/MediaPlayer2/Playlists/30"
         string "Playlist 30"
         string ""
      }
   ]
In the example above the sender that sent/invoked the GetPlaylists messages/command is the com.canonical.indicator.sound service. This command sent four arguments each for every parameter and the response was an array that contained two playlists. Unfortunately, yet another quirk came to light (to us) which I believe is related with the "sound menu caches playlists which causes issues" bug that was fixed (or reported fixed) recently. Here are tools that guided me through the process of experimenting with d-bus.


Additionally, several interesting utilities exist like qdbus (for Qt based applications), mdbus2 (for general introspection) and dbus-send for complete control on sending commands over d-bus. Of course there are visual tools also (like Qt's D-Bus Viewer-qdbusviewer), that will definitely assist you.

Qt

 

I didn't have any previous experience with Qt but a lot from Windows Forms, ASP.net and Silverlight instead to have enough intuition about the general abstraction. I also have some experience from MFC C++ that was enough to catch up easily with the low-level-ness of Qt.

The thing I found interesting about Qt at first glance was the mechanism that it employs to implement signalling between objects. Qt employs a loosely coupled design separating the concerns of the binding process between slots and signals diverging by the callback-based design. In essence signals are emitted when something happens and slots are potential handlers. The gluing code (which signal is going to be handled-or delivered-by what slot) is realized by a connect function. The documentation mentions:
You can connect as many signals as you want to a single slot, and a signal can be connected to as many slots as you need. It is even possible to connect a signal directly to another signal. 
What is of great interest (for the C++ realm) is that the connect functions are promoted with a programming model that is considered type-safe (or just more safe that a callback based design).

Slot and signals can be wired like below:
connect(playlist_manager,       
        SIGNAL(SelectionChanged(QItemSelection&),
        SLOT(SelectionChanged(QItemSelection&));
Note that SIGNAL and SLOT are macro functions that support the static check of whether types match (and certain rules, e.g. about the number of parameters between signal and slots). How are these macros defined?
#define Q_SLOTS 
#define Q_SIGNALS protected 
#define SLOT(a) "1"#a 
#define SIGNAL(a) "2"#a
From the definitions above you understand that something else happens from what you have initially thought. Qt uses the mechanism of a Meta-Object Compiler (moc). Why does Qt use moc for signals and slots? This article provides a reasonable argument that by this way they keep syntax easy to read, generated code is compiled by a standard C++ compiler and performance isn't that of a big compromise for Qt to have used a template metaprogramming solution, instead of moc. You can find more info about the usage of moc at the Using the Meta-Object Compiler (moc) article.

No comments:

Post a Comment