Making Single Instance Programs with DBus

By Iain Holmes iain@openedhand.com

DBus is an Interprocess Communications protocol, just as CORBA and DCOP are.

We will make the following program unique instance. Its not a very exciting program really. It opens a window which displays the message passed in on the commandline. If you look closely it already has some of the makings of a single instance program with the stored list of windows. These are here so that they do not confuse the actual single instance stuff that we will be adding later on. The code for this program is available here: original.c.

#include <gtk/gtk.h> /* A list of our windows. * When the last window is closed the program terminates. */ GList *windows = NULL; static void window_close (GtkWidget *widget, gpointer userdata) { /* Find our window in the list and delete it */ windows = g_list_remove (windows, widget); /* Quit if there are no more windows */ if (windows == NULL) { gtk_main_quit (); } } static void open_window (const char *message) { GtkWidget *window; GtkWidget *label; window = gtk_window_new (GTK_WINDOW_TOPLEVEL); /* Add our window to the windows list */ windows = g_list_append (windows, window); g_signal_connect (window, "destroy", G_CALLBACK (window_close), NULL); label = gtk_label_new (message); gtk_container_add (GTK_CONTAINER (window), label); gtk_widget_show_all (window); } int main (int argc, char **argv) { gtk_init (&argc, &argv); if (argc == 1) { g_print ("Usage: %s <message>\n", argv[0]); return 0; } open_window (argv[1]); gtk_main (); return 0; }

The first thing to do when trying to make a program single instance is to define the single instance interface. This is done with an XML file that will be converted into two C header files. For single instance this interface needs one method: NewInstance. The interface is defined in the file single-instance.xml as follows.

<node name="/"> <interface name="org.myorg.SingleInstanceInterface"> <method name="NewInstance"> <arg type="as"/> </method> </interface> </node>

This defines the interface called org.myorg.SingleInstanceInterface. The org.myorg is just the unique namespace and can be anything that is likely to be unique such as domain names. The GNOME project would use org.gnome, Novell could use com.novell and so on.

After the interface name we define the methods that our interface has. As mentioned above our SingleInstanceInterface only has one method and it is defined here. The args define the signature of the method and these are written in shorthand. NewInstance only has one argument with type 'as'. The 's' stands for string and the 'a' stands for an array, so we have an array of strings. Translated to C code this is 'const char **'. There are many type defintions available and the system is fully recursive, meaning that types can be held within other types.

Now that we have defined the interface we need to get it into our program. To convert the interface definition into C code we use the program dbus-binding-tool. We need to run this twice: once to create the server bindings, and once to create the client bindings. Firstly, the server:

$ dbus-binding-tool --mode=glib-server --output=single-instance-glue.h --prefix=single_instance single-instance.xml

This command is hopefully self-explanatory. We are creating the factory bindings (glib-server is used as a factory is just another DBus server to the bindings), they are to be placed in the file single-instance-glue.h, the functions are prefixed with single_instance and the definition is from the file single-instance.xml. There is a convention that the factory bindings go into a file with the -glue.h prefix, although it isn't necessary. After running the command, we have everything needed to implement the factory side of our single instance program.

The complete code for the factory implementation is available at here: factory.c. Only snippets of code will be shown below, so it may be useful to refer to the whole file to see how it all fits together.

To implement our factory, we include the single-instance-glue.h file at the start or our program.

#include <gtk/gtk.h> #include "single-instance-glue.h" /* A list of our windows. . . .

And then we need to register the factory with the DBus server.

. . . if (argc == 1) { g_print ("Usage: %s <message>\n", argv[0]); return 0; } register_factory (); open_window (argv[1]); gtk_main (); . . .

It is this new register_factory function that does all the hard work to set up the factory. It does three things

We'll go through these steps one at a time.

1: Getting the DBus connection.

This is the easiest bit. We want to connect to the session bus, so we just call dbus_g_bus_get

DBusGConnection *connection; GError *error = NULL; . . . connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); if (connection == NULL) { g_warning ("Failed to make connection to session bus: %s", error->message); g_error_free (error); return FALSE; }

2: Checking for other instances

Once we have the connection we can check to see if another factory was registered before us. To do this, we attempt to register our factory name with the bus. If someone got there before us, then the name will already be registered.

. . . #define FACTORY_NAME "org.myorg.SingleInstanceFactory" static gboolean register_factory (void) { DBusGConnection *connection; DBusGProxy *proxy; GError *error = NULL; guint32 request_name_ret; . . . proxy = dbus_g_proxy_new_for_name (connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS); if (!org_freedesktop_DBus_request_name (proxy, FACTORY_NAME, 0, &request_name_ret, &error)) { g_warning ("There was an error requesting the name: %s", error->message); g_error_free (error); return FALSE; } if (request_name_ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { /* Someone else registered the name before us */ return FALSE; } . . .

We get a proxy object for the main DBus service with dbus_g_proxy_new_for_name. This proxy allows us to call functions that exist on the remote service, which in this case is the DBus service. Once we have this proxy we can request the name we want to register our factory under with the strange looking function org_freedesktop_DBus_request_name. This function is what the DBus remote methods look like in C. org_freedesktop_DBus_request_name is a call to the method RequestName that resides on the org.freedesktop.DBus interface.

The result code is placed in request_name_ret. If we are the first program to attempt to register the name then it will equal DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER. If it equals anything else then there is already a registered factory and we do not need to register it again.

3: Registering our factory object

An object? What object? Well, for our factory to be registered we need to create a GObject that implements the NewInstance method. It will be an incredibly basic object, just implementing what we need.

. This information for this function comes from our server bindings and it tells the server what functions implement what methods. Our NewInstance method will be implemented by a function called single_instance_new_instance

. . . static gboolean single_instance_new_instance (SingleInstanceFactory *factory, const char **IN_args, GError **error) { open_window (IN_args[0]); return TRUE; } . . .

For this program we just need to take the first argument from 'IN_args' and pass it to open_window.

If we were to encounter an error in single_instance_new_instance then we would set the error and return FALSE.

One more thing that is needed to define the object is the structure definitions and a forward declaration of single_instance_new_instance as it is mentioned in the single-instance-glue.h file.

. . . #define SINGLE_INSTANCE_FACTORY_TYPE (single_instance_factory_get_type ()) typedef struct _SingleInstanceFactory { GObject object; } SingleInstanceFactory; typedef struct _SingleInstanceFactoryClass { GObjectClass object_class; } SingleInstanceFactoryClass; static gboolean single_instance_new_instance (SingleInstanceFactory *factory, const char **IN_args, GError **error); #include "single-instance-glue.h" . . .

Now we can create our factory object and register it with the DBus session bus.

. . . SingleInstanceFactory *factory; . . . if (request_name_ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { return FALSE; } factory = g_object_new (SINGLE_INSTANCE_FACTORY_TYPE, NULL); dbus_g_connection_register_g_object (connection, "/Factory", G_OBJECT (factory)); return TRUE; . . .

And thats our factory implemented. Now we need to implement the client side of it.

Again we need to turn our interface definition into C code to get it into our program. The complete code for this section is available here: client.c. As for factory.c only snippets of code that are added or changed will be discussed below.

$ dbus-binding-tool --mode=glib-client --output=single-instance-bindings.h single-instance.xml

This creates the file single-instance-bindings.h which is just included in the program normally.

. . . #include "single-instance-glue.h" #include "single-instance-bindings.h" . . .

We only need to open the window in this program if we were the initial instance of the program. If there is already a factory running then we want it to open the window. We can know whether we were the factory or if there was one running by checking the return value of register_factory which returns TRUE if we are the factory and FALSE otherwise.

. . . if (register_factory () == TRUE) { open_window (argv[1]); gtk_main (); return 0; } . . .

If register_factory returns FALSE, then we need to find the factory that is already running and call NewInstance on it. First, we need to get a connection to the bus. This is the same as when we were implementing the factory.

. . . DBusGConnection *connection; GError *error = NULL; . . . connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); if (connection == NULL) { g_warning ("Failed to open connection to bus: %s\n", error->message); g_error_free (error); return 0; } . . .

After we have the session bus, we need to get a proxy for our remote object, again similar to the way we did it in the factory.

. . . proxy = dbus_g_proxy_new_for_name (connection, FACTORY_NAME, "/Factory", "org.myorg.SingleInstanceInterface"); . . .

The arguments for dbus_g_proxy_new_for_name are the way to identify the interface and the object. We are looking for the object called "/Factory" that is registered on the FACTORY_NAME namespace. Once we have that object, we want the interface called "org.myorg.SingleInstanceInterface" that it implements. Remember that when we started our factory FACTORY_NAME was the name that we claimed as our identifier with the call to org_freedesktop_DBus_request_name and that "/Factory" was the name we registered our factory object under with the call to dbus_g_connection_register_g_object.

Now that we have a proxy to our remote object, we can call the NewInstance method. We first need to create our arguments. They are a NULL terminated array of strings, so we need to copy the argv into an array and NULL terminate it and then call the remote function.

. . . char **args; int i; . . . /* Need to NULL terminate argv */ args = g_new (char *, argc + 1); for (i = 1; i < argc; i++) { args[i - 1] = argv[i]; } args[argc - 1] = NULL; if (org_myorg_SingleInstanceInterface_new_instance (proxy, (const char **) args, &error) == FALSE) { g_free (args); g_warning ("Failed to start new instance: %s", error->message); g_error_free (error); return 0; } . . .

Once we have called the remote object and told it to open a new instance this program is finished and can quit happily.