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,
19 * Author: Ryan Lortie <desrt@desrt.ca>
24 #include "gmenuexporter.h"
26 #include "gdbusmethodinvocation.h"
27 #include "gdbusintrospection.h"
28 #include "gdbusnamewatching.h"
29 #include "gdbuserror.h"
32 * SECTION:gmenuexporter
33 * @title: GMenuModel exporter
34 * @short_description: Export GMenuModels on D-Bus
36 * @see_also: #GMenuModel, #GDBusMenuModel
38 * These functions support exporting a #GMenuModel on D-Bus.
39 * The D-Bus interface that is used is a private implementation
42 * To access an exported #GMenuModel remotely, use
43 * g_dbus_menu_model_get() to obtain a #GDBusMenuModel.
46 /* {{{1 D-Bus Interface description */
48 /* For documentation of this interface, see
49 * http://live.gnome.org/GTK+/GApplication-dbus-apis
52 static GDBusInterfaceInfo *
53 org_gtk_Menus_get_interface (void)
55 static GDBusInterfaceInfo *interface_info;
57 if (interface_info == NULL)
62 info = g_dbus_node_info_new_for_xml ("<node>"
63 " <interface name='org.gtk.Menus'>"
64 " <method name='Start'>"
65 " <arg type='au' name='groups' direction='in'/>"
66 " <arg type='a(uuaa{sv})' name='content' direction='out'/>"
68 " <method name='End'>"
69 " <arg type='au' name='groups' direction='in'/>"
71 " <signal name='Changed'>"
72 " arg type='a(uuuuaa{sv})' name='changes'/>"
77 g_error ("%s\n", error->message);
78 interface_info = g_dbus_node_info_lookup_interface (info, "org.gtk.Menus");
79 g_assert (interface_info != NULL);
80 g_dbus_interface_info_ref (interface_info);
81 g_dbus_node_info_unref (info);
84 return interface_info;
87 /* {{{1 Forward declarations */
88 typedef struct _GMenuExporterMenu GMenuExporterMenu;
89 typedef struct _GMenuExporterLink GMenuExporterLink;
90 typedef struct _GMenuExporterGroup GMenuExporterGroup;
91 typedef struct _GMenuExporterRemote GMenuExporterRemote;
92 typedef struct _GMenuExporterWatch GMenuExporterWatch;
93 typedef struct _GMenuExporter GMenuExporter;
95 static gboolean g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group);
96 static guint g_menu_exporter_group_get_id (GMenuExporterGroup *group);
97 static GMenuExporter * g_menu_exporter_group_get_exporter (GMenuExporterGroup *group);
98 static GMenuExporterMenu * g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
100 static void g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
103 static GMenuExporterGroup * g_menu_exporter_create_group (GMenuExporter *exporter);
104 static GMenuExporterGroup * g_menu_exporter_lookup_group (GMenuExporter *exporter,
106 static void g_menu_exporter_report (GMenuExporter *exporter,
108 static void g_menu_exporter_remove_group (GMenuExporter *exporter,
111 /* {{{1 GMenuExporterLink, GMenuExporterMenu */
113 struct _GMenuExporterMenu
115 GMenuExporterGroup *group;
120 GSequence *item_links;
123 struct _GMenuExporterLink
126 GMenuExporterMenu *menu;
127 GMenuExporterLink *next;
131 g_menu_exporter_menu_free (GMenuExporterMenu *menu)
133 g_menu_exporter_group_remove_menu (menu->group, menu->id);
135 if (menu->handler_id != 0)
136 g_signal_handler_disconnect (menu->model, menu->handler_id);
138 if (menu->item_links != NULL)
139 g_sequence_free (menu->item_links);
141 g_object_unref (menu->model);
143 g_slice_free (GMenuExporterMenu, menu);
147 g_menu_exporter_link_free (gpointer data)
149 GMenuExporterLink *link = data;
153 GMenuExporterLink *tmp = link;
156 g_menu_exporter_menu_free (tmp->menu);
159 g_slice_free (GMenuExporterLink, tmp);
163 static GMenuExporterLink *
164 g_menu_exporter_menu_create_links (GMenuExporterMenu *menu,
167 GMenuExporterLink *list = NULL;
172 iter = g_menu_model_iterate_item_links (menu->model, position);
174 while (g_menu_link_iter_get_next (iter, &name, &model))
176 GMenuExporterGroup *group;
177 GMenuExporterLink *tmp;
179 /* keep sections in the same group, but create new groups
182 if (!g_str_equal (name, "section"))
183 group = g_menu_exporter_create_group (g_menu_exporter_group_get_exporter (menu->group));
187 tmp = g_slice_new (GMenuExporterLink);
188 tmp->name = g_strconcat (":", name, NULL);
189 tmp->menu = g_menu_exporter_group_add_menu (group, model);
193 g_object_unref (model);
196 g_object_unref (iter);
202 g_menu_exporter_menu_describe_item (GMenuExporterMenu *menu,
205 GMenuAttributeIter *attr_iter;
206 GVariantBuilder builder;
208 GMenuExporterLink *link;
212 g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
214 attr_iter = g_menu_model_iterate_item_attributes (menu->model, position);
215 while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
217 g_variant_builder_add (&builder, "{sv}", name, value);
218 g_variant_unref (value);
220 g_object_unref (attr_iter);
222 iter = g_sequence_get_iter_at_pos (menu->item_links, position);
223 for (link = g_sequence_get (iter); link; link = link->next)
224 g_variant_builder_add (&builder, "{sv}", link->name,
225 g_variant_new ("(uu)", g_menu_exporter_group_get_id (link->menu->group), link->menu->id));
227 return g_variant_builder_end (&builder);
231 g_menu_exporter_menu_list (GMenuExporterMenu *menu)
233 GVariantBuilder builder;
236 g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
238 n = g_sequence_get_length (menu->item_links);
239 for (i = 0; i < n; i++)
240 g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
242 return g_variant_builder_end (&builder);
246 g_menu_exporter_menu_items_changed (GMenuModel *model,
252 GMenuExporterMenu *menu = user_data;
253 GSequenceIter *point;
256 g_assert (menu->model == model);
257 g_assert (menu->item_links != NULL);
258 g_assert (position + removed <= g_sequence_get_length (menu->item_links));
260 point = g_sequence_get_iter_at_pos (menu->item_links, position + removed);
261 g_sequence_remove_range (g_sequence_get_iter_at_pos (menu->item_links, position), point);
263 for (i = position; i < position + added; i++)
264 g_sequence_insert_before (point, g_menu_exporter_menu_create_links (menu, i));
266 if (g_menu_exporter_group_is_subscribed (menu->group))
268 GVariantBuilder builder;
270 g_variant_builder_init (&builder, G_VARIANT_TYPE ("(uuuuaa{sv})"));
271 g_variant_builder_add (&builder, "u", g_menu_exporter_group_get_id (menu->group));
272 g_variant_builder_add (&builder, "u", menu->id);
273 g_variant_builder_add (&builder, "u", position);
274 g_variant_builder_add (&builder, "u", removed);
276 g_variant_builder_open (&builder, G_VARIANT_TYPE ("aa{sv}"));
277 for (i = position; i < position + added; i++)
278 g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
279 g_variant_builder_close (&builder);
281 g_menu_exporter_report (g_menu_exporter_group_get_exporter (menu->group), g_variant_builder_end (&builder));
286 g_menu_exporter_menu_prepare (GMenuExporterMenu *menu)
290 g_assert (menu->item_links == NULL);
292 if (g_menu_model_is_mutable (menu->model))
293 menu->handler_id = g_signal_connect (menu->model, "items-changed",
294 G_CALLBACK (g_menu_exporter_menu_items_changed), menu);
296 menu->item_links = g_sequence_new (g_menu_exporter_link_free);
298 n_items = g_menu_model_get_n_items (menu->model);
300 g_menu_exporter_menu_items_changed (menu->model, 0, 0, n_items, menu);
303 static GMenuExporterMenu *
304 g_menu_exporter_menu_new (GMenuExporterGroup *group,
308 GMenuExporterMenu *menu;
310 menu = g_slice_new0 (GMenuExporterMenu);
313 menu->model = g_object_ref (model);
318 /* {{{1 GMenuExporterGroup */
320 struct _GMenuExporterGroup
322 GMenuExporter *exporter;
333 g_menu_exporter_group_check_if_useless (GMenuExporterGroup *group)
335 if (g_hash_table_size (group->menus) == 0 && group->subscribed == 0)
337 g_menu_exporter_remove_group (group->exporter, group->id);
339 g_hash_table_unref (group->menus);
341 g_slice_free (GMenuExporterGroup, group);
346 g_menu_exporter_group_subscribe (GMenuExporterGroup *group,
347 GVariantBuilder *builder)
352 if (!group->prepared)
354 GMenuExporterMenu *menu;
356 /* set this first, so that any menus created during the
357 * preparation of the first menu also end up in the prepared
360 group->prepared = TRUE;
362 menu = g_hash_table_lookup (group->menus, 0);
364 /* If the group was created by a subscription and does not yet
365 * exist, it won't have a root menu...
367 * That menu will be prepared if it is ever added (due to
368 * group->prepared == TRUE).
371 g_menu_exporter_menu_prepare (menu);
376 g_hash_table_iter_init (&iter, group->menus);
377 while (g_hash_table_iter_next (&iter, &key, &val))
379 guint id = GPOINTER_TO_INT (key);
380 GMenuExporterMenu *menu = val;
382 if (g_sequence_get_length (menu->item_links))
384 g_variant_builder_open (builder, G_VARIANT_TYPE ("(uuaa{sv})"));
385 g_variant_builder_add (builder, "u", group->id);
386 g_variant_builder_add (builder, "u", id);
387 g_variant_builder_add_value (builder, g_menu_exporter_menu_list (menu));
388 g_variant_builder_close (builder);
394 g_menu_exporter_group_unsubscribe (GMenuExporterGroup *group,
397 g_assert (group->subscribed >= count);
399 group->subscribed -= count;
401 g_menu_exporter_group_check_if_useless (group);
404 static GMenuExporter *
405 g_menu_exporter_group_get_exporter (GMenuExporterGroup *group)
407 return group->exporter;
411 g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group)
413 return group->subscribed > 0;
417 g_menu_exporter_group_get_id (GMenuExporterGroup *group)
423 g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
426 g_hash_table_remove (group->menus, GINT_TO_POINTER (id));
428 g_menu_exporter_group_check_if_useless (group);
431 static GMenuExporterMenu *
432 g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
435 GMenuExporterMenu *menu;
438 id = group->next_menu_id++;
439 menu = g_menu_exporter_menu_new (group, id, model);
440 g_hash_table_insert (group->menus, GINT_TO_POINTER (id), menu);
443 g_menu_exporter_menu_prepare (menu);
448 static GMenuExporterGroup *
449 g_menu_exporter_group_new (GMenuExporter *exporter,
452 GMenuExporterGroup *group;
454 group = g_slice_new0 (GMenuExporterGroup);
455 group->menus = g_hash_table_new (NULL, NULL);
456 group->exporter = exporter;
462 /* {{{1 GMenuExporterRemote */
464 struct _GMenuExporterRemote
466 GMenuExporter *exporter;
472 g_menu_exporter_remote_subscribe (GMenuExporterRemote *remote,
474 GVariantBuilder *builder)
476 GMenuExporterGroup *group;
479 count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
480 g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count + 1));
482 /* Group will be created (as empty/unsubscribed if it does not exist) */
483 group = g_menu_exporter_lookup_group (remote->exporter, group_id);
484 g_menu_exporter_group_subscribe (group, builder);
488 g_menu_exporter_remote_unsubscribe (GMenuExporterRemote *remote,
491 GMenuExporterGroup *group;
494 count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
500 g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count - 1));
502 g_hash_table_remove (remote->watches, GINT_TO_POINTER (group_id));
504 group = g_menu_exporter_lookup_group (remote->exporter, group_id);
505 g_menu_exporter_group_unsubscribe (group, 1);
509 g_menu_exporter_remote_has_subscriptions (GMenuExporterRemote *remote)
511 return g_hash_table_size (remote->watches) != 0;
515 g_menu_exporter_remote_free (gpointer data)
517 GMenuExporterRemote *remote = data;
521 g_hash_table_iter_init (&iter, remote->watches);
522 while (g_hash_table_iter_next (&iter, &key, &val))
524 GMenuExporterGroup *group;
526 group = g_menu_exporter_lookup_group (remote->exporter, GPOINTER_TO_INT (key));
527 g_menu_exporter_group_unsubscribe (group, GPOINTER_TO_INT (val));
530 g_bus_unwatch_name (remote->watch_id);
531 g_hash_table_unref (remote->watches);
533 g_slice_free (GMenuExporterRemote, remote);
536 static GMenuExporterRemote *
537 g_menu_exporter_remote_new (GMenuExporter *exporter,
540 GMenuExporterRemote *remote;
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;
550 /* {{{1 GMenuExporter */
552 struct _GMenuExporter
554 GDBusConnection *connection;
556 guint registration_id;
560 GMenuExporterMenu *root;
565 g_menu_exporter_name_vanished (GDBusConnection *connection,
569 GMenuExporter *exporter = user_data;
571 /* connection == NULL when we get called because the connection closed */
572 g_assert (exporter->connection == connection || connection == NULL);
574 g_hash_table_remove (exporter->remotes, name);
578 g_menu_exporter_subscribe (GMenuExporter *exporter,
582 GMenuExporterRemote *remote;
583 GVariantBuilder builder;
587 remote = g_hash_table_lookup (exporter->remotes, sender);
593 watch_id = g_bus_watch_name_on_connection (exporter->connection, sender, G_BUS_NAME_WATCHER_FLAGS_NONE,
594 NULL, g_menu_exporter_name_vanished, exporter, NULL);
595 remote = g_menu_exporter_remote_new (exporter, watch_id);
596 g_hash_table_insert (exporter->remotes, g_strdup (sender), remote);
599 g_variant_builder_init (&builder, G_VARIANT_TYPE ("(a(uuaa{sv}))"));
601 g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(uuaa{sv})"));
603 g_variant_iter_init (&iter, group_ids);
604 while (g_variant_iter_next (&iter, "u", &id))
605 g_menu_exporter_remote_subscribe (remote, id, &builder);
607 g_variant_builder_close (&builder);
609 return g_variant_builder_end (&builder);
613 g_menu_exporter_unsubscribe (GMenuExporter *exporter,
617 GMenuExporterRemote *remote;
621 remote = g_hash_table_lookup (exporter->remotes, sender);
626 g_variant_iter_init (&iter, group_ids);
627 while (g_variant_iter_next (&iter, "u", &id))
628 g_menu_exporter_remote_unsubscribe (remote, id);
630 if (!g_menu_exporter_remote_has_subscriptions (remote))
631 g_hash_table_remove (exporter->remotes, sender);
635 g_menu_exporter_report (GMenuExporter *exporter,
638 GVariantBuilder builder;
640 g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
641 g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY);
642 g_variant_builder_add_value (&builder, report);
643 g_variant_builder_close (&builder);
645 g_dbus_connection_emit_signal (exporter->connection,
647 exporter->object_path,
648 "org.gtk.Menus", "Changed",
649 g_variant_builder_end (&builder),
654 g_menu_exporter_remove_group (GMenuExporter *exporter,
657 g_hash_table_remove (exporter->groups, GINT_TO_POINTER (id));
660 static GMenuExporterGroup *
661 g_menu_exporter_lookup_group (GMenuExporter *exporter,
664 GMenuExporterGroup *group;
666 group = g_hash_table_lookup (exporter->groups, GINT_TO_POINTER (group_id));
670 group = g_menu_exporter_group_new (exporter, group_id);
671 g_hash_table_insert (exporter->groups, GINT_TO_POINTER (group_id), group);
677 static GMenuExporterGroup *
678 g_menu_exporter_create_group (GMenuExporter *exporter)
680 GMenuExporterGroup *group;
683 id = exporter->next_group_id++;
684 group = g_menu_exporter_group_new (exporter, id);
685 g_hash_table_insert (exporter->groups, GINT_TO_POINTER (id), group);
691 g_menu_exporter_free (gpointer user_data)
693 GMenuExporter *exporter = user_data;
695 g_menu_exporter_menu_free (exporter->root);
696 g_hash_table_unref (exporter->remotes);
697 g_hash_table_unref (exporter->groups);
698 g_object_unref (exporter->connection);
699 g_free (exporter->object_path);
701 g_slice_free (GMenuExporter, exporter);
705 g_menu_exporter_method_call (GDBusConnection *connection,
707 const gchar *object_path,
708 const gchar *interface_name,
709 const gchar *method_name,
710 GVariant *parameters,
711 GDBusMethodInvocation *invocation,
714 GMenuExporter *exporter = user_data;
717 group_ids = g_variant_get_child_value (parameters, 0);
719 if (g_str_equal (method_name, "Start"))
720 g_dbus_method_invocation_return_value (invocation, g_menu_exporter_subscribe (exporter, sender, group_ids));
722 else if (g_str_equal (method_name, "End"))
724 g_menu_exporter_unsubscribe (exporter, sender, group_ids);
725 g_dbus_method_invocation_return_value (invocation, NULL);
729 g_assert_not_reached ();
731 g_variant_unref (group_ids);
734 /* {{{1 Public API */
737 * g_dbus_connection_export_menu_model:
738 * @connection: a #GDBusConnection
739 * @object_path: a D-Bus object path
740 * @menu: a #GMenuModel
741 * @error: return location for an error, or %NULL
743 * Exports @menu on @connection at @object_path.
745 * The implemented D-Bus API should be considered private.
746 * It is subject to change in the future.
748 * An object path can only have one menu model exported on it. If this
749 * constraint is violated, the export will fail and 0 will be
750 * returned (with @error set accordingly).
752 * You can unexport the menu model using
753 * g_dbus_connection_unexport_menu_model() with the return value of
756 * Returns: the ID of the export (never zero), or 0 in case of failure
761 g_dbus_connection_export_menu_model (GDBusConnection *connection,
762 const gchar *object_path,
766 const GDBusInterfaceVTable vtable = {
767 g_menu_exporter_method_call,
769 GMenuExporter *exporter;
772 exporter = g_slice_new0 (GMenuExporter);
774 id = g_dbus_connection_register_object (connection, object_path, org_gtk_Menus_get_interface (),
775 &vtable, exporter, g_menu_exporter_free, error);
779 g_slice_free (GMenuExporter, exporter);
783 exporter->connection = g_object_ref (connection);
784 exporter->object_path = g_strdup (object_path);
785 exporter->groups = g_hash_table_new (NULL, NULL);
786 exporter->remotes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_menu_exporter_remote_free);
787 exporter->root = g_menu_exporter_group_add_menu (g_menu_exporter_create_group (exporter), menu);
793 * g_dbus_connection_unexport_menu_model:
794 * @connection: a #GDBusConnection
795 * @export_id: the ID from g_dbus_connection_export_menu_model()
797 * Reverses the effect of a previous call to
798 * g_dbus_connection_export_menu_model().
800 * It is an error to call this function with an ID that wasn't returned
801 * from g_dbus_connection_export_menu_model() or to call it with the
802 * same ID more than once.
807 g_dbus_connection_unexport_menu_model (GDBusConnection *connection,
810 g_dbus_connection_unregister_object (connection, export_id);
814 /* vim:set foldmethod=marker: */