Add some more cases to the app-id unit tests
[glib.git] / gio / gmenuexporter.c
blob68a59aec1e05653dc2ef7cd98dd72de6fc4f63d1
1 /*
2 * Copyright © 2011 Canonical Ltd.
4 * This library is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * licence, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 * Author: Ryan Lortie <desrt@desrt.ca>
20 #include "config.h"
22 #include "gmenuexporter.h"
24 #include "gdbusmethodinvocation.h"
25 #include "gdbusintrospection.h"
26 #include "gdbusnamewatching.h"
27 #include "gdbuserror.h"
29 /**
30 * SECTION:gmenuexporter
31 * @title: GMenuModel exporter
32 * @short_description: Export GMenuModels on D-Bus
33 * @include: gio/gio.h
34 * @see_also: #GMenuModel, #GDBusMenuModel
36 * These functions support exporting a #GMenuModel on D-Bus.
37 * The D-Bus interface that is used is a private implementation
38 * detail.
40 * To access an exported #GMenuModel remotely, use
41 * g_dbus_menu_model_get() to obtain a #GDBusMenuModel.
44 /* {{{1 D-Bus Interface description */
46 /* For documentation of this interface, see
47 * https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI
50 static GDBusInterfaceInfo *
51 org_gtk_Menus_get_interface (void)
53 static GDBusInterfaceInfo *interface_info;
55 if (interface_info == NULL)
57 GError *error = NULL;
58 GDBusNodeInfo *info;
60 info = g_dbus_node_info_new_for_xml ("<node>"
61 " <interface name='org.gtk.Menus'>"
62 " <method name='Start'>"
63 " <arg type='au' name='groups' direction='in'/>"
64 " <arg type='a(uuaa{sv})' name='content' direction='out'/>"
65 " </method>"
66 " <method name='End'>"
67 " <arg type='au' name='groups' direction='in'/>"
68 " </method>"
69 " <signal name='Changed'>"
70 " arg type='a(uuuuaa{sv})' name='changes'/>"
71 " </signal>"
72 " </interface>"
73 "</node>", &error);
74 if (info == NULL)
75 g_error ("%s\n", error->message);
76 interface_info = g_dbus_node_info_lookup_interface (info, "org.gtk.Menus");
77 g_assert (interface_info != NULL);
78 g_dbus_interface_info_ref (interface_info);
79 g_dbus_node_info_unref (info);
82 return interface_info;
85 /* {{{1 Forward declarations */
86 typedef struct _GMenuExporterMenu GMenuExporterMenu;
87 typedef struct _GMenuExporterLink GMenuExporterLink;
88 typedef struct _GMenuExporterGroup GMenuExporterGroup;
89 typedef struct _GMenuExporterRemote GMenuExporterRemote;
90 typedef struct _GMenuExporterWatch GMenuExporterWatch;
91 typedef struct _GMenuExporter GMenuExporter;
93 static gboolean g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group);
94 static guint g_menu_exporter_group_get_id (GMenuExporterGroup *group);
95 static GMenuExporter * g_menu_exporter_group_get_exporter (GMenuExporterGroup *group);
96 static GMenuExporterMenu * g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
97 GMenuModel *model);
98 static void g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
99 guint id);
101 static GMenuExporterGroup * g_menu_exporter_create_group (GMenuExporter *exporter);
102 static GMenuExporterGroup * g_menu_exporter_lookup_group (GMenuExporter *exporter,
103 guint group_id);
104 static void g_menu_exporter_report (GMenuExporter *exporter,
105 GVariant *report);
106 static void g_menu_exporter_remove_group (GMenuExporter *exporter,
107 guint id);
109 /* {{{1 GMenuExporterLink, GMenuExporterMenu */
111 struct _GMenuExporterMenu
113 GMenuExporterGroup *group;
114 guint id;
116 GMenuModel *model;
117 gulong handler_id;
118 GSequence *item_links;
121 struct _GMenuExporterLink
123 gchar *name;
124 GMenuExporterMenu *menu;
125 GMenuExporterLink *next;
128 static void
129 g_menu_exporter_menu_free (GMenuExporterMenu *menu)
131 g_menu_exporter_group_remove_menu (menu->group, menu->id);
133 if (menu->handler_id != 0)
134 g_signal_handler_disconnect (menu->model, menu->handler_id);
136 if (menu->item_links != NULL)
137 g_sequence_free (menu->item_links);
139 g_object_unref (menu->model);
141 g_slice_free (GMenuExporterMenu, menu);
144 static void
145 g_menu_exporter_link_free (gpointer data)
147 GMenuExporterLink *link = data;
149 while (link != NULL)
151 GMenuExporterLink *tmp = link;
152 link = tmp->next;
154 g_menu_exporter_menu_free (tmp->menu);
155 g_free (tmp->name);
157 g_slice_free (GMenuExporterLink, tmp);
161 static GMenuExporterLink *
162 g_menu_exporter_menu_create_links (GMenuExporterMenu *menu,
163 gint position)
165 GMenuExporterLink *list = NULL;
166 GMenuLinkIter *iter;
167 const char *name;
168 GMenuModel *model;
170 iter = g_menu_model_iterate_item_links (menu->model, position);
172 while (g_menu_link_iter_get_next (iter, &name, &model))
174 GMenuExporterGroup *group;
175 GMenuExporterLink *tmp;
177 /* keep sections in the same group, but create new groups
178 * otherwise
180 if (!g_str_equal (name, "section"))
181 group = g_menu_exporter_create_group (g_menu_exporter_group_get_exporter (menu->group));
182 else
183 group = menu->group;
185 tmp = g_slice_new (GMenuExporterLink);
186 tmp->name = g_strconcat (":", name, NULL);
187 tmp->menu = g_menu_exporter_group_add_menu (group, model);
188 tmp->next = list;
189 list = tmp;
191 g_object_unref (model);
194 g_object_unref (iter);
196 return list;
199 static GVariant *
200 g_menu_exporter_menu_describe_item (GMenuExporterMenu *menu,
201 gint position)
203 GMenuAttributeIter *attr_iter;
204 GVariantBuilder builder;
205 GSequenceIter *iter;
206 GMenuExporterLink *link;
207 const char *name;
208 GVariant *value;
210 g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
212 attr_iter = g_menu_model_iterate_item_attributes (menu->model, position);
213 while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
215 g_variant_builder_add (&builder, "{sv}", name, value);
216 g_variant_unref (value);
218 g_object_unref (attr_iter);
220 iter = g_sequence_get_iter_at_pos (menu->item_links, position);
221 for (link = g_sequence_get (iter); link; link = link->next)
222 g_variant_builder_add (&builder, "{sv}", link->name,
223 g_variant_new ("(uu)", g_menu_exporter_group_get_id (link->menu->group), link->menu->id));
225 return g_variant_builder_end (&builder);
228 static GVariant *
229 g_menu_exporter_menu_list (GMenuExporterMenu *menu)
231 GVariantBuilder builder;
232 gint i, n;
234 g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
236 n = g_sequence_get_length (menu->item_links);
237 for (i = 0; i < n; i++)
238 g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
240 return g_variant_builder_end (&builder);
243 static void
244 g_menu_exporter_menu_items_changed (GMenuModel *model,
245 gint position,
246 gint removed,
247 gint added,
248 gpointer user_data)
250 GMenuExporterMenu *menu = user_data;
251 GSequenceIter *point;
252 gint i;
254 g_assert (menu->model == model);
255 g_assert (menu->item_links != NULL);
256 g_assert (position + removed <= g_sequence_get_length (menu->item_links));
258 point = g_sequence_get_iter_at_pos (menu->item_links, position + removed);
259 g_sequence_remove_range (g_sequence_get_iter_at_pos (menu->item_links, position), point);
261 for (i = position; i < position + added; i++)
262 g_sequence_insert_before (point, g_menu_exporter_menu_create_links (menu, i));
264 if (g_menu_exporter_group_is_subscribed (menu->group))
266 GVariantBuilder builder;
268 g_variant_builder_init (&builder, G_VARIANT_TYPE ("(uuuuaa{sv})"));
269 g_variant_builder_add (&builder, "u", g_menu_exporter_group_get_id (menu->group));
270 g_variant_builder_add (&builder, "u", menu->id);
271 g_variant_builder_add (&builder, "u", position);
272 g_variant_builder_add (&builder, "u", removed);
274 g_variant_builder_open (&builder, G_VARIANT_TYPE ("aa{sv}"));
275 for (i = position; i < position + added; i++)
276 g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
277 g_variant_builder_close (&builder);
279 g_menu_exporter_report (g_menu_exporter_group_get_exporter (menu->group), g_variant_builder_end (&builder));
283 static void
284 g_menu_exporter_menu_prepare (GMenuExporterMenu *menu)
286 gint n_items;
288 g_assert (menu->item_links == NULL);
290 if (g_menu_model_is_mutable (menu->model))
291 menu->handler_id = g_signal_connect (menu->model, "items-changed",
292 G_CALLBACK (g_menu_exporter_menu_items_changed), menu);
294 menu->item_links = g_sequence_new (g_menu_exporter_link_free);
296 n_items = g_menu_model_get_n_items (menu->model);
297 if (n_items)
298 g_menu_exporter_menu_items_changed (menu->model, 0, 0, n_items, menu);
301 static GMenuExporterMenu *
302 g_menu_exporter_menu_new (GMenuExporterGroup *group,
303 guint id,
304 GMenuModel *model)
306 GMenuExporterMenu *menu;
308 menu = g_slice_new0 (GMenuExporterMenu);
309 menu->group = group;
310 menu->id = id;
311 menu->model = g_object_ref (model);
313 return menu;
316 /* {{{1 GMenuExporterGroup */
318 struct _GMenuExporterGroup
320 GMenuExporter *exporter;
321 guint id;
323 GHashTable *menus;
324 guint next_menu_id;
325 gboolean prepared;
327 gint subscribed;
330 static void
331 g_menu_exporter_group_check_if_useless (GMenuExporterGroup *group)
333 if (g_hash_table_size (group->menus) == 0 && group->subscribed == 0)
335 g_menu_exporter_remove_group (group->exporter, group->id);
337 g_hash_table_unref (group->menus);
339 g_slice_free (GMenuExporterGroup, group);
343 static void
344 g_menu_exporter_group_subscribe (GMenuExporterGroup *group,
345 GVariantBuilder *builder)
347 GHashTableIter iter;
348 gpointer key, val;
350 if (!group->prepared)
352 GMenuExporterMenu *menu;
354 /* set this first, so that any menus created during the
355 * preparation of the first menu also end up in the prepared
356 * state.
357 * */
358 group->prepared = TRUE;
360 menu = g_hash_table_lookup (group->menus, 0);
362 /* If the group was created by a subscription and does not yet
363 * exist, it won't have a root menu...
365 * That menu will be prepared if it is ever added (due to
366 * group->prepared == TRUE).
368 if (menu)
369 g_menu_exporter_menu_prepare (menu);
372 group->subscribed++;
374 g_hash_table_iter_init (&iter, group->menus);
375 while (g_hash_table_iter_next (&iter, &key, &val))
377 guint id = GPOINTER_TO_INT (key);
378 GMenuExporterMenu *menu = val;
380 if (!g_sequence_is_empty (menu->item_links))
382 g_variant_builder_open (builder, G_VARIANT_TYPE ("(uuaa{sv})"));
383 g_variant_builder_add (builder, "u", group->id);
384 g_variant_builder_add (builder, "u", id);
385 g_variant_builder_add_value (builder, g_menu_exporter_menu_list (menu));
386 g_variant_builder_close (builder);
391 static void
392 g_menu_exporter_group_unsubscribe (GMenuExporterGroup *group,
393 gint count)
395 g_assert (group->subscribed >= count);
397 group->subscribed -= count;
399 g_menu_exporter_group_check_if_useless (group);
402 static GMenuExporter *
403 g_menu_exporter_group_get_exporter (GMenuExporterGroup *group)
405 return group->exporter;
408 static gboolean
409 g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group)
411 return group->subscribed > 0;
414 static guint
415 g_menu_exporter_group_get_id (GMenuExporterGroup *group)
417 return group->id;
420 static void
421 g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
422 guint id)
424 g_hash_table_remove (group->menus, GINT_TO_POINTER (id));
426 g_menu_exporter_group_check_if_useless (group);
429 static GMenuExporterMenu *
430 g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
431 GMenuModel *model)
433 GMenuExporterMenu *menu;
434 guint id;
436 id = group->next_menu_id++;
437 menu = g_menu_exporter_menu_new (group, id, model);
438 g_hash_table_insert (group->menus, GINT_TO_POINTER (id), menu);
440 if (group->prepared)
441 g_menu_exporter_menu_prepare (menu);
443 return menu;
446 static GMenuExporterGroup *
447 g_menu_exporter_group_new (GMenuExporter *exporter,
448 guint id)
450 GMenuExporterGroup *group;
452 group = g_slice_new0 (GMenuExporterGroup);
453 group->menus = g_hash_table_new (NULL, NULL);
454 group->exporter = exporter;
455 group->id = id;
457 return group;
460 /* {{{1 GMenuExporterRemote */
462 struct _GMenuExporterRemote
464 GMenuExporter *exporter;
465 GHashTable *watches;
466 guint watch_id;
469 static void
470 g_menu_exporter_remote_subscribe (GMenuExporterRemote *remote,
471 guint group_id,
472 GVariantBuilder *builder)
474 GMenuExporterGroup *group;
475 guint count;
477 count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
478 g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count + 1));
480 /* Group will be created (as empty/unsubscribed if it does not exist) */
481 group = g_menu_exporter_lookup_group (remote->exporter, group_id);
482 g_menu_exporter_group_subscribe (group, builder);
485 static void
486 g_menu_exporter_remote_unsubscribe (GMenuExporterRemote *remote,
487 guint group_id)
489 GMenuExporterGroup *group;
490 guint count;
492 count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
494 if (count == 0)
495 return;
497 if (count != 1)
498 g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count - 1));
499 else
500 g_hash_table_remove (remote->watches, GINT_TO_POINTER (group_id));
502 group = g_menu_exporter_lookup_group (remote->exporter, group_id);
503 g_menu_exporter_group_unsubscribe (group, 1);
506 static gboolean
507 g_menu_exporter_remote_has_subscriptions (GMenuExporterRemote *remote)
509 return g_hash_table_size (remote->watches) != 0;
512 static void
513 g_menu_exporter_remote_free (gpointer data)
515 GMenuExporterRemote *remote = data;
516 GHashTableIter iter;
517 gpointer key, val;
519 g_hash_table_iter_init (&iter, remote->watches);
520 while (g_hash_table_iter_next (&iter, &key, &val))
522 GMenuExporterGroup *group;
524 group = g_menu_exporter_lookup_group (remote->exporter, GPOINTER_TO_INT (key));
525 g_menu_exporter_group_unsubscribe (group, GPOINTER_TO_INT (val));
528 g_bus_unwatch_name (remote->watch_id);
529 g_hash_table_unref (remote->watches);
531 g_slice_free (GMenuExporterRemote, remote);
534 static GMenuExporterRemote *
535 g_menu_exporter_remote_new (GMenuExporter *exporter,
536 guint watch_id)
538 GMenuExporterRemote *remote;
540 remote = g_slice_new0 (GMenuExporterRemote);
541 remote->exporter = exporter;
542 remote->watches = g_hash_table_new (NULL, NULL);
543 remote->watch_id = watch_id;
545 return remote;
548 /* {{{1 GMenuExporter */
550 struct _GMenuExporter
552 GDBusConnection *connection;
553 gchar *object_path;
554 guint registration_id;
555 GHashTable *groups;
556 guint next_group_id;
558 GMenuExporterMenu *root;
559 GHashTable *remotes;
562 static void
563 g_menu_exporter_name_vanished (GDBusConnection *connection,
564 const gchar *name,
565 gpointer user_data)
567 GMenuExporter *exporter = user_data;
569 /* connection == NULL when we get called because the connection closed */
570 g_assert (exporter->connection == connection || connection == NULL);
572 g_hash_table_remove (exporter->remotes, name);
575 static GVariant *
576 g_menu_exporter_subscribe (GMenuExporter *exporter,
577 const gchar *sender,
578 GVariant *group_ids)
580 GMenuExporterRemote *remote;
581 GVariantBuilder builder;
582 GVariantIter iter;
583 guint32 id;
585 remote = g_hash_table_lookup (exporter->remotes, sender);
587 if (remote == NULL)
589 guint watch_id;
591 watch_id = g_bus_watch_name_on_connection (exporter->connection, sender, G_BUS_NAME_WATCHER_FLAGS_NONE,
592 NULL, g_menu_exporter_name_vanished, exporter, NULL);
593 remote = g_menu_exporter_remote_new (exporter, watch_id);
594 g_hash_table_insert (exporter->remotes, g_strdup (sender), remote);
597 g_variant_builder_init (&builder, G_VARIANT_TYPE ("(a(uuaa{sv}))"));
599 g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(uuaa{sv})"));
601 g_variant_iter_init (&iter, group_ids);
602 while (g_variant_iter_next (&iter, "u", &id))
603 g_menu_exporter_remote_subscribe (remote, id, &builder);
605 g_variant_builder_close (&builder);
607 return g_variant_builder_end (&builder);
610 static void
611 g_menu_exporter_unsubscribe (GMenuExporter *exporter,
612 const gchar *sender,
613 GVariant *group_ids)
615 GMenuExporterRemote *remote;
616 GVariantIter iter;
617 guint32 id;
619 remote = g_hash_table_lookup (exporter->remotes, sender);
621 if (remote == NULL)
622 return;
624 g_variant_iter_init (&iter, group_ids);
625 while (g_variant_iter_next (&iter, "u", &id))
626 g_menu_exporter_remote_unsubscribe (remote, id);
628 if (!g_menu_exporter_remote_has_subscriptions (remote))
629 g_hash_table_remove (exporter->remotes, sender);
632 static void
633 g_menu_exporter_report (GMenuExporter *exporter,
634 GVariant *report)
636 GVariantBuilder builder;
638 g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
639 g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY);
640 g_variant_builder_add_value (&builder, report);
641 g_variant_builder_close (&builder);
643 g_dbus_connection_emit_signal (exporter->connection,
644 NULL,
645 exporter->object_path,
646 "org.gtk.Menus", "Changed",
647 g_variant_builder_end (&builder),
648 NULL);
651 static void
652 g_menu_exporter_remove_group (GMenuExporter *exporter,
653 guint id)
655 g_hash_table_remove (exporter->groups, GINT_TO_POINTER (id));
658 static GMenuExporterGroup *
659 g_menu_exporter_lookup_group (GMenuExporter *exporter,
660 guint group_id)
662 GMenuExporterGroup *group;
664 group = g_hash_table_lookup (exporter->groups, GINT_TO_POINTER (group_id));
666 if (group == NULL)
668 group = g_menu_exporter_group_new (exporter, group_id);
669 g_hash_table_insert (exporter->groups, GINT_TO_POINTER (group_id), group);
672 return group;
675 static GMenuExporterGroup *
676 g_menu_exporter_create_group (GMenuExporter *exporter)
678 GMenuExporterGroup *group;
679 guint id;
681 id = exporter->next_group_id++;
682 group = g_menu_exporter_group_new (exporter, id);
683 g_hash_table_insert (exporter->groups, GINT_TO_POINTER (id), group);
685 return group;
688 static void
689 g_menu_exporter_free (gpointer user_data)
691 GMenuExporter *exporter = user_data;
693 g_menu_exporter_menu_free (exporter->root);
694 g_hash_table_unref (exporter->remotes);
695 g_hash_table_unref (exporter->groups);
696 g_object_unref (exporter->connection);
697 g_free (exporter->object_path);
699 g_slice_free (GMenuExporter, exporter);
702 static void
703 g_menu_exporter_method_call (GDBusConnection *connection,
704 const gchar *sender,
705 const gchar *object_path,
706 const gchar *interface_name,
707 const gchar *method_name,
708 GVariant *parameters,
709 GDBusMethodInvocation *invocation,
710 gpointer user_data)
712 GMenuExporter *exporter = user_data;
713 GVariant *group_ids;
715 group_ids = g_variant_get_child_value (parameters, 0);
717 if (g_str_equal (method_name, "Start"))
718 g_dbus_method_invocation_return_value (invocation, g_menu_exporter_subscribe (exporter, sender, group_ids));
720 else if (g_str_equal (method_name, "End"))
722 g_menu_exporter_unsubscribe (exporter, sender, group_ids);
723 g_dbus_method_invocation_return_value (invocation, NULL);
726 else
727 g_assert_not_reached ();
729 g_variant_unref (group_ids);
732 /* {{{1 Public API */
735 * g_dbus_connection_export_menu_model:
736 * @connection: a #GDBusConnection
737 * @object_path: a D-Bus object path
738 * @menu: a #GMenuModel
739 * @error: return location for an error, or %NULL
741 * Exports @menu on @connection at @object_path.
743 * The implemented D-Bus API should be considered private.
744 * It is subject to change in the future.
746 * An object path can only have one menu model exported on it. If this
747 * constraint is violated, the export will fail and 0 will be
748 * returned (with @error set accordingly).
750 * You can unexport the menu model using
751 * g_dbus_connection_unexport_menu_model() with the return value of
752 * this function.
754 * Returns: the ID of the export (never zero), or 0 in case of failure
756 * Since: 2.32
758 guint
759 g_dbus_connection_export_menu_model (GDBusConnection *connection,
760 const gchar *object_path,
761 GMenuModel *menu,
762 GError **error)
764 const GDBusInterfaceVTable vtable = {
765 g_menu_exporter_method_call,
767 GMenuExporter *exporter;
768 guint id;
770 exporter = g_slice_new0 (GMenuExporter);
772 id = g_dbus_connection_register_object (connection, object_path, org_gtk_Menus_get_interface (),
773 &vtable, exporter, g_menu_exporter_free, error);
775 if (id == 0)
777 g_slice_free (GMenuExporter, exporter);
778 return 0;
781 exporter->connection = g_object_ref (connection);
782 exporter->object_path = g_strdup (object_path);
783 exporter->groups = g_hash_table_new (NULL, NULL);
784 exporter->remotes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_menu_exporter_remote_free);
785 exporter->root = g_menu_exporter_group_add_menu (g_menu_exporter_create_group (exporter), menu);
787 return id;
791 * g_dbus_connection_unexport_menu_model:
792 * @connection: a #GDBusConnection
793 * @export_id: the ID from g_dbus_connection_export_menu_model()
795 * Reverses the effect of a previous call to
796 * g_dbus_connection_export_menu_model().
798 * It is an error to call this function with an ID that wasn't returned
799 * from g_dbus_connection_export_menu_model() or to call it with the
800 * same ID more than once.
802 * Since: 2.32
804 void
805 g_dbus_connection_unexport_menu_model (GDBusConnection *connection,
806 guint export_id)
808 g_dbus_connection_unregister_object (connection, export_id);
811 /* {{{1 Epilogue */
812 /* vim:set foldmethod=marker: */