Improve docs
[glib.git] / gio / gmenuexporter.c
blob6d9df454d1acbc780dab0247baeb1ea2810daebe
1 /*
2 * Copyright © 2011 Canonical Ltd.
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.
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.
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.
19 * Author: Ryan Lortie <desrt@desrt.ca>
22 #include "gmenuexporter.h"
24 #include "gdbusmethodinvocation.h"
25 #include "gdbusintrospection.h"
26 #include "gdbusnamewatching.h"
27 #include "gdbuserror.h"
29 /**
30 * SECTION:gmenuexporter
31 * @title: GMenuModel exporter
32 * @short_description: Export GMenuModels on D-Bus
33 * @see_also: #GMenuModel, #GMenuProxy
35 * These functions support exporting a #GMenuModel on D-Bus.
36 * The D-Bus interface that is used is a private implementation
37 * detail.
39 * To access an exported #GMenuModel remotely, use
40 * g_menu_proxy_get() to obtain a #GMenuProxy.
43 /* {{{1 D-Bus Interface description */
45 /* The org.gtk.Menus interface
46 * ===========================
48 * The interface is primarily concerned with three things:
50 * - communicating menus to the client
51 * - establishing links between menus and other menus
52 * - notifying clients of changes
54 * As a basic principle, it is recognised that the menu structure
55 * of an application is often large. It is also recognised that some
56 * menus are liable to frequently change without the user ever having
57 * opened the menu. For both of these reasons, the individual menus are
58 * arranged into subscription groups. Each subscription group is specified
59 * by an unsigned integer. The assignment of integers need not be consecutive.
61 * Within a subscription group there are multiple menus. Each menu is
62 * identified with an unsigned integer, unique to its subscription group.
64 * By convention, the primary menu is numbered 0 without subscription group 0.
66 * Actionable menu items (ie: those that produce some effect in the
67 * application when they are activated) have a related action, specified by
68 * a string. This string specifies the name of the action, according to the
69 * org.gtk.Actions interface, at the same object path as the menu.
71 * Methods
72 * -------
74 * Start :: (au) → (a(uuaa{sv}))
76 * The Start method is used to indicate that a client is interested in
77 * tracking and displaying the content of the menus of a particular list
78 * of subscription groups.
80 * Most typically, the client will request subscription group 0 to start.
82 * The call has two effects. First, it replies with all menus defined
83 * within the requested subscription groups. The format of the reply is
84 * an array of tuples, where the items in each tuple are:
85 * - the subscription group of the menu
86 * - the number of the menu within that group
87 * - an array of menu items
89 * Each menu item is a dictionary of attributes (a{sv}).
91 * Secondly, this call has a side effect: it atomically requests that
92 * the Changed signal start to be emitted for the requested subscription
93 * group. Each group has a subscription count and only signals changes
94 * on itself when this count is greater than zero.
96 * If a group is specified multiple times then the result is that the
97 * contents of that group is only returned once, but the subscription
98 * count is increased multiple times.
100 * If a client disconnects from the bus while holding subscriptions then
101 * its subscriptions will be cancelled. This prevents "leaking" subscriptions
102 * in the case of crashes and is also useful for applications that want
103 * to exit without manually cleaning up.
105 * End :: (au)
107 * The End method reverses the previous effects of a call to Start.
109 * When clients are no longer interested in the contents of a subscription
110 * group, they should call the End method.
112 * The parameter lists the subscription groups. A subscription group
113 * needs to be cancelled the same number of times as it was requested.
114 * For this reason, it might make sense to specify the same subscription
115 * group multiple times (if multiple Start calls were made for this group).
117 * Signals
118 * -------
120 * Changed :: (a(uuuuaa{sv}))
122 * The changed signal indicates changes to a particular menu.
124 * The changes come as an array of tuples where the items in each tuple are:
125 * - the subscription group of the menu
126 * - the number of the menu within that group
127 * - the position in the menu at which to make the change
128 * - the number of items to delete from that position
129 * - a list of new items to insert at that position
131 * Each new menu item is a dictionary of attributes (a{sv}).
133 * Attributes
134 * ----------
136 * label (string): the label to display
137 * action (string): the name of the action
138 * target (variant): the parameter to pass when activating the action
139 * :section ((uu)): the menu to use to populate that section, specified
140 * as a pair of subscription group and menu within that group
141 * :submenu ((uu)): the menu to use as a submenu, specified
142 * as a pair of subscription group and menu within that group
145 static GDBusInterfaceInfo *
146 org_gtk_Menus_get_interface (void)
148 static GDBusInterfaceInfo *interface_info;
150 if (interface_info == NULL)
152 GError *error = NULL;
153 GDBusNodeInfo *info;
155 info = g_dbus_node_info_new_for_xml ("<node>"
156 " <interface name='org.gtk.Menus'>"
157 " <method name='Start'>"
158 " <arg type='au' name='groups' direction='in'/>"
159 " <arg type='a(uuaa{sv})' name='content' direction='out'/>"
160 " </method>"
161 " <method name='End'>"
162 " <arg type='au' name='groups' direction='in'/>"
163 " </method>"
164 " <signal name='Changed'>"
165 " arg type='a(uuuuaa{sv})' name='changes'/>"
166 " </signal>"
167 " </interface>"
168 "</node>", &error);
169 if (info == NULL)
170 g_error ("%s\n", error->message);
171 interface_info = g_dbus_node_info_lookup_interface (info, "org.gtk.Menus");
172 g_assert (interface_info != NULL);
173 g_dbus_interface_info_ref (interface_info);
174 g_dbus_node_info_unref (info);
177 return interface_info;
180 /* {{{1 Forward declarations */
181 typedef struct _GMenuExporterMenu GMenuExporterMenu;
182 typedef struct _GMenuExporterLink GMenuExporterLink;
183 typedef struct _GMenuExporterGroup GMenuExporterGroup;
184 typedef struct _GMenuExporterRemote GMenuExporterRemote;
185 typedef struct _GMenuExporterWatch GMenuExporterWatch;
186 typedef struct _GMenuExporter GMenuExporter;
188 static gboolean g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group);
189 static guint g_menu_exporter_group_get_id (GMenuExporterGroup *group);
190 static GMenuExporter * g_menu_exporter_group_get_exporter (GMenuExporterGroup *group);
191 static GMenuExporterMenu * g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
192 GMenuModel *model);
193 static void g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
194 guint id);
196 static GMenuExporterGroup * g_menu_exporter_create_group (GMenuExporter *exporter);
197 static GMenuExporterGroup * g_menu_exporter_lookup_group (GMenuExporter *exporter,
198 guint group_id);
199 static void g_menu_exporter_report (GMenuExporter *exporter,
200 GVariant *report);
201 static void g_menu_exporter_remove_group (GMenuExporter *exporter,
202 guint id);
204 /* {{{1 GMenuExporterLink, GMenuExporterMenu */
206 struct _GMenuExporterMenu
208 GMenuExporterGroup *group;
209 guint id;
211 GMenuModel *model;
212 gulong handler_id;
213 GSequence *item_links;
216 struct _GMenuExporterLink
218 gchar *name;
219 GMenuExporterMenu *menu;
220 GMenuExporterLink *next;
223 static void
224 g_menu_exporter_menu_free (GMenuExporterMenu *menu)
226 g_menu_exporter_group_remove_menu (menu->group, menu->id);
228 if (menu->handler_id != 0)
229 g_signal_handler_disconnect (menu->model, menu->handler_id);
231 if (menu->item_links != NULL)
232 g_sequence_free (menu->item_links);
234 g_object_unref (menu->model);
236 g_slice_free (GMenuExporterMenu, menu);
239 static void
240 g_menu_exporter_link_free (gpointer data)
242 GMenuExporterLink *link = data;
244 while (link != NULL)
246 GMenuExporterLink *tmp = link;
247 link = tmp->next;
249 g_menu_exporter_menu_free (tmp->menu);
250 g_free (tmp->name);
252 g_slice_free (GMenuExporterLink, tmp);
256 static GMenuExporterLink *
257 g_menu_exporter_menu_create_links (GMenuExporterMenu *menu,
258 gint position)
260 GMenuExporterLink *list = NULL;
261 GMenuLinkIter *iter;
262 const char *name;
263 GMenuModel *model;
265 iter = g_menu_model_iterate_item_links (menu->model, position);
267 while (g_menu_link_iter_get_next (iter, &name, &model))
269 GMenuExporterGroup *group;
270 GMenuExporterLink *tmp;
272 if (0) /* [magic] */
273 group = g_menu_exporter_create_group (g_menu_exporter_group_get_exporter (menu->group));
274 else
275 group = menu->group;
277 tmp = g_slice_new (GMenuExporterLink);
278 tmp->name = g_strconcat (":", name, NULL);
279 tmp->menu = g_menu_exporter_group_add_menu (group, model);
280 tmp->next = list;
281 list = tmp;
283 g_object_unref (model);
286 g_object_unref (iter);
288 return list;
291 static GVariant *
292 g_menu_exporter_menu_describe_item (GMenuExporterMenu *menu,
293 gint position)
295 GMenuAttributeIter *attr_iter;
296 GVariantBuilder builder;
297 GSequenceIter *iter;
298 GMenuExporterLink *link;
299 const char *name;
300 GVariant *value;
302 g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
304 attr_iter = g_menu_model_iterate_item_attributes (menu->model, position);
305 while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
307 g_variant_builder_add (&builder, "{sv}", name, value);
308 g_variant_unref (value);
310 g_object_unref (attr_iter);
312 iter = g_sequence_get_iter_at_pos (menu->item_links, position);
313 for (link = g_sequence_get (iter); link; link = link->next)
314 g_variant_builder_add (&builder, "{sv}", link->name,
315 g_variant_new ("(uu)", g_menu_exporter_group_get_id (link->menu->group), link->menu->id));
317 return g_variant_builder_end (&builder);
320 static GVariant *
321 g_menu_exporter_menu_list (GMenuExporterMenu *menu)
323 GVariantBuilder builder;
324 gint i, n;
326 g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
328 n = g_sequence_get_length (menu->item_links);
329 for (i = 0; i < n; i++)
330 g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
332 return g_variant_builder_end (&builder);
335 static void
336 g_menu_exporter_menu_items_changed (GMenuModel *model,
337 gint position,
338 gint removed,
339 gint added,
340 gpointer user_data)
342 GMenuExporterMenu *menu = user_data;
343 GSequenceIter *point;
344 gint i;
346 g_assert (menu->model == model);
347 g_assert (menu->item_links != NULL);
348 g_assert (position + removed <= g_sequence_get_length (menu->item_links));
350 point = g_sequence_get_iter_at_pos (menu->item_links, position + removed);
351 g_sequence_remove_range (g_sequence_get_iter_at_pos (menu->item_links, position), point);
353 for (i = position; i < position + added; i++)
354 g_sequence_insert_before (point, g_menu_exporter_menu_create_links (menu, i));
356 if (g_menu_exporter_group_is_subscribed (menu->group))
358 GVariantBuilder builder;
360 g_variant_builder_init (&builder, G_VARIANT_TYPE ("(uuuuaa{sv})"));
361 g_variant_builder_add (&builder, "u", g_menu_exporter_group_get_id (menu->group));
362 g_variant_builder_add (&builder, "u", menu->id);
363 g_variant_builder_add (&builder, "u", position);
364 g_variant_builder_add (&builder, "u", removed);
366 g_variant_builder_open (&builder, G_VARIANT_TYPE ("aa{sv}"));
367 for (i = position; i < position + added; i++)
368 g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
369 g_variant_builder_close (&builder);
371 g_menu_exporter_report (g_menu_exporter_group_get_exporter (menu->group), g_variant_builder_end (&builder));
375 static void
376 g_menu_exporter_menu_prepare (GMenuExporterMenu *menu)
378 gint n_items;
380 g_assert (menu->item_links == NULL);
382 if (g_menu_model_is_mutable (menu->model))
383 menu->handler_id = g_signal_connect (menu->model, "items-changed",
384 G_CALLBACK (g_menu_exporter_menu_items_changed), menu);
386 menu->item_links = g_sequence_new (g_menu_exporter_link_free);
388 n_items = g_menu_model_get_n_items (menu->model);
389 if (n_items)
390 g_menu_exporter_menu_items_changed (menu->model, 0, 0, n_items, menu);
393 static GMenuExporterMenu *
394 g_menu_exporter_menu_new (GMenuExporterGroup *group,
395 guint id,
396 GMenuModel *model)
398 GMenuExporterMenu *menu;
400 menu = g_slice_new0 (GMenuExporterMenu);
401 menu->group = group;
402 menu->id = id;
403 menu->model = g_object_ref (model);
405 return menu;
408 /* {{{1 GMenuExporterGroup */
410 struct _GMenuExporterGroup
412 GMenuExporter *exporter;
413 guint id;
415 GHashTable *menus;
416 guint next_menu_id;
417 gboolean prepared;
419 gint subscribed;
422 static void
423 g_menu_exporter_group_check_if_useless (GMenuExporterGroup *group)
425 if (g_hash_table_size (group->menus) == 0 && group->subscribed == 0)
427 g_menu_exporter_remove_group (group->exporter, group->id);
429 g_hash_table_unref (group->menus);
431 g_slice_free (GMenuExporterGroup, group);
435 static void
436 g_menu_exporter_group_subscribe (GMenuExporterGroup *group,
437 GVariantBuilder *builder)
439 GHashTableIter iter;
440 gpointer key, val;
442 if (!group->prepared)
444 GMenuExporterMenu *menu;
446 /* set this first, so that any menus created during the
447 * preparation of the first menu also end up in the prepared
448 * state.
449 * */
450 group->prepared = TRUE;
452 menu = g_hash_table_lookup (group->menus, 0);
453 g_menu_exporter_menu_prepare (menu);
456 group->subscribed++;
458 g_hash_table_iter_init (&iter, group->menus);
459 while (g_hash_table_iter_next (&iter, &key, &val))
461 guint id = GPOINTER_TO_INT (key);
462 GMenuExporterMenu *menu = val;
464 if (g_sequence_get_length (menu->item_links))
466 g_variant_builder_open (builder, G_VARIANT_TYPE ("(uuaa{sv})"));
467 g_variant_builder_add (builder, "u", group->id);
468 g_variant_builder_add (builder, "u", id);
469 g_variant_builder_add_value (builder, g_menu_exporter_menu_list (menu));
470 g_variant_builder_close (builder);
475 static void
476 g_menu_exporter_group_unsubscribe (GMenuExporterGroup *group,
477 gint count)
479 g_assert (group->subscribed >= count);
481 group->subscribed -= count;
483 g_menu_exporter_group_check_if_useless (group);
486 static GMenuExporter *
487 g_menu_exporter_group_get_exporter (GMenuExporterGroup *group)
489 return group->exporter;
492 static gboolean
493 g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group)
495 return group->subscribed > 0;
498 static guint
499 g_menu_exporter_group_get_id (GMenuExporterGroup *group)
501 return group->id;
504 static void
505 g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
506 guint id)
508 g_hash_table_remove (group->menus, GINT_TO_POINTER (id));
510 g_menu_exporter_group_check_if_useless (group);
513 static GMenuExporterMenu *
514 g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
515 GMenuModel *model)
517 GMenuExporterMenu *menu;
518 guint id;
520 id = group->next_menu_id++;
521 menu = g_menu_exporter_menu_new (group, id, model);
522 g_hash_table_insert (group->menus, GINT_TO_POINTER (id), menu);
524 if (group->prepared)
525 g_menu_exporter_menu_prepare (menu);
527 return menu;
530 static GMenuExporterGroup *
531 g_menu_exporter_group_new (GMenuExporter *exporter,
532 guint id)
534 GMenuExporterGroup *group;
536 group = g_slice_new0 (GMenuExporterGroup);
537 group->menus = g_hash_table_new (NULL, NULL);
538 group->exporter = exporter;
539 group->id = id;
541 return group;
544 /* {{{1 GMenuExporterRemote */
546 struct _GMenuExporterRemote
548 GMenuExporter *exporter;
549 GHashTable *watches;
550 guint watch_id;
553 static void
554 g_menu_exporter_remote_subscribe (GMenuExporterRemote *remote,
555 guint group_id,
556 GVariantBuilder *builder)
558 GMenuExporterGroup *group;
559 guint count;
561 count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
562 g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count + 1));
564 group = g_menu_exporter_lookup_group (remote->exporter, group_id);
565 g_menu_exporter_group_subscribe (group, builder);
568 static void
569 g_menu_exporter_remote_unsubscribe (GMenuExporterRemote *remote,
570 guint group_id)
572 GMenuExporterGroup *group;
573 guint count;
575 count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
577 if (count == 0)
578 return;
580 if (count != 1)
581 g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count - 1));
582 else
583 g_hash_table_remove (remote->watches, GINT_TO_POINTER (group_id));
585 group = g_menu_exporter_lookup_group (remote->exporter, group_id);
586 g_menu_exporter_group_unsubscribe (group, 1);
589 static gboolean
590 g_menu_exporter_remote_has_subscriptions (GMenuExporterRemote *remote)
592 return g_hash_table_size (remote->watches) != 0;
595 static void
596 g_menu_exporter_remote_free (gpointer data)
598 GMenuExporterRemote *remote = data;
599 GHashTableIter iter;
600 gpointer key, val;
602 g_hash_table_iter_init (&iter, remote->watches);
603 while (g_hash_table_iter_next (&iter, &key, &val))
605 GMenuExporterGroup *group;
607 group = g_menu_exporter_lookup_group (remote->exporter, GPOINTER_TO_INT (key));
608 g_menu_exporter_group_unsubscribe (group, GPOINTER_TO_INT (val));
611 g_bus_unwatch_name (remote->watch_id);
612 g_hash_table_unref (remote->watches);
614 g_slice_free (GMenuExporterRemote, remote);
617 static GMenuExporterRemote *
618 g_menu_exporter_remote_new (GMenuExporter *exporter,
619 guint watch_id)
621 GMenuExporterRemote *remote;
623 remote = g_slice_new0 (GMenuExporterRemote);
624 remote->exporter = exporter;
625 remote->watches = g_hash_table_new (NULL, NULL);
626 remote->watch_id = watch_id;
628 return remote;
631 /* {{{1 GMenuExporter */
633 struct _GMenuExporter
635 GDBusConnection *connection;
636 gchar *object_path;
637 guint registration_id;
638 GHashTable *groups;
639 guint next_group_id;
641 GMenuExporterMenu *root;
642 GHashTable *remotes;
645 static void
646 g_menu_exporter_name_vanished (GDBusConnection *connection,
647 const gchar *name,
648 gpointer user_data)
650 GMenuExporter *exporter = user_data;
652 g_assert (exporter->connection == connection);
654 g_hash_table_remove (exporter->remotes, name);
657 static GVariant *
658 g_menu_exporter_subscribe (GMenuExporter *exporter,
659 const gchar *sender,
660 GVariant *group_ids)
662 GMenuExporterRemote *remote;
663 GVariantBuilder builder;
664 GVariantIter iter;
665 guint32 id;
667 remote = g_hash_table_lookup (exporter->remotes, sender);
669 if (remote == NULL)
671 guint watch_id;
673 watch_id = g_bus_watch_name_on_connection (exporter->connection, sender, G_BUS_NAME_WATCHER_FLAGS_NONE,
674 NULL, g_menu_exporter_name_vanished, exporter, NULL);
675 remote = g_menu_exporter_remote_new (exporter, watch_id);
676 g_hash_table_insert (exporter->remotes, g_strdup (sender), remote);
679 g_variant_builder_init (&builder, G_VARIANT_TYPE ("(a(uuaa{sv}))"));
681 g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(uuaa{sv})"));
683 g_variant_iter_init (&iter, group_ids);
684 while (g_variant_iter_next (&iter, "u", &id))
685 g_menu_exporter_remote_subscribe (remote, id, &builder);
687 g_variant_builder_close (&builder);
689 return g_variant_builder_end (&builder);
692 static void
693 g_menu_exporter_unsubscribe (GMenuExporter *exporter,
694 const gchar *sender,
695 GVariant *group_ids)
697 GMenuExporterRemote *remote;
698 GVariantIter iter;
699 guint32 id;
701 remote = g_hash_table_lookup (exporter->remotes, sender);
703 if (remote == NULL)
704 return;
706 g_variant_iter_init (&iter, group_ids);
707 while (g_variant_iter_next (&iter, "u", &id))
708 g_menu_exporter_remote_unsubscribe (remote, id);
710 if (!g_menu_exporter_remote_has_subscriptions (remote))
711 g_hash_table_remove (exporter->remotes, sender);
714 static void
715 g_menu_exporter_report (GMenuExporter *exporter,
716 GVariant *report)
718 GVariantBuilder builder;
720 g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
721 g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY);
722 g_variant_builder_add_value (&builder, report);
723 g_variant_builder_close (&builder);
725 g_dbus_connection_emit_signal (exporter->connection,
726 NULL,
727 exporter->object_path,
728 "org.gtk.Menus", "Changed",
729 g_variant_builder_end (&builder),
730 NULL);
733 static void
734 g_menu_exporter_remove_group (GMenuExporter *exporter,
735 guint id)
737 g_hash_table_remove (exporter->groups, GINT_TO_POINTER (id));
740 static GMenuExporterGroup *
741 g_menu_exporter_lookup_group (GMenuExporter *exporter,
742 guint group_id)
744 GMenuExporterGroup *group;
746 group = g_hash_table_lookup (exporter->groups, GINT_TO_POINTER (group_id));
748 if (group == NULL)
750 group = g_menu_exporter_group_new (exporter, group_id);
751 g_hash_table_insert (exporter->groups, GINT_TO_POINTER (group_id), group);
754 return group;
757 static GMenuExporterGroup *
758 g_menu_exporter_create_group (GMenuExporter *exporter)
760 GMenuExporterGroup *group;
761 guint id;
763 id = exporter->next_group_id++;
764 group = g_menu_exporter_group_new (exporter, id);
765 g_hash_table_insert (exporter->groups, GINT_TO_POINTER (id), group);
767 return group;
770 static void
771 g_menu_exporter_free (GMenuExporter *exporter)
773 g_dbus_connection_unregister_object (exporter->connection, exporter->registration_id);
774 g_menu_exporter_menu_free (exporter->root);
775 g_hash_table_unref (exporter->remotes);
776 g_hash_table_unref (exporter->groups);
777 g_object_unref (exporter->connection);
778 g_free (exporter->object_path);
780 g_slice_free (GMenuExporter, exporter);
783 static void
784 g_menu_exporter_method_call (GDBusConnection *connection,
785 const gchar *sender,
786 const gchar *object_path,
787 const gchar *interface_name,
788 const gchar *method_name,
789 GVariant *parameters,
790 GDBusMethodInvocation *invocation,
791 gpointer user_data)
793 GMenuExporter *exporter = user_data;
794 GVariant *group_ids;
796 group_ids = g_variant_get_child_value (parameters, 0);
798 if (g_str_equal (method_name, "Start"))
799 g_dbus_method_invocation_return_value (invocation, g_menu_exporter_subscribe (exporter, sender, group_ids));
801 else if (g_str_equal (method_name, "End"))
803 g_menu_exporter_unsubscribe (exporter, sender, group_ids);
804 g_dbus_method_invocation_return_value (invocation, NULL);
807 else
808 g_assert_not_reached ();
810 g_variant_unref (group_ids);
813 static GDBusConnection *
814 g_menu_exporter_get_connection (GMenuExporter *exporter)
816 return exporter->connection;
819 static const gchar *
820 g_menu_exporter_get_object_path (GMenuExporter *exporter)
822 return exporter->object_path;
825 static GMenuExporter *
826 g_menu_exporter_new (GDBusConnection *connection,
827 const gchar *object_path,
828 GMenuModel *model,
829 GError **error)
831 const GDBusInterfaceVTable vtable = {
832 g_menu_exporter_method_call,
834 GMenuExporter *exporter;
835 guint id;
837 exporter = g_slice_new0 (GMenuExporter);
839 id = g_dbus_connection_register_object (connection, object_path, org_gtk_Menus_get_interface (),
840 &vtable, exporter, NULL, error);
842 if (id == 0)
844 g_slice_free (GMenuExporter, exporter);
845 return NULL;
848 exporter->connection = g_object_ref (connection);
849 exporter->object_path = g_strdup (object_path);
850 exporter->registration_id = id;
851 exporter->groups = g_hash_table_new (NULL, NULL);
852 exporter->remotes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_menu_exporter_remote_free);
853 exporter->root = g_menu_exporter_group_add_menu (g_menu_exporter_create_group (exporter), model);
855 return exporter;
858 /* {{{1 Public API */
860 static GHashTable *g_menu_exporter_exported_menus;
863 * g_menu_model_dbus_export_start:
864 * @connection: a #GDBusConnection
865 * @object_path: a D-Bus object path
866 * @menu: a #GMenuModel
867 * @error: return location for an error, or %NULL
869 * Exports @menu on @connection at @object_path.
871 * The implemented D-Bus API should be considered private.
872 * It is subject to change in the future.
874 * A given menu model can only be exported on one object path
875 * and an object path can only have one action group exported
876 * on it. If either constraint is violated, the export will
877 * fail and %FALSE will be returned (with @error set accordingly).
879 * Use g_menu_model_dbus_export_stop() to stop exporting @menu
880 * or g_menu_model_dbus_export_query() to find out if and where
881 * a given menu model is exported.
883 * Returns: %TRUE if the export is successful, or %FALSE (with
884 * @error set) in the event of a failure.
886 gboolean
887 g_menu_model_dbus_export_start (GDBusConnection *connection,
888 const gchar *object_path,
889 GMenuModel *menu,
890 GError **error)
892 GMenuExporter *exporter;
894 if G_UNLIKELY (g_menu_exporter_exported_menus == NULL)
895 g_menu_exporter_exported_menus = g_hash_table_new (NULL, NULL);
897 if G_UNLIKELY (g_hash_table_lookup (g_menu_exporter_exported_menus, menu))
899 g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FILE_EXISTS, "The given GMenuModel has already been exported");
900 return FALSE;
903 exporter = g_menu_exporter_new (connection, object_path, menu, error);
905 if (exporter == NULL)
906 return FALSE;
908 g_hash_table_insert (g_menu_exporter_exported_menus, menu, exporter);
910 return TRUE;
914 * g_menu_model_dbus_export_stop:
915 * @menu: a #GMenuModel
917 * Stops the export of @menu.
919 * This reverses the effect of a previous call to
920 * g_menu_model_dbus_export_start() for @menu.
922 * Returns: %TRUE if an export was stopped or %FALSE
923 * if @menu was not exported in the first place
925 gboolean
926 g_menu_model_dbus_export_stop (GMenuModel *menu)
928 GMenuExporter *exporter;
930 if G_UNLIKELY (g_menu_exporter_exported_menus == NULL)
931 return FALSE;
933 exporter = g_hash_table_lookup (g_menu_exporter_exported_menus, menu);
934 if G_UNLIKELY (exporter == NULL)
935 return FALSE;
937 g_hash_table_remove (g_menu_exporter_exported_menus, menu);
938 g_menu_exporter_free (exporter);
940 return TRUE;
944 * g_menu_model_dbus_export_query:
945 * @menu: a #GMenuModel
946 * @connection: (out): the #GDBusConnection used for exporting
947 * @object_path: (out): the object path used for exporting
949 * Queries if and where @menu is exported.
951 * If @menu is exported, %TRUE is returned. If @connection is
952 * non-%NULL then it is set to the #GDBusConnection used for
953 * the export. If @object_path is non-%NULL then it is set to
954 * the object path.
956 * If the @menu is not exported, %FALSE is returned and
957 * @connection and @object_path remain unmodified.
959 * Returns: %TRUE if @menu was exported, else %FALSE
961 gboolean
962 g_menu_model_dbus_export_query (GMenuModel *menu,
963 GDBusConnection **connection,
964 const gchar **object_path)
966 GMenuExporter *exporter;
968 if (g_menu_exporter_exported_menus == NULL)
969 return FALSE;
971 exporter = g_hash_table_lookup (g_menu_exporter_exported_menus, menu);
972 if (exporter == NULL)
973 return FALSE;
975 if (connection)
976 *connection = g_menu_exporter_get_connection (exporter);
978 if (object_path)
979 *object_path = g_menu_exporter_get_object_path (exporter);
981 return TRUE;
984 /* {{{1 Epilogue */
985 /* vim:set foldmethod=marker: */