Make stopping an action group export work
[platform/upstream/glib.git] / gio / gactiongroupexporter.c
1 /*
2  * Copyright © 2010 Codethink Limited
3  * Copyright © 2011 Canonical Limited
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published
7  * by the Free Software Foundation; either version 2 of the licence or (at
8  * your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
18  * Boston, MA 02111-1307, USA.
19  *
20  * Authors: Ryan Lortie <desrt@desrt.ca>
21  */
22
23 #include "config.h"
24
25 #include "gactiongroupexporter.h"
26
27 #include "gdbusmethodinvocation.h"
28 #include "gdbusintrospection.h"
29 #include "gdbusconnection.h"
30 #include "gactiongroup.h"
31 #include "gapplication.h"
32 #include "gdbuserror.h"
33
34 /**
35  * SECTION:gactiongroupexporter
36  * @title: GActionGroup exporter
37  * @short_description: Export GActionGroups on D-Bus
38  * @see_also: #GActionGroup, #GDBusActionGroup
39  *
40  * These functions support exporting a #GActionGroup on D-Bus.
41  * The D-Bus interface that is used is a private implementation
42  * detail.
43  *
44  * To access an exported #GActionGroup remotely, use
45  * g_dbus_action_group_new() to obtain a #GDBusActionGroup.
46  */
47
48 G_GNUC_INTERNAL GVariant *
49 g_action_group_describe_action (GActionGroup *action_group,
50                                 const gchar  *name)
51 {
52   const GVariantType *type;
53   GVariantBuilder builder;
54   gboolean enabled;
55   GVariant *state;
56
57   g_variant_builder_init (&builder, G_VARIANT_TYPE ("(bgav)"));
58
59   enabled = g_action_group_get_action_enabled (action_group, name);
60   g_variant_builder_add (&builder, "b", enabled);
61
62   if ((type = g_action_group_get_action_parameter_type (action_group, name)))
63     {
64       gchar *str = g_variant_type_dup_string (type);
65       g_variant_builder_add (&builder, "g", str);
66       g_free (str);
67     }
68   else
69     g_variant_builder_add (&builder, "g", "");
70
71   g_variant_builder_open (&builder, G_VARIANT_TYPE ("av"));
72   if ((state = g_action_group_get_action_state (action_group, name)))
73     {
74       g_variant_builder_add (&builder, "v", state);
75       g_variant_unref (state);
76     }
77   g_variant_builder_close (&builder);
78
79   return g_variant_builder_end (&builder);
80 }
81
82 /* The org.gtk.Actions interface
83  * =============================
84  *
85  * This interface describes a group of actions.
86  *
87  * Each action:
88  * - has a unique string name
89  * - can be activated
90  * - optionally has a parameter type that must be given to the activation
91  * - has an enabled state that may be true or false
92  * - optionally has a state which can change value, but not type
93  *
94  * Methods
95  * -------
96  *
97  * List :: () → (as)
98  *
99  *   Lists the names of the actions exported at this object path.
100  *
101  * Describe :: (s) → (bgav)
102  *
103  *   Describes a single action, or a given name.
104  *
105  *   The return value has the following components:
106  *   b: specifies if the action is currently enabled. This is
107  *      a hint that attempting to interact with the action will
108  *      produce no effect.
109  *   g: specifies the optional parameter type. If not "",
110  *      the string specifies the type of argument that must
111  *      be passed to the activation.
112  *   av: specifies the optional state. If not empty, the array
113  *       contains the current value of the state as a variant
114  *
115  * DescribeAll :: () → (a{s(bgav)})
116  *
117  *   Describes all actions in a single round-trip.
118  *
119  *   The dictionary maps action name strings to their descriptions
120  *   (in the format discussed above).
121  *
122  * Activate :: (sava{sv}) → ()
123  *
124  *   Requests activation of the named action.
125  *
126  *   The action is named by the first parameter (s).
127  *
128  *   If the action activation requires a parameter then this parameter
129  *   must be given in the second parameter (av). If there is no parameter
130  *   to be specified, the array must be empty.
131  *
132  *   The final parameter (a{sv}) is a list of "platform data".
133  *
134  *   This method is not guaranteed to have any particular effect. The
135  *   implementation may take some action (including changing the state
136  *   of the action, if it is stateful) or it may take none at all. In
137  *   particular, callers should expect their request to be completely
138  *   ignored when the enabled flag is false (but even this is not
139  *   guaranteed).
140  *
141  * SetState :: (sva{sv}) → ()
142  *
143  *   Requests the state of an action to be changed to the given value.
144  *
145  *   The action is named by the first parameter (s).
146  *
147  *   The requested new state is given in the second parameter (v).
148  *   It must be equal in type to the existing state.
149  *
150  *   The final parameter (a{sv}) is a list of "platform data".
151  *
152  *   This method is not guaranteed to have any particular effect.
153  *   The implementation of an action can choose to ignore the requested
154  *   state change, or choose to change its state to something else or
155  *   to trigger other side effects. In particular, callers should expect
156  *   their request to be completely ignored when the enabled flag is
157  *   false (but even this is not guaranteed).
158  *
159  * Signals
160  * -------
161  *
162  * Changed :: (asa{sb}a{sv}a{s(bgav)})
163  *
164  *   Signals that some change has occured to the action group.
165  *
166  *   Four separate types of changes are possible, and the 4 parameters
167  *   of the change signal reflect these possibilities:
168  *   as: a list of removed actions
169  *   a{sb}: a list of actions that had their enabled flag changed
170  *   a{sv}: a list of actions that had their state changed
171  *   a{s(bgav)}: a list of new actions added in the same format as
172  *               the return value of the DescribeAll method
173  */
174
175 /* Using XML saves us dozens of relocations vs. using the introspection
176  * structure types.  We only need to burn cycles and memory if we
177  * actually use the exporter -- not in every single app using GIO.
178  *
179  * It's also a lot easier to read. :)
180  */
181 const char org_gtk_Actions_xml[] =
182   "<node>"
183   "  <interface name='org.gtk.Actions'>"
184   "    <method name='List'>"
185   "      <arg type='as' name='list' direction='out'/>"
186   "    </method>"
187   "    <method name='Describe'>"
188   "      <arg type='s' name='action_name' direction='in'/>"
189   "      <arg type='(bgav)' name='description' direction='out'/>"
190   "    </method>"
191   "    <method name='DescribeAll'>"
192   "      <arg type='a{s(bgav)}' name='descriptions' direction='out'/>"
193   "    </method>"
194   "    <method name='Activate'>"
195   "      <arg type='s' name='action_name' direction='in'/>"
196   "      <arg type='av' name='parameter' direction='in'/>"
197   "      <arg type='a{sv}' name='platform_data' direction='in'/>"
198   "    </method>"
199   "    <method name='SetState'>"
200   "      <arg type='s' name='action_name' direction='in'/>"
201   "      <arg type='v' name='value' direction='in'/>"
202   "      <arg type='a{sv}' name='platform_data' direction='in'/>"
203   "    </method>"
204   "    <signal name='Changed'>"
205   "      <arg type='as' name='removals'/>"
206   "      <arg type='a{sb}' name='enable_changes'/>"
207   "      <arg type='a{sv}' name='state_changes'/>"
208   "      <arg type='a{s(bgav)}' name='additions'/>"
209   "    </signal>"
210   "  </interface>"
211   "</node>";
212
213 static GDBusInterfaceInfo *org_gtk_Actions;
214 static GHashTable *exported_groups;
215
216 typedef struct
217 {
218   GActionGroup    *action_group;
219   GDBusConnection *connection;
220   gchar           *object_path;
221   guint            registration_id;
222   GHashTable      *pending_changes;
223   guint            pending_id;
224   gulong           signal_ids[4];
225 } GActionGroupExporter;
226
227 #define ACTION_ADDED_EVENT             (1u<<0)
228 #define ACTION_REMOVED_EVENT           (1u<<1)
229 #define ACTION_STATE_CHANGED_EVENT     (1u<<2)
230 #define ACTION_ENABLED_CHANGED_EVENT   (1u<<3)
231
232 static gboolean
233 g_action_group_exporter_dispatch_events (gpointer user_data)
234 {
235   GActionGroupExporter *exporter = user_data;
236   GVariantBuilder removes;
237   GVariantBuilder enabled_changes;
238   GVariantBuilder state_changes;
239   GVariantBuilder adds;
240   GHashTableIter iter;
241   gpointer value;
242   gpointer key;
243
244   g_variant_builder_init (&removes, G_VARIANT_TYPE_STRING_ARRAY);
245   g_variant_builder_init (&enabled_changes, G_VARIANT_TYPE ("a{sb}"));
246   g_variant_builder_init (&state_changes, G_VARIANT_TYPE ("a{sv}"));
247   g_variant_builder_init (&adds, G_VARIANT_TYPE ("a{s(bgav)}"));
248
249   g_hash_table_iter_init (&iter, exporter->pending_changes);
250   while (g_hash_table_iter_next (&iter, &key, &value))
251     {
252       guint events = GPOINTER_TO_INT (value);
253       const gchar *name = key;
254
255       /* Adds and removes are incompatible with enabled or state
256        * changes, but we must report at least one event.
257        */
258       g_assert (((events & (ACTION_ENABLED_CHANGED_EVENT | ACTION_STATE_CHANGED_EVENT)) == 0) !=
259                 ((events & (ACTION_REMOVED_EVENT | ACTION_ADDED_EVENT)) == 0));
260
261       if (events & ACTION_REMOVED_EVENT)
262         g_variant_builder_add (&removes, "s", name);
263
264       if (events & ACTION_ENABLED_CHANGED_EVENT)
265         {
266           gboolean enabled;
267
268           enabled = g_action_group_get_action_enabled (exporter->action_group, name);
269           g_variant_builder_add (&enabled_changes, "{sb}", name, enabled);
270         }
271
272       if (events & ACTION_STATE_CHANGED_EVENT)
273         {
274           GVariant *state;
275
276           state = g_action_group_get_action_state (exporter->action_group, name);
277           g_variant_builder_add (&state_changes, "{sv}", name, state);
278           g_variant_unref (state);
279         }
280
281       if (events & ACTION_ADDED_EVENT)
282         {
283           GVariant *description;
284
285           description = g_action_group_describe_action (exporter->action_group, name);
286           g_variant_builder_add (&adds, "{s@(bgav)}", name, description);
287         }
288     }
289
290   g_hash_table_remove_all (exporter->pending_changes);
291
292   g_dbus_connection_emit_signal (exporter->connection, NULL, exporter->object_path,
293                                  "org.gtk.Actions", "Changed",
294                                  g_variant_new ("(asa{sb}a{sv}a{s(bgav)})",
295                                                 &removes, &enabled_changes,
296                                                 &state_changes, &adds),
297                                  NULL);
298
299   exporter->pending_id = 0;
300
301   return FALSE;
302 }
303
304 static guint
305 g_action_group_exporter_get_events (GActionGroupExporter *exporter,
306                                     const gchar          *name)
307 {
308   return (gsize) g_hash_table_lookup (exporter->pending_changes, name);
309 }
310
311 static void
312 g_action_group_exporter_set_events (GActionGroupExporter *exporter,
313                                     const gchar          *name,
314                                     guint                 events)
315 {
316   gboolean have_events;
317   gboolean is_queued;
318
319   if (events != 0)
320     g_hash_table_insert (exporter->pending_changes, g_strdup (name), GINT_TO_POINTER (events));
321   else
322     g_hash_table_remove (exporter->pending_changes, name);
323
324   have_events = g_hash_table_size (exporter->pending_changes) > 0;
325   is_queued = exporter->pending_id > 0;
326
327   if (have_events && !is_queued)
328     exporter->pending_id = g_idle_add (g_action_group_exporter_dispatch_events, exporter);
329
330   if (!have_events && is_queued)
331     {
332       g_source_remove (exporter->pending_id);
333       exporter->pending_id = 0;
334     }
335 }
336
337 static void
338 g_action_group_exporter_action_added (GActionGroup *action_group,
339                                       const gchar  *action_name,
340                                       gpointer      user_data)
341 {
342   GActionGroupExporter *exporter = user_data;
343   guint event_mask;
344
345   event_mask = g_action_group_exporter_get_events (exporter, action_name);
346
347   /* The action is new, so we should not have any pending
348    * enabled-changed or state-changed signals for it.
349    */
350   g_assert (~event_mask & (ACTION_STATE_CHANGED_EVENT |
351                            ACTION_ENABLED_CHANGED_EVENT));
352
353   event_mask |= ACTION_ADDED_EVENT;
354
355   g_action_group_exporter_set_events (exporter, action_name, event_mask);
356 }
357
358 static void
359 g_action_group_exporter_action_removed (GActionGroup *action_group,
360                                         const gchar  *action_name,
361                                         gpointer      user_data)
362 {
363   GActionGroupExporter *exporter = user_data;
364   guint event_mask;
365
366   event_mask = g_action_group_exporter_get_events (exporter, action_name);
367
368   /* If the add event for this is still queued then just cancel it since
369    * it's gone now.
370    *
371    * If the event was freshly added, there should not have been any
372    * enabled or state changed events.
373    */
374   if (event_mask & ACTION_ADDED_EVENT)
375     {
376       g_assert (~event_mask & ~(ACTION_STATE_CHANGED_EVENT | ACTION_ENABLED_CHANGED_EVENT));
377       event_mask &= ~ACTION_ADDED_EVENT;
378     }
379
380   /* Otherwise, queue a remove event.  Drop any state or enabled changes
381    * that were queued before the remove. */
382   else
383     {
384       event_mask |= ACTION_REMOVED_EVENT;
385       event_mask &= ~(ACTION_STATE_CHANGED_EVENT | ACTION_ENABLED_CHANGED_EVENT);
386     }
387
388   g_action_group_exporter_set_events (exporter, action_name, event_mask);
389 }
390
391 static void
392 g_action_group_exporter_action_state_changed (GActionGroup *action_group,
393                                               const gchar  *action_name,
394                                               GVariant     *value,
395                                               gpointer      user_data)
396 {
397   GActionGroupExporter *exporter = user_data;
398   guint event_mask;
399
400   event_mask = g_action_group_exporter_get_events (exporter, action_name);
401
402   /* If it was removed, it must have been added back.  Otherwise, why
403    * are we hearing about changes?
404    */
405   g_assert (~event_mask & ACTION_REMOVED_EVENT ||
406             event_mask & ACTION_ADDED_EVENT);
407
408   /* If it is freshly added, don't also bother with the state change
409    * signal since the updated state will be sent as part of the pending
410    * add message.
411    */
412   if (~event_mask & ACTION_ADDED_EVENT)
413     event_mask |= ACTION_STATE_CHANGED_EVENT;
414
415   g_action_group_exporter_set_events (exporter, action_name, event_mask);
416 }
417
418 static void
419 g_action_group_exporter_action_enabled_changed (GActionGroup *action_group,
420                                                 const gchar  *action_name,
421                                                 gboolean      enabled,
422                                                 gpointer      user_data)
423 {
424   GActionGroupExporter *exporter = user_data;
425   guint event_mask;
426
427   event_mask = g_action_group_exporter_get_events (exporter, action_name);
428
429   /* Reasoning as above. */
430   g_assert (~event_mask & ACTION_REMOVED_EVENT ||
431             event_mask & ACTION_ADDED_EVENT);
432
433   if (~event_mask & ACTION_ADDED_EVENT)
434     event_mask |= ACTION_ENABLED_CHANGED_EVENT;
435
436   g_action_group_exporter_set_events (exporter, action_name, event_mask);
437 }
438
439 static void
440 g_action_group_exporter_pre_emit (GActionGroupExporter *exporter,
441                                   GVariant             *platform_data)
442 {
443   if (G_IS_APPLICATION (exporter->action_group))
444     G_APPLICATION_GET_CLASS (exporter->action_group)
445       ->before_emit (G_APPLICATION (exporter->action_group), platform_data);
446 }
447
448 static void
449 g_action_group_exporter_post_emit (GActionGroupExporter *exporter,
450                                    GVariant             *platform_data)
451 {
452   if (G_IS_APPLICATION (exporter->action_group))
453     G_APPLICATION_GET_CLASS (exporter->action_group)
454       ->after_emit (G_APPLICATION (exporter->action_group), platform_data);
455 }
456
457 static void
458 org_gtk_Actions_method_call (GDBusConnection       *connection,
459                              const gchar           *sender,
460                              const gchar           *object_path,
461                              const gchar           *interface_name,
462                              const gchar           *method_name,
463                              GVariant              *parameters,
464                              GDBusMethodInvocation *invocation,
465                              gpointer               user_data)
466 {
467   GActionGroupExporter *exporter = user_data;
468   GVariant *result = NULL;
469
470   if (g_str_equal (method_name, "List"))
471     {
472       gchar **list;
473
474       list = g_action_group_list_actions (exporter->action_group);
475       result = g_variant_new ("(^as)", list);
476       g_strfreev (list);
477     }
478
479   else if (g_str_equal (method_name, "Describe"))
480     {
481       const gchar *name;
482       GVariant *desc;
483
484       g_variant_get (parameters, "(&s)", &name);
485       desc = g_action_group_describe_action (exporter->action_group, name);
486       result = g_variant_new ("(@(bgav))", desc);
487     }
488
489   else if (g_str_equal (method_name, "DescribeAll"))
490     {
491       GVariantBuilder builder;
492       gchar **list;
493       gint i;
494
495       list = g_action_group_list_actions (exporter->action_group);
496       g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{s(bgav)}"));
497       for (i = 0; list[i]; i++)
498         {
499           const gchar *name = list[i];
500           GVariant *description;
501
502           description = g_action_group_describe_action (exporter->action_group, name);
503           g_variant_builder_add (&builder, "{s@(bgav)}", name, description);
504         }
505       result = g_variant_new ("(a{s(bgav)})", &builder);
506       g_strfreev (list);
507     }
508
509   else if (g_str_equal (method_name, "Activate"))
510     {
511       GVariant *parameter = NULL;
512       GVariant *platform_data;
513       GVariantIter *iter;
514       const gchar *name;
515
516       g_variant_get (parameters, "(&sav@a{sv})", &name, &iter, &platform_data);
517       g_variant_iter_next (iter, "v", &parameter);
518       g_variant_iter_free (iter);
519
520       g_action_group_exporter_pre_emit (exporter, platform_data);
521       g_action_group_activate_action (exporter->action_group, name, parameter);
522       g_action_group_exporter_post_emit (exporter, platform_data);
523
524       if (parameter)
525         g_variant_unref (parameter);
526
527       g_variant_unref (platform_data);
528     }
529
530   else if (g_str_equal (method_name, "SetState"))
531     {
532       GVariant *platform_data;
533       const gchar *name;
534       GVariant *state;
535
536       g_variant_get (parameters, "(&sv@a{sv})", &name, &state, &platform_data);
537       g_action_group_exporter_pre_emit (exporter, platform_data);
538       g_action_group_change_action_state (exporter->action_group, name, state);
539       g_action_group_exporter_post_emit (exporter, platform_data);
540       g_variant_unref (platform_data);
541       g_variant_unref (state);
542     }
543
544   else
545     g_assert_not_reached ();
546
547   g_dbus_method_invocation_return_value (invocation, result);
548 }
549
550 /**
551  * g_action_group_dbus_export_start:
552  * @connection: a #GDBusConnection
553  * @object_path: a D-Bus object path
554  * @action_group: a #GActionGroup
555  * @error: a pointer to a %NULL #GError, or %NULL
556  *
557  * Exports @action_group on @connection at @object_path.
558  *
559  * The implemented D-Bus API should be considered private.  It is
560  * subject to change in the future.
561  *
562  * A given action group can only be exported on one object path and an
563  * object path can only have one action group exported on it. If either
564  * constraint is violated, the export will fail and %FALSE will be
565  * returned (with @error set accordingly).
566  *
567  * Use g_action_group_dbus_export_stop() to stop exporting @action_group,
568  * or g_action_group_dbus_export_query() to find out if and where a given
569  * action group is exported.
570  *
571  * Returns: %TRUE if the export is successful, or %FALSE (with @error
572  *          set) in the event of a failure.
573  **/
574 gboolean
575 g_action_group_dbus_export_start (GDBusConnection  *connection,
576                                   const gchar      *object_path,
577                                   GActionGroup     *action_group,
578                                   GError          **error)
579 {
580   const GDBusInterfaceVTable vtable = {
581     org_gtk_Actions_method_call
582   };
583   GActionGroupExporter *exporter;
584
585   if G_UNLIKELY (exported_groups == NULL)
586     exported_groups = g_hash_table_new (NULL, NULL);
587
588   if G_UNLIKELY (org_gtk_Actions == NULL)
589     {
590       GError *error = NULL;
591       GDBusNodeInfo *info;
592
593       info = g_dbus_node_info_new_for_xml (org_gtk_Actions_xml, &error);
594       if G_UNLIKELY (info == NULL)
595         g_error ("%s", error->message);
596       org_gtk_Actions = g_dbus_node_info_lookup_interface (info, "org.gtk.Actions");
597       g_assert (org_gtk_Actions != NULL);
598       g_dbus_interface_info_ref (org_gtk_Actions);
599       g_dbus_node_info_unref (info);
600     }
601
602   if G_UNLIKELY (g_hash_table_lookup (exported_groups, action_group))
603     {
604       g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FILE_EXISTS,
605                    "The given GActionGroup has already been exported");
606       return FALSE;
607     }
608
609   exporter = g_slice_new (GActionGroupExporter);
610   exporter->registration_id = g_dbus_connection_register_object (connection, object_path, org_gtk_Actions,
611                                                                  &vtable, exporter, NULL, error);
612
613   if (exporter->registration_id == 0)
614     {
615       g_slice_free (GActionGroupExporter, exporter);
616       return FALSE;
617     }
618
619   g_hash_table_insert (exported_groups, action_group, exporter);
620   exporter->pending_changes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
621   exporter->pending_id = 0;
622   exporter->action_group = g_object_ref (action_group);
623   exporter->connection = g_object_ref (connection);
624   exporter->object_path = g_strdup (object_path);
625
626   exporter->signal_ids[0] =
627     g_signal_connect (action_group, "action-added",
628                       G_CALLBACK (g_action_group_exporter_action_added), exporter);
629   exporter->signal_ids[1] =
630     g_signal_connect (action_group, "action-removed",
631                       G_CALLBACK (g_action_group_exporter_action_removed), exporter);
632   exporter->signal_ids[2] =
633     g_signal_connect (action_group, "action-state-changed",
634                       G_CALLBACK (g_action_group_exporter_action_state_changed), exporter);
635   exporter->signal_ids[3] =
636     g_signal_connect (action_group, "action-enabled-changed",
637                       G_CALLBACK (g_action_group_exporter_action_enabled_changed), exporter);
638
639   return TRUE;
640 }
641
642 /**
643  * g_action_group_dbus_export_stop:
644  * @action_group: a #GActionGroup
645  *
646  * Stops the export of @action_group.
647  *
648  * This reverses the effect of a previous call to
649  * g_action_group_dbus_export_start() for @action_group.
650  *
651  * Returns: %TRUE if an export was stopped or %FALSE if @action_group
652  *          was not exported in the first place
653  **/
654 gboolean
655 g_action_group_dbus_export_stop (GActionGroup *action_group)
656 {
657   GActionGroupExporter *exporter;
658   gint i;
659
660   if G_UNLIKELY (exported_groups == NULL)
661     return FALSE;
662
663   exporter = g_hash_table_lookup (exported_groups, action_group);
664   if G_UNLIKELY (exporter == NULL)
665     return FALSE;
666
667   g_hash_table_remove (exported_groups, action_group);
668
669   g_dbus_connection_unregister_object (exporter->connection, exporter->registration_id);
670   for (i = 0; i < G_N_ELEMENTS (exporter->signal_ids); i++)
671     g_signal_handler_disconnect (exporter->action_group, exporter->signal_ids[i]);
672   g_object_unref (exporter->connection);
673   g_object_unref (exporter->action_group);
674   g_free (exporter->object_path);
675
676   g_slice_free (GActionGroupExporter, exporter);
677
678   return TRUE;
679 }
680
681 /**
682  * g_action_group_dbus_export_query:
683  * @action_group: a #GActionGroup
684  * @connection: (out): the #GDBusConnection used for exporting
685  * @object_path: (out): the object path used for exporting
686  *
687  * Queries if and where @action_group is exported.
688  *
689  * If @action_group is exported, %TRUE is returned.  If @connection is
690  * non-%NULL then it is set to the #GDBusConnection used for the export.
691  * If @object_path is non-%NULL then it is set to the object path.
692  *
693  * If the @action_group is not exported, %FALSE is returned and
694  * @connection and @object_path remain unmodified.
695  *
696  * Returns: %TRUE if @action_group was exported, else %FALSE
697  **/
698 gboolean
699 g_action_group_dbus_export_query (GActionGroup     *action_group,
700                                   GDBusConnection **connection,
701                                   const gchar     **object_path)
702 {
703   GActionGroupExporter *exporter;
704
705   if (exported_groups == NULL)
706     return FALSE;
707
708   exporter = g_hash_table_lookup (exported_groups, action_group);
709   if (exporter == NULL)
710     return FALSE;
711
712   if (connection)
713     *connection = exporter->connection;
714
715   if (object_path)
716     *object_path = exporter->object_path;
717
718   return TRUE;
719 }