Branch data Line data Source code
1 : : /*
2 : : * Copyright © 2010, 2011, 2013, 2014 Codethink Limited
3 : : * Copyright © 2010, 2011, 2012, 2013, 2015 Red Hat, Inc.
4 : : * Copyright © 2012 Pavel Vasin
5 : : * Copyright © 2022 Endless OS Foundation, LLC
6 : : *
7 : : * SPDX-License-Identifier: LGPL-2.1-or-later
8 : : *
9 : : * This library is free software; you can redistribute it and/or
10 : : * modify it under the terms of the GNU Lesser General Public
11 : : * License as published by the Free Software Foundation; either
12 : : * version 2.1 of the License, or (at your option) any later version.
13 : : *
14 : : * This library is distributed in the hope that it will be useful,
15 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 : : * Lesser General Public License for more details.
18 : : *
19 : : * You should have received a copy of the GNU Lesser General
20 : : * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
21 : : *
22 : : * Authors: Ryan Lortie <desrt@desrt.ca>
23 : : */
24 : :
25 : : #include <gio/gio.h>
26 : : #include <stdlib.h>
27 : : #include <string.h>
28 : :
29 : : #include "gdbus-sessionbus.h"
30 : :
31 : : typedef struct
32 : : {
33 : : GVariant *params;
34 : : gboolean did_run;
35 : : } Activation;
36 : :
37 : : static void
38 : 4 : activate (GAction *action,
39 : : GVariant *parameter,
40 : : gpointer user_data)
41 : : {
42 : 4 : Activation *activation = user_data;
43 : :
44 : 4 : if (parameter)
45 : 1 : activation->params = g_variant_ref (parameter);
46 : : else
47 : 3 : activation->params = NULL;
48 : 4 : activation->did_run = TRUE;
49 : 4 : }
50 : :
51 : : static void
52 : 1 : test_basic (void)
53 : : {
54 : 1 : Activation a = { 0, };
55 : : GSimpleAction *action;
56 : : gchar *name;
57 : : GVariantType *parameter_type;
58 : : gboolean enabled;
59 : : GVariantType *state_type;
60 : : GVariant *state;
61 : :
62 : 1 : action = g_simple_action_new ("foo", NULL);
63 : 1 : g_assert_true (g_action_get_enabled (G_ACTION (action)));
64 : 1 : g_assert_null (g_action_get_parameter_type (G_ACTION (action)));
65 : 1 : g_assert_null (g_action_get_state_type (G_ACTION (action)));
66 : 1 : g_assert_null (g_action_get_state_hint (G_ACTION (action)));
67 : 1 : g_assert_null (g_action_get_state (G_ACTION (action)));
68 : 1 : g_object_get (action,
69 : : "name", &name,
70 : : "parameter-type", ¶meter_type,
71 : : "enabled", &enabled,
72 : : "state-type", &state_type,
73 : : "state", &state,
74 : : NULL);
75 : 1 : g_assert_cmpstr (name, ==, "foo");
76 : 1 : g_assert_null (parameter_type);
77 : 1 : g_assert_true (enabled);
78 : 1 : g_assert_null (state_type);
79 : 1 : g_assert_null (state);
80 : 1 : g_free (name);
81 : :
82 : 1 : g_signal_connect (action, "activate", G_CALLBACK (activate), &a);
83 : 1 : g_assert_false (a.did_run);
84 : 1 : g_action_activate (G_ACTION (action), NULL);
85 : 1 : g_assert_true (a.did_run);
86 : 1 : a.did_run = FALSE;
87 : :
88 : 1 : g_simple_action_set_enabled (action, FALSE);
89 : 1 : g_action_activate (G_ACTION (action), NULL);
90 : 1 : g_assert_false (a.did_run);
91 : :
92 : 1 : if (g_test_undefined ())
93 : : {
94 : 1 : g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
95 : : "*assertion*g_variant_is_of_type*failed*");
96 : 1 : g_action_activate (G_ACTION (action), g_variant_new_string ("xxx"));
97 : 1 : g_test_assert_expected_messages ();
98 : : }
99 : :
100 : 1 : g_object_unref (action);
101 : 1 : g_assert_false (a.did_run);
102 : :
103 : 1 : action = g_simple_action_new ("foo", G_VARIANT_TYPE_STRING);
104 : 1 : g_assert_true (g_action_get_enabled (G_ACTION (action)));
105 : 1 : g_assert_true (g_variant_type_equal (g_action_get_parameter_type (G_ACTION (action)), G_VARIANT_TYPE_STRING));
106 : 1 : g_assert_null (g_action_get_state_type (G_ACTION (action)));
107 : 1 : g_assert_null (g_action_get_state_hint (G_ACTION (action)));
108 : 1 : g_assert_null (g_action_get_state (G_ACTION (action)));
109 : :
110 : 1 : g_signal_connect (action, "activate", G_CALLBACK (activate), &a);
111 : 1 : g_assert_false (a.did_run);
112 : 1 : g_action_activate (G_ACTION (action), g_variant_new_string ("Hello world"));
113 : 1 : g_assert_true (a.did_run);
114 : 1 : g_assert_cmpstr (g_variant_get_string (a.params, NULL), ==, "Hello world");
115 : 1 : g_variant_unref (a.params);
116 : 1 : a.did_run = FALSE;
117 : :
118 : 1 : if (g_test_undefined ())
119 : : {
120 : 1 : g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
121 : : "*assertion*!= NULL*failed*");
122 : 1 : g_action_activate (G_ACTION (action), NULL);
123 : 1 : g_test_assert_expected_messages ();
124 : : }
125 : :
126 : 1 : g_object_unref (action);
127 : 1 : g_assert_false (a.did_run);
128 : 1 : }
129 : :
130 : : static void
131 : 1 : test_name (void)
132 : : {
133 : 1 : g_assert_false (g_action_name_is_valid (""));
134 : 1 : g_assert_false (g_action_name_is_valid ("("));
135 : 1 : g_assert_false (g_action_name_is_valid ("%abc"));
136 : 1 : g_assert_false (g_action_name_is_valid ("$x1"));
137 : 1 : g_assert_true (g_action_name_is_valid ("abc.def"));
138 : 1 : g_assert_true (g_action_name_is_valid ("ABC-DEF"));
139 : 1 : }
140 : :
141 : : static gboolean
142 : 8 : strv_strv_cmp (const gchar * const *a,
143 : : const gchar * const *b)
144 : : {
145 : : guint n;
146 : :
147 : 67 : for (n = 0; a[n] != NULL; n++)
148 : : {
149 : 59 : if (!g_strv_contains (b, a[n]))
150 : 0 : return FALSE;
151 : : }
152 : :
153 : 67 : for (n = 0; b[n] != NULL; n++)
154 : : {
155 : 59 : if (!g_strv_contains (a, b[n]))
156 : 0 : return FALSE;
157 : : }
158 : :
159 : 8 : return TRUE;
160 : : }
161 : :
162 : : static gboolean
163 : 1 : strv_set_equal (const gchar * const *strv, ...)
164 : : {
165 : : guint count;
166 : : va_list list;
167 : : const gchar *str;
168 : : gboolean res;
169 : :
170 : 1 : res = TRUE;
171 : 1 : count = 0;
172 : 1 : va_start (list, strv);
173 : : while (1)
174 : : {
175 : 3 : str = va_arg (list, const gchar *);
176 : 3 : if (str == NULL)
177 : 1 : break;
178 : 2 : if (!g_strv_contains (strv, str))
179 : : {
180 : 0 : res = FALSE;
181 : 0 : break;
182 : : }
183 : 2 : count++;
184 : : }
185 : 1 : va_end (list);
186 : :
187 : 1 : if (res)
188 : 1 : res = g_strv_length ((gchar**)strv) == count;
189 : :
190 : 1 : return res;
191 : : }
192 : :
193 : : static void
194 : 26 : ensure_state (GActionGroup *group,
195 : : const gchar *action_name,
196 : : const gchar *expected)
197 : : {
198 : : GVariant *value;
199 : : gchar *printed;
200 : :
201 : 26 : value = g_action_group_get_action_state (group, action_name);
202 : 26 : printed = g_variant_print (value, TRUE);
203 : 26 : g_variant_unref (value);
204 : :
205 : 26 : g_assert_cmpstr (printed, ==, expected);
206 : 26 : g_free (printed);
207 : 26 : }
208 : :
209 : : G_GNUC_BEGIN_IGNORE_DEPRECATIONS
210 : :
211 : : static void
212 : 1 : test_simple_group (void)
213 : : {
214 : : GSimpleActionGroup *group;
215 : 1 : Activation a = { 0, };
216 : : GSimpleAction *simple;
217 : : GAction *action;
218 : : gchar **actions;
219 : : GVariant *state;
220 : :
221 : 1 : simple = g_simple_action_new ("foo", NULL);
222 : 1 : g_signal_connect (simple, "activate", G_CALLBACK (activate), &a);
223 : 1 : g_assert_false (a.did_run);
224 : 1 : g_action_activate (G_ACTION (simple), NULL);
225 : 1 : g_assert_true (a.did_run);
226 : 1 : a.did_run = FALSE;
227 : :
228 : 1 : group = g_simple_action_group_new ();
229 : 1 : g_simple_action_group_insert (group, G_ACTION (simple));
230 : 1 : g_object_unref (simple);
231 : :
232 : 1 : g_assert_false (a.did_run);
233 : 1 : g_action_group_activate_action (G_ACTION_GROUP (group), "foo", NULL);
234 : 1 : g_assert_true (a.did_run);
235 : :
236 : 1 : simple = g_simple_action_new_stateful ("bar", G_VARIANT_TYPE_STRING, g_variant_new_string ("hihi"));
237 : 1 : g_simple_action_group_insert (group, G_ACTION (simple));
238 : 1 : g_object_unref (simple);
239 : :
240 : 1 : g_assert_true (g_action_group_has_action (G_ACTION_GROUP (group), "foo"));
241 : 1 : g_assert_true (g_action_group_has_action (G_ACTION_GROUP (group), "bar"));
242 : 1 : g_assert_false (g_action_group_has_action (G_ACTION_GROUP (group), "baz"));
243 : 1 : actions = g_action_group_list_actions (G_ACTION_GROUP (group));
244 : 1 : g_assert_cmpint (g_strv_length (actions), ==, 2);
245 : 1 : g_assert_true (strv_set_equal ((const gchar * const *) actions, "foo", "bar", NULL));
246 : 1 : g_strfreev (actions);
247 : 1 : g_assert_true (g_action_group_get_action_enabled (G_ACTION_GROUP (group), "foo"));
248 : 1 : g_assert_true (g_action_group_get_action_enabled (G_ACTION_GROUP (group), "bar"));
249 : 1 : g_assert_null (g_action_group_get_action_parameter_type (G_ACTION_GROUP (group), "foo"));
250 : 1 : g_assert_true (g_variant_type_equal (g_action_group_get_action_parameter_type (G_ACTION_GROUP (group), "bar"), G_VARIANT_TYPE_STRING));
251 : 1 : g_assert_null (g_action_group_get_action_state_type (G_ACTION_GROUP (group), "foo"));
252 : 1 : g_assert_true (g_variant_type_equal (g_action_group_get_action_state_type (G_ACTION_GROUP (group), "bar"), G_VARIANT_TYPE_STRING));
253 : 1 : g_assert_null (g_action_group_get_action_state_hint (G_ACTION_GROUP (group), "foo"));
254 : 1 : g_assert_null (g_action_group_get_action_state_hint (G_ACTION_GROUP (group), "bar"));
255 : 1 : g_assert_null (g_action_group_get_action_state (G_ACTION_GROUP (group), "foo"));
256 : 1 : state = g_action_group_get_action_state (G_ACTION_GROUP (group), "bar");
257 : 1 : g_assert_true (g_variant_type_equal (g_variant_get_type (state), G_VARIANT_TYPE_STRING));
258 : 1 : g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "hihi");
259 : 1 : g_variant_unref (state);
260 : :
261 : 1 : g_action_group_change_action_state (G_ACTION_GROUP (group), "bar", g_variant_new_string ("boo"));
262 : 1 : state = g_action_group_get_action_state (G_ACTION_GROUP (group), "bar");
263 : 1 : g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "boo");
264 : 1 : g_variant_unref (state);
265 : :
266 : 1 : action = g_simple_action_group_lookup (group, "bar");
267 : 1 : g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
268 : 1 : g_assert_false (g_action_group_get_action_enabled (G_ACTION_GROUP (group), "bar"));
269 : :
270 : 1 : g_simple_action_group_remove (group, "bar");
271 : 1 : action = g_simple_action_group_lookup (group, "foo");
272 : 1 : g_assert_cmpstr (g_action_get_name (action), ==, "foo");
273 : 1 : action = g_simple_action_group_lookup (group, "bar");
274 : 1 : g_assert_null (action);
275 : :
276 : 1 : simple = g_simple_action_new ("foo", NULL);
277 : 1 : g_simple_action_group_insert (group, G_ACTION (simple));
278 : 1 : g_object_unref (simple);
279 : :
280 : 1 : a.did_run = FALSE;
281 : 1 : g_object_unref (group);
282 : 1 : g_assert_false (a.did_run);
283 : 1 : }
284 : :
285 : : G_GNUC_END_IGNORE_DEPRECATIONS
286 : :
287 : : static void
288 : 1 : test_stateful (void)
289 : : {
290 : : GSimpleAction *action;
291 : : GVariant *state;
292 : :
293 : 1 : action = g_simple_action_new_stateful ("foo", NULL, g_variant_new_string ("hihi"));
294 : 1 : g_assert_true (g_action_get_enabled (G_ACTION (action)));
295 : 1 : g_assert_null (g_action_get_parameter_type (G_ACTION (action)));
296 : 1 : g_assert_null (g_action_get_state_hint (G_ACTION (action)));
297 : 1 : g_assert_true (g_variant_type_equal (g_action_get_state_type (G_ACTION (action)),
298 : : G_VARIANT_TYPE_STRING));
299 : 1 : state = g_action_get_state (G_ACTION (action));
300 : 1 : g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "hihi");
301 : 1 : g_variant_unref (state);
302 : :
303 : 1 : if (g_test_undefined ())
304 : : {
305 : 1 : GVariant *new_state = g_variant_ref_sink (g_variant_new_int32 (123));
306 : 1 : g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
307 : : "*assertion*g_variant_is_of_type*failed*");
308 : 1 : g_simple_action_set_state (action, new_state);
309 : 1 : g_test_assert_expected_messages ();
310 : 1 : g_variant_unref (new_state);
311 : : }
312 : :
313 : 1 : g_simple_action_set_state (action, g_variant_new_string ("hello"));
314 : 1 : state = g_action_get_state (G_ACTION (action));
315 : 1 : g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "hello");
316 : 1 : g_variant_unref (state);
317 : :
318 : 1 : g_object_unref (action);
319 : :
320 : 1 : action = g_simple_action_new ("foo", NULL);
321 : :
322 : 1 : if (g_test_undefined ())
323 : : {
324 : 1 : GVariant *new_state = g_variant_ref_sink (g_variant_new_int32 (123));
325 : 1 : g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
326 : : "*assertion*!= NULL*failed*");
327 : 1 : g_simple_action_set_state (action, new_state);
328 : 1 : g_test_assert_expected_messages ();
329 : 1 : g_variant_unref (new_state);
330 : : }
331 : :
332 : 1 : g_object_unref (action);
333 : 1 : }
334 : :
335 : : static void
336 : 1 : test_default_activate (void)
337 : : {
338 : : GSimpleAction *action;
339 : : GVariant *state;
340 : :
341 : : /* Test changing state via activation with parameter */
342 : 1 : action = g_simple_action_new_stateful ("foo", G_VARIANT_TYPE_STRING, g_variant_new_string ("hihi"));
343 : 1 : g_action_activate (G_ACTION (action), g_variant_new_string ("bye"));
344 : 1 : state = g_action_get_state (G_ACTION (action));
345 : 1 : g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "bye");
346 : 1 : g_variant_unref (state);
347 : 1 : g_object_unref (action);
348 : :
349 : : /* Test toggling a boolean action via activation with no parameter */
350 : 1 : action = g_simple_action_new_stateful ("foo", NULL, g_variant_new_boolean (FALSE));
351 : 1 : g_action_activate (G_ACTION (action), NULL);
352 : 1 : state = g_action_get_state (G_ACTION (action));
353 : 1 : g_assert_true (g_variant_get_boolean (state));
354 : 1 : g_variant_unref (state);
355 : : /* and back again */
356 : 1 : g_action_activate (G_ACTION (action), NULL);
357 : 1 : state = g_action_get_state (G_ACTION (action));
358 : 1 : g_assert_false (g_variant_get_boolean (state));
359 : 1 : g_variant_unref (state);
360 : 1 : g_object_unref (action);
361 : 1 : }
362 : :
363 : : static gboolean foo_activated = FALSE;
364 : : static gboolean bar_activated = FALSE;
365 : :
366 : : static void
367 : 1 : activate_foo (GSimpleAction *simple,
368 : : GVariant *parameter,
369 : : gpointer user_data)
370 : : {
371 : 1 : g_assert_true (user_data == GINT_TO_POINTER (123));
372 : 1 : g_assert_null (parameter);
373 : 1 : foo_activated = TRUE;
374 : 1 : }
375 : :
376 : : static void
377 : 1 : activate_bar (GSimpleAction *simple,
378 : : GVariant *parameter,
379 : : gpointer user_data)
380 : : {
381 : 1 : g_assert_true (user_data == GINT_TO_POINTER (123));
382 : 1 : g_assert_cmpstr (g_variant_get_string (parameter, NULL), ==, "param");
383 : 1 : bar_activated = TRUE;
384 : 1 : }
385 : :
386 : : static void
387 : 2 : change_volume_state (GSimpleAction *action,
388 : : GVariant *value,
389 : : gpointer user_data)
390 : : {
391 : : gint requested;
392 : :
393 : 2 : requested = g_variant_get_int32 (value);
394 : :
395 : : /* Volume only goes from 0 to 10 */
396 : 2 : if (0 <= requested && requested <= 10)
397 : 1 : g_simple_action_set_state (action, value);
398 : 2 : }
399 : :
400 : : G_GNUC_BEGIN_IGNORE_DEPRECATIONS
401 : :
402 : : static void
403 : 1 : test_entries (void)
404 : : {
405 : 1 : const GActionEntry entries[] = {
406 : : { "foo", activate_foo, NULL, NULL, NULL, { 0 } },
407 : : { "bar", activate_bar, "s", NULL, NULL, { 0 } },
408 : : { "toggle", NULL, NULL, "false", NULL, { 0 } },
409 : : { "volume", NULL, NULL, "0", change_volume_state, { 0 } },
410 : : };
411 : 1 : const GActionEntry entries2[] = {
412 : : { "foo", activate_foo, NULL, NULL, NULL, { 0 } },
413 : : { "bar", activate_bar, "s", NULL, NULL, { 0 } },
414 : : { NULL },
415 : : };
416 : : GSimpleActionGroup *actions;
417 : : GVariant *state;
418 : : GStrv names;
419 : :
420 : 1 : actions = g_simple_action_group_new ();
421 : 1 : g_simple_action_group_add_entries (actions, entries,
422 : : G_N_ELEMENTS (entries),
423 : : GINT_TO_POINTER (123));
424 : :
425 : 1 : g_assert_false (foo_activated);
426 : 1 : g_action_group_activate_action (G_ACTION_GROUP (actions), "foo", NULL);
427 : 1 : g_assert_true (foo_activated);
428 : 1 : foo_activated = FALSE;
429 : :
430 : 1 : g_assert_false (bar_activated);
431 : 1 : g_action_group_activate_action (G_ACTION_GROUP (actions), "bar",
432 : : g_variant_new_string ("param"));
433 : 1 : g_assert_true (bar_activated);
434 : 1 : g_assert_false (foo_activated);
435 : :
436 : 1 : if (g_test_undefined ())
437 : : {
438 : 1 : const GActionEntry bad_type = {
439 : : "bad-type", NULL, "ss", NULL, NULL, { 0 }
440 : : };
441 : 1 : const GActionEntry bad_state = {
442 : : "bad-state", NULL, NULL, "flse", NULL, { 0 }
443 : : };
444 : :
445 : 1 : g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
446 : : "*not a valid GVariant type string*");
447 : 1 : g_simple_action_group_add_entries (actions, &bad_type, 1, NULL);
448 : 1 : g_test_assert_expected_messages ();
449 : :
450 : 1 : g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
451 : : "*could not parse*");
452 : 1 : g_simple_action_group_add_entries (actions, &bad_state, 1, NULL);
453 : 1 : g_test_assert_expected_messages ();
454 : : }
455 : :
456 : 1 : state = g_action_group_get_action_state (G_ACTION_GROUP (actions), "volume");
457 : 1 : g_assert_cmpint (g_variant_get_int32 (state), ==, 0);
458 : 1 : g_variant_unref (state);
459 : :
460 : : /* should change */
461 : 1 : g_action_group_change_action_state (G_ACTION_GROUP (actions), "volume",
462 : : g_variant_new_int32 (7));
463 : 1 : state = g_action_group_get_action_state (G_ACTION_GROUP (actions), "volume");
464 : 1 : g_assert_cmpint (g_variant_get_int32 (state), ==, 7);
465 : 1 : g_variant_unref (state);
466 : :
467 : : /* should not change */
468 : 1 : g_action_group_change_action_state (G_ACTION_GROUP (actions), "volume",
469 : : g_variant_new_int32 (11));
470 : 1 : state = g_action_group_get_action_state (G_ACTION_GROUP (actions), "volume");
471 : 1 : g_assert_cmpint (g_variant_get_int32 (state), ==, 7);
472 : 1 : g_variant_unref (state);
473 : :
474 : 1 : names = g_action_group_list_actions (G_ACTION_GROUP (actions));
475 : 1 : g_assert_cmpuint (g_strv_length (names), ==, G_N_ELEMENTS (entries));
476 : 1 : g_strfreev (names);
477 : :
478 : 1 : g_action_map_remove_action_entries (G_ACTION_MAP (actions), entries, G_N_ELEMENTS (entries));
479 : 1 : names = g_action_group_list_actions (G_ACTION_GROUP (actions));
480 : 1 : g_assert_cmpuint (g_strv_length (names), ==, 0);
481 : 1 : g_strfreev (names);
482 : :
483 : : /* Check addition and removal of %NULL terminated array */
484 : 1 : g_action_map_add_action_entries (G_ACTION_MAP (actions), entries2, -1, NULL);
485 : 1 : names = g_action_group_list_actions (G_ACTION_GROUP (actions));
486 : 1 : g_assert_cmpuint (g_strv_length (names), ==, G_N_ELEMENTS (entries2) - 1);
487 : 1 : g_strfreev (names);
488 : 1 : g_action_map_remove_action_entries (G_ACTION_MAP (actions), entries2, -1);
489 : 1 : names = g_action_group_list_actions (G_ACTION_GROUP (actions));
490 : 1 : g_assert_cmpuint (g_strv_length (names), ==, 0);
491 : 1 : g_strfreev (names);
492 : :
493 : 1 : g_object_unref (actions);
494 : 1 : }
495 : :
496 : : G_GNUC_END_IGNORE_DEPRECATIONS
497 : :
498 : : static void
499 : 1 : test_parse_detailed (void)
500 : : {
501 : : struct {
502 : : const gchar *detailed;
503 : : const gchar *expected_name;
504 : : const gchar *expected_target;
505 : : const gchar *expected_error;
506 : : const gchar *detailed_roundtrip;
507 : 1 : } testcases[] = {
508 : : { "abc", "abc", NULL, NULL, "abc" },
509 : : { " abc", NULL, NULL, "invalid format", NULL },
510 : : { " abc", NULL, NULL, "invalid format", NULL },
511 : : { "abc:", NULL, NULL, "invalid format", NULL },
512 : : { ":abc", NULL, NULL, "invalid format", NULL },
513 : : { "abc(", NULL, NULL, "invalid format", NULL },
514 : : { "abc)", NULL, NULL, "invalid format", NULL },
515 : : { "(abc", NULL, NULL, "invalid format", NULL },
516 : : { ")abc", NULL, NULL, "invalid format", NULL },
517 : : { "abc::xyz", "abc", "'xyz'", NULL, "abc::xyz" },
518 : : { "abc('xyz')", "abc", "'xyz'", NULL, "abc::xyz" },
519 : : { "abc(42)", "abc", "42", NULL, "abc(42)" },
520 : : { "abc(int32 42)", "abc", "42", NULL, "abc(42)" },
521 : : { "abc(@i 42)", "abc", "42", NULL, "abc(42)" },
522 : : { "abc (42)", NULL, NULL, "invalid format", NULL },
523 : : { "abc(42abc)", NULL, NULL, "invalid character in number", NULL },
524 : : { "abc(42, 4)", "abc", "(42, 4)", "expected end of input", NULL },
525 : : { "abc(42,)", "abc", "(42,)", "expected end of input", NULL }
526 : : };
527 : : gsize i;
528 : :
529 : 19 : for (i = 0; i < G_N_ELEMENTS (testcases); i++)
530 : : {
531 : 18 : GError *error = NULL;
532 : : GVariant *target;
533 : : gboolean success;
534 : : gchar *name;
535 : :
536 : 18 : success = g_action_parse_detailed_name (testcases[i].detailed, &name, &target, &error);
537 : 18 : g_assert_true (success == (error == NULL));
538 : 18 : if (success && testcases[i].expected_error)
539 : 0 : g_error ("Unexpected success on '%s'. Expected error containing '%s'",
540 : : testcases[i].detailed, testcases[i].expected_error);
541 : :
542 : 18 : if (!success && !testcases[i].expected_error)
543 : 0 : g_error ("Unexpected failure on '%s': %s", testcases[i].detailed, error->message);
544 : :
545 : 18 : if (!success)
546 : : {
547 : 12 : if (!strstr (error->message, testcases[i].expected_error))
548 : 0 : g_error ("Failure message '%s' for string '%s' did not contained expected substring '%s'",
549 : : error->message, testcases[i].detailed, testcases[i].expected_error);
550 : :
551 : 12 : g_error_free (error);
552 : 12 : continue;
553 : : }
554 : :
555 : 6 : g_assert_cmpstr (name, ==, testcases[i].expected_name);
556 : 6 : g_assert_true ((target == NULL) == (testcases[i].expected_target == NULL));
557 : :
558 : 6 : if (success)
559 : : {
560 : : gchar *detailed;
561 : :
562 : 6 : detailed = g_action_print_detailed_name (name, target);
563 : 6 : g_assert_cmpstr (detailed, ==, testcases[i].detailed_roundtrip);
564 : 6 : g_free (detailed);
565 : : }
566 : :
567 : 6 : if (target)
568 : : {
569 : : GVariant *expected;
570 : :
571 : 5 : expected = g_variant_parse (NULL, testcases[i].expected_target, NULL, NULL, NULL);
572 : 5 : g_assert_true (expected);
573 : :
574 : 5 : g_assert_cmpvariant (expected, target);
575 : 5 : g_variant_unref (expected);
576 : 5 : g_variant_unref (target);
577 : : }
578 : :
579 : 6 : g_free (name);
580 : : }
581 : 1 : }
582 : :
583 : : GHashTable *activation_counts;
584 : :
585 : : static void
586 : 4 : count_activation (const gchar *action)
587 : : {
588 : : gint count;
589 : :
590 : 4 : if (activation_counts == NULL)
591 : 1 : activation_counts = g_hash_table_new (g_str_hash, g_str_equal);
592 : 4 : count = GPOINTER_TO_INT (g_hash_table_lookup (activation_counts, action));
593 : 4 : count++;
594 : 4 : g_hash_table_insert (activation_counts, (gpointer)action, GINT_TO_POINTER (count));
595 : :
596 : 4 : g_main_context_wakeup (NULL);
597 : 4 : }
598 : :
599 : : static gint
600 : 14 : activation_count (const gchar *action)
601 : : {
602 : 14 : if (activation_counts == NULL)
603 : 1 : return 0;
604 : :
605 : 13 : return GPOINTER_TO_INT (g_hash_table_lookup (activation_counts, action));
606 : : }
607 : :
608 : : static void
609 : 2 : activate_action (GSimpleAction *action, GVariant *parameter, gpointer user_data)
610 : : {
611 : 2 : count_activation (g_action_get_name (G_ACTION (action)));
612 : 2 : }
613 : :
614 : : static void
615 : 1 : activate_toggle (GSimpleAction *action, GVariant *parameter, gpointer user_data)
616 : : {
617 : : GVariant *old_state, *new_state;
618 : :
619 : 1 : count_activation (g_action_get_name (G_ACTION (action)));
620 : :
621 : 1 : old_state = g_action_get_state (G_ACTION (action));
622 : 1 : new_state = g_variant_new_boolean (!g_variant_get_boolean (old_state));
623 : 1 : g_simple_action_set_state (action, new_state);
624 : 1 : g_variant_unref (old_state);
625 : 1 : }
626 : :
627 : : static void
628 : 1 : activate_radio (GSimpleAction *action, GVariant *parameter, gpointer user_data)
629 : : {
630 : : GVariant *new_state;
631 : :
632 : 1 : count_activation (g_action_get_name (G_ACTION (action)));
633 : :
634 : 1 : new_state = g_variant_new_string (g_variant_get_string (parameter, NULL));
635 : 1 : g_simple_action_set_state (action, new_state);
636 : 1 : }
637 : :
638 : : static gboolean
639 : 8 : compare_action_groups (GActionGroup *a, GActionGroup *b)
640 : : {
641 : : gchar **alist;
642 : : gchar **blist;
643 : : gint i;
644 : : gboolean equal;
645 : : gboolean ares, bres;
646 : : gboolean aenabled, benabled;
647 : : const GVariantType *aparameter_type, *bparameter_type;
648 : : const GVariantType *astate_type, *bstate_type;
649 : : GVariant *astate_hint, *bstate_hint;
650 : : GVariant *astate, *bstate;
651 : :
652 : 8 : alist = g_action_group_list_actions (a);
653 : 8 : blist = g_action_group_list_actions (b);
654 : 8 : equal = strv_strv_cmp ((const gchar * const *) alist, (const gchar * const *) blist);
655 : :
656 : 67 : for (i = 0; equal && alist[i]; i++)
657 : : {
658 : 59 : ares = g_action_group_query_action (a, alist[i], &aenabled, &aparameter_type, &astate_type, &astate_hint, &astate);
659 : 59 : bres = g_action_group_query_action (b, alist[i], &benabled, &bparameter_type, &bstate_type, &bstate_hint, &bstate);
660 : :
661 : 59 : if (ares && bres)
662 : : {
663 : 59 : equal = equal && (aenabled == benabled);
664 : 59 : equal = equal && ((!aparameter_type && !bparameter_type) || g_variant_type_equal (aparameter_type, bparameter_type));
665 : 59 : equal = equal && ((!astate_type && !bstate_type) || g_variant_type_equal (astate_type, bstate_type));
666 : 59 : equal = equal && ((!astate_hint && !bstate_hint) || g_variant_equal (astate_hint, bstate_hint));
667 : 59 : equal = equal && ((!astate && !bstate) || g_variant_equal (astate, bstate));
668 : :
669 : 59 : if (astate_hint)
670 : 0 : g_variant_unref (astate_hint);
671 : 59 : if (bstate_hint)
672 : 0 : g_variant_unref (bstate_hint);
673 : 59 : if (astate)
674 : 19 : g_variant_unref (astate);
675 : 59 : if (bstate)
676 : 19 : g_variant_unref (bstate);
677 : : }
678 : : else
679 : 0 : equal = FALSE;
680 : : }
681 : :
682 : 8 : g_strfreev (alist);
683 : 8 : g_strfreev (blist);
684 : :
685 : 8 : return equal;
686 : : }
687 : :
688 : : static gboolean
689 : 1 : timeout_cb (gpointer user_data)
690 : : {
691 : 1 : gboolean *timed_out = user_data;
692 : :
693 : 1 : g_assert_false (*timed_out);
694 : 1 : *timed_out = TRUE;
695 : 1 : g_main_context_wakeup (NULL);
696 : :
697 : 1 : return G_SOURCE_REMOVE;
698 : : }
699 : :
700 : : static GActionEntry exported_entries[] = {
701 : : { "undo", activate_action, NULL, NULL, NULL, { 0 } },
702 : : { "redo", activate_action, NULL, NULL, NULL, { 0 } },
703 : : { "cut", activate_action, NULL, NULL, NULL, { 0 } },
704 : : { "copy", activate_action, NULL, NULL, NULL, { 0 } },
705 : : { "paste", activate_action, NULL, NULL, NULL, { 0 } },
706 : : { "bold", activate_toggle, NULL, "true", NULL, { 0 } },
707 : : { "lang", activate_radio, "s", "'latin'", NULL, { 0 } },
708 : : };
709 : :
710 : : static void
711 : 12 : async_result_cb (GObject *source,
712 : : GAsyncResult *res,
713 : : gpointer user_data)
714 : : {
715 : 12 : GAsyncResult **result_out = user_data;
716 : :
717 : 12 : g_assert_null (*result_out);
718 : 12 : *result_out = g_object_ref (res);
719 : :
720 : 12 : g_main_context_wakeup (NULL);
721 : 12 : }
722 : :
723 : : G_GNUC_BEGIN_IGNORE_DEPRECATIONS
724 : :
725 : : static void
726 : 9 : action_added_removed_cb (GActionGroup *action_group,
727 : : char *action_name,
728 : : gpointer user_data)
729 : : {
730 : 9 : guint *counter = user_data;
731 : :
732 : 9 : *counter = *counter + 1;
733 : 9 : g_main_context_wakeup (NULL);
734 : 9 : }
735 : :
736 : : static void
737 : 1 : action_enabled_changed_cb (GActionGroup *action_group,
738 : : char *action_name,
739 : : gboolean enabled,
740 : : gpointer user_data)
741 : : {
742 : 1 : guint *counter = user_data;
743 : :
744 : 1 : *counter = *counter + 1;
745 : 1 : g_main_context_wakeup (NULL);
746 : 1 : }
747 : :
748 : : static void
749 : 5 : action_state_changed_cb (GActionGroup *action_group,
750 : : char *action_name,
751 : : GVariant *value,
752 : : gpointer user_data)
753 : : {
754 : 5 : guint *counter = user_data;
755 : :
756 : 5 : *counter = *counter + 1;
757 : 5 : g_main_context_wakeup (NULL);
758 : 5 : }
759 : :
760 : : static void
761 : 1 : test_dbus_export (void)
762 : : {
763 : : GDBusConnection *bus;
764 : : GSimpleActionGroup *group;
765 : : GDBusActionGroup *proxy;
766 : : GSimpleAction *action;
767 : 1 : GError *error = NULL;
768 : : GVariant *v;
769 : : guint id;
770 : : gchar **actions;
771 : 1 : guint n_actions_added = 0, n_actions_enabled_changed = 0, n_actions_removed = 0, n_actions_state_changed = 0;
772 : : gulong added_signal_id, enabled_changed_signal_id, removed_signal_id, state_changed_signal_id;
773 : : gboolean enabled;
774 : : gchar *param;
775 : : GVariantIter *iter;
776 : 1 : GAsyncResult *async_result = NULL;
777 : :
778 : 1 : session_bus_up ();
779 : 1 : bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
780 : :
781 : 1 : group = g_simple_action_group_new ();
782 : 1 : g_simple_action_group_add_entries (group,
783 : : exported_entries,
784 : : G_N_ELEMENTS (exported_entries),
785 : : NULL);
786 : :
787 : 1 : id = g_dbus_connection_export_action_group (bus, "/", G_ACTION_GROUP (group), &error);
788 : 1 : g_assert_no_error (error);
789 : :
790 : 1 : proxy = g_dbus_action_group_get (bus, g_dbus_connection_get_unique_name (bus), "/");
791 : 1 : added_signal_id = g_signal_connect (proxy, "action-added", G_CALLBACK (action_added_removed_cb), &n_actions_added);
792 : 1 : enabled_changed_signal_id = g_signal_connect (proxy, "action-enabled-changed", G_CALLBACK (action_enabled_changed_cb), &n_actions_enabled_changed);
793 : 1 : removed_signal_id = g_signal_connect (proxy, "action-removed", G_CALLBACK (action_added_removed_cb), &n_actions_removed);
794 : 1 : state_changed_signal_id = g_signal_connect (proxy, "action-state-changed", G_CALLBACK (action_state_changed_cb), &n_actions_state_changed);
795 : :
796 : 1 : actions = g_action_group_list_actions (G_ACTION_GROUP (proxy));
797 : 1 : g_assert_cmpint (g_strv_length (actions), ==, 0);
798 : 1 : g_strfreev (actions);
799 : :
800 : : /* Actions are queried from the bus asynchronously after the first
801 : : * list_actions() call. Wait for the expected signals then check again. */
802 : 3 : while (n_actions_added < G_N_ELEMENTS (exported_entries))
803 : 2 : g_main_context_iteration (NULL, TRUE);
804 : :
805 : 1 : actions = g_action_group_list_actions (G_ACTION_GROUP (proxy));
806 : 1 : g_assert_cmpint (g_strv_length (actions), ==, G_N_ELEMENTS (exported_entries));
807 : 1 : g_strfreev (actions);
808 : :
809 : : /* check that calling "List" works too */
810 : 1 : g_dbus_connection_call (bus,
811 : : g_dbus_connection_get_unique_name (bus),
812 : : "/",
813 : : "org.gtk.Actions",
814 : : "List",
815 : : NULL,
816 : : NULL,
817 : : 0,
818 : : G_MAXINT,
819 : : NULL,
820 : : async_result_cb,
821 : : &async_result);
822 : :
823 : 4 : while (async_result == NULL)
824 : 3 : g_main_context_iteration (NULL, TRUE);
825 : :
826 : 1 : v = g_dbus_connection_call_finish (bus, async_result, &error);
827 : 1 : g_assert_no_error (error);
828 : 1 : g_assert_nonnull (v);
829 : 1 : g_variant_get (v, "(^a&s)", &actions);
830 : 1 : g_assert_cmpuint (g_strv_length (actions), ==, G_N_ELEMENTS (exported_entries));
831 : 1 : g_free (actions);
832 : 1 : g_variant_unref (v);
833 : 1 : g_clear_object (&async_result);
834 : :
835 : : /* check that calling "Describe" works */
836 : 1 : g_dbus_connection_call (bus,
837 : : g_dbus_connection_get_unique_name (bus),
838 : : "/",
839 : : "org.gtk.Actions",
840 : : "Describe",
841 : : g_variant_new ("(s)", "copy"),
842 : : NULL,
843 : : 0,
844 : : G_MAXINT,
845 : : NULL,
846 : : async_result_cb,
847 : : &async_result);
848 : :
849 : 4 : while (async_result == NULL)
850 : 3 : g_main_context_iteration (NULL, TRUE);
851 : :
852 : 1 : v = g_dbus_connection_call_finish (bus, async_result, &error);
853 : 1 : g_assert_no_error (error);
854 : 1 : g_assert_nonnull (v);
855 : : /* FIXME: there's an extra level of tuplelization in here */
856 : 1 : g_variant_get (v, "((bgav))", &enabled, ¶m, &iter);
857 : 1 : g_assert_true (enabled);
858 : 1 : g_assert_cmpstr (param, ==, "");
859 : 1 : g_assert_cmpint (g_variant_iter_n_children (iter), ==, 0);
860 : 1 : g_free (param);
861 : 1 : g_variant_iter_free (iter);
862 : 1 : g_variant_unref (v);
863 : 1 : g_clear_object (&async_result);
864 : :
865 : : /* check that activating a parameterless action over D-Bus works */
866 : 1 : g_assert_cmpint (activation_count ("undo"), ==, 0);
867 : :
868 : 1 : g_dbus_connection_call (bus,
869 : : g_dbus_connection_get_unique_name (bus),
870 : : "/",
871 : : "org.gtk.Actions",
872 : : "Activate",
873 : : g_variant_new ("(sava{sv})", "undo", NULL, NULL),
874 : : NULL,
875 : : 0,
876 : : G_MAXINT,
877 : : NULL,
878 : : async_result_cb,
879 : : &async_result);
880 : :
881 : 5 : while (async_result == NULL)
882 : 4 : g_main_context_iteration (NULL, TRUE);
883 : :
884 : 1 : v = g_dbus_connection_call_finish (bus, async_result, &error);
885 : 1 : g_assert_no_error (error);
886 : 1 : g_assert_nonnull (v);
887 : 1 : g_assert_true (g_variant_is_of_type (v, G_VARIANT_TYPE_UNIT));
888 : 1 : g_variant_unref (v);
889 : 1 : g_clear_object (&async_result);
890 : :
891 : 1 : g_assert_cmpint (activation_count ("undo"), ==, 1);
892 : :
893 : : /* check that activating a parameterful action over D-Bus works */
894 : 1 : g_assert_cmpint (activation_count ("lang"), ==, 0);
895 : 1 : ensure_state (G_ACTION_GROUP (group), "lang", "'latin'");
896 : :
897 : 1 : g_dbus_connection_call (bus,
898 : : g_dbus_connection_get_unique_name (bus),
899 : : "/",
900 : : "org.gtk.Actions",
901 : : "Activate",
902 : : g_variant_new ("(s@ava{sv})", "lang", g_variant_new_parsed ("[<'spanish'>]"), NULL),
903 : : NULL,
904 : : 0,
905 : : G_MAXINT,
906 : : NULL,
907 : : async_result_cb,
908 : : &async_result);
909 : :
910 : 5 : while (async_result == NULL)
911 : 4 : g_main_context_iteration (NULL, TRUE);
912 : :
913 : 1 : v = g_dbus_connection_call_finish (bus, async_result, &error);
914 : 1 : g_assert_no_error (error);
915 : 1 : g_assert_nonnull (v);
916 : 1 : g_assert_true (g_variant_is_of_type (v, G_VARIANT_TYPE_UNIT));
917 : 1 : g_variant_unref (v);
918 : 1 : g_clear_object (&async_result);
919 : :
920 : 1 : g_assert_cmpint (activation_count ("lang"), ==, 1);
921 : 1 : ensure_state (G_ACTION_GROUP (group), "lang", "'spanish'");
922 : :
923 : : /* check that various error conditions are rejected */
924 : : struct
925 : : {
926 : : const gchar *action_name;
927 : : GVariant *parameter; /* (owned floating) (nullable) */
928 : : }
929 : 3 : activate_error_conditions[] =
930 : : {
931 : : { "nope", NULL }, /* non-existent action */
932 : 1 : { "lang", g_variant_new_parsed ("[<@u 4>]") }, /* wrong parameter type */
933 : : { "lang", NULL }, /* parameter missing */
934 : 1 : { "undo", g_variant_new_parsed ("[<'silly'>]") }, /* extraneous parameter */
935 : : };
936 : :
937 : 5 : for (gsize i = 0; i < G_N_ELEMENTS (activate_error_conditions); i++)
938 : : {
939 : 4 : GVariant *parameter = g_steal_pointer (&activate_error_conditions[i].parameter);
940 : 4 : const gchar *type_string = (parameter != NULL) ? "(s@ava{sv})" : "(sava{sv})";
941 : :
942 : 4 : g_dbus_connection_call (bus,
943 : : g_dbus_connection_get_unique_name (bus),
944 : : "/",
945 : : "org.gtk.Actions",
946 : : "Activate",
947 : : g_variant_new (type_string,
948 : : activate_error_conditions[i].action_name,
949 : 4 : g_steal_pointer (¶meter),
950 : : NULL),
951 : : NULL,
952 : : 0,
953 : : G_MAXINT,
954 : : NULL,
955 : : async_result_cb,
956 : : &async_result);
957 : :
958 : 18 : while (async_result == NULL)
959 : 14 : g_main_context_iteration (NULL, TRUE);
960 : :
961 : 4 : v = g_dbus_connection_call_finish (bus, async_result, &error);
962 : 4 : g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
963 : 4 : g_assert_null (v);
964 : 4 : g_clear_error (&error);
965 : 4 : g_clear_object (&async_result);
966 : : }
967 : :
968 : : /* check that setting an action’s state over D-Bus works */
969 : 1 : g_assert_cmpint (activation_count ("lang"), ==, 1);
970 : 1 : ensure_state (G_ACTION_GROUP (group), "lang", "'spanish'");
971 : :
972 : 1 : g_dbus_connection_call (bus,
973 : : g_dbus_connection_get_unique_name (bus),
974 : : "/",
975 : : "org.gtk.Actions",
976 : : "SetState",
977 : : g_variant_new ("(sva{sv})", "lang", g_variant_new_string ("portuguese"), NULL),
978 : : NULL,
979 : : 0,
980 : : G_MAXINT,
981 : : NULL,
982 : : async_result_cb,
983 : : &async_result);
984 : :
985 : 5 : while (async_result == NULL)
986 : 4 : g_main_context_iteration (NULL, TRUE);
987 : :
988 : 1 : v = g_dbus_connection_call_finish (bus, async_result, &error);
989 : 1 : g_assert_no_error (error);
990 : 1 : g_assert_nonnull (v);
991 : 1 : g_assert_true (g_variant_is_of_type (v, G_VARIANT_TYPE_UNIT));
992 : 1 : g_variant_unref (v);
993 : 1 : g_clear_object (&async_result);
994 : :
995 : 1 : g_assert_cmpint (activation_count ("lang"), ==, 1);
996 : 1 : ensure_state (G_ACTION_GROUP (group), "lang", "'portuguese'");
997 : :
998 : : /* check that various error conditions are rejected */
999 : : struct
1000 : : {
1001 : : const gchar *action_name;
1002 : : GVariant *state; /* (owned floating) (not nullable) */
1003 : : }
1004 : 4 : set_state_error_conditions[] =
1005 : : {
1006 : 1 : { "nope", g_variant_new_string ("hello") }, /* non-existent action */
1007 : 1 : { "undo", g_variant_new_string ("not stateful") }, /* not a stateful action */
1008 : 1 : { "lang", g_variant_new_uint32 (3) }, /* wrong state type */
1009 : : };
1010 : :
1011 : 4 : for (gsize i = 0; i < G_N_ELEMENTS (set_state_error_conditions); i++)
1012 : : {
1013 : 3 : g_dbus_connection_call (bus,
1014 : : g_dbus_connection_get_unique_name (bus),
1015 : : "/",
1016 : : "org.gtk.Actions",
1017 : : "SetState",
1018 : : g_variant_new ("(s@va{sv})",
1019 : : set_state_error_conditions[i].action_name,
1020 : 3 : g_variant_new_variant (g_steal_pointer (&set_state_error_conditions[i].state)),
1021 : : NULL),
1022 : : NULL,
1023 : : 0,
1024 : : G_MAXINT,
1025 : : NULL,
1026 : : async_result_cb,
1027 : : &async_result);
1028 : :
1029 : 14 : while (async_result == NULL)
1030 : 11 : g_main_context_iteration (NULL, TRUE);
1031 : :
1032 : 3 : v = g_dbus_connection_call_finish (bus, async_result, &error);
1033 : 3 : g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
1034 : 3 : g_assert_null (v);
1035 : 3 : g_clear_error (&error);
1036 : 3 : g_clear_object (&async_result);
1037 : : }
1038 : :
1039 : : /* test that the initial transfer works */
1040 : 1 : g_assert_true (G_IS_DBUS_ACTION_GROUP (proxy));
1041 : 1 : while (!compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)))
1042 : 0 : g_main_context_iteration (NULL, TRUE);
1043 : 1 : n_actions_state_changed = 0;
1044 : :
1045 : : /* test that various changes get propagated from group to proxy */
1046 : 1 : n_actions_added = 0;
1047 : 1 : action = g_simple_action_new_stateful ("italic", NULL, g_variant_new_boolean (FALSE));
1048 : 1 : g_simple_action_group_insert (group, G_ACTION (action));
1049 : 1 : g_object_unref (action);
1050 : :
1051 : 3 : while (n_actions_added == 0)
1052 : 2 : g_main_context_iteration (NULL, TRUE);
1053 : :
1054 : 1 : g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
1055 : :
1056 : 1 : action = G_SIMPLE_ACTION (g_simple_action_group_lookup (group, "cut"));
1057 : 1 : g_simple_action_set_enabled (action, FALSE);
1058 : :
1059 : 3 : while (n_actions_enabled_changed == 0)
1060 : 2 : g_main_context_iteration (NULL, TRUE);
1061 : :
1062 : 1 : g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
1063 : :
1064 : 1 : action = G_SIMPLE_ACTION (g_simple_action_group_lookup (group, "bold"));
1065 : 1 : g_simple_action_set_state (action, g_variant_new_boolean (FALSE));
1066 : :
1067 : 3 : while (n_actions_state_changed == 0)
1068 : 2 : g_main_context_iteration (NULL, TRUE);
1069 : :
1070 : 1 : g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
1071 : :
1072 : 1 : g_simple_action_group_remove (group, "italic");
1073 : :
1074 : 3 : while (n_actions_removed == 0)
1075 : 2 : g_main_context_iteration (NULL, TRUE);
1076 : :
1077 : 1 : g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
1078 : :
1079 : : /* test that activations and state changes propagate the other way */
1080 : 1 : n_actions_state_changed = 0;
1081 : 1 : g_assert_cmpint (activation_count ("copy"), ==, 0);
1082 : 1 : g_action_group_activate_action (G_ACTION_GROUP (proxy), "copy", NULL);
1083 : :
1084 : 3 : while (activation_count ("copy") == 0)
1085 : 2 : g_main_context_iteration (NULL, TRUE);
1086 : :
1087 : 1 : g_assert_cmpint (activation_count ("copy"), ==, 1);
1088 : 1 : g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
1089 : :
1090 : 1 : n_actions_state_changed = 0;
1091 : 1 : g_assert_cmpint (activation_count ("bold"), ==, 0);
1092 : 1 : g_action_group_activate_action (G_ACTION_GROUP (proxy), "bold", NULL);
1093 : :
1094 : 5 : while (n_actions_state_changed == 0)
1095 : 4 : g_main_context_iteration (NULL, TRUE);
1096 : :
1097 : 1 : g_assert_cmpint (activation_count ("bold"), ==, 1);
1098 : 1 : g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
1099 : 1 : v = g_action_group_get_action_state (G_ACTION_GROUP (group), "bold");
1100 : 1 : g_assert_true (g_variant_get_boolean (v));
1101 : 1 : g_variant_unref (v);
1102 : :
1103 : 1 : n_actions_state_changed = 0;
1104 : 1 : g_action_group_change_action_state (G_ACTION_GROUP (proxy), "bold", g_variant_new_boolean (FALSE));
1105 : :
1106 : 5 : while (n_actions_state_changed == 0)
1107 : 4 : g_main_context_iteration (NULL, TRUE);
1108 : :
1109 : 1 : g_assert_cmpint (activation_count ("bold"), ==, 1);
1110 : 1 : g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
1111 : 1 : v = g_action_group_get_action_state (G_ACTION_GROUP (group), "bold");
1112 : 1 : g_assert_false (g_variant_get_boolean (v));
1113 : 1 : g_variant_unref (v);
1114 : :
1115 : 1 : g_dbus_connection_unexport_action_group (bus, id);
1116 : :
1117 : 1 : g_signal_handler_disconnect (proxy, added_signal_id);
1118 : 1 : g_signal_handler_disconnect (proxy, enabled_changed_signal_id);
1119 : 1 : g_signal_handler_disconnect (proxy, removed_signal_id);
1120 : 1 : g_signal_handler_disconnect (proxy, state_changed_signal_id);
1121 : 1 : g_object_unref (proxy);
1122 : 1 : g_object_unref (group);
1123 : 1 : g_object_unref (bus);
1124 : :
1125 : 1 : session_bus_down ();
1126 : 1 : }
1127 : :
1128 : : static void
1129 : 1 : test_dbus_export_error_handling (void)
1130 : : {
1131 : 1 : GDBusConnection *bus = NULL;
1132 : 1 : GSimpleActionGroup *group = NULL;
1133 : 1 : GError *local_error = NULL;
1134 : : guint id1, id2;
1135 : :
1136 : 1 : g_test_summary ("Test that error handling of action group export failure works");
1137 : 1 : g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/3366");
1138 : :
1139 : 1 : session_bus_up ();
1140 : 1 : bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
1141 : :
1142 : 1 : group = g_simple_action_group_new ();
1143 : 1 : g_simple_action_group_add_entries (group,
1144 : : exported_entries,
1145 : : G_N_ELEMENTS (exported_entries),
1146 : : NULL);
1147 : :
1148 : 1 : id1 = g_dbus_connection_export_action_group (bus, "/", G_ACTION_GROUP (group), &local_error);
1149 : 1 : g_assert_no_error (local_error);
1150 : 1 : g_assert_cmpuint (id1, !=, 0);
1151 : :
1152 : : /* Trigger a failure by trying to export on a path which is already in use */
1153 : 1 : id2 = g_dbus_connection_export_action_group (bus, "/", G_ACTION_GROUP (group), &local_error);
1154 : 1 : g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS);
1155 : 1 : g_assert_cmpuint (id2, ==, 0);
1156 : 1 : g_clear_error (&local_error);
1157 : :
1158 : 1 : g_dbus_connection_unexport_action_group (bus, id1);
1159 : :
1160 : 2 : while (g_main_context_iteration (NULL, FALSE));
1161 : :
1162 : 1 : g_object_unref (group);
1163 : 1 : g_object_unref (bus);
1164 : :
1165 : 1 : session_bus_down ();
1166 : 1 : }
1167 : :
1168 : : static gpointer
1169 : 10 : do_export (gpointer data)
1170 : : {
1171 : 10 : GActionGroup *group = data;
1172 : : GMainContext *ctx;
1173 : : gint i;
1174 : 10 : GError *error = NULL;
1175 : : guint id;
1176 : : GDBusConnection *bus;
1177 : : GAction *action;
1178 : : gchar *path;
1179 : :
1180 : 10 : ctx = g_main_context_new ();
1181 : :
1182 : 10 : g_main_context_push_thread_default (ctx);
1183 : :
1184 : 10 : bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
1185 : 10 : path = g_strdup_printf("/%p", data);
1186 : :
1187 : 100010 : for (i = 0; i < 10000; i++)
1188 : : {
1189 : 100000 : id = g_dbus_connection_export_action_group (bus, path, G_ACTION_GROUP (group), &error);
1190 : 100000 : g_assert_no_error (error);
1191 : :
1192 : 100000 : action = g_simple_action_group_lookup (G_SIMPLE_ACTION_GROUP (group), "a");
1193 : 100000 : g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
1194 : 100000 : !g_action_get_enabled (action));
1195 : :
1196 : 100000 : g_dbus_connection_unexport_action_group (bus, id);
1197 : :
1198 : 200000 : while (g_main_context_iteration (ctx, FALSE));
1199 : : }
1200 : :
1201 : 10 : g_free (path);
1202 : 10 : g_object_unref (bus);
1203 : :
1204 : 10 : g_main_context_pop_thread_default (ctx);
1205 : :
1206 : 10 : g_main_context_unref (ctx);
1207 : :
1208 : 10 : return NULL;
1209 : : }
1210 : :
1211 : : static void
1212 : 1 : test_dbus_threaded (void)
1213 : : {
1214 : : GSimpleActionGroup *group[10];
1215 : : GThread *export[10];
1216 : : static GActionEntry entries[] = {
1217 : : { "a", activate_action, NULL, NULL, NULL, { 0 } },
1218 : : { "b", activate_action, NULL, NULL, NULL, { 0 } },
1219 : : };
1220 : : gint i;
1221 : :
1222 : 1 : session_bus_up ();
1223 : :
1224 : 11 : for (i = 0; i < 10; i++)
1225 : : {
1226 : 10 : group[i] = g_simple_action_group_new ();
1227 : 10 : g_simple_action_group_add_entries (group[i], entries, G_N_ELEMENTS (entries), NULL);
1228 : 10 : export[i] = g_thread_new ("export", do_export, group[i]);
1229 : : }
1230 : :
1231 : 11 : for (i = 0; i < 10; i++)
1232 : 10 : g_thread_join (export[i]);
1233 : :
1234 : 11 : for (i = 0; i < 10; i++)
1235 : 10 : g_object_unref (group[i]);
1236 : :
1237 : 1 : session_bus_down ();
1238 : 1 : }
1239 : :
1240 : : G_GNUC_END_IGNORE_DEPRECATIONS
1241 : :
1242 : : static void
1243 : 1 : test_bug679509 (void)
1244 : : {
1245 : : GDBusConnection *bus;
1246 : : GDBusActionGroup *proxy;
1247 : 1 : gboolean timed_out = FALSE;
1248 : :
1249 : 1 : session_bus_up ();
1250 : 1 : bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
1251 : :
1252 : 1 : proxy = g_dbus_action_group_get (bus, g_dbus_connection_get_unique_name (bus), "/");
1253 : 1 : g_strfreev (g_action_group_list_actions (G_ACTION_GROUP (proxy)));
1254 : 1 : g_object_unref (proxy);
1255 : :
1256 : 1 : g_timeout_add (100, timeout_cb, &timed_out);
1257 : 4 : while (!timed_out)
1258 : 3 : g_main_context_iteration (NULL, TRUE);
1259 : :
1260 : 1 : g_object_unref (bus);
1261 : :
1262 : 1 : session_bus_down ();
1263 : 1 : }
1264 : :
1265 : : static gchar *state_change_log;
1266 : :
1267 : : static void
1268 : 17 : state_changed (GActionGroup *group,
1269 : : const gchar *action_name,
1270 : : GVariant *value,
1271 : : gpointer user_data)
1272 : : {
1273 : : GString *string;
1274 : :
1275 : 17 : g_assert_false (state_change_log);
1276 : :
1277 : 17 : string = g_string_new (action_name);
1278 : : g_string_append_c (string, ':');
1279 : 17 : g_variant_print_string (value, string, TRUE);
1280 : 17 : state_change_log = g_string_free (string, FALSE);
1281 : 17 : }
1282 : :
1283 : : static void
1284 : 18 : verify_changed (const gchar *log_entry)
1285 : : {
1286 : 18 : g_assert_cmpstr (state_change_log, ==, log_entry);
1287 : 18 : g_clear_pointer (&state_change_log, g_free);
1288 : 18 : }
1289 : :
1290 : : static void
1291 : 1 : test_property_actions (void)
1292 : : {
1293 : : GSimpleActionGroup *group;
1294 : : GPropertyAction *action;
1295 : : GSocketClient *client;
1296 : : GApplication *app;
1297 : : gchar *name;
1298 : : GVariantType *ptype, *stype;
1299 : : gboolean enabled;
1300 : : GVariant *state;
1301 : :
1302 : 1 : group = g_simple_action_group_new ();
1303 : 1 : g_signal_connect (group, "action-state-changed", G_CALLBACK (state_changed), NULL);
1304 : :
1305 : 1 : client = g_socket_client_new ();
1306 : 1 : app = g_application_new ("org.gtk.test", 0);
1307 : :
1308 : : /* string... */
1309 : 1 : action = g_property_action_new ("app-id", app, "application-id");
1310 : 1 : g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
1311 : 1 : g_object_unref (action);
1312 : :
1313 : : /* uint... */
1314 : 1 : action = g_property_action_new ("keepalive", app, "inactivity-timeout");
1315 : 1 : g_object_get (action, "name", &name, "parameter-type", &ptype, "enabled", &enabled, "state-type", &stype, "state", &state, NULL);
1316 : 1 : g_assert_cmpstr (name, ==, "keepalive");
1317 : 1 : g_assert_true (enabled);
1318 : 1 : g_free (name);
1319 : 1 : g_variant_type_free (ptype);
1320 : 1 : g_variant_type_free (stype);
1321 : 1 : g_variant_unref (state);
1322 : :
1323 : 1 : g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
1324 : 1 : g_object_unref (action);
1325 : :
1326 : : /* bool... */
1327 : 1 : action = g_property_action_new ("tls", client, "tls");
1328 : 1 : g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
1329 : 1 : g_object_unref (action);
1330 : :
1331 : : /* inverted */
1332 : 1 : action = g_object_new (G_TYPE_PROPERTY_ACTION,
1333 : : "name", "disable-proxy",
1334 : : "object", client,
1335 : : "property-name", "enable-proxy",
1336 : : "invert-boolean", TRUE,
1337 : : NULL);
1338 : 1 : g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
1339 : 1 : g_object_unref (action);
1340 : :
1341 : : /* enum... */
1342 : 1 : action = g_property_action_new ("type", client, "type");
1343 : 1 : g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
1344 : 1 : g_object_unref (action);
1345 : :
1346 : : /* the objects should be held alive by the actions... */
1347 : 1 : g_object_unref (client);
1348 : 1 : g_object_unref (app);
1349 : :
1350 : 1 : ensure_state (G_ACTION_GROUP (group), "app-id", "'org.gtk.test'");
1351 : 1 : ensure_state (G_ACTION_GROUP (group), "keepalive", "uint32 0");
1352 : 1 : ensure_state (G_ACTION_GROUP (group), "tls", "false");
1353 : 1 : ensure_state (G_ACTION_GROUP (group), "disable-proxy", "false");
1354 : 1 : ensure_state (G_ACTION_GROUP (group), "type", "'stream'");
1355 : :
1356 : 1 : verify_changed (NULL);
1357 : :
1358 : : /* some string tests... */
1359 : 1 : g_action_group_change_action_state (G_ACTION_GROUP (group), "app-id", g_variant_new ("s", "org.gtk.test2"));
1360 : 1 : verify_changed ("app-id:'org.gtk.test2'");
1361 : 1 : g_assert_cmpstr (g_application_get_application_id (app), ==, "org.gtk.test2");
1362 : 1 : ensure_state (G_ACTION_GROUP (group), "app-id", "'org.gtk.test2'");
1363 : :
1364 : 1 : g_action_group_activate_action (G_ACTION_GROUP (group), "app-id", g_variant_new ("s", "org.gtk.test3"));
1365 : 1 : verify_changed ("app-id:'org.gtk.test3'");
1366 : 1 : g_assert_cmpstr (g_application_get_application_id (app), ==, "org.gtk.test3");
1367 : 1 : ensure_state (G_ACTION_GROUP (group), "app-id", "'org.gtk.test3'");
1368 : :
1369 : 1 : g_application_set_application_id (app, "org.gtk.test");
1370 : 1 : verify_changed ("app-id:'org.gtk.test'");
1371 : 1 : ensure_state (G_ACTION_GROUP (group), "app-id", "'org.gtk.test'");
1372 : :
1373 : : /* uint tests */
1374 : 1 : g_action_group_change_action_state (G_ACTION_GROUP (group), "keepalive", g_variant_new ("u", 1234));
1375 : 1 : verify_changed ("keepalive:uint32 1234");
1376 : 1 : g_assert_cmpuint (g_application_get_inactivity_timeout (app), ==, 1234);
1377 : 1 : ensure_state (G_ACTION_GROUP (group), "keepalive", "uint32 1234");
1378 : :
1379 : 1 : g_action_group_activate_action (G_ACTION_GROUP (group), "keepalive", g_variant_new ("u", 5678));
1380 : 1 : verify_changed ("keepalive:uint32 5678");
1381 : 1 : g_assert_cmpuint (g_application_get_inactivity_timeout (app), ==, 5678);
1382 : 1 : ensure_state (G_ACTION_GROUP (group), "keepalive", "uint32 5678");
1383 : :
1384 : 1 : g_application_set_inactivity_timeout (app, 0);
1385 : 1 : verify_changed ("keepalive:uint32 0");
1386 : 1 : ensure_state (G_ACTION_GROUP (group), "keepalive", "uint32 0");
1387 : :
1388 : : /* bool tests */
1389 : 1 : g_action_group_change_action_state (G_ACTION_GROUP (group), "tls", g_variant_new ("b", TRUE));
1390 : 1 : verify_changed ("tls:true");
1391 : 1 : g_assert_true (g_socket_client_get_tls (client));
1392 : 1 : ensure_state (G_ACTION_GROUP (group), "tls", "true");
1393 : :
1394 : 1 : g_action_group_change_action_state (G_ACTION_GROUP (group), "disable-proxy", g_variant_new ("b", TRUE));
1395 : 1 : verify_changed ("disable-proxy:true");
1396 : 1 : ensure_state (G_ACTION_GROUP (group), "disable-proxy", "true");
1397 : 1 : g_assert_false (g_socket_client_get_enable_proxy (client));
1398 : :
1399 : : /* test toggle true->false */
1400 : 1 : g_action_group_activate_action (G_ACTION_GROUP (group), "tls", NULL);
1401 : 1 : verify_changed ("tls:false");
1402 : 1 : g_assert_false (g_socket_client_get_tls (client));
1403 : 1 : ensure_state (G_ACTION_GROUP (group), "tls", "false");
1404 : :
1405 : : /* and now back false->true */
1406 : 1 : g_action_group_activate_action (G_ACTION_GROUP (group), "tls", NULL);
1407 : 1 : verify_changed ("tls:true");
1408 : 1 : g_assert_true (g_socket_client_get_tls (client));
1409 : 1 : ensure_state (G_ACTION_GROUP (group), "tls", "true");
1410 : :
1411 : 1 : g_socket_client_set_tls (client, FALSE);
1412 : 1 : verify_changed ("tls:false");
1413 : 1 : ensure_state (G_ACTION_GROUP (group), "tls", "false");
1414 : :
1415 : : /* now do the same for the inverted action */
1416 : 1 : g_action_group_activate_action (G_ACTION_GROUP (group), "disable-proxy", NULL);
1417 : 1 : verify_changed ("disable-proxy:false");
1418 : 1 : g_assert_true (g_socket_client_get_enable_proxy (client));
1419 : 1 : ensure_state (G_ACTION_GROUP (group), "disable-proxy", "false");
1420 : :
1421 : 1 : g_action_group_activate_action (G_ACTION_GROUP (group), "disable-proxy", NULL);
1422 : 1 : verify_changed ("disable-proxy:true");
1423 : 1 : g_assert_false (g_socket_client_get_enable_proxy (client));
1424 : 1 : ensure_state (G_ACTION_GROUP (group), "disable-proxy", "true");
1425 : :
1426 : 1 : g_socket_client_set_enable_proxy (client, TRUE);
1427 : 1 : verify_changed ("disable-proxy:false");
1428 : 1 : ensure_state (G_ACTION_GROUP (group), "disable-proxy", "false");
1429 : :
1430 : : /* enum tests */
1431 : 1 : g_action_group_change_action_state (G_ACTION_GROUP (group), "type", g_variant_new ("s", "datagram"));
1432 : 1 : verify_changed ("type:'datagram'");
1433 : 1 : g_assert_cmpint (g_socket_client_get_socket_type (client), ==, G_SOCKET_TYPE_DATAGRAM);
1434 : 1 : ensure_state (G_ACTION_GROUP (group), "type", "'datagram'");
1435 : :
1436 : 1 : g_action_group_activate_action (G_ACTION_GROUP (group), "type", g_variant_new ("s", "stream"));
1437 : 1 : verify_changed ("type:'stream'");
1438 : 1 : g_assert_cmpint (g_socket_client_get_socket_type (client), ==, G_SOCKET_TYPE_STREAM);
1439 : 1 : ensure_state (G_ACTION_GROUP (group), "type", "'stream'");
1440 : :
1441 : 1 : g_socket_client_set_socket_type (client, G_SOCKET_TYPE_SEQPACKET);
1442 : 1 : verify_changed ("type:'seqpacket'");
1443 : 1 : ensure_state (G_ACTION_GROUP (group), "type", "'seqpacket'");
1444 : :
1445 : : /* Check some error cases... */
1446 : 1 : g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_CRITICAL, "*non-existent*");
1447 : 1 : action = g_property_action_new ("foo", app, "xyz");
1448 : 1 : g_test_assert_expected_messages ();
1449 : 1 : g_object_unref (action);
1450 : :
1451 : 1 : g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_CRITICAL, "*writable*");
1452 : 1 : action = g_property_action_new ("foo", app, "is-registered");
1453 : 1 : g_test_assert_expected_messages ();
1454 : 1 : g_object_unref (action);
1455 : :
1456 : 1 : g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_CRITICAL, "*type 'GSocketAddress'*");
1457 : 1 : action = g_property_action_new ("foo", client, "local-address");
1458 : 1 : g_test_assert_expected_messages ();
1459 : 1 : g_object_unref (action);
1460 : :
1461 : 1 : g_object_unref (group);
1462 : 1 : }
1463 : :
1464 : : static void
1465 : 1 : test_property_actions_no_properties (void)
1466 : : {
1467 : : GPropertyAction *action;
1468 : :
1469 : 1 : g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_CRITICAL, "*Attempted to use an empty property name for GPropertyAction*");
1470 : 1 : action = (GPropertyAction*) g_object_new_with_properties (G_TYPE_PROPERTY_ACTION, 0, NULL, NULL);
1471 : :
1472 : 1 : g_test_assert_expected_messages ();
1473 : 1 : g_assert_true (G_IS_PROPERTY_ACTION (action));
1474 : :
1475 : 1 : g_object_unref (action);
1476 : 1 : }
1477 : :
1478 : : int
1479 : 1 : main (int argc, char **argv)
1480 : : {
1481 : 1 : g_test_init (&argc, &argv, NULL);
1482 : :
1483 : 1 : g_test_add_func ("/actions/basic", test_basic);
1484 : 1 : g_test_add_func ("/actions/name", test_name);
1485 : 1 : g_test_add_func ("/actions/simplegroup", test_simple_group);
1486 : 1 : g_test_add_func ("/actions/stateful", test_stateful);
1487 : 1 : g_test_add_func ("/actions/default-activate", test_default_activate);
1488 : 1 : g_test_add_func ("/actions/entries", test_entries);
1489 : 1 : g_test_add_func ("/actions/parse-detailed", test_parse_detailed);
1490 : 1 : g_test_add_func ("/actions/dbus/export", test_dbus_export);
1491 : 1 : g_test_add_func ("/actions/dbus/export/error-handling", test_dbus_export_error_handling);
1492 : 1 : g_test_add_func ("/actions/dbus/threaded", test_dbus_threaded);
1493 : 1 : g_test_add_func ("/actions/dbus/bug679509", test_bug679509);
1494 : 1 : g_test_add_func ("/actions/property", test_property_actions);
1495 : 1 : g_test_add_func ("/actions/no-properties", test_property_actions_no_properties);
1496 : :
1497 : 1 : return g_test_run ();
1498 : : }
|