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 activate (GAction *action,
39 GVariant *parameter,
40 gpointer user_data)
41 {
42 Activation *activation = user_data;
43
44 if (parameter)
45 activation->params = g_variant_ref (parameter);
46 else
47 activation->params = NULL;
48 activation->did_run = TRUE;
49 }
50
51 static void
52 test_basic (void)
53 {
54 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 action = g_simple_action_new ("foo", NULL);
63 g_assert_true (g_action_get_enabled (G_ACTION (action)));
64 g_assert_null (g_action_get_parameter_type (G_ACTION (action)));
65 g_assert_null (g_action_get_state_type (G_ACTION (action)));
66 g_assert_null (g_action_get_state_hint (G_ACTION (action)));
67 g_assert_null (g_action_get_state (G_ACTION (action)));
68 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 g_assert_cmpstr (name, ==, "foo");
76 g_assert_null (parameter_type);
77 g_assert_true (enabled);
78 g_assert_null (state_type);
79 g_assert_null (state);
80 g_free (name);
81
82 g_signal_connect (action, "activate", G_CALLBACK (activate), &a);
83 g_assert_false (a.did_run);
84 g_action_activate (G_ACTION (action), NULL);
85 g_assert_true (a.did_run);
86 a.did_run = FALSE;
87
88 g_simple_action_set_enabled (action, FALSE);
89 g_action_activate (G_ACTION (action), NULL);
90 g_assert_false (a.did_run);
91
92 if (g_test_undefined ())
93 {
94 g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
95 "*assertion*g_variant_is_of_type*failed*");
96 g_action_activate (G_ACTION (action), g_variant_new_string ("xxx"));
97 g_test_assert_expected_messages ();
98 }
99
100 g_object_unref (action);
101 g_assert_false (a.did_run);
102
103 action = g_simple_action_new ("foo", G_VARIANT_TYPE_STRING);
104 g_assert_true (g_action_get_enabled (G_ACTION (action)));
105 g_assert_true (g_variant_type_equal (g_action_get_parameter_type (G_ACTION (action)), G_VARIANT_TYPE_STRING));
106 g_assert_null (g_action_get_state_type (G_ACTION (action)));
107 g_assert_null (g_action_get_state_hint (G_ACTION (action)));
108 g_assert_null (g_action_get_state (G_ACTION (action)));
109
110 g_signal_connect (action, "activate", G_CALLBACK (activate), &a);
111 g_assert_false (a.did_run);
112 g_action_activate (G_ACTION (action), g_variant_new_string ("Hello world"));
113 g_assert_true (a.did_run);
114 g_assert_cmpstr (g_variant_get_string (a.params, NULL), ==, "Hello world");
115 g_variant_unref (a.params);
116 a.did_run = FALSE;
117
118 if (g_test_undefined ())
119 {
120 g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
121 "*assertion*!= NULL*failed*");
122 g_action_activate (G_ACTION (action), NULL);
123 g_test_assert_expected_messages ();
124 }
125
126 g_object_unref (action);
127 g_assert_false (a.did_run);
128 }
129
130 static void
131 test_name (void)
132 {
133 g_assert_false (g_action_name_is_valid (""));
134 g_assert_false (g_action_name_is_valid ("("));
135 g_assert_false (g_action_name_is_valid ("%abc"));
136 g_assert_false (g_action_name_is_valid ("$x1"));
137 g_assert_true (g_action_name_is_valid ("abc.def"));
138 g_assert_true (g_action_name_is_valid ("ABC-DEF"));
139 }
140
141 static gboolean
142 strv_strv_cmp (const gchar * const *a,
143 const gchar * const *b)
144 {
145 guint n;
146
147 for (n = 0; a[n] != NULL; n++)
148 {
149 if (!g_strv_contains (b, a[n]))
150 return FALSE;
151 }
152
153 for (n = 0; b[n] != NULL; n++)
154 {
155 if (!g_strv_contains (a, b[n]))
156 return FALSE;
157 }
158
159 return TRUE;
160 }
161
162 static gboolean
163 strv_set_equal (const gchar * const *strv, ...)
164 {
165 guint count;
166 va_list list;
167 const gchar *str;
168 gboolean res;
169
170 res = TRUE;
171 count = 0;
172 va_start (list, strv);
173 while (1)
174 {
175 str = va_arg (list, const gchar *);
176 if (str == NULL)
177 break;
178 if (!g_strv_contains (strv, str))
179 {
180 res = FALSE;
181 break;
182 }
183 count++;
184 }
185 va_end (list);
186
187 if (res)
188 res = g_strv_length ((gchar**)strv) == count;
189
190 return res;
191 }
192
193 static void
194 ensure_state (GActionGroup *group,
195 const gchar *action_name,
196 const gchar *expected)
197 {
198 GVariant *value;
199 gchar *printed;
200
201 value = g_action_group_get_action_state (group, action_name);
202 printed = g_variant_print (value, TRUE);
203 g_variant_unref (value);
204
205 g_assert_cmpstr (printed, ==, expected);
206 g_free (printed);
207 }
208
209 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
210
211 static void
212 test_simple_group (void)
213 {
214 GSimpleActionGroup *group;
215 Activation a = { 0, };
216 GSimpleAction *simple;
217 GAction *action;
218 gchar **actions;
219 GVariant *state;
220
221 simple = g_simple_action_new ("foo", NULL);
222 g_signal_connect (simple, "activate", G_CALLBACK (activate), &a);
223 g_assert_false (a.did_run);
224 g_action_activate (G_ACTION (simple), NULL);
225 g_assert_true (a.did_run);
226 a.did_run = FALSE;
227
228 group = g_simple_action_group_new ();
229 g_simple_action_group_insert (group, G_ACTION (simple));
230 g_object_unref (simple);
231
232 g_assert_false (a.did_run);
233 g_action_group_activate_action (G_ACTION_GROUP (group), "foo", NULL);
234 g_assert_true (a.did_run);
235
236 simple = g_simple_action_new_stateful ("bar", G_VARIANT_TYPE_STRING, g_variant_new_string ("hihi"));
237 g_simple_action_group_insert (group, G_ACTION (simple));
238 g_object_unref (simple);
239
240 g_assert_true (g_action_group_has_action (G_ACTION_GROUP (group), "foo"));
241 g_assert_true (g_action_group_has_action (G_ACTION_GROUP (group), "bar"));
242 g_assert_false (g_action_group_has_action (G_ACTION_GROUP (group), "baz"));
243 actions = g_action_group_list_actions (G_ACTION_GROUP (group));
244 g_assert_cmpint (g_strv_length (actions), ==, 2);
245 g_assert_true (strv_set_equal ((const gchar * const *) actions, "foo", "bar", NULL));
246 g_strfreev (actions);
247 g_assert_true (g_action_group_get_action_enabled (G_ACTION_GROUP (group), "foo"));
248 g_assert_true (g_action_group_get_action_enabled (G_ACTION_GROUP (group), "bar"));
249 g_assert_null (g_action_group_get_action_parameter_type (G_ACTION_GROUP (group), "foo"));
250 g_assert_true (g_variant_type_equal (g_action_group_get_action_parameter_type (G_ACTION_GROUP (group), "bar"), G_VARIANT_TYPE_STRING));
251 g_assert_null (g_action_group_get_action_state_type (G_ACTION_GROUP (group), "foo"));
252 g_assert_true (g_variant_type_equal (g_action_group_get_action_state_type (G_ACTION_GROUP (group), "bar"), G_VARIANT_TYPE_STRING));
253 g_assert_null (g_action_group_get_action_state_hint (G_ACTION_GROUP (group), "foo"));
254 g_assert_null (g_action_group_get_action_state_hint (G_ACTION_GROUP (group), "bar"));
255 g_assert_null (g_action_group_get_action_state (G_ACTION_GROUP (group), "foo"));
256 state = g_action_group_get_action_state (G_ACTION_GROUP (group), "bar");
257 g_assert_true (g_variant_type_equal (g_variant_get_type (state), G_VARIANT_TYPE_STRING));
258 g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "hihi");
259 g_variant_unref (state);
260
261 g_action_group_change_action_state (G_ACTION_GROUP (group), "bar", g_variant_new_string ("boo"));
262 state = g_action_group_get_action_state (G_ACTION_GROUP (group), "bar");
263 g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "boo");
264 g_variant_unref (state);
265
266 action = g_simple_action_group_lookup (group, "bar");
267 g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
268 g_assert_false (g_action_group_get_action_enabled (G_ACTION_GROUP (group), "bar"));
269
270 g_simple_action_group_remove (group, "bar");
271 action = g_simple_action_group_lookup (group, "foo");
272 g_assert_cmpstr (g_action_get_name (action), ==, "foo");
273 action = g_simple_action_group_lookup (group, "bar");
274 g_assert_null (action);
275
276 simple = g_simple_action_new ("foo", NULL);
277 g_simple_action_group_insert (group, G_ACTION (simple));
278 g_object_unref (simple);
279
280 a.did_run = FALSE;
281 g_object_unref (group);
282 g_assert_false (a.did_run);
283 }
284
285 G_GNUC_END_IGNORE_DEPRECATIONS
286
287 static void
288 test_stateful (void)
289 {
290 GSimpleAction *action;
291 GVariant *state;
292
293 action = g_simple_action_new_stateful ("foo", NULL, g_variant_new_string ("hihi"));
294 g_assert_true (g_action_get_enabled (G_ACTION (action)));
295 g_assert_null (g_action_get_parameter_type (G_ACTION (action)));
296 g_assert_null (g_action_get_state_hint (G_ACTION (action)));
297 g_assert_true (g_variant_type_equal (g_action_get_state_type (G_ACTION (action)),
298 G_VARIANT_TYPE_STRING));
299 state = g_action_get_state (G_ACTION (action));
300 g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "hihi");
301 g_variant_unref (state);
302
303 if (g_test_undefined ())
304 {
305 GVariant *new_state = g_variant_ref_sink (g_variant_new_int32 (123));
306 g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
307 "*assertion*g_variant_is_of_type*failed*");
308 g_simple_action_set_state (action, new_state);
309 g_test_assert_expected_messages ();
310 g_variant_unref (new_state);
311 }
312
313 g_simple_action_set_state (action, g_variant_new_string ("hello"));
314 state = g_action_get_state (G_ACTION (action));
315 g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "hello");
316 g_variant_unref (state);
317
318 g_object_unref (action);
319
320 action = g_simple_action_new ("foo", NULL);
321
322 if (g_test_undefined ())
323 {
324 GVariant *new_state = g_variant_ref_sink (g_variant_new_int32 (123));
325 g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
326 "*assertion*!= NULL*failed*");
327 g_simple_action_set_state (action, new_state);
328 g_test_assert_expected_messages ();
329 g_variant_unref (new_state);
330 }
331
332 g_object_unref (action);
333 }
334
335 static void
336 test_default_activate (void)
337 {
338 GSimpleAction *action;
339 GVariant *state;
340
341 /* Test changing state via activation with parameter */
342 action = g_simple_action_new_stateful ("foo", G_VARIANT_TYPE_STRING, g_variant_new_string ("hihi"));
343 g_action_activate (G_ACTION (action), g_variant_new_string ("bye"));
344 state = g_action_get_state (G_ACTION (action));
345 g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "bye");
346 g_variant_unref (state);
347 g_object_unref (action);
348
349 /* Test toggling a boolean action via activation with no parameter */
350 action = g_simple_action_new_stateful ("foo", NULL, g_variant_new_boolean (FALSE));
351 g_action_activate (G_ACTION (action), NULL);
352 state = g_action_get_state (G_ACTION (action));
353 g_assert_true (g_variant_get_boolean (state));
354 g_variant_unref (state);
355 /* and back again */
356 g_action_activate (G_ACTION (action), NULL);
357 state = g_action_get_state (G_ACTION (action));
358 g_assert_false (g_variant_get_boolean (state));
359 g_variant_unref (state);
360 g_object_unref (action);
361 }
362
363 static gboolean foo_activated = FALSE;
364 static gboolean bar_activated = FALSE;
365
366 static void
367 activate_foo (GSimpleAction *simple,
368 GVariant *parameter,
369 gpointer user_data)
370 {
371 g_assert_true (user_data == GINT_TO_POINTER (123));
372 g_assert_null (parameter);
373 foo_activated = TRUE;
374 }
375
376 static void
377 activate_bar (GSimpleAction *simple,
378 GVariant *parameter,
379 gpointer user_data)
380 {
381 g_assert_true (user_data == GINT_TO_POINTER (123));
382 g_assert_cmpstr (g_variant_get_string (parameter, NULL), ==, "param");
383 bar_activated = TRUE;
384 }
385
386 static void
387 change_volume_state (GSimpleAction *action,
388 GVariant *value,
389 gpointer user_data)
390 {
391 gint requested;
392
393 requested = g_variant_get_int32 (value);
394
395 /* Volume only goes from 0 to 10 */
396 if (0 <= requested && requested <= 10)
397 g_simple_action_set_state (action, value);
398 }
399
400 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
401
402 static void
403 test_entries (void)
404 {
405 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 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 actions = g_simple_action_group_new ();
421 g_simple_action_group_add_entries (actions, entries,
422 G_N_ELEMENTS (entries),
423 GINT_TO_POINTER (123));
424
425 g_assert_false (foo_activated);
426 g_action_group_activate_action (G_ACTION_GROUP (actions), "foo", NULL);
427 g_assert_true (foo_activated);
428 foo_activated = FALSE;
429
430 g_assert_false (bar_activated);
431 g_action_group_activate_action (G_ACTION_GROUP (actions), "bar",
432 g_variant_new_string ("param"));
433 g_assert_true (bar_activated);
434 g_assert_false (foo_activated);
435
436 if (g_test_undefined ())
437 {
438 const GActionEntry bad_type = {
439 "bad-type", NULL, "ss", NULL, NULL, { 0 }
440 };
441 const GActionEntry bad_state = {
442 "bad-state", NULL, NULL, "flse", NULL, { 0 }
443 };
444
445 g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
446 "*not a valid GVariant type string*");
447 g_simple_action_group_add_entries (actions, &bad_type, 1, NULL);
448 g_test_assert_expected_messages ();
449
450 g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
451 "*could not parse*");
452 g_simple_action_group_add_entries (actions, &bad_state, 1, NULL);
453 g_test_assert_expected_messages ();
454 }
455
456 state = g_action_group_get_action_state (G_ACTION_GROUP (actions), "volume");
457 g_assert_cmpint (g_variant_get_int32 (state), ==, 0);
458 g_variant_unref (state);
459
460 /* should change */
461 g_action_group_change_action_state (G_ACTION_GROUP (actions), "volume",
462 g_variant_new_int32 (7));
463 state = g_action_group_get_action_state (G_ACTION_GROUP (actions), "volume");
464 g_assert_cmpint (g_variant_get_int32 (state), ==, 7);
465 g_variant_unref (state);
466
467 /* should not change */
468 g_action_group_change_action_state (G_ACTION_GROUP (actions), "volume",
469 g_variant_new_int32 (11));
470 state = g_action_group_get_action_state (G_ACTION_GROUP (actions), "volume");
471 g_assert_cmpint (g_variant_get_int32 (state), ==, 7);
472 g_variant_unref (state);
473
474 names = g_action_group_list_actions (G_ACTION_GROUP (actions));
475 g_assert_cmpuint (g_strv_length (names), ==, G_N_ELEMENTS (entries));
476 g_strfreev (names);
477
478 g_action_map_remove_action_entries (G_ACTION_MAP (actions), entries, G_N_ELEMENTS (entries));
479 names = g_action_group_list_actions (G_ACTION_GROUP (actions));
480 g_assert_cmpuint (g_strv_length (names), ==, 0);
481 g_strfreev (names);
482
483 /* Check addition and removal of %NULL terminated array */
484 g_action_map_add_action_entries (G_ACTION_MAP (actions), entries2, -1, NULL);
485 names = g_action_group_list_actions (G_ACTION_GROUP (actions));
486 g_assert_cmpuint (g_strv_length (names), ==, G_N_ELEMENTS (entries2) - 1);
487 g_strfreev (names);
488 g_action_map_remove_action_entries (G_ACTION_MAP (actions), entries2, -1);
489 names = g_action_group_list_actions (G_ACTION_GROUP (actions));
490 g_assert_cmpuint (g_strv_length (names), ==, 0);
491 g_strfreev (names);
492
493 g_object_unref (actions);
494 }
495
496 G_GNUC_END_IGNORE_DEPRECATIONS
497
498 static void
499 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 } 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 for (i = 0; i < G_N_ELEMENTS (testcases); i++)
530 {
531 GError *error = NULL;
532 GVariant *target;
533 gboolean success;
534 gchar *name;
535
536 success = g_action_parse_detailed_name (testcases[i].detailed, &name, &target, &error);
537 g_assert_true (success == (error == NULL));
538 if (success && testcases[i].expected_error)
539 g_error ("Unexpected success on '%s'. Expected error containing '%s'",
540 testcases[i].detailed, testcases[i].expected_error);
541
542 if (!success && !testcases[i].expected_error)
543 g_error ("Unexpected failure on '%s': %s", testcases[i].detailed, error->message);
544
545 if (!success)
546 {
547 if (!strstr (error->message, testcases[i].expected_error))
548 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 g_error_free (error);
552 continue;
553 }
554
555 g_assert_cmpstr (name, ==, testcases[i].expected_name);
556 g_assert_true ((target == NULL) == (testcases[i].expected_target == NULL));
557
558 if (success)
559 {
560 gchar *detailed;
561
562 detailed = g_action_print_detailed_name (name, target);
563 g_assert_cmpstr (detailed, ==, testcases[i].detailed_roundtrip);
564 g_free (detailed);
565 }
566
567 if (target)
568 {
569 GVariant *expected;
570
571 expected = g_variant_parse (NULL, testcases[i].expected_target, NULL, NULL, NULL);
572 g_assert_true (expected);
573
574 g_assert_cmpvariant (expected, target);
575 g_variant_unref (expected);
576 g_variant_unref (target);
577 }
578
579 g_free (name);
580 }
581 }
582
583 GHashTable *activation_counts;
584
585 static void
586 count_activation (const gchar *action)
587 {
588 gint count;
589
590 if (activation_counts == NULL)
591 activation_counts = g_hash_table_new (g_str_hash, g_str_equal);
592 count = GPOINTER_TO_INT (g_hash_table_lookup (activation_counts, action));
593 count++;
594 g_hash_table_insert (activation_counts, (gpointer)action, GINT_TO_POINTER (count));
595
596 g_main_context_wakeup (NULL);
597 }
598
599 static gint
600 activation_count (const gchar *action)
601 {
602 if (activation_counts == NULL)
603 return 0;
604
605 return GPOINTER_TO_INT (g_hash_table_lookup (activation_counts, action));
606 }
607
608 static void
609 activate_action (GSimpleAction *action, GVariant *parameter, gpointer user_data)
610 {
611 count_activation (g_action_get_name (G_ACTION (action)));
612 }
613
614 static void
615 activate_toggle (GSimpleAction *action, GVariant *parameter, gpointer user_data)
616 {
617 GVariant *old_state, *new_state;
618
619 count_activation (g_action_get_name (G_ACTION (action)));
620
621 old_state = g_action_get_state (G_ACTION (action));
622 new_state = g_variant_new_boolean (!g_variant_get_boolean (old_state));
623 g_simple_action_set_state (action, new_state);
624 g_variant_unref (old_state);
625 }
626
627 static void
628 activate_radio (GSimpleAction *action, GVariant *parameter, gpointer user_data)
629 {
630 GVariant *new_state;
631
632 count_activation (g_action_get_name (G_ACTION (action)));
633
634 new_state = g_variant_new_string (g_variant_get_string (parameter, NULL));
635 g_simple_action_set_state (action, new_state);
636 }
637
638 static gboolean
639 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 alist = g_action_group_list_actions (a);
653 blist = g_action_group_list_actions (b);
654 equal = strv_strv_cmp ((const gchar * const *) alist, (const gchar * const *) blist);
655
656 for (i = 0; equal && alist[i]; i++)
657 {
658 ares = g_action_group_query_action (a, alist[i], &aenabled, &aparameter_type, &astate_type, &astate_hint, &astate);
659 bres = g_action_group_query_action (b, alist[i], &benabled, &bparameter_type, &bstate_type, &bstate_hint, &bstate);
660
661 if (ares && bres)
662 {
663 equal = equal && (aenabled == benabled);
664 equal = equal && ((!aparameter_type && !bparameter_type) || g_variant_type_equal (aparameter_type, bparameter_type));
665 equal = equal && ((!astate_type && !bstate_type) || g_variant_type_equal (astate_type, bstate_type));
666 equal = equal && ((!astate_hint && !bstate_hint) || g_variant_equal (astate_hint, bstate_hint));
667 equal = equal && ((!astate && !bstate) || g_variant_equal (astate, bstate));
668
669 if (astate_hint)
670 g_variant_unref (astate_hint);
671 if (bstate_hint)
672 g_variant_unref (bstate_hint);
673 if (astate)
674 g_variant_unref (astate);
675 if (bstate)
676 g_variant_unref (bstate);
677 }
678 else
679 equal = FALSE;
680 }
681
682 g_strfreev (alist);
683 g_strfreev (blist);
684
685 return equal;
686 }
687
688 static gboolean
689 timeout_cb (gpointer user_data)
690 {
691 gboolean *timed_out = user_data;
692
693 g_assert_false (*timed_out);
694 *timed_out = TRUE;
695 g_main_context_wakeup (NULL);
696
697 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 async_result_cb (GObject *source,
712 GAsyncResult *res,
713 gpointer user_data)
714 {
715 GAsyncResult **result_out = user_data;
716
717 g_assert_null (*result_out);
718 *result_out = g_object_ref (res);
719
720 g_main_context_wakeup (NULL);
721 }
722
723 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
724
725 static void
726 action_added_removed_cb (GActionGroup *action_group,
727 char *action_name,
728 gpointer user_data)
729 {
730 guint *counter = user_data;
731
732 *counter = *counter + 1;
733 g_main_context_wakeup (NULL);
734 }
735
736 static void
737 action_enabled_changed_cb (GActionGroup *action_group,
738 char *action_name,
739 gboolean enabled,
740 gpointer user_data)
741 {
742 guint *counter = user_data;
743
744 *counter = *counter + 1;
745 g_main_context_wakeup (NULL);
746 }
747
748 static void
749 action_state_changed_cb (GActionGroup *action_group,
750 char *action_name,
751 GVariant *value,
752 gpointer user_data)
753 {
754 guint *counter = user_data;
755
756 *counter = *counter + 1;
757 g_main_context_wakeup (NULL);
758 }
759
760 static void
761 test_dbus_export (void)
762 {
763 GDBusConnection *bus;
764 GSimpleActionGroup *group;
765 GDBusActionGroup *proxy;
766 GSimpleAction *action;
767 GError *error = NULL;
768 GVariant *v;
769 guint id;
770 gchar **actions;
771 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 GAsyncResult *async_result = NULL;
777
778 session_bus_up ();
779 bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
780
781 group = g_simple_action_group_new ();
782 g_simple_action_group_add_entries (group,
783 exported_entries,
784 G_N_ELEMENTS (exported_entries),
785 NULL);
786
787 id = g_dbus_connection_export_action_group (bus, "/", G_ACTION_GROUP (group), &error);
788 g_assert_no_error (error);
789
790 proxy = g_dbus_action_group_get (bus, g_dbus_connection_get_unique_name (bus), "/");
791 added_signal_id = g_signal_connect (proxy, "action-added", G_CALLBACK (action_added_removed_cb), &n_actions_added);
792 enabled_changed_signal_id = g_signal_connect (proxy, "action-enabled-changed", G_CALLBACK (action_enabled_changed_cb), &n_actions_enabled_changed);
793 removed_signal_id = g_signal_connect (proxy, "action-removed", G_CALLBACK (action_added_removed_cb), &n_actions_removed);
794 state_changed_signal_id = g_signal_connect (proxy, "action-state-changed", G_CALLBACK (action_state_changed_cb), &n_actions_state_changed);
795
796 actions = g_action_group_list_actions (G_ACTION_GROUP (proxy));
797 g_assert_cmpint (g_strv_length (actions), ==, 0);
798 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 while (n_actions_added < G_N_ELEMENTS (exported_entries))
803 g_main_context_iteration (NULL, TRUE);
804
805 actions = g_action_group_list_actions (G_ACTION_GROUP (proxy));
806 g_assert_cmpint (g_strv_length (actions), ==, G_N_ELEMENTS (exported_entries));
807 g_strfreev (actions);
808
809 /* check that calling "List" works too */
810 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 while (async_result == NULL)
824 g_main_context_iteration (NULL, TRUE);
825
826 v = g_dbus_connection_call_finish (bus, async_result, &error);
827 g_assert_no_error (error);
828 g_assert_nonnull (v);
829 g_variant_get (v, "(^a&s)", &actions);
830 g_assert_cmpuint (g_strv_length (actions), ==, G_N_ELEMENTS (exported_entries));
831 g_free (actions);
832 g_variant_unref (v);
833 g_clear_object (&async_result);
834
835 /* check that calling "Describe" works */
836 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 while (async_result == NULL)
850 g_main_context_iteration (NULL, TRUE);
851
852 v = g_dbus_connection_call_finish (bus, async_result, &error);
853 g_assert_no_error (error);
854 g_assert_nonnull (v);
855 /* FIXME: there's an extra level of tuplelization in here */
856 g_variant_get (v, "((bgav))", &enabled, ¶m, &iter);
857 g_assert_true (enabled);
858 g_assert_cmpstr (param, ==, "");
859 g_assert_cmpint (g_variant_iter_n_children (iter), ==, 0);
860 g_free (param);
861 g_variant_iter_free (iter);
862 g_variant_unref (v);
863 g_clear_object (&async_result);
864
865 /* check that activating a parameterless action over D-Bus works */
866 g_assert_cmpint (activation_count ("undo"), ==, 0);
867
868 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 while (async_result == NULL)
882 g_main_context_iteration (NULL, TRUE);
883
884 v = g_dbus_connection_call_finish (bus, async_result, &error);
885 g_assert_no_error (error);
886 g_assert_nonnull (v);
887 g_assert_true (g_variant_is_of_type (v, G_VARIANT_TYPE_UNIT));
888 g_variant_unref (v);
889 g_clear_object (&async_result);
890
891 g_assert_cmpint (activation_count ("undo"), ==, 1);
892
893 /* check that activating a parameterful action over D-Bus works */
894 g_assert_cmpint (activation_count ("lang"), ==, 0);
895 ensure_state (G_ACTION_GROUP (group), "lang", "'latin'");
896
897 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 while (async_result == NULL)
911 g_main_context_iteration (NULL, TRUE);
912
913 v = g_dbus_connection_call_finish (bus, async_result, &error);
914 g_assert_no_error (error);
915 g_assert_nonnull (v);
916 g_assert_true (g_variant_is_of_type (v, G_VARIANT_TYPE_UNIT));
917 g_variant_unref (v);
918 g_clear_object (&async_result);
919
920 g_assert_cmpint (activation_count ("lang"), ==, 1);
921 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 activate_error_conditions[] =
930 {
931 { "nope", NULL }, /* non-existent action */
932 { "lang", g_variant_new_parsed ("[<@u 4>]") }, /* wrong parameter type */
933 { "lang", NULL }, /* parameter missing */
934 { "undo", g_variant_new_parsed ("[<'silly'>]") }, /* extraneous parameter */
935 };
936
937 for (gsize i = 0; i < G_N_ELEMENTS (activate_error_conditions); i++)
938 {
939 GVariant *parameter = g_steal_pointer (&activate_error_conditions[i].parameter);
940 const gchar *type_string = (parameter != NULL) ? "(s@ava{sv})" : "(sava{sv})";
941
942 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 g_steal_pointer (¶meter),
950 NULL),
951 NULL,
952 0,
953 G_MAXINT,
954 NULL,
955 async_result_cb,
956 &async_result);
957
958 while (async_result == NULL)
959 g_main_context_iteration (NULL, TRUE);
960
961 v = g_dbus_connection_call_finish (bus, async_result, &error);
962 g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
963 g_assert_null (v);
964 g_clear_error (&error);
965 g_clear_object (&async_result);
966 }
967
968 /* check that setting an action’s state over D-Bus works */
969 g_assert_cmpint (activation_count ("lang"), ==, 1);
970 ensure_state (G_ACTION_GROUP (group), "lang", "'spanish'");
971
972 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 while (async_result == NULL)
986 g_main_context_iteration (NULL, TRUE);
987
988 v = g_dbus_connection_call_finish (bus, async_result, &error);
989 g_assert_no_error (error);
990 g_assert_nonnull (v);
991 g_assert_true (g_variant_is_of_type (v, G_VARIANT_TYPE_UNIT));
992 g_variant_unref (v);
993 g_clear_object (&async_result);
994
995 g_assert_cmpint (activation_count ("lang"), ==, 1);
996 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 set_state_error_conditions[] =
1005 {
1006 { "nope", g_variant_new_string ("hello") }, /* non-existent action */
1007 { "undo", g_variant_new_string ("not stateful") }, /* not a stateful action */
1008 { "lang", g_variant_new_uint32 (3) }, /* wrong state type */
1009 };
1010
1011 for (gsize i = 0; i < G_N_ELEMENTS (set_state_error_conditions); i++)
1012 {
1013 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 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 while (async_result == NULL)
1030 g_main_context_iteration (NULL, TRUE);
1031
1032 v = g_dbus_connection_call_finish (bus, async_result, &error);
1033 g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
1034 g_assert_null (v);
1035 g_clear_error (&error);
1036 g_clear_object (&async_result);
1037 }
1038
1039 /* test that the initial transfer works */
1040 g_assert_true (G_IS_DBUS_ACTION_GROUP (proxy));
1041 while (!compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)))
1042 g_main_context_iteration (NULL, TRUE);
1043 n_actions_state_changed = 0;
1044
1045 /* test that various changes get propagated from group to proxy */
1046 n_actions_added = 0;
1047 action = g_simple_action_new_stateful ("italic", NULL, g_variant_new_boolean (FALSE));
1048 g_simple_action_group_insert (group, G_ACTION (action));
1049 g_object_unref (action);
1050
1051 while (n_actions_added == 0)
1052 g_main_context_iteration (NULL, TRUE);
1053
1054 g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
1055
1056 action = G_SIMPLE_ACTION (g_simple_action_group_lookup (group, "cut"));
1057 g_simple_action_set_enabled (action, FALSE);
1058
1059 while (n_actions_enabled_changed == 0)
1060 g_main_context_iteration (NULL, TRUE);
1061
1062 g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
1063
1064 action = G_SIMPLE_ACTION (g_simple_action_group_lookup (group, "bold"));
1065 g_simple_action_set_state (action, g_variant_new_boolean (FALSE));
1066
1067 while (n_actions_state_changed == 0)
1068 g_main_context_iteration (NULL, TRUE);
1069
1070 g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
1071
1072 g_simple_action_group_remove (group, "italic");
1073
1074 while (n_actions_removed == 0)
1075 g_main_context_iteration (NULL, TRUE);
1076
1077 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 n_actions_state_changed = 0;
1081 g_assert_cmpint (activation_count ("copy"), ==, 0);
1082 g_action_group_activate_action (G_ACTION_GROUP (proxy), "copy", NULL);
1083
1084 while (activation_count ("copy") == 0)
1085 g_main_context_iteration (NULL, TRUE);
1086
1087 g_assert_cmpint (activation_count ("copy"), ==, 1);
1088 g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
1089
1090 n_actions_state_changed = 0;
1091 g_assert_cmpint (activation_count ("bold"), ==, 0);
1092 g_action_group_activate_action (G_ACTION_GROUP (proxy), "bold", NULL);
1093
1094 while (n_actions_state_changed == 0)
1095 g_main_context_iteration (NULL, TRUE);
1096
1097 g_assert_cmpint (activation_count ("bold"), ==, 1);
1098 g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
1099 v = g_action_group_get_action_state (G_ACTION_GROUP (group), "bold");
1100 g_assert_true (g_variant_get_boolean (v));
1101 g_variant_unref (v);
1102
1103 n_actions_state_changed = 0;
1104 g_action_group_change_action_state (G_ACTION_GROUP (proxy), "bold", g_variant_new_boolean (FALSE));
1105
1106 while (n_actions_state_changed == 0)
1107 g_main_context_iteration (NULL, TRUE);
1108
1109 g_assert_cmpint (activation_count ("bold"), ==, 1);
1110 g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
1111 v = g_action_group_get_action_state (G_ACTION_GROUP (group), "bold");
1112 g_assert_false (g_variant_get_boolean (v));
1113 g_variant_unref (v);
1114
1115 g_dbus_connection_unexport_action_group (bus, id);
1116
1117 g_signal_handler_disconnect (proxy, added_signal_id);
1118 g_signal_handler_disconnect (proxy, enabled_changed_signal_id);
1119 g_signal_handler_disconnect (proxy, removed_signal_id);
1120 g_signal_handler_disconnect (proxy, state_changed_signal_id);
1121 g_object_unref (proxy);
1122 g_object_unref (group);
1123 g_object_unref (bus);
1124
1125 session_bus_down ();
1126 }
1127
1128 static gpointer
1129 do_export (gpointer data)
1130 {
1131 GActionGroup *group = data;
1132 GMainContext *ctx;
1133 gint i;
1134 GError *error = NULL;
1135 guint id;
1136 GDBusConnection *bus;
1137 GAction *action;
1138 gchar *path;
1139
1140 ctx = g_main_context_new ();
1141
1142 g_main_context_push_thread_default (ctx);
1143
1144 bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
1145 path = g_strdup_printf("/%p", data);
1146
1147 for (i = 0; i < 10000; i++)
1148 {
1149 id = g_dbus_connection_export_action_group (bus, path, G_ACTION_GROUP (group), &error);
1150 g_assert_no_error (error);
1151
1152 action = g_simple_action_group_lookup (G_SIMPLE_ACTION_GROUP (group), "a");
1153 g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
1154 !g_action_get_enabled (action));
1155
1156 g_dbus_connection_unexport_action_group (bus, id);
1157
1158 while (g_main_context_iteration (ctx, FALSE));
1159 }
1160
1161 g_free (path);
1162 g_object_unref (bus);
1163
1164 g_main_context_pop_thread_default (ctx);
1165
1166 g_main_context_unref (ctx);
1167
1168 return NULL;
1169 }
1170
1171 static void
1172 test_dbus_threaded (void)
1173 {
1174 GSimpleActionGroup *group[10];
1175 GThread *export[10];
1176 static GActionEntry entries[] = {
1177 { "a", activate_action, NULL, NULL, NULL, { 0 } },
1178 { "b", activate_action, NULL, NULL, NULL, { 0 } },
1179 };
1180 gint i;
1181
1182 session_bus_up ();
1183
1184 for (i = 0; i < 10; i++)
1185 {
1186 group[i] = g_simple_action_group_new ();
1187 g_simple_action_group_add_entries (group[i], entries, G_N_ELEMENTS (entries), NULL);
1188 export[i] = g_thread_new ("export", do_export, group[i]);
1189 }
1190
1191 for (i = 0; i < 10; i++)
1192 g_thread_join (export[i]);
1193
1194 for (i = 0; i < 10; i++)
1195 g_object_unref (group[i]);
1196
1197 session_bus_down ();
1198 }
1199
1200 G_GNUC_END_IGNORE_DEPRECATIONS
1201
1202 static void
1203 test_bug679509 (void)
1204 {
1205 GDBusConnection *bus;
1206 GDBusActionGroup *proxy;
1207 gboolean timed_out = FALSE;
1208
1209 session_bus_up ();
1210 bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
1211
1212 proxy = g_dbus_action_group_get (bus, g_dbus_connection_get_unique_name (bus), "/");
1213 g_strfreev (g_action_group_list_actions (G_ACTION_GROUP (proxy)));
1214 g_object_unref (proxy);
1215
1216 g_timeout_add (100, timeout_cb, &timed_out);
1217 while (!timed_out)
1218 g_main_context_iteration (NULL, TRUE);
1219
1220 g_object_unref (bus);
1221
1222 session_bus_down ();
1223 }
1224
1225 static gchar *state_change_log;
1226
1227 static void
1228 state_changed (GActionGroup *group,
1229 const gchar *action_name,
1230 GVariant *value,
1231 gpointer user_data)
1232 {
1233 GString *string;
1234
1235 g_assert_false (state_change_log);
1236
1237 string = g_string_new (action_name);
1238 g_string_append_c (string, ':');
1239 g_variant_print_string (value, string, TRUE);
1240 state_change_log = g_string_free (string, FALSE);
1241 }
1242
1243 static void
1244 verify_changed (const gchar *log_entry)
1245 {
1246 g_assert_cmpstr (state_change_log, ==, log_entry);
1247 g_clear_pointer (&state_change_log, g_free);
1248 }
1249
1250 static void
1251 test_property_actions (void)
1252 {
1253 GSimpleActionGroup *group;
1254 GPropertyAction *action;
1255 GSocketClient *client;
1256 GApplication *app;
1257 gchar *name;
1258 GVariantType *ptype, *stype;
1259 gboolean enabled;
1260 GVariant *state;
1261
1262 group = g_simple_action_group_new ();
1263 g_signal_connect (group, "action-state-changed", G_CALLBACK (state_changed), NULL);
1264
1265 client = g_socket_client_new ();
1266 app = g_application_new ("org.gtk.test", 0);
1267
1268 /* string... */
1269 action = g_property_action_new ("app-id", app, "application-id");
1270 g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
1271 g_object_unref (action);
1272
1273 /* uint... */
1274 action = g_property_action_new ("keepalive", app, "inactivity-timeout");
1275 g_object_get (action, "name", &name, "parameter-type", &ptype, "enabled", &enabled, "state-type", &stype, "state", &state, NULL);
1276 g_assert_cmpstr (name, ==, "keepalive");
1277 g_assert_true (enabled);
1278 g_free (name);
1279 g_variant_type_free (ptype);
1280 g_variant_type_free (stype);
1281 g_variant_unref (state);
1282
1283 g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
1284 g_object_unref (action);
1285
1286 /* bool... */
1287 action = g_property_action_new ("tls", client, "tls");
1288 g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
1289 g_object_unref (action);
1290
1291 /* inverted */
1292 action = g_object_new (G_TYPE_PROPERTY_ACTION,
1293 "name", "disable-proxy",
1294 "object", client,
1295 "property-name", "enable-proxy",
1296 "invert-boolean", TRUE,
1297 NULL);
1298 g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
1299 g_object_unref (action);
1300
1301 /* enum... */
1302 action = g_property_action_new ("type", client, "type");
1303 g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
1304 g_object_unref (action);
1305
1306 /* the objects should be held alive by the actions... */
1307 g_object_unref (client);
1308 g_object_unref (app);
1309
1310 ensure_state (G_ACTION_GROUP (group), "app-id", "'org.gtk.test'");
1311 ensure_state (G_ACTION_GROUP (group), "keepalive", "uint32 0");
1312 ensure_state (G_ACTION_GROUP (group), "tls", "false");
1313 ensure_state (G_ACTION_GROUP (group), "disable-proxy", "false");
1314 ensure_state (G_ACTION_GROUP (group), "type", "'stream'");
1315
1316 verify_changed (NULL);
1317
1318 /* some string tests... */
1319 g_action_group_change_action_state (G_ACTION_GROUP (group), "app-id", g_variant_new ("s", "org.gtk.test2"));
1320 verify_changed ("app-id:'org.gtk.test2'");
1321 g_assert_cmpstr (g_application_get_application_id (app), ==, "org.gtk.test2");
1322 ensure_state (G_ACTION_GROUP (group), "app-id", "'org.gtk.test2'");
1323
1324 g_action_group_activate_action (G_ACTION_GROUP (group), "app-id", g_variant_new ("s", "org.gtk.test3"));
1325 verify_changed ("app-id:'org.gtk.test3'");
1326 g_assert_cmpstr (g_application_get_application_id (app), ==, "org.gtk.test3");
1327 ensure_state (G_ACTION_GROUP (group), "app-id", "'org.gtk.test3'");
1328
1329 g_application_set_application_id (app, "org.gtk.test");
1330 verify_changed ("app-id:'org.gtk.test'");
1331 ensure_state (G_ACTION_GROUP (group), "app-id", "'org.gtk.test'");
1332
1333 /* uint tests */
1334 g_action_group_change_action_state (G_ACTION_GROUP (group), "keepalive", g_variant_new ("u", 1234));
1335 verify_changed ("keepalive:uint32 1234");
1336 g_assert_cmpuint (g_application_get_inactivity_timeout (app), ==, 1234);
1337 ensure_state (G_ACTION_GROUP (group), "keepalive", "uint32 1234");
1338
1339 g_action_group_activate_action (G_ACTION_GROUP (group), "keepalive", g_variant_new ("u", 5678));
1340 verify_changed ("keepalive:uint32 5678");
1341 g_assert_cmpuint (g_application_get_inactivity_timeout (app), ==, 5678);
1342 ensure_state (G_ACTION_GROUP (group), "keepalive", "uint32 5678");
1343
1344 g_application_set_inactivity_timeout (app, 0);
1345 verify_changed ("keepalive:uint32 0");
1346 ensure_state (G_ACTION_GROUP (group), "keepalive", "uint32 0");
1347
1348 /* bool tests */
1349 g_action_group_change_action_state (G_ACTION_GROUP (group), "tls", g_variant_new ("b", TRUE));
1350 verify_changed ("tls:true");
1351 g_assert_true (g_socket_client_get_tls (client));
1352 ensure_state (G_ACTION_GROUP (group), "tls", "true");
1353
1354 g_action_group_change_action_state (G_ACTION_GROUP (group), "disable-proxy", g_variant_new ("b", TRUE));
1355 verify_changed ("disable-proxy:true");
1356 ensure_state (G_ACTION_GROUP (group), "disable-proxy", "true");
1357 g_assert_false (g_socket_client_get_enable_proxy (client));
1358
1359 /* test toggle true->false */
1360 g_action_group_activate_action (G_ACTION_GROUP (group), "tls", NULL);
1361 verify_changed ("tls:false");
1362 g_assert_false (g_socket_client_get_tls (client));
1363 ensure_state (G_ACTION_GROUP (group), "tls", "false");
1364
1365 /* and now back false->true */
1366 g_action_group_activate_action (G_ACTION_GROUP (group), "tls", NULL);
1367 verify_changed ("tls:true");
1368 g_assert_true (g_socket_client_get_tls (client));
1369 ensure_state (G_ACTION_GROUP (group), "tls", "true");
1370
1371 g_socket_client_set_tls (client, FALSE);
1372 verify_changed ("tls:false");
1373 ensure_state (G_ACTION_GROUP (group), "tls", "false");
1374
1375 /* now do the same for the inverted action */
1376 g_action_group_activate_action (G_ACTION_GROUP (group), "disable-proxy", NULL);
1377 verify_changed ("disable-proxy:false");
1378 g_assert_true (g_socket_client_get_enable_proxy (client));
1379 ensure_state (G_ACTION_GROUP (group), "disable-proxy", "false");
1380
1381 g_action_group_activate_action (G_ACTION_GROUP (group), "disable-proxy", NULL);
1382 verify_changed ("disable-proxy:true");
1383 g_assert_false (g_socket_client_get_enable_proxy (client));
1384 ensure_state (G_ACTION_GROUP (group), "disable-proxy", "true");
1385
1386 g_socket_client_set_enable_proxy (client, TRUE);
1387 verify_changed ("disable-proxy:false");
1388 ensure_state (G_ACTION_GROUP (group), "disable-proxy", "false");
1389
1390 /* enum tests */
1391 g_action_group_change_action_state (G_ACTION_GROUP (group), "type", g_variant_new ("s", "datagram"));
1392 verify_changed ("type:'datagram'");
1393 g_assert_cmpint (g_socket_client_get_socket_type (client), ==, G_SOCKET_TYPE_DATAGRAM);
1394 ensure_state (G_ACTION_GROUP (group), "type", "'datagram'");
1395
1396 g_action_group_activate_action (G_ACTION_GROUP (group), "type", g_variant_new ("s", "stream"));
1397 verify_changed ("type:'stream'");
1398 g_assert_cmpint (g_socket_client_get_socket_type (client), ==, G_SOCKET_TYPE_STREAM);
1399 ensure_state (G_ACTION_GROUP (group), "type", "'stream'");
1400
1401 g_socket_client_set_socket_type (client, G_SOCKET_TYPE_SEQPACKET);
1402 verify_changed ("type:'seqpacket'");
1403 ensure_state (G_ACTION_GROUP (group), "type", "'seqpacket'");
1404
1405 /* Check some error cases... */
1406 g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_CRITICAL, "*non-existent*");
1407 action = g_property_action_new ("foo", app, "xyz");
1408 g_test_assert_expected_messages ();
1409 g_object_unref (action);
1410
1411 g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_CRITICAL, "*writable*");
1412 action = g_property_action_new ("foo", app, "is-registered");
1413 g_test_assert_expected_messages ();
1414 g_object_unref (action);
1415
1416 g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_CRITICAL, "*type 'GSocketAddress'*");
1417 action = g_property_action_new ("foo", client, "local-address");
1418 g_test_assert_expected_messages ();
1419 g_object_unref (action);
1420
1421 g_object_unref (group);
1422 }
1423
1424 static void
1425 test_property_actions_no_properties (void)
1426 {
1427 GPropertyAction *action;
1428
1429 g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_CRITICAL, "*Attempted to use an empty property name for GPropertyAction*");
1430 action = (GPropertyAction*) g_object_new_with_properties (G_TYPE_PROPERTY_ACTION, 0, NULL, NULL);
1431
1432 g_test_assert_expected_messages ();
1433 g_assert_true (G_IS_PROPERTY_ACTION (action));
1434
1435 g_object_unref (action);
1436 }
1437
1438 int
1439 main (int argc, char **argv)
1440 {
1441 g_test_init (&argc, &argv, NULL);
1442
1443 g_test_add_func ("/actions/basic", test_basic);
1444 g_test_add_func ("/actions/name", test_name);
1445 g_test_add_func ("/actions/simplegroup", test_simple_group);
1446 g_test_add_func ("/actions/stateful", test_stateful);
1447 g_test_add_func ("/actions/default-activate", test_default_activate);
1448 g_test_add_func ("/actions/entries", test_entries);
1449 g_test_add_func ("/actions/parse-detailed", test_parse_detailed);
1450 g_test_add_func ("/actions/dbus/export", test_dbus_export);
1451 g_test_add_func ("/actions/dbus/threaded", test_dbus_threaded);
1452 g_test_add_func ("/actions/dbus/bug679509", test_bug679509);
1453 g_test_add_func ("/actions/property", test_property_actions);
1454 g_test_add_func ("/actions/no-properties", test_property_actions_no_properties);
1455
1456 return g_test_run ();
1457 }