Nothing Special   »   [go: up one dir, main page]

DBus Activation Tutorial

The DBus missing tutorial – DBus Activation

I’ll explain the activation part of dbus, that’s how you can request the dbus daemon to automatically
start a program that provide a given service. You will also find general dbus tips in there.

Intro

We have a program in /usr/bin/program-providing-servicename providing service
org.gnome.ServiceName, and we want that program to be started whenever dbus needs
that service (if it isn’t already running). On the other end, we have a client that
wants to use the service, and doesn’t care wether it’s running or not, it just want to access the service.

See the end of the tutorial for credits, external links, changelog, and license.

Autotools

Here is the layout we will use for our example program:

program-
       |-data-
       |     |-Makefile.am
       |     |-datafiles.[png,desktop,..]
       |     |-org.gnome.ServiceName.service.in
       |
       |-src-
       |     |-Makefile.am
       |     |-server.[hc]
       |     |-client.[hc]
       |     |-server-bindings.h
       |     |-client-bindings.h
       |     |-servicename-infos.xml
       |
       |-configure.ac
       |-acinclude.m4

Let’s see what goes in each file. The main purpose of all this is to install a .service file, which is used
by the dbus-daemon to launch a process owning a given service.

program/configure.ac

We need to retrieve the directory on the system in which to place the .service file. With modern DBuses (>0.30)
it is placed in /usr/share/dbus-1/services/. The following m4 macro to expand the $datadir variable is useful. Put this
in your configure.ac file:

AS_AC_EXPAND(DATADIR, $datadir)

DBUS_SERVICES_DIR="$DATADIR/dbus-1/services"
AC_SUBST(DBUS_SERVICES_DIR)
AC_DEFINE_UNQUOTED(DBUS_SERVICES_DIR, "$DBUS_SERVICES_DIR", [Where services dir for DBUS is])

Then you need to place this code snippet in program/acinclude.m4 file, or append it to the existing one:

dnl AS_AC_EXPAND(VAR, CONFIGURE_VAR)
dnl
dnl example
dnl AS_AC_EXPAND(SYSCONFDIR, $sysconfdir)
dnl will set SYSCONFDIR to /usr/local/etc if prefix=/usr/local

AC_DEFUN([AS_AC_EXPAND],
[
  EXP_VAR=[$1]
  FROM_VAR=[$2]

  dnl first expand prefix and exec_prefix if necessary
  prefix_save=$prefix
  exec_prefix_save=$exec_prefix

  dnl if no prefix given, then use /usr/local, the default prefix
  if test "x$prefix" = "xNONE"; then
    prefix=$ac_default_prefix
  fi
  dnl if no exec_prefix given, then use prefix
  if test "x$exec_prefix" = "xNONE"; then
    exec_prefix=$prefix
  fi

  full_var="$FROM_VAR"
  dnl loop until it doesn't change anymore
  while true; do
    new_full_var="`eval echo $full_var`"
    if test "x$new_full_var"="x$full_var"; then break; fi
    full_var=$new_full_var
  done

  dnl clean up
  full_var=$new_full_var
  AC_SUBST([$1], "$full_var")

  dnl restore prefix and exec_prefix
  prefix=$prefix_save
  exec_prefix=$exec_prefix_save
])
data/Makefile.am

DBus needs a .service file to know what program to launch for a given service name.
That file has to be in /usr/share/dbus-1/services/, add this snippet to your automake file:

# Dbus service file
servicedir = $(DBUS_SERVICES_DIR)
service_in_files = org.gnome.ServiceName.service.in
service_DATA = $(service_in_files:.service.in=.service)

# Rule to make the service file with bindir expanded
$(service_DATA): $(service_in_files) Makefile
	@sed -e "s|@bindir@|$(bindir)|" $<> $@

The content of that data/org.gnome.ServiceName.service.in file is the following:

[D-BUS Service]
Name=org.gnome.ServiceName
Exec=@bindir@/program-providing-servicename
src/Makefile.am

In this file we generate glib bindings headers that define method stubs to be implemented/called by the server/client. DBus comes with
a tool called dbus-binding-tool that can generate both client and server header files. Let’s see what we
write in the Makefile.am in addition to the usual stuff:

BUILT_SOURCES = server-bindings.h client-bindings.h
# We don't want to install this header
noinst_HEADERS = $(BUILT_SOURCES)

# Correctly clean the generated headers, but keep the xml description
CLEANFILES = $(BUILT_SOURCES)
EXTRA_DIST = servicename-infos.xml

#Rule to generate the binding headers
server-bindings.h:  servicename-infos.xml
	dbus-binding-tool --prefix=server_object --mode=glib-server $<> $@

client-bindings.h:  servicename-infos.xml
	dbus-binding-tool --prefix=server_object --mode=glib-client $<> $@

Code to use that stuff

Now you have your autotools setup to correctly prepare and install the service file, Dbus knows how to launch
the program corresponding to your service. If the program is already running, nothing happens. If the program isn’t running,
dbus launches the program, waits for it to take ownership of the service, then executes the remote method and returns the result.
The following section will most likely be similar in some parts to the
dbus tutorial

The XML description

The src/servicename-infos.xml is a description of the interface provided by service, here is a quick example, we
define one method that takes a string as parameter and returns a string.

<?xml version="1.0" encoding="UTF-8"?>

<node name="/org/gnome/ServiceName">
	<interface name="org.gnome.ServiceName">
		<annotation name="org.freedesktop.DBus.GLib.CSymbol" value="server"/>
		<method name="EchoString">
			<arg type="s" name="original" direction="in" />
			<arg type="s" name="echo" direction="out" />
		</method>
		<!-- Add more methods/signals if you want -->
	</interface>
</node>
  • direction="in" means a parameter to the method, direction="out" is a return value
  • type="s" means a string, see dbus tutorial for more types signatures
  • name="xxx" is purely decorative
  • The annotation is the prefix used in the server binding, in this case, all our server method stubs will be prefixed by server_,
    that means we will have a method called server_echo_string to implement.

The server needs to #include "server-bindings.h" header,
the client needs to #include "client-bindings.h" header. When we look at that generate header we can see how the XML
description has been translated to C method calls.

Server implementation

In the server, we must ensure that we will take ownership of the provided service, otherwise the activation procedure won’t succeed.

In the class_init of your GObject, you must install the dbus introspection infos, and in the init function we will
register for the service.

#include <dbus/dbus-glib-bindings.h>

/* Standard GObject class structures, etc */
typedef struct
{
	DBusGConnection *connection;
}ServerObjectClass;

class_init(ServerObjectClass *klass)
{
	GError *error = NULL;

	/* Init the DBus connection, per-klass */
	klass->connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
	if (klass->connection == NULL)
	{
		g_warning("Unable to connect to dbus: %s", error->message);
		g_error_free (error);
		return;
	}

	/* &dbus_glib__object_info is provided in the server-bindings.h file */
	/* OBJECT_TYPE_SERVER is the GType of your server object */
	dbus_g_object_type_install_info (OBJECT_TYPE_SERVER, &dbus_glib__object_info);
}

init(ServerObject *server)
{
	GError *error = NULL;
	DBusGProxy *driver_proxy;
	ServerObjectClass *klass = SERVER_OBJET_GET_CLASS (server);
	int request_ret;

	/* Register DBUS path */
	dbus_g_connection_register_g_object (klass->connection,
			"/org/gnome/ServiceName",
			G_OBJECT (server));

	/* Register the service name, the constant here are defined in dbus-glib-bindings.h */
	driver_proxy = dbus_g_proxy_new_for_name (klass->connection,
			DBUS_SERVICE_DBUS,
			DBUS_PATH_DBUS,
			DBUS_INTERFACE_DBUS);

	if(!org_freedesktop_DBus_request_name (driver_proxy,
			"org.gnome.ServiceName",
			0, &request_ret,    /* See tutorial for more infos about these */
			&error))
	{
		g_warning("Unable to register service: %s", error->message);
		g_error_free (error);
	}
	g_object_unref (driver_proxy);
}

Now our server object is ready, and is available on dbus to answer remote calls. We must now provide an implementation
of the exported methods.

The server-bindings.h shows a marshaller declaration of type
dbus_glib_marshal__BOOLEAN__STRING_POINTER_POINTER. We must implement a function called server_echo_string
that respect this prototype. Note that the annotation is translated with a server_ prefix to the method, and the method name
is mangled to a C style name xx_xxx. That method must return TRUE in case of success, or FALSE if an error occured, and in
that case, the GError must be set. The return values are given as pointers to the return location.

gboolean
server_echo_string (ServerObject *server, gchar *original, gchar **echo, GError **error)
{
	*echo = g_strdup (original);

	if (problem)
	{
		/* We have an error, set the gerror */
		g_set_error (error, g_quark_from_static_string ("echo"),
					0xdeadbeef,
					"Some random problem occured, you're screwed");
		return FALSE;
	}

	return TRUE;
}
Client implementation

The client-bindings.h shows the prototypes of the function we can call to execute a remote request transparently.
Before the call a proxy object must be created, this is also shown. In real situations, the two pieces of code
would be placed in their own place, for example the creation of the proxy in the object contructor, and the method call in a callback
for a button, this is just an exemple to show the base mecanism.

/* Somewhere in the code, we want to execute EchoString remote method */
DBusGProxy *proxy;
DBusGConnection *connection;
GError *error = NULL;
gchar *result;

connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
if (connection == NULL)
{
	g_warning("Unable to connect to dbus: %sn", error->message);
	g_error_free (error);
	/* Basically here, there is a problem, since there is no dbus :) */
	return;
}

/* This won't trigger activation! */
proxy = dbus_g_proxy_new_for_name (connection,
		"org.gnome.ServiceName",
		"/org/gnome/ServiceName",
		"org.gnome.ServiceName");

/* The method call will trigger activation, more on that later */
if (!org_gnome_ServiceName_echo_string (proxy, "The string we want echo-ed", &result, &error))
{
	/* Method failed, the GError is set, let's warn everyone */
	g_warning ("Woops remote method failed: %s", error->message);
	g_error_free (error);
	return;
}

g_print ("We got the folowing result: %s", result);

/* Cleanup */
g_free (result);
g_object_unref (proxy);

/* The DBusGConnection should never be unreffed, it lives once and is shared amongst the process */

This first snippet shows a simple method call. First we get the dbus connection, then we create a proxy object that
will talk with dbus, and finally we call the remote method using the binding header. You see that the prototype is the
same as the one we implemented in the server, boolean return value indicating succes or failure,
pointers to method result values are passed after the parameters. In case of error (return FALSE) , the GError is set.

Now the activation bit, when you create the proxy nothing happens yet, but when you call the remote method, dbus will check to see
if the service exists on the bus.

  • If the service already exists, the method is called, blocks until the result is returned.
  • If the service does not exist, dbus searches the dbus-1/services/*.service files until it finds a
    matching service descriptor.

    • When the service file is found, the method blocks, dbus launches the associated executable, wait for the executable to take
      ownership of the service, execute the remote call, and finally returns.
    • When the service file isn’t found, it returns an error.

The above procedure means that your program will block during the process launch, which can be bad, for example in case of a gtk mainloop.
Fortunately, there is also a way to do asynchronous calls, and very easily!

Client asynchronous implementation

Everything can be copied from the previous example, excepted the actual remote method call, this one now becomes:

[... Get the dbus connection, create the proxy ...]

/* We now call the method asynchronously */
org_gnome_ServiceName_echo_string_async (proxy,
		"The string we want echo-ed",
		client_echo_reply,    /* See below */
		client);

/* Of course, do not unref the proxy now, since we need it for the callback */

As you can see, we give the parameters directly, but instead of giving the error, and return pointers, we pass
a callback client_echo_reply that will be called when the method call has finished. We can also pass
any user data as gpointer, here we pass the hypothetical ClientObject instance

Now we must implement the callback

static void
client_echo_reply (DBusGProxy *proxy, char *answer, GError *error, gpointer userdata)
{
	ClientObject *client = CLIENT_OBJECT (userdata);
	if (error!= NULL)
	{
		g_warning ("An error occured while calling echo_string remote method: %s", error->message);
		g_error_free (error);
		return;
	}

	g_print ("We got an echo reply, result: %sn", answer);
}

That’s it, that was a piece of cake! Now as you can guess, the async call will also trigger the activation, if needed,
but now your program won’t block until the remote application has started, you can happily continue your mainloop, and be
notified when the remote program gives the answer!

What happens if the remote program fails to launch? Dbus has a timeout timer, if after a given time, no program
has claimed the requested service, the method callback is called with the GError set, that’s it.

Python Service

You can also provide the service with a python program, here is an example:

#!/usr/bin/env python
import gtk
import dbus
import dbus.service
if getattr(dbus, 'version', (0,0,0)) >= (0,41,0):
	import dbus.glib

class ServerObject(dbus.service.Object):
	def __init__(self):
		# Here the service name
		bus_name = dbus.service.BusName('org.gnome.ServiceName',bus=dbus.SessionBus())
		# Here the object path
		dbus.service.Object.__init__(self, bus_name, '/org/gnome/ServiceName')

	# Here the interface name, and the method is named same as on dbus.
	@dbus.service.method('org.gnome.ServiceName')
	def EchoString(self, original):
		return original

server = ServerObject()
gtk.main()

Just save that as /usr/bin/program-providing-servicename, make it executable..

Random comments

  • The executable given in the .service file can be a normal binary file,
    but also a !#/usr/bin/sh-type file, like a shell script, or a python script
  • Never unref DBusGConnection
  • The server’s method prototypes must come before you include the server-bindings.h file
  • ALWAYS use a –prefix with dbus-bindings-tool, otherwise problems will arise when mixing multiple codes that use dbus independently

ChangeLog

  • 2005-08-20: Update the automake snippet with BUILT_SOURCES, otherwise the header doesn’t auto-build with make

Credits and links

Creative Commons license


Contrat Creative Commons

Comments

14 responses to “DBus Activation Tutorial”

  1. CsorbaGomba Avatar

    Can you make available the files, you’ve created? Especially sever.c amd client.c?

  2. Prasad K Avatar
    Prasad K

    Hi I really enjoyed your tutorial.Will it be possible to get hold of source code?

  3. Raphaël Slinckx Avatar

    Unfortunately tere is no source code, and it’s more like excerpts from many different sources. The goal here was to explain concepts more than actual code, sorry for that..

  4. frederic heem Avatar
    frederic heem

    With dbus-glib-0.71, dbus-binding-tool is not generating OBJECT_TYPE_SERVER and SERVER_OBJET_GET_CLASS.
    The problem now is implementing the “glib object emulation”. In other word, how to define and use ServerObjectClass and ServerObject ? When and how to call the G_DEFINE_TYPE macro ?
    I think providing an archive with the samples code would be very nice.

  5. Matt Wette Avatar
    Matt Wette

    Nice review, but I am still not getting things to work.
    I seem to havea problem with security policy. This tutorial
    would have helped better if security policies were covered,
    or a link to this topic was included. On my system this stuff
    is in /etc/dbus-1/system.d.

  6. Curtis Magyar Avatar
    Curtis Magyar

    Hello, thanks for the tutorial. I had to make a change to the Makefile.am snippet to make it work.

    Instead of:
    @sed -e “s|@bindir@|$(bindir)|” $<> $@

    I used:
    @sed -e “s|\@bindir\@|$(bindir)|” $ $@

    or else the actual sed command that is executed is:

    sed -e “s|/usr/bin|/usr/bin|” $ $@

  7. […] on the back burner due to the usual time constraints. Revisiting the issue I’ve come across this great tutorial. Finally somebody has put all the pieces needed together, thanks a million for […]

  8. Karl H. Beckers Avatar
    Karl H. Beckers

    Thank you so much for this tutorial.

  9. Karl H. Beckers Avatar
    Karl H. Beckers

    One practical question:
    I end up with -server-bindings.h building up the array of remote functions available. But then those are not defined there and I need to add an #include of the header where they are.
    … and I need to do that every time I run make. Am I missing anything?

  10. […] 2008 Official Dbus Tutorial Basic Glib DBus Server using XML Basic GLib DBus Server without XML DBus Activation Tutorial Another DBus Basic Example Asynchronous DBus […]

  11. […] This has the great advantage over X messages that if the handler isn’t running the bus will start it, and the handler can do its work and then softly and silently vanish away without ever hanging […]

  12. […] cronopete en sí casi no hay cambios, salvo haber añadido soporte de D-Bus activation, para que si no está lanzado, y el usuario hace click en el icono de configuración o de […]

  13. […] few other people have done similar things in their blogs. This one explains how to use org.freedesktop.DBus.GLib.Async a little bit […]

Leave a Reply