How do I extract the returned data from QDBusMessage in a Qt DBus call?

MatthewD picture MatthewD · Nov 26, 2013 · Viewed 7.6k times · Source

I'm trying to call WPA supplicant's DBus interface using Qt's QDBus class library. In particular, I'm trying to use the "Get" property call to retrieve the "Interfaces" property value.

The DBus specification (via introspection) for "Get" is:

<interface name="org.freedesktop.DBus.Properties">
    <method name="Get">
        <arg name="interface" type="s" direction="in"/>
        <arg name="propname" type="s" direction="in"/>
        <arg name="value" type="v" direction="out"/>
    </method>
    ...
</interface>

Seems simple enough. Two strings input and the output is a variant (these are DBus types). For the "Interfaces" property I'm expecting the variant to be an array of object paths (DBus type "ao").

I'm using QDBusInterface::call() to call the DBus method, which returns a QDBusMessage, but I can't figure out how to extract my data from this.

QDBusMessage::arguments() returns a QList<QVariant>. I've tried various conversions of the items in this list in an attempt to find my array of object paths, but I just seem to end up with an empty string instead.

QVariant::type() seems like it should help, but it only seems to return the type QDBusMessage, which is clearly wrong. For example:

// 'message' is of type QDBusMessage
qDebug() << "Argument 0 type is" << message.arguments().at(0).type();

prints:

Argument 0 type is QVariant::QDBusMessage

How do I extract the actual message data?

Answer

MatthewD picture MatthewD · Nov 26, 2013

The easiest way I've found is to use qDebug() to print results as you go. This usually points you to which type you need to convert to next, until you finally reach the innermost type.

Qdbusviewer is a useful tool for determining the DBus parameters that will be required. In this case:

  • WPAS Service: "fi.w1.wpa_supplicant1"
  • WPAS Path: "/fi/w1/wpa_supplicant1"
  • Properties interface identifier: "org.freedesktop.DBus.Properties"
  • WPAS interface identifier: "fi.w1.wpa_supplicant1"

In initialising the QDBusInterface for calling Get, we need to use the Properties interface, since that's the interface that provides the Get method.

In calling Get using the QDBusInterface::call() method, the second and third parameters correspond to the parameters listed in the Introspection output ("interface" and "propname"). "interface" is where the property can be found, which for the "Interfaces" property is "fi.w1.wpa_supplicant1" (this can be confirmed using qdbusviewer).

The "propname" parameter is just the name of the property: "Interfaces" in this case.

The code so far:

std::string getInterface()
{
    QDBusInterface interface( "fi.w1.wpa_supplicant1",
                              "/fi/w1/wpa_supplicant1",
                              "org.freedesktop.DBus.Properties",
                              QDBusConnection::systemBus() );

    // Calls DBus method
    QDBusMessage result = interface.call( "Get",
                                          "fi.w1.wpa_supplicant1",
                                          "Interfaces" );

This is the hard part. QDBusInterface::call() returns a QDBusMessage, which has our property information trapped within.

    qDebug() << result;

This debug statement prints:

QDBusMessage(type=MethodReturn, service=":1.2431", signature="v", contents=([Variant: [ObjectPath: /fi/w1/wpa_supplicant1/Interfaces/7/Networks/0]]) )

Looks good. The "ObjectPath" is what we're after, and it's definitely in there somewhere.

Next we need QDBusMessage::arguments(), which "Returns the list of arguments that are going to be sent or were received from D-Bus." It returns a QList<QVariant>.

    QList<QVariant> outArgs = result.arguments();
    qDebug() << outArgs;

The debug statement prints:

(QVariant(QDBusVariant, ) )

This 'notation' is a bit unclear (do brackets mean lists?), but we'll keep going.

    QVariant first = outArgs.at(0);
    qDebug() << first;

prints:

QVariant(QDBusVariant, )

So the outer brackets do seem to indicate an array, though why there is a comma used in the inner set and not in the outer set is a bit of a mystery.

We keep converting types as we come across them:

    QDBusVariant dbvFirst = first.value<QDBusVariant>();
    //qDebug() << dbvFirst; // compile error!

qDebug() doesn't understand QDBusVariant, so no debug print is available here. Instead if we look at the documentation for QDBusVariant, we see that it provides a variant() method for converting to a regular QVariant type.

    QVariant vFirst = dbvFirst.variant();
    qDebug() << vFirst;

We do seem to be going in circles, but the print output is a bit different this time:

QVariant(QDBusArgument, )

Another conversion:

    QDBusArgument dbusArgs = vFirst.value<QDBusArgument>();

Unfortuately, qDebug() doesn't work here either. The QDBusArgument type can hold a number of different element types, which are described in the Qt documentation. QDBusArgument::currentType() tells you which type you have. In our case:

    qDebug() << "QDBusArgument current type is" << dbusArgs.currentType();

prints:

QDBusArgument current type is 2

2 means ArrayType.

According to the QDBusArgument documentation, we can extract the elements of the array using code like the following:

    QDBusObjectPath path;
    dbusArgs.beginArray();
    while (!dbusArgs.atEnd())
    {
        dbusArgs >> path;
        // append path to a vector here if you want to keep it
    }
    dbusArgs.endArray();

I've assumed the array element type is QDBusObjectPath, since at this point it makes sense for it to be so. It'll be clear if I'm right.

If you get the error message QDBusArgument: write from a read-only object, change the declaration of dbusArgs to:

    const QDBusArgument &dbusArgs = vFirst.value<QDBusArgument>();

qDebug() doesn't support QDBusObjectPath either, but QDBusObjectPath::path() returns a QString, so we can get our debug print like this:

    qDebug() << path.path();

prints:

"/fi/w1/wpa_supplicant1/Interfaces/7"

At last!