Thursday, 3 October 2013

Including Dbus in a Python QT4 application

Dbus isn't the most complicated beast in software, but it's notable.

There are a list of things I *don't* like about dbus.

- The C/C++ code is well documented, but good tutorials are absent
- The Python code is poorly documented if it exists, but there are some great tutorials to look off of
- Python Dbus + Qt or GTK requires that the process is forked or put into a daemon in order to allow connections. (Cannot initiate in a GUI application without detachment)

I just spent an hour figuring out that last one. I'm big on documentation, both in my work and when using other people's code. If it isn't there, the difficulties in developing with the software is exponentially greater and my time is wasted.

That said, dbus is amazing when it works and I love that KDE and QT/Maemo have taken great lengths to adopt it. Even the KDEOnWindows project uses dbus, on Windows!



I am trying to create a dbus interface so that my GUI application can talk to different portions of itself without messy imports and passing of variables. I will have a dbus/ folder that contains one module with one class that inherits dbus services and imports respective modules. Each method will pass the call and any arguments it includes to the function, which in turn passes it to the UI.

Why have dbus classes and modules in one folder? So I can iterate through the folder and add every dbus service without having to manually type it all in; automate it! (It's inspired by the debian and gentoo directory.d/ style of configuration management. I plan to extend this to a plugin system at some point.) Make sure all dbus files are referenced before the mainloop but after application is started.

It seems that walking through a directory and dynamically loading dbus objects still causes dbus to hang. My guess is that every dbus.service object needs to be placed into its own mainloop and importing a module with dbus methods doesn't properly place it into the mainloop.

Sorry for the mess. Dbus services must be added after the QApplication or QCoreApplication is initialized. I was adding them before, then getting this message in the terminal:

QSocketNotifier: Can only be used with threads started with QThread

Create references to dbus objects after application initialization!

For a real-world example, I will launch a dbus service that contains a method for creating a new project. I will name it NewProject and it will have an argument of a name. We will not worry about uniqueness or purpose for now.

------------------------------------------------------------

Import Code:

import dbus
import dbus.service
from dbus.mainloop.qt import DBusQtMainLoop

If this fails, you need to install dbus dependencies. This could be python3-dbus.mainloop.qt, python3-dbus/python-dbus, or python3-pyqt4. You will need to check with your distribution for the proper package names. A search for "dbus" is usually adequate.

Define an Interface Name:

This should reflect your application or purpose. I usually store it as a global variable.

INTERFACE = 'org.qtcide'

Write your class with the dbus methods
class DbusTest(dbus.service.Object):

    def __init__(self):
        busName = dbus.service.BusName(INTERFACE, bus = dbus.SessionBus())
        dbus.service.Object.__init__(self, busName, '/org/qtcide')
        

    @dbus.service.method(INTERFACE,
                        in_signature = 's', out_signature = 's')
    def NewProject(self, name):
        return name

Initialize application and mainloop:
DBusQtMainLoop(set_as_default = True)    
app = QtGui.QApplication(sys.argv)

Keep it looping:
sys.exit(app.exec_())


And you should have a dbus process running. Currently the NewProject method only returns the name you give it, but adding an import and calling a method with that Name is simple, and I shall explain how in the next post.

Forking may have some strange consequences on Windows, as true forking is resource intensive and not native.


For those of you interested, you can find my project I'm basing this tutorial on here: https://github.com/NucleaPeon/QTCIDE

1 comment:

  1. Thank you! figuring out that Dbus services must be added after the QApplication was the key that solved everything for me!

    ReplyDelete