Add includes to all gio docs
[platform/upstream/glib.git] / gio / gmenuexporter.c
1 /*
2  * Copyright © 2011 Canonical Ltd.
3  *
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.
8  *
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.
13  *
14  *  You should have received a copy of the GNU Lesser General Public
15  *  License along with this library; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
17  *  USA.
18  *
19  * Author: Ryan Lortie <desrt@desrt.ca>
20  */
21
22 #include "config.h"
23
24 #include "gmenuexporter.h"
25
26 #include "gdbusmethodinvocation.h"
27 #include "gdbusintrospection.h"
28 #include "gdbusnamewatching.h"
29 #include "gdbuserror.h"
30
31 /**
32  * SECTION:gmenuexporter
33  * @title: GMenuModel exporter
34  * @short_description: Export GMenuModels on D-Bus
35  * @include: gio/gio.h
36  * @see_also: #GMenuModel, #GDBusMenuModel
37  *
38  * These functions support exporting a #GMenuModel on D-Bus.
39  * The D-Bus interface that is used is a private implementation
40  * detail.
41  *
42  * To access an exported #GMenuModel remotely, use
43  * g_dbus_menu_model_get() to obtain a #GDBusMenuModel.
44  */
45
46 /* {{{1 D-Bus Interface description */
47
48 /* For documentation of this interface, see
49  * http://live.gnome.org/GTK+/GApplication-dbus-apis
50  */
51
52 static GDBusInterfaceInfo *
53 org_gtk_Menus_get_interface (void)
54 {
55   static GDBusInterfaceInfo *interface_info;
56
57   if (interface_info == NULL)
58     {
59       GError *error = NULL;
60       GDBusNodeInfo *info;
61
62       info = g_dbus_node_info_new_for_xml ("<node>"
63                                            "  <interface name='org.gtk.Menus'>"
64                                            "    <method name='Start'>"
65                                            "      <arg type='au' name='groups' direction='in'/>"
66                                            "      <arg type='a(uuaa{sv})' name='content' direction='out'/>"
67                                            "    </method>"
68                                            "    <method name='End'>"
69                                            "      <arg type='au' name='groups' direction='in'/>"
70                                            "    </method>"
71                                            "    <signal name='Changed'>"
72                                            "      arg type='a(uuuuaa{sv})' name='changes'/>"
73                                            "    </signal>"
74                                            "  </interface>"
75                                            "</node>", &error);
76       if (info == NULL)
77         g_error ("%s\n", error->message);
78       interface_info = g_dbus_node_info_lookup_interface (info, "org.gtk.Menus");
79       g_assert (interface_info != NULL);
80       g_dbus_interface_info_ref (interface_info);
81       g_dbus_node_info_unref (info);
82     }
83
84   return interface_info;
85 }
86
87 /* {{{1 Forward declarations */
88 typedef struct _GMenuExporterMenu                           GMenuExporterMenu;
89 typedef struct _GMenuExporterLink                           GMenuExporterLink;
90 typedef struct _GMenuExporterGroup                          GMenuExporterGroup;
91 typedef struct _GMenuExporterRemote                         GMenuExporterRemote;
92 typedef struct _GMenuExporterWatch                          GMenuExporterWatch;
93 typedef struct _GMenuExporter                               GMenuExporter;
94
95 static gboolean                 g_menu_exporter_group_is_subscribed    (GMenuExporterGroup *group);
96 static guint                    g_menu_exporter_group_get_id           (GMenuExporterGroup *group);
97 static GMenuExporter *          g_menu_exporter_group_get_exporter     (GMenuExporterGroup *group);
98 static GMenuExporterMenu *      g_menu_exporter_group_add_menu         (GMenuExporterGroup *group,
99                                                                         GMenuModel         *model);
100 static void                     g_menu_exporter_group_remove_menu      (GMenuExporterGroup *group,
101                                                                         guint               id);
102
103 static GMenuExporterGroup *     g_menu_exporter_create_group           (GMenuExporter      *exporter);
104 static GMenuExporterGroup *     g_menu_exporter_lookup_group           (GMenuExporter      *exporter,
105                                                                         guint               group_id);
106 static void                     g_menu_exporter_report                 (GMenuExporter      *exporter,
107                                                                         GVariant           *report);
108 static void                     g_menu_exporter_remove_group           (GMenuExporter      *exporter,
109                                                                         guint               id);
110
111 /* {{{1 GMenuExporterLink, GMenuExporterMenu */
112
113 struct _GMenuExporterMenu
114 {
115   GMenuExporterGroup *group;
116   guint               id;
117
118   GMenuModel *model;
119   gulong      handler_id;
120   GSequence  *item_links;
121 };
122
123 struct _GMenuExporterLink
124 {
125   gchar             *name;
126   GMenuExporterMenu *menu;
127   GMenuExporterLink *next;
128 };
129
130 static void
131 g_menu_exporter_menu_free (GMenuExporterMenu *menu)
132 {
133   g_menu_exporter_group_remove_menu (menu->group, menu->id);
134
135   if (menu->handler_id != 0)
136     g_signal_handler_disconnect (menu->model, menu->handler_id);
137
138   if (menu->item_links != NULL)
139     g_sequence_free (menu->item_links);
140
141   g_object_unref (menu->model);
142
143   g_slice_free (GMenuExporterMenu, menu);
144 }
145
146 static void
147 g_menu_exporter_link_free (gpointer data)
148 {
149   GMenuExporterLink *link = data;
150
151   while (link != NULL)
152     {
153       GMenuExporterLink *tmp = link;
154       link = tmp->next;
155
156       g_menu_exporter_menu_free (tmp->menu);
157       g_free (tmp->name);
158
159       g_slice_free (GMenuExporterLink, tmp);
160     }
161 }
162
163 static GMenuExporterLink *
164 g_menu_exporter_menu_create_links (GMenuExporterMenu *menu,
165                                    gint               position)
166 {
167   GMenuExporterLink *list = NULL;
168   GMenuLinkIter *iter;
169   const char *name;
170   GMenuModel *model;
171
172   iter = g_menu_model_iterate_item_links (menu->model, position);
173
174   while (g_menu_link_iter_get_next (iter, &name, &model))
175     {
176       GMenuExporterGroup *group;
177       GMenuExporterLink *tmp;
178
179       /* keep sections in the same group, but create new groups
180        * otherwise
181        */
182       if (!g_str_equal (name, "section"))
183         group = g_menu_exporter_create_group (g_menu_exporter_group_get_exporter (menu->group));
184       else
185         group = menu->group;
186
187       tmp = g_slice_new (GMenuExporterLink);
188       tmp->name = g_strconcat (":", name, NULL);
189       tmp->menu = g_menu_exporter_group_add_menu (group, model);
190       tmp->next = list;
191       list = tmp;
192
193       g_object_unref (model);
194     }
195
196   g_object_unref (iter);
197
198   return list;
199 }
200
201 static GVariant *
202 g_menu_exporter_menu_describe_item (GMenuExporterMenu *menu,
203                                     gint               position)
204 {
205   GMenuAttributeIter *attr_iter;
206   GVariantBuilder builder;
207   GSequenceIter *iter;
208   GMenuExporterLink *link;
209   const char *name;
210   GVariant *value;
211
212   g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
213
214   attr_iter = g_menu_model_iterate_item_attributes (menu->model, position);
215   while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
216     {
217       g_variant_builder_add (&builder, "{sv}", name, value);
218       g_variant_unref (value);
219     }
220   g_object_unref (attr_iter);
221
222   iter = g_sequence_get_iter_at_pos (menu->item_links, position);
223   for (link = g_sequence_get (iter); link; link = link->next)
224     g_variant_builder_add (&builder, "{sv}", link->name,
225                            g_variant_new ("(uu)", g_menu_exporter_group_get_id (link->menu->group), link->menu->id));
226
227   return g_variant_builder_end (&builder);
228 }
229
230 static GVariant *
231 g_menu_exporter_menu_list (GMenuExporterMenu *menu)
232 {
233   GVariantBuilder builder;
234   gint i, n;
235
236   g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
237
238   n = g_sequence_get_length (menu->item_links);
239   for (i = 0; i < n; i++)
240     g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
241
242   return g_variant_builder_end (&builder);
243 }
244
245 static void
246 g_menu_exporter_menu_items_changed (GMenuModel *model,
247                                     gint        position,
248                                     gint        removed,
249                                     gint        added,
250                                     gpointer    user_data)
251 {
252   GMenuExporterMenu *menu = user_data;
253   GSequenceIter *point;
254   gint i;
255
256   g_assert (menu->model == model);
257   g_assert (menu->item_links != NULL);
258   g_assert (position + removed <= g_sequence_get_length (menu->item_links));
259
260   point = g_sequence_get_iter_at_pos (menu->item_links, position + removed);
261   g_sequence_remove_range (g_sequence_get_iter_at_pos (menu->item_links, position), point);
262
263   for (i = position; i < position + added; i++)
264     g_sequence_insert_before (point, g_menu_exporter_menu_create_links (menu, i));
265
266   if (g_menu_exporter_group_is_subscribed (menu->group))
267     {
268       GVariantBuilder builder;
269
270       g_variant_builder_init (&builder, G_VARIANT_TYPE ("(uuuuaa{sv})"));
271       g_variant_builder_add (&builder, "u", g_menu_exporter_group_get_id (menu->group));
272       g_variant_builder_add (&builder, "u", menu->id);
273       g_variant_builder_add (&builder, "u", position);
274       g_variant_builder_add (&builder, "u", removed);
275
276       g_variant_builder_open (&builder, G_VARIANT_TYPE ("aa{sv}"));
277       for (i = position; i < position + added; i++)
278         g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
279       g_variant_builder_close (&builder);
280
281       g_menu_exporter_report (g_menu_exporter_group_get_exporter (menu->group), g_variant_builder_end (&builder));
282     }
283 }
284
285 static void
286 g_menu_exporter_menu_prepare (GMenuExporterMenu *menu)
287 {
288   gint n_items;
289
290   g_assert (menu->item_links == NULL);
291
292   if (g_menu_model_is_mutable (menu->model))
293     menu->handler_id = g_signal_connect (menu->model, "items-changed",
294                                          G_CALLBACK (g_menu_exporter_menu_items_changed), menu);
295
296   menu->item_links = g_sequence_new (g_menu_exporter_link_free);
297
298   n_items = g_menu_model_get_n_items (menu->model);
299   if (n_items)
300     g_menu_exporter_menu_items_changed (menu->model, 0, 0, n_items, menu);
301 }
302
303 static GMenuExporterMenu *
304 g_menu_exporter_menu_new (GMenuExporterGroup *group,
305                           guint               id,
306                           GMenuModel         *model)
307 {
308   GMenuExporterMenu *menu;
309
310   menu = g_slice_new0 (GMenuExporterMenu);
311   menu->group = group;
312   menu->id = id;
313   menu->model = g_object_ref (model);
314
315   return menu;
316 }
317
318 /* {{{1 GMenuExporterGroup */
319
320 struct _GMenuExporterGroup
321 {
322   GMenuExporter *exporter;
323   guint          id;
324
325   GHashTable *menus;
326   guint       next_menu_id;
327   gboolean    prepared;
328
329   gint subscribed;
330 };
331
332 static void
333 g_menu_exporter_group_check_if_useless (GMenuExporterGroup *group)
334 {
335   if (g_hash_table_size (group->menus) == 0 && group->subscribed == 0)
336     {
337       g_menu_exporter_remove_group (group->exporter, group->id);
338
339       g_hash_table_unref (group->menus);
340
341       g_slice_free (GMenuExporterGroup, group);
342     }
343 }
344
345 static void
346 g_menu_exporter_group_subscribe (GMenuExporterGroup *group,
347                                  GVariantBuilder    *builder)
348 {
349   GHashTableIter iter;
350   gpointer key, val;
351
352   if (!group->prepared)
353     {
354       GMenuExporterMenu *menu;
355
356       /* set this first, so that any menus created during the
357        * preparation of the first menu also end up in the prepared
358        * state.
359        * */
360       group->prepared = TRUE;
361
362       menu = g_hash_table_lookup (group->menus, 0);
363
364       /* If the group was created by a subscription and does not yet
365        * exist, it won't have a root menu...
366        *
367        * That menu will be prepared if it is ever added (due to
368        * group->prepared == TRUE).
369        */
370       if (menu)
371         g_menu_exporter_menu_prepare (menu);
372     }
373
374   group->subscribed++;
375
376   g_hash_table_iter_init (&iter, group->menus);
377   while (g_hash_table_iter_next (&iter, &key, &val))
378     {
379       guint id = GPOINTER_TO_INT (key);
380       GMenuExporterMenu *menu = val;
381
382       if (g_sequence_get_length (menu->item_links))
383         {
384           g_variant_builder_open (builder, G_VARIANT_TYPE ("(uuaa{sv})"));
385           g_variant_builder_add (builder, "u", group->id);
386           g_variant_builder_add (builder, "u", id);
387           g_variant_builder_add_value (builder, g_menu_exporter_menu_list (menu));
388           g_variant_builder_close (builder);
389         }
390     }
391 }
392
393 static void
394 g_menu_exporter_group_unsubscribe (GMenuExporterGroup *group,
395                                    gint                count)
396 {
397   g_assert (group->subscribed >= count);
398
399   group->subscribed -= count;
400
401   g_menu_exporter_group_check_if_useless (group);
402 }
403
404 static GMenuExporter *
405 g_menu_exporter_group_get_exporter (GMenuExporterGroup *group)
406 {
407   return group->exporter;
408 }
409
410 static gboolean
411 g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group)
412 {
413   return group->subscribed > 0;
414 }
415
416 static guint
417 g_menu_exporter_group_get_id (GMenuExporterGroup *group)
418 {
419   return group->id;
420 }
421
422 static void
423 g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
424                                    guint               id)
425 {
426   g_hash_table_remove (group->menus, GINT_TO_POINTER (id));
427
428   g_menu_exporter_group_check_if_useless (group);
429 }
430
431 static GMenuExporterMenu *
432 g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
433                                 GMenuModel         *model)
434 {
435   GMenuExporterMenu *menu;
436   guint id;
437
438   id = group->next_menu_id++;
439   menu = g_menu_exporter_menu_new (group, id, model);
440   g_hash_table_insert (group->menus, GINT_TO_POINTER (id), menu);
441
442   if (group->prepared)
443     g_menu_exporter_menu_prepare (menu);
444
445   return menu;
446 }
447
448 static GMenuExporterGroup *
449 g_menu_exporter_group_new (GMenuExporter *exporter,
450                            guint          id)
451 {
452   GMenuExporterGroup *group;
453
454   group = g_slice_new0 (GMenuExporterGroup);
455   group->menus = g_hash_table_new (NULL, NULL);
456   group->exporter = exporter;
457   group->id = id;
458
459   return group;
460 }
461
462 /* {{{1 GMenuExporterRemote */
463
464 struct _GMenuExporterRemote
465 {
466   GMenuExporter *exporter;
467   GHashTable    *watches;
468   guint          watch_id;
469 };
470
471 static void
472 g_menu_exporter_remote_subscribe (GMenuExporterRemote *remote,
473                                   guint                group_id,
474                                   GVariantBuilder     *builder)
475 {
476   GMenuExporterGroup *group;
477   guint count;
478
479   count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
480   g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count + 1));
481
482   /* Group will be created (as empty/unsubscribed if it does not exist) */
483   group = g_menu_exporter_lookup_group (remote->exporter, group_id);
484   g_menu_exporter_group_subscribe (group, builder);
485 }
486
487 static void
488 g_menu_exporter_remote_unsubscribe (GMenuExporterRemote *remote,
489                                     guint                group_id)
490 {
491   GMenuExporterGroup *group;
492   guint count;
493
494   count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
495
496   if (count == 0)
497     return;
498
499   if (count != 1)
500     g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count - 1));
501   else
502     g_hash_table_remove (remote->watches, GINT_TO_POINTER (group_id));
503
504   group = g_menu_exporter_lookup_group (remote->exporter, group_id);
505   g_menu_exporter_group_unsubscribe (group, 1);
506 }
507
508 static gboolean
509 g_menu_exporter_remote_has_subscriptions (GMenuExporterRemote *remote)
510 {
511   return g_hash_table_size (remote->watches) != 0;
512 }
513
514 static void
515 g_menu_exporter_remote_free (gpointer data)
516 {
517   GMenuExporterRemote *remote = data;
518   GHashTableIter iter;
519   gpointer key, val;
520
521   g_hash_table_iter_init (&iter, remote->watches);
522   while (g_hash_table_iter_next (&iter, &key, &val))
523     {
524       GMenuExporterGroup *group;
525
526       group = g_menu_exporter_lookup_group (remote->exporter, GPOINTER_TO_INT (key));
527       g_menu_exporter_group_unsubscribe (group, GPOINTER_TO_INT (val));
528     }
529
530   g_bus_unwatch_name (remote->watch_id);
531   g_hash_table_unref (remote->watches);
532
533   g_slice_free (GMenuExporterRemote, remote);
534 }
535
536 static GMenuExporterRemote *
537 g_menu_exporter_remote_new (GMenuExporter *exporter,
538                             guint          watch_id)
539 {
540   GMenuExporterRemote *remote;
541
542   remote = g_slice_new0 (GMenuExporterRemote);
543   remote->exporter = exporter;
544   remote->watches = g_hash_table_new (NULL, NULL);
545   remote->watch_id = watch_id;
546
547   return remote;
548 }
549
550 /* {{{1 GMenuExporter */
551
552 struct _GMenuExporter
553 {
554   GDBusConnection *connection;
555   gchar *object_path;
556   guint registration_id;
557   GHashTable *groups;
558   guint next_group_id;
559
560   GMenuExporterMenu *root;
561   GHashTable *remotes;
562 };
563
564 static void
565 g_menu_exporter_name_vanished (GDBusConnection *connection,
566                                const gchar     *name,
567                                gpointer         user_data)
568 {
569   GMenuExporter *exporter = user_data;
570
571   /* connection == NULL when we get called because the connection closed */
572   g_assert (exporter->connection == connection || connection == NULL);
573
574   g_hash_table_remove (exporter->remotes, name);
575 }
576
577 static GVariant *
578 g_menu_exporter_subscribe (GMenuExporter *exporter,
579                            const gchar   *sender,
580                            GVariant      *group_ids)
581 {
582   GMenuExporterRemote *remote;
583   GVariantBuilder builder;
584   GVariantIter iter;
585   guint32 id;
586
587   remote = g_hash_table_lookup (exporter->remotes, sender);
588
589   if (remote == NULL)
590     {
591       guint watch_id;
592
593       watch_id = g_bus_watch_name_on_connection (exporter->connection, sender, G_BUS_NAME_WATCHER_FLAGS_NONE,
594                                                  NULL, g_menu_exporter_name_vanished, exporter, NULL);
595       remote = g_menu_exporter_remote_new (exporter, watch_id);
596       g_hash_table_insert (exporter->remotes, g_strdup (sender), remote);
597     }
598
599   g_variant_builder_init (&builder, G_VARIANT_TYPE ("(a(uuaa{sv}))"));
600
601   g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(uuaa{sv})"));
602
603   g_variant_iter_init (&iter, group_ids);
604   while (g_variant_iter_next (&iter, "u", &id))
605     g_menu_exporter_remote_subscribe (remote, id, &builder);
606
607   g_variant_builder_close (&builder);
608
609   return g_variant_builder_end (&builder);
610 }
611
612 static void
613 g_menu_exporter_unsubscribe (GMenuExporter *exporter,
614                              const gchar   *sender,
615                              GVariant      *group_ids)
616 {
617   GMenuExporterRemote *remote;
618   GVariantIter iter;
619   guint32 id;
620
621   remote = g_hash_table_lookup (exporter->remotes, sender);
622
623   if (remote == NULL)
624     return;
625
626   g_variant_iter_init (&iter, group_ids);
627   while (g_variant_iter_next (&iter, "u", &id))
628     g_menu_exporter_remote_unsubscribe (remote, id);
629
630   if (!g_menu_exporter_remote_has_subscriptions (remote))
631     g_hash_table_remove (exporter->remotes, sender);
632 }
633
634 static void
635 g_menu_exporter_report (GMenuExporter *exporter,
636                         GVariant      *report)
637 {
638   GVariantBuilder builder;
639
640   g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
641   g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY);
642   g_variant_builder_add_value (&builder, report);
643   g_variant_builder_close (&builder);
644
645   g_dbus_connection_emit_signal (exporter->connection,
646                                  NULL,
647                                  exporter->object_path,
648                                  "org.gtk.Menus", "Changed",
649                                  g_variant_builder_end (&builder),
650                                  NULL);
651 }
652
653 static void
654 g_menu_exporter_remove_group (GMenuExporter *exporter,
655                               guint          id)
656 {
657   g_hash_table_remove (exporter->groups, GINT_TO_POINTER (id));
658 }
659
660 static GMenuExporterGroup *
661 g_menu_exporter_lookup_group (GMenuExporter *exporter,
662                               guint          group_id)
663 {
664   GMenuExporterGroup *group;
665
666   group = g_hash_table_lookup (exporter->groups, GINT_TO_POINTER (group_id));
667
668   if (group == NULL)
669     {
670       group = g_menu_exporter_group_new (exporter, group_id);
671       g_hash_table_insert (exporter->groups, GINT_TO_POINTER (group_id), group);
672     }
673
674   return group;
675 }
676
677 static GMenuExporterGroup *
678 g_menu_exporter_create_group (GMenuExporter *exporter)
679 {
680   GMenuExporterGroup *group;
681   guint id;
682
683   id = exporter->next_group_id++;
684   group = g_menu_exporter_group_new (exporter, id);
685   g_hash_table_insert (exporter->groups, GINT_TO_POINTER (id), group);
686
687   return group;
688 }
689
690 static void
691 g_menu_exporter_free (gpointer user_data)
692 {
693   GMenuExporter *exporter = user_data;
694
695   g_menu_exporter_menu_free (exporter->root);
696   g_hash_table_unref (exporter->remotes);
697   g_hash_table_unref (exporter->groups);
698   g_object_unref (exporter->connection);
699   g_free (exporter->object_path);
700
701   g_slice_free (GMenuExporter, exporter);
702 }
703
704 static void
705 g_menu_exporter_method_call (GDBusConnection       *connection,
706                              const gchar           *sender,
707                              const gchar           *object_path,
708                              const gchar           *interface_name,
709                              const gchar           *method_name,
710                              GVariant              *parameters,
711                              GDBusMethodInvocation *invocation,
712                              gpointer               user_data)
713 {
714   GMenuExporter *exporter = user_data;
715   GVariant *group_ids;
716
717   group_ids = g_variant_get_child_value (parameters, 0);
718
719   if (g_str_equal (method_name, "Start"))
720     g_dbus_method_invocation_return_value (invocation, g_menu_exporter_subscribe (exporter, sender, group_ids));
721
722   else if (g_str_equal (method_name, "End"))
723     {
724       g_menu_exporter_unsubscribe (exporter, sender, group_ids);
725       g_dbus_method_invocation_return_value (invocation, NULL);
726     }
727
728   else
729     g_assert_not_reached ();
730
731   g_variant_unref (group_ids);
732 }
733
734 /* {{{1 Public API */
735
736 /**
737  * g_dbus_connection_export_menu_model:
738  * @connection: a #GDBusConnection
739  * @object_path: a D-Bus object path
740  * @menu: a #GMenuModel
741  * @error: return location for an error, or %NULL
742  *
743  * Exports @menu on @connection at @object_path.
744  *
745  * The implemented D-Bus API should be considered private.
746  * It is subject to change in the future.
747  *
748  * An object path can only have one menu model exported on it. If this
749  * constraint is violated, the export will fail and 0 will be
750  * returned (with @error set accordingly).
751  *
752  * You can unexport the menu model using
753  * g_dbus_connection_unexport_menu_model() with the return value of
754  * this function.
755  *
756  * Returns: the ID of the export (never zero), or 0 in case of failure
757  *
758  * Since: 2.32
759  */
760 guint
761 g_dbus_connection_export_menu_model (GDBusConnection  *connection,
762                                      const gchar      *object_path,
763                                      GMenuModel       *menu,
764                                      GError          **error)
765 {
766   const GDBusInterfaceVTable vtable = {
767     g_menu_exporter_method_call,
768   };
769   GMenuExporter *exporter;
770   guint id;
771
772   exporter = g_slice_new0 (GMenuExporter);
773
774   id = g_dbus_connection_register_object (connection, object_path, org_gtk_Menus_get_interface (),
775                                           &vtable, exporter, g_menu_exporter_free, error);
776
777   if (id == 0)
778     {
779       g_slice_free (GMenuExporter, exporter);
780       return 0;
781     }
782
783   exporter->connection = g_object_ref (connection);
784   exporter->object_path = g_strdup (object_path);
785   exporter->groups = g_hash_table_new (NULL, NULL);
786   exporter->remotes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_menu_exporter_remote_free);
787   exporter->root = g_menu_exporter_group_add_menu (g_menu_exporter_create_group (exporter), menu);
788
789   return id;
790 }
791
792 /**
793  * g_dbus_connection_unexport_menu_model:
794  * @connection: a #GDBusConnection
795  * @export_id: the ID from g_dbus_connection_export_menu_model()
796  *
797  * Reverses the effect of a previous call to
798  * g_dbus_connection_export_menu_model().
799  *
800  * It is an error to call this function with an ID that wasn't returned
801  * from g_dbus_connection_export_menu_model() or to call it with the
802  * same ID more than once.
803  *
804  * Since: 2.32
805  */
806 void
807 g_dbus_connection_unexport_menu_model (GDBusConnection *connection,
808                                        guint            export_id)
809 {
810   g_dbus_connection_unregister_object (connection, export_id);
811 }
812
813 /* {{{1 Epilogue */
814 /* vim:set foldmethod=marker: */