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?
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:
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!