2 * Copyright © 2011 Canonical Ltd.
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.
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, see <http://www.gnu.org/licenses/>.
17 * 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
34 * @see_also: #GMenuModel, #GDBusMenuModel
36 * These functions support exporting a #GMenuModel on D-Bus.
37 * The D-Bus interface that is used is a private implementation
40 * To access an exported #GMenuModel remotely, use
41 * g_dbus_menu_model_get() to obtain a #GDBusMenuModel.
44 /* {{{1 D-Bus Interface description */
46 /* For documentation of this interface, see
47 * https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI
50 static GDBusInterfaceInfo
*
51 org_gtk_Menus_get_interface (void)
53 static GDBusInterfaceInfo
*interface_info
;
55 if (interface_info
== NULL
)
60 info
= g_dbus_node_info_new_for_xml ("<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'/>"
66 " <method name='End'>"
67 " <arg type='au' name='groups' direction='in'/>"
69 " <signal name='Changed'>"
70 " arg type='a(uuuuaa{sv})' name='changes'/>"
75 g_error ("%s\n", error
->message
);
76 interface_info
= g_dbus_node_info_lookup_interface (info
, "org.gtk.Menus");
77 g_assert (interface_info
!= NULL
);
78 g_dbus_interface_info_ref (interface_info
);
79 g_dbus_node_info_unref (info
);
82 return interface_info
;
85 /* {{{1 Forward declarations */
86 typedef struct _GMenuExporterMenu GMenuExporterMenu
;
87 typedef struct _GMenuExporterLink GMenuExporterLink
;
88 typedef struct _GMenuExporterGroup GMenuExporterGroup
;
89 typedef struct _GMenuExporterRemote GMenuExporterRemote
;
90 typedef struct _GMenuExporterWatch GMenuExporterWatch
;
91 typedef struct _GMenuExporter GMenuExporter
;
93 static gboolean
g_menu_exporter_group_is_subscribed (GMenuExporterGroup
*group
);
94 static guint
g_menu_exporter_group_get_id (GMenuExporterGroup
*group
);
95 static GMenuExporter
* g_menu_exporter_group_get_exporter (GMenuExporterGroup
*group
);
96 static GMenuExporterMenu
* g_menu_exporter_group_add_menu (GMenuExporterGroup
*group
,
98 static void g_menu_exporter_group_remove_menu (GMenuExporterGroup
*group
,
101 static GMenuExporterGroup
* g_menu_exporter_create_group (GMenuExporter
*exporter
);
102 static GMenuExporterGroup
* g_menu_exporter_lookup_group (GMenuExporter
*exporter
,
104 static void g_menu_exporter_report (GMenuExporter
*exporter
,
106 static void g_menu_exporter_remove_group (GMenuExporter
*exporter
,
109 /* {{{1 GMenuExporterLink, GMenuExporterMenu */
111 struct _GMenuExporterMenu
113 GMenuExporterGroup
*group
;
118 GSequence
*item_links
;
121 struct _GMenuExporterLink
124 GMenuExporterMenu
*menu
;
125 GMenuExporterLink
*next
;
129 g_menu_exporter_menu_free (GMenuExporterMenu
*menu
)
131 g_menu_exporter_group_remove_menu (menu
->group
, menu
->id
);
133 if (menu
->handler_id
!= 0)
134 g_signal_handler_disconnect (menu
->model
, menu
->handler_id
);
136 if (menu
->item_links
!= NULL
)
137 g_sequence_free (menu
->item_links
);
139 g_object_unref (menu
->model
);
141 g_slice_free (GMenuExporterMenu
, menu
);
145 g_menu_exporter_link_free (gpointer data
)
147 GMenuExporterLink
*link
= data
;
151 GMenuExporterLink
*tmp
= link
;
154 g_menu_exporter_menu_free (tmp
->menu
);
157 g_slice_free (GMenuExporterLink
, tmp
);
161 static GMenuExporterLink
*
162 g_menu_exporter_menu_create_links (GMenuExporterMenu
*menu
,
165 GMenuExporterLink
*list
= NULL
;
170 iter
= g_menu_model_iterate_item_links (menu
->model
, position
);
172 while (g_menu_link_iter_get_next (iter
, &name
, &model
))
174 GMenuExporterGroup
*group
;
175 GMenuExporterLink
*tmp
;
177 /* keep sections in the same group, but create new groups
180 if (!g_str_equal (name
, "section"))
181 group
= g_menu_exporter_create_group (g_menu_exporter_group_get_exporter (menu
->group
));
185 tmp
= g_slice_new (GMenuExporterLink
);
186 tmp
->name
= g_strconcat (":", name
, NULL
);
187 tmp
->menu
= g_menu_exporter_group_add_menu (group
, model
);
191 g_object_unref (model
);
194 g_object_unref (iter
);
200 g_menu_exporter_menu_describe_item (GMenuExporterMenu
*menu
,
203 GMenuAttributeIter
*attr_iter
;
204 GVariantBuilder builder
;
206 GMenuExporterLink
*link
;
210 g_variant_builder_init (&builder
, G_VARIANT_TYPE_VARDICT
);
212 attr_iter
= g_menu_model_iterate_item_attributes (menu
->model
, position
);
213 while (g_menu_attribute_iter_get_next (attr_iter
, &name
, &value
))
215 g_variant_builder_add (&builder
, "{sv}", name
, value
);
216 g_variant_unref (value
);
218 g_object_unref (attr_iter
);
220 iter
= g_sequence_get_iter_at_pos (menu
->item_links
, position
);
221 for (link
= g_sequence_get (iter
); link
; link
= link
->next
)
222 g_variant_builder_add (&builder
, "{sv}", link
->name
,
223 g_variant_new ("(uu)", g_menu_exporter_group_get_id (link
->menu
->group
), link
->menu
->id
));
225 return g_variant_builder_end (&builder
);
229 g_menu_exporter_menu_list (GMenuExporterMenu
*menu
)
231 GVariantBuilder builder
;
234 g_variant_builder_init (&builder
, G_VARIANT_TYPE ("aa{sv}"));
236 n
= g_sequence_get_length (menu
->item_links
);
237 for (i
= 0; i
< n
; i
++)
238 g_variant_builder_add_value (&builder
, g_menu_exporter_menu_describe_item (menu
, i
));
240 return g_variant_builder_end (&builder
);
244 g_menu_exporter_menu_items_changed (GMenuModel
*model
,
250 GMenuExporterMenu
*menu
= user_data
;
251 GSequenceIter
*point
;
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
));
258 point
= g_sequence_get_iter_at_pos (menu
->item_links
, position
+ removed
);
259 g_sequence_remove_range (g_sequence_get_iter_at_pos (menu
->item_links
, position
), point
);
261 for (i
= position
; i
< position
+ added
; i
++)
262 g_sequence_insert_before (point
, g_menu_exporter_menu_create_links (menu
, i
));
264 if (g_menu_exporter_group_is_subscribed (menu
->group
))
266 GVariantBuilder builder
;
268 g_variant_builder_init (&builder
, G_VARIANT_TYPE ("(uuuuaa{sv})"));
269 g_variant_builder_add (&builder
, "u", g_menu_exporter_group_get_id (menu
->group
));
270 g_variant_builder_add (&builder
, "u", menu
->id
);
271 g_variant_builder_add (&builder
, "u", position
);
272 g_variant_builder_add (&builder
, "u", removed
);
274 g_variant_builder_open (&builder
, G_VARIANT_TYPE ("aa{sv}"));
275 for (i
= position
; i
< position
+ added
; i
++)
276 g_variant_builder_add_value (&builder
, g_menu_exporter_menu_describe_item (menu
, i
));
277 g_variant_builder_close (&builder
);
279 g_menu_exporter_report (g_menu_exporter_group_get_exporter (menu
->group
), g_variant_builder_end (&builder
));
284 g_menu_exporter_menu_prepare (GMenuExporterMenu
*menu
)
288 g_assert (menu
->item_links
== NULL
);
290 if (g_menu_model_is_mutable (menu
->model
))
291 menu
->handler_id
= g_signal_connect (menu
->model
, "items-changed",
292 G_CALLBACK (g_menu_exporter_menu_items_changed
), menu
);
294 menu
->item_links
= g_sequence_new (g_menu_exporter_link_free
);
296 n_items
= g_menu_model_get_n_items (menu
->model
);
298 g_menu_exporter_menu_items_changed (menu
->model
, 0, 0, n_items
, menu
);
301 static GMenuExporterMenu
*
302 g_menu_exporter_menu_new (GMenuExporterGroup
*group
,
306 GMenuExporterMenu
*menu
;
308 menu
= g_slice_new0 (GMenuExporterMenu
);
311 menu
->model
= g_object_ref (model
);
316 /* {{{1 GMenuExporterGroup */
318 struct _GMenuExporterGroup
320 GMenuExporter
*exporter
;
331 g_menu_exporter_group_check_if_useless (GMenuExporterGroup
*group
)
333 if (g_hash_table_size (group
->menus
) == 0 && group
->subscribed
== 0)
335 g_menu_exporter_remove_group (group
->exporter
, group
->id
);
337 g_hash_table_unref (group
->menus
);
339 g_slice_free (GMenuExporterGroup
, group
);
344 g_menu_exporter_group_subscribe (GMenuExporterGroup
*group
,
345 GVariantBuilder
*builder
)
350 if (!group
->prepared
)
352 GMenuExporterMenu
*menu
;
354 /* set this first, so that any menus created during the
355 * preparation of the first menu also end up in the prepared
358 group
->prepared
= TRUE
;
360 menu
= g_hash_table_lookup (group
->menus
, 0);
362 /* If the group was created by a subscription and does not yet
363 * exist, it won't have a root menu...
365 * That menu will be prepared if it is ever added (due to
366 * group->prepared == TRUE).
369 g_menu_exporter_menu_prepare (menu
);
374 g_hash_table_iter_init (&iter
, group
->menus
);
375 while (g_hash_table_iter_next (&iter
, &key
, &val
))
377 guint id
= GPOINTER_TO_INT (key
);
378 GMenuExporterMenu
*menu
= val
;
380 if (!g_sequence_is_empty (menu
->item_links
))
382 g_variant_builder_open (builder
, G_VARIANT_TYPE ("(uuaa{sv})"));
383 g_variant_builder_add (builder
, "u", group
->id
);
384 g_variant_builder_add (builder
, "u", id
);
385 g_variant_builder_add_value (builder
, g_menu_exporter_menu_list (menu
));
386 g_variant_builder_close (builder
);
392 g_menu_exporter_group_unsubscribe (GMenuExporterGroup
*group
,
395 g_assert (group
->subscribed
>= count
);
397 group
->subscribed
-= count
;
399 g_menu_exporter_group_check_if_useless (group
);
402 static GMenuExporter
*
403 g_menu_exporter_group_get_exporter (GMenuExporterGroup
*group
)
405 return group
->exporter
;
409 g_menu_exporter_group_is_subscribed (GMenuExporterGroup
*group
)
411 return group
->subscribed
> 0;
415 g_menu_exporter_group_get_id (GMenuExporterGroup
*group
)
421 g_menu_exporter_group_remove_menu (GMenuExporterGroup
*group
,
424 g_hash_table_remove (group
->menus
, GINT_TO_POINTER (id
));
426 g_menu_exporter_group_check_if_useless (group
);
429 static GMenuExporterMenu
*
430 g_menu_exporter_group_add_menu (GMenuExporterGroup
*group
,
433 GMenuExporterMenu
*menu
;
436 id
= group
->next_menu_id
++;
437 menu
= g_menu_exporter_menu_new (group
, id
, model
);
438 g_hash_table_insert (group
->menus
, GINT_TO_POINTER (id
), menu
);
441 g_menu_exporter_menu_prepare (menu
);
446 static GMenuExporterGroup
*
447 g_menu_exporter_group_new (GMenuExporter
*exporter
,
450 GMenuExporterGroup
*group
;
452 group
= g_slice_new0 (GMenuExporterGroup
);
453 group
->menus
= g_hash_table_new (NULL
, NULL
);
454 group
->exporter
= exporter
;
460 /* {{{1 GMenuExporterRemote */
462 struct _GMenuExporterRemote
464 GMenuExporter
*exporter
;
470 g_menu_exporter_remote_subscribe (GMenuExporterRemote
*remote
,
472 GVariantBuilder
*builder
)
474 GMenuExporterGroup
*group
;
477 count
= (gsize
) g_hash_table_lookup (remote
->watches
, GINT_TO_POINTER (group_id
));
478 g_hash_table_insert (remote
->watches
, GINT_TO_POINTER (group_id
), GINT_TO_POINTER (count
+ 1));
480 /* Group will be created (as empty/unsubscribed if it does not exist) */
481 group
= g_menu_exporter_lookup_group (remote
->exporter
, group_id
);
482 g_menu_exporter_group_subscribe (group
, builder
);
486 g_menu_exporter_remote_unsubscribe (GMenuExporterRemote
*remote
,
489 GMenuExporterGroup
*group
;
492 count
= (gsize
) g_hash_table_lookup (remote
->watches
, GINT_TO_POINTER (group_id
));
498 g_hash_table_insert (remote
->watches
, GINT_TO_POINTER (group_id
), GINT_TO_POINTER (count
- 1));
500 g_hash_table_remove (remote
->watches
, GINT_TO_POINTER (group_id
));
502 group
= g_menu_exporter_lookup_group (remote
->exporter
, group_id
);
503 g_menu_exporter_group_unsubscribe (group
, 1);
507 g_menu_exporter_remote_has_subscriptions (GMenuExporterRemote
*remote
)
509 return g_hash_table_size (remote
->watches
) != 0;
513 g_menu_exporter_remote_free (gpointer data
)
515 GMenuExporterRemote
*remote
= data
;
519 g_hash_table_iter_init (&iter
, remote
->watches
);
520 while (g_hash_table_iter_next (&iter
, &key
, &val
))
522 GMenuExporterGroup
*group
;
524 group
= g_menu_exporter_lookup_group (remote
->exporter
, GPOINTER_TO_INT (key
));
525 g_menu_exporter_group_unsubscribe (group
, GPOINTER_TO_INT (val
));
528 if (remote
->watch_id
> 0)
529 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
;
561 GMenuExporterRemote
*peer_remote
;
566 g_menu_exporter_name_vanished (GDBusConnection
*connection
,
570 GMenuExporter
*exporter
= user_data
;
572 /* connection == NULL when we get called because the connection closed */
573 g_assert (exporter
->connection
== connection
|| connection
== NULL
);
575 g_hash_table_remove (exporter
->remotes
, name
);
579 g_menu_exporter_subscribe (GMenuExporter
*exporter
,
583 GMenuExporterRemote
*remote
;
584 GVariantBuilder builder
;
589 remote
= g_hash_table_lookup (exporter
->remotes
, sender
);
591 remote
= exporter
->peer_remote
;
599 watch_id
= g_bus_watch_name_on_connection (exporter
->connection
, sender
, G_BUS_NAME_WATCHER_FLAGS_NONE
,
600 NULL
, g_menu_exporter_name_vanished
, exporter
, NULL
);
601 remote
= g_menu_exporter_remote_new (exporter
, watch_id
);
602 g_hash_table_insert (exporter
->remotes
, g_strdup (sender
), remote
);
605 remote
= exporter
->peer_remote
=
606 g_menu_exporter_remote_new (exporter
, 0);
609 g_variant_builder_init (&builder
, G_VARIANT_TYPE ("(a(uuaa{sv}))"));
611 g_variant_builder_open (&builder
, G_VARIANT_TYPE ("a(uuaa{sv})"));
613 g_variant_iter_init (&iter
, group_ids
);
614 while (g_variant_iter_next (&iter
, "u", &id
))
615 g_menu_exporter_remote_subscribe (remote
, id
, &builder
);
617 g_variant_builder_close (&builder
);
619 return g_variant_builder_end (&builder
);
623 g_menu_exporter_unsubscribe (GMenuExporter
*exporter
,
627 GMenuExporterRemote
*remote
;
632 remote
= g_hash_table_lookup (exporter
->remotes
, sender
);
634 remote
= exporter
->peer_remote
;
639 g_variant_iter_init (&iter
, group_ids
);
640 while (g_variant_iter_next (&iter
, "u", &id
))
641 g_menu_exporter_remote_unsubscribe (remote
, id
);
643 if (!g_menu_exporter_remote_has_subscriptions (remote
))
646 g_hash_table_remove (exporter
->remotes
, sender
);
648 g_clear_pointer (&exporter
->peer_remote
, g_menu_exporter_remote_free
);
653 g_menu_exporter_report (GMenuExporter
*exporter
,
656 GVariantBuilder builder
;
658 g_variant_builder_init (&builder
, G_VARIANT_TYPE_TUPLE
);
659 g_variant_builder_open (&builder
, G_VARIANT_TYPE_ARRAY
);
660 g_variant_builder_add_value (&builder
, report
);
661 g_variant_builder_close (&builder
);
663 g_dbus_connection_emit_signal (exporter
->connection
,
665 exporter
->object_path
,
666 "org.gtk.Menus", "Changed",
667 g_variant_builder_end (&builder
),
672 g_menu_exporter_remove_group (GMenuExporter
*exporter
,
675 g_hash_table_remove (exporter
->groups
, GINT_TO_POINTER (id
));
678 static GMenuExporterGroup
*
679 g_menu_exporter_lookup_group (GMenuExporter
*exporter
,
682 GMenuExporterGroup
*group
;
684 group
= g_hash_table_lookup (exporter
->groups
, GINT_TO_POINTER (group_id
));
688 group
= g_menu_exporter_group_new (exporter
, group_id
);
689 g_hash_table_insert (exporter
->groups
, GINT_TO_POINTER (group_id
), group
);
695 static GMenuExporterGroup
*
696 g_menu_exporter_create_group (GMenuExporter
*exporter
)
698 GMenuExporterGroup
*group
;
701 id
= exporter
->next_group_id
++;
702 group
= g_menu_exporter_group_new (exporter
, id
);
703 g_hash_table_insert (exporter
->groups
, GINT_TO_POINTER (id
), group
);
709 g_menu_exporter_free (gpointer user_data
)
711 GMenuExporter
*exporter
= user_data
;
713 g_menu_exporter_menu_free (exporter
->root
);
714 g_clear_pointer (&exporter
->peer_remote
, g_menu_exporter_remote_free
);
715 g_hash_table_unref (exporter
->remotes
);
716 g_hash_table_unref (exporter
->groups
);
717 g_object_unref (exporter
->connection
);
718 g_free (exporter
->object_path
);
720 g_slice_free (GMenuExporter
, exporter
);
724 g_menu_exporter_method_call (GDBusConnection
*connection
,
726 const gchar
*object_path
,
727 const gchar
*interface_name
,
728 const gchar
*method_name
,
729 GVariant
*parameters
,
730 GDBusMethodInvocation
*invocation
,
733 GMenuExporter
*exporter
= user_data
;
736 group_ids
= g_variant_get_child_value (parameters
, 0);
738 if (g_str_equal (method_name
, "Start"))
739 g_dbus_method_invocation_return_value (invocation
, g_menu_exporter_subscribe (exporter
, sender
, group_ids
));
741 else if (g_str_equal (method_name
, "End"))
743 g_menu_exporter_unsubscribe (exporter
, sender
, group_ids
);
744 g_dbus_method_invocation_return_value (invocation
, NULL
);
748 g_assert_not_reached ();
750 g_variant_unref (group_ids
);
753 /* {{{1 Public API */
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
762 * Exports @menu on @connection at @object_path.
764 * The implemented D-Bus API should be considered private.
765 * It is subject to change in the future.
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).
771 * You can unexport the menu model using
772 * g_dbus_connection_unexport_menu_model() with the return value of
775 * Returns: the ID of the export (never zero), or 0 in case of failure
780 g_dbus_connection_export_menu_model (GDBusConnection
*connection
,
781 const gchar
*object_path
,
785 const GDBusInterfaceVTable vtable
= {
786 g_menu_exporter_method_call
,
788 GMenuExporter
*exporter
;
791 exporter
= g_slice_new0 (GMenuExporter
);
793 id
= g_dbus_connection_register_object (connection
, object_path
, org_gtk_Menus_get_interface (),
794 &vtable
, exporter
, g_menu_exporter_free
, error
);
798 g_slice_free (GMenuExporter
, exporter
);
802 exporter
->connection
= g_object_ref (connection
);
803 exporter
->object_path
= g_strdup (object_path
);
804 exporter
->groups
= g_hash_table_new (NULL
, NULL
);
805 exporter
->remotes
= g_hash_table_new_full (g_str_hash
, g_str_equal
, g_free
, g_menu_exporter_remote_free
);
806 exporter
->root
= g_menu_exporter_group_add_menu (g_menu_exporter_create_group (exporter
), menu
);
812 * g_dbus_connection_unexport_menu_model:
813 * @connection: a #GDBusConnection
814 * @export_id: the ID from g_dbus_connection_export_menu_model()
816 * Reverses the effect of a previous call to
817 * g_dbus_connection_export_menu_model().
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.
826 g_dbus_connection_unexport_menu_model (GDBusConnection
*connection
,
829 g_dbus_connection_unregister_object (connection
, export_id
);
833 /* vim:set foldmethod=marker: */