[PyQt] How to expose function accepting array of struct to D-Bus?

Evade Flow evadeflow at gmail.com
Tue Aug 7 20:45:43 BST 2012


> In theory you should be able to use QDBusArgument but I've not tested
> it - I've always struggled to find test cases.

Maybe the little server app appended below will help. Using dbus-send, I
can exercise the 'name' property and 'echo' method, as well as the
introspection interface:

  # Call 'echo' method
  dbus-send --print-reply --dest=com.example.dbus /com/example/dbus \
      com.example.dbus.echo string:Hello

  # Exercise introspection interface
  dbus-send --print-reply --dest=com.example.dbus /com/example/dbus \
      org.freedesktop.DBus.Introspectable.Introspect

  # Get the 'name' property
  dbus-send --print-reply --dest=com.example.dbus /com/example/dbus \
      org.freedesktop.DBus.Properties.Get string:com.example.dbus string:name

  # Set 'name'
  dbus-send --print-reply --dest=com.example.dbus /com/example/dbus \
      org.freedesktop.DBus.Properties.Set string:com.example.dbus \
      string:name variant:string:MyNewName

  # Call 'setPosition' method
  dbus-send --print-reply --dest=com.example.dbus /com/example/dbus \
      com.example.dbus.setPosition double:1.0 double:2.0 double:3.0

Unfortunately for me, attempts to call setPosition() result in:

  Error org.freedesktop.DBus.Error.UnknownMethod: No such method
  'setPosition' in interface 'com.example.dbus' at object path
  '/com/example/dbus' (signature 'ddd')

I suppose this makes sense, but I was hoping there might be a
workaround. It would be really great to be able to write a PyQt-based
implementation of *any* D-Bus specification. That way, I could mock out
any arbitrary server's functionality for testing.

For now, it looks like structs aren't supported?  I'd be willing to help
add that support if it seems doable, but I'm not sure where to start, or
what the effort level might be. (I can probably spend about 20 hours on
it without getting into too much trouble...)

----------

from PyQt4 import QtDBus
from PyQt4.QtCore import (QCoreApplication, QObject, Q_CLASSINFO, pyqtSlot,
                          pyqtProperty)
from PyQt4.QtDBus import QDBusArgument, QDBusConnection, QDBusAbstractAdaptor

class MyServer(QObject):

    def __init__(self):
        QObject.__init__(self)
        self.__dbusAdaptor = ServerAdaptor(self)
        self.__name = 'myname'

    def echo(self, value):
        return'Received: {0}'.format(value)

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self, value):
        self.__name = value


class ServerAdaptor(QDBusAbstractAdaptor):
    Q_CLASSINFO("D-Bus Interface", "com.example.dbus")
    Q_CLASSINFO("D-Bus Introspection",
    '  <interface name="com.example.dbus">\n'
    '    <property name="name" type="s" access="readwrite"/>\n'
    '    <method name="echo">\n'
    '      <arg direction="in" type="s" name="phrase"/>\n'
    '      <arg direction="out" type="s" name="echoed"/>\n'
    '    </method>\n'
    '    <method name="setPosition">\n'
    '      <arg direction="in" type="(ddd)" name="pos"/>\n'
    '    </method>\n'
    '  </interface>\n')

    def __init__(self, parent):
        super().__init__(parent)

    @pyqtSlot(str, result=str)
    def echo(self, phrase):
        return self.parent().echo(phrase)

    @pyqtSlot(QDBusArgument)
    def setPosition(self, pos):
        print("How can I call this function?")


    @pyqtProperty(str)
    def name(self):
        return self.parent().name

    @name.setter
    def name(self, value):
        self.parent().name = value

def start():
    app = QCoreApplication([])
    bus = QDBusConnection.sessionBus()
    server = MyServer()
    bus.registerObject('/com/example/dbus', server)
    bus.registerService('com.example.dbus')
    app.exec()

if __name__ == '__main__':
    start()

On Mon, Aug 6, 2012 at 5:23 PM, Phil Thompson
<phil at riverbankcomputing.com> wrote:
> On Mon, 6 Aug 2012 13:59:08 -0400, Evade Flow <evadeflow at gmail.com> wrote:
>> On Mon, Aug 6, 2012 at 1:03 PM, Phil Thompson
>> <phil at riverbankcomputing.com> wrote:
>>> On Mon, 6 Aug 2012 12:49:21 -0400, Evade Flow <evadeflow at gmail.com>
>>> wrote:
>>>> On Mon, Aug 6, 2012 at 12:29 PM, Phil Thompson
>>>> <phil at riverbankcomputing.com> wrote:
>>>>> On Mon, 6 Aug 2012 12:15:44 -0400, Evade Flow <evadeflow at gmail.com>
>>>>> wrote:
>>>>>> I'm trying to write a PyQt4-based mock object for a C++ app exposed
>>> over
>>>>>> D-Bus with the following interface:
>>>>>>
>>>>>>     Q_CLASSINFO("D-Bus Interface", "com.acme.Audio.Control")
>>>>>>     Q_CLASSINFO("D-Bus Introspection",
>>>>>>     '  <interface name="com.acme.Audio.Control">\n'
>>>>>>     '    <method name="echo">\n'
>>>>>>     '      <arg direction="in" type="s" name="phrase"/>\n'
>>>>>>     '      <arg direction="out" type="s" name="echoed"/>\n'
>>>>>>     '    </method>\n'
>>>>>>     '    <method name="setParams">\n'
>>>>>>     '      <arg direction="out" type="(i)" name="error"/>\n'
>>>>>>     '      <arg direction="in" type="a(iiiii)"
>>>>>>     name="audioSourceParameter"/>\n'
>>>>>>     '      <annotation value="QVector<AudioSourceParameters>"
>>>>>> name="com.trolltech.QtDBus.QtTypeName.In0"/>\n'
>>>>>>     '      <annotation value="Errors::ErrorCode"
>>>>>> name="com.trolltech.QtDBus.QtTypeName.Out0"/>\n'
>>>>>>     '    </method>\n'
>>>>>>     '  </interface>\n')
>>>>>>
>>>>>> It's unclear to me how how the setParams() function should be
>>> decorated.
>>>>>> For the echo() function, I have:
>>>>>>
>>>>>>     @pyqtSlot(str, result=str)
>>>>>>     def echo(self, phrase):
>>>>>>         return self.parent().echo(phrase)
>>>>>>
>>>>>> But what should I put for setParams()? I was tempted to write:
>>>>>>
>>>>>>     @pyqtSlot('a(iiiii)', result='(i)')
>>>>>>     def setParams(self, volume):
>>>>>>         return self.parent().echo(phrase)
>>>>>>
>>>>>> But this results in:
>>>>>>
>>>>>> TypeError: C++ type 'a(iiiii)' is not supported as a pyqtSlot type
>>>>>> argument type
>>>>>>
>>>>>> So... how does one expose a function to D-Bus that accepts an array
> of
>>>>>> structs, each containing 5 ints?
>>>>>
>>>>> What would be the C++ signature? Try that as a string.
>>>>>
>>>>> Phil
>>>>
>>>> The C++ signature is:
>>>>
>>>>   void setParams(QList<AudioSourceParameters> audioSourceParameters,
>>>>                  Errors::ErrorCode &error);
>>>>
>>>> where AudioSourceParameters is defined as:
>>>>
>>>>   struct AudioSourceParameters
>>>>   {
>>>>       int volume;
>>>>       int balance;
>>>>       int fader;
>>>>       int fadein;
>>>>       int fadeout;
>>>>   };
>>>>
>>>> The C++ code sets things up with:
>>>>
>>>>   Q_DECLARE_METATYPE(QList<AudioSourceParameters>);
>>>>
>>>> and:
>>>>
>>>>    qDBusRegisterMetaType<AudioSourceParameters>();
>>>>    qDBusRegisterMetaType<QList<AudioSourceParameters> >();
>>>>
>>>> The introspection XML (presumably output by qdbuscpp2xml) shows this
> as
>>>> 'a(iiiii)'. That's what the client app (the one I'm trying to provide
> a
>>>> mock server for) is expecting to see.
>>>>
>>>> Any ideas how to make this work with PyQt?
>>>
>>> You probably can't using the QtDBus module because PyQt doesn't know
>>> anything about AudioSourceParameters.
>>>
>>> You should be able to use the standard Python dbus module which will
>>> bypass the Qt conversions.
>>>
>>> Phil
>>
>> What clients expect is 'a(iiiii)', so the convenience type declared in
>> the C++ server I'm trying to mock out shouldn't matter for my purposes
>> (should it?)
>>
>> It makes sense that PyQt wouldn't know anything about
>> 'AudioSourceParameters', but can it handle 'a(iiiii)'? Or are D-Bus
>> arrays and structs (and arrays-of-structs) not supported?
>>
>> I've seen a few PyQt code examples that use QDBusArgument to marshal
>> arbitrary arguments, but I haven't seen any examples where complex
>> objects are demarshalled.
>>
>> This seems fine:
>>
>>   @pyqtSlot('QList<int>')
>>   def func(self, args):
>>       pass
>
> Because QList<int> is explicitly supported by PyQt.
>
>> but this:
>>
>>     @pyqtSlot('QList<QList<int> >')
>>       def func(self, args):
>>         pass
>>
>> results in:
>>
>>   File "fake_hifi_audio.py", line 44, in HifiAudioServerAdaptor
>>     @pyqtSlot('QList<QList<int> >')
>> TypeError: C++ type 'QList<QList<int> >' is not supported as a
>> pyqtSlot type argument type
>
> Because QList<QList<int> > isn't supported.
>
>> If arrays of structs aren't supported, is there some way I can get the
>> thing that clients send as 'a(iiiii)' into a QDBusArgument variable and
>> demarshal it manually? PyQt's DBus bindings are *so* much better than
>> the standard python dbus module that I shudder at the thought of having
>> to go back to it... `:-}
>
> In theory you should be able to use QDBusArgument but I've not tested it -
> I've always struggled to find test cases.
>
> Phil


More information about the PyQt mailing list