Branch data Line data Source code
1 : : /*
2 : : * Copyright © 2010 Codethink Limited
3 : : * Copyright © 2011 Canonical Limited
4 : : *
5 : : * SPDX-License-Identifier: LGPL-2.1-or-later
6 : : *
7 : : * This library is free software; you can redistribute it and/or
8 : : * modify it under the terms of the GNU Lesser General Public
9 : : * License as published by the Free Software Foundation; either
10 : : * version 2.1 of the License, or (at your option) any later version.
11 : : *
12 : : * This library is distributed in the hope that it will be useful,
13 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 : : * Lesser General Public License for more details.
16 : : *
17 : : * You should have received a copy of the GNU Lesser General
18 : : * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19 : : *
20 : : * Authors: Ryan Lortie <desrt@desrt.ca>
21 : : */
22 : :
23 : : #include "config.h"
24 : :
25 : : #include "gdbusactiongroup-private.h"
26 : :
27 : : #include "gremoteactiongroup.h"
28 : : #include "gdbusconnection.h"
29 : : #include "gactiongroup.h"
30 : :
31 : : /**
32 : : * GDBusActionGroup:
33 : : *
34 : : * `GDBusActionGroup` is an implementation of the [iface@Gio.ActionGroup]
35 : : * interface.
36 : : *
37 : : * `GDBusActionGroup` can be used as a proxy for an action group
38 : : * that is exported over D-Bus with [method@Gio.DBusConnection.export_action_group].
39 : : */
40 : :
41 : : struct _GDBusActionGroup
42 : : {
43 : : GObject parent_instance;
44 : :
45 : : GDBusConnection *connection;
46 : : gchar *bus_name;
47 : : gchar *object_path;
48 : : guint subscription_id;
49 : : GHashTable *actions;
50 : :
51 : : /* The 'strict' flag indicates that the non-existence of at least one
52 : : * action has potentially been observed through the API. This means
53 : : * that we should always emit 'action-added' signals for all new
54 : : * actions.
55 : : *
56 : : * The user can observe the non-existence of an action by listing the
57 : : * actions or by performing a query (such as parameter type) on a
58 : : * non-existent action.
59 : : *
60 : : * If the user has no way of knowing that a given action didn't
61 : : * already exist then we can skip emitting 'action-added' signals
62 : : * since they have no way of knowing that it wasn't there from the
63 : : * start.
64 : : */
65 : : gboolean strict;
66 : : };
67 : :
68 : : typedef GObjectClass GDBusActionGroupClass;
69 : :
70 : : typedef struct
71 : : {
72 : : gchar *name;
73 : : GVariantType *parameter_type;
74 : : gboolean enabled;
75 : : GVariant *state;
76 : : } ActionInfo;
77 : :
78 : : static void
79 : 8 : action_info_free (gpointer user_data)
80 : : {
81 : 8 : ActionInfo *info = user_data;
82 : :
83 : 8 : g_free (info->name);
84 : :
85 : 8 : if (info->state)
86 : 3 : g_variant_unref (info->state);
87 : :
88 : 8 : if (info->parameter_type)
89 : 1 : g_variant_type_free (info->parameter_type);
90 : :
91 : 8 : g_slice_free (ActionInfo, info);
92 : 8 : }
93 : :
94 : : static ActionInfo *
95 : 18 : action_info_new_from_iter (GVariantIter *iter)
96 : : {
97 : : const gchar *param_str;
98 : : ActionInfo *info;
99 : : gboolean enabled;
100 : : GVariant *state;
101 : : gchar *name;
102 : :
103 : 18 : if (!g_variant_iter_next (iter, "{s(b&g@av)}", &name,
104 : : &enabled, ¶m_str, &state))
105 : 10 : return NULL;
106 : :
107 : 8 : info = g_slice_new (ActionInfo);
108 : 8 : info->name = name;
109 : 8 : info->enabled = enabled;
110 : :
111 : 8 : if (g_variant_n_children (state))
112 : 3 : g_variant_get_child (state, 0, "v", &info->state);
113 : : else
114 : 5 : info->state = NULL;
115 : 8 : g_variant_unref (state);
116 : :
117 : 8 : if (param_str[0])
118 : 1 : info->parameter_type = g_variant_type_copy ((GVariantType *) param_str);
119 : : else
120 : 7 : info->parameter_type = NULL;
121 : :
122 : 8 : return info;
123 : : }
124 : :
125 : : static void g_dbus_action_group_remote_iface_init (GRemoteActionGroupInterface *iface);
126 : : static void g_dbus_action_group_iface_init (GActionGroupInterface *iface);
127 : 244 : G_DEFINE_TYPE_WITH_CODE (GDBusActionGroup, g_dbus_action_group, G_TYPE_OBJECT,
128 : : G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, g_dbus_action_group_iface_init)
129 : : G_IMPLEMENT_INTERFACE (G_TYPE_REMOTE_ACTION_GROUP, g_dbus_action_group_remote_iface_init))
130 : :
131 : : static void
132 : 8 : g_dbus_action_group_changed (GDBusConnection *connection,
133 : : const gchar *sender,
134 : : const gchar *object_path,
135 : : const gchar *interface_name,
136 : : const gchar *signal_name,
137 : : GVariant *parameters,
138 : : gpointer user_data)
139 : : {
140 : 8 : GDBusActionGroup *group = user_data;
141 : 8 : GActionGroup *g_group = user_data;
142 : :
143 : : /* make sure that we've been fully initialised */
144 : 8 : if (group->actions == NULL)
145 : 0 : return;
146 : :
147 : 16 : if (g_str_equal (signal_name, "Changed") &&
148 : 8 : g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(asa{sb}a{sv}a{s(bgav)})")))
149 : : {
150 : : /* Removes */
151 : : {
152 : : GVariantIter *iter;
153 : : const gchar *name;
154 : :
155 : 8 : g_variant_get_child (parameters, 0, "as", &iter);
156 : 17 : while (g_variant_iter_next (iter, "&s", &name))
157 : : {
158 : 1 : if (g_hash_table_lookup (group->actions, name))
159 : : {
160 : 1 : g_hash_table_remove (group->actions, name);
161 : 1 : g_action_group_action_removed (g_group, name);
162 : : }
163 : : }
164 : 8 : g_variant_iter_free (iter);
165 : : }
166 : :
167 : : /* Enable changes */
168 : : {
169 : : GVariantIter *iter;
170 : : const gchar *name;
171 : : gboolean enabled;
172 : :
173 : 8 : g_variant_get_child (parameters, 1, "a{sb}", &iter);
174 : 9 : while (g_variant_iter_next (iter, "{&sb}", &name, &enabled))
175 : : {
176 : : ActionInfo *info;
177 : :
178 : 1 : info = g_hash_table_lookup (group->actions, name);
179 : :
180 : 1 : if (info && info->enabled != enabled)
181 : : {
182 : 1 : info->enabled = enabled;
183 : 1 : g_action_group_action_enabled_changed (g_group, name, enabled);
184 : : }
185 : : }
186 : 8 : g_variant_iter_free (iter);
187 : : }
188 : :
189 : : /* State changes */
190 : : {
191 : : GVariantIter *iter;
192 : : const gchar *name;
193 : : GVariant *state;
194 : :
195 : 8 : g_variant_get_child (parameters, 2, "a{sv}", &iter);
196 : 13 : while (g_variant_iter_next (iter, "{&sv}", &name, &state))
197 : : {
198 : : ActionInfo *info;
199 : :
200 : 5 : info = g_hash_table_lookup (group->actions, name);
201 : :
202 : 10 : if (info && info->state && !g_variant_equal (state, info->state) &&
203 : 5 : g_variant_is_of_type (state, g_variant_get_type (info->state)))
204 : : {
205 : 5 : g_variant_unref (info->state);
206 : 5 : info->state = g_variant_ref (state);
207 : :
208 : 5 : g_action_group_action_state_changed (g_group, name, state);
209 : : }
210 : :
211 : 5 : g_variant_unref (state);
212 : : }
213 : 8 : g_variant_iter_free (iter);
214 : : }
215 : :
216 : : /* Additions */
217 : : {
218 : : GVariantIter *iter;
219 : : ActionInfo *info;
220 : :
221 : 8 : g_variant_get_child (parameters, 3, "a{s(bgav)}", &iter);
222 : 9 : while ((info = action_info_new_from_iter (iter)))
223 : : {
224 : 1 : if (!g_hash_table_lookup (group->actions, info->name))
225 : : {
226 : 1 : g_hash_table_insert (group->actions, info->name, info);
227 : :
228 : 1 : if (group->strict)
229 : 1 : g_action_group_action_added (g_group, info->name);
230 : : }
231 : : else
232 : 0 : action_info_free (info);
233 : : }
234 : 8 : g_variant_iter_free (iter);
235 : : }
236 : : }
237 : : }
238 : :
239 : :
240 : : static void
241 : 2 : g_dbus_action_group_describe_all_done (GObject *source,
242 : : GAsyncResult *result,
243 : : gpointer user_data)
244 : : {
245 : 2 : GDBusActionGroup *group= user_data;
246 : : GVariant *reply;
247 : :
248 : 2 : g_assert (group->actions == NULL);
249 : 2 : group->actions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, action_info_free);
250 : :
251 : 2 : g_assert (group->connection == (gpointer) source);
252 : 2 : reply = g_dbus_connection_call_finish (group->connection, result, NULL);
253 : :
254 : 2 : if (reply != NULL)
255 : : {
256 : : GVariantIter *iter;
257 : : ActionInfo *action;
258 : :
259 : 1 : g_variant_get (reply, "(a{s(bgav)})", &iter);
260 : 9 : while ((action = action_info_new_from_iter (iter)))
261 : : {
262 : 7 : g_hash_table_insert (group->actions, action->name, action);
263 : :
264 : 7 : if (group->strict)
265 : 7 : g_action_group_action_added (G_ACTION_GROUP (group), action->name);
266 : : }
267 : 1 : g_variant_iter_free (iter);
268 : 1 : g_variant_unref (reply);
269 : : }
270 : :
271 : 2 : g_object_unref (group);
272 : 2 : }
273 : :
274 : :
275 : : static void
276 : 2 : g_dbus_action_group_async_init (GDBusActionGroup *group)
277 : : {
278 : 2 : if (group->subscription_id != 0)
279 : 0 : return;
280 : :
281 : 2 : group->subscription_id =
282 : 2 : g_dbus_connection_signal_subscribe (group->connection, group->bus_name, "org.gtk.Actions", "Changed", group->object_path,
283 : : NULL, G_DBUS_SIGNAL_FLAGS_NONE, g_dbus_action_group_changed, group, NULL);
284 : :
285 : 2 : g_dbus_connection_call (group->connection, group->bus_name, group->object_path, "org.gtk.Actions", "DescribeAll", NULL,
286 : : G_VARIANT_TYPE ("(a{s(bgav)})"), G_DBUS_CALL_FLAGS_NONE, -1, NULL,
287 : : g_dbus_action_group_describe_all_done, g_object_ref (group));
288 : : }
289 : :
290 : : static gchar **
291 : 11 : g_dbus_action_group_list_actions (GActionGroup *g_group)
292 : : {
293 : 11 : GDBusActionGroup *group = G_DBUS_ACTION_GROUP (g_group);
294 : : gchar **keys;
295 : :
296 : 11 : if (group->actions != NULL)
297 : : {
298 : : GHashTableIter iter;
299 : 9 : gint n, i = 0;
300 : : gpointer key;
301 : :
302 : 9 : n = g_hash_table_size (group->actions);
303 : 9 : keys = g_new (gchar *, n + 1);
304 : :
305 : 9 : g_hash_table_iter_init (&iter, group->actions);
306 : 75 : while (g_hash_table_iter_next (&iter, &key, NULL))
307 : 132 : keys[i++] = g_strdup (key);
308 : 9 : g_assert_cmpint (i, ==, n);
309 : 9 : keys[n] = NULL;
310 : : }
311 : : else
312 : : {
313 : 2 : g_dbus_action_group_async_init (group);
314 : 2 : keys = g_new0 (gchar *, 1);
315 : : }
316 : :
317 : 11 : group->strict = TRUE;
318 : :
319 : 11 : return keys;
320 : : }
321 : :
322 : : static gboolean
323 : 59 : g_dbus_action_group_query_action (GActionGroup *g_group,
324 : : const gchar *action_name,
325 : : gboolean *enabled,
326 : : const GVariantType **parameter_type,
327 : : const GVariantType **state_type,
328 : : GVariant **state_hint,
329 : : GVariant **state)
330 : : {
331 : 59 : GDBusActionGroup *group = G_DBUS_ACTION_GROUP (g_group);
332 : : ActionInfo *info;
333 : :
334 : 59 : if (group->actions != NULL)
335 : : {
336 : 59 : info = g_hash_table_lookup (group->actions, action_name);
337 : :
338 : 59 : if (info == NULL)
339 : : {
340 : 0 : group->strict = TRUE;
341 : 0 : return FALSE;
342 : : }
343 : :
344 : 59 : if (enabled)
345 : 59 : *enabled = info->enabled;
346 : :
347 : 59 : if (parameter_type)
348 : 59 : *parameter_type = info->parameter_type;
349 : :
350 : 59 : if (state_type)
351 : 59 : *state_type = info->state ? g_variant_get_type (info->state) : NULL;
352 : :
353 : 59 : if (state_hint)
354 : 59 : *state_hint = NULL;
355 : :
356 : 59 : if (state)
357 : 59 : *state = info->state ? g_variant_ref (info->state) : NULL;
358 : :
359 : 59 : return TRUE;
360 : : }
361 : : else
362 : : {
363 : 0 : g_dbus_action_group_async_init (group);
364 : 0 : group->strict = TRUE;
365 : :
366 : 0 : return FALSE;
367 : : }
368 : : }
369 : :
370 : : static void
371 : 2 : g_dbus_action_group_activate_action_full (GRemoteActionGroup *remote,
372 : : const gchar *action_name,
373 : : GVariant *parameter,
374 : : GVariant *platform_data)
375 : : {
376 : 2 : GDBusActionGroup *group = G_DBUS_ACTION_GROUP (remote);
377 : : GVariantBuilder builder;
378 : :
379 : 2 : g_variant_builder_init_static (&builder, G_VARIANT_TYPE ("av"));
380 : :
381 : 2 : if (parameter)
382 : 0 : g_variant_builder_add (&builder, "v", parameter);
383 : :
384 : 2 : g_dbus_connection_call (group->connection, group->bus_name, group->object_path, "org.gtk.Actions", "Activate",
385 : : g_variant_new ("(sav@a{sv})", action_name, &builder, platform_data),
386 : : NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
387 : 2 : }
388 : :
389 : : static void
390 : 1 : g_dbus_action_group_change_action_state_full (GRemoteActionGroup *remote,
391 : : const gchar *action_name,
392 : : GVariant *value,
393 : : GVariant *platform_data)
394 : : {
395 : 1 : GDBusActionGroup *group = G_DBUS_ACTION_GROUP (remote);
396 : :
397 : 1 : g_dbus_connection_call (group->connection, group->bus_name, group->object_path, "org.gtk.Actions", "SetState",
398 : : g_variant_new ("(sv@a{sv})", action_name, value, platform_data),
399 : : NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
400 : 1 : }
401 : :
402 : : static void
403 : 1 : g_dbus_action_group_change_state (GActionGroup *group,
404 : : const gchar *action_name,
405 : : GVariant *value)
406 : : {
407 : 1 : g_dbus_action_group_change_action_state_full (G_REMOTE_ACTION_GROUP (group),
408 : : action_name, value, g_variant_new ("a{sv}", NULL));
409 : 1 : }
410 : :
411 : : static void
412 : 2 : g_dbus_action_group_activate (GActionGroup *group,
413 : : const gchar *action_name,
414 : : GVariant *parameter)
415 : : {
416 : 2 : g_dbus_action_group_activate_action_full (G_REMOTE_ACTION_GROUP (group),
417 : : action_name, parameter, g_variant_new ("a{sv}", NULL));
418 : 2 : }
419 : :
420 : : static void
421 : 3 : g_dbus_action_group_finalize (GObject *object)
422 : : {
423 : 3 : GDBusActionGroup *group = G_DBUS_ACTION_GROUP (object);
424 : :
425 : 3 : if (group->subscription_id)
426 : 3 : g_dbus_connection_signal_unsubscribe (group->connection, g_steal_handle_id (&group->subscription_id));
427 : :
428 : 3 : if (group->actions)
429 : 3 : g_hash_table_unref (group->actions);
430 : :
431 : 3 : g_object_unref (group->connection);
432 : 3 : g_free (group->object_path);
433 : 3 : g_free (group->bus_name);
434 : :
435 : 3 : G_OBJECT_CLASS (g_dbus_action_group_parent_class)
436 : 3 : ->finalize (object);
437 : 3 : }
438 : :
439 : : static void
440 : 3 : g_dbus_action_group_init (GDBusActionGroup *group)
441 : : {
442 : 3 : }
443 : :
444 : : static void
445 : 3 : g_dbus_action_group_class_init (GDBusActionGroupClass *class)
446 : : {
447 : 3 : GObjectClass *object_class = G_OBJECT_CLASS (class);
448 : :
449 : 3 : object_class->finalize = g_dbus_action_group_finalize;
450 : 3 : }
451 : :
452 : : static void
453 : 3 : g_dbus_action_group_remote_iface_init (GRemoteActionGroupInterface *iface)
454 : : {
455 : 3 : iface->activate_action_full = g_dbus_action_group_activate_action_full;
456 : 3 : iface->change_action_state_full = g_dbus_action_group_change_action_state_full;
457 : 3 : }
458 : :
459 : : static void
460 : 3 : g_dbus_action_group_iface_init (GActionGroupInterface *iface)
461 : : {
462 : 3 : iface->list_actions = g_dbus_action_group_list_actions;
463 : 3 : iface->query_action = g_dbus_action_group_query_action;
464 : 3 : iface->change_action_state = g_dbus_action_group_change_state;
465 : 3 : iface->activate_action = g_dbus_action_group_activate;
466 : 3 : }
467 : :
468 : : /**
469 : : * g_dbus_action_group_get:
470 : : * @connection: A #GDBusConnection
471 : : * @bus_name: (nullable): the bus name which exports the action
472 : : * group or %NULL if @connection is not a message bus connection
473 : : * @object_path: the object path at which the action group is exported
474 : : *
475 : : * Obtains a #GDBusActionGroup for the action group which is exported at
476 : : * the given @bus_name and @object_path.
477 : : *
478 : : * The thread default main context is taken at the time of this call.
479 : : * All signals on the menu model (and any linked models) are reported
480 : : * with respect to this context. All calls on the returned menu model
481 : : * (and linked models) must also originate from this same context, with
482 : : * the thread default main context unchanged.
483 : : *
484 : : * This call is non-blocking. The returned action group may or may not
485 : : * already be filled in. The correct thing to do is connect the signals
486 : : * for the action group to monitor for changes and then to call
487 : : * g_action_group_list_actions() to get the initial list.
488 : : *
489 : : * Returns: (transfer full): a #GDBusActionGroup
490 : : *
491 : : * Since: 2.32
492 : : */
493 : : GDBusActionGroup *
494 : 3 : g_dbus_action_group_get (GDBusConnection *connection,
495 : : const gchar *bus_name,
496 : : const gchar *object_path)
497 : : {
498 : : GDBusActionGroup *group;
499 : :
500 : 3 : g_return_val_if_fail (bus_name != NULL || g_dbus_connection_get_unique_name (connection) == NULL, NULL);
501 : :
502 : 3 : group = g_object_new (G_TYPE_DBUS_ACTION_GROUP, NULL);
503 : 3 : group->connection = g_object_ref (connection);
504 : 3 : group->bus_name = g_strdup (bus_name);
505 : 3 : group->object_path = g_strdup (object_path);
506 : :
507 : 3 : return group;
508 : : }
509 : :
510 : : gboolean
511 : 1 : g_dbus_action_group_sync (GDBusActionGroup *group,
512 : : GCancellable *cancellable,
513 : : GError **error)
514 : : {
515 : : GVariant *reply;
516 : :
517 : 1 : g_assert (group->subscription_id == 0);
518 : :
519 : 1 : group->subscription_id =
520 : 1 : g_dbus_connection_signal_subscribe (group->connection, group->bus_name, "org.gtk.Actions", "Changed", group->object_path,
521 : : NULL, G_DBUS_SIGNAL_FLAGS_NONE, g_dbus_action_group_changed, group, NULL);
522 : :
523 : 1 : reply = g_dbus_connection_call_sync (group->connection, group->bus_name, group->object_path, "org.gtk.Actions",
524 : : "DescribeAll", NULL, G_VARIANT_TYPE ("(a{s(bgav)})"),
525 : : G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error);
526 : :
527 : 1 : if (reply != NULL)
528 : : {
529 : : GVariantIter *iter;
530 : : ActionInfo *action;
531 : :
532 : 1 : g_assert (group->actions == NULL);
533 : 1 : group->actions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, action_info_free);
534 : :
535 : 1 : g_variant_get (reply, "(a{s(bgav)})", &iter);
536 : 1 : while ((action = action_info_new_from_iter (iter)))
537 : 0 : g_hash_table_insert (group->actions, action->name, action);
538 : 1 : g_variant_iter_free (iter);
539 : 1 : g_variant_unref (reply);
540 : : }
541 : :
542 : 1 : return reply != NULL;
543 : : }
|