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 "gdbusactiongroup.h"
31 #include "gactiongroup.h"
32 #include "gapplication.h"
33 #include "gdbuserror.h"
35 extern GDBusActionGroupEmitHookFunc g_dbus_action_group_before_emit_hook;
36 extern GDBusActionGroupEmitHookFunc g_dbus_action_group_after_emit_hook;
39 * SECTION:gactiongroupexporter
40 * @title: GActionGroup exporter
41 * @short_description: Export GActionGroups on D-Bus
42 * @see_also: #GActionGroup, #GDBusActionGroup
44 * These functions support exporting a #GActionGroup on D-Bus.
45 * The D-Bus interface that is used is a private implementation
48 * To access an exported #GActionGroup remotely, use
49 * g_dbus_action_group_new() to obtain a #GDBusActionGroup.
52 G_GNUC_INTERNAL GVariant *
53 g_action_group_describe_action (GActionGroup *action_group,
56 const GVariantType *type;
57 GVariantBuilder builder;
61 g_variant_builder_init (&builder, G_VARIANT_TYPE ("(bgav)"));
63 enabled = g_action_group_get_action_enabled (action_group, name);
64 g_variant_builder_add (&builder, "b", enabled);
66 if ((type = g_action_group_get_action_parameter_type (action_group, name)))
68 gchar *str = g_variant_type_dup_string (type);
69 g_variant_builder_add (&builder, "g", str);
73 g_variant_builder_add (&builder, "g", "");
75 g_variant_builder_open (&builder, G_VARIANT_TYPE ("av"));
76 if ((state = g_action_group_get_action_state (action_group, name)))
78 g_variant_builder_add (&builder, "v", state);
79 g_variant_unref (state);
81 g_variant_builder_close (&builder);
83 return g_variant_builder_end (&builder);
86 /* The org.gtk.Actions interface
87 * =============================
89 * This interface describes a group of actions.
92 * - has a unique string name
94 * - optionally has a parameter type that must be given to the activation
95 * - has an enabled state that may be true or false
96 * - optionally has a state which can change value, but not type
103 * Lists the names of the actions exported at this object path.
105 * Describe :: (s) → (bgav)
107 * Describes a single action, or a given name.
109 * The return value has the following components:
110 * b: specifies if the action is currently enabled. This is
111 * a hint that attempting to interact with the action will
113 * g: specifies the optional parameter type. If not "",
114 * the string specifies the type of argument that must
115 * be passed to the activation.
116 * av: specifies the optional state. If not empty, the array
117 * contains the current value of the state as a variant
119 * DescribeAll :: () → (a{s(bgav)})
121 * Describes all actions in a single round-trip.
123 * The dictionary maps action name strings to their descriptions
124 * (in the format discussed above).
126 * Activate :: (sava{sv}) → ()
128 * Requests activation of the named action.
130 * The action is named by the first parameter (s).
132 * If the action activation requires a parameter then this parameter
133 * must be given in the second parameter (av). If there is no parameter
134 * to be specified, the array must be empty.
136 * The final parameter (a{sv}) is a list of "platform data".
138 * This method is not guaranteed to have any particular effect. The
139 * implementation may take some action (including changing the state
140 * of the action, if it is stateful) or it may take none at all. In
141 * particular, callers should expect their request to be completely
142 * ignored when the enabled flag is false (but even this is not
145 * SetState :: (sva{sv}) → ()
147 * Requests the state of an action to be changed to the given value.
149 * The action is named by the first parameter (s).
151 * The requested new state is given in the second parameter (v).
152 * It must be equal in type to the existing state.
154 * The final parameter (a{sv}) is a list of "platform data".
156 * This method is not guaranteed to have any particular effect.
157 * The implementation of an action can choose to ignore the requested
158 * state change, or choose to change its state to something else or
159 * to trigger other side effects. In particular, callers should expect
160 * their request to be completely ignored when the enabled flag is
161 * false (but even this is not guaranteed).
166 * Changed :: (asa{sb}a{sv}a{s(bgav)})
168 * Signals that some change has occured to the action group.
170 * Four separate types of changes are possible, and the 4 parameters
171 * of the change signal reflect these possibilities:
172 * as: a list of removed actions
173 * a{sb}: a list of actions that had their enabled flag changed
174 * a{sv}: a list of actions that had their state changed
175 * a{s(bgav)}: a list of new actions added in the same format as
176 * the return value of the DescribeAll method
179 /* Using XML saves us dozens of relocations vs. using the introspection
180 * structure types. We only need to burn cycles and memory if we
181 * actually use the exporter -- not in every single app using GIO.
183 * It's also a lot easier to read. :)
185 const char org_gtk_Actions_xml[] =
187 " <interface name='org.gtk.Actions'>"
188 " <method name='List'>"
189 " <arg type='as' name='list' direction='out'/>"
191 " <method name='Describe'>"
192 " <arg type='s' name='action_name' direction='in'/>"
193 " <arg type='(bgav)' name='description' direction='out'/>"
195 " <method name='DescribeAll'>"
196 " <arg type='a{s(bgav)}' name='descriptions' direction='out'/>"
198 " <method name='Activate'>"
199 " <arg type='s' name='action_name' direction='in'/>"
200 " <arg type='av' name='parameter' direction='in'/>"
201 " <arg type='a{sv}' name='platform_data' direction='in'/>"
203 " <method name='SetState'>"
204 " <arg type='s' name='action_name' direction='in'/>"
205 " <arg type='v' name='value' direction='in'/>"
206 " <arg type='a{sv}' name='platform_data' direction='in'/>"
208 " <signal name='Changed'>"
209 " <arg type='as' name='removals'/>"
210 " <arg type='a{sb}' name='enable_changes'/>"
211 " <arg type='a{sv}' name='state_changes'/>"
212 " <arg type='a{s(bgav)}' name='additions'/>"
217 static GDBusInterfaceInfo *org_gtk_Actions;
221 GActionGroup *action_group;
222 GDBusConnection *connection;
223 GMainContext *context;
225 GHashTable *pending_changes;
226 GSource *pending_source;
227 } GActionGroupExporter;
229 #define ACTION_ADDED_EVENT (1u<<0)
230 #define ACTION_REMOVED_EVENT (1u<<1)
231 #define ACTION_STATE_CHANGED_EVENT (1u<<2)
232 #define ACTION_ENABLED_CHANGED_EVENT (1u<<3)
235 g_action_group_exporter_dispatch_events (gpointer user_data)
237 GActionGroupExporter *exporter = user_data;
238 GVariantBuilder removes;
239 GVariantBuilder enabled_changes;
240 GVariantBuilder state_changes;
241 GVariantBuilder adds;
246 g_variant_builder_init (&removes, G_VARIANT_TYPE_STRING_ARRAY);
247 g_variant_builder_init (&enabled_changes, G_VARIANT_TYPE ("a{sb}"));
248 g_variant_builder_init (&state_changes, G_VARIANT_TYPE ("a{sv}"));
249 g_variant_builder_init (&adds, G_VARIANT_TYPE ("a{s(bgav)}"));
251 g_hash_table_iter_init (&iter, exporter->pending_changes);
252 while (g_hash_table_iter_next (&iter, &key, &value))
254 guint events = GPOINTER_TO_INT (value);
255 const gchar *name = key;
257 /* Adds and removes are incompatible with enabled or state
258 * changes, but we must report at least one event.
260 g_assert (((events & (ACTION_ENABLED_CHANGED_EVENT | ACTION_STATE_CHANGED_EVENT)) == 0) !=
261 ((events & (ACTION_REMOVED_EVENT | ACTION_ADDED_EVENT)) == 0));
263 if (events & ACTION_REMOVED_EVENT)
264 g_variant_builder_add (&removes, "s", name);
266 if (events & ACTION_ENABLED_CHANGED_EVENT)
270 enabled = g_action_group_get_action_enabled (exporter->action_group, name);
271 g_variant_builder_add (&enabled_changes, "{sb}", name, enabled);
274 if (events & ACTION_STATE_CHANGED_EVENT)
278 state = g_action_group_get_action_state (exporter->action_group, name);
279 g_variant_builder_add (&state_changes, "{sv}", name, state);
280 g_variant_unref (state);
283 if (events & ACTION_ADDED_EVENT)
285 GVariant *description;
287 description = g_action_group_describe_action (exporter->action_group, name);
288 g_variant_builder_add (&adds, "{s@(bgav)}", name, description);
292 g_hash_table_remove_all (exporter->pending_changes);
294 g_dbus_connection_emit_signal (exporter->connection, NULL, exporter->object_path,
295 "org.gtk.Actions", "Changed",
296 g_variant_new ("(asa{sb}a{sv}a{s(bgav)})",
297 &removes, &enabled_changes,
298 &state_changes, &adds),
301 exporter->pending_source = NULL;
307 g_action_group_exporter_get_events (GActionGroupExporter *exporter,
310 return (gsize) g_hash_table_lookup (exporter->pending_changes, name);
314 g_action_group_exporter_set_events (GActionGroupExporter *exporter,
318 gboolean have_events;
322 g_hash_table_insert (exporter->pending_changes, g_strdup (name), GINT_TO_POINTER (events));
324 g_hash_table_remove (exporter->pending_changes, name);
326 have_events = g_hash_table_size (exporter->pending_changes) > 0;
327 is_queued = exporter->pending_source != NULL;
329 if (have_events && !is_queued)
333 source = g_idle_source_new ();
334 exporter->pending_source = source;
335 g_source_set_callback (source, g_action_group_exporter_dispatch_events, exporter, NULL);
336 g_source_attach (source, exporter->context);
337 g_source_unref (source);
340 if (!have_events && is_queued)
342 g_source_destroy (exporter->pending_source);
343 exporter->pending_source = NULL;
348 g_action_group_exporter_action_added (GActionGroup *action_group,
349 const gchar *action_name,
352 GActionGroupExporter *exporter = user_data;
355 event_mask = g_action_group_exporter_get_events (exporter, action_name);
357 /* The action is new, so we should not have any pending
358 * enabled-changed or state-changed signals for it.
360 g_assert (~event_mask & (ACTION_STATE_CHANGED_EVENT |
361 ACTION_ENABLED_CHANGED_EVENT));
363 event_mask |= ACTION_ADDED_EVENT;
365 g_action_group_exporter_set_events (exporter, action_name, event_mask);
369 g_action_group_exporter_action_removed (GActionGroup *action_group,
370 const gchar *action_name,
373 GActionGroupExporter *exporter = user_data;
376 event_mask = g_action_group_exporter_get_events (exporter, action_name);
378 /* If the add event for this is still queued then just cancel it since
381 * If the event was freshly added, there should not have been any
382 * enabled or state changed events.
384 if (event_mask & ACTION_ADDED_EVENT)
386 g_assert (~event_mask & ~(ACTION_STATE_CHANGED_EVENT | ACTION_ENABLED_CHANGED_EVENT));
387 event_mask &= ~ACTION_ADDED_EVENT;
390 /* Otherwise, queue a remove event. Drop any state or enabled changes
391 * that were queued before the remove. */
394 event_mask |= ACTION_REMOVED_EVENT;
395 event_mask &= ~(ACTION_STATE_CHANGED_EVENT | ACTION_ENABLED_CHANGED_EVENT);
398 g_action_group_exporter_set_events (exporter, action_name, event_mask);
402 g_action_group_exporter_action_state_changed (GActionGroup *action_group,
403 const gchar *action_name,
407 GActionGroupExporter *exporter = user_data;
410 event_mask = g_action_group_exporter_get_events (exporter, action_name);
412 /* If it was removed, it must have been added back. Otherwise, why
413 * are we hearing about changes?
415 g_assert (~event_mask & ACTION_REMOVED_EVENT ||
416 event_mask & ACTION_ADDED_EVENT);
418 /* If it is freshly added, don't also bother with the state change
419 * signal since the updated state will be sent as part of the pending
422 if (~event_mask & ACTION_ADDED_EVENT)
423 event_mask |= ACTION_STATE_CHANGED_EVENT;
425 g_action_group_exporter_set_events (exporter, action_name, event_mask);
429 g_action_group_exporter_action_enabled_changed (GActionGroup *action_group,
430 const gchar *action_name,
434 GActionGroupExporter *exporter = user_data;
437 event_mask = g_action_group_exporter_get_events (exporter, action_name);
439 /* Reasoning as above. */
440 g_assert (~event_mask & ACTION_REMOVED_EVENT ||
441 event_mask & ACTION_ADDED_EVENT);
443 if (~event_mask & ACTION_ADDED_EVENT)
444 event_mask |= ACTION_ENABLED_CHANGED_EVENT;
446 g_action_group_exporter_set_events (exporter, action_name, event_mask);
450 g_action_group_exporter_pre_emit (GActionGroupExporter *exporter,
451 GVariant *platform_data)
453 if (G_IS_APPLICATION (exporter->action_group))
454 G_APPLICATION_GET_CLASS (exporter->action_group)
455 ->before_emit (G_APPLICATION (exporter->action_group), platform_data);
457 else if (g_dbus_action_group_before_emit_hook != NULL)
458 (* g_dbus_action_group_before_emit_hook) (exporter->action_group, platform_data);
462 g_action_group_exporter_post_emit (GActionGroupExporter *exporter,
463 GVariant *platform_data)
465 if (G_IS_APPLICATION (exporter->action_group))
466 G_APPLICATION_GET_CLASS (exporter->action_group)
467 ->after_emit (G_APPLICATION (exporter->action_group), platform_data);
469 else if (g_dbus_action_group_after_emit_hook != NULL)
470 (* g_dbus_action_group_after_emit_hook) (exporter->action_group, platform_data);
474 org_gtk_Actions_method_call (GDBusConnection *connection,
476 const gchar *object_path,
477 const gchar *interface_name,
478 const gchar *method_name,
479 GVariant *parameters,
480 GDBusMethodInvocation *invocation,
483 GActionGroupExporter *exporter = user_data;
484 GVariant *result = NULL;
486 if (g_str_equal (method_name, "List"))
490 list = g_action_group_list_actions (exporter->action_group);
491 result = g_variant_new ("(^as)", list);
495 else if (g_str_equal (method_name, "Describe"))
500 g_variant_get (parameters, "(&s)", &name);
501 desc = g_action_group_describe_action (exporter->action_group, name);
502 result = g_variant_new ("(@(bgav))", desc);
505 else if (g_str_equal (method_name, "DescribeAll"))
507 GVariantBuilder builder;
511 list = g_action_group_list_actions (exporter->action_group);
512 g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{s(bgav)}"));
513 for (i = 0; list[i]; i++)
515 const gchar *name = list[i];
516 GVariant *description;
518 description = g_action_group_describe_action (exporter->action_group, name);
519 g_variant_builder_add (&builder, "{s@(bgav)}", name, description);
521 result = g_variant_new ("(a{s(bgav)})", &builder);
525 else if (g_str_equal (method_name, "Activate"))
527 GVariant *parameter = NULL;
528 GVariant *platform_data;
532 g_variant_get (parameters, "(&sav@a{sv})", &name, &iter, &platform_data);
533 g_variant_iter_next (iter, "v", ¶meter);
534 g_variant_iter_free (iter);
536 g_action_group_exporter_pre_emit (exporter, platform_data);
537 g_action_group_activate_action (exporter->action_group, name, parameter);
538 g_action_group_exporter_post_emit (exporter, platform_data);
541 g_variant_unref (parameter);
543 g_variant_unref (platform_data);
546 else if (g_str_equal (method_name, "SetState"))
548 GVariant *platform_data;
552 g_variant_get (parameters, "(&sv@a{sv})", &name, &state, &platform_data);
553 g_action_group_exporter_pre_emit (exporter, platform_data);
554 g_action_group_change_action_state (exporter->action_group, name, state);
555 g_action_group_exporter_post_emit (exporter, platform_data);
556 g_variant_unref (platform_data);
557 g_variant_unref (state);
561 g_assert_not_reached ();
563 g_dbus_method_invocation_return_value (invocation, result);
567 g_action_group_exporter_free (gpointer user_data)
569 GActionGroupExporter *exporter = user_data;
571 g_signal_handlers_disconnect_by_func (exporter->action_group,
572 g_action_group_exporter_action_added, exporter);
573 g_signal_handlers_disconnect_by_func (exporter->action_group,
574 g_action_group_exporter_action_enabled_changed, exporter);
575 g_signal_handlers_disconnect_by_func (exporter->action_group,
576 g_action_group_exporter_action_state_changed, exporter);
577 g_signal_handlers_disconnect_by_func (exporter->action_group,
578 g_action_group_exporter_action_removed, exporter);
580 g_hash_table_unref (exporter->pending_changes);
581 if (exporter->pending_source)
582 g_source_destroy (exporter->pending_source);
584 g_main_context_unref (exporter->context);
585 g_object_unref (exporter->connection);
586 g_object_unref (exporter->action_group);
587 g_free (exporter->object_path);
589 g_slice_free (GActionGroupExporter, exporter);
593 * g_dbus_connection_export_action_group:
594 * @connection: a #GDBusConnection
595 * @object_path: a D-Bus object path
596 * @action_group: a #GActionGroup
597 * @error: a pointer to a %NULL #GError, or %NULL
599 * Exports @action_group on @connection at @object_path.
601 * The implemented D-Bus API should be considered private. It is
602 * subject to change in the future.
604 * A given object path can only have one action group exported on it.
605 * If this constraint is violated, the export will fail and 0 will be
606 * returned (with @error set accordingly).
608 * You can unexport the action group using
609 * g_dbus_connection_unexport_action_group() with the return value of
612 * The thread default main context is taken at the time of this call.
613 * All incoming action activations and state change requests are
614 * reported from this context. Any changes on the action group that
615 * cause it to emit signals must also come from this same context.
616 * Since incoming action activations and state change requests are
617 * rather likely to cause changes on the action group, this effectively
618 * limits a given action group to being exported from only one main
621 * Returns: the ID of the export (never zero), or 0 in case of failure
626 g_dbus_connection_export_action_group (GDBusConnection *connection,
627 const gchar *object_path,
628 GActionGroup *action_group,
631 const GDBusInterfaceVTable vtable = {
632 org_gtk_Actions_method_call
634 GActionGroupExporter *exporter;
637 if G_UNLIKELY (org_gtk_Actions == NULL)
639 GError *error = NULL;
642 info = g_dbus_node_info_new_for_xml (org_gtk_Actions_xml, &error);
643 if G_UNLIKELY (info == NULL)
644 g_error ("%s", error->message);
645 org_gtk_Actions = g_dbus_node_info_lookup_interface (info, "org.gtk.Actions");
646 g_assert (org_gtk_Actions != NULL);
647 g_dbus_interface_info_ref (org_gtk_Actions);
648 g_dbus_node_info_unref (info);
651 exporter = g_slice_new (GActionGroupExporter);
652 id = g_dbus_connection_register_object (connection, object_path, org_gtk_Actions, &vtable,
653 exporter, g_action_group_exporter_free, error);
657 g_slice_free (GActionGroupExporter, exporter);
661 exporter->context = g_main_context_ref_thread_default ();
662 exporter->pending_changes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
663 exporter->pending_source = NULL;
664 exporter->action_group = g_object_ref (action_group);
665 exporter->connection = g_object_ref (connection);
666 exporter->object_path = g_strdup (object_path);
668 g_signal_connect (action_group, "action-added",
669 G_CALLBACK (g_action_group_exporter_action_added), exporter);
670 g_signal_connect (action_group, "action-removed",
671 G_CALLBACK (g_action_group_exporter_action_removed), exporter);
672 g_signal_connect (action_group, "action-state-changed",
673 G_CALLBACK (g_action_group_exporter_action_state_changed), exporter);
674 g_signal_connect (action_group, "action-enabled-changed",
675 G_CALLBACK (g_action_group_exporter_action_enabled_changed), exporter);
681 * g_dbus_connection_unexport_action_group:
682 * @connection: a #GDBusConnection
683 * @export_id: the ID from g_dbus_connection_export_action_group()
685 * Reverses the effect of a previous call to
686 * g_dbus_connection_export_action_group().
688 * It is an error to call this function with an ID that wasn't returned
689 * from g_dbus_connection_export_action_group() or to call it with the
690 * same ID more than once.
695 g_dbus_connection_unexport_action_group (GDBusConnection *connection,
698 g_dbus_connection_unregister_object (connection, export_id);