Add a test for the previous fix
[glib.git] / gio / gmenuexporter.c
blob8509ffdb0cbbf82a3cc020bb5497f930d39fba14
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, #GDBusMenuModel
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_dbus_menu_model_get() to obtain a #GDBusMenuModel.
43 /* {{{1 D-Bus Interface description */
45 /* For documentation of this interface, see
46 * http://live.gnome.org/GTK+/GApplication-dbus-apis
49 static GDBusInterfaceInfo *
50 org_gtk_Menus_get_interface (void)
52 static GDBusInterfaceInfo *interface_info;
54 if (interface_info == NULL)
56 GError *error = NULL;
57 GDBusNodeInfo *info;
59 info = g_dbus_node_info_new_for_xml ("<node>"
60 " <interface name='org.gtk.Menus'>"
61 " <method name='Start'>"
62 " <arg type='au' name='groups' direction='in'/>"
63 " <arg type='a(uuaa{sv})' name='content' direction='out'/>"
64 " </method>"
65 " <method name='End'>"
66 " <arg type='au' name='groups' direction='in'/>"
67 " </method>"
68 " <signal name='Changed'>"
69 " arg type='a(uuuuaa{sv})' name='changes'/>"
70 " </signal>"
71 " </interface>"
72 "</node>", &error);
73 if (info == NULL)
74 g_error ("%s\n", error->message);
75 interface_info = g_dbus_node_info_lookup_interface (info, "org.gtk.Menus");
76 g_assert (interface_info != NULL);
77 g_dbus_interface_info_ref (interface_info);
78 g_dbus_node_info_unref (info);
81 return interface_info;
84 /* {{{1 Forward declarations */
85 typedef struct _GMenuExporterMenu GMenuExporterMenu;
86 typedef struct _GMenuExporterLink GMenuExporterLink;
87 typedef struct _GMenuExporterGroup GMenuExporterGroup;
88 typedef struct _GMenuExporterRemote GMenuExporterRemote;
89 typedef struct _GMenuExporterWatch GMenuExporterWatch;
90 typedef struct _GMenuExporter GMenuExporter;
92 static gboolean g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group);
93 static guint g_menu_exporter_group_get_id (GMenuExporterGroup *group);
94 static GMenuExporter * g_menu_exporter_group_get_exporter (GMenuExporterGroup *group);
95 static GMenuExporterMenu * g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
96 GMenuModel *model);
97 static void g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
98 guint id);
100 static GMenuExporterGroup * g_menu_exporter_create_group (GMenuExporter *exporter);
101 static GMenuExporterGroup * g_menu_exporter_lookup_group (GMenuExporter *exporter,
102 guint group_id);
103 static void g_menu_exporter_report (GMenuExporter *exporter,
104 GVariant *report);
105 static void g_menu_exporter_remove_group (GMenuExporter *exporter,
106 guint id);
108 /* {{{1 GMenuExporterLink, GMenuExporterMenu */
110 struct _GMenuExporterMenu
112 GMenuExporterGroup *group;
113 guint id;
115 GMenuModel *model;
116 gulong handler_id;
117 GSequence *item_links;
120 struct _GMenuExporterLink
122 gchar *name;
123 GMenuExporterMenu *menu;
124 GMenuExporterLink *next;
127 static void
128 g_menu_exporter_menu_free (GMenuExporterMenu *menu)
130 g_menu_exporter_group_remove_menu (menu->group, menu->id);
132 if (menu->handler_id != 0)
133 g_signal_handler_disconnect (menu->model, menu->handler_id);
135 if (menu->item_links != NULL)
136 g_sequence_free (menu->item_links);
138 g_object_unref (menu->model);
140 g_slice_free (GMenuExporterMenu, menu);
143 static void
144 g_menu_exporter_link_free (gpointer data)
146 GMenuExporterLink *link = data;
148 while (link != NULL)
150 GMenuExporterLink *tmp = link;
151 link = tmp->next;
153 g_menu_exporter_menu_free (tmp->menu);
154 g_free (tmp->name);
156 g_slice_free (GMenuExporterLink, tmp);
160 static GMenuExporterLink *
161 g_menu_exporter_menu_create_links (GMenuExporterMenu *menu,
162 gint position)
164 GMenuExporterLink *list = NULL;
165 GMenuLinkIter *iter;
166 const char *name;
167 GMenuModel *model;
169 iter = g_menu_model_iterate_item_links (menu->model, position);
171 while (g_menu_link_iter_get_next (iter, &name, &model))
173 GMenuExporterGroup *group;
174 GMenuExporterLink *tmp;
176 /* keep sections in the same group, but create new groups
177 * otherwise
179 if (!g_str_equal (name, "section"))
180 group = g_menu_exporter_create_group (g_menu_exporter_group_get_exporter (menu->group));
181 else
182 group = menu->group;
184 tmp = g_slice_new (GMenuExporterLink);
185 tmp->name = g_strconcat (":", name, NULL);
186 tmp->menu = g_menu_exporter_group_add_menu (group, model);
187 tmp->next = list;
188 list = tmp;
190 g_object_unref (model);
193 g_object_unref (iter);
195 return list;
198 static GVariant *
199 g_menu_exporter_menu_describe_item (GMenuExporterMenu *menu,
200 gint position)
202 GMenuAttributeIter *attr_iter;
203 GVariantBuilder builder;
204 GSequenceIter *iter;
205 GMenuExporterLink *link;
206 const char *name;
207 GVariant *value;
209 g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
211 attr_iter = g_menu_model_iterate_item_attributes (menu->model, position);
212 while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
214 g_variant_builder_add (&builder, "{sv}", name, value);
215 g_variant_unref (value);
217 g_object_unref (attr_iter);
219 iter = g_sequence_get_iter_at_pos (menu->item_links, position);
220 for (link = g_sequence_get (iter); link; link = link->next)
221 g_variant_builder_add (&builder, "{sv}", link->name,
222 g_variant_new ("(uu)", g_menu_exporter_group_get_id (link->menu->group), link->menu->id));
224 return g_variant_builder_end (&builder);
227 static GVariant *
228 g_menu_exporter_menu_list (GMenuExporterMenu *menu)
230 GVariantBuilder builder;
231 gint i, n;
233 g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
235 n = g_sequence_get_length (menu->item_links);
236 for (i = 0; i < n; i++)
237 g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
239 return g_variant_builder_end (&builder);
242 static void
243 g_menu_exporter_menu_items_changed (GMenuModel *model,
244 gint position,
245 gint removed,
246 gint added,
247 gpointer user_data)
249 GMenuExporterMenu *menu = user_data;
250 GSequenceIter *point;
251 gint i;
253 g_assert (menu->model == model);
254 g_assert (menu->item_links != NULL);
255 g_assert (position + removed <= g_sequence_get_length (menu->item_links));
257 point = g_sequence_get_iter_at_pos (menu->item_links, position + removed);
258 g_sequence_remove_range (g_sequence_get_iter_at_pos (menu->item_links, position), point);
260 for (i = position; i < position + added; i++)
261 g_sequence_insert_before (point, g_menu_exporter_menu_create_links (menu, i));
263 if (g_menu_exporter_group_is_subscribed (menu->group))
265 GVariantBuilder builder;
267 g_variant_builder_init (&builder, G_VARIANT_TYPE ("(uuuuaa{sv})"));
268 g_variant_builder_add (&builder, "u", g_menu_exporter_group_get_id (menu->group));
269 g_variant_builder_add (&builder, "u", menu->id);
270 g_variant_builder_add (&builder, "u", position);
271 g_variant_builder_add (&builder, "u", removed);
273 g_variant_builder_open (&builder, G_VARIANT_TYPE ("aa{sv}"));
274 for (i = position; i < position + added; i++)
275 g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
276 g_variant_builder_close (&builder);
278 g_menu_exporter_report (g_menu_exporter_group_get_exporter (menu->group), g_variant_builder_end (&builder));
282 static void
283 g_menu_exporter_menu_prepare (GMenuExporterMenu *menu)
285 gint n_items;
287 g_assert (menu->item_links == NULL);
289 if (g_menu_model_is_mutable (menu->model))
290 menu->handler_id = g_signal_connect (menu->model, "items-changed",
291 G_CALLBACK (g_menu_exporter_menu_items_changed), menu);
293 menu->item_links = g_sequence_new (g_menu_exporter_link_free);
295 n_items = g_menu_model_get_n_items (menu->model);
296 if (n_items)
297 g_menu_exporter_menu_items_changed (menu->model, 0, 0, n_items, menu);
300 static GMenuExporterMenu *
301 g_menu_exporter_menu_new (GMenuExporterGroup *group,
302 guint id,
303 GMenuModel *model)
305 GMenuExporterMenu *menu;
307 menu = g_slice_new0 (GMenuExporterMenu);
308 menu->group = group;
309 menu->id = id;
310 menu->model = g_object_ref (model);
312 return menu;
315 /* {{{1 GMenuExporterGroup */
317 struct _GMenuExporterGroup
319 GMenuExporter *exporter;
320 guint id;
322 GHashTable *menus;
323 guint next_menu_id;
324 gboolean prepared;
326 gint subscribed;
329 static void
330 g_menu_exporter_group_check_if_useless (GMenuExporterGroup *group)
332 if (g_hash_table_size (group->menus) == 0 && group->subscribed == 0)
334 g_menu_exporter_remove_group (group->exporter, group->id);
336 g_hash_table_unref (group->menus);
338 g_slice_free (GMenuExporterGroup, group);
342 static void
343 g_menu_exporter_group_subscribe (GMenuExporterGroup *group,
344 GVariantBuilder *builder)
346 GHashTableIter iter;
347 gpointer key, val;
349 if (!group->prepared)
351 GMenuExporterMenu *menu;
353 /* set this first, so that any menus created during the
354 * preparation of the first menu also end up in the prepared
355 * state.
356 * */
357 group->prepared = TRUE;
359 menu = g_hash_table_lookup (group->menus, 0);
360 g_menu_exporter_menu_prepare (menu);
363 group->subscribed++;
365 g_hash_table_iter_init (&iter, group->menus);
366 while (g_hash_table_iter_next (&iter, &key, &val))
368 guint id = GPOINTER_TO_INT (key);
369 GMenuExporterMenu *menu = val;
371 if (g_sequence_get_length (menu->item_links))
373 g_variant_builder_open (builder, G_VARIANT_TYPE ("(uuaa{sv})"));
374 g_variant_builder_add (builder, "u", group->id);
375 g_variant_builder_add (builder, "u", id);
376 g_variant_builder_add_value (builder, g_menu_exporter_menu_list (menu));
377 g_variant_builder_close (builder);
382 static void
383 g_menu_exporter_group_unsubscribe (GMenuExporterGroup *group,
384 gint count)
386 g_assert (group->subscribed >= count);
388 group->subscribed -= count;
390 g_menu_exporter_group_check_if_useless (group);
393 static GMenuExporter *
394 g_menu_exporter_group_get_exporter (GMenuExporterGroup *group)
396 return group->exporter;
399 static gboolean
400 g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group)
402 return group->subscribed > 0;
405 static guint
406 g_menu_exporter_group_get_id (GMenuExporterGroup *group)
408 return group->id;
411 static void
412 g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
413 guint id)
415 g_hash_table_remove (group->menus, GINT_TO_POINTER (id));
417 g_menu_exporter_group_check_if_useless (group);
420 static GMenuExporterMenu *
421 g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
422 GMenuModel *model)
424 GMenuExporterMenu *menu;
425 guint id;
427 id = group->next_menu_id++;
428 menu = g_menu_exporter_menu_new (group, id, model);
429 g_hash_table_insert (group->menus, GINT_TO_POINTER (id), menu);
431 if (group->prepared)
432 g_menu_exporter_menu_prepare (menu);
434 return menu;
437 static GMenuExporterGroup *
438 g_menu_exporter_group_new (GMenuExporter *exporter,
439 guint id)
441 GMenuExporterGroup *group;
443 group = g_slice_new0 (GMenuExporterGroup);
444 group->menus = g_hash_table_new (NULL, NULL);
445 group->exporter = exporter;
446 group->id = id;
448 return group;
451 /* {{{1 GMenuExporterRemote */
453 struct _GMenuExporterRemote
455 GMenuExporter *exporter;
456 GHashTable *watches;
457 guint watch_id;
460 static void
461 g_menu_exporter_remote_subscribe (GMenuExporterRemote *remote,
462 guint group_id,
463 GVariantBuilder *builder)
465 GMenuExporterGroup *group;
466 guint count;
468 count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
469 g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count + 1));
471 group = g_menu_exporter_lookup_group (remote->exporter, group_id);
472 g_menu_exporter_group_subscribe (group, builder);
475 static void
476 g_menu_exporter_remote_unsubscribe (GMenuExporterRemote *remote,
477 guint group_id)
479 GMenuExporterGroup *group;
480 guint count;
482 count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
484 if (count == 0)
485 return;
487 if (count != 1)
488 g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count - 1));
489 else
490 g_hash_table_remove (remote->watches, GINT_TO_POINTER (group_id));
492 group = g_menu_exporter_lookup_group (remote->exporter, group_id);
493 g_menu_exporter_group_unsubscribe (group, 1);
496 static gboolean
497 g_menu_exporter_remote_has_subscriptions (GMenuExporterRemote *remote)
499 return g_hash_table_size (remote->watches) != 0;
502 static void
503 g_menu_exporter_remote_free (gpointer data)
505 GMenuExporterRemote *remote = data;
506 GHashTableIter iter;
507 gpointer key, val;
509 g_hash_table_iter_init (&iter, remote->watches);
510 while (g_hash_table_iter_next (&iter, &key, &val))
512 GMenuExporterGroup *group;
514 group = g_menu_exporter_lookup_group (remote->exporter, GPOINTER_TO_INT (key));
515 g_menu_exporter_group_unsubscribe (group, GPOINTER_TO_INT (val));
518 g_bus_unwatch_name (remote->watch_id);
519 g_hash_table_unref (remote->watches);
521 g_slice_free (GMenuExporterRemote, remote);
524 static GMenuExporterRemote *
525 g_menu_exporter_remote_new (GMenuExporter *exporter,
526 guint watch_id)
528 GMenuExporterRemote *remote;
530 remote = g_slice_new0 (GMenuExporterRemote);
531 remote->exporter = exporter;
532 remote->watches = g_hash_table_new (NULL, NULL);
533 remote->watch_id = watch_id;
535 return remote;
538 /* {{{1 GMenuExporter */
540 struct _GMenuExporter
542 GDBusConnection *connection;
543 gchar *object_path;
544 guint registration_id;
545 GHashTable *groups;
546 guint next_group_id;
548 GMenuExporterMenu *root;
549 GHashTable *remotes;
552 static void
553 g_menu_exporter_name_vanished (GDBusConnection *connection,
554 const gchar *name,
555 gpointer user_data)
557 GMenuExporter *exporter = user_data;
559 /* connection == NULL when we get called because the connection closed */
560 g_assert (exporter->connection == connection || connection == NULL);
562 g_hash_table_remove (exporter->remotes, name);
565 static GVariant *
566 g_menu_exporter_subscribe (GMenuExporter *exporter,
567 const gchar *sender,
568 GVariant *group_ids)
570 GMenuExporterRemote *remote;
571 GVariantBuilder builder;
572 GVariantIter iter;
573 guint32 id;
575 remote = g_hash_table_lookup (exporter->remotes, sender);
577 if (remote == NULL)
579 guint watch_id;
581 watch_id = g_bus_watch_name_on_connection (exporter->connection, sender, G_BUS_NAME_WATCHER_FLAGS_NONE,
582 NULL, g_menu_exporter_name_vanished, exporter, NULL);
583 remote = g_menu_exporter_remote_new (exporter, watch_id);
584 g_hash_table_insert (exporter->remotes, g_strdup (sender), remote);
587 g_variant_builder_init (&builder, G_VARIANT_TYPE ("(a(uuaa{sv}))"));
589 g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(uuaa{sv})"));
591 g_variant_iter_init (&iter, group_ids);
592 while (g_variant_iter_next (&iter, "u", &id))
593 g_menu_exporter_remote_subscribe (remote, id, &builder);
595 g_variant_builder_close (&builder);
597 return g_variant_builder_end (&builder);
600 static void
601 g_menu_exporter_unsubscribe (GMenuExporter *exporter,
602 const gchar *sender,
603 GVariant *group_ids)
605 GMenuExporterRemote *remote;
606 GVariantIter iter;
607 guint32 id;
609 remote = g_hash_table_lookup (exporter->remotes, sender);
611 if (remote == NULL)
612 return;
614 g_variant_iter_init (&iter, group_ids);
615 while (g_variant_iter_next (&iter, "u", &id))
616 g_menu_exporter_remote_unsubscribe (remote, id);
618 if (!g_menu_exporter_remote_has_subscriptions (remote))
619 g_hash_table_remove (exporter->remotes, sender);
622 static void
623 g_menu_exporter_report (GMenuExporter *exporter,
624 GVariant *report)
626 GVariantBuilder builder;
628 g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
629 g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY);
630 g_variant_builder_add_value (&builder, report);
631 g_variant_builder_close (&builder);
633 g_dbus_connection_emit_signal (exporter->connection,
634 NULL,
635 exporter->object_path,
636 "org.gtk.Menus", "Changed",
637 g_variant_builder_end (&builder),
638 NULL);
641 static void
642 g_menu_exporter_remove_group (GMenuExporter *exporter,
643 guint id)
645 g_hash_table_remove (exporter->groups, GINT_TO_POINTER (id));
648 static GMenuExporterGroup *
649 g_menu_exporter_lookup_group (GMenuExporter *exporter,
650 guint group_id)
652 GMenuExporterGroup *group;
654 group = g_hash_table_lookup (exporter->groups, GINT_TO_POINTER (group_id));
656 if (group == NULL)
658 group = g_menu_exporter_group_new (exporter, group_id);
659 g_hash_table_insert (exporter->groups, GINT_TO_POINTER (group_id), group);
662 return group;
665 static GMenuExporterGroup *
666 g_menu_exporter_create_group (GMenuExporter *exporter)
668 GMenuExporterGroup *group;
669 guint id;
671 id = exporter->next_group_id++;
672 group = g_menu_exporter_group_new (exporter, id);
673 g_hash_table_insert (exporter->groups, GINT_TO_POINTER (id), group);
675 return group;
678 static void
679 g_menu_exporter_free (gpointer user_data)
681 GMenuExporter *exporter = user_data;
683 g_menu_exporter_menu_free (exporter->root);
684 g_hash_table_unref (exporter->remotes);
685 g_hash_table_unref (exporter->groups);
686 g_object_unref (exporter->connection);
687 g_free (exporter->object_path);
689 g_slice_free (GMenuExporter, exporter);
692 static void
693 g_menu_exporter_method_call (GDBusConnection *connection,
694 const gchar *sender,
695 const gchar *object_path,
696 const gchar *interface_name,
697 const gchar *method_name,
698 GVariant *parameters,
699 GDBusMethodInvocation *invocation,
700 gpointer user_data)
702 GMenuExporter *exporter = user_data;
703 GVariant *group_ids;
705 group_ids = g_variant_get_child_value (parameters, 0);
707 if (g_str_equal (method_name, "Start"))
708 g_dbus_method_invocation_return_value (invocation, g_menu_exporter_subscribe (exporter, sender, group_ids));
710 else if (g_str_equal (method_name, "End"))
712 g_menu_exporter_unsubscribe (exporter, sender, group_ids);
713 g_dbus_method_invocation_return_value (invocation, NULL);
716 else
717 g_assert_not_reached ();
719 g_variant_unref (group_ids);
722 /* {{{1 Public API */
725 * g_dbus_connection_export_menu_model:
726 * @connection: a #GDBusConnection
727 * @object_path: a D-Bus object path
728 * @menu: a #GMenuModel
729 * @error: return location for an error, or %NULL
731 * Exports @menu on @connection at @object_path.
733 * The implemented D-Bus API should be considered private.
734 * It is subject to change in the future.
736 * An object path can only have one action group exported on it. If this
737 * constraint is violated, the export will fail and 0 will be
738 * returned (with @error set accordingly).
740 * You can unexport the menu model using
741 * g_dbus_connection_unexport_menu_model() with the return value of
742 * this function.
744 * Returns: the ID of the export (never zero), or 0 in case of failure
746 * Since: 2.32
748 guint
749 g_dbus_connection_export_menu_model (GDBusConnection *connection,
750 const gchar *object_path,
751 GMenuModel *menu,
752 GError **error)
754 const GDBusInterfaceVTable vtable = {
755 g_menu_exporter_method_call,
757 GMenuExporter *exporter;
758 guint id;
760 exporter = g_slice_new0 (GMenuExporter);
762 id = g_dbus_connection_register_object (connection, object_path, org_gtk_Menus_get_interface (),
763 &vtable, exporter, g_menu_exporter_free, error);
765 if (id == 0)
767 g_slice_free (GMenuExporter, exporter);
768 return 0;
771 exporter->connection = g_object_ref (connection);
772 exporter->object_path = g_strdup (object_path);
773 exporter->groups = g_hash_table_new (NULL, NULL);
774 exporter->remotes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_menu_exporter_remote_free);
775 exporter->root = g_menu_exporter_group_add_menu (g_menu_exporter_create_group (exporter), menu);
777 return id;
781 * g_dbus_connection_unexport_menu_model:
782 * @connection: a #GDBusConnection
783 * @export_id: the ID from g_dbus_connection_export_menu_model()
785 * Reverses the effect of a previous call to
786 * g_dbus_connection_export_menu_model().
788 * It is an error to call this function with an ID that wasn't returned
789 * from g_dbus_connection_export_menu_model() or to call it with the
790 * same ID more than once.
792 * Since: 2.32
794 void
795 g_dbus_connection_unexport_menu_model (GDBusConnection *connection,
796 guint export_id)
798 g_dbus_connection_unregister_object (connection, export_id);
801 /* {{{1 Epilogue */
802 /* vim:set foldmethod=marker: */