dbus

  1. dbus
    1. Overview
    2. Goals & status
    3. Author
    4. Requirements
    5. License
    6. Version
    7. Terminology
    8. DBus in general
    9. Data types
    10. Exported functions
    11. Exported constants
    12. Examples
      1. Examples you can test with QT
      2. Examples based on the DBus Tutorial

Overview

This is a binding for libdbus 1.x. DBus is a popular IPC (inter-process communication) protocol which is often used between system components (for example hald informs applications when new hardware becomes available, gpsd informs apps when the location has changed, or an application requests gsmd to place a phone call).

Goals & status

Goal Achieved?
send signals yes
call methods in other processes, and get the return values yes
call methods in other processes asynchronously, and the return values come back to a callback later
register a procedure as a handler which will be called when a signal is received yes
register a procedure as a method which can be called from another process yes
assign a path to a TinyClos object and map applicable generic functions as dbus methods
create proxy methods matching remote methods yes
create proxy objects matching remote objects (so that all the remote object's applicable methods are made into local proxy methods)
discover services locally yes
discover services on nearby machines impossible with dbus-daemon as-is
discover methods and signals provided by services XML only so far
registered methods and signal-handlers are automatically included in the Introspectable interface implementation
user code to do any of the above should be minimal: abstract away the orthogonal extra steps (open a connection, start a polling thread, etc.) yes
support all DBus data types for method parameters and return values

Author

Shawn Rutledge

Requirements

libdbus and its headers

License

libdbus has historically had a GPL license. Later they switched to AFL / GPL dual license. This egg is released under the MIT license, which is compatible with AFL.

Version

As of version 0.8, it seems to be working for the limited use cases of sending and receiving method calls, but the interface should not be considered "frozen" yet.

Terminology

bus
The aggregator and dispatcher of messages between processes. The usual choices are system bus, session bus or an app-specific bus. This egg uses the session bus by default. It is the same bus used by desktop session services, like KDE components for example. Accessing the system bus requires special permission. An app-specific bus does not promote application interoperability. So that leaves the session bus as generally being the most appropriate.
bus name
A unique identifier for a DBus client. Every DBus client is automatically assigned a bus name. These consist of a colon character ':' followed by some numbers. You can see these in the DBus traffic in the output of dbus-monitor. DBus clients that want to listen for method calls can request a second bus name, a so-called well-known bus name, which is a name by which other DBus clients can refer to them. Well-known bus names are what you use in the 'service' field of a method call.
service
A well-known bus name giving the destination of a method call. Conventionally, these look like reversed domain names. Signals do not use services.
path
A DBus-exposed API can be conceptually organized into a hierarchy. Paths index into this hierarchy with notation familiarly derived from file and URL paths, where path components are separated by slashes '/'. The program might map path components to actual objects in memory, but this is not required. The default path, when not given, is "/".
interface
a partitioned set of methods which is being offered, like a Java interface or an abstract class. An interface is technically optional when registering a method or signal handler, but this egg currently requires it.
member name
also called method name, or the name of the signal or message.
signal
a one-to-many notification from one process to any others which happen to be listening for that particular notification. A signal message does not include a service because it does not have a specific destination. Signals are one way; there is no explicit reply from the receivers.
method call
a one-to-one message between DBus clients. The caller can pass arguments, and the handler sends a method return message back with the result.
method return
the message type of the response to a method call.
method
a scheme function to be invoked when a particular method call message is received. Its result will be sent back to the calling client as a method return.
message
the DBus-protocol representation of a signal or a method call passing across the DBus connection between two processes.

And this egg introduces one more:

context
a combination of bus, service, interface and path. That and a method name will get you a unique identifier for a callback (AKA method or message handler).

This egg does not yet fully support making methods, signal handlers, and interfaces discoverable by other clients.

DBus in general

Use dbus-monitor to see all the dbus traffic (on the session bus by default).

A many-to-many "bus" exists as a running dbus-daemon process. Each application which is interested in using that bus connects to the daemon via Unix-domain sockets. The daemon collects incoming messages and dispatches them to the connected processes which have expressed interest in receiving particular categories of messages. Usually there is one running instance of dbus-daemon for the system bus, and one running instance for each session bus: each user who is logged in and running a desktop session is typically running one instance of the daemon.

A one-to-one application-specific bus can bypass the daemon: one app connects directly to the other. But this is not a flexible service-oriented approach to IPC, so it is not usually done, and this egg does not support it.

Data types

DBus protocol (unlike XML, for example) allows sending real native datatypes across the connection. In fact it provides more data types than does Chicken. So when you pass some parameters along with a method call, these are the default conversions:

Chicken data type DBus data type
fixnum DBUS_TYPE_INT32
flonum DBUS_TYPE_DOUBLE
boolean DBUS_TYPE_BOOLEAN
vector DBUS_TYPE_ARRAY
anything else DBUS_TYPE_STRING

When a DBus message is received, the parameters are converted similarly, but unsupported DBus types will be converted to #f.

Watch out for cases when Chicken converts an integer to a flonum, because it was too large to fit in a fixnum. It will go across the DBus connection as a double, which might not be what you wanted.

It is planned to support requests for conversion to other DBUS types, in case you need to interface with an existing service that requires datatypes other than the typical ones above. DBus supports variants and structs; these conversions are not done yet.

Exported functions

[procedure] (dbus:make-context #!key (bus dbus:session-bus) service interface (path "/"))

Glom together a bus, service, interface and path into an object that you can conveniently hold for later use. The choices for bus are dbus:session-bus (which is the default if you don't specify it), dbus:system-bus and dbus:starter-bus. The service, interface and path can be given as strings or symbols. Symbols would be preferred; if you provide strings, dbus:make-context will convert them to symbols. The default for path is "/" so if you don't need a hierarchical organization of "objects", you don't need to specify it.

[procedure] (dbus:send context name . params)

Send a signal.

[procedure] (dbus:call context name . params)

Call a remote method, wait for the reply, and receive the return values from the remote method, as a list. Note that since this is a blocking call (it waits for the reply), it's not suitable for implementing the actor model or anything similarly lightweight/low-latency.

[procedure] (dbus:make-method-proxy context name)

Wrap dbus:call in a lambda, which you can then use like any local function. The return value from the function thus created will be the list of return values from the remote method.

[procedure] (dbus:register-signal-handler context name msg-cb)

Provide a handler to be called when the current process receives a DBus signal which matches the given context and the given signal name. Start a SRFI-18 thread to poll for incoming signals (unless polling has been disabled on this bus).

[procedure] (dbus:register-method context name msg-cb)

Provide a handler (a method body) to be called when the current process receives a DBus message which matches the given context and the given method name. Start a SRFI-18 thread to poll for incoming messages (unless polling has been disabled on this bus).

[procedure] (dbus:enable-polling-thread! #!key (bus dbus:session-bus) (enable #t) (interval default-polling-interval))

Enable or disable the polling thread for a particular bus, and set the polling interval in seconds.

[procedure] (dbus:poll-for-message #!key (bus dbus:session-bus) (timeout 0))

Check once whether any incoming DBus message is waiting on a particular bus, and if so, dispatch it to the appropriate callback (which you have previously registered using dbus:register-method or dbus:register-signal-handler). Returns #t if it received a message, #f if not.

[procedure] (dbus:discover-services #!key (bus dbus:session-bus))

Get a list of service names available on a bus.

[procedure] (dbus:discover-api-xml context)

Get the XML API "documentation" provided by the Introspectable interface of a service, if that interface is implemented.

Exported constants

[constant] dbus:session-bus

Session bus.

[constant] dbus:system-bus

System bus.

[constant] dbus:starter-bus

Starter bus.

Examples

These are in the examples subdirectory in svn, and included with the egg too.

Examples you can test with QT

QT includes a DBUS remote-controlled car example. E.g. it might be located in /usr/share/qt4/examples/qdbus/remotecontrolledcar/car depending on your distro. If you run the car, you can cause the wheels of the car to turn to the right by doing this:

(use dbus)
(define rc-car-context (dbus:make-context
        service: 'com.trolltech.CarExample
        interface: 'com.trolltech.Examples.CarInterface
        path: '/Car))
(dbus:call rc-car-context "turnRight")

That example called a method but it did not expect any return values.

Now suppose you want to simulate the car, so you can use the above example to control your own car rather than the QT one. And suppose you want to poll for messages manually rather than via the default SRFI-18 polling thread:

(use dbus)

(define (turn-right) (printf "car is turning to the right~%"))
(define (turn-left) (printf "car is turning to the left~%"))

(define rc-car-context (dbus:make-context
	service: 'com.trolltech.CarExample
	path: '/Car
	interface: 'com.trolltech.Examples.CarInterface ))

(dbus:enable-polling-thread enable: #f)

(dbus:register-method rc-car-context "turnRight" turn-right)
(dbus:register-method rc-car-context "turnLeft" turn-left)

(let loop ()
  (dbus:poll-for-message)
  (loop))

dbus:register-method starts a polling loop the first time that it is called with a context specifying a particular bus. (And if you register methods on multiple buses, there must be a polling thread for each.) So you can then run the program above which does dbus:send, and you will see the appropriate printf statement execute asynchronously when the message is received. However the polling thread is subject to the usual limitations of Chicken threads: if there is any blocking I/O, which is waiting for something, then all threads are blocked. That means you should not use the readline egg for example, because the polling thread will be blocked between each time that you type something and hit Enter.

If the polling thread doesn't work, and you would prefer to poll manually, you can call (dbus:enable-polling-thread! enable: #f) to stop the thread (or to prevent it from being started when you register a new method), and then call dbus:poll-for-message periodically. Both of those functions can take an optional bus: parameter (which defaults to dbus:session-bus, naturally). dbus:poll-for-message can also take an optional timeout: parameter (which is 0 by default, so that it is a non-blocking call).

Examples based on the DBus Tutorial

The next example, taken from the DBus tutorial, shows how to deal with return values. First the "listener" program which will answer the query:

(use dbus)
(define (query . params)
        (printf "got a query; params: ~s~%" params)
        ;; the response to the query:
        `(#t 42))
(define ctxt (dbus:make-context
        service: 'test.method.server
        interface: 'test.method.Type
        path: '/test/method/Object))
(dbus:register-method ctxt "Method" query)

And now the program which sends a query and prints out the response:

(use dbus)
(define ctxt (dbus:make-context
        service: 'test.method.server
        interface: 'test.method.Type
        path: '/test/method/Object))
(let ([response (dbus:call ctxt "Method" "query"
                "What is the meaning of life, the universe and everything?") ])
(printf "sent a very important query with a known answer; got flippant response ~s~%" response)
        (if (and (list? response) (eq? 42 (cadr response)))
                (printf "bingo!~%")
                (printf "and the answer is wrong too!  Bad supercomputer, bad!~%")))

What do you get if you combine a remote-controlled mobile thingy with a supercomputer capable of deep thought? A paranoid android of course! This example in the test directory handles both kinds of messages from the two examples above (the query and the turnLeft/turnRight commands) and proves that one polling thread is enough to handle multiple, completely different contexts, on a single bus. So registering more than one method is low-cost compared to registering the first one.