4aba34f60d22209eb84a35687ca6810e2cbe7869
[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  * @flags: Flags used when constructing the object
406  * @cancellable: A #GCancellable or %NULL
407  * @callback: Callback function to invoke when the object is ready
408  * @user_data: User data to pass to @callback
409  *
410  * Creates a new, empty, #GDBusActionGroup.
411  *
412  * This is a failable asynchronous constructor - when the object
413  * is ready, @callback will be invoked and you can use
414  * g_dbus_action_group_new_finish() to get the result.
415  *
416  * See g_dbus_action_group_new_sync() and for a synchronous version
417  * of this constructor.
418  */
419 void
420 g_dbus_action_group_new (GDBusConnection       *connection,
421                          const gchar           *bus_name,
422                          const gchar           *object_path,
423                          GDBusActionGroupFlags  flags,
424                          GCancellable          *cancellable,
425                          GAsyncReadyCallback    callback,
426                          gpointer               user_data)
427 {
428   GSimpleAsyncResult *result;
429   GDBusActionGroup *group;
430
431   group = g_object_new (G_TYPE_DBUS_ACTION_GROUP, NULL);
432   group->connection = g_object_ref (connection);
433   group->bus_name = g_strdup (bus_name);
434   group->object_path = g_strdup (object_path);
435
436   /* It's probably excessive to worry about watching the name ownership.
437    * The person using this class will know for themselves when the name
438    * disappears.
439    */
440
441   result = g_simple_async_result_new (G_OBJECT (group), callback, user_data, g_dbus_action_group_new);
442   g_simple_async_result_set_op_res_gpointer (result, group, g_object_unref);
443
444   if (~flags & G_DBUS_ACTION_GROUP_FLAGS_DO_NOT_WATCH)
445     group->subscription_id =
446       g_dbus_connection_signal_subscribe (connection, bus_name, "org.gtk.Actions", "Changed", object_path, NULL,
447                                           G_DBUS_SIGNAL_FLAGS_NONE, g_dbus_action_group_changed, group, NULL);
448
449   if (~flags & G_DBUS_ACTION_GROUP_FLAGS_DO_NOT_POPULATE)
450     g_dbus_connection_call (connection, bus_name, object_path, "org.gtk.Actions", "DescribeAll", NULL,
451                             G_VARIANT_TYPE ("(a{s(bgav)})"), G_DBUS_CALL_FLAGS_NONE, -1, cancellable,
452                             g_dbus_action_group_describe_all_done, result);
453
454   else
455     g_simple_async_result_complete_in_idle (result);
456 }
457
458 /**
459  * g_dbus_action_group_new_finish:
460  * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback
461  *     function passed to g_dbus_action_group_new()
462  * @error: Return location for error or %NULL
463  *
464  * Finishes creating a #GDBusActionGroup.
465  *
466  * Returns: A #GDBusProxy or %NULL if @error is set. Free with g_object_unref().
467  */
468 GDBusActionGroup *
469 g_dbus_action_group_new_finish (GAsyncResult  *result,
470                                 GError       **error)
471 {
472   GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
473
474   g_return_val_if_fail (g_simple_async_result_is_valid (result,
475                                                         g_simple_async_result_get_op_res_gpointer (simple),
476                                                         g_dbus_action_group_new), NULL);
477
478   if (g_simple_async_result_propagate_error (simple, error))
479     return NULL;
480
481   return g_object_ref (g_simple_async_result_get_op_res_gpointer (simple));
482 }
483
484 /**
485  * g_dbus_action_group_new_sync:
486  * @connection: A #GDBusConnection
487  * @bus_name: the bus name which exports the action group
488  * @object_path: the object path at which the action group is exported
489  * @flags: Flags used when constructing the object
490  * @cancellable: A #GCancellable or %NULL
491  * @error: Return location for error or %NULL
492  *
493  * This is a synchronous failable constructor. See g_dbus_action_group_new()
494  * and g_dbus_action_group_new_finish() for the asynchronous version.
495  *
496  * Returns: A #GDBusProxy or %NULL if @error is set. Free with g_object_unref().
497  */
498 GDBusActionGroup *
499 g_dbus_action_group_new_sync (GDBusConnection        *connection,
500                               const gchar            *bus_name,
501                               const gchar            *object_path,
502                               GDBusActionGroupFlags   flags,
503                               GCancellable           *cancellable,
504                               GError                **error)
505 {
506   GDBusActionGroup *group;
507
508   group = g_object_new (G_TYPE_DBUS_ACTION_GROUP, NULL);
509   group->connection = g_object_ref (connection);
510   group->bus_name = g_strdup (bus_name);
511   group->object_path = g_strdup (object_path);
512
513   if (~flags & G_DBUS_ACTION_GROUP_FLAGS_DO_NOT_WATCH)
514     group->subscription_id =
515       g_dbus_connection_signal_subscribe (connection, bus_name, "org.gtk.Actions", "Changed", object_path, NULL,
516                                           G_DBUS_SIGNAL_FLAGS_NONE, g_dbus_action_group_changed, group, NULL);
517
518   if (~flags & G_DBUS_ACTION_GROUP_FLAGS_DO_NOT_POPULATE)
519     {
520       GVariant *reply;
521
522       reply = g_dbus_connection_call_sync (connection, bus_name, object_path, "org.gtk.Actions",
523                                            "DescribeAll", NULL, G_VARIANT_TYPE ("(a{s(bgav)})"),
524                                            G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error);
525
526       if (reply != NULL)
527         {
528           GVariantIter *iter;
529           ActionInfo *action;
530
531           g_variant_get (reply, "(a{s(bgav)})", &iter);
532           while ((action = action_info_new_from_iter (iter)))
533             g_hash_table_insert (group->actions, action->name, action);
534           g_variant_iter_free (iter);
535           g_variant_unref (reply);
536         }
537       else
538         {
539           g_object_unref (group);
540           return NULL;
541         }
542     }
543
544   return group;
545 }
546
547 gboolean
548 g_dbus_action_group_inject (GDBusActionGroup   *group,
549                             const gchar        *action_name,
550                             const GVariantType *parameter_type,
551                             gboolean            enabled,
552                             GVariant           *state)
553 {
554   ActionInfo *action;
555
556   g_return_val_if_fail (G_IS_DBUS_ACTION_GROUP (group), FALSE);
557   g_return_val_if_fail (action_name != NULL, FALSE);
558
559   if (g_hash_table_lookup (group->actions, action_name))
560     return FALSE;
561
562   action = g_slice_new (ActionInfo);
563   action->name = g_strdup (action_name);
564   action->parameter_type = parameter_type ? g_variant_type_copy (parameter_type) : NULL;
565   action->enabled = !!enabled;
566   action->state = state ? g_variant_ref_sink (state) : NULL;
567
568   g_hash_table_insert (group->actions, action->name, action);
569
570   if (group->strict)
571     g_action_group_action_added (G_ACTION_GROUP (group), action_name);
572
573   return TRUE;
574 }