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>
22 #include "gmenuexporter.h"
24 #include "gdbusmethodinvocation.h"
25 #include "gdbusintrospection.h"
26 #include "gdbusnamewatching.h"
27 #include "gdbuserror.h"
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
39 * To access an exported #GMenuModel remotely, use
40 * g_menu_proxy_get() to obtain a #GMenuProxy.
43 /* {{{1 D-Bus Interface description */
44 static GDBusInterfaceInfo *
45 org_gtk_Menus_get_interface (void)
47 static GDBusInterfaceInfo *interface_info;
49 if (interface_info == NULL)
54 info = g_dbus_node_info_new_for_xml ("<node>"
55 " <interface name='org.gtk.Menus'>"
56 " <method name='Start'>"
57 " <arg type='au' name='groups' direction='in'/>"
58 " <arg type='a(uuaa{sv})' name='content' direction='out'/>"
60 " <method name='End'>"
61 " <arg type='au' name='groups' direction='in'/>"
63 " <signal name='Changed'>"
64 " arg type='a(uuuuaa{sv})' name='changes'/>"
69 g_error ("%s\n", error->message);
70 interface_info = g_dbus_node_info_lookup_interface (info, "org.gtk.Menus");
71 g_assert (interface_info != NULL);
72 g_dbus_interface_info_ref (interface_info);
73 g_dbus_node_info_unref (info);
76 return interface_info;
79 /* {{{1 Forward declarations */
80 typedef struct _GMenuExporterMenu GMenuExporterMenu;
81 typedef struct _GMenuExporterLink GMenuExporterLink;
82 typedef struct _GMenuExporterGroup GMenuExporterGroup;
83 typedef struct _GMenuExporterRemote GMenuExporterRemote;
84 typedef struct _GMenuExporterWatch GMenuExporterWatch;
85 typedef struct _GMenuExporter GMenuExporter;
87 static gboolean g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group);
88 static guint g_menu_exporter_group_get_id (GMenuExporterGroup *group);
89 static GMenuExporter * g_menu_exporter_group_get_exporter (GMenuExporterGroup *group);
90 static GMenuExporterMenu * g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
92 static void g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
95 static GMenuExporterGroup * g_menu_exporter_create_group (GMenuExporter *exporter);
96 static GMenuExporterGroup * g_menu_exporter_lookup_group (GMenuExporter *exporter,
98 static void g_menu_exporter_report (GMenuExporter *exporter,
100 static void g_menu_exporter_remove_group (GMenuExporter *exporter,
103 /* {{{1 GMenuExporterLink, GMenuExporterMenu */
105 struct _GMenuExporterMenu
107 GMenuExporterGroup *group;
112 GSequence *item_links;
115 struct _GMenuExporterLink
118 GMenuExporterMenu *menu;
119 GMenuExporterLink *next;
123 g_menu_exporter_menu_free (GMenuExporterMenu *menu)
125 g_menu_exporter_group_remove_menu (menu->group, menu->id);
127 if (menu->handler_id != 0)
128 g_signal_handler_disconnect (menu->model, menu->handler_id);
130 if (menu->item_links != NULL)
131 g_sequence_free (menu->item_links);
133 g_object_unref (menu->model);
135 g_slice_free (GMenuExporterMenu, menu);
139 g_menu_exporter_link_free (gpointer data)
141 GMenuExporterLink *link = data;
145 GMenuExporterLink *tmp = link;
148 g_menu_exporter_menu_free (tmp->menu);
151 g_slice_free (GMenuExporterLink, tmp);
155 static GMenuExporterLink *
156 g_menu_exporter_menu_create_links (GMenuExporterMenu *menu,
159 GMenuExporterLink *list = NULL;
164 iter = g_menu_model_iterate_item_links (menu->model, position);
166 while (g_menu_link_iter_get_next (iter, &name, &model))
168 GMenuExporterGroup *group;
169 GMenuExporterLink *tmp;
172 group = g_menu_exporter_create_group (g_menu_exporter_group_get_exporter (menu->group));
176 tmp = g_slice_new (GMenuExporterLink);
177 tmp->name = g_strconcat (":", name, NULL);
178 tmp->menu = g_menu_exporter_group_add_menu (group, model);
182 g_object_unref (model);
185 g_object_unref (iter);
191 g_menu_exporter_menu_describe_item (GMenuExporterMenu *menu,
194 GMenuAttributeIter *attr_iter;
195 GVariantBuilder builder;
197 GMenuExporterLink *link;
201 g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
203 attr_iter = g_menu_model_iterate_item_attributes (menu->model, position);
204 while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
206 g_variant_builder_add (&builder, "{sv}", name, value);
207 g_variant_unref (value);
209 g_object_unref (attr_iter);
211 iter = g_sequence_get_iter_at_pos (menu->item_links, position);
212 for (link = g_sequence_get (iter); link; link = link->next)
213 g_variant_builder_add (&builder, "{sv}", link->name,
214 g_variant_new ("(uu)", g_menu_exporter_group_get_id (link->menu->group), link->menu->id));
216 return g_variant_builder_end (&builder);
220 g_menu_exporter_menu_list (GMenuExporterMenu *menu)
222 GVariantBuilder builder;
225 g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
227 n = g_sequence_get_length (menu->item_links);
228 for (i = 0; i < n; i++)
229 g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
231 return g_variant_builder_end (&builder);
235 g_menu_exporter_menu_items_changed (GMenuModel *model,
241 GMenuExporterMenu *menu = user_data;
242 GSequenceIter *point;
245 g_assert (menu->model == model);
246 g_assert (menu->item_links != NULL);
247 g_assert (position + removed <= g_sequence_get_length (menu->item_links));
249 point = g_sequence_get_iter_at_pos (menu->item_links, position + removed);
250 g_sequence_remove_range (g_sequence_get_iter_at_pos (menu->item_links, position), point);
252 for (i = position; i < position + added; i++)
253 g_sequence_insert_before (point, g_menu_exporter_menu_create_links (menu, i));
255 if (g_menu_exporter_group_is_subscribed (menu->group))
257 GVariantBuilder builder;
259 g_variant_builder_init (&builder, G_VARIANT_TYPE ("(uuuuaa{sv})"));
260 g_variant_builder_add (&builder, "u", g_menu_exporter_group_get_id (menu->group));
261 g_variant_builder_add (&builder, "u", menu->id);
262 g_variant_builder_add (&builder, "u", position);
263 g_variant_builder_add (&builder, "u", removed);
265 g_variant_builder_open (&builder, G_VARIANT_TYPE ("aa{sv}"));
266 for (i = position; i < position + added; i++)
267 g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
268 g_variant_builder_close (&builder);
270 g_menu_exporter_report (g_menu_exporter_group_get_exporter (menu->group), g_variant_builder_end (&builder));
275 g_menu_exporter_menu_prepare (GMenuExporterMenu *menu)
279 g_assert (menu->item_links == NULL);
281 if (g_menu_model_is_mutable (menu->model))
282 menu->handler_id = g_signal_connect (menu->model, "items-changed",
283 G_CALLBACK (g_menu_exporter_menu_items_changed), menu);
285 menu->item_links = g_sequence_new (g_menu_exporter_link_free);
287 n_items = g_menu_model_get_n_items (menu->model);
289 g_menu_exporter_menu_items_changed (menu->model, 0, 0, n_items, menu);
292 static GMenuExporterMenu *
293 g_menu_exporter_menu_new (GMenuExporterGroup *group,
297 GMenuExporterMenu *menu;
299 menu = g_slice_new0 (GMenuExporterMenu);
302 menu->model = g_object_ref (model);
307 /* {{{1 GMenuExporterGroup */
309 struct _GMenuExporterGroup
311 GMenuExporter *exporter;
322 g_menu_exporter_group_check_if_useless (GMenuExporterGroup *group)
324 if (g_hash_table_size (group->menus) == 0 && group->subscribed == 0)
326 g_menu_exporter_remove_group (group->exporter, group->id);
328 g_hash_table_unref (group->menus);
330 g_slice_free (GMenuExporterGroup, group);
335 g_menu_exporter_group_subscribe (GMenuExporterGroup *group,
336 GVariantBuilder *builder)
341 if (!group->prepared)
343 GMenuExporterMenu *menu;
345 /* set this first, so that any menus created during the
346 * preparation of the first menu also end up in the prepared
349 group->prepared = TRUE;
351 menu = g_hash_table_lookup (group->menus, 0);
352 g_menu_exporter_menu_prepare (menu);
357 g_hash_table_iter_init (&iter, group->menus);
358 while (g_hash_table_iter_next (&iter, &key, &val))
360 guint id = GPOINTER_TO_INT (key);
361 GMenuExporterMenu *menu = val;
363 if (g_sequence_get_length (menu->item_links))
365 g_variant_builder_open (builder, G_VARIANT_TYPE ("(uuaa{sv})"));
366 g_variant_builder_add (builder, "u", group->id);
367 g_variant_builder_add (builder, "u", id);
368 g_variant_builder_add_value (builder, g_menu_exporter_menu_list (menu));
369 g_variant_builder_close (builder);
375 g_menu_exporter_group_unsubscribe (GMenuExporterGroup *group,
378 g_assert (group->subscribed >= count);
380 group->subscribed -= count;
382 g_menu_exporter_group_check_if_useless (group);
385 static GMenuExporter *
386 g_menu_exporter_group_get_exporter (GMenuExporterGroup *group)
388 return group->exporter;
392 g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group)
394 return group->subscribed > 0;
398 g_menu_exporter_group_get_id (GMenuExporterGroup *group)
404 g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
407 g_hash_table_remove (group->menus, GINT_TO_POINTER (id));
409 g_menu_exporter_group_check_if_useless (group);
412 static GMenuExporterMenu *
413 g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
416 GMenuExporterMenu *menu;
419 id = group->next_menu_id++;
420 menu = g_menu_exporter_menu_new (group, id, model);
421 g_hash_table_insert (group->menus, GINT_TO_POINTER (id), menu);
424 g_menu_exporter_menu_prepare (menu);
429 static GMenuExporterGroup *
430 g_menu_exporter_group_new (GMenuExporter *exporter,
433 GMenuExporterGroup *group;
435 group = g_slice_new0 (GMenuExporterGroup);
436 group->menus = g_hash_table_new (NULL, NULL);
437 group->exporter = exporter;
443 /* {{{1 GMenuExporterRemote */
445 struct _GMenuExporterRemote
447 GMenuExporter *exporter;
453 g_menu_exporter_remote_subscribe (GMenuExporterRemote *remote,
455 GVariantBuilder *builder)
457 GMenuExporterGroup *group;
460 count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
461 g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count + 1));
463 group = g_menu_exporter_lookup_group (remote->exporter, group_id);
464 g_menu_exporter_group_subscribe (group, builder);
468 g_menu_exporter_remote_unsubscribe (GMenuExporterRemote *remote,
471 GMenuExporterGroup *group;
474 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 g_hash_table_remove (remote->watches, GINT_TO_POINTER (group_id));
484 group = g_menu_exporter_lookup_group (remote->exporter, group_id);
485 g_menu_exporter_group_unsubscribe (group, 1);
489 g_menu_exporter_remote_has_subscriptions (GMenuExporterRemote *remote)
491 return g_hash_table_size (remote->watches) != 0;
495 g_menu_exporter_remote_free (gpointer data)
497 GMenuExporterRemote *remote = data;
501 g_hash_table_iter_init (&iter, remote->watches);
502 while (g_hash_table_iter_next (&iter, &key, &val))
504 GMenuExporterGroup *group;
506 group = g_menu_exporter_lookup_group (remote->exporter, GPOINTER_TO_INT (key));
507 g_menu_exporter_group_unsubscribe (group, GPOINTER_TO_INT (val));
510 g_bus_unwatch_name (remote->watch_id);
511 g_hash_table_unref (remote->watches);
513 g_slice_free (GMenuExporterRemote, remote);
516 static GMenuExporterRemote *
517 g_menu_exporter_remote_new (GMenuExporter *exporter,
520 GMenuExporterRemote *remote;
522 remote = g_slice_new0 (GMenuExporterRemote);
523 remote->exporter = exporter;
524 remote->watches = g_hash_table_new (NULL, NULL);
525 remote->watch_id = watch_id;
530 /* {{{1 GMenuExporter */
532 struct _GMenuExporter
534 GDBusConnection *connection;
536 guint registration_id;
540 GMenuExporterMenu *root;
545 g_menu_exporter_name_vanished (GDBusConnection *connection,
549 GMenuExporter *exporter = user_data;
551 g_assert (exporter->connection == connection);
553 g_hash_table_remove (exporter->remotes, name);
557 g_menu_exporter_subscribe (GMenuExporter *exporter,
561 GMenuExporterRemote *remote;
562 GVariantBuilder builder;
566 remote = g_hash_table_lookup (exporter->remotes, sender);
572 watch_id = g_bus_watch_name_on_connection (exporter->connection, sender, G_BUS_NAME_WATCHER_FLAGS_NONE,
573 NULL, g_menu_exporter_name_vanished, exporter, NULL);
574 remote = g_menu_exporter_remote_new (exporter, watch_id);
575 g_hash_table_insert (exporter->remotes, g_strdup (sender), remote);
578 g_variant_builder_init (&builder, G_VARIANT_TYPE ("(a(uuaa{sv}))"));
580 g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(uuaa{sv})"));
582 g_variant_iter_init (&iter, group_ids);
583 while (g_variant_iter_next (&iter, "u", &id))
584 g_menu_exporter_remote_subscribe (remote, id, &builder);
586 g_variant_builder_close (&builder);
588 return g_variant_builder_end (&builder);
592 g_menu_exporter_unsubscribe (GMenuExporter *exporter,
596 GMenuExporterRemote *remote;
600 remote = g_hash_table_lookup (exporter->remotes, sender);
605 g_variant_iter_init (&iter, group_ids);
606 while (g_variant_iter_next (&iter, "u", &id))
607 g_menu_exporter_remote_unsubscribe (remote, id);
609 if (!g_menu_exporter_remote_has_subscriptions (remote))
610 g_hash_table_remove (exporter->remotes, sender);
614 g_menu_exporter_report (GMenuExporter *exporter,
617 GVariantBuilder builder;
619 g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
620 g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY);
621 g_variant_builder_add_value (&builder, report);
622 g_variant_builder_close (&builder);
624 g_dbus_connection_emit_signal (exporter->connection,
626 exporter->object_path,
627 "org.gtk.Menus", "Changed",
628 g_variant_builder_end (&builder),
633 g_menu_exporter_remove_group (GMenuExporter *exporter,
636 g_hash_table_remove (exporter->groups, GINT_TO_POINTER (id));
639 static GMenuExporterGroup *
640 g_menu_exporter_lookup_group (GMenuExporter *exporter,
643 GMenuExporterGroup *group;
645 group = g_hash_table_lookup (exporter->groups, GINT_TO_POINTER (group_id));
649 group = g_menu_exporter_group_new (exporter, group_id);
650 g_hash_table_insert (exporter->groups, GINT_TO_POINTER (group_id), group);
656 static GMenuExporterGroup *
657 g_menu_exporter_create_group (GMenuExporter *exporter)
659 GMenuExporterGroup *group;
662 id = exporter->next_group_id++;
663 group = g_menu_exporter_group_new (exporter, id);
664 g_hash_table_insert (exporter->groups, GINT_TO_POINTER (id), group);
670 g_menu_exporter_free (GMenuExporter *exporter)
672 g_dbus_connection_unregister_object (exporter->connection, exporter->registration_id);
673 g_menu_exporter_menu_free (exporter->root);
674 g_hash_table_unref (exporter->remotes);
675 g_hash_table_unref (exporter->groups);
676 g_object_unref (exporter->connection);
677 g_free (exporter->object_path);
679 g_slice_free (GMenuExporter, exporter);
683 g_menu_exporter_method_call (GDBusConnection *connection,
685 const gchar *object_path,
686 const gchar *interface_name,
687 const gchar *method_name,
688 GVariant *parameters,
689 GDBusMethodInvocation *invocation,
692 GMenuExporter *exporter = user_data;
695 group_ids = g_variant_get_child_value (parameters, 0);
697 if (g_str_equal (method_name, "Start"))
698 g_dbus_method_invocation_return_value (invocation, g_menu_exporter_subscribe (exporter, sender, group_ids));
700 else if (g_str_equal (method_name, "End"))
702 g_menu_exporter_unsubscribe (exporter, sender, group_ids);
703 g_dbus_method_invocation_return_value (invocation, NULL);
707 g_assert_not_reached ();
709 g_variant_unref (group_ids);
712 static GDBusConnection *
713 g_menu_exporter_get_connection (GMenuExporter *exporter)
715 return exporter->connection;
719 g_menu_exporter_get_object_path (GMenuExporter *exporter)
721 return exporter->object_path;
724 static GMenuExporter *
725 g_menu_exporter_new (GDBusConnection *connection,
726 const gchar *object_path,
730 const GDBusInterfaceVTable vtable = {
731 g_menu_exporter_method_call,
733 GMenuExporter *exporter;
736 exporter = g_slice_new0 (GMenuExporter);
738 id = g_dbus_connection_register_object (connection, object_path, org_gtk_Menus_get_interface (),
739 &vtable, exporter, NULL, error);
743 g_slice_free (GMenuExporter, exporter);
747 exporter->connection = g_object_ref (connection);
748 exporter->object_path = g_strdup (object_path);
749 exporter->registration_id = id;
750 exporter->groups = g_hash_table_new (NULL, NULL);
751 exporter->remotes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_menu_exporter_remote_free);
752 exporter->root = g_menu_exporter_group_add_menu (g_menu_exporter_create_group (exporter), model);
757 /* {{{1 Public API */
759 static GHashTable *g_menu_exporter_exported_menus;
762 * g_menu_exporter_export:
763 * @connection: a #GDBusConnection
764 * @object_path: a D-Bus object path
765 * @menu: a #GMenuModel
766 * @error: return location for an error, or %NULL
768 * Exports @menu on @connection at @object_path.
770 * The implemented D-Bus API should be considered private.
771 * It is subject to change in the future.
773 * A given menu model can only be exported on one object path
774 * and an object_path can only have one action group exported
775 * on it. If either constraint is violated, the export will
776 * fail and %FALSE will be returned (with @error set accordingly).
778 * Use g_menu_exporter_stop() to stop exporting @menu
779 * or g_menu_exporter_query() to find out if and where a given
780 * menu model is exported.
782 * Returns: %TRUE if the export is successful, or %FALSE (with
783 * @error set) in the event of a failure.
786 g_menu_exporter_export (GDBusConnection *connection,
787 const gchar *object_path,
791 GMenuExporter *exporter;
793 if G_UNLIKELY (g_menu_exporter_exported_menus == NULL)
794 g_menu_exporter_exported_menus = g_hash_table_new (NULL, NULL);
796 if G_UNLIKELY (g_hash_table_lookup (g_menu_exporter_exported_menus, menu))
798 g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FILE_EXISTS, "The given GMenuModel has already been exported");
802 exporter = g_menu_exporter_new (connection, object_path, menu, error);
804 if (exporter == NULL)
807 g_hash_table_insert (g_menu_exporter_exported_menus, menu, exporter);
813 * g_menu_exporter_stop:
814 * @menu: a #GMenuModel
816 * Stops the export of @menu.
818 * This reverses the effect of a previous call to
819 * g_menu_exporter_export() for @menu.
821 * Returns: %TRUE if an export was stopped or %FALSE
822 * if @menu was not exported in the first place
825 g_menu_exporter_stop (GMenuModel *menu)
827 GMenuExporter *exporter;
829 if G_UNLIKELY (g_menu_exporter_exported_menus == NULL)
832 exporter = g_hash_table_lookup (g_menu_exporter_exported_menus, menu);
833 if G_UNLIKELY (exporter == NULL)
836 g_hash_table_remove (g_menu_exporter_exported_menus, menu);
837 g_menu_exporter_free (exporter);
843 * g_menu_exporter_query:
844 * @menu: a #GMenuModel
845 * @connection: (out): the #GDBusConnection used for exporting
846 * @object_path: (out): the object path used for exporting
848 * Queries if and where @menu is exported.
850 * If @menu is exported, %TRUE is returned. If @connection is
851 * non-%NULL then it is set to the #GDBusConnection used for
852 * the export. If @object_path is non-%NULL then it is set to
855 * If the @menu is not exported, %FALSE is returned and
856 * @connection and @object_path remain unmodified.
858 * Returns: %TRUE if @menu was exported, else %FALSE
861 g_menu_exporter_query (GMenuModel *menu,
862 GDBusConnection **connection,
863 const gchar **object_path)
865 GMenuExporter *exporter;
867 if (g_menu_exporter_exported_menus == NULL)
870 exporter = g_hash_table_lookup (g_menu_exporter_exported_menus, menu);
871 if (exporter == NULL)
875 *connection = g_menu_exporter_get_connection (exporter);
878 *object_path = g_menu_exporter_get_object_path (exporter);
884 /* vim:set foldmethod=marker: */