D-Bus

Introduction

D-Bus is a message bus system, a simple way for applications to talk to one another, D-Bus supplies a system and a session daemons.

The system daemon is launched at the system startup level and used mainly for hardware events, while the session daemon is launched when the user login to a desktop environment and it is for use for desktop applications to connect to each other.

The D-Bus developer always recommend the usage of a D-Bus binding library, such as dbus-glib or dbus-qt, instead of using the D-Bus API directly, they said that the D-Bus API is not yet frozen and by using this API directly the programmer is signing up for some pain, in my opinion, in order to understand clearly any D-Bus binding libraries, it is a very good idea to dive into the D-Bus low level programming, keep in mind that what we are going to use here is the trivial part of the D-Bus API.

There is some terms that one should be familiar with:

  • D-Bus Connection: is the structure to use for opening a connection to the daemon, either the system bus daemon by specifying DBUS_BUS_SYSTEM or to the user session bus daemon using DBUS_BUS_SESSION.
  • D-Bus Message: It is a message between two processes, all the D-Bus intercommunication are done using messages, these messages can have the following types, method calls, method returns, signals, and errors. The message can carry out parameters, such as, boolean integers, real numbers, string, arrays, etc.
  • path: Is the path of a remote object, example /org/freedesktop/DBus.
  • Interface: Is the interface on a given Object to talk with.

Getting a connection

DBusConnection *connection;
DBusError error;
 
dbus_error_init (&error); /* Initialize the error structure */
 
connection = dbus_bus_get (DBUS_BUS_SESSION, &error); /* Or DBUS_BUS_SYSTEM */
 
if ( dbus_error_is_set (&error) )
{
    printf ("Error connecting to the daemon bus: %s", error.message);
    dbus_error_free (&error);
}

Connecting two applications

In this section, we will use D-Bus to connect two desktop applications, one listen to D-Bus messages and the other send D-Bus messages, but before starting, the listener program should not just start and exit, it has to wait for events, so we have to find a way to organize the events sent to our program, a simple solution for this is to use the main event loop from glib, when using it we can keep our program in sleep mode until receiving events, another problem occurs is the fact that how we can integrate our bus connection with the glib main event loop, here comes dbus-glib, so our tiny program will depend also on dbus-glib for just one call, dbus_connection_setup_with_g_main, this call integrates the glib main loop and the DBus bus events.

A question raises here, if we want to use just D-Bus, how we can avoid the usage of its glib binding, the answer is not simple, first we have to write our own loop events, and integrate it with the bus events, a good start is to look at the D-Bus source as they have a helpful code in dbus/dbus-mainloop, but to simplify our job we will use dbus-glib.

listen.c In this program we will use dbus_bus_add_match to add a match for the messages that we want to receive, the rule string has a specific format, see D-Bus match rule for full details.

#include <stdio.h>
#include <dbus/dbus.h>
 
#include <dbus/dbus-glib.h>
#include <glib.h>
 
static DBusHandlerResult
dbus_filter (DBusConnection *connection, DBusMessage *message, void *user_data)
{
 
    if ( dbus_message_is_signal (message,"org.dbus.tutorial","Customize" ) )
    {
        printf ("Message cutomize received\n");
        return DBUS_HANDLER_RESULT_HANDLED;
    }
 
    if ( dbus_message_is_signal (message, "org.dbus.tutorial", "Quit" ) )
    {
        printf ("Message quit received\n");
        GMainLoop *loop = (GMainLoop*) user_data;
        g_main_loop_quit (loop);
        return DBUS_HANDLER_RESULT_HANDLED;
    }
    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
 
int main()
{
    DBusConnection *connection;
    DBusError error;
 
    /* glib main loop */
    GMainLoop *loop;
    loop = g_main_loop_new(NULL,FALSE);
 
    dbus_error_init (&error);
 
    connection = dbus_bus_get (DBUS_BUS_SESSION, &error);
 
    if ( dbus_error_is_set (&error) )
    {
        printf ("Error connecting to the daemon bus: %s",error.message);
        dbus_error_free (&error);
        return 1;
    }
 
    dbus_bus_add_match (connection, "type=\'signal\',interface=\'org.dbus.tutorial\'", NULL);
    dbus_connection_add_filter (connection, dbus_filter, loop, NULL);
 
    /* dbus-glib call */
    dbus_connection_setup_with_g_main (connection, NULL);
 
    /* run glib main loop */
    g_main_loop_run (loop);
 
    return 0;
}

 

 

send.c

#include <stdio.h>
#include <dbus/dbus.h>
 
static void
send_message (DBusConeection *connection, const char *msg)
{
    DBusMessage *message;
 
    message = dbus_message_new_signal ("/org/share/linux",
                                       "org.share.linux",
                                       msg);
    /* Send the message */
    dbus_connection_send (connection, message, NULL);
    dbus_message_unref (message);
}
 
int
main (int argc, char **argv)
{
    DBusConnection *connection;
    DBusError error;
 
    dbus_error_init (&error);
    connection = dbus_bus_get (DBUS_BUS_SESSION, &error);
    if (!connection)
    {
        printf ("Failed to connect to the D-BUS daemon: %s", error.message);
        dbus_error_free (&error);
        return 1;
    }
 
    if ( argc == 1 )
    {
        return 0;
    }
 
    int i;
    for ( i = 1; i &lt; argc; i++)
    {
        if (!strcmp (argv[i], "-c") )
        {
            send_message (connection, "Customize");
        }
        else if ( !strcmp (argv[i], "-q") )
        {
            send_message (connection, "Quit");
        }
    }
    return 0;
}

To compile run the following commands:

gcc `pkg-config --libs --cflags dbus-glib-1` listen.c -o listen
gcc `pkg-config --libs --cflags dbus-1` send.c -o send

Reserving a bus name

An application can request a bus name from the D-Bus daemon, this name can be used to identify the application and the services that it provides, the name have to be a valid UTF-8 string and must have at least one ‘.’ which separates the elements name, each element must contains at least one character, example “org.freedesktop”, for a full list read the DBus specification section D-Bus Bus names

#include <stdio.h>
#include <dbus/dbus.h>
 
int main()
{
    DBusConnection *connection;
    DBusError error;
 
    char *name = "org.dbus.tutorial";
 
    dbus_error_init (&error);
 
    connection = dbus_bus_get (DBUS_BUS_SESSION, &error);
 
    if ( dbus_error_is_set (&error) )
    {
        printf ("Error connecting to the daemon bus: %s", error.message);
        dbus_error_free (&error);
        return 1;
    }
 
    dbus_bool_t ret = dbus_bus_name_has_owner (connection, name, &error);
 
    if ( dbus_error_is_set (&error) )
    {
        dbus_error_free (&error);
        printf ("DBus Error: %s\n", error.message);
        return 1;
    }
 
    if ( ret == FALSE )
    {
        printf ("Bus name %s doesn't have an owner, reserving it...\n", name);
        int request_name_reply =
        dbus_bus_request_name (connection,name, DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
 
        if ( dbus_error_is_set (&error) )
        {
            dbus_error_free (&error);
            printf ("Error requesting a bus name: %s\n", error.message);
            return 1;
        }
 
        if ( request_name_reply == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER )
        {
            printf ("Bus name %s Successfully reserved!\n", name);
            return 0;
        }
        else
        {
            printf ("Failed to reserve name %s\n", name);
            return 1;
        }
    }
    else
    /*
    if ret of method dbus_bus_name_has_owner is TRUE, then this is useful for
    detecting if your application is already running and had reserved a bus name
    unless somebody stole this name from you, so better to choose a correct bus name
    */
    {
        printf ("%s is already reserved\n", name);
        return 1;
    }
    return 0;
}

We have used the DBUS_NAME_FLAG_DO_NOT_QUEUE flag, D-Bus will not queue us in case the bus name that we want to reserve is already used. For a full list of flags for dbus_bus_request_name and return code see the D-Bus API. A requested bus name can always be released using dbus_bus_release_name.

To Compile the above code, the D-Bus development package should be installed, depending on your distribution the name of this package may differ, but should be something like libdbus-dev. Then you compile the code with the following command:

gcc `pkg-config --libs --cflags dbus-1` example1.c -o example1

Services activation

The message bus can start applications (services) on behalf of other applications, the application asks DBus to start a service by its name, usually the name should be known such as org.freedesktop.TextEditor.

In order for DBus to find the executable corresponding to a particular name, the bus daemon looks for service description files which usually are installed in /usr/share/dbus-1/services and they have .service in their extension name (almost all Linux distributions use this prefix to install dbus services files), as an example of a service file.

DBus service file example: org.dbus.tutorial.service

[D-BUS Service]
Name=org.dbus.tutorial
Exec=path to the executable.

We will write two programs, one is the service that we want to start the other is the application that activates this service

dbus-service-example.c

#include <stdio.h>
#include <dbus/dbus.h>
 
int main()
{
    DBusConnection *connection;
    DBusError error;
 
    dbus_error_init (&error);
    /* DBUS_BUS_STARTER is the bus that started us */
    connection = dbus_bus_get (DBUS_BUS_STARTER, &error);
 
    /*
     * Do something here to make sure that the application was
     * successfully started by DBus Example could be something like
     * FILE *tmp;
     * tmp = fopen ("/tmp/dbus-tutorial-service.result", "w");
     * fprintf (tmp, "dbus-tutorial service was started successfully");
     * fclose (tmp);
     * /
 
    /* After that you have the service up, so you can do whetever you like */
    dbus_connection_unref (connection);
 
    return 0;
}

Compile this example with dbus-1 argument for pkg-config, you need to install the service file in /usr/share/dbus-1/service, name it org.dbus.tutorial and edit the Exec path to where you have the service example binary.

start-service.c

#include <stdio.h>
#include <dbus/dbus.h>
 
int main()
{
    DBusConnection *connection;
    DBusError error;
    DBusMessage *message;
 
    const char *service_name = "org.dbus.tutorial";
    /* Currently this is not used by D-Bus, they say it is for future expansion*/
    dbus_uint32_t flag;
    dbus_bool_t result;
 
    dbus_error_init (&error);
 
    connection = dbus_bus_get (DBUS_BUS_SESSION, &error);
 
    if ( dbus_error_is_set (&error) )
    {
        printf ("Error getting dbus connection: %s\n", error.message);
        dbus_error_free (&error);
        dbus_connection_unref (connection);
        return 0;
    }
 
    message = dbus_message_new_method_call ("org.freedesktop.DBus",
                                            "/org/freedesktop/DBus",
                                            "org.freedesktop.DBus",
                                            "StartServiceByName");
    if (!message )
    {
        printf ("Error creating DBus message\n");
        dbus_connection_unref (connection);
        return 0;
    }
    /* We don't want to receive a reply */
    dbus_message_set_no_reply (message, TRUE);
 
    /* Append the argument to the message, must ends with DBUS_TYPE_INVALID*/
    dbus_message_append_args (message,
                              DBUS_TYPE_STRING,
                              &service_name,
                              DBUS_TYPE_UINT32,
                              &flag,
                              DBUS_TYPE_INVALID);
    result = dbus_connection_send (connection, message, NULL);
 
    if ( result == TRUE )
    {
        printf ("Successfully activating the %s service\n", service_name);
    }
    else
    {
        printf ("Failed to activate the %s service\n", service_name);
    }
    dbus_message_unref (message);
    dbus_connection_unref (connection);
    return 0;
}

3 Comments

  1. Matias says:

    Thanks. :)

    Inspired by this post, I made a plugin to control pragha from xfce panel (Also based on last xfce4-sample-plugin). :)

    http://code.google.com/p/dissonance/downloads

    I would like to make it work from mpris2 and perhaps for any player mpris2 compatible. would be very complicated?

    From console it works
    “dbus-send –dest=org.mpris.MediaPlayer2.pragha /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Play”

    But making this, I return errors.
    “message = dbus_message_new_signal (“/org/mpris/MediaPlayer2″, “org.mpris.MediaPlayer2.pragha”, “org.mpris.MediaPlayer2.Player.Play”);”

    Is correct?.

    Thanks agian.
    Regards.

  2. ali says:

    @Matias

    It should be the other way round:
    (“org.mpris.MediaPlayer2.pragha”, “/org/mpris/MediaPlayer2″, “org.mpris.MediaPlayer2.Player.Play);

    Also i guess you need to use dbus_message_new_method_call, because you are invoking a method on the interface /org/mpris/MediaPlayer2.

    dbus_message_new_signal is the construction of a new signal emission, see http://dbus.freedesktop.org/doc/api/html/group__DBusMessage.html for more details.

  3. Matias says:

    Yes..
    A bit of d-feet, a little more documentation, and looking much code and make it work with mpris2. :)

    But when try to make it generic, I get strange results depending the player.. Also.. for now I’m very pleased with the results.

    Thanks.
    If I can work for every player, I will comment on the xfce list.

    Regards.

Leave a Reply