« Barb is published again | Main | Prayer study continues to blaze new ground »

Rough notes: Python and D-Bus

At work I had to figure out how to use D-Bus from Python. GNOME documentation generally sucks, so I'm posting the following notes in case they provide useful to someone (maybe me in six months).

D-Bus is a system-wide message bus used by the GNOME desktop. Python D-Bus bindings are available; in Ubuntu, they're in the python-dbus package.

There are two buses, one that's system-wide and one that's tied to your desktop session. The system-wide bus lets you talk to unique daemons, such as the Avahi server that I'm interested in, and the session bus talks to your current panel, window manager, or whatever.

To start off, you import the modules and get an object for the message bus you want:

import avahi, dbus, dbus.glib

bus = dbus.SystemBus()

Once you have the bus, you need to find an object to talk to; this is done by requesting a particular application and an object path that's a unique identifier within that application. (One application can contain many different objects; consider a window manager that has a number of windows open.) Applications are identified by Java package-style reversed domain names, like "org.freedesktop.Avahi". Avahi happens to contain constants for these identifiers, but you can also just specify them as literals:

raw_server = bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER)
#                        = ('org.freedesktop.Avahi', '/')

An object can support any number of different interfaces. Before calling any methods, you need to specify which interface you want to use. They're also identified by reversed domains:

server = dbus.Interface(raw_server, avahi.DBUS_INTERFACE_SERVER)
#                              = "org.freedesktop.Avahi.Server"

The Avahi developers don't actually document their API anywhere. You have to read some XML files that are in /usr/share/avahi/introspection that describe each interface to find out what methods are available. (Someone should write a man- or pydoc-style tool to format these descriptions.) For example, here's one method on Server:

  <method name="ResolveService">
      <arg name="interface" type="i" direction="in"/>
      <arg name="protocol" type="i" direction="in"/>
      <arg name="name" type="s" direction="in"/>
      <arg name="type" type="s" direction="in"/>
      <arg name="domain" type="s" direction="in"/>
      <arg name="aprotocol" type="i" direction="in"/>
      <arg name="flags" type="u" direction="in"/>

      <arg name="interface" type="i" direction="out"/>
      <arg name="protocol" type="i" direction="out"/>
      <arg name="name" type="s" direction="out"/>
      <arg name="type" type="s" direction="out"/>
      <arg name="domain" type="s" direction="out"/>
      <arg name="host" type="s" direction="out"/>
      <arg name="aprotocol" type="i" direction="out"/>
      <arg name="address" type="s" direction="out"/>
      <arg name="port" type="q" direction="out"/>
      <arg name="txt" type="aay" direction="out"/>
      <arg name="flags" type="u" direction="out"/>
 </method>

Annoyingly, the XML doesn't contain any comments describing the semantics of the methods, so you have to hope that the method names are understandable. I had to figure out legal values for some parameters by looking at Avahi example code. Calling the above ResolveService method looks like this:

output = server.ResolveService(interface, protocol, name, type,
                               domain, avahi.PROTO_UNSPEC, dbus.UInt32(0))
(interface2, protocol2, name2, type2, domain2, ...) = output

Interfaces can also support signals. The following example creates a new ServiceBrowser object

# Call method to create new browser, and get back an object path for it.
obj_path = server.ServiceBrowserNew(interface, protocol, '_durus._tcp',
                                    domain, dbus.UInt32(0))

# Create browser interface for the new object
browser = dbus.Interface(bus.get_object(avahi.DBUS_NAME, obj_path),
                   avahi.DBUS_INTERFACE_SERVICE_BROWSER)

# Connect signals for browser -- they'll be called as new ZeroConf
# services are seen.
browser.connect_to_signal('ItemNew', new)
browser.connect_to_signal('AllForNow', all_for_now)

Here's a small service written in Python:

import avahi, dbus, dbus.glib, dbus.service
import gobject

class Server (dbus.service.Object):
    @dbus.service.method(dbus_interface='ca.amk.Interface',
                         in_signature='', out_signature='si')
    def get_result (self):
        return ('hello', 42)


bus = dbus.SessionBus()
name = dbus.service.BusName('ca.amk.Server', bus=bus)
obj = Server(name, '/')
loop = gobject.MainLoop()
print 'Listening'
loop.run()

And here's the corresponding client:

import dbus, dbus.glib

bus = dbus.SessionBus()

server = dbus.Interface(bus.get_object('ca.amk.Server', '/'),
                        'ca.amk.Interface')
print server.get_result()

The example uses the session bus because an ordinary user can't run services on the system bus; you could spoof system daemons if that was allowed.

Performance on this trivial server isn't too bad: about 1000 calls/second on a 2.2GHz Linux machine. Returning more data slows things down; returning 5 values runs at 785 calls/sec.

See the slightly-outdated python-dbus tutorial for more on using the Python bindings. Here's someone else's rough notes.

About

This page contains a single entry from the blog posted on April 13, 2007 2:12 PM.

The previous post in this blog was Barb is published again.

The next post in this blog is Prayer study continues to blaze new ground.

Many more can be found on the main index page or by looking through the archives.

Powered by
Movable Type 3.31