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
50static GDBusInterfaceInfo *
51org_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 (xml_data: "<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: &error);
74 if (info == NULL)
75 g_error ("%s", error->message);
76 interface_info = g_dbus_node_info_lookup_interface (info, name: "org.gtk.Menus");
77 g_assert (interface_info != NULL);
78 g_dbus_interface_info_ref (info: interface_info);
79 g_dbus_node_info_unref (info);
80 }
81
82 return interface_info;
83}
84
85/* {{{1 Forward declarations */
86typedef struct _GMenuExporterMenu GMenuExporterMenu;
87typedef struct _GMenuExporterLink GMenuExporterLink;
88typedef struct _GMenuExporterGroup GMenuExporterGroup;
89typedef struct _GMenuExporterRemote GMenuExporterRemote;
90typedef struct _GMenuExporterWatch GMenuExporterWatch;
91typedef struct _GMenuExporter GMenuExporter;
92
93static gboolean g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group);
94static guint g_menu_exporter_group_get_id (GMenuExporterGroup *group);
95static GMenuExporter * g_menu_exporter_group_get_exporter (GMenuExporterGroup *group);
96static GMenuExporterMenu * g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
97 GMenuModel *model);
98static void g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
99 guint id);
100
101static GMenuExporterGroup * g_menu_exporter_create_group (GMenuExporter *exporter);
102static GMenuExporterGroup * g_menu_exporter_lookup_group (GMenuExporter *exporter,
103 guint group_id);
104static void g_menu_exporter_report (GMenuExporter *exporter,
105 GVariant *report);
106static void g_menu_exporter_remove_group (GMenuExporter *exporter,
107 guint id);
108
109/* {{{1 GMenuExporterLink, GMenuExporterMenu */
110
111struct _GMenuExporterMenu
112{
113 GMenuExporterGroup *group;
114 guint id;
115
116 GMenuModel *model;
117 gulong handler_id;
118 GSequence *item_links;
119};
120
121struct _GMenuExporterLink
122{
123 gchar *name;
124 GMenuExporterMenu *menu;
125 GMenuExporterLink *next;
126};
127
128static void
129g_menu_exporter_menu_free (GMenuExporterMenu *menu)
130{
131 g_menu_exporter_group_remove_menu (group: menu->group, id: menu->id);
132
133 if (menu->handler_id != 0)
134 g_signal_handler_disconnect (instance: menu->model, handler_id: menu->handler_id);
135
136 if (menu->item_links != NULL)
137 g_sequence_free (seq: menu->item_links);
138
139 g_object_unref (object: menu->model);
140
141 g_slice_free (GMenuExporterMenu, menu);
142}
143
144static void
145g_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 (menu: tmp->menu);
155 g_free (mem: tmp->name);
156
157 g_slice_free (GMenuExporterLink, tmp);
158 }
159}
160
161static GMenuExporterLink *
162g_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 (model: menu->model, item_index: position);
171
172 while (g_menu_link_iter_get_next (iter, out_link: &name, value: &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 (v1: name, v2: "section"))
181 group = g_menu_exporter_create_group (exporter: g_menu_exporter_group_get_exporter (group: menu->group));
182 else
183 group = menu->group;
184
185 tmp = g_slice_new (GMenuExporterLink);
186 tmp->name = g_strconcat (string1: ":", 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 (object: model);
192 }
193
194 g_object_unref (object: iter);
195
196 return list;
197}
198
199static GVariant *
200g_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: &builder, G_VARIANT_TYPE_VARDICT);
211
212 attr_iter = g_menu_model_iterate_item_attributes (model: menu->model, item_index: position);
213 while (g_menu_attribute_iter_get_next (iter: attr_iter, out_name: &name, value: &value))
214 {
215 g_variant_builder_add (builder: &builder, format_string: "{sv}", name, value);
216 g_variant_unref (value);
217 }
218 g_object_unref (object: attr_iter);
219
220 iter = g_sequence_get_iter_at_pos (seq: menu->item_links, pos: position);
221 for (link = g_sequence_get (iter); link; link = link->next)
222 g_variant_builder_add (builder: &builder, format_string: "{sv}", link->name,
223 g_variant_new (format_string: "(uu)", g_menu_exporter_group_get_id (group: link->menu->group), link->menu->id));
224
225 return g_variant_builder_end (builder: &builder);
226}
227
228static GVariant *
229g_menu_exporter_menu_list (GMenuExporterMenu *menu)
230{
231 GVariantBuilder builder;
232 gint i, n;
233
234 g_variant_builder_init (builder: &builder, G_VARIANT_TYPE ("aa{sv}"));
235
236 n = g_sequence_get_length (seq: menu->item_links);
237 for (i = 0; i < n; i++)
238 g_variant_builder_add_value (builder: &builder, value: g_menu_exporter_menu_describe_item (menu, position: i));
239
240 return g_variant_builder_end (builder: &builder);
241}
242
243static void
244g_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 (seq: menu->item_links, pos: position + removed);
259 g_sequence_remove_range (begin: g_sequence_get_iter_at_pos (seq: menu->item_links, pos: position), end: point);
260
261 for (i = position; i < position + added; i++)
262 g_sequence_insert_before (iter: point, data: g_menu_exporter_menu_create_links (menu, position: i));
263
264 if (g_menu_exporter_group_is_subscribed (group: menu->group))
265 {
266 GVariantBuilder builder;
267
268 g_variant_builder_init (builder: &builder, G_VARIANT_TYPE ("(uuuuaa{sv})"));
269 g_variant_builder_add (builder: &builder, format_string: "u", g_menu_exporter_group_get_id (group: menu->group));
270 g_variant_builder_add (builder: &builder, format_string: "u", menu->id);
271 g_variant_builder_add (builder: &builder, format_string: "u", position);
272 g_variant_builder_add (builder: &builder, format_string: "u", removed);
273
274 g_variant_builder_open (builder: &builder, G_VARIANT_TYPE ("aa{sv}"));
275 for (i = position; i < position + added; i++)
276 g_variant_builder_add_value (builder: &builder, value: g_menu_exporter_menu_describe_item (menu, position: i));
277 g_variant_builder_close (builder: &builder);
278
279 g_menu_exporter_report (exporter: g_menu_exporter_group_get_exporter (group: menu->group), report: g_variant_builder_end (builder: &builder));
280 }
281}
282
283static void
284g_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 (model: 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 (data_destroy: g_menu_exporter_link_free);
295
296 n_items = g_menu_model_get_n_items (model: menu->model);
297 if (n_items)
298 g_menu_exporter_menu_items_changed (model: menu->model, position: 0, removed: 0, added: n_items, user_data: menu);
299}
300
301static GMenuExporterMenu *
302g_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
318struct _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
330static void
331g_menu_exporter_group_check_if_useless (GMenuExporterGroup *group)
332{
333 if (g_hash_table_size (hash_table: group->menus) == 0 && group->subscribed == 0)
334 {
335 g_menu_exporter_remove_group (exporter: group->exporter, id: group->id);
336
337 g_hash_table_unref (hash_table: group->menus);
338
339 g_slice_free (GMenuExporterGroup, group);
340 }
341}
342
343static void
344g_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 (hash_table: group->menus, key: 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: &iter, hash_table: group->menus);
375 while (g_hash_table_iter_next (iter: &iter, key: &key, value: &val))
376 {
377 guint id = GPOINTER_TO_INT (key);
378 GMenuExporterMenu *menu = val;
379
380 if (!g_sequence_is_empty (seq: menu->item_links))
381 {
382 g_variant_builder_open (builder, G_VARIANT_TYPE ("(uuaa{sv})"));
383 g_variant_builder_add (builder, format_string: "u", group->id);
384 g_variant_builder_add (builder, format_string: "u", id);
385 g_variant_builder_add_value (builder, value: g_menu_exporter_menu_list (menu));
386 g_variant_builder_close (builder);
387 }
388 }
389}
390
391static void
392g_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
402static GMenuExporter *
403g_menu_exporter_group_get_exporter (GMenuExporterGroup *group)
404{
405 return group->exporter;
406}
407
408static gboolean
409g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group)
410{
411 return group->subscribed > 0;
412}
413
414static guint
415g_menu_exporter_group_get_id (GMenuExporterGroup *group)
416{
417 return group->id;
418}
419
420static void
421g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
422 guint id)
423{
424 g_hash_table_remove (hash_table: group->menus, GINT_TO_POINTER (id));
425
426 g_menu_exporter_group_check_if_useless (group);
427}
428
429static GMenuExporterMenu *
430g_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 (hash_table: group->menus, GINT_TO_POINTER (id), value: menu);
439
440 if (group->prepared)
441 g_menu_exporter_menu_prepare (menu);
442
443 return menu;
444}
445
446static GMenuExporterGroup *
447g_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
462struct _GMenuExporterRemote
463{
464 GMenuExporter *exporter;
465 GHashTable *watches;
466 guint watch_id;
467};
468
469static void
470g_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 (hash_table: remote->watches, GINT_TO_POINTER (group_id));
478 g_hash_table_insert (hash_table: 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 (exporter: remote->exporter, group_id);
482 g_menu_exporter_group_subscribe (group, builder);
483}
484
485static void
486g_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 (hash_table: remote->watches, GINT_TO_POINTER (group_id));
493
494 if (count == 0)
495 return;
496
497 if (count != 1)
498 g_hash_table_insert (hash_table: remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count - 1));
499 else
500 g_hash_table_remove (hash_table: remote->watches, GINT_TO_POINTER (group_id));
501
502 group = g_menu_exporter_lookup_group (exporter: remote->exporter, group_id);
503 g_menu_exporter_group_unsubscribe (group, count: 1);
504}
505
506static gboolean
507g_menu_exporter_remote_has_subscriptions (GMenuExporterRemote *remote)
508{
509 return g_hash_table_size (hash_table: remote->watches) != 0;
510}
511
512static void
513g_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: &iter, hash_table: remote->watches);
520 while (g_hash_table_iter_next (iter: &iter, key: &key, value: &val))
521 {
522 GMenuExporterGroup *group;
523
524 group = g_menu_exporter_lookup_group (exporter: 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 (watcher_id: remote->watch_id);
530
531 g_hash_table_unref (hash_table: remote->watches);
532
533 g_slice_free (GMenuExporterRemote, remote);
534}
535
536static GMenuExporterRemote *
537g_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
552struct _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
565static void
566g_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 (hash_table: exporter->remotes, key: name);
576}
577
578static GVariant *
579g_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 (hash_table: exporter->remotes, key: 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 (connection: exporter->connection, name: sender, flags: G_BUS_NAME_WATCHER_FLAGS_NONE,
600 NULL, name_vanished_handler: g_menu_exporter_name_vanished, user_data: exporter, NULL);
601 remote = g_menu_exporter_remote_new (exporter, watch_id);
602 g_hash_table_insert (hash_table: exporter->remotes, key: g_strdup (str: sender), value: remote);
603 }
604 else
605 remote = exporter->peer_remote =
606 g_menu_exporter_remote_new (exporter, watch_id: 0);
607 }
608
609 g_variant_builder_init (builder: &builder, G_VARIANT_TYPE ("(a(uuaa{sv}))"));
610
611 g_variant_builder_open (builder: &builder, G_VARIANT_TYPE ("a(uuaa{sv})"));
612
613 g_variant_iter_init (iter: &iter, value: group_ids);
614 while (g_variant_iter_next (iter: &iter, format_string: "u", &id))
615 g_menu_exporter_remote_subscribe (remote, group_id: id, builder: &builder);
616
617 g_variant_builder_close (builder: &builder);
618
619 return g_variant_builder_end (builder: &builder);
620}
621
622static void
623g_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 (hash_table: exporter->remotes, key: sender);
633 else
634 remote = exporter->peer_remote;
635
636 if (remote == NULL)
637 return;
638
639 g_variant_iter_init (iter: &iter, value: group_ids);
640 while (g_variant_iter_next (iter: &iter, format_string: "u", &id))
641 g_menu_exporter_remote_unsubscribe (remote, group_id: id);
642
643 if (!g_menu_exporter_remote_has_subscriptions (remote))
644 {
645 if (sender != NULL)
646 g_hash_table_remove (hash_table: exporter->remotes, key: sender);
647 else
648 g_clear_pointer (&exporter->peer_remote, g_menu_exporter_remote_free);
649 }
650}
651
652static void
653g_menu_exporter_report (GMenuExporter *exporter,
654 GVariant *report)
655{
656 GVariantBuilder builder;
657
658 g_variant_builder_init (builder: &builder, G_VARIANT_TYPE_TUPLE);
659 g_variant_builder_open (builder: &builder, G_VARIANT_TYPE_ARRAY);
660 g_variant_builder_add_value (builder: &builder, value: report);
661 g_variant_builder_close (builder: &builder);
662
663 g_dbus_connection_emit_signal (connection: exporter->connection,
664 NULL,
665 object_path: exporter->object_path,
666 interface_name: "org.gtk.Menus", signal_name: "Changed",
667 parameters: g_variant_builder_end (builder: &builder),
668 NULL);
669}
670
671static void
672g_menu_exporter_remove_group (GMenuExporter *exporter,
673 guint id)
674{
675 g_hash_table_remove (hash_table: exporter->groups, GINT_TO_POINTER (id));
676}
677
678static GMenuExporterGroup *
679g_menu_exporter_lookup_group (GMenuExporter *exporter,
680 guint group_id)
681{
682 GMenuExporterGroup *group;
683
684 group = g_hash_table_lookup (hash_table: exporter->groups, GINT_TO_POINTER (group_id));
685
686 if (group == NULL)
687 {
688 group = g_menu_exporter_group_new (exporter, id: group_id);
689 g_hash_table_insert (hash_table: exporter->groups, GINT_TO_POINTER (group_id), value: group);
690 }
691
692 return group;
693}
694
695static GMenuExporterGroup *
696g_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 (hash_table: exporter->groups, GINT_TO_POINTER (id), value: group);
704
705 return group;
706}
707
708static void
709g_menu_exporter_free (gpointer user_data)
710{
711 GMenuExporter *exporter = user_data;
712
713 g_menu_exporter_menu_free (menu: exporter->root);
714 g_clear_pointer (&exporter->peer_remote, g_menu_exporter_remote_free);
715 g_hash_table_unref (hash_table: exporter->remotes);
716 g_hash_table_unref (hash_table: exporter->groups);
717 g_object_unref (object: exporter->connection);
718 g_free (mem: exporter->object_path);
719
720 g_slice_free (GMenuExporter, exporter);
721}
722
723static void
724g_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 (value: parameters, index_: 0);
737
738 if (g_str_equal (v1: method_name, v2: "Start"))
739 g_dbus_method_invocation_return_value (invocation, parameters: g_menu_exporter_subscribe (exporter, sender, group_ids));
740
741 else if (g_str_equal (v1: method_name, v2: "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 (value: 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 */
779guint
780g_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, NULL, NULL, { 0 }
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, interface_info: org_gtk_Menus_get_interface (),
794 vtable: &vtable, user_data: exporter, user_data_free_func: 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 (str: object_path);
804 exporter->groups = g_hash_table_new (NULL, NULL);
805 exporter->remotes = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal, key_destroy_func: g_free, value_destroy_func: g_menu_exporter_remote_free);
806 exporter->root = g_menu_exporter_group_add_menu (group: g_menu_exporter_create_group (exporter), model: 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 */
825void
826g_dbus_connection_unexport_menu_model (GDBusConnection *connection,
827 guint export_id)
828{
829 g_dbus_connection_unregister_object (connection, registration_id: export_id);
830}
831
832/* {{{1 Epilogue */
833/* vim:set foldmethod=marker: */
834

source code of gtk/subprojects/glib/gio/gmenuexporter.c