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