GDBusActionGroup: drop ability to 'inject'
[platform/upstream/glib.git] / gio / gdbusactiongroup.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 "gsimpleasyncresult.h"
26 #include "gdbusactiongroup.h"
27 #include "gdbusconnection.h"
28 #include "gasyncinitable.h"
29 #include "gactiongroup.h"
30
31
32 /**
33  * SECTION:gdbusactiongroup
34  * @title: GDBusActionGroup
35  * @short_description: A D-Bus GActionGroup implementation
36  * @see_also: <link linkend="gio-GActionGroup-exporter">GActionGroup exporter</link>
37  *
38  * #GDBusActionGroup is an implementation of the #GActionGroup
39  * interface that can be used as a proxy for an action group
40  * that is exported over D-Bus with g_dbus_connection_export_action_group().
41  */
42
43 struct _GDBusActionGroup
44 {
45   GObject parent_instance;
46
47   GDBusConnection *connection;
48   gchar           *bus_name;
49   gchar           *object_path;
50   guint            subscription_id;
51   GHashTable      *actions;
52
53   /* The 'strict' flag indicates that the non-existence of at least one
54    * action has potentially been observed through the API.  This means
55    * that we should always emit 'action-added' signals for all new
56    * actions.
57    *
58    * The user can observe the non-existence of an action by listing the
59    * actions or by performing a query (such as parameter type) on a
60    * non-existent action.
61    *
62    * If the user has no way of knowing that a given action didn't
63    * already exist then we can skip emitting 'action-added' signals
64    * since they have no way of knowing that it wasn't there from the
65    * start.
66    */
67   gboolean         strict;
68 };
69
70 typedef GObjectClass GDBusActionGroupClass;
71
72 typedef struct
73 {
74   gchar        *name;
75   GVariantType *parameter_type;
76   gboolean      enabled;
77   GVariant     *state;
78 } ActionInfo;
79
80 static void
81 action_info_free (gpointer user_data)
82 {
83   ActionInfo *info = user_data;
84
85   g_free (info->name);
86
87   if (info->state)
88     g_variant_unref (info->state);
89
90   if (info->parameter_type)
91     g_variant_type_free (info->parameter_type);
92
93   g_slice_free (ActionInfo, info);
94 }
95
96 ActionInfo *
97 action_info_new_from_iter (GVariantIter *iter)
98 {
99   const gchar *param_str;
100   ActionInfo *info;
101   gboolean enabled;
102   GVariant *state;
103   gchar *name;
104
105   if (!g_variant_iter_next (iter, "{s(bg@av)}", &name,
106                             &enabled, &param_str, &state))
107     return NULL;
108
109   info = g_slice_new (ActionInfo);
110   info->name = name;
111   info->enabled = enabled;
112
113   if (g_variant_n_children (state))
114     g_variant_get_child (state, 0, "v", &info->state);
115   else
116     info->state = NULL;
117   g_variant_unref (state);
118
119   if (param_str[0])
120     info->parameter_type = g_variant_type_copy ((GVariantType *) param_str);
121   else
122     info->parameter_type = NULL;
123
124   return info;
125 }
126
127 static void g_dbus_action_group_iface_init (GActionGroupInterface *);
128 G_DEFINE_TYPE_WITH_CODE (GDBusActionGroup, g_dbus_action_group, G_TYPE_OBJECT,
129   G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, g_dbus_action_group_iface_init))
130
131 static gchar **
132 g_dbus_action_group_list_actions (GActionGroup *g_group)
133 {
134   GDBusActionGroup *group = G_DBUS_ACTION_GROUP (g_group);
135   GHashTableIter iter;
136   gint n, i = 0;
137   gchar **keys;
138   gpointer key;
139
140   n = g_hash_table_size (group->actions);
141   keys = g_new (gchar *, n + 1);
142
143   g_hash_table_iter_init (&iter, group->actions);
144   while (g_hash_table_iter_next (&iter, &key, NULL))
145     keys[i++] = g_strdup (key);
146   g_assert_cmpint (i, ==, n);
147   keys[n] = NULL;
148
149   group->strict = TRUE;
150
151   return keys;
152 }
153
154 static gboolean
155 g_dbus_action_group_query_action (GActionGroup        *g_group,
156                                   const gchar         *action_name,
157                                   gboolean            *enabled,
158                                   const GVariantType **parameter_type,
159                                   const GVariantType **state_type,
160                                   GVariant           **state_hint,
161                                   GVariant           **state)
162 {
163   GDBusActionGroup *group = G_DBUS_ACTION_GROUP (g_group);
164   ActionInfo *info;
165
166   info = g_hash_table_lookup (group->actions, action_name);
167
168   if (info == NULL)
169     {
170       group->strict = TRUE;
171       return FALSE;
172     }
173
174   if (enabled)
175     *enabled = info->enabled;
176
177   if (parameter_type)
178     *parameter_type = info->parameter_type;
179
180   if (state_type)
181     *state_type = info->state ? g_variant_get_type (info->state) : NULL;
182
183   if (state_hint)
184     *state_hint = NULL;
185
186   if (state)
187     *state = info->state ? g_variant_ref (info->state) : NULL;
188
189   return TRUE;
190 }
191
192 static void
193 g_dbus_action_group_change_state (GActionGroup *g_group,
194                                   const gchar  *action_name,
195                                   GVariant     *value)
196 {
197   GDBusActionGroup *group = G_DBUS_ACTION_GROUP (g_group);
198
199   /* Don't bother with the checks.  The other side will do it again. */
200   g_dbus_connection_call (group->connection, group->bus_name, group->object_path, "org.gtk.Actions", "SetState",
201                           g_variant_new ("(sva{sv})", action_name, value, NULL),
202                           NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
203 }
204
205 static void
206 g_dbus_action_group_activate (GActionGroup *g_group,
207                               const gchar  *action_name,
208                               GVariant     *parameter)
209 {
210   GDBusActionGroup *group = G_DBUS_ACTION_GROUP (g_group);
211   GVariantBuilder builder;
212
213   g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
214
215   if (parameter)
216     g_variant_builder_add (&builder, "v", parameter);
217
218   g_dbus_connection_call (group->connection, group->bus_name, group->object_path, "org.gtk.Actions", "Activate",
219                           g_variant_new ("(sava{sv})", action_name, &builder, NULL),
220                           NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
221 }
222
223 static void
224 g_dbus_action_group_finalize (GObject *object)
225 {
226   GDBusActionGroup *group = G_DBUS_ACTION_GROUP (object);
227
228   g_dbus_connection_signal_unsubscribe (group->connection, group->subscription_id);
229   g_hash_table_unref (group->actions);
230   g_object_unref (group->connection);
231   g_free (group->object_path);
232   g_free (group->bus_name);
233
234   G_OBJECT_CLASS (g_dbus_action_group_parent_class)
235     ->finalize (object);
236 }
237
238 static void
239 g_dbus_action_group_init (GDBusActionGroup *group)
240 {
241   group->actions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, action_info_free);
242 }
243
244 static void
245 g_dbus_action_group_class_init (GDBusActionGroupClass *class)
246 {
247   GObjectClass *object_class = G_OBJECT_CLASS (class);
248
249   object_class->finalize = g_dbus_action_group_finalize;
250 }
251
252 static void
253 g_dbus_action_group_iface_init (GActionGroupInterface *iface)
254 {
255   iface->list_actions = g_dbus_action_group_list_actions;
256   iface->query_action = g_dbus_action_group_query_action;
257   iface->change_action_state = g_dbus_action_group_change_state;
258   iface->activate_action = g_dbus_action_group_activate;
259 }
260
261 static void
262 g_dbus_action_group_changed (GDBusConnection *connection,
263                              const gchar     *sender,
264                              const gchar     *object_path,
265                              const gchar     *interface_name,
266                              const gchar     *signal_name,
267                              GVariant        *parameters,
268                              gpointer         user_data)
269 {
270   GDBusActionGroup *group = user_data;
271   GActionGroup *g_group = user_data;
272
273   if (g_str_equal (signal_name, "Changed") &&
274       g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(asa{sb}a{sv}a{s(bgav)})")))
275     {
276       /* Removes */
277       {
278         GVariantIter *iter;
279         const gchar *name;
280
281         g_variant_get_child (parameters, 0, "as", &iter);
282         while (g_variant_iter_next (iter, "&s", &name))
283           {
284             if (g_hash_table_lookup (group->actions, name))
285               {
286                 g_hash_table_remove (group->actions, name);
287                 g_action_group_action_removed (g_group, name);
288               }
289           }
290         g_variant_iter_free (iter);
291       }
292
293       /* Enable changes */
294       {
295         GVariantIter *iter;
296         const gchar *name;
297         gboolean enabled;
298
299         g_variant_get_child (parameters, 1, "a{sb}", &iter);
300         while (g_variant_iter_next (iter, "{&sb}", &name, &enabled))
301           {
302             ActionInfo *info;
303
304             info = g_hash_table_lookup (group->actions, name);
305
306             if (info && info->enabled != enabled)
307               {
308                 info->enabled = enabled;
309                 g_action_group_action_enabled_changed (g_group, name, enabled);
310               }
311           }
312         g_variant_iter_free (iter);
313       }
314
315       /* State changes */
316       {
317         GVariantIter *iter;
318         const gchar *name;
319         GVariant *state;
320
321         g_variant_get_child (parameters, 2, "a{sv}", &iter);
322         while (g_variant_iter_next (iter, "{&sv}", &name, &state))
323           {
324             ActionInfo *info;
325
326             info = g_hash_table_lookup (group->actions, name);
327
328             if (info && info->state && !g_variant_equal (state, info->state) &&
329                 g_variant_is_of_type (state, g_variant_get_type (info->state)))
330               {
331                 g_variant_unref (info->state);
332                 info->state = g_variant_ref (state);
333
334                 g_action_group_action_state_changed (g_group, name, state);
335               }
336
337             g_variant_unref (state);
338           }
339         g_variant_iter_free (iter);
340       }
341
342       /* Additions */
343       {
344         GVariantIter *iter;
345         ActionInfo *info;
346
347         g_variant_get_child (parameters, 3, "a{s(bgav)}", &iter);
348         while ((info = action_info_new_from_iter (iter)))
349           {
350             if (!g_hash_table_lookup (group->actions, info->name))
351               {
352                 g_hash_table_insert (group->actions, info->name, info);
353
354                 if (group->strict)
355                   g_action_group_action_added (g_group, info->name);
356               }
357             else
358               action_info_free (info);
359           }
360       }
361     }
362 }
363
364 static void
365 g_dbus_action_group_describe_all_done (GObject      *source,
366                                        GAsyncResult *result,
367                                        gpointer      user_data)
368 {
369   GSimpleAsyncResult *my_result = user_data;
370   GDBusActionGroup *group;
371   GError *error = NULL;
372   GVariant *reply;
373
374   group = g_simple_async_result_get_op_res_gpointer (my_result);
375   g_assert (G_IS_DBUS_ACTION_GROUP (group));
376   g_assert (group->connection == (gpointer) source);
377   reply = g_dbus_connection_call_finish (group->connection, result, &error);
378
379   if (reply != NULL)
380     {
381       GVariantIter *iter;
382       ActionInfo *action;
383
384       g_variant_get (reply, "(a{s(bgav)})", &iter);
385       while ((action = action_info_new_from_iter (iter)))
386         g_hash_table_insert (group->actions, action->name, action);
387       g_variant_iter_free (iter);
388       g_variant_unref (reply);
389     }
390   else
391     {
392       g_simple_async_result_set_from_error (my_result, error);
393       g_error_free (error);
394     }
395
396   g_simple_async_result_complete (my_result);
397   g_object_unref (my_result);
398 }
399
400 /**
401  * g_dbus_action_group_new:
402  * @connection: A #GDBusConnection
403  * @bus_name: the bus name which exports the action group
404  * @object_path: the object path at which the action group is exported
405  * @cancellable: A #GCancellable or %NULL
406  * @callback: Callback function to invoke when the object is ready
407  * @user_data: User data to pass to @callback
408  *
409  * Creates a new, empty, #GDBusActionGroup.
410  *
411  * This is a failable asynchronous constructor - when the object
412  * is ready, @callback will be invoked and you can use
413  * g_dbus_action_group_new_finish() to get the result.
414  *
415  * See g_dbus_action_group_new_sync() and for a synchronous version
416  * of this constructor.
417  */
418 void
419 g_dbus_action_group_new (GDBusConnection       *connection,
420                          const gchar           *bus_name,
421                          const gchar           *object_path,
422                          GCancellable          *cancellable,
423                          GAsyncReadyCallback    callback,
424                          gpointer               user_data)
425 {
426   GSimpleAsyncResult *result;
427   GDBusActionGroup *group;
428
429   group = g_object_new (G_TYPE_DBUS_ACTION_GROUP, NULL);
430   group->connection = g_object_ref (connection);
431   group->bus_name = g_strdup (bus_name);
432   group->object_path = g_strdup (object_path);
433
434   /* It's probably excessive to worry about watching the name ownership.
435    * The person using this class will know for themselves when the name
436    * disappears.
437    */
438
439   result = g_simple_async_result_new (G_OBJECT (group), callback, user_data, g_dbus_action_group_new);
440   g_simple_async_result_set_op_res_gpointer (result, group, g_object_unref);
441
442   group->subscription_id =
443     g_dbus_connection_signal_subscribe (connection, bus_name, "org.gtk.Actions", "Changed", object_path, NULL,
444                                         G_DBUS_SIGNAL_FLAGS_NONE, g_dbus_action_group_changed, group, NULL);
445
446   g_dbus_connection_call (connection, bus_name, object_path, "org.gtk.Actions", "DescribeAll", NULL,
447                           G_VARIANT_TYPE ("(a{s(bgav)})"), G_DBUS_CALL_FLAGS_NONE, -1, cancellable,
448                           g_dbus_action_group_describe_all_done, result);
449 }
450
451 /**
452  * g_dbus_action_group_new_finish:
453  * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback
454  *     function passed to g_dbus_action_group_new()
455  * @error: Return location for error or %NULL
456  *
457  * Finishes creating a #GDBusActionGroup.
458  *
459  * Returns: A #GDBusProxy or %NULL if @error is set. Free with g_object_unref().
460  */
461 GDBusActionGroup *
462 g_dbus_action_group_new_finish (GAsyncResult  *result,
463                                 GError       **error)
464 {
465   GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
466
467   g_return_val_if_fail (g_simple_async_result_is_valid (result,
468                                                         g_simple_async_result_get_op_res_gpointer (simple),
469                                                         g_dbus_action_group_new), NULL);
470
471   if (g_simple_async_result_propagate_error (simple, error))
472     return NULL;
473
474   return g_object_ref (g_simple_async_result_get_op_res_gpointer (simple));
475 }
476
477 /**
478  * g_dbus_action_group_new_sync:
479  * @connection: A #GDBusConnection
480  * @bus_name: the bus name which exports the action group
481  * @object_path: the object path at which the action group is exported
482  * @cancellable: A #GCancellable or %NULL
483  * @error: Return location for error or %NULL
484  *
485  * This is a synchronous failable constructor. See g_dbus_action_group_new()
486  * and g_dbus_action_group_new_finish() for the asynchronous version.
487  *
488  * Returns: A #GDBusProxy or %NULL if @error is set. Free with g_object_unref().
489  */
490 GDBusActionGroup *
491 g_dbus_action_group_new_sync (GDBusConnection        *connection,
492                               const gchar            *bus_name,
493                               const gchar            *object_path,
494                               GCancellable           *cancellable,
495                               GError                **error)
496 {
497   GDBusActionGroup *group;
498   GVariant *reply;
499
500   group = g_object_new (G_TYPE_DBUS_ACTION_GROUP, NULL);
501   group->connection = g_object_ref (connection);
502   group->bus_name = g_strdup (bus_name);
503   group->object_path = g_strdup (object_path);
504
505   group->subscription_id =
506     g_dbus_connection_signal_subscribe (connection, bus_name, "org.gtk.Actions", "Changed", object_path, NULL,
507                                           G_DBUS_SIGNAL_FLAGS_NONE, g_dbus_action_group_changed, group, NULL);
508
509   reply = g_dbus_connection_call_sync (connection, bus_name, object_path, "org.gtk.Actions",
510                                        "DescribeAll", NULL, G_VARIANT_TYPE ("(a{s(bgav)})"),
511                                        G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error);
512
513   if (reply != NULL)
514     {
515       GVariantIter *iter;
516       ActionInfo *action;
517
518       g_variant_get (reply, "(a{s(bgav)})", &iter);
519       while ((action = action_info_new_from_iter (iter)))
520         g_hash_table_insert (group->actions, action->name, action);
521       g_variant_iter_free (iter);
522       g_variant_unref (reply);
523     }
524   else
525     {
526       g_object_unref (group);
527       return NULL;
528     }
529
530   return group;
531 }