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.