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 "gactiongroupexporter.h"
26 : :
27 : : #include "gdbusmethodinvocation.h"
28 : : #include "gremoteactiongroup.h"
29 : : #include "gdbusintrospection.h"
30 : : #include "gdbusconnection.h"
31 : : #include "gactiongroup.h"
32 : : #include "gdbuserror.h"
33 : :
34 : : /**
35 : : * GActionGroupExporter:
36 : : *
37 : : * These functions support exporting a [iface@Gio.ActionGroup] on D-Bus.
38 : : * The D-Bus interface that is used is a private implementation
39 : : * detail.
40 : : *
41 : : * To access an exported `GActionGroup` remotely, use
42 : : * [method@Gio.DBusActionGroup.get] to obtain a [class@Gio.DBusActionGroup].
43 : : */
44 : :
45 : : static GVariant *
46 : 15 : g_action_group_describe_action (GActionGroup *action_group,
47 : : const gchar *name)
48 : : {
49 : : const GVariantType *type;
50 : : GVariantBuilder builder;
51 : : gboolean enabled;
52 : : GVariant *state;
53 : :
54 : 15 : g_variant_builder_init_static (&builder, G_VARIANT_TYPE ("(bgav)"));
55 : :
56 : 15 : enabled = g_action_group_get_action_enabled (action_group, name);
57 : 15 : g_variant_builder_add (&builder, "b", enabled);
58 : :
59 : 15 : if ((type = g_action_group_get_action_parameter_type (action_group, name)))
60 : : {
61 : 2 : gchar *str = g_variant_type_dup_string (type);
62 : 2 : g_variant_builder_add (&builder, "g", str);
63 : 2 : g_free (str);
64 : : }
65 : : else
66 : 13 : g_variant_builder_add (&builder, "g", "");
67 : :
68 : 15 : g_variant_builder_open (&builder, G_VARIANT_TYPE ("av"));
69 : 15 : if ((state = g_action_group_get_action_state (action_group, name)))
70 : : {
71 : 4 : g_variant_builder_add (&builder, "v", state);
72 : 4 : g_variant_unref (state);
73 : : }
74 : 15 : g_variant_builder_close (&builder);
75 : :
76 : 15 : return g_variant_builder_end (&builder);
77 : : }
78 : :
79 : : /* Using XML saves us dozens of relocations vs. using the introspection
80 : : * structure types. We only need to burn cycles and memory if we
81 : : * actually use the exporter -- not in every single app using GIO.
82 : : *
83 : : * It's also a lot easier to read. :)
84 : : *
85 : : * For documentation of this interface, see
86 : : * https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI
87 : : */
88 : : const char org_gtk_Actions_xml[] =
89 : : "<node>"
90 : : " <interface name='org.gtk.Actions'>"
91 : : " <method name='List'>"
92 : : " <arg type='as' name='list' direction='out'/>"
93 : : " </method>"
94 : : " <method name='Describe'>"
95 : : " <arg type='s' name='action_name' direction='in'/>"
96 : : " <arg type='(bgav)' name='description' direction='out'/>"
97 : : " </method>"
98 : : " <method name='DescribeAll'>"
99 : : " <arg type='a{s(bgav)}' name='descriptions' direction='out'/>"
100 : : " </method>"
101 : : " <method name='Activate'>"
102 : : " <arg type='s' name='action_name' direction='in'/>"
103 : : " <arg type='av' name='parameter' direction='in'/>"
104 : : " <arg type='a{sv}' name='platform_data' direction='in'/>"
105 : : " </method>"
106 : : " <method name='SetState'>"
107 : : " <arg type='s' name='action_name' direction='in'/>"
108 : : " <arg type='v' name='value' direction='in'/>"
109 : : " <arg type='a{sv}' name='platform_data' direction='in'/>"
110 : : " </method>"
111 : : " <signal name='Changed'>"
112 : : " <arg type='as' name='removals'/>"
113 : : " <arg type='a{sb}' name='enable_changes'/>"
114 : : " <arg type='a{sv}' name='state_changes'/>"
115 : : " <arg type='a{s(bgav)}' name='additions'/>"
116 : : " </signal>"
117 : : " </interface>"
118 : : "</node>";
119 : :
120 : : static GDBusInterfaceInfo *org_gtk_Actions;
121 : :
122 : : typedef struct
123 : : {
124 : : GActionGroup *action_group;
125 : : GDBusConnection *connection;
126 : : GMainContext *context;
127 : : gchar *object_path;
128 : : GHashTable *pending_changes;
129 : : GSource *pending_source;
130 : : } GActionGroupExporter;
131 : :
132 : : #define ACTION_ADDED_EVENT (1u<<0)
133 : : #define ACTION_REMOVED_EVENT (1u<<1)
134 : : #define ACTION_STATE_CHANGED_EVENT (1u<<2)
135 : : #define ACTION_ENABLED_CHANGED_EVENT (1u<<3)
136 : :
137 : : static gboolean
138 : 10 : g_action_group_exporter_dispatch_events (gpointer user_data)
139 : : {
140 : 10 : GActionGroupExporter *exporter = user_data;
141 : : GVariantBuilder removes;
142 : : GVariantBuilder enabled_changes;
143 : : GVariantBuilder state_changes;
144 : : GVariantBuilder adds;
145 : : GHashTableIter iter;
146 : : gpointer value;
147 : : gpointer key;
148 : :
149 : 10 : g_variant_builder_init_static (&removes, G_VARIANT_TYPE_STRING_ARRAY);
150 : 10 : g_variant_builder_init_static (&enabled_changes, G_VARIANT_TYPE ("a{sb}"));
151 : 10 : g_variant_builder_init_static (&state_changes, G_VARIANT_TYPE ("a{sv}"));
152 : 10 : g_variant_builder_init_static (&adds, G_VARIANT_TYPE ("a{s(bgav)}"));
153 : :
154 : 10 : g_hash_table_iter_init (&iter, exporter->pending_changes);
155 : 34 : while (g_hash_table_iter_next (&iter, &key, &value))
156 : : {
157 : 14 : guint events = GPOINTER_TO_INT (value);
158 : 14 : const gchar *name = key;
159 : :
160 : : /* Adds and removes are incompatible with enabled or state
161 : : * changes, but we must report at least one event.
162 : : */
163 : 14 : g_assert (((events & (ACTION_ENABLED_CHANGED_EVENT | ACTION_STATE_CHANGED_EVENT)) == 0) !=
164 : : ((events & (ACTION_REMOVED_EVENT | ACTION_ADDED_EVENT)) == 0));
165 : :
166 : 14 : if (events & ACTION_REMOVED_EVENT)
167 : 1 : g_variant_builder_add (&removes, "s", name);
168 : :
169 : 14 : if (events & ACTION_ENABLED_CHANGED_EVENT)
170 : : {
171 : : gboolean enabled;
172 : :
173 : 1 : enabled = g_action_group_get_action_enabled (exporter->action_group, name);
174 : 1 : g_variant_builder_add (&enabled_changes, "{sb}", name, enabled);
175 : : }
176 : :
177 : 14 : if (events & ACTION_STATE_CHANGED_EVENT)
178 : : {
179 : : GVariant *state;
180 : :
181 : 5 : state = g_action_group_get_action_state (exporter->action_group, name);
182 : 5 : g_variant_builder_add (&state_changes, "{sv}", name, state);
183 : 5 : g_variant_unref (state);
184 : : }
185 : :
186 : 14 : if (events & ACTION_ADDED_EVENT)
187 : : {
188 : : GVariant *description;
189 : :
190 : 7 : description = g_action_group_describe_action (exporter->action_group, name);
191 : 7 : g_variant_builder_add (&adds, "{s@(bgav)}", name, description);
192 : : }
193 : : }
194 : :
195 : 10 : g_hash_table_remove_all (exporter->pending_changes);
196 : :
197 : 10 : g_dbus_connection_emit_signal (exporter->connection, NULL, exporter->object_path,
198 : : "org.gtk.Actions", "Changed",
199 : : g_variant_new ("(asa{sb}a{sv}a{s(bgav)})",
200 : : &removes, &enabled_changes,
201 : : &state_changes, &adds),
202 : : NULL);
203 : :
204 : 10 : exporter->pending_source = NULL;
205 : :
206 : 10 : return FALSE;
207 : : }
208 : :
209 : : static void
210 : 17 : g_action_group_exporter_flush_queue (GActionGroupExporter *exporter)
211 : : {
212 : 17 : if (exporter->pending_source)
213 : : {
214 : 0 : g_source_destroy (exporter->pending_source);
215 : 0 : g_action_group_exporter_dispatch_events (exporter);
216 : 0 : g_assert (exporter->pending_source == NULL);
217 : : }
218 : 17 : }
219 : :
220 : : static guint
221 : 100014 : g_action_group_exporter_get_events (GActionGroupExporter *exporter,
222 : : const gchar *name)
223 : : {
224 : 100014 : return (gsize) g_hash_table_lookup (exporter->pending_changes, name);
225 : : }
226 : :
227 : : static void
228 : 100014 : g_action_group_exporter_set_events (GActionGroupExporter *exporter,
229 : : const gchar *name,
230 : : guint events)
231 : : {
232 : : gboolean have_events;
233 : : gboolean is_queued;
234 : :
235 : 100014 : if (events != 0)
236 : 200028 : g_hash_table_insert (exporter->pending_changes, g_strdup (name), GINT_TO_POINTER (events));
237 : : else
238 : 0 : g_hash_table_remove (exporter->pending_changes, name);
239 : :
240 : 100014 : have_events = g_hash_table_size (exporter->pending_changes) > 0;
241 : 100014 : is_queued = exporter->pending_source != NULL;
242 : :
243 : 100014 : if (have_events && !is_queued)
244 : : {
245 : : GSource *source;
246 : :
247 : 100010 : source = g_idle_source_new ();
248 : 100010 : exporter->pending_source = source;
249 : 100010 : g_source_set_callback (source, g_action_group_exporter_dispatch_events, exporter, NULL);
250 : 100010 : g_source_set_static_name (source, "[gio] g_action_group_exporter_dispatch_events");
251 : 100010 : g_source_attach (source, exporter->context);
252 : 100010 : g_source_unref (source);
253 : : }
254 : :
255 : 100014 : if (!have_events && is_queued)
256 : : {
257 : 0 : g_source_destroy (exporter->pending_source);
258 : 0 : exporter->pending_source = NULL;
259 : : }
260 : 100014 : }
261 : :
262 : : static void
263 : 7 : g_action_group_exporter_action_added (GActionGroup *action_group,
264 : : const gchar *action_name,
265 : : gpointer user_data)
266 : : {
267 : 7 : GActionGroupExporter *exporter = user_data;
268 : : guint event_mask;
269 : :
270 : 7 : event_mask = g_action_group_exporter_get_events (exporter, action_name);
271 : :
272 : : /* The action is new, so we should not have any pending
273 : : * enabled-changed or state-changed signals for it.
274 : : */
275 : 7 : g_assert (~event_mask & (ACTION_STATE_CHANGED_EVENT |
276 : : ACTION_ENABLED_CHANGED_EVENT));
277 : :
278 : 7 : event_mask |= ACTION_ADDED_EVENT;
279 : :
280 : 7 : g_action_group_exporter_set_events (exporter, action_name, event_mask);
281 : 7 : }
282 : :
283 : : static void
284 : 1 : g_action_group_exporter_action_removed (GActionGroup *action_group,
285 : : const gchar *action_name,
286 : : gpointer user_data)
287 : : {
288 : 1 : GActionGroupExporter *exporter = user_data;
289 : : guint event_mask;
290 : :
291 : 1 : event_mask = g_action_group_exporter_get_events (exporter, action_name);
292 : :
293 : : /* If the add event for this is still queued then just cancel it since
294 : : * it's gone now.
295 : : *
296 : : * If the event was freshly added, there should not have been any
297 : : * enabled or state changed events.
298 : : */
299 : 1 : if (event_mask & ACTION_ADDED_EVENT)
300 : : {
301 : 0 : g_assert (~event_mask & ~(ACTION_STATE_CHANGED_EVENT | ACTION_ENABLED_CHANGED_EVENT));
302 : 0 : event_mask &= ~ACTION_ADDED_EVENT;
303 : : }
304 : :
305 : : /* Otherwise, queue a remove event. Drop any state or enabled changes
306 : : * that were queued before the remove. */
307 : : else
308 : : {
309 : 1 : event_mask |= ACTION_REMOVED_EVENT;
310 : 1 : event_mask &= ~(ACTION_STATE_CHANGED_EVENT | ACTION_ENABLED_CHANGED_EVENT);
311 : : }
312 : :
313 : 1 : g_action_group_exporter_set_events (exporter, action_name, event_mask);
314 : 1 : }
315 : :
316 : : static void
317 : 5 : g_action_group_exporter_action_state_changed (GActionGroup *action_group,
318 : : const gchar *action_name,
319 : : GVariant *value,
320 : : gpointer user_data)
321 : : {
322 : 5 : GActionGroupExporter *exporter = user_data;
323 : : guint event_mask;
324 : :
325 : 5 : event_mask = g_action_group_exporter_get_events (exporter, action_name);
326 : :
327 : : /* If it was removed, it must have been added back. Otherwise, why
328 : : * are we hearing about changes?
329 : : */
330 : 5 : g_assert (~event_mask & ACTION_REMOVED_EVENT ||
331 : : event_mask & ACTION_ADDED_EVENT);
332 : :
333 : : /* If it is freshly added, don't also bother with the state change
334 : : * signal since the updated state will be sent as part of the pending
335 : : * add message.
336 : : */
337 : 5 : if (~event_mask & ACTION_ADDED_EVENT)
338 : 5 : event_mask |= ACTION_STATE_CHANGED_EVENT;
339 : :
340 : 5 : g_action_group_exporter_set_events (exporter, action_name, event_mask);
341 : 5 : }
342 : :
343 : : static void
344 : 100001 : g_action_group_exporter_action_enabled_changed (GActionGroup *action_group,
345 : : const gchar *action_name,
346 : : gboolean enabled,
347 : : gpointer user_data)
348 : : {
349 : 100001 : GActionGroupExporter *exporter = user_data;
350 : : guint event_mask;
351 : :
352 : 100001 : event_mask = g_action_group_exporter_get_events (exporter, action_name);
353 : :
354 : : /* Reasoning as above. */
355 : 100001 : g_assert (~event_mask & ACTION_REMOVED_EVENT ||
356 : : event_mask & ACTION_ADDED_EVENT);
357 : :
358 : 100001 : if (~event_mask & ACTION_ADDED_EVENT)
359 : 100001 : event_mask |= ACTION_ENABLED_CHANGED_EVENT;
360 : :
361 : 100001 : g_action_group_exporter_set_events (exporter, action_name, event_mask);
362 : 100001 : }
363 : :
364 : : static void
365 : 17 : org_gtk_Actions_method_call (GDBusConnection *connection,
366 : : const gchar *sender,
367 : : const gchar *object_path,
368 : : const gchar *interface_name,
369 : : const gchar *method_name,
370 : : GVariant *parameters,
371 : : GDBusMethodInvocation *invocation,
372 : : gpointer user_data)
373 : : {
374 : 17 : GActionGroupExporter *exporter = user_data;
375 : 17 : GVariant *result = NULL;
376 : :
377 : 17 : g_action_group_exporter_flush_queue (exporter);
378 : :
379 : 17 : if (g_str_equal (method_name, "List"))
380 : : {
381 : : gchar **list;
382 : :
383 : 1 : list = g_action_group_list_actions (exporter->action_group);
384 : 1 : result = g_variant_new ("(^as)", list);
385 : 1 : g_strfreev (list);
386 : : }
387 : :
388 : 16 : else if (g_str_equal (method_name, "Describe"))
389 : : {
390 : : const gchar *name;
391 : : GVariant *desc;
392 : :
393 : 1 : g_variant_get (parameters, "(&s)", &name);
394 : :
395 : 1 : if (!g_action_group_has_action (exporter->action_group, name))
396 : : {
397 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
398 : : "The named action ('%s') does not exist.", name);
399 : 0 : return;
400 : : }
401 : :
402 : 1 : desc = g_action_group_describe_action (exporter->action_group, name);
403 : 1 : result = g_variant_new ("(@(bgav))", desc);
404 : : }
405 : :
406 : 15 : else if (g_str_equal (method_name, "DescribeAll"))
407 : : {
408 : : GVariantBuilder builder;
409 : : gchar **list;
410 : : gint i;
411 : :
412 : 2 : list = g_action_group_list_actions (exporter->action_group);
413 : 2 : g_variant_builder_init_static (&builder, G_VARIANT_TYPE ("a{s(bgav)}"));
414 : 9 : for (i = 0; list[i]; i++)
415 : : {
416 : 7 : const gchar *name = list[i];
417 : : GVariant *description;
418 : :
419 : 7 : description = g_action_group_describe_action (exporter->action_group, name);
420 : 7 : g_variant_builder_add (&builder, "{s@(bgav)}", name, description);
421 : : }
422 : 2 : result = g_variant_new ("(a{s(bgav)})", &builder);
423 : 2 : g_strfreev (list);
424 : : }
425 : :
426 : 13 : else if (g_str_equal (method_name, "Activate"))
427 : : {
428 : 8 : GVariant *parameter = NULL;
429 : : GVariant *platform_data;
430 : : GVariantIter *iter;
431 : : const gchar *name;
432 : 8 : const GVariantType *parameter_type = NULL;
433 : :
434 : 8 : g_variant_get (parameters, "(&sav@a{sv})", &name, &iter, &platform_data);
435 : 8 : g_variant_iter_next (iter, "v", ¶meter);
436 : 8 : g_variant_iter_free (iter);
437 : :
438 : : /* Check the action exists and the parameter type matches. */
439 : 8 : if (!g_action_group_query_action (exporter->action_group,
440 : : name, NULL, ¶meter_type,
441 : : NULL, NULL, NULL))
442 : : {
443 : 1 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
444 : : "Unknown action ‘%s’", name);
445 : 1 : g_clear_pointer (¶meter, g_variant_unref);
446 : 1 : g_variant_unref (platform_data);
447 : 4 : return;
448 : : }
449 : :
450 : 7 : if (!((parameter_type == NULL && parameter == NULL) ||
451 : 4 : (parameter_type != NULL && parameter != NULL && g_variant_is_of_type (parameter, parameter_type))))
452 : : {
453 : 4 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
454 : : "Invalid parameter for action ‘%s’: expected type %s but got type %s",
455 : : name,
456 : 3 : (parameter_type != NULL) ? (const gchar *) parameter_type : "()",
457 : 3 : (parameter != NULL) ? g_variant_get_type_string (parameter) : "()");
458 : 3 : g_clear_pointer (¶meter, g_variant_unref);
459 : 3 : g_variant_unref (platform_data);
460 : 3 : return;
461 : : }
462 : :
463 : 4 : if (G_IS_REMOTE_ACTION_GROUP (exporter->action_group))
464 : 0 : g_remote_action_group_activate_action_full (G_REMOTE_ACTION_GROUP (exporter->action_group),
465 : : name, parameter, platform_data);
466 : : else
467 : 4 : g_action_group_activate_action (exporter->action_group, name, parameter);
468 : :
469 : 4 : if (parameter)
470 : 1 : g_variant_unref (parameter);
471 : :
472 : 4 : g_variant_unref (platform_data);
473 : : }
474 : :
475 : 5 : else if (g_str_equal (method_name, "SetState"))
476 : : {
477 : : GVariant *platform_data;
478 : : const gchar *name;
479 : : GVariant *state;
480 : 5 : const GVariantType *state_type = NULL;
481 : :
482 : 5 : g_variant_get (parameters, "(&sv@a{sv})", &name, &state, &platform_data);
483 : :
484 : : /* Check the action exists and the state type matches. */
485 : 5 : if (!g_action_group_query_action (exporter->action_group,
486 : : name, NULL, NULL,
487 : : &state_type, NULL, NULL))
488 : : {
489 : 1 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
490 : : "Unknown action ‘%s’", name);
491 : 1 : g_variant_unref (state);
492 : 1 : g_variant_unref (platform_data);
493 : 3 : return;
494 : : }
495 : :
496 : 4 : if (state_type == NULL)
497 : : {
498 : 1 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
499 : : "Cannot change state of action ‘%s’ as it is stateless", name);
500 : 1 : g_variant_unref (state);
501 : 1 : g_variant_unref (platform_data);
502 : 1 : return;
503 : : }
504 : :
505 : 3 : if (!g_variant_is_of_type (state, state_type))
506 : : {
507 : 1 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
508 : : "Invalid state for action ‘%s’: expected type %s but got type %s",
509 : : name,
510 : : (const gchar *) state_type,
511 : : g_variant_get_type_string (state));
512 : 1 : g_variant_unref (state);
513 : 1 : g_variant_unref (platform_data);
514 : 1 : return;
515 : : }
516 : :
517 : 2 : if (G_IS_REMOTE_ACTION_GROUP (exporter->action_group))
518 : 0 : g_remote_action_group_change_action_state_full (G_REMOTE_ACTION_GROUP (exporter->action_group),
519 : : name, state, platform_data);
520 : : else
521 : 2 : g_action_group_change_action_state (exporter->action_group, name, state);
522 : :
523 : 2 : g_variant_unref (platform_data);
524 : 2 : g_variant_unref (state);
525 : : }
526 : :
527 : : else
528 : : g_assert_not_reached ();
529 : :
530 : 10 : g_dbus_method_invocation_return_value (invocation, result);
531 : : }
532 : :
533 : : static void
534 : 100039 : g_action_group_exporter_free (GActionGroupExporter *exporter)
535 : : {
536 : 100039 : g_signal_handlers_disconnect_by_func (exporter->action_group,
537 : : g_action_group_exporter_action_added, exporter);
538 : 100039 : g_signal_handlers_disconnect_by_func (exporter->action_group,
539 : : g_action_group_exporter_action_enabled_changed, exporter);
540 : 100039 : g_signal_handlers_disconnect_by_func (exporter->action_group,
541 : : g_action_group_exporter_action_state_changed, exporter);
542 : 100039 : g_signal_handlers_disconnect_by_func (exporter->action_group,
543 : : g_action_group_exporter_action_removed, exporter);
544 : :
545 : 100039 : g_hash_table_unref (exporter->pending_changes);
546 : 100039 : if (exporter->pending_source)
547 : 100000 : g_source_destroy (exporter->pending_source);
548 : :
549 : 100039 : g_main_context_unref (exporter->context);
550 : 100039 : g_object_unref (exporter->connection);
551 : 100039 : g_object_unref (exporter->action_group);
552 : 100039 : g_free (exporter->object_path);
553 : :
554 : 100039 : g_slice_free (GActionGroupExporter, exporter);
555 : 100039 : }
556 : :
557 : : /**
558 : : * g_dbus_connection_export_action_group:
559 : : * @connection: the D-Bus connection
560 : : * @object_path: a D-Bus object path
561 : : * @action_group: an action group
562 : : *
563 : : * Exports @action_group on @connection at @object_path.
564 : : *
565 : : * The implemented D-Bus API should be considered private. It is
566 : : * subject to change in the future.
567 : : *
568 : : * A given object path can only have one action group exported on it.
569 : : * If this constraint is violated, the export will fail and 0 will be
570 : : * returned (with @error set accordingly).
571 : : *
572 : : * You can unexport the action group using
573 : : * [method@Gio.DBusConnection.unexport_action_group] with the return value of
574 : : * this function.
575 : : *
576 : : * The thread default main context is taken at the time of this call.
577 : : * All incoming action activations and state change requests are
578 : : * reported from this context. Any changes on the action group that
579 : : * cause it to emit signals must also come from this same context.
580 : : * Since incoming action activations and state change requests are
581 : : * rather likely to cause changes on the action group, this effectively
582 : : * limits a given action group to being exported from only one main
583 : : * context.
584 : : *
585 : : * Returns: the ID of the export (never zero), or 0 in case of failure
586 : : *
587 : : * Since: 2.32
588 : : **/
589 : : guint
590 : 100039 : g_dbus_connection_export_action_group (GDBusConnection *connection,
591 : : const gchar *object_path,
592 : : GActionGroup *action_group,
593 : : GError **error)
594 : : {
595 : 100039 : const GDBusInterfaceVTable vtable = {
596 : : org_gtk_Actions_method_call, NULL, NULL, { 0 }
597 : : };
598 : : GActionGroupExporter *exporter;
599 : : guint id;
600 : :
601 : 100039 : if G_UNLIKELY (org_gtk_Actions == NULL)
602 : : {
603 : 7 : GError *my_error = NULL;
604 : : GDBusNodeInfo *info;
605 : :
606 : 7 : info = g_dbus_node_info_new_for_xml (org_gtk_Actions_xml, &my_error);
607 : 7 : if G_UNLIKELY (info == NULL)
608 : 0 : g_error ("%s", my_error->message);
609 : 7 : org_gtk_Actions = g_dbus_node_info_lookup_interface (info, "org.gtk.Actions");
610 : 7 : g_assert (org_gtk_Actions != NULL);
611 : 7 : g_dbus_interface_info_ref (org_gtk_Actions);
612 : 7 : g_dbus_node_info_unref (info);
613 : : }
614 : :
615 : 100039 : exporter = g_slice_new (GActionGroupExporter);
616 : 100039 : exporter->context = g_main_context_ref_thread_default ();
617 : 100039 : exporter->pending_changes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
618 : 100039 : exporter->pending_source = NULL;
619 : 100039 : exporter->action_group = g_object_ref (action_group);
620 : 100039 : exporter->connection = g_object_ref (connection);
621 : 100039 : exporter->object_path = g_strdup (object_path);
622 : :
623 : 100039 : id = g_dbus_connection_register_object (connection, object_path, org_gtk_Actions, &vtable,
624 : : exporter, (GDestroyNotify) g_action_group_exporter_free, error);
625 : :
626 : 100039 : if (id != 0)
627 : : {
628 : 100038 : g_signal_connect (action_group, "action-added",
629 : : G_CALLBACK (g_action_group_exporter_action_added), exporter);
630 : 100038 : g_signal_connect (action_group, "action-removed",
631 : : G_CALLBACK (g_action_group_exporter_action_removed), exporter);
632 : 100038 : g_signal_connect (action_group, "action-state-changed",
633 : : G_CALLBACK (g_action_group_exporter_action_state_changed), exporter);
634 : 100038 : g_signal_connect (action_group, "action-enabled-changed",
635 : : G_CALLBACK (g_action_group_exporter_action_enabled_changed), exporter);
636 : : }
637 : :
638 : 100039 : return id;
639 : : }
640 : :
641 : : /**
642 : : * g_dbus_connection_unexport_action_group:
643 : : * @connection: the D-Bus connection
644 : : * @export_id: the ID from [method@Gio.DBusConnection.export_action_group]
645 : : *
646 : : * Reverses the effect of a previous call to
647 : : * [method@Gio.DBusConnection.export_action_group].
648 : : *
649 : : * It is an error to call this function with an ID that wasn’t returned from
650 : : * [method@Gio.DBusConnection.export_action_group] or to call it with the same
651 : : * ID more than once.
652 : : *
653 : : * Since: 2.32
654 : : **/
655 : : void
656 : 100038 : g_dbus_connection_unexport_action_group (GDBusConnection *connection,
657 : : guint export_id)
658 : : {
659 : 100038 : g_dbus_connection_unregister_object (connection, export_id);
660 : 100038 : }
|