Skip to content

Latest commit

 

History

History
681 lines (505 loc) · 26.5 KB

dbus_tutorial.txt

File metadata and controls

681 lines (505 loc) · 26.5 KB

dbus-python tutorial

Author: Simon McVittie, Collabora Ltd.
Date: 2006-06-14

This tutorial requires Python 2.4 or up, and dbus-python 0.80rc4 or up.

Applications that use D-Bus typically connect to a bus daemon, which forwards messages between the applications. To use D-Bus, you need to create a Bus object representing the connection to the bus daemon.

There are generally two bus daemons you may be interested in. Each user login session should have a session bus, which is local to that session. It's used to communicate between desktop applications. Connect to the session bus by creating a SessionBus object:

import dbus

session_bus = dbus.SessionBus()

The system bus is global and usually started during boot; it's used to communicate with system services like udev, NetworkManager, and the Hardware Abstraction Layer daemon (hald). To connect to the system bus, create a SystemBus object:

import dbus

system_bus = dbus.SystemBus()

Of course, you can connect to both in the same application.

For special purposes, you might use a non-default Bus, or a connection which isn't a Bus at all, using some new API added in dbus-python 0.81.0. This is not described here, and will at some stage be the subject of a separate tutorial.

D-Bus applications can export objects for other applications' use. To start working with an object in another application, you need to know:

  • The bus name. This identifies which application you want to communicate with. You'll usually identify applications by a well-known name, which is a dot-separated string starting with a reversed domain name, such as org.freedesktop.NetworkManager or com.example.WordProcessor.

  • The object path. Applications can export many objects - for instance, example.com's word processor might provide an object representing the word processor application itself and an object for each document window opened, or it might also provide an object for each paragraph within a document.

    To identify which one you want to interact with, you use an object path, a slash-separated string resembling a filename. For instance, example.com's word processor might provide an object at / representing the word processor itself, and objects at /documents/123 and /documents/345 representing opened document windows.

As you'd expect, one of the main things you can do with remote objects is to call their methods. As in Python, methods may have parameters, and they may return one or more values.

To interact with a remote object, you use a proxy object. This is a Python object which acts as a proxy or "stand-in" for the remote object - when you call a method on a proxy object, this causes dbus-python to make a method call on the remote object, passing back any return values from the remote object's method as the return values of the proxy method call.

To obtain a proxy object, call the get_object method on the Bus. For example, NetworkManager has the well-known name org.freedesktop.NetworkManager and exports an object whose object path is /org/freedesktop/NetworkManager, plus an object per network interface at object paths like /org/freedesktop/NetworkManager/Devices/eth0. You can get a proxy for the object representing eth0 like this:

import dbus
bus = dbus.SystemBus()
proxy = bus.get_object('org.freedesktop.NetworkManager',
                       '/org/freedesktop/NetworkManager/Devices/eth0')
# proxy is a dbus.proxies.ProxyObject

D-Bus uses interfaces to provide a namespacing mechanism for methods. An interface is a group of related methods and signals (more on signals later), identified by a name which is a series of dot-separated components starting with a reversed domain name. For instance, each NetworkManager object representing a network interface implements the interface org.freedesktop.NetworkManager.Devices, which has methods like getProperties.

To call a method, call the method of the same name on the proxy object, passing in the interface name via the dbus_interface keyword argument:

import dbus
bus = dbus.SystemBus()
eth0 = bus.get_object('org.freedesktop.NetworkManager',
                      '/org/freedesktop/NetworkManager/Devices/eth0')
props = eth0.getProperties(dbus_interface='org.freedesktop.NetworkManager.Devices')
# props is a tuple of properties, the first of which is the object path

As a short cut, if you're going to be calling many methods with the same interface, you can construct a dbus.Interface object and call methods on that, without needing to specify the interface again:

import dbus
bus = dbus.SystemBus()
eth0 = bus.get_object('org.freedesktop.NetworkManager',
                      '/org/freedesktop/NetworkManager/Devices/eth0')
eth0_dev_iface = dbus.Interface(eth0,
    dbus_interface='org.freedesktop.NetworkManager.Devices')
props = eth0_dev_iface.getProperties()
# props is the same as before

See the example in examples/example-client.py. Before running it, you'll need to run examples/example-service.py in the background or in another shell.

Unlike Python, D-Bus is statically typed - each method has a certain signature representing the types of its arguments, and will not accept arguments of other types.

D-Bus has an introspection mechanism, which dbus-python tries to use to discover the correct argument types. If this succeeds, Python types are converted into the right D-Bus data types automatically, if possible; TypeError is raised if the type is inappropriate.

If the introspection mechanism fails (or the argument's type is variant - see below), you have to provide arguments of the correct type. dbus-python provides Python types corresponding to the D-Bus data types, and a few native Python types are also converted to D-Bus data types automatically. If you use a type which isn't among these, a TypeError will be raised telling you that dbus-python was unable to guess the D-Bus signature.

The following basic data types are supported.

Python type converted to D-Bus type notes
D-Bus proxy object ObjectPath (signature 'o') (+)
dbus.Interface ObjectPath (signature 'o') (+)
dbus.service.Object ObjectPath (signature 'o') (+)
dbus.Boolean Boolean (signature 'b') a subclass of int
dbus.Byte byte (signature 'y') a subclass of int
dbus.Int16 16-bit signed integer ('n') a subclass of int
dbus.Int32 32-bit signed integer ('i') a subclass of int
dbus.Int64 64-bit signed integer ('x') (*)
dbus.UInt16 16-bit unsigned integer ('q') a subclass of int
dbus.UInt32 32-bit unsigned integer ('u') (*)_
dbus.UInt64 64-bit unsigned integer ('t') (*)_
dbus.Double double-precision float ('d') a subclass of float
dbus.ObjectPath object path ('o') a subclass of str
dbus.Signature signature ('g') a subclass of str
dbus.String string ('s') a subclass of unicode
dbus.UTF8String string ('s') a subclass of str
bool Boolean ('b')  
int or subclass 32-bit signed integer ('i')  
long or subclass 64-bit signed integer ('x')  
float or subclass double-precision float ('d')  
str or subclass string ('s') must be valid UTF-8
unicode or subclass string ('s')  

Types marked (*) may be a subclass of either int or long, depending on platform.

(+): D-Bus proxy objects, exported D-Bus service objects and anything else with the special attribute __dbus_object_path__, which must be a string, are converted to their object-path. This might be useful if you're writing an object-oriented API using dbus-python.

If introspection succeeded, dbus-python will also accept:

  • for Boolean parameters, any object (converted as if via int(bool(...)))
  • for byte parameters, a single-character string (converted as if via ord())
  • for byte and integer parameters, any integer (must be in the correct range)
  • for object-path and signature parameters, any str or unicode subclass (the value must follow the appropriate syntax)

D-Bus supports four container types: array (a variable-length sequence of the same type), struct (a fixed-length sequence whose members may have different types), dictionary (a mapping from values of the same basic type to values of the same type), and variant (a container which may hold any D-Bus type, including another variant).

Arrays are represented by Python lists, or by dbus.Array, a subclass of list. When sending an array, if an introspected signature is available, that will be used; otherwise, if the signature keyword parameter was passed to the Array constructor, that will be used to determine the contents' signature; otherwise, dbus-python will guess from the array's first item.

The signature of an array is 'ax' where 'x' represents the signature of one item. For instance, you could also have 'as' (array of strings) or 'a(ii)' (array of structs each containing two 32-bit integers).

There's also a type dbus.ByteArray which is a subclass of str, used as a more efficient representation of a D-Bus array of bytes (signature 'ay').

Structs are represented by Python tuples, or by dbus.Struct, a subclass of tuple. When sending a struct, if an introspected signature is available, that will be used; otherwise, if the signature keyword parameter was passed to the Array constructor, that will be used to determine the contents' signature; otherwise, dbus-python will guess from the array's first item.

The signature of a struct consists of the signatures of the contents, in parentheses - for instance '(is)' is the signature of a struct containing a 32-bit integer and a string.

Dictionaries are represented by Python dictionaries, or by dbus.Dictionary, a subclass of dict. When sending a dictionary, if an introspected signature is available, that will be used; otherwise, if the signature keyword parameter was passed to the Dictionary constructor, that will be used to determine the contents' key and value signatures; otherwise, dbus-python will guess from an arbitrary item of the dict.

The signature of a dictionary is 'a{xy}' where 'x' represents the signature of the keys (which may not be a container type) and 'y' represents the signature of the values. For instance, 'a{s(ii)}' is a dictionary where the keys are strings and the values are structs containing two 32-bit integers.

Variants are represented by setting the variant_level keyword argument in the constructor of any D-Bus data type to a value greater than 0 (variant_level 1 means a variant containing some other data type, variant_level 2 means a variant containing a variant containing some other data type, and so on). If a non-variant is passed as an argument but introspection indicates that a variant is expected, it'll automatically be wrapped in a variant.

The signature of a variant is 'v'.

If a D-Bus method returns no value, the Python proxy method will return None.

If a D-Bus method returns one value, the Python proxy method will return that value as one of the dbus. types - by default, strings are returned as dbus.String (a subclass of Unicode) and byte arrays are returned as a dbus.Array of dbus.Byte.

If a D-Bus method returns multiple values, the Python proxy method will return a tuple containing those values.

If you want strings returned as dbus.UTF8String (a subclass of str) pass the keyword parameter utf8_strings=True to the proxy method.

If you want byte arrays returned as dbus.ByteArray (also a subclass of str - in practice, this is often what you want) pass the keyword parameter byte_arrays=True to the proxy method.

Asynchronous (non-blocking) method calls allow multiple method calls to be in progress simultaneously, and allow your application to do other work while it's waiting for the results. To make asynchronous calls, you first need an event loop or "main loop".

Currently, the only main loop supported by dbus-python is GLib.

dbus-python has a global default main loop, which is the easiest way to use this functionality. To arrange for the GLib main loop to be the default, use:

from dbus.mainloop.glib import DBusGMainLoop

DBusGMainLoop(set_as_default=True)

You must do this before connecting to the bus.

Actually starting the main loop is as usual for pygobject:

import gobject

loop = gobject.MainLoop()
loop.run()

While loop.run() is executing, GLib will run your callbacks when appropriate. To stop, call loop.quit().

You can also set a main loop on a per-connection basis, by passing a main loop to the Bus constructor:

import dbus
from dbus.mainloop.glib import DBusGMainLoop

dbus_loop = DBusGMainLoop()

bus = dbus.SessionBus(mainloop=dbus_loop)

This isn't very useful until we support more than one main loop, though.

In versions of dbus-python prior to 0.80, the way to set GLib as the default main loop was:

import dbus.glib

Executing that import statement would automatically load the GLib main loop and make this the default. This is now deprecated, since it's highly non-obvious, but may be useful if you want to write or understand backwards-compatible code.

PyQt v4.2 and later includes support for integrating dbus-python with the Qt event loop. To connect D-Bus to this main loop, call dbus.mainloop.qt.DBusQtMainLoop instead of dbus.mainloop.glib.DBusGMainLoop. Otherwise the Qt loop is used in exactly the same way as the GLib loop.

To make a call asynchronous, pass two callables as keyword arguments reply_handler and error_handler to the proxy method. The proxy method will immediately return None. At some later time, when the event loop is running, one of these will happen: either

  • the reply_handler will be called with the method's return values as arguments; or
  • the error_handler will be called with one argument, an instance of DBusException representing a remote exception.

examples/example-async-client.py makes asynchronous method calls to the service provided by examples/example-service.py which return either a value or an exception. As for examples/example-client.py, you need to run examples/example-service.py in the background or in another shell first.

To receive signals, the Bus needs to be connected to an event loop - see section Setting up an event loop. Signals will only be received while the event loop is running.

To respond to signals, you can use the add_signal_receiver method on Bus objects. This arranges for a callback to be called when a matching signal is received, and has the following arguments:

  • a callable (the handler_function) which will be called by the event loop when the signal is received - its parameters will be the arguments of the signal
  • the signal name, signal_name: here None (the default) matches all names
  • the D-Bus interface, dbus_interface: again None is the default, and matches all interfaces
  • a sender bus name (well-known or unique), bus_name: None is again the default, and matches all senders. Well-known names match signals from whatever application is currently the primary owner of that well-known name.
  • a sender object path, path: once again None is the default and matches all object paths

add_signal_receiver also has keyword arguments utf8_strings and byte_arrays which influence the types used when calling the handler function, in the same way as the byte_arrays and utf8_strings options on proxy methods.

add_signal_receiver returns a SignalMatch object. Its only useful public API at the moment is a remove method with no arguments, which removes the signal match from the connection.

You can also arrange for more information to be passed to the handler function. If you pass the keyword arguments sender_keyword, destination_keyword, interface_keyword, member_keyword or path_keyword to the connect_to_signal method, the appropriate part of the signal message will be passed to the handler function as a keyword argument: for instance if you use

def handler(sender=None):
    print "got signal from %r" % sender

iface.connect_to_signal("Hello", handler, sender_keyword='sender')

and a signal Hello with no arguments is received from com.example.Foo, the handler function will be called with sender='com.example.Foo'.

If there are keyword parameters for the form argn where n is a small non-negative number, their values must be unicode objects or UTF-8 strings. The handler will only be called if that argument of the signal (numbered from zero) is a D-Bus string (in particular, not an object-path or a signature) with that value.

Proxy objects have a special method connect_to_signal which arranges for a callback to be called when a signal is received from the corresponding remote object. The parameters are:

  • the name of the signal
  • a callable (the handler function) which will be called by the event loop when the signal is received - its parameters will be the arguments of the signal
  • the handler function, a callable: the same as for add_signal_receiver
  • the keyword argument dbus_interface qualifies the name with its interface

dbus.Interface objects have a similar connect_to_signal method, but in this case you don't need the dbus_interface keyword argument since the interface to use is already known.

The same extra keyword arguments as for add_signal_receiver are also available, and just like add_signal_receiver, it returns a SignalMatch.

You shouldn't use proxy objects just to listen to signals, since they might activate the relevant service when created, but if you already have a proxy object in order to call methods, it's often convenient to use it to add signal matches too.

examples/signal-recipient.py receives signals - it demonstrates general signal matching as well as connect_to_signal. Before running it, you'll need to run examples/signal-emitter.py in the background or in another shell.

FIXME describe BusName - perhaps fix its API first?

FIXME provide exemplary code, put it in examples

Objects made available to other applications over D-Bus are said to be exported. All subclasses of dbus.service.Object are automatically exported.

To export objects, the Bus needs to be connected to an event loop - see section Setting up an event loop. Exported methods will only be called, and queued signals will only be sent, while the event loop is running.

To export an object onto the Bus, just subclass dbus.service.Object. Object expects either a BusName or a Bus object, and an object-path, to be passed to its constructor: arrange for this information to be available. For example:

class Example(dbus.service.Object):
    def __init__(self, object_path):
        dbus.service.Object.__init__(self, dbus.SessionBus(), path)

This object will automatically support introspection, but won't do anything particularly interesting. To fix that, you'll need to export some methods and signals too.

FIXME also mention dbus.gobject.ExportedGObject once I've written it

To export a method, use the decorator dbus.service.method. For example:

class Example(dbus.service.Object):
    def __init__(self, object_path):
        dbus.service.Object.__init__(self, dbus.SessionBus(), path)

    @dbus.service.method(dbus_interface='com.example.Sample',
                         in_signature='v', out_signature='s')
    def StringifyVariant(self, variant):
        return str(variant)

The in_signature and out_signature are D-Bus signature strings as described in Data Types.

As well as the keywords shown, you can pass utf8_strings and byte_arrays keyword arguments, which influence the types which will be passed to the decorated method when it's called via D-Bus, in the same way that the byte_arrays and utf8_strings options affect the return value of a proxy method.

You can find a simple example in examples/example-service.py, which we used earlier to demonstrate examples/example-client.py.

The method decorator accepts a sender_keyword keyword argument. If you set that to a string, the unique bus name of the sender will be passed to the decorated method as a keyword argument of that name:

class Example(dbus.service.Object):
    def __init__(self, object_path):
        dbus.service.Object.__init__(self, dbus.SessionBus(), path)

    @dbus.service.method(dbus_interface='com.example.Sample',
                         in_signature='', out_signature='s',
                         sender_keyword='sender')
    def SayHello(self, sender=None):
        return 'Hello, %s!' % sender
        # -> something like 'Hello, :1.1!'

FIXME and also add an example, perhaps examples/example-async-service.py

To export a signal, use the decorator dbus.service.signal; to emit that signal, call the decorated method. The decorated method can also contain code which will be run when called, as usual. For example:

class Example(dbus.service.Object):
    def __init__(self, object_path):
        dbus.service.Object.__init__(self, dbus.SessionBus(), path)

    @dbus.service.signal(dbus_interface='com.example.Sample',
                         signature='us')
    def NumberOfBottlesChanged(self, number, contents):
        print "%d bottles of %s on the wall" % (number, contents)

e = Example('/bottle-counter')
e.NumberOfBottlesChanged(100, 'beer')
# -> emits com.example.Sample.NumberOfBottlesChanged(100, 'beer')
#    and prints "100 bottles of beer on the wall"

The signal will be queued for sending when the decorated method returns - you can prevent the signal from being sent by raising an exception from the decorated method (for instance, if the parameters are inappropriate). The signal will only actually be sent when the event loop next runs.

examples/example-signal-emitter.py emits some signals on demand when one of its methods is called. (In reality, you'd emit a signal when some sort of internal state changed, which may or may not be triggered by a D-Bus method call.)

Copyright 2006-2007 Collabora Ltd.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.