D-Bus Tutorial for Python

Those days I had to make a D-Bus server and client in python and I thought to share with you guys some things about the D-Bus.

First of all, what is D-Bus? Well, D-Bus is a system that offers a communication channel for multiple programs.

Someone must also distinguish between: session and system bus.

The session bus is specific for each user, while the system bus is specially designed for the state of the whole system and can change this state (like adding a new storage device, the internet connection suffers some modification).


“Talk is cheap, show me the code”  Linus Torvalds

One example of a session bus server is the following:

#! /usr/bin/env python3

# D-Bus Server -- Session Bus

from gi.repository import GLib
import dbus
import dbus.service
from dbus.mainloop.glib import DBusGMainLoop

class Session_DBus(dbus.service.Object):

    def __init__(self):
        bus_name = dbus.service.BusName('org.me.test_session', bus=dbus.SessionBus())
         dbus.service.Object.__init__(self, bus_name, '/org/me/test_session')

    # Interface and Method
    @dbus.service.method('org.me.test1')
    def session_bus_message1(self):
        return "Session Bus 1"

    # Different Interface and different Method
    # The method must not have not the same name as the first
    @dbus.service.method('org.me.test2')
    def session_bus_message2(self):
        return "Session Bus 2"

    # Method with arguments
    @dbus.service.method('org.me.test2')
    def session_bus_strings(self, string1, string2):
        return string1 + " " + string2

DBusGMainLoop(set_as_default=True)
dbus_service = Session_DBus()

try: 
    GLib.MainLoop().run() 
except KeyboardInterrupt: 
    print("\nThe MainLoop will close...") 
    GLib.MainLoop().quit() 

And the client (2 versions):

Vers1

#!/usr/bin/env python3                                              

# D-Bus Client V1 -- Session Bus

import dbus                                                                    

bus = dbus.SessionBus()                                                       
session = bus.get_object("org.me.test_session", "/org/me/test_session")         
                                                                                
method_message1 = session.get_dbus_method('session_bus_message1', 'org.me.test1')
method_message2 = session.get_dbus_method('session_bus_message2', 'org.me.test2')
method_message3 = session.get_dbus_method('session_bus_strings', 'org.me.test2')                                                                                                             

# Call the methods with their specific parameters                               
print(method_message1())                                      
print(method_message2())                                      
print(method_message3("Hello", "world")) 

Vers2

#!/usr/bin/env python3                                                         

# D-Bus Client V2 -- Session Bus         
                                                                  
import dbus                                                                     
                                                                                
bus = dbus.SessionBus()                                                         
session = bus.get_object("org.me.test_session", "/org/me/test_session")         
                                                                                
interface1 = dbus.Interface(session, "org.me.test1")                            
interface2 = dbus.Interface(session, "org.me.test2")                            
                                                                                
# Call the methods using the interface                                          
print(interface1.session_bus_message1())                                        
print(interface2.session_bus_message2())                                        
print(interface2.session_bus_strings("Hello", "world"))   

For the client scripts to run, one must first run the script for the server and after that to run the clients. Also if you want to have to have the server initiated (to not manually run it) you could create a file in /usr/share/dbus-1/service/; let’s say org.me.test_session.config with the following content:

[D-BUS Service]
Name=org.me.test_session
Exec=server_location

After this you can run only the clients and the system would know where server part is and how to handle it. Also, do not forget to make the server script executable:

chmod +x server.py

For the system bus server you would need only to modify line 13 from the sessionbus server and line 7 from the client scripts; to replace SessionBus() with SystemBus(). Let’s also change the names for the methods and for the service.

The final code would look like this:


#!/usr/bin/env python3

# D-Bus Server -- Server Bus

from gi.repository import GLib
import dbus
import dbus.service
from dbus.mainloop.glib import DBusGMainLoop

class Session_DBus(dbus.service.Object):


        def __init__(self):
                bus_name = dbus.service.BusName('org.me.test_server', bus=dbus.SystemBus())
                dbus.service.Object.__init__(self, bus_name, '/org/me/test_server')

        # Interface and Method
        @dbus.service.method('org.me.test1')
        def session_bus_message1(self):
                return "Server Bus 1"


        # Different Interface and different Method
        # The method must not have not the same name as the first 
        @dbus.service.method('org.me.test2')
        def session_bus_message2(self):
                return "Server Bus 2"


        # Method with arguments
        @dbus.service.method('org.me.test2')
        def session_bus_strings(self, string1, string2):
                return string1 + " " + string2


DBusGMainLoop(set_as_default=True)
dbus_service = Session_DBus()

try: 
    GLib.MainLoop().run() 
except KeyboardInterrupt: 
    print("\nThe MainLoop will close...") 
    GLib.MainLoop().quit() 

And for the clients it would look something like this:
Vers1

#! /usr/bin/env python3

import dbus

bus = dbus.SystemBus()
system = bus.get_object("org.me.test_system", "/org/me/test_system")

# Get each method for the specific interface
method_message1 = system.get_dbus_method('system_bus_message1', 'org.me.test1')
method_message2 = system.get_dbus_method('system_bus_message2', 'org.me.test2')
method_message3 = system.get_dbus_method('system_bus_strings', 'org.me.test2')

# Call the methods with their specific parameters
print(method_message1())
print(method_message2())
print(method_message3("Hello", "world"))

Vers2

#!/usr/bin/env python3

import dbus

bus = dbus.SystemBus()
system = bus.get_object("org.me.test_system", "/org/me/test_system")

interface1 = dbus.Interface(system, "org.me.test1")
interface2 = dbus.Interface(system, "org.me.test2")

# Call the methods using the interface
print(interface1.system_bus_message1())
print(interface2.system_bus_message2())
print(interface2.system_bus_strings("Hello", "world")

Of course, if you try the same thing now: run the server and then test the clients you would see that you get an error after trying to turn on the server: a message saying that a specific connection is not allowed to own a service. This thing is because (remember from the first lines of the post) you try (as a regular user) to run a D-Bus that may change the entire system state.

To run the server and test the clients you could to the following:

  1. Create a file in /etc/dbus-1/system.d/ with the name org.me.test_system.conf with the following content:
  2. <!DOCTYPE busconfig PUBLIC                                                      
     "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"                          
     "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">                 
                                                                                     
    <busconfig>                                                                     
        <!-- Owned only by the root -->                                            
        <policy user="root">                                                        
            <allow own="*"/>                                                        
        </policy>                                                                   
                                                                                     
        <!-- What to allow for users -->                                           
        <policy context="default">                                                  
            <allow send_destination="*"/>                                           
            <allow send_interface="*"/>                                             
        </policy>                                                                   
    </busconfig>  
    
  3. Run the server and then you can test the clients :).

Also if you want to only to run the clients (the server to automatically kick in) you can create a file in /usr/share/dbus-1/system-services/ with the name org.me.test_system.service that would contain the following lines:

[D-BUS Service]
Name=org.me.test_system
Exec=server_location
User=root

That is all for now and I hope you found this tutorial helping :D.

“In vain have you acquired knowledge if you have not imparted it to others.”
Deuteronomy Rabbah

Have a great day,
George