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 struct
69 : : {
70 : : gchar *name;
71 : : GVariantType *parameter_type;
72 : : gboolean enabled;
73 : : GVariant *state;
74 : : } ActionInfo;
75 : :
76 : : static void
77 : 8 : action_info_free (gpointer user_data)
78 : : {
79 : 8 : ActionInfo *info = user_data;
80 : :
81 : 8 : g_free (info->name);
82 : :
83 : 8 : if (info->state)
84 : 3 : g_variant_unref (info->state);
85 : :
86 : 8 : if (info->parameter_type)
87 : 1 : g_variant_type_free (info->parameter_type);
88 : :
89 : 8 : g_slice_free (ActionInfo, info);
90 : 8 : }
91 : :
92 : : static ActionInfo *
93 : 18 : action_info_new_from_iter (GVariantIter *iter)
94 : : {
95 : : const gchar *param_str;
96 : : ActionInfo *info;
97 : : gboolean enabled;
98 : : GVariant *state;
99 : : gchar *name;
100 : :
101 : 18 : if (!g_variant_iter_next (iter, "{s(b&g@av)}", &name,
102 : : &enabled, ¶m_str, &state))
103 : 10 : return NULL;
104 : :
105 : 8 : info = g_slice_new (ActionInfo);
106 : 8 : info->name = name;
107 : 8 : info->enabled = enabled;
108 : :
109 : 8 : if (g_variant_n_children (state))
110 : 3 : g_variant_get_child (state, 0, "v", &info->state);
111 : : else
112 : 5 : info->state = NULL;
113 : 8 : g_variant_unref (state);
114 : :
115 : 8 : if (param_str[0])
116 : 1 : info->parameter_type = g_variant_type_copy ((GVariantType *) param_str);
117 : : else
118 : 7 : info->parameter_type = NULL;
119 : :
120 : 8 : return info;
121 : 0 : }
122 : :
123 : : static void g_dbus_action_group_remote_iface_init (GRemoteActionGroupInterface *iface);
124 : : static void g_dbus_action_group_iface_init (GActionGroupInterface *iface);
125 : 252 : G_DEFINE_TYPE_WITH_CODE (GDBusActionGroup, g_dbus_action_group, G_TYPE_OBJECT,
126 : 1 : G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, g_dbus_action_group_iface_init)
127 : 1 : G_IMPLEMENT_INTERFACE (G_TYPE_REMOTE_ACTION_GROUP, g_dbus_action_group_remote_iface_init))
128 : :
129 : : static void
130 : 8 : g_dbus_action_group_changed (GDBusConnection *connection,
131 : : const gchar *sender,
132 : : const gchar *object_path,
133 : : const gchar *interface_name,
134 : : const gchar *signal_name,
135 : : GVariant *parameters,
136 : : gpointer user_data)
137 : : {
138 : 8 : GDBusActionGroup *group = user_data;
139 : 8 : GActionGroup *g_group = user_data;
140 : :
141 : : /* make sure that we've been fully initialised */
142 : 8 : if (group->actions == NULL)
143 : 0 : return;
144 : :
145 : 16 : if (g_str_equal (signal_name, "Changed") &&
146 : 8 : g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(asa{sb}a{sv}a{s(bgav)})")))
147 : : {
148 : : /* Removes */
149 : : {
150 : : GVariantIter *iter;
151 : : const gchar *name;
152 : :
153 : 8 : g_variant_get_child (parameters, 0, "as", &iter);
154 : 17 : while (g_variant_iter_next (iter, "&s", &name))
155 : : {
156 : 1 : if (g_hash_table_lookup (group->actions, name))
157 : : {
158 : 1 : g_hash_table_remove (group->actions, name);
159 : 1 : g_action_group_action_removed (g_group, name);
160 : 0 : }
161 : : }
162 : 8 : g_variant_iter_free (iter);
163 : : }
164 : :
165 : : /* Enable changes */
166 : : {
167 : : GVariantIter *iter;
168 : : const gchar *name;
169 : : gboolean enabled;
170 : :
171 : 8 : g_variant_get_child (parameters, 1, "a{sb}", &iter);
172 : 9 : while (g_variant_iter_next (iter, "{&sb}", &name, &enabled))
173 : : {
174 : : ActionInfo *info;
175 : :
176 : 1 : info = g_hash_table_lookup (group->actions, name);
177 : :
178 : 1 : if (info && info->enabled != enabled)
179 : : {
180 : 1 : info->enabled = enabled;
181 : 1 : g_action_group_action_enabled_changed (g_group, name, enabled);
182 : 0 : }
183 : : }
184 : 8 : g_variant_iter_free (iter);
185 : : }
186 : :
187 : : /* State changes */
188 : : {
189 : : GVariantIter *iter;
190 : : const gchar *name;
191 : : GVariant *state;
192 : :
193 : 8 : g_variant_get_child (parameters, 2, "a{sv}", &iter);
194 : 13 : while (g_variant_iter_next (iter, "{&sv}", &name, &state))
195 : : {
196 : : ActionInfo *info;
197 : :
198 : 5 : info = g_hash_table_lookup (group->actions, name);
199 : :
200 : 10 : if (info && info->state && !g_variant_equal (state, info->state) &&
201 : 5 : g_variant_is_of_type (state, g_variant_get_type (info->state)))
202 : : {
203 : 5 : g_variant_unref (info->state);
204 : 5 : info->state = g_variant_ref (state);
205 : :
206 : 5 : g_action_group_action_state_changed (g_group, name, state);
207 : 0 : }
208 : :
209 : 5 : g_variant_unref (state);
210 : : }
211 : 8 : g_variant_iter_free (iter);
212 : : }
213 : :
214 : : /* Additions */
215 : : {
216 : : GVariantIter *iter;
217 : : ActionInfo *info;
218 : :
219 : 8 : g_variant_get_child (parameters, 3, "a{s(bgav)}", &iter);
220 : 9 : while ((info = action_info_new_from_iter (iter)))
221 : : {
222 : 1 : if (!g_hash_table_lookup (group->actions, info->name))
223 : : {
224 : 1 : g_hash_table_insert (group->actions, info->name, info);
225 : :
226 : 1 : if (group->strict)
227 : 1 : g_action_group_action_added (g_group, info->name);
228 : 0 : }
229 : : else
230 : 0 : action_info_free (info);
231 : : }
232 : 8 : g_variant_iter_free (iter);
233 : : }
234 : 0 : }
235 : 0 : }
236 : :
237 : :
238 : : static void
239 : 2 : g_dbus_action_group_describe_all_done (GObject *source,
240 : : GAsyncResult *result,
241 : : gpointer user_data)
242 : : {
243 : 2 : GDBusActionGroup *group= user_data;
244 : : GVariant *reply;
245 : :
246 : 2 : g_assert (group->actions == NULL);
247 : 2 : group->actions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, action_info_free);
248 : :
249 : 2 : g_assert (group->connection == (gpointer) source);
250 : 2 : reply = g_dbus_connection_call_finish (group->connection, result, NULL);
251 : :
252 : 2 : if (reply != NULL)
253 : : {
254 : : GVariantIter *iter;
255 : : ActionInfo *action;
256 : :
257 : 1 : g_variant_get (reply, "(a{s(bgav)})", &iter);
258 : 9 : while ((action = action_info_new_from_iter (iter)))
259 : : {
260 : 7 : g_hash_table_insert (group->actions, action->name, action);
261 : :
262 : 7 : if (group->strict)
263 : 7 : g_action_group_action_added (G_ACTION_GROUP (group), action->name);
264 : : }
265 : 1 : g_variant_iter_free (iter);
266 : 1 : g_variant_unref (reply);
267 : 0 : }
268 : :
269 : 2 : g_object_unref (group);
270 : 2 : }
271 : :
272 : :
273 : : static void
274 : 2 : g_dbus_action_group_async_init (GDBusActionGroup *group)
275 : : {
276 : 2 : if (group->subscription_id != 0)
277 : 0 : return;
278 : :
279 : 2 : group->subscription_id =
280 : 2 : g_dbus_connection_signal_subscribe (group->connection, group->bus_name, "org.gtk.Actions", "Changed", group->object_path,
281 : 0 : NULL, G_DBUS_SIGNAL_FLAGS_NONE, g_dbus_action_group_changed, group, NULL);
282 : :
283 : 2 : g_dbus_connection_call (group->connection, group->bus_name, group->object_path, "org.gtk.Actions", "DescribeAll", NULL,
284 : 0 : G_VARIANT_TYPE ("(a{s(bgav)})"), G_DBUS_CALL_FLAGS_NONE, -1, NULL,
285 : 0 : g_dbus_action_group_describe_all_done, g_object_ref (group));
286 : 0 : }
287 : :
288 : : static gchar **
289 : 11 : g_dbus_action_group_list_actions (GActionGroup *g_group)
290 : : {
291 : 11 : GDBusActionGroup *group = G_DBUS_ACTION_GROUP (g_group);
292 : : gchar **keys;
293 : :
294 : 11 : if (group->actions != NULL)
295 : : {
296 : : GHashTableIter iter;
297 : 9 : unsigned int n, i = 0;
298 : : gpointer key;
299 : :
300 : 9 : n = g_hash_table_size (group->actions);
301 : 9 : keys = g_new (gchar *, n + 1);
302 : :
303 : 9 : g_hash_table_iter_init (&iter, group->actions);
304 : 75 : while (g_hash_table_iter_next (&iter, &key, NULL))
305 : 132 : keys[i++] = g_strdup (key);
306 : 9 : g_assert (i == n);
307 : 9 : keys[n] = NULL;
308 : 0 : }
309 : : else
310 : : {
311 : 2 : g_dbus_action_group_async_init (group);
312 : 2 : keys = g_new0 (gchar *, 1);
313 : : }
314 : :
315 : 11 : group->strict = TRUE;
316 : :
317 : 11 : return keys;
318 : : }
319 : :
320 : : static gboolean
321 : 59 : g_dbus_action_group_query_action (GActionGroup *g_group,
322 : : const gchar *action_name,
323 : : gboolean *enabled,
324 : : const GVariantType **parameter_type,
325 : : const GVariantType **state_type,
326 : : GVariant **state_hint,
327 : : GVariant **state)
328 : : {
329 : 59 : GDBusActionGroup *group = G_DBUS_ACTION_GROUP (g_group);
330 : : ActionInfo *info;
331 : :
332 : 59 : if (group->actions != NULL)
333 : : {
334 : 59 : info = g_hash_table_lookup (group->actions, action_name);
335 : :
336 : 59 : if (info == NULL)
337 : : {
338 : 0 : group->strict = TRUE;
339 : 0 : return FALSE;
340 : : }
341 : :
342 : 59 : if (enabled)
343 : 59 : *enabled = info->enabled;
344 : :
345 : 59 : if (parameter_type)
346 : 59 : *parameter_type = info->parameter_type;
347 : :
348 : 59 : if (state_type)
349 : 59 : *state_type = info->state ? g_variant_get_type (info->state) : NULL;
350 : :
351 : 59 : if (state_hint)
352 : 59 : *state_hint = NULL;
353 : :
354 : 59 : if (state)
355 : 59 : *state = info->state ? g_variant_ref (info->state) : NULL;
356 : :
357 : 59 : return TRUE;
358 : : }
359 : : else
360 : : {
361 : 0 : g_dbus_action_group_async_init (group);
362 : 0 : group->strict = TRUE;
363 : :
364 : 0 : return FALSE;
365 : : }
366 : 0 : }
367 : :
368 : : static void
369 : 2 : g_dbus_action_group_activate_action_full (GRemoteActionGroup *remote,
370 : : const gchar *action_name,
371 : : GVariant *parameter,
372 : : GVariant *platform_data)
373 : : {
374 : 2 : GDBusActionGroup *group = G_DBUS_ACTION_GROUP (remote);
375 : : GVariantBuilder builder;
376 : :
377 : 2 : g_variant_builder_init_static (&builder, G_VARIANT_TYPE ("av"));
378 : :
379 : 2 : if (parameter)
380 : 0 : g_variant_builder_add (&builder, "v", parameter);
381 : :
382 : 2 : g_dbus_connection_call (group->connection, group->bus_name, group->object_path, "org.gtk.Actions", "Activate",
383 : 0 : g_variant_new ("(sav@a{sv})", action_name, &builder, platform_data),
384 : : NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
385 : 2 : }
386 : :
387 : : static void
388 : 1 : g_dbus_action_group_change_action_state_full (GRemoteActionGroup *remote,
389 : : const gchar *action_name,
390 : : GVariant *value,
391 : : GVariant *platform_data)
392 : : {
393 : 1 : GDBusActionGroup *group = G_DBUS_ACTION_GROUP (remote);
394 : :
395 : 1 : g_dbus_connection_call (group->connection, group->bus_name, group->object_path, "org.gtk.Actions", "SetState",
396 : 0 : g_variant_new ("(sv@a{sv})", action_name, value, platform_data),
397 : : NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
398 : 1 : }
399 : :
400 : : static void
401 : 1 : g_dbus_action_group_change_state (GActionGroup *group,
402 : : const gchar *action_name,
403 : : GVariant *value)
404 : : {
405 : 1 : g_dbus_action_group_change_action_state_full (G_REMOTE_ACTION_GROUP (group),
406 : 0 : action_name, value, g_variant_new ("a{sv}", NULL));
407 : 1 : }
408 : :
409 : : static void
410 : 2 : g_dbus_action_group_activate (GActionGroup *group,
411 : : const gchar *action_name,
412 : : GVariant *parameter)
413 : : {
414 : 2 : g_dbus_action_group_activate_action_full (G_REMOTE_ACTION_GROUP (group),
415 : 0 : action_name, parameter, g_variant_new ("a{sv}", NULL));
416 : 2 : }
417 : :
418 : : static void
419 : 3 : g_dbus_action_group_finalize (GObject *object)
420 : : {
421 : 3 : GDBusActionGroup *group = G_DBUS_ACTION_GROUP (object);
422 : :
423 : 3 : if (group->subscription_id)
424 : 3 : g_dbus_connection_signal_unsubscribe (group->connection, g_steal_handle_id (&group->subscription_id));
425 : :
426 : 3 : if (group->actions)
427 : 3 : g_hash_table_unref (group->actions);
428 : :
429 : 3 : g_object_unref (group->connection);
430 : 3 : g_free (group->object_path);
431 : 3 : g_free (group->bus_name);
432 : :
433 : 3 : G_OBJECT_CLASS (g_dbus_action_group_parent_class)
434 : 3 : ->finalize (object);
435 : 3 : }
436 : :
437 : : static void
438 : 3 : g_dbus_action_group_init (GDBusActionGroup *group)
439 : : {
440 : 3 : }
441 : :
442 : : static void
443 : 4 : g_dbus_action_group_class_init (GDBusActionGroupClass *class)
444 : : {
445 : 4 : GObjectClass *object_class = G_OBJECT_CLASS (class);
446 : :
447 : 4 : object_class->finalize = g_dbus_action_group_finalize;
448 : 4 : }
449 : :
450 : : static void
451 : 4 : g_dbus_action_group_remote_iface_init (GRemoteActionGroupInterface *iface)
452 : : {
453 : 4 : iface->activate_action_full = g_dbus_action_group_activate_action_full;
454 : 4 : iface->change_action_state_full = g_dbus_action_group_change_action_state_full;
455 : 4 : }
456 : :
457 : : static void
458 : 4 : g_dbus_action_group_iface_init (GActionGroupInterface *iface)
459 : : {
460 : 4 : iface->list_actions = g_dbus_action_group_list_actions;
461 : 4 : iface->query_action = g_dbus_action_group_query_action;
462 : 4 : iface->change_action_state = g_dbus_action_group_change_state;
463 : 4 : iface->activate_action = g_dbus_action_group_activate;
464 : 4 : }
465 : :
466 : : /**
467 : : * g_dbus_action_group_get:
468 : : * @connection: A #GDBusConnection
469 : : * @bus_name: (nullable): the bus name which exports the action
470 : : * group or %NULL if @connection is not a message bus connection
471 : : * @object_path: the object path at which the action group is exported
472 : : *
473 : : * Obtains a #GDBusActionGroup for the action group which is exported at
474 : : * the given @bus_name and @object_path.
475 : : *
476 : : * The thread default main context is taken at the time of this call.
477 : : * All signals on the menu model (and any linked models) are reported
478 : : * with respect to this context. All calls on the returned menu model
479 : : * (and linked models) must also originate from this same context, with
480 : : * the thread default main context unchanged.
481 : : *
482 : : * This call is non-blocking. The returned action group may or may not
483 : : * already be filled in. The correct thing to do is connect the signals
484 : : * for the action group to monitor for changes and then to call
485 : : * g_action_group_list_actions() to get the initial list.
486 : : *
487 : : * Returns: (transfer full): a #GDBusActionGroup
488 : : *
489 : : * Since: 2.32
490 : : */
491 : : GDBusActionGroup *
492 : 3 : g_dbus_action_group_get (GDBusConnection *connection,
493 : : const gchar *bus_name,
494 : : const gchar *object_path)
495 : : {
496 : : GDBusActionGroup *group;
497 : :
498 : 3 : g_return_val_if_fail (bus_name != NULL || g_dbus_connection_get_unique_name (connection) == NULL, NULL);
499 : :
500 : 3 : group = g_object_new (G_TYPE_DBUS_ACTION_GROUP, NULL);
501 : 3 : group->connection = g_object_ref (connection);
502 : 3 : group->bus_name = g_strdup (bus_name);
503 : 3 : group->object_path = g_strdup (object_path);
504 : :
505 : 3 : return group;
506 : 0 : }
507 : :
508 : : gboolean
509 : 1 : g_dbus_action_group_sync (GDBusActionGroup *group,
510 : : GCancellable *cancellable,
511 : : GError **error)
512 : : {
513 : : GVariant *reply;
514 : :
515 : 1 : g_assert (group->subscription_id == 0);
516 : :
517 : 1 : group->subscription_id =
518 : 1 : g_dbus_connection_signal_subscribe (group->connection, group->bus_name, "org.gtk.Actions", "Changed", group->object_path,
519 : 0 : NULL, G_DBUS_SIGNAL_FLAGS_NONE, g_dbus_action_group_changed, group, NULL);
520 : :
521 : 1 : reply = g_dbus_connection_call_sync (group->connection, group->bus_name, group->object_path, "org.gtk.Actions",
522 : 0 : "DescribeAll", NULL, G_VARIANT_TYPE ("(a{s(bgav)})"),
523 : 0 : G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error);
524 : :
525 : 1 : if (reply != NULL)
526 : : {
527 : : GVariantIter *iter;
528 : : ActionInfo *action;
529 : :
530 : 1 : g_assert (group->actions == NULL);
531 : 1 : group->actions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, action_info_free);
532 : :
533 : 1 : g_variant_get (reply, "(a{s(bgav)})", &iter);
534 : 1 : while ((action = action_info_new_from_iter (iter)))
535 : 0 : g_hash_table_insert (group->actions, action->name, action);
536 : 1 : g_variant_iter_free (iter);
537 : 1 : g_variant_unref (reply);
538 : 0 : }
539 : :
540 : 1 : return reply != NULL;
541 : : }
|