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