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