| Title: Migrating to GDBus | |
| SPDX-License-Identifier: LGPL-2.1-or-later | |
| SPDX-FileCopyrightText: 2010 Matthias Clasen | |
| SPDX-FileCopyrightText: 2010, 2011 David Zeuthen | |
| # Migrating to GDBus | |
| ## Conceptual differences | |
| The central concepts of D-Bus are modelled in a very similar way in | |
| dbus-glib and GDBus. Both have objects representing connections, proxies and | |
| method invocations. But there are some important differences: | |
| - dbus-glib uses the libdbus reference implementation, GDBus doesn't. | |
| Instead, it relies on GIO streams as transport layer, and has its own | |
| implementation for the D-Bus connection setup and authentication. Apart | |
| from using streams as transport, avoiding libdbus also lets GDBus avoid | |
| some thorny multithreading issues. | |
| - dbus-glib uses the GObject type system for method arguments and return | |
| values, including a homegrown container specialization mechanism. GDBus | |
| relies on the GVariant type system which is explicitly designed to match | |
| D-Bus types. | |
| - dbus-glib models only D-Bus interfaces and does not provide any types for | |
| objects. GDBus models both D-Bus interfaces (via the GDBusInterface, | |
| GDBusProxy and GDBusInterfaceSkeleton types) and objects (via the | |
| GDBusObject, GDBusObjectSkeleton and GDBusObjectProxy types). | |
| - GDBus includes native support for the org.freedesktop.DBus.Properties (via | |
| the GDBusProxy type) and org.freedesktop.DBus.ObjectManager D-Bus | |
| interfaces, dbus-glib doesn't. | |
| - The typical way to export an object in dbus-glib involves generating glue | |
| code from XML introspection data using dbus-binding-tool. GDBus provides a | |
| similar tool called gdbus-codegen that can also generate Docbook D-Bus | |
| interface documentation. | |
| - dbus-glib doesn't provide any convenience API for owning and watching bus | |
| names, GDBus provides the `g_bus_own_name()` and `g_bus_watch_name()` | |
| family of convenience functions. | |
| - GDBus provides API to parse, generate and work with Introspection XML, | |
| dbus-glib doesn't. | |
| - GTestDBus provides API to create isolated unit tests | |
| ## API comparison | |
| | dbus-glib | GDBus | | |
| |-----------|-------| | |
| | `DBusGConnection` | `GDBusConnection` | | |
| | `DBusGProxy` | `GDBusProxy`, `GDBusInterface` - also see `GDBusObjectProxy` | | |
| | `DBusGObject` | `GDBusInterfaceSkeleton`, `GDBusInterface` - also see `GDBusObjectSkeleton` | | |
| | `DBusGMethodInvocation` | `GDBusMethodInvocation` | | |
| | `dbus_g_bus_get()` | `g_bus_get_sync()`, also see `g_bus_get()` | | |
| | `dbus_g_proxy_new_for_name()` | `g_dbus_proxy_new_sync()` and `g_dbus_proxy_new_for_bus_sync()`, also see `g_dbus_proxy_new()` | | |
| | `dbus_g_proxy_add_signal()` | not needed, use the generic “g-signal” | | |
| | `dbus_g_proxy_connect_signal()` | use `g_signal_connect()` with “g-signal” | | |
| | `dbus_g_connection_register_g_object()` | `g_dbus_connection_register_object()` - also see `g_dbus_object_manager_server_export()` | | |
| | `dbus_g_connection_unregister_g_object()` | `g_dbus_connection_unregister_object()` - also see `g_dbus_object_manager_server_unexport()` | | |
| | `dbus_g_object_type_install_info()` | introspection data is installed while registering an object, see `g_dbus_connection_register_object()` | | |
| | `dbus_g_proxy_begin_call()` | `g_dbus_proxy_call()` | | |
| | `dbus_g_proxy_end_call()` | `g_dbus_proxy_call_finish()` | | |
| | `dbus_g_proxy_call()` | `g_dbus_proxy_call_sync()` | | |
| | `dbus_g_error_domain_register()` | `g_dbus_error_register_error_domain()` | | |
| | `dbus_g_error_has_name()` | no direct equivalent, see `g_dbus_error_get_remote_error()` | | |
| | `dbus_g_method_return()` | `g_dbus_method_invocation_return_value()` | | |
| | `dbus_g_method_return_error()` | `g_dbus_method_invocation_return_error()` and variants | | |
| | `dbus_g_method_get_sender()` | `g_dbus_method_invocation_get_sender()` | | |
| ## Owning bus names | |
| Using dbus-glib, you typically call RequestName manually to own a name, like in the following excerpt: | |
| ```c | |
| error = NULL; | |
| res = dbus_g_proxy_call (system_bus_proxy, | |
| "RequestName", | |
| &error, | |
| G_TYPE_STRING, NAME_TO_CLAIM, | |
| G_TYPE_UINT, DBUS_NAME_FLAG_ALLOW_REPLACEMENT, | |
| G_TYPE_INVALID, | |
| G_TYPE_UINT, &result, | |
| G_TYPE_INVALID); | |
| if (!res) | |
| { | |
| if (error != NULL) | |
| { | |
| g_warning ("Failed to acquire %s: %s", | |
| NAME_TO_CLAIM, error->message); | |
| g_error_free (error); | |
| } | |
| else | |
| { | |
| g_warning ("Failed to acquire %s", NAME_TO_CLAIM); | |
| } | |
| goto out; | |
| } | |
| if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) | |
| { | |
| if (error != NULL) | |
| { | |
| g_warning ("Failed to acquire %s: %s", | |
| NAME_TO_CLAIM, error->message); | |
| g_error_free (error); | |
| } | |
| else | |
| { | |
| g_warning ("Failed to acquire %s", NAME_TO_CLAIM); | |
| } | |
| exit (1); | |
| } | |
| dbus_g_proxy_add_signal (system_bus_proxy, "NameLost", | |
| G_TYPE_STRING, G_TYPE_INVALID); | |
| dbus_g_proxy_connect_signal (system_bus_proxy, "NameLost", | |
| G_CALLBACK (on_name_lost), NULL, NULL); | |
| /* further setup ... */ | |
| ``` | |
| While you can do things this way with GDBus too, using | |
| `g_dbus_proxy_call_sync()`, it is much nicer to use the high-level API for | |
| this: | |
| ```c | |
| static void | |
| on_name_acquired (GDBusConnection *connection, | |
| const gchar *name, | |
| gpointer user_data) | |
| { | |
| /* further setup ... */ | |
| } | |
| /* ... */ | |
| owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM, | |
| NAME_TO_CLAIM, | |
| G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, | |
| on_bus_acquired, | |
| on_name_acquired, | |
| on_name_lost, | |
| NULL, | |
| NULL); | |
| g_main_loop_run (loop); | |
| g_bus_unown_name (owner_id); | |
| ``` | |
| Note that `g_bus_own_name()` works asynchronously and requires you to enter | |
| your mainloop to await the `on_name_acquired()` callback. Also note that in | |
| order to avoid race conditions (e.g. when your service is activated by a | |
| method call), you have to export your manager object before acquiring the | |
| name. The `on_bus_acquired()` callback is the right place to do such | |
| preparations. | |
| ## Creating proxies for well-known names | |
| dbus-glib lets you create proxy objects for well-known names, like the following example: | |
| ```c | |
| proxy = dbus_g_proxy_new_for_name (system_bus_connection, | |
| "org.freedesktop.Accounts", | |
| "/org/freedesktop/Accounts", | |
| "org.freedesktop.Accounts"); | |
| ``` | |
| For a DBusGProxy constructed like this, method calls will be sent to the current owner of the name, and that owner can change over time. | |
| The same can be achieved with GDBusProxy: | |
| ```c | |
| error = NULL; | |
| proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, | |
| G_DBUS_PROXY_FLAGS_NONE, | |
| NULL, /* GDBusInterfaceInfo */ | |
| "org.freedesktop.Accounts", | |
| "/org/freedesktop/Accounts", | |
| "org.freedesktop.Accounts", | |
| NULL, /* GCancellable */ | |
| &error); | |
| ``` | |
| For an added layer of safety, you can specify what D-Bus interface the proxy | |
| is expected to conform to by using the GDBusInterfaceInfo type. | |
| Additionally, GDBusProxy loads, caches and tracks changes to the D-Bus | |
| properties on the remote object. It also sets up match rules so D-Bus | |
| signals from the remote object are delivered locally. | |
| The GDBusProxy type normally isn't used directly - instead proxies | |
| subclassing GDBusProxy generated by `gdbus-codegen` is used, see the section | |
| called “Using gdbus-codegen”. | |
| ## Generating code and docs | |
| ### Using gdbus-codegen | |
| dbus-glib comes with dbus-binding-tool, which can produce somewhat nice | |
| client- and server-side wrappers for a D-Bus interface. With GDBus, | |
| gdbus-codegen is used and like its counterpart, it also takes D-Bus | |
| Introspection XML as input: | |
| #### Example D-Bus Introspection XML | |
| ```xml | |
| <node> | |
| <!-- org.gtk.GDBus.Example.ObjectManager.Animal: | |
| @short_description: Example docs generated by gdbus-codegen | |
| @since: 2.30 | |
| This D-Bus interface is used to describe a simple animal. | |
| --> | |
| <interface name="org.gtk.GDBus.Example.ObjectManager.Animal"> | |
| <!-- Mood: The mood of the animal. | |
| @since: 2.30 | |
| Known values for this property include | |
| <literal>Happy</literal> and <literal>Sad</literal>. Use the | |
| org.gtk.GDBus.Example.ObjectManager.Animal.Poke() method to | |
| change this property. | |
| This property influences how often the animal jumps up and | |
| down, see the | |
| #org.gtk.GDBus.Example.ObjectManager.Animal::Jumped signal | |
| for more details. | |
| --> | |
| <property name="Mood" type="s" access="read"/> | |
| <!-- | |
| Poke: | |
| @make_sad: Whether to make the animal sad. | |
| @make_happy: Whether to make the animal happy. | |
| @since: 2.30 | |
| Method used to changing the mood of the animal. See also the | |
| #org.gtk.GDBus.Example.ObjectManager.Animal:Mood property. | |
| --> | |
| <method name="Poke"> | |
| <arg direction="in" type="b" name="make_sad"/> | |
| <arg direction="in" type="b" name="make_happy"/> | |
| </method> | |
| <!-- | |
| Jumped: | |
| @height: Height, in meters, that the animal jumped. | |
| @since: 2.30 | |
| Emitted when the animal decides to jump. | |
| --> | |
| <signal name="Jumped"> | |
| <arg type="d" name="height"/> | |
| </signal> | |
| <!-- | |
| Foo: | |
| Property with no <quote>since</quote> annotation (should inherit the 2.30 from its containing interface). | |
| --> | |
| <property name="Foo" type="s" access="read"/> | |
| <!-- | |
| Bar: | |
| @since: 2.36 | |
| Property with a later <quote>since</quote> annotation. | |
| --> | |
| <property name="Bar" type="s" access="read"/> | |
| </interface> | |
| <!-- org.gtk.GDBus.Example.ObjectManager.Cat: | |
| @short_description: More example docs generated by gdbus-codegen | |
| This D-Bus interface is used to describe a cat. Right now there | |
| are no properties, methods or signals associated with this | |
| interface so it is essentially a <ulink | |
| url="http://en.wikipedia.org/wiki/Marker_interface_pattern">Marker | |
| Interface</ulink>. | |
| Note that D-Bus objects implementing this interface also | |
| implement the #org.gtk.GDBus.Example.ObjectManager.Animal | |
| interface. | |
| --> | |
| <interface name="org.gtk.GDBus.Example.ObjectManager.Cat"> | |
| </interface> | |
| </node> | |
| ``` | |
| If this XML is processed like this | |
| ``` | |
| gdbus-codegen --interface-prefix org.gtk.GDBus.Example.ObjectManager. \ | |
| --generate-c-code generated-code \ | |
| --c-namespace Example \ | |
| --c-generate-object-manager \ | |
| --generate-docbook generated-docs \ | |
| gdbus-example-objectmanager.xml | |
| ``` | |
| then two files generated-code.h and generated-code.c are generated. | |
| Additionally, two XML files | |
| generated-docs-org.gtk.GDBus.Example.ObjectManager.Animal and | |
| generated-docs-org.gtk.GDBus.Example.ObjectManager.Cat with Docbook XML are | |
| generated. | |
| While the contents of `generated-code.h` and `generated-code.c` are best described by the `gdbus-codegen` manual page, here's a brief example of how this generated code can be used: | |
| ```c | |
| #include "gdbus-object-manager-example/objectmanager-gen.h" | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| static GDBusObjectManagerServer *manager = NULL; | |
| static gboolean | |
| on_animal_poke (ExampleAnimal *animal, | |
| GDBusMethodInvocation *invocation, | |
| gboolean make_sad, | |
| gboolean make_happy, | |
| gpointer user_data) | |
| { | |
| if ((make_sad && make_happy) || (!make_sad && !make_happy)) | |
| { | |
| g_dbus_method_invocation_return_dbus_error (invocation, | |
| "org.gtk.GDBus.Examples.ObjectManager.Error.Failed", | |
| "Exactly one of make_sad or make_happy must be TRUE"); | |
| goto out; | |
| } | |
| if (make_sad) | |
| { | |
| if (g_strcmp0 (example_animal_get_mood (animal), "Sad") == 0) | |
| { | |
| g_dbus_method_invocation_return_dbus_error (invocation, | |
| "org.gtk.GDBus.Examples.ObjectManager.Error.SadAnimalIsSad", | |
| "Sad animal is already sad"); | |
| goto out; | |
| } | |
| example_animal_set_mood (animal, "Sad"); | |
| example_animal_complete_poke (animal, invocation); | |
| goto out; | |
| } | |
| if (make_happy) | |
| { | |
| if (g_strcmp0 (example_animal_get_mood (animal), "Happy") == 0) | |
| { | |
| g_dbus_method_invocation_return_dbus_error (invocation, | |
| "org.gtk.GDBus.Examples.ObjectManager.Error.HappyAnimalIsHappy", | |
| "Happy animal is already happy"); | |
| goto out; | |
| } | |
| example_animal_set_mood (animal, "Happy"); | |
| example_animal_complete_poke (animal, invocation); | |
| goto out; | |
| } | |
| g_assert_not_reached (); | |
| out: | |
| return G_DBUS_METHOD_INVOCATION_HANDLED; | |
| } | |
| static void | |
| on_bus_acquired (GDBusConnection *connection, | |
| const gchar *name, | |
| gpointer user_data) | |
| { | |
| ExampleObjectSkeleton *object; | |
| guint n; | |
| g_print ("Acquired a message bus connection\n"); | |
| /* Create a new org.freedesktop.DBus.ObjectManager rooted at /example/Animals */ | |
| manager = g_dbus_object_manager_server_new ("/example/Animals"); | |
| for (n = 0; n < 10; n++) | |
| { | |
| gchar *s; | |
| ExampleAnimal *animal; | |
| /* Create a new D-Bus object at the path /example/Animals/N where N is 000..009 */ | |
| s = g_strdup_printf ("/example/Animals/%03d", n); | |
| object = example_object_skeleton_new (s); | |
| g_free (s); | |
| /* Make the newly created object export the interface | |
| * org.gtk.GDBus.Example.ObjectManager.Animal (note | |
| * that @object takes its own reference to @animal). | |
| */ | |
| animal = example_animal_skeleton_new (); | |
| example_animal_set_mood (animal, "Happy"); | |
| example_object_skeleton_set_animal (object, animal); | |
| g_object_unref (animal); | |
| /* Cats are odd animals - so some of our objects implement the | |
| * org.gtk.GDBus.Example.ObjectManager.Cat interface in addition | |
| * to the .Animal interface | |
| */ | |
| if (n % 2 == 1) | |
| { | |
| ExampleCat *cat; | |
| cat = example_cat_skeleton_new (); | |
| example_object_skeleton_set_cat (object, cat); | |
| g_object_unref (cat); | |
| } | |
| /* Handle Poke() D-Bus method invocations on the .Animal interface */ | |
| g_signal_connect (animal, | |
| "handle-poke", | |
| G_CALLBACK (on_animal_poke), | |
| NULL); /* user_data */ | |
| /* Export the object (@manager takes its own reference to @object) */ | |
| g_dbus_object_manager_server_export (manager, G_DBUS_OBJECT_SKELETON (object)); | |
| g_object_unref (object); | |
| } | |
| /* Export all objects */ | |
| g_dbus_object_manager_server_set_connection (manager, connection); | |
| } | |
| static void | |
| on_name_acquired (GDBusConnection *connection, | |
| const gchar *name, | |
| gpointer user_data) | |
| { | |
| g_print ("Acquired the name %s\n", name); | |
| } | |
| static void | |
| on_name_lost (GDBusConnection *connection, | |
| const gchar *name, | |
| gpointer user_data) | |
| { | |
| g_print ("Lost the name %s\n", name); | |
| } | |
| gint | |
| main (gint argc, gchar *argv[]) | |
| { | |
| GMainLoop *loop; | |
| guint id; | |
| loop = g_main_loop_new (NULL, FALSE); | |
| id = g_bus_own_name (G_BUS_TYPE_SESSION, | |
| "org.gtk.GDBus.Examples.ObjectManager", | |
| G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | | |
| G_BUS_NAME_OWNER_FLAGS_REPLACE, | |
| on_bus_acquired, | |
| on_name_acquired, | |
| on_name_lost, | |
| loop, | |
| NULL); | |
| g_main_loop_run (loop); | |
| g_bus_unown_name (id); | |
| g_main_loop_unref (loop); | |
| return 0; | |
| } | |
| ``` | |
| This, on the other hand, is a client-side application using generated code: | |
| ```c | |
| #include "gdbus-object-manager-example/objectmanager-gen.h" | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| static void | |
| print_objects (GDBusObjectManager *manager) | |
| { | |
| GList *objects; | |
| GList *l; | |
| g_print ("Object manager at %s\n", g_dbus_object_manager_get_object_path (manager)); | |
| objects = g_dbus_object_manager_get_objects (manager); | |
| for (l = objects; l != NULL; l = l->next) | |
| { | |
| ExampleObject *object = EXAMPLE_OBJECT (l->data); | |
| GList *interfaces; | |
| GList *ll; | |
| g_print (" - Object at %s\n", g_dbus_object_get_object_path (G_DBUS_OBJECT (object))); | |
| interfaces = g_dbus_object_get_interfaces (G_DBUS_OBJECT (object)); | |
| for (ll = interfaces; ll != NULL; ll = ll->next) | |
| { | |
| GDBusInterface *interface = G_DBUS_INTERFACE (ll->data); | |
| g_print (" - Interface %s\n", g_dbus_interface_get_info (interface)->name); | |
| /* Note that @interface is really a GDBusProxy instance - and additionally also | |
| * an ExampleAnimal or ExampleCat instance - either of these can be used to | |
| * invoke methods on the remote object. For example, the generated function | |
| * | |
| * void example_animal_call_poke_sync (ExampleAnimal *proxy, | |
| * gboolean make_sad, | |
| * gboolean make_happy, | |
| * GCancellable *cancellable, | |
| * GError **error); | |
| * | |
| * can be used to call the Poke() D-Bus method on the .Animal interface. | |
| * Additionally, the generated function | |
| * | |
| * const gchar *example_animal_get_mood (ExampleAnimal *object); | |
| * | |
| * can be used to get the value of the :Mood property. | |
| */ | |
| } | |
| g_list_free_full (interfaces, g_object_unref); | |
| } | |
| g_list_free_full (objects, g_object_unref); | |
| } | |
| static void | |
| on_object_added (GDBusObjectManager *manager, | |
| GDBusObject *object, | |
| gpointer user_data) | |
| { | |
| gchar *owner; | |
| owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (manager)); | |
| g_print ("Added object at %s (owner %s)\n", g_dbus_object_get_object_path (object), owner); | |
| g_free (owner); | |
| } | |
| static void | |
| on_object_removed (GDBusObjectManager *manager, | |
| GDBusObject *object, | |
| gpointer user_data) | |
| { | |
| gchar *owner; | |
| owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (manager)); | |
| g_print ("Removed object at %s (owner %s)\n", g_dbus_object_get_object_path (object), owner); | |
| g_free (owner); | |
| } | |
| static void | |
| on_notify_name_owner (GObject *object, | |
| GParamSpec *pspec, | |
| gpointer user_data) | |
| { | |
| GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (object); | |
| gchar *name_owner; | |
| name_owner = g_dbus_object_manager_client_get_name_owner (manager); | |
| g_print ("name-owner: %s\n", name_owner); | |
| g_free (name_owner); | |
| } | |
| static void | |
| on_interface_proxy_properties_changed (GDBusObjectManagerClient *manager, | |
| GDBusObjectProxy *object_proxy, | |
| GDBusProxy *interface_proxy, | |
| GVariant *changed_properties, | |
| const gchar *const *invalidated_properties, | |
| gpointer user_data) | |
| { | |
| GVariantIter iter; | |
| const gchar *key; | |
| GVariant *value; | |
| gchar *s; | |
| g_print ("Properties Changed on %s:\n", g_dbus_object_get_object_path (G_DBUS_OBJECT (object_proxy))); | |
| g_variant_iter_init (&iter, changed_properties); | |
| while (g_variant_iter_next (&iter, "{&sv}", &key, &value)) | |
| { | |
| s = g_variant_print (value, TRUE); | |
| g_print (" %s -> %s\n", key, s); | |
| g_variant_unref (value); | |
| g_free (s); | |
| } | |
| } | |
| gint | |
| main (gint argc, gchar *argv[]) | |
| { | |
| GDBusObjectManager *manager; | |
| GMainLoop *loop; | |
| GError *error; | |
| gchar *name_owner; | |
| manager = NULL; | |
| loop = NULL; | |
| loop = g_main_loop_new (NULL, FALSE); | |
| error = NULL; | |
| manager = example_object_manager_client_new_for_bus_sync (G_BUS_TYPE_SESSION, | |
| G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, | |
| "org.gtk.GDBus.Examples.ObjectManager", | |
| "/example/Animals", | |
| NULL, /* GCancellable */ | |
| &error); | |
| if (manager == NULL) | |
| { | |
| g_printerr ("Error getting object manager client: %s", error->message); | |
| g_error_free (error); | |
| goto out; | |
| } | |
| name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (manager)); | |
| g_print ("name-owner: %s\n", name_owner); | |
| g_free (name_owner); | |
| print_objects (manager); | |
| g_signal_connect (manager, | |
| "notify::name-owner", | |
| G_CALLBACK (on_notify_name_owner), | |
| NULL); | |
| g_signal_connect (manager, | |
| "object-added", | |
| G_CALLBACK (on_object_added), | |
| NULL); | |
| g_signal_connect (manager, | |
| "object-removed", | |
| G_CALLBACK (on_object_removed), | |
| NULL); | |
| g_signal_connect (manager, | |
| "interface-proxy-properties-changed", | |
| G_CALLBACK (on_interface_proxy_properties_changed), | |
| NULL); | |
| g_main_loop_run (loop); | |
| out: | |
| if (manager != NULL) | |
| g_object_unref (manager); | |
| if (loop != NULL) | |
| g_main_loop_unref (loop); | |
| return 0; | |
| } | |
| ``` |