2 * Copyright © 2010 Codethink Limited
3 * Copyright © 2011 Canonical Limited
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.
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.
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.
20 * Authors: Ryan Lortie <desrt@desrt.ca>
25 #include "gactiongroupexporter.h"
27 #include "gdbusmethodinvocation.h"
28 #include "gdbusintrospection.h"
29 #include "gdbusconnection.h"
30 #include "gactiongroup.h"
31 #include "gapplication.h"
32 #include "gdbuserror.h"
35 * SECTION:gactiongroupexporter
36 * @title: GActionGroup exporter
37 * @short_description: Export GActionGroups on D-Bus
38 * @see_also: #GActionGroup, #GDBusActionGroup
40 * These functions support exporting a #GActionGroup on D-Bus.
41 * The D-Bus interface that is used is a private implementation
44 * To access an exported #GActionGroup remotely, use
45 * g_dbus_action_group_new() to obtain a #GDBusActionGroup.
48 G_GNUC_INTERNAL GVariant *
49 g_action_group_describe_action (GActionGroup *action_group,
52 const GVariantType *type;
53 GVariantBuilder builder;
57 g_variant_builder_init (&builder, G_VARIANT_TYPE ("(bgav)"));
59 enabled = g_action_group_get_action_enabled (action_group, name);
60 g_variant_builder_add (&builder, "b", enabled);
62 if ((type = g_action_group_get_action_parameter_type (action_group, name)))
64 gchar *str = g_variant_type_dup_string (type);
65 g_variant_builder_add (&builder, "g", str);
69 g_variant_builder_add (&builder, "g", "");
71 g_variant_builder_open (&builder, G_VARIANT_TYPE ("av"));
72 if ((state = g_action_group_get_action_state (action_group, name)))
74 g_variant_builder_add (&builder, "v", state);
75 g_variant_unref (state);
77 g_variant_builder_close (&builder);
79 return g_variant_builder_end (&builder);
82 /* The org.gtk.Actions interface
83 * =============================
85 * This interface describes a group of actions.
88 * - has a unique string name
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
99 * Lists the names of the actions exported at this object path.
101 * Describe :: (s) → (bgav)
103 * Describes a single action, or a given name.
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
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
115 * DescribeAll :: () → (a{s(bgav)})
117 * Describes all actions in a single round-trip.
119 * The dictionary maps action name strings to their descriptions
120 * (in the format discussed above).
122 * Activate :: (sava{sv}) → ()
124 * Requests activation of the named action.
126 * The action is named by the first parameter (s).
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.
132 * The final parameter (a{sv}) is a list of "platform data".
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
141 * SetState :: (sva{sv}) → ()
143 * Requests the state of an action to be changed to the given value.
145 * The action is named by the first parameter (s).
147 * The requested new state is given in the second parameter (v).
148 * It must be equal in type to the existing state.
150 * The final parameter (a{sv}) is a list of "platform data".
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).
162 * Changed :: (asa{sb}a{sv}a{s(bgav)})
164 * Signals that some change has occured to the action group.
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
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.
179 * It's also a lot easier to read. :)
181 const char org_gtk_Actions_xml[] =
183 " <interface name='org.gtk.Actions'>"
184 " <method name='List'>"
185 " <arg type='as' name='list' direction='out'/>"
187 " <method name='Describe'>"
188 " <arg type='s' name='action_name' direction='in'/>"
189 " <arg type='(bgav)' name='description' direction='out'/>"
191 " <method name='DescribeAll'>"
192 " <arg type='a{s(bgav)}' name='descriptions' direction='out'/>"
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'/>"
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'/>"
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'/>"
213 static GDBusInterfaceInfo *org_gtk_Actions;
214 static GHashTable *exported_groups;
218 GActionGroup *action_group;
219 GDBusConnection *connection;
221 guint registration_id;
222 GHashTable *pending_changes;
224 gulong signal_ids[4];
225 } GActionGroupExporter;
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)
233 g_action_group_exporter_dispatch_events (gpointer user_data)
235 GActionGroupExporter *exporter = user_data;
236 GVariantBuilder removes;
237 GVariantBuilder enabled_changes;
238 GVariantBuilder state_changes;
239 GVariantBuilder adds;
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)}"));
249 g_hash_table_iter_init (&iter, exporter->pending_changes);
250 while (g_hash_table_iter_next (&iter, &key, &value))
252 guint events = GPOINTER_TO_INT (value);
253 const gchar *name = key;
255 /* Adds and removes are incompatible with enabled or state
256 * changes, but we must report at least one event.
258 g_assert (((events & (ACTION_ENABLED_CHANGED_EVENT | ACTION_STATE_CHANGED_EVENT)) == 0) !=
259 ((events & (ACTION_REMOVED_EVENT | ACTION_ADDED_EVENT)) == 0));
261 if (events & ACTION_REMOVED_EVENT)
262 g_variant_builder_add (&removes, "s", name);
264 if (events & ACTION_ENABLED_CHANGED_EVENT)
268 enabled = g_action_group_get_action_enabled (exporter->action_group, name);
269 g_variant_builder_add (&enabled_changes, "{sb}", name, enabled);
272 if (events & ACTION_STATE_CHANGED_EVENT)
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);
281 if (events & ACTION_ADDED_EVENT)
283 GVariant *description;
285 description = g_action_group_describe_action (exporter->action_group, name);
286 g_variant_builder_add (&adds, "{s@(bgav)}", name, description);
290 g_hash_table_remove_all (exporter->pending_changes);
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),
299 exporter->pending_id = 0;
305 g_action_group_exporter_get_events (GActionGroupExporter *exporter,
308 return (gsize) g_hash_table_lookup (exporter->pending_changes, name);
312 g_action_group_exporter_set_events (GActionGroupExporter *exporter,
316 gboolean have_events;
320 g_hash_table_insert (exporter->pending_changes, g_strdup (name), GINT_TO_POINTER (events));
322 g_hash_table_remove (exporter->pending_changes, name);
324 have_events = g_hash_table_size (exporter->pending_changes) > 0;
325 is_queued = exporter->pending_id > 0;
327 if (have_events && !is_queued)
328 exporter->pending_id = g_idle_add (g_action_group_exporter_dispatch_events, exporter);
330 if (!have_events && is_queued)
332 g_source_remove (exporter->pending_id);
333 exporter->pending_id = 0;
338 g_action_group_exporter_action_added (GActionGroup *action_group,
339 const gchar *action_name,
342 GActionGroupExporter *exporter = user_data;
345 event_mask = g_action_group_exporter_get_events (exporter, action_name);
347 /* The action is new, so we should not have any pending
348 * enabled-changed or state-changed signals for it.
350 g_assert (~event_mask & (ACTION_STATE_CHANGED_EVENT |
351 ACTION_ENABLED_CHANGED_EVENT));
353 event_mask |= ACTION_ADDED_EVENT;
355 g_action_group_exporter_set_events (exporter, action_name, event_mask);
359 g_action_group_exporter_action_removed (GActionGroup *action_group,
360 const gchar *action_name,
363 GActionGroupExporter *exporter = user_data;
366 event_mask = g_action_group_exporter_get_events (exporter, action_name);
368 /* If the add event for this is still queued then just cancel it since
371 * If the event was freshly added, there should not have been any
372 * enabled or state changed events.
374 if (event_mask & ACTION_ADDED_EVENT)
376 g_assert (~event_mask & ~(ACTION_STATE_CHANGED_EVENT | ACTION_ENABLED_CHANGED_EVENT));
377 event_mask &= ~ACTION_ADDED_EVENT;
380 /* Otherwise, queue a remove event. Drop any state or enabled changes
381 * that were queued before the remove. */
384 event_mask |= ACTION_REMOVED_EVENT;
385 event_mask &= ~(ACTION_STATE_CHANGED_EVENT | ACTION_ENABLED_CHANGED_EVENT);
388 g_action_group_exporter_set_events (exporter, action_name, event_mask);
392 g_action_group_exporter_action_state_changed (GActionGroup *action_group,
393 const gchar *action_name,
397 GActionGroupExporter *exporter = user_data;
400 event_mask = g_action_group_exporter_get_events (exporter, action_name);
402 /* If it was removed, it must have been added back. Otherwise, why
403 * are we hearing about changes?
405 g_assert (~event_mask & ACTION_REMOVED_EVENT ||
406 event_mask & ACTION_ADDED_EVENT);
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
412 if (~event_mask & ACTION_ADDED_EVENT)
413 event_mask |= ACTION_STATE_CHANGED_EVENT;
415 g_action_group_exporter_set_events (exporter, action_name, event_mask);
419 g_action_group_exporter_action_enabled_changed (GActionGroup *action_group,
420 const gchar *action_name,
424 GActionGroupExporter *exporter = user_data;
427 event_mask = g_action_group_exporter_get_events (exporter, action_name);
429 /* Reasoning as above. */
430 g_assert (~event_mask & ACTION_REMOVED_EVENT ||
431 event_mask & ACTION_ADDED_EVENT);
433 if (~event_mask & ACTION_ADDED_EVENT)
434 event_mask |= ACTION_ENABLED_CHANGED_EVENT;
436 g_action_group_exporter_set_events (exporter, action_name, event_mask);
440 g_action_group_exporter_pre_emit (GActionGroupExporter *exporter,
441 GVariant *platform_data)
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);
449 g_action_group_exporter_post_emit (GActionGroupExporter *exporter,
450 GVariant *platform_data)
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);
458 org_gtk_Actions_method_call (GDBusConnection *connection,
460 const gchar *object_path,
461 const gchar *interface_name,
462 const gchar *method_name,
463 GVariant *parameters,
464 GDBusMethodInvocation *invocation,
467 GActionGroupExporter *exporter = user_data;
468 GVariant *result = NULL;
470 if (g_str_equal (method_name, "List"))
474 list = g_action_group_list_actions (exporter->action_group);
475 result = g_variant_new ("(^as)", list);
479 else if (g_str_equal (method_name, "Describe"))
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);
489 else if (g_str_equal (method_name, "DescribeAll"))
491 GVariantBuilder builder;
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++)
499 const gchar *name = list[i];
500 GVariant *description;
502 description = g_action_group_describe_action (exporter->action_group, name);
503 g_variant_builder_add (&builder, "{s@(bgav)}", name, description);
505 result = g_variant_new ("(a{s(bgav)})", &builder);
509 else if (g_str_equal (method_name, "Activate"))
511 GVariant *parameter = NULL;
512 GVariant *platform_data;
516 g_variant_get (parameters, "(&sav@a{sv})", &name, &iter, &platform_data);
517 g_variant_iter_next (iter, "v", ¶meter);
518 g_variant_iter_free (iter);
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);
525 g_variant_unref (parameter);
527 g_variant_unref (platform_data);
530 else if (g_str_equal (method_name, "SetState"))
532 GVariant *platform_data;
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);
545 g_assert_not_reached ();
547 g_dbus_method_invocation_return_value (invocation, result);
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
557 * Exports @action_group on @connection at @object_path.
559 * The implemented D-Bus API should be considered private. It is
560 * subject to change in the future.
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).
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.
571 * Returns: %TRUE if the export is successful, or %FALSE (with @error
572 * set) in the event of a failure.
575 g_action_group_dbus_export_start (GDBusConnection *connection,
576 const gchar *object_path,
577 GActionGroup *action_group,
580 const GDBusInterfaceVTable vtable = {
581 org_gtk_Actions_method_call
583 GActionGroupExporter *exporter;
585 if G_UNLIKELY (exported_groups == NULL)
586 exported_groups = g_hash_table_new (NULL, NULL);
588 if G_UNLIKELY (org_gtk_Actions == NULL)
590 GError *error = NULL;
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);
602 if G_UNLIKELY (g_hash_table_lookup (exported_groups, action_group))
604 g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FILE_EXISTS,
605 "The given GActionGroup has already been exported");
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);
613 if (exporter->registration_id == 0)
615 g_slice_free (GActionGroupExporter, exporter);
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);
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);
643 * g_action_group_dbus_export_stop:
644 * @action_group: a #GActionGroup
646 * Stops the export of @action_group.
648 * This reverses the effect of a previous call to
649 * g_action_group_dbus_export_start() for @action_group.
651 * Returns: %TRUE if an export was stopped or %FALSE if @action_group
652 * was not exported in the first place
655 g_action_group_dbus_export_stop (GActionGroup *action_group)
657 GActionGroupExporter *exporter;
660 if G_UNLIKELY (exported_groups == NULL)
663 exporter = g_hash_table_lookup (exported_groups, action_group);
664 if G_UNLIKELY (exporter == NULL)
667 g_dbus_connection_unregister_object (exporter->connection, exporter->registration_id);
668 for (i = 0; i < G_N_ELEMENTS (exporter->signal_ids); i++)
669 g_signal_handler_disconnect (exporter->action_group, exporter->signal_ids[i]);
670 g_object_unref (exporter->connection);
671 g_object_unref (exporter->action_group);
672 g_free (exporter->object_path);
674 g_slice_free (GActionGroupExporter, exporter);
680 * g_action_group_dbus_export_query:
681 * @action_group: a #GActionGroup
682 * @connection: (out): the #GDBusConnection used for exporting
683 * @object_path: (out): the object path used for exporting
685 * Queries if and where @action_group is exported.
687 * If @action_group is exported, %TRUE is returned. If @connection is
688 * non-%NULL then it is set to the #GDBusConnection used for the export.
689 * If @object_path is non-%NULL then it is set to the object path.
691 * If the @action_group is not exported, %FALSE is returned and
692 * @connection and @object_path remain unmodified.
694 * Returns: %TRUE if @action_group was exported, else %FALSE
697 g_action_group_dbus_export_query (GActionGroup *action_group,
698 GDBusConnection **connection,
699 const gchar **object_path)
701 GActionGroupExporter *exporter;
703 if (exported_groups == NULL)
706 exporter = g_hash_table_lookup (exported_groups, action_group);
707 if (exporter == NULL)
711 *connection = exporter->connection;
714 *object_path = exporter->object_path;