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 */
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.
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.
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).
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}).
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
;
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'/>"
161 " <method name='End'>"
162 " <arg type='au' name='groups' direction='in'/>"
164 " <signal name='Changed'>"
165 " arg type='a(uuuuaa{sv})' name='changes'/>"
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
,
193 static void g_menu_exporter_group_remove_menu (GMenuExporterGroup
*group
,
196 static GMenuExporterGroup
* g_menu_exporter_create_group (GMenuExporter
*exporter
);
197 static GMenuExporterGroup
* g_menu_exporter_lookup_group (GMenuExporter
*exporter
,
199 static void g_menu_exporter_report (GMenuExporter
*exporter
,
201 static void g_menu_exporter_remove_group (GMenuExporter
*exporter
,
204 /* {{{1 GMenuExporterLink, GMenuExporterMenu */
206 struct _GMenuExporterMenu
208 GMenuExporterGroup
*group
;
213 GSequence
*item_links
;
216 struct _GMenuExporterLink
219 GMenuExporterMenu
*menu
;
220 GMenuExporterLink
*next
;
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
);
240 g_menu_exporter_link_free (gpointer data
)
242 GMenuExporterLink
*link
= data
;
246 GMenuExporterLink
*tmp
= link
;
249 g_menu_exporter_menu_free (tmp
->menu
);
252 g_slice_free (GMenuExporterLink
, tmp
);
256 static GMenuExporterLink
*
257 g_menu_exporter_menu_create_links (GMenuExporterMenu
*menu
,
260 GMenuExporterLink
*list
= NULL
;
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
;
273 group
= g_menu_exporter_create_group (g_menu_exporter_group_get_exporter (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
);
283 g_object_unref (model
);
286 g_object_unref (iter
);
292 g_menu_exporter_menu_describe_item (GMenuExporterMenu
*menu
,
295 GMenuAttributeIter
*attr_iter
;
296 GVariantBuilder builder
;
298 GMenuExporterLink
*link
;
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
);
321 g_menu_exporter_menu_list (GMenuExporterMenu
*menu
)
323 GVariantBuilder builder
;
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
);
336 g_menu_exporter_menu_items_changed (GMenuModel
*model
,
342 GMenuExporterMenu
*menu
= user_data
;
343 GSequenceIter
*point
;
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
));
376 g_menu_exporter_menu_prepare (GMenuExporterMenu
*menu
)
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
);
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
,
398 GMenuExporterMenu
*menu
;
400 menu
= g_slice_new0 (GMenuExporterMenu
);
403 menu
->model
= g_object_ref (model
);
408 /* {{{1 GMenuExporterGroup */
410 struct _GMenuExporterGroup
412 GMenuExporter
*exporter
;
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
);
436 g_menu_exporter_group_subscribe (GMenuExporterGroup
*group
,
437 GVariantBuilder
*builder
)
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
450 group
->prepared
= TRUE
;
452 menu
= g_hash_table_lookup (group
->menus
, 0);
453 g_menu_exporter_menu_prepare (menu
);
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
);
476 g_menu_exporter_group_unsubscribe (GMenuExporterGroup
*group
,
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
;
493 g_menu_exporter_group_is_subscribed (GMenuExporterGroup
*group
)
495 return group
->subscribed
> 0;
499 g_menu_exporter_group_get_id (GMenuExporterGroup
*group
)
505 g_menu_exporter_group_remove_menu (GMenuExporterGroup
*group
,
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
,
517 GMenuExporterMenu
*menu
;
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
);
525 g_menu_exporter_menu_prepare (menu
);
530 static GMenuExporterGroup
*
531 g_menu_exporter_group_new (GMenuExporter
*exporter
,
534 GMenuExporterGroup
*group
;
536 group
= g_slice_new0 (GMenuExporterGroup
);
537 group
->menus
= g_hash_table_new (NULL
, NULL
);
538 group
->exporter
= exporter
;
544 /* {{{1 GMenuExporterRemote */
546 struct _GMenuExporterRemote
548 GMenuExporter
*exporter
;
554 g_menu_exporter_remote_subscribe (GMenuExporterRemote
*remote
,
556 GVariantBuilder
*builder
)
558 GMenuExporterGroup
*group
;
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
);
569 g_menu_exporter_remote_unsubscribe (GMenuExporterRemote
*remote
,
572 GMenuExporterGroup
*group
;
575 count
= (gsize
) g_hash_table_lookup (remote
->watches
, GINT_TO_POINTER (group_id
));
581 g_hash_table_insert (remote
->watches
, GINT_TO_POINTER (group_id
), GINT_TO_POINTER (count
- 1));
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);
590 g_menu_exporter_remote_has_subscriptions (GMenuExporterRemote
*remote
)
592 return g_hash_table_size (remote
->watches
) != 0;
596 g_menu_exporter_remote_free (gpointer data
)
598 GMenuExporterRemote
*remote
= data
;
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
,
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
;
631 /* {{{1 GMenuExporter */
633 struct _GMenuExporter
635 GDBusConnection
*connection
;
637 guint registration_id
;
641 GMenuExporterMenu
*root
;
646 g_menu_exporter_name_vanished (GDBusConnection
*connection
,
650 GMenuExporter
*exporter
= user_data
;
652 g_assert (exporter
->connection
== connection
);
654 g_hash_table_remove (exporter
->remotes
, name
);
658 g_menu_exporter_subscribe (GMenuExporter
*exporter
,
662 GMenuExporterRemote
*remote
;
663 GVariantBuilder builder
;
667 remote
= g_hash_table_lookup (exporter
->remotes
, sender
);
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
);
693 g_menu_exporter_unsubscribe (GMenuExporter
*exporter
,
697 GMenuExporterRemote
*remote
;
701 remote
= g_hash_table_lookup (exporter
->remotes
, sender
);
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
);
715 g_menu_exporter_report (GMenuExporter
*exporter
,
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
,
727 exporter
->object_path
,
728 "org.gtk.Menus", "Changed",
729 g_variant_builder_end (&builder
),
734 g_menu_exporter_remove_group (GMenuExporter
*exporter
,
737 g_hash_table_remove (exporter
->groups
, GINT_TO_POINTER (id
));
740 static GMenuExporterGroup
*
741 g_menu_exporter_lookup_group (GMenuExporter
*exporter
,
744 GMenuExporterGroup
*group
;
746 group
= g_hash_table_lookup (exporter
->groups
, GINT_TO_POINTER (group_id
));
750 group
= g_menu_exporter_group_new (exporter
, group_id
);
751 g_hash_table_insert (exporter
->groups
, GINT_TO_POINTER (group_id
), group
);
757 static GMenuExporterGroup
*
758 g_menu_exporter_create_group (GMenuExporter
*exporter
)
760 GMenuExporterGroup
*group
;
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
);
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
);
784 g_menu_exporter_method_call (GDBusConnection
*connection
,
786 const gchar
*object_path
,
787 const gchar
*interface_name
,
788 const gchar
*method_name
,
789 GVariant
*parameters
,
790 GDBusMethodInvocation
*invocation
,
793 GMenuExporter
*exporter
= user_data
;
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
);
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
;
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
,
831 const GDBusInterfaceVTable vtable
= {
832 g_menu_exporter_method_call
,
834 GMenuExporter
*exporter
;
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
);
844 g_slice_free (GMenuExporter
, exporter
);
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
);
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.
887 g_menu_model_dbus_export_start (GDBusConnection
*connection
,
888 const gchar
*object_path
,
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");
903 exporter
= g_menu_exporter_new (connection
, object_path
, menu
, error
);
905 if (exporter
== NULL
)
908 g_hash_table_insert (g_menu_exporter_exported_menus
, menu
, exporter
);
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
926 g_menu_model_dbus_export_stop (GMenuModel
*menu
)
928 GMenuExporter
*exporter
;
930 if G_UNLIKELY (g_menu_exporter_exported_menus
== NULL
)
933 exporter
= g_hash_table_lookup (g_menu_exporter_exported_menus
, menu
);
934 if G_UNLIKELY (exporter
== NULL
)
937 g_hash_table_remove (g_menu_exporter_exported_menus
, menu
);
938 g_menu_exporter_free (exporter
);
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
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
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
)
971 exporter
= g_hash_table_lookup (g_menu_exporter_exported_menus
, menu
);
972 if (exporter
== NULL
)
976 *connection
= g_menu_exporter_get_connection (exporter
);
979 *object_path
= g_menu_exporter_get_object_path (exporter
);
985 /* vim:set foldmethod=marker: */