Imported Upstream version 2.66.6
[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
5  *  modify it under the terms of the GNU Lesser General Public
6  *  License as published by the Free Software Foundation; either
7  *  version 2.1 of the License, 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, see <http://www.gnu.org/licenses/>.
16  *
17  * Author: Ryan Lortie <desrt@desrt.ca>
18  */
19
20 #include "config.h"
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  * @include: gio/gio.h
34  * @see_also: #GMenuModel, #GDBusMenuModel
35  *
36  * These functions support exporting a #GMenuModel on D-Bus.
37  * The D-Bus interface that is used is a private implementation
38  * detail.
39  *
40  * To access an exported #GMenuModel remotely, use
41  * g_dbus_menu_model_get() to obtain a #GDBusMenuModel.
42  */
43
44 /* {{{1 D-Bus Interface description */
45
46 /* For documentation of this interface, see
47  * https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI
48  */
49
50 static GDBusInterfaceInfo *
51 org_gtk_Menus_get_interface (void)
52 {
53   static GDBusInterfaceInfo *interface_info;
54
55   if (interface_info == NULL)
56     {
57       GError *error = NULL;
58       GDBusNodeInfo *info;
59
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", 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);
80     }
81
82   return interface_info;
83 }
84
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;
92
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);
100
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);
108
109 /* {{{1 GMenuExporterLink, GMenuExporterMenu */
110
111 struct _GMenuExporterMenu
112 {
113   GMenuExporterGroup *group;
114   guint               id;
115
116   GMenuModel *model;
117   gulong      handler_id;
118   GSequence  *item_links;
119 };
120
121 struct _GMenuExporterLink
122 {
123   gchar             *name;
124   GMenuExporterMenu *menu;
125   GMenuExporterLink *next;
126 };
127
128 static void
129 g_menu_exporter_menu_free (GMenuExporterMenu *menu)
130 {
131   g_menu_exporter_group_remove_menu (menu->group, menu->id);
132
133   if (menu->handler_id != 0)
134     g_signal_handler_disconnect (menu->model, menu->handler_id);
135
136   if (menu->item_links != NULL)
137     g_sequence_free (menu->item_links);
138
139   g_object_unref (menu->model);
140
141   g_slice_free (GMenuExporterMenu, menu);
142 }
143
144 static void
145 g_menu_exporter_link_free (gpointer data)
146 {
147   GMenuExporterLink *link = data;
148
149   while (link != NULL)
150     {
151       GMenuExporterLink *tmp = link;
152       link = tmp->next;
153
154       g_menu_exporter_menu_free (tmp->menu);
155       g_free (tmp->name);
156
157       g_slice_free (GMenuExporterLink, tmp);
158     }
159 }
160
161 static GMenuExporterLink *
162 g_menu_exporter_menu_create_links (GMenuExporterMenu *menu,
163                                    gint               position)
164 {
165   GMenuExporterLink *list = NULL;
166   GMenuLinkIter *iter;
167   const char *name;
168   GMenuModel *model;
169
170   iter = g_menu_model_iterate_item_links (menu->model, position);
171
172   while (g_menu_link_iter_get_next (iter, &name, &model))
173     {
174       GMenuExporterGroup *group;
175       GMenuExporterLink *tmp;
176
177       /* keep sections in the same group, but create new groups
178        * otherwise
179        */
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;
184
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;
190
191       g_object_unref (model);
192     }
193
194   g_object_unref (iter);
195
196   return list;
197 }
198
199 static GVariant *
200 g_menu_exporter_menu_describe_item (GMenuExporterMenu *menu,
201                                     gint               position)
202 {
203   GMenuAttributeIter *attr_iter;
204   GVariantBuilder builder;
205   GSequenceIter *iter;
206   GMenuExporterLink *link;
207   const char *name;
208   GVariant *value;
209
210   g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
211
212   attr_iter = g_menu_model_iterate_item_attributes (menu->model, position);
213   while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
214     {
215       g_variant_builder_add (&builder, "{sv}", name, value);
216       g_variant_unref (value);
217     }
218   g_object_unref (attr_iter);
219
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));
224
225   return g_variant_builder_end (&builder);
226 }
227
228 static GVariant *
229 g_menu_exporter_menu_list (GMenuExporterMenu *menu)
230 {
231   GVariantBuilder builder;
232   gint i, n;
233
234   g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
235
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));
239
240   return g_variant_builder_end (&builder);
241 }
242
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)
249 {
250   GMenuExporterMenu *menu = user_data;
251   GSequenceIter *point;
252   gint i;
253
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));
257
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);
260
261   for (i = position; i < position + added; i++)
262     g_sequence_insert_before (point, g_menu_exporter_menu_create_links (menu, i));
263
264   if (g_menu_exporter_group_is_subscribed (menu->group))
265     {
266       GVariantBuilder builder;
267
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);
273
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);
278
279       g_menu_exporter_report (g_menu_exporter_group_get_exporter (menu->group), g_variant_builder_end (&builder));
280     }
281 }
282
283 static void
284 g_menu_exporter_menu_prepare (GMenuExporterMenu *menu)
285 {
286   gint n_items;
287
288   g_assert (menu->item_links == NULL);
289
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);
293
294   menu->item_links = g_sequence_new (g_menu_exporter_link_free);
295
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);
299 }
300
301 static GMenuExporterMenu *
302 g_menu_exporter_menu_new (GMenuExporterGroup *group,
303                           guint               id,
304                           GMenuModel         *model)
305 {
306   GMenuExporterMenu *menu;
307
308   menu = g_slice_new0 (GMenuExporterMenu);
309   menu->group = group;
310   menu->id = id;
311   menu->model = g_object_ref (model);
312
313   return menu;
314 }
315
316 /* {{{1 GMenuExporterGroup */
317
318 struct _GMenuExporterGroup
319 {
320   GMenuExporter *exporter;
321   guint          id;
322
323   GHashTable *menus;
324   guint       next_menu_id;
325   gboolean    prepared;
326
327   gint subscribed;
328 };
329
330 static void
331 g_menu_exporter_group_check_if_useless (GMenuExporterGroup *group)
332 {
333   if (g_hash_table_size (group->menus) == 0 && group->subscribed == 0)
334     {
335       g_menu_exporter_remove_group (group->exporter, group->id);
336
337       g_hash_table_unref (group->menus);
338
339       g_slice_free (GMenuExporterGroup, group);
340     }
341 }
342
343 static void
344 g_menu_exporter_group_subscribe (GMenuExporterGroup *group,
345                                  GVariantBuilder    *builder)
346 {
347   GHashTableIter iter;
348   gpointer key, val;
349
350   if (!group->prepared)
351     {
352       GMenuExporterMenu *menu;
353
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;
359
360       menu = g_hash_table_lookup (group->menus, 0);
361
362       /* If the group was created by a subscription and does not yet
363        * exist, it won't have a root menu...
364        *
365        * That menu will be prepared if it is ever added (due to
366        * group->prepared == TRUE).
367        */
368       if (menu)
369         g_menu_exporter_menu_prepare (menu);
370     }
371
372   group->subscribed++;
373
374   g_hash_table_iter_init (&iter, group->menus);
375   while (g_hash_table_iter_next (&iter, &key, &val))
376     {
377       guint id = GPOINTER_TO_INT (key);
378       GMenuExporterMenu *menu = val;
379
380       if (!g_sequence_is_empty (menu->item_links))
381         {
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);
387         }
388     }
389 }
390
391 static void
392 g_menu_exporter_group_unsubscribe (GMenuExporterGroup *group,
393                                    gint                count)
394 {
395   g_assert (group->subscribed >= count);
396
397   group->subscribed -= count;
398
399   g_menu_exporter_group_check_if_useless (group);
400 }
401
402 static GMenuExporter *
403 g_menu_exporter_group_get_exporter (GMenuExporterGroup *group)
404 {
405   return group->exporter;
406 }
407
408 static gboolean
409 g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group)
410 {
411   return group->subscribed > 0;
412 }
413
414 static guint
415 g_menu_exporter_group_get_id (GMenuExporterGroup *group)
416 {
417   return group->id;
418 }
419
420 static void
421 g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
422                                    guint               id)
423 {
424   g_hash_table_remove (group->menus, GINT_TO_POINTER (id));
425
426   g_menu_exporter_group_check_if_useless (group);
427 }
428
429 static GMenuExporterMenu *
430 g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
431                                 GMenuModel         *model)
432 {
433   GMenuExporterMenu *menu;
434   guint id;
435
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);
439
440   if (group->prepared)
441     g_menu_exporter_menu_prepare (menu);
442
443   return menu;
444 }
445
446 static GMenuExporterGroup *
447 g_menu_exporter_group_new (GMenuExporter *exporter,
448                            guint          id)
449 {
450   GMenuExporterGroup *group;
451
452   group = g_slice_new0 (GMenuExporterGroup);
453   group->menus = g_hash_table_new (NULL, NULL);
454   group->exporter = exporter;
455   group->id = id;
456
457   return group;
458 }
459
460 /* {{{1 GMenuExporterRemote */
461
462 struct _GMenuExporterRemote
463 {
464   GMenuExporter *exporter;
465   GHashTable    *watches;
466   guint          watch_id;
467 };
468
469 static void
470 g_menu_exporter_remote_subscribe (GMenuExporterRemote *remote,
471                                   guint                group_id,
472                                   GVariantBuilder     *builder)
473 {
474   GMenuExporterGroup *group;
475   guint count;
476
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));
479
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);
483 }
484
485 static void
486 g_menu_exporter_remote_unsubscribe (GMenuExporterRemote *remote,
487                                     guint                group_id)
488 {
489   GMenuExporterGroup *group;
490   guint count;
491
492   count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
493
494   if (count == 0)
495     return;
496
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));
501
502   group = g_menu_exporter_lookup_group (remote->exporter, group_id);
503   g_menu_exporter_group_unsubscribe (group, 1);
504 }
505
506 static gboolean
507 g_menu_exporter_remote_has_subscriptions (GMenuExporterRemote *remote)
508 {
509   return g_hash_table_size (remote->watches) != 0;
510 }
511
512 static void
513 g_menu_exporter_remote_free (gpointer data)
514 {
515   GMenuExporterRemote *remote = data;
516   GHashTableIter iter;
517   gpointer key, val;
518
519   g_hash_table_iter_init (&iter, remote->watches);
520   while (g_hash_table_iter_next (&iter, &key, &val))
521     {
522       GMenuExporterGroup *group;
523
524       group = g_menu_exporter_lookup_group (remote->exporter, GPOINTER_TO_INT (key));
525       g_menu_exporter_group_unsubscribe (group, GPOINTER_TO_INT (val));
526     }
527
528   if (remote->watch_id > 0)
529     g_bus_unwatch_name (remote->watch_id);
530
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   GMenuExporterRemote *peer_remote;
562   GHashTable *remotes;
563 };
564
565 static void
566 g_menu_exporter_name_vanished (GDBusConnection *connection,
567                                const gchar     *name,
568                                gpointer         user_data)
569 {
570   GMenuExporter *exporter = user_data;
571
572   /* connection == NULL when we get called because the connection closed */
573   g_assert (exporter->connection == connection || connection == NULL);
574
575   g_hash_table_remove (exporter->remotes, name);
576 }
577
578 static GVariant *
579 g_menu_exporter_subscribe (GMenuExporter *exporter,
580                            const gchar   *sender,
581                            GVariant      *group_ids)
582 {
583   GMenuExporterRemote *remote;
584   GVariantBuilder builder;
585   GVariantIter iter;
586   guint32 id;
587
588   if (sender != NULL)
589     remote = g_hash_table_lookup (exporter->remotes, sender);
590   else
591     remote = exporter->peer_remote;
592
593   if (remote == NULL)
594     {
595       if (sender != NULL)
596         {
597           guint watch_id;
598
599           watch_id = g_bus_watch_name_on_connection (exporter->connection, sender, G_BUS_NAME_WATCHER_FLAGS_NONE,
600                                                      NULL, g_menu_exporter_name_vanished, exporter, NULL);
601           remote = g_menu_exporter_remote_new (exporter, watch_id);
602           g_hash_table_insert (exporter->remotes, g_strdup (sender), remote);
603         }
604       else
605         remote = exporter->peer_remote =
606           g_menu_exporter_remote_new (exporter, 0);
607     }
608
609   g_variant_builder_init (&builder, G_VARIANT_TYPE ("(a(uuaa{sv}))"));
610
611   g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(uuaa{sv})"));
612
613   g_variant_iter_init (&iter, group_ids);
614   while (g_variant_iter_next (&iter, "u", &id))
615     g_menu_exporter_remote_subscribe (remote, id, &builder);
616
617   g_variant_builder_close (&builder);
618
619   return g_variant_builder_end (&builder);
620 }
621
622 static void
623 g_menu_exporter_unsubscribe (GMenuExporter *exporter,
624                              const gchar   *sender,
625                              GVariant      *group_ids)
626 {
627   GMenuExporterRemote *remote;
628   GVariantIter iter;
629   guint32 id;
630
631   if (sender != NULL)
632     remote = g_hash_table_lookup (exporter->remotes, sender);
633   else
634     remote = exporter->peer_remote;
635
636   if (remote == NULL)
637     return;
638
639   g_variant_iter_init (&iter, group_ids);
640   while (g_variant_iter_next (&iter, "u", &id))
641     g_menu_exporter_remote_unsubscribe (remote, id);
642
643   if (!g_menu_exporter_remote_has_subscriptions (remote))
644     {
645       if (sender != NULL)
646         g_hash_table_remove (exporter->remotes, sender);
647       else
648         g_clear_pointer (&exporter->peer_remote, g_menu_exporter_remote_free);
649     }
650 }
651
652 static void
653 g_menu_exporter_report (GMenuExporter *exporter,
654                         GVariant      *report)
655 {
656   GVariantBuilder builder;
657
658   g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
659   g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY);
660   g_variant_builder_add_value (&builder, report);
661   g_variant_builder_close (&builder);
662
663   g_dbus_connection_emit_signal (exporter->connection,
664                                  NULL,
665                                  exporter->object_path,
666                                  "org.gtk.Menus", "Changed",
667                                  g_variant_builder_end (&builder),
668                                  NULL);
669 }
670
671 static void
672 g_menu_exporter_remove_group (GMenuExporter *exporter,
673                               guint          id)
674 {
675   g_hash_table_remove (exporter->groups, GINT_TO_POINTER (id));
676 }
677
678 static GMenuExporterGroup *
679 g_menu_exporter_lookup_group (GMenuExporter *exporter,
680                               guint          group_id)
681 {
682   GMenuExporterGroup *group;
683
684   group = g_hash_table_lookup (exporter->groups, GINT_TO_POINTER (group_id));
685
686   if (group == NULL)
687     {
688       group = g_menu_exporter_group_new (exporter, group_id);
689       g_hash_table_insert (exporter->groups, GINT_TO_POINTER (group_id), group);
690     }
691
692   return group;
693 }
694
695 static GMenuExporterGroup *
696 g_menu_exporter_create_group (GMenuExporter *exporter)
697 {
698   GMenuExporterGroup *group;
699   guint id;
700
701   id = exporter->next_group_id++;
702   group = g_menu_exporter_group_new (exporter, id);
703   g_hash_table_insert (exporter->groups, GINT_TO_POINTER (id), group);
704
705   return group;
706 }
707
708 static void
709 g_menu_exporter_free (gpointer user_data)
710 {
711   GMenuExporter *exporter = user_data;
712
713   g_menu_exporter_menu_free (exporter->root);
714   g_clear_pointer (&exporter->peer_remote, g_menu_exporter_remote_free);
715   g_hash_table_unref (exporter->remotes);
716   g_hash_table_unref (exporter->groups);
717   g_object_unref (exporter->connection);
718   g_free (exporter->object_path);
719
720   g_slice_free (GMenuExporter, exporter);
721 }
722
723 static void
724 g_menu_exporter_method_call (GDBusConnection       *connection,
725                              const gchar           *sender,
726                              const gchar           *object_path,
727                              const gchar           *interface_name,
728                              const gchar           *method_name,
729                              GVariant              *parameters,
730                              GDBusMethodInvocation *invocation,
731                              gpointer               user_data)
732 {
733   GMenuExporter *exporter = user_data;
734   GVariant *group_ids;
735
736   group_ids = g_variant_get_child_value (parameters, 0);
737
738   if (g_str_equal (method_name, "Start"))
739     g_dbus_method_invocation_return_value (invocation, g_menu_exporter_subscribe (exporter, sender, group_ids));
740
741   else if (g_str_equal (method_name, "End"))
742     {
743       g_menu_exporter_unsubscribe (exporter, sender, group_ids);
744       g_dbus_method_invocation_return_value (invocation, NULL);
745     }
746
747   else
748     g_assert_not_reached ();
749
750   g_variant_unref (group_ids);
751 }
752
753 /* {{{1 Public API */
754
755 /**
756  * g_dbus_connection_export_menu_model:
757  * @connection: a #GDBusConnection
758  * @object_path: a D-Bus object path
759  * @menu: a #GMenuModel
760  * @error: return location for an error, or %NULL
761  *
762  * Exports @menu on @connection at @object_path.
763  *
764  * The implemented D-Bus API should be considered private.
765  * It is subject to change in the future.
766  *
767  * An object path can only have one menu model exported on it. If this
768  * constraint is violated, the export will fail and 0 will be
769  * returned (with @error set accordingly).
770  *
771  * You can unexport the menu model using
772  * g_dbus_connection_unexport_menu_model() with the return value of
773  * this function.
774  *
775  * Returns: the ID of the export (never zero), or 0 in case of failure
776  *
777  * Since: 2.32
778  */
779 guint
780 g_dbus_connection_export_menu_model (GDBusConnection  *connection,
781                                      const gchar      *object_path,
782                                      GMenuModel       *menu,
783                                      GError          **error)
784 {
785   const GDBusInterfaceVTable vtable = {
786     g_menu_exporter_method_call,
787   };
788   GMenuExporter *exporter;
789   guint id;
790
791   exporter = g_slice_new0 (GMenuExporter);
792
793   id = g_dbus_connection_register_object (connection, object_path, org_gtk_Menus_get_interface (),
794                                           &vtable, exporter, g_menu_exporter_free, error);
795
796   if (id == 0)
797     {
798       g_slice_free (GMenuExporter, exporter);
799       return 0;
800     }
801
802   exporter->connection = g_object_ref (connection);
803   exporter->object_path = g_strdup (object_path);
804   exporter->groups = g_hash_table_new (NULL, NULL);
805   exporter->remotes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_menu_exporter_remote_free);
806   exporter->root = g_menu_exporter_group_add_menu (g_menu_exporter_create_group (exporter), menu);
807
808   return id;
809 }
810
811 /**
812  * g_dbus_connection_unexport_menu_model:
813  * @connection: a #GDBusConnection
814  * @export_id: the ID from g_dbus_connection_export_menu_model()
815  *
816  * Reverses the effect of a previous call to
817  * g_dbus_connection_export_menu_model().
818  *
819  * It is an error to call this function with an ID that wasn't returned
820  * from g_dbus_connection_export_menu_model() or to call it with the
821  * same ID more than once.
822  *
823  * Since: 2.32
824  */
825 void
826 g_dbus_connection_unexport_menu_model (GDBusConnection *connection,
827                                        guint            export_id)
828 {
829   g_dbus_connection_unregister_object (connection, export_id);
830 }
831
832 /* {{{1 Epilogue */
833 /* vim:set foldmethod=marker: */