Introduction to D-Bus

From CodeCodex

D-Bus is the high-level interprocess communication protocol endorsed by FreeDesktop.Org. The protocol specification is here.

Python[edit]

Documention for the Python D-Bus API can be found here.

Finding Out What’s On The Bus[edit]

The following list_bus script, when passed an argument of 0, will list the names on the system bus, while passing an argument of 1 will make it list the names on the session bus.

import sys
import dbus

if len(sys.argv) != 2 :
    raise RuntimeError("need one arg specifying bus to list, 0 for system bus, 1 for session bus")
#end if

the_bus = (dbus.SystemBus, dbus.SessionBus)[int(sys.argv[1])]()
for name in the_bus.list_names() :
    print(name)
#end for

On my Debian system with KDE 4.3,

list_bus 0

produces output like this:

org.freedesktop.DBus
:1.7
:1.8
:1.9
:1.171
:1.260
org.freedesktop.NetworkManagerUserSettings
:1.150
org.freedesktop.PolicyKit1
:1.20
... (etc) ...

while

list_bus 1

produces output like this:

org.freedesktop.DBus
org.freedesktop.PowerManagement
:1.7
:1.8
org.kde.kded
org.kde.NepomukServer
org.kde.korgac
org.kde.nepomuk.services.nepomukfilewatch
org.kde.klipper
org.kde.kwalletd
org.kde.krunner
org.kde.NotificationItemWatcher
... (etc) ...

Finding Out What Things Do[edit]

If you’ve read the specs, you’ll see that actually communicating with apps over D-Bus is quite an involved process: you have to specify a bus name to communicate with a particular application, an object path to some object supported by that bus name, a method name to call, and an interface name which includes that method. Luckily, if the method name is unique among all interfaces supported by that object you can leave out the interface name.

One thing that helps you figure out how to call things is you can usually ask the application itself, via D-Bus, what its supported objects, interfaces and methods are, if the application supports introspection.

The following introspect script takes three command-line arguments, the first being 0 or 1 to specify the system or session bus respectively (as with list_bus above), the second being the bus name and the third being the object path, and returns the result of introspecting that object:

import dbus
import sys

if len(sys.argv) != 4 :
    raise RuntimeError("need three args, the bus spec, the bus name and object path to introspect")
#end if
the_bus = (dbus.SystemBus, dbus.SessionBus)[int(sys.argv[1])]()
the_bus_name = sys.argv[2]
the_path = sys.argv[3]
sys.stdout.write \
  (
    dbus.Interface
      (
        the_bus.get_object(the_bus_name, the_path),
        dbus_interface = "org.freedesktop.DBus.Introspectable"
      ).Introspect()
  )

For the bus name, pass any of the names returned by list_bus, and for the object name, you can start with “/” for the root object.

For example, the call

introspect 1 org.kde.screensaver /

returns

<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.freedesktop.DBus.Introspectable">
    <method name="Introspect">
      <arg name="xml_data" type="s" direction="out"/>
    </method>
  </interface>
  <node name="App"/>
  <node name="KBookmarkManager"/>
  <node name="KIO"/>
  <node name="MainApplication"/>
  <node name="ManagerIface_contact"/>
  <node name="ScreenSaver"/>
</node>

Notice that child node called ScreenSaver? What’s in there?

ldo@theon:dbus_try> introspect 1 org.kde.screensaver /ScreenSaver

<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"             
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">                            
<node>                                                                                     
  <interface name="org.freedesktop.ScreenSaver" >                                          
    <signal name="ActiveChanged" >                                                         
      <arg type="b" />                                                                     
    </signal>                                                                              
    <method name="Lock" />                                                                 
    <method name="SimulateUserActivity" />                                                 
    <method name="GetActive" >                                                             
      <arg direction="out" type="b" />                                                     
    </method>                                                                              
    <method name="GetActiveTime" >                                                         
      <arg direction="out" type="u" name="seconds" />                                      
    </method>                                                                              
    <method name="GetSessionIdleTime" >                                                    
      <arg direction="out" type="u" name="seconds" />                                      
    </method>                                                                              
    <method name="SetActive" >                                                             
      <arg direction="out" type="b" />                                                     
      <arg direction="in" type="b" name="e" />                                             
    </method>                                                                              
    ... rest omitted ...
  </interface>
</node>

See that SetActive method call? Could that be what activates the screen saver?

Doing Something 1: Activate The Screensaver[edit]

Let’s try it:

import dbus
import time

screensaver = dbus.Interface \
  (
    dbus.SessionBus().get_object("org.kde.screensaver", "/ScreenSaver"),
    dbus_interface = "org.freedesktop.ScreenSaver"
  )
screensaver.SetActive(True)
time.sleep(5)
screensaver.SetActive(False)

If you run this script and don’t touch the mouse or keyboard, it should activate the screen saver for 5 seconds, then turn it off again.

Doing Something 2: Show A Progress Dialog From A Shell Script[edit]

The kdialog command can be used to bring up various KDE UI elements from within a shell script, including a progress bar. It returns the D-BUS bus name and object path for the window it creates, and you can use these to communicate with the window via the dbus-send command.

Here’s an example script that brings up a progress dialog and steps it through 10 increments at 1-second intervals. Note the careful specification of argument types (including variant where appropriate), otherwise dbus-send fails to find the right interface for the method call. Also note the seemingly redundant use of --print-reply where the result is discarded; the calls don’t seem to happen correctly otherwise.

And finally, note that the progress dialog can be set to automatically close itself when it is given an increment of 100%.

NrSteps=10
DelayPerStep=1

Progress=$(kdialog --progressbar "Doing Something..." $NrSteps)
  # returns 2 values, the bus name and the object path, separated by a space.
  # These can be nicely substituted into the following dbus-send commands without quotes.
dbus-send --print-reply --session --dest=$Progress org.freedesktop.DBus.Properties.Set \
    string:org.kde.kdialog.ProgressDialog string:autoClose variant:boolean:true >/dev/null
dbus-send --print-reply --session --dest=$Progress org.freedesktop.DBus.Properties.Set \
    string:org.kde.kdialog.ProgressDialog string:value variant:int32:0 >/dev/null
for Step in $(seq 1 $NrSteps); do
    sleep $DelayPerStep
    dbus-send --print-reply --session --dest=$Progress org.freedesktop.DBus.Properties.Set \
        string:org.kde.kdialog.ProgressDialog string:value variant:int32:$Step >/dev/null
done