Archive for September, 2010

The Magic of QtDBus, and the PropertyChanged signal

7th September 2010

I’ve had reason recently to delve into the inner workings of QtDBus.  It does more for programmers than I (and probably many of you) realised.

For example, when you tell qdbusxml2cpp to generate a proxy for you, it creates a subclass of QDBusAbstractInterface, and populates it with properties, signals and slots that correspond to the D-Bus properties, methods and slots.  But there’s more going on under the hood than it seems.  In particular, all those annotations for input and output types are used for more than just simple casting of QVariants.

Consider, for example, what happens when you fetch the value of a property of D-Bus type a{sv}.  This is a map from strings to variants.  The natural representation in Qt is a QVariantMap, but it could just as easily be in a QHash<QString, QVariant>, or even something based on std::map.  Qt doesn’t know.  So you can put an annotation in your D-Bus interface specifications, indicating what type it should be represented as:

<annotation name="com.trolltech.QtDBus.QtTypeName" value="QVariantMap"/>

OK, so what does qdbusxml2cpp do with this information?  Well, in this case, we’re annotating a property.  qdbusxml2cpp will produce the following code in the proxy class:

Q_PROPERTY(QVariantMap Foo READ foo)
inline QVariantMap foo() const
{ return qvariant_cast< QVariantMap >(property("Foo")); }

As you can see, it casts a property (which is a QVariant) to a QVariantMap.  But that surely demands that the QVariant already contains a QVariantMap and not, say, a QHash<QString,QVariant>?  Well, there’s some magic in QDBusAbstractInterface to deal with this for you.

Basically, when you request a property (QObject::property()), QDBusAbstractInterface requests it over D-Bus.  If it’s something simple, like a string (D-Bus type s), it gets demarshalled automagically in the internals of QtDBus, giving you a QString in this example.  But if it’s something more complex, like the above a{sv} type, QDBusAbstractInterface looks up the type information in Qt’s meta-object system, and uses that to demarshall it into the correct type – if the property type is QVariantMap, it will be demarshalled into a QVariantMap.

A similar trick is done for signals (note that if you get the signal signature wrong, you will simply never receive the signal).  Sidenote: if you add a QDBusMessage parameter as the last parameter of the slot you connect to a D-Bus signal, you’ll get a copy of the signal message.  You can even just have the QDBusMessage parameter, although you’ll be creating more work for yourself.

So, this is all very nice, but why am I telling you about stuff that “just works”?  Well, you have to watch out for the times when it doesn’t “just work”.  I’ll illustrate just such a case with an implementation of the PropertyChanged signal.

The PropertiesChanged signal is a recent addition to the org.freedesktop.DBus.Properties interface in the D-Bus specification that allows you to receive notifications when a property value changes.  It’s rather new and hasn’t even made it into the released version of the spec yet.  However, it will be supported by the next version of GIO.  Hopefully, it will be supported in a not-too-distant release of Qt (4.8?), but in the meantime you can quite easily add support yourself.

The signal looks like

org.freedesktop.DBus.Properties.PropertiesChanged (
    STRING interface_name,
    DICT<STRING,VARIANT> changed_properties,
    ARRAY<STRING> invalidated_properties);

where changed_properties is a dictionary containing the changed properties with the new values and invalidated_properties is an array of properties that changed but the value is not conveyed.

Now, on the server side, you can’t support it “properly”, because the introspection for the org.freedesktop.DBus.Properties interface is fixed by QtDBus, but you can emit the signal yourself anyway:

QDBusMessage signal = QDBusMessage::createSignal(
signal << "";

QVariantMap changedProps;
changedProps.insert("somestringprop", "newstringvalue");
changedProps.insert("someintprop", 4);
changedProps.insert("somemapprop", newmapvalue);
signal << changedProps;

QStringList invalidatedProps;
invalidatedProps << "invprop1" << "invprop2";
signal << invalidatedProps;


Easy as pie, right?  You can even have a helper method that does this:

void notifyPropertyChanged( const QString& interface,
                            const QString& propertyName )
    QDBusMessage signal = QDBusMessage::createSignal(
    signal << interface;
    QVariantMap changedProps;
    changedProps.insert(propertyName, property(propertyName));
    signal << changedProps;
    signal << QStringList();

On the client side, things are slightly more complex.  My approach is to insert a class into the proxy code heirachy.  While qdbusxml2cpp produces subclasses of QDBusAbstractInterface, I modify these to inherit a class of my own (DBusAbstractInterface) that in turn inherits QDBusAbstractInterface.  This class has all the magic needed to deal with the PropertiesChanged signal.

Essentially, it connects to the PropertiesChanged signal using QDBusConnection::connect(), and forwards this to any listeners.  So far, so simple.  But what happens if we have a property with D-Bus type a{si} (a map from strings to integers)?  Then the changed_properties map will contain an entry mapping the property name to an a{si} value.  How does QtDBus know how to demarshall this?

Well, the fact is, it doesn’t.  QtDBus will demarshall the changed_properties map into a QVariantMap (or whatever appropriate type the second argument of the slot has), but it can’t demarshall any complex types that are values in that map.  Do you want your a{si} map as a QMap or a QHash?  QtDBus doesn’t know, and so puts off the demarshalling by inserting a QDBusArgument value into the map.

If you know what structure you’re expecting here, you can just demarshall it with something like

QMap<QString,int> map;
changed_properties.value("foo").value<QDBusArgument>() >> map;

My helper class does something roughly similar to what QtDBus does internally, and uses the meta type system to figure out how it should demarshall the values in changed_properties.  But just remember that if you have a complex structure passed over D-Bus as a variant (D-Bus type v) and received in your code as a QVariant, the chances are it’ll be in the form of a QDBusArgument, and you’ll have to demarshall it yourself.