Search code examples
pythonqtpyqt4dbusqtdbus

How does one expose methods and properties on DBus using a QDBusAbstractAdaptor with PyQt4?


I am trying to get some basic code running on DBus using PyQt4, specifically QtDBus. I am using a Python3 version of PyQt4. I have already gotten the code I want running on Qt (c++) but I want to get similar code running using only Python. I want to expose Methods, signals/slots and properties on DBus for other Python code to call.

In Qt you use the Q_CLASSINFO macro/function to do DBus introspection. While I have pulled in the Q_CLASSINFO method, i can't get it to produce the same type of functionality. As far as I can tell there is zero documentation on the Q_CLASSINFO method, so I'm not sure if there is another way. Using D-Feet i can clearly see that no methods are exposed automatically, so I'm kind of stuck.

Here is what I have so far.

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

SERVICE = 'com.home.dbus'

class MyServer(QObject):


    def __init__(self):
        QObject.__init__(self)
        self.__dbusAdaptor = ServerAdaptor(self)

    def close(self):
        pass

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

    def name(self):
        return 'myname'

    def dbus_adaptor(self):
        return self.__dbusAdaptor

class ServerAdaptor(QDBusAbstractAdaptor):
    """ This provides the DBus adaptor to the outside world"""

    def __init__(self, parent):
        super().__init__(parent)
        self.__parent = parent
        Q_CLASSINFO("D-Bus Introspection",
        "  <interface name=\"com.home.dbus\">\n"
        "    <method name=\"name\">\n"
        "      <arg direction=\"out\" type=\"s\" name=\"name\"/>\n"
        "    </method>\n"
        "    <method name=\"echo\">\n"
        "      <arg direction=\"in\" type=\"s\" name=\"phrase\"/>\n"
        "      <arg directory=\"out\" type=\"s\" name=\"echoed\"/>\n"
        "    </method>\n"
        "  </interface>\n")

    def close(self):
        parent.close()

    def echo(self, value):
        return parent.echo(value)

    def name(self):
        return parent.name

def start():
    app = QCoreApplication([])
    if QDBusConnection.sessionBus().isConnected() == False:
        print('Cannot connect to D-Bus session bus')
        return
    print('Starting')
    server = MyServer()
    if not QDBusConnection.sessionBus().registerService(SERVICE):
        print('Unable to register service name')
        return
    if not QDBusConnection.sessionBus().registerObject('/mydbus', server.dbus_adaptor):
        print('Unable to register object at service path')
        return
    app.exec();
    print('Exited')

if __name__ == '__main__':
    start()

While I really like the using QtDBus in C++ because of how I want to structure this large project of mine, I really need the object accessed via DBus to be written in Python3.


Solution

  • There are several problems with your program. I recommend having a look at the remotecontrolledcar and pingpong examples from the latest PyQt sources, they're pretty helpful. The main points to note are:

    • You should pass a MyServer instance (not ServerAdaptor) to registerObject()
    • Add pyqtSlot() decorators to the functions you wish to expose via D-Bus
    • Call Q_CLASSINFO() at the top of the adaptor class, not in its __init__() function
    • Also set the "D-Bus Interface" using Q_CLASSINFO()
    • Your introspection XML contained a typo ('directory' instead of 'direction')

    Here's a stripped-down example that works for me (Python 3.2.3/Qt 4.8.2/PyQt 4.9.4):

    from PyQt4 import QtDBus
    from PyQt4.QtCore import (QCoreApplication, QObject, Q_CLASSINFO, pyqtSlot,
                              pyqtProperty)
    from PyQt4.QtDBus import 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):
        """ This provides the DBus adaptor to the outside world"""
    
        Q_CLASSINFO("D-Bus Interface", "com.home.dbus")
        Q_CLASSINFO("D-Bus Introspection",
        '  <interface name="com.home.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'
        '  </interface>\n')
    
        def __init__(self, parent):
            super().__init__(parent)
    
        @pyqtSlot(str, result=str)
        def echo(self, phrase):
            return self.parent().echo(phrase)
    
        @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('/mydbus', server)
        bus.registerService('com.home.dbus')
        app.exec()
    
    if __name__ == '__main__':
        start()