1 /*
2 * Copyright © 2011 Canonical Ltd.
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
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 /* {{{1 D-Bus Interface description */
32
33 /* For documentation of this interface, see
34 * https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI
35 */
36
37 static GDBusInterfaceInfo *
38 org_gtk_Menus_get_interface (void)
39 {
40 static GDBusInterfaceInfo *interface_info;
41 static gsize interface_info_initialized = 0;
42
43 if (g_once_init_enter (&interface_info_initialized))
44 {
45 GError *error = NULL;
46 GDBusNodeInfo *info;
47
48 info = g_dbus_node_info_new_for_xml ("<node>"
49 " <interface name='org.gtk.Menus'>"
50 " <method name='Start'>"
51 " <arg type='au' name='groups' direction='in'/>"
52 " <arg type='a(uuaa{sv})' name='content' direction='out'/>"
53 " </method>"
54 " <method name='End'>"
55 " <arg type='au' name='groups' direction='in'/>"
56 " </method>"
57 " <signal name='Changed'>"
58 " arg type='a(uuuuaa{sv})' name='changes'/>"
59 " </signal>"
60 " </interface>"
61 "</node>", &error);
62 if (info == NULL)
63 g_error ("%s", error->message);
64 interface_info = g_dbus_node_info_lookup_interface (info, "org.gtk.Menus");
65 g_assert (interface_info != NULL);
66 g_dbus_interface_info_ref (interface_info);
67 g_dbus_node_info_unref (info);
68
69 g_once_init_leave (&interface_info_initialized, 1);
70 }
71
72 return interface_info;
73 }
74
75 /* {{{1 Forward declarations */
76 typedef struct _GMenuExporterMenu GMenuExporterMenu;
77 typedef struct _GMenuExporterLink GMenuExporterLink;
78 typedef struct _GMenuExporterGroup GMenuExporterGroup;
79 typedef struct _GMenuExporterRemote GMenuExporterRemote;
80 typedef struct _GMenuExporterWatch GMenuExporterWatch;
81 typedef struct _GMenuExporter GMenuExporter;
82
83 static gboolean g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group);
84 static guint g_menu_exporter_group_get_id (GMenuExporterGroup *group);
85 static GMenuExporter * g_menu_exporter_group_get_exporter (GMenuExporterGroup *group);
86 static GMenuExporterMenu * g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
87 GMenuModel *model);
88 static void g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
89 guint id);
90
91 static GMenuExporterGroup * g_menu_exporter_create_group (GMenuExporter *exporter);
92 static GMenuExporterGroup * g_menu_exporter_lookup_group (GMenuExporter *exporter,
93 guint group_id);
94 static void g_menu_exporter_report (GMenuExporter *exporter,
95 GVariant *report);
96 static void g_menu_exporter_remove_group (GMenuExporter *exporter,
97 guint id);
98
99 /* {{{1 GMenuExporterLink, GMenuExporterMenu */
100
101 struct _GMenuExporterMenu
102 {
103 GMenuExporterGroup *group;
104 guint id;
105
106 GMenuModel *model;
107 gulong handler_id;
108 GSequence *item_links;
109 };
110
111 struct _GMenuExporterLink
112 {
113 gchar *name;
114 GMenuExporterMenu *menu;
115 GMenuExporterLink *next;
116 };
117
118 static void
119 g_menu_exporter_menu_free (GMenuExporterMenu *menu)
120 {
121 g_menu_exporter_group_remove_menu (menu->group, menu->id);
122
123 if (menu->handler_id != 0)
124 g_signal_handler_disconnect (menu->model, menu->handler_id);
125
126 if (menu->item_links != NULL)
127 g_sequence_free (menu->item_links);
128
129 g_object_unref (menu->model);
130
131 g_slice_free (GMenuExporterMenu, menu);
132 }
133
134 static void
135 g_menu_exporter_link_free (gpointer data)
136 {
137 GMenuExporterLink *link = data;
138
139 while (link != NULL)
140 {
141 GMenuExporterLink *tmp = link;
142 link = tmp->next;
143
144 g_menu_exporter_menu_free (tmp->menu);
145 g_free (tmp->name);
146
147 g_slice_free (GMenuExporterLink, tmp);
148 }
149 }
150
151 static GMenuExporterLink *
152 g_menu_exporter_menu_create_links (GMenuExporterMenu *menu,
153 gint position)
154 {
155 GMenuExporterLink *list = NULL;
156 GMenuLinkIter *iter;
157 const char *name;
158 GMenuModel *model;
159
160 iter = g_menu_model_iterate_item_links (menu->model, position);
161
162 while (g_menu_link_iter_get_next (iter, &name, &model))
163 {
164 GMenuExporterGroup *group;
165 GMenuExporterLink *tmp;
166
167 /* keep sections in the same group, but create new groups
168 * otherwise
169 */
170 if (!g_str_equal (name, "section"))
171 group = g_menu_exporter_create_group (g_menu_exporter_group_get_exporter (menu->group));
172 else
173 group = menu->group;
174
175 tmp = g_slice_new (GMenuExporterLink);
176 tmp->name = g_strconcat (":", name, NULL);
177 tmp->menu = g_menu_exporter_group_add_menu (group, model);
178 tmp->next = list;
179 list = tmp;
180
181 g_object_unref (model);
182 }
183
184 g_object_unref (iter);
185
186 return list;
187 }
188
189 static GVariant *
190 g_menu_exporter_menu_describe_item (GMenuExporterMenu *menu,
191 gint position)
192 {
193 GMenuAttributeIter *attr_iter;
194 GVariantBuilder builder;
195 GSequenceIter *iter;
196 GMenuExporterLink *link;
197 const char *name;
198 GVariant *value;
199
200 g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
201
202 attr_iter = g_menu_model_iterate_item_attributes (menu->model, position);
203 while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
204 {
205 g_variant_builder_add (&builder, "{sv}", name, value);
206 g_variant_unref (value);
207 }
208 g_object_unref (attr_iter);
209
210 iter = g_sequence_get_iter_at_pos (menu->item_links, position);
211 for (link = g_sequence_get (iter); link; link = link->next)
212 g_variant_builder_add (&builder, "{sv}", link->name,
213 g_variant_new ("(uu)", g_menu_exporter_group_get_id (link->menu->group), link->menu->id));
214
215 return g_variant_builder_end (&builder);
216 }
217
218 static GVariant *
219 g_menu_exporter_menu_list (GMenuExporterMenu *menu)
220 {
221 GVariantBuilder builder;
222 gint i, n;
223
224 g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
225
226 n = g_sequence_get_length (menu->item_links);
227 for (i = 0; i < n; i++)
228 g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
229
230 return g_variant_builder_end (&builder);
231 }
232
233 static void
234 g_menu_exporter_menu_items_changed (GMenuModel *model,
235 gint position,
236 gint removed,
237 gint added,
238 gpointer user_data)
239 {
240 GMenuExporterMenu *menu = user_data;
241 GSequenceIter *point;
242 gint i;
243 #ifndef G_DISABLE_ASSERT
244 gint n_items;
245 #endif
246
247 g_assert (menu->model == model);
248 g_assert (menu->item_links != NULL);
249
250 #ifndef G_DISABLE_ASSERT
251 n_items = g_sequence_get_length (menu->item_links);
252 #endif
253 g_assert (position >= 0 && position < G_MENU_EXPORTER_MAX_SECTION_SIZE);
254 g_assert (removed >= 0 && removed < G_MENU_EXPORTER_MAX_SECTION_SIZE);
255 g_assert (added < G_MENU_EXPORTER_MAX_SECTION_SIZE);
256 g_assert (position + removed <= n_items);
257 g_assert (n_items - removed + added < G_MENU_EXPORTER_MAX_SECTION_SIZE);
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_is_empty (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 if (remote->watch_id > 0)
530 g_bus_unwatch_name (remote->watch_id);
531
532 g_hash_table_unref (remote->watches);
533
534 g_slice_free (GMenuExporterRemote, remote);
535 }
536
537 static GMenuExporterRemote *
538 g_menu_exporter_remote_new (GMenuExporter *exporter,
539 guint watch_id)
540 {
541 GMenuExporterRemote *remote;
542
543 remote = g_slice_new0 (GMenuExporterRemote);
544 remote->exporter = exporter;
545 remote->watches = g_hash_table_new (NULL, NULL);
546 remote->watch_id = watch_id;
547
548 return remote;
549 }
550
551 /* {{{1 GMenuExporter */
552
553 struct _GMenuExporter
554 {
555 GDBusConnection *connection;
556 gchar *object_path;
557 guint registration_id;
558 GHashTable *groups;
559 guint next_group_id;
560
561 GMenuExporterMenu *root;
562 GMenuExporterRemote *peer_remote;
563 GHashTable *remotes;
564 };
565
566 static void
567 g_menu_exporter_name_vanished (GDBusConnection *connection,
568 const gchar *name,
569 gpointer user_data)
570 {
571 GMenuExporter *exporter = user_data;
572
573 /* connection == NULL when we get called because the connection closed */
574 g_assert (exporter->connection == connection || connection == NULL);
575
576 g_hash_table_remove (exporter->remotes, name);
577 }
578
579 static GVariant *
580 g_menu_exporter_subscribe (GMenuExporter *exporter,
581 const gchar *sender,
582 GVariant *group_ids)
583 {
584 GMenuExporterRemote *remote;
585 GVariantBuilder builder;
586 GVariantIter iter;
587 guint32 id;
588
589 if (sender != NULL)
590 remote = g_hash_table_lookup (exporter->remotes, sender);
591 else
592 remote = exporter->peer_remote;
593
594 if (remote == NULL)
595 {
596 if (sender != NULL)
597 {
598 guint watch_id;
599
600 watch_id = g_bus_watch_name_on_connection (exporter->connection, sender, G_BUS_NAME_WATCHER_FLAGS_NONE,
601 NULL, g_menu_exporter_name_vanished, exporter, NULL);
602 remote = g_menu_exporter_remote_new (exporter, watch_id);
603 g_hash_table_insert (exporter->remotes, g_strdup (sender), remote);
604 }
605 else
606 remote = exporter->peer_remote =
607 g_menu_exporter_remote_new (exporter, 0);
608 }
609
610 g_variant_builder_init (&builder, G_VARIANT_TYPE ("(a(uuaa{sv}))"));
611
612 g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(uuaa{sv})"));
613
614 g_variant_iter_init (&iter, group_ids);
615 while (g_variant_iter_next (&iter, "u", &id))
616 g_menu_exporter_remote_subscribe (remote, id, &builder);
617
618 g_variant_builder_close (&builder);
619
620 return g_variant_builder_end (&builder);
621 }
622
623 static void
624 g_menu_exporter_unsubscribe (GMenuExporter *exporter,
625 const gchar *sender,
626 GVariant *group_ids)
627 {
628 GMenuExporterRemote *remote;
629 GVariantIter iter;
630 guint32 id;
631
632 if (sender != NULL)
633 remote = g_hash_table_lookup (exporter->remotes, sender);
634 else
635 remote = exporter->peer_remote;
636
637 if (remote == NULL)
638 return;
639
640 g_variant_iter_init (&iter, group_ids);
641 while (g_variant_iter_next (&iter, "u", &id))
642 g_menu_exporter_remote_unsubscribe (remote, id);
643
644 if (!g_menu_exporter_remote_has_subscriptions (remote))
645 {
646 if (sender != NULL)
647 g_hash_table_remove (exporter->remotes, sender);
648 else
649 g_clear_pointer (&exporter->peer_remote, g_menu_exporter_remote_free);
650 }
651 }
652
653 static void
654 g_menu_exporter_report (GMenuExporter *exporter,
655 GVariant *report)
656 {
657 GVariantBuilder builder;
658
659 g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
660 g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY);
661 g_variant_builder_add_value (&builder, report);
662 g_variant_builder_close (&builder);
663
664 g_dbus_connection_emit_signal (exporter->connection,
665 NULL,
666 exporter->object_path,
667 "org.gtk.Menus", "Changed",
668 g_variant_builder_end (&builder),
669 NULL);
670 }
671
672 static void
673 g_menu_exporter_remove_group (GMenuExporter *exporter,
674 guint id)
675 {
676 g_hash_table_remove (exporter->groups, GINT_TO_POINTER (id));
677 }
678
679 static GMenuExporterGroup *
680 g_menu_exporter_lookup_group (GMenuExporter *exporter,
681 guint group_id)
682 {
683 GMenuExporterGroup *group;
684
685 group = g_hash_table_lookup (exporter->groups, GINT_TO_POINTER (group_id));
686
687 if (group == NULL)
688 {
689 group = g_menu_exporter_group_new (exporter, group_id);
690 g_hash_table_insert (exporter->groups, GINT_TO_POINTER (group_id), group);
691 }
692
693 return group;
694 }
695
696 static GMenuExporterGroup *
697 g_menu_exporter_create_group (GMenuExporter *exporter)
698 {
699 GMenuExporterGroup *group;
700 guint id;
701
702 id = exporter->next_group_id++;
703 group = g_menu_exporter_group_new (exporter, id);
704 g_hash_table_insert (exporter->groups, GINT_TO_POINTER (id), group);
705
706 return group;
707 }
708
709 static void
710 g_menu_exporter_free (gpointer user_data)
711 {
712 GMenuExporter *exporter = user_data;
713
714 g_menu_exporter_menu_free (exporter->root);
715 g_clear_pointer (&exporter->peer_remote, g_menu_exporter_remote_free);
716 g_hash_table_unref (exporter->remotes);
717 g_hash_table_unref (exporter->groups);
718 g_object_unref (exporter->connection);
719 g_free (exporter->object_path);
720
721 g_slice_free (GMenuExporter, exporter);
722 }
723
724 static void
725 g_menu_exporter_method_call (GDBusConnection *connection,
726 const gchar *sender,
727 const gchar *object_path,
728 const gchar *interface_name,
729 const gchar *method_name,
730 GVariant *parameters,
731 GDBusMethodInvocation *invocation,
732 gpointer user_data)
733 {
734 GMenuExporter *exporter = user_data;
735 GVariant *group_ids;
736
737 group_ids = g_variant_get_child_value (parameters, 0);
738
739 if (g_str_equal (method_name, "Start"))
740 g_dbus_method_invocation_return_value (invocation, g_menu_exporter_subscribe (exporter, sender, group_ids));
741
742 else if (g_str_equal (method_name, "End"))
743 {
744 g_menu_exporter_unsubscribe (exporter, sender, group_ids);
745 g_dbus_method_invocation_return_value (invocation, NULL);
746 }
747
748 else
749 g_assert_not_reached ();
750
751 g_variant_unref (group_ids);
752 }
753
754 /* {{{1 Public API */
755
756 /**
757 * g_dbus_connection_export_menu_model:
758 * @connection: a #GDBusConnection
759 * @object_path: a D-Bus object path
760 * @menu: a #GMenuModel
761 * @error: return location for an error, or %NULL
762 *
763 * Exports @menu on @connection at @object_path.
764 *
765 * The implemented D-Bus API should be considered private.
766 * It is subject to change in the future.
767 *
768 * An object path can only have one menu model exported on it. If this
769 * constraint is violated, the export will fail and 0 will be
770 * returned (with @error set accordingly).
771 *
772 * Exporting menus with sections containing more than
773 * %G_MENU_EXPORTER_MAX_SECTION_SIZE items is not supported and results in
774 * undefined behavior.
775 *
776 * You can unexport the menu model using
777 * g_dbus_connection_unexport_menu_model() with the return value of
778 * this function.
779 *
780 * Returns: the ID of the export (never zero), or 0 in case of failure
781 *
782 * Since: 2.32
783 */
784 guint
785 g_dbus_connection_export_menu_model (GDBusConnection *connection,
786 const gchar *object_path,
787 GMenuModel *menu,
788 GError **error)
789 {
790 const GDBusInterfaceVTable vtable = {
791 g_menu_exporter_method_call, NULL, NULL, { 0 }
792 };
793 GMenuExporter *exporter;
794 guint id;
795
796 exporter = g_slice_new0 (GMenuExporter);
797
798 id = g_dbus_connection_register_object (connection, object_path, org_gtk_Menus_get_interface (),
799 &vtable, exporter, g_menu_exporter_free, error);
800
801 if (id == 0)
802 {
803 g_slice_free (GMenuExporter, exporter);
804 return 0;
805 }
806
807 exporter->connection = g_object_ref (connection);
808 exporter->object_path = g_strdup (object_path);
809 exporter->groups = g_hash_table_new (NULL, NULL);
810 exporter->remotes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_menu_exporter_remote_free);
811 exporter->root = g_menu_exporter_group_add_menu (g_menu_exporter_create_group (exporter), menu);
812
813 return id;
814 }
815
816 /**
817 * g_dbus_connection_unexport_menu_model:
818 * @connection: a #GDBusConnection
819 * @export_id: the ID from g_dbus_connection_export_menu_model()
820 *
821 * Reverses the effect of a previous call to
822 * g_dbus_connection_export_menu_model().
823 *
824 * It is an error to call this function with an ID that wasn't returned
825 * from g_dbus_connection_export_menu_model() or to call it with the
826 * same ID more than once.
827 *
828 * Since: 2.32
829 */
830 void
831 g_dbus_connection_unexport_menu_model (GDBusConnection *connection,
832 guint export_id)
833 {
834 g_dbus_connection_unregister_object (connection, export_id);
835 }
836
837 /* {{{1 Epilogue */
838 /* vim:set foldmethod=marker: */