1#include <gio/gio.h>
2#include <stdlib.h>
3#include <string.h>
4
5#include "gdbus-sessionbus.h"
6
7typedef struct
8{
9 GVariant *params;
10 gboolean did_run;
11} Activation;
12
13static void
14activate (GAction *action,
15 GVariant *parameter,
16 gpointer user_data)
17{
18 Activation *activation = user_data;
19
20 if (parameter)
21 activation->params = g_variant_ref (value: parameter);
22 else
23 activation->params = NULL;
24 activation->did_run = TRUE;
25}
26
27static void
28test_basic (void)
29{
30 Activation a = { 0, };
31 GSimpleAction *action;
32 gchar *name;
33 GVariantType *parameter_type;
34 gboolean enabled;
35 GVariantType *state_type;
36 GVariant *state;
37
38 action = g_simple_action_new (name: "foo", NULL);
39 g_assert_true (g_action_get_enabled (G_ACTION (action)));
40 g_assert_null (g_action_get_parameter_type (G_ACTION (action)));
41 g_assert_null (g_action_get_state_type (G_ACTION (action)));
42 g_assert_null (g_action_get_state_hint (G_ACTION (action)));
43 g_assert_null (g_action_get_state (G_ACTION (action)));
44 g_object_get (object: action,
45 first_property_name: "name", &name,
46 "parameter-type", &parameter_type,
47 "enabled", &enabled,
48 "state-type", &state_type,
49 "state", &state,
50 NULL);
51 g_assert_cmpstr (name, ==, "foo");
52 g_assert_null (parameter_type);
53 g_assert_true (enabled);
54 g_assert_null (state_type);
55 g_assert_null (state);
56 g_free (mem: name);
57
58 g_signal_connect (action, "activate", G_CALLBACK (activate), &a);
59 g_assert_false (a.did_run);
60 g_action_activate (G_ACTION (action), NULL);
61 g_assert_true (a.did_run);
62 a.did_run = FALSE;
63
64 g_simple_action_set_enabled (simple: action, FALSE);
65 g_action_activate (G_ACTION (action), NULL);
66 g_assert_false (a.did_run);
67
68 if (g_test_undefined ())
69 {
70 g_test_expect_message (G_LOG_DOMAIN, log_level: G_LOG_LEVEL_CRITICAL,
71 pattern: "*assertion*g_variant_is_of_type*failed*");
72 g_action_activate (G_ACTION (action), parameter: g_variant_new_string (string: "xxx"));
73 g_test_assert_expected_messages ();
74 }
75
76 g_object_unref (object: action);
77 g_assert_false (a.did_run);
78
79 action = g_simple_action_new (name: "foo", G_VARIANT_TYPE_STRING);
80 g_assert_true (g_action_get_enabled (G_ACTION (action)));
81 g_assert_true (g_variant_type_equal (g_action_get_parameter_type (G_ACTION (action)), G_VARIANT_TYPE_STRING));
82 g_assert_null (g_action_get_state_type (G_ACTION (action)));
83 g_assert_null (g_action_get_state_hint (G_ACTION (action)));
84 g_assert_null (g_action_get_state (G_ACTION (action)));
85
86 g_signal_connect (action, "activate", G_CALLBACK (activate), &a);
87 g_assert_false (a.did_run);
88 g_action_activate (G_ACTION (action), parameter: g_variant_new_string (string: "Hello world"));
89 g_assert_true (a.did_run);
90 g_assert_cmpstr (g_variant_get_string (a.params, NULL), ==, "Hello world");
91 g_variant_unref (value: a.params);
92 a.did_run = FALSE;
93
94 if (g_test_undefined ())
95 {
96 g_test_expect_message (G_LOG_DOMAIN, log_level: G_LOG_LEVEL_CRITICAL,
97 pattern: "*assertion*!= NULL*failed*");
98 g_action_activate (G_ACTION (action), NULL);
99 g_test_assert_expected_messages ();
100 }
101
102 g_object_unref (object: action);
103 g_assert_false (a.did_run);
104}
105
106static void
107test_name (void)
108{
109 g_assert_false (g_action_name_is_valid (""));
110 g_assert_false (g_action_name_is_valid ("("));
111 g_assert_false (g_action_name_is_valid ("%abc"));
112 g_assert_false (g_action_name_is_valid ("$x1"));
113 g_assert_true (g_action_name_is_valid ("abc.def"));
114 g_assert_true (g_action_name_is_valid ("ABC-DEF"));
115}
116
117static gboolean
118strv_has_string (gchar **haystack,
119 const gchar *needle)
120{
121 guint n;
122
123 for (n = 0; haystack != NULL && haystack[n] != NULL; n++)
124 {
125 if (g_strcmp0 (str1: haystack[n], str2: needle) == 0)
126 return TRUE;
127 }
128 return FALSE;
129}
130
131static gboolean
132strv_strv_cmp (gchar **a, gchar **b)
133{
134 guint n;
135
136 for (n = 0; a[n] != NULL; n++)
137 {
138 if (!strv_has_string (haystack: b, needle: a[n]))
139 return FALSE;
140 }
141
142 for (n = 0; b[n] != NULL; n++)
143 {
144 if (!strv_has_string (haystack: a, needle: b[n]))
145 return FALSE;
146 }
147
148 return TRUE;
149}
150
151static gboolean
152strv_set_equal (gchar **strv, ...)
153{
154 gint count;
155 va_list list;
156 const gchar *str;
157 gboolean res;
158
159 res = TRUE;
160 count = 0;
161 va_start (list, strv);
162 while (1)
163 {
164 str = va_arg (list, const gchar *);
165 if (str == NULL)
166 break;
167 if (!strv_has_string (haystack: strv, needle: str))
168 {
169 res = FALSE;
170 break;
171 }
172 count++;
173 }
174 va_end (list);
175
176 if (res)
177 res = g_strv_length (str_array: (gchar**)strv) == count;
178
179 return res;
180}
181
182G_GNUC_BEGIN_IGNORE_DEPRECATIONS
183
184static void
185test_simple_group (void)
186{
187 GSimpleActionGroup *group;
188 Activation a = { 0, };
189 GSimpleAction *simple;
190 GAction *action;
191 gchar **actions;
192 GVariant *state;
193
194 simple = g_simple_action_new (name: "foo", NULL);
195 g_signal_connect (simple, "activate", G_CALLBACK (activate), &a);
196 g_assert_false (a.did_run);
197 g_action_activate (G_ACTION (simple), NULL);
198 g_assert_true (a.did_run);
199 a.did_run = FALSE;
200
201 group = g_simple_action_group_new ();
202 g_simple_action_group_insert (simple: group, G_ACTION (simple));
203 g_object_unref (object: simple);
204
205 g_assert_false (a.did_run);
206 g_action_group_activate_action (G_ACTION_GROUP (group), action_name: "foo", NULL);
207 g_assert_true (a.did_run);
208
209 simple = g_simple_action_new_stateful (name: "bar", G_VARIANT_TYPE_STRING, state: g_variant_new_string (string: "hihi"));
210 g_simple_action_group_insert (simple: group, G_ACTION (simple));
211 g_object_unref (object: simple);
212
213 g_assert_true (g_action_group_has_action (G_ACTION_GROUP (group), "foo"));
214 g_assert_true (g_action_group_has_action (G_ACTION_GROUP (group), "bar"));
215 g_assert_false (g_action_group_has_action (G_ACTION_GROUP (group), "baz"));
216 actions = g_action_group_list_actions (G_ACTION_GROUP (group));
217 g_assert_cmpint (g_strv_length (actions), ==, 2);
218 g_assert_true (strv_set_equal (actions, "foo", "bar", NULL));
219 g_strfreev (str_array: actions);
220 g_assert_true (g_action_group_get_action_enabled (G_ACTION_GROUP (group), "foo"));
221 g_assert_true (g_action_group_get_action_enabled (G_ACTION_GROUP (group), "bar"));
222 g_assert_null (g_action_group_get_action_parameter_type (G_ACTION_GROUP (group), "foo"));
223 g_assert_true (g_variant_type_equal (g_action_group_get_action_parameter_type (G_ACTION_GROUP (group), "bar"), G_VARIANT_TYPE_STRING));
224 g_assert_null (g_action_group_get_action_state_type (G_ACTION_GROUP (group), "foo"));
225 g_assert_true (g_variant_type_equal (g_action_group_get_action_state_type (G_ACTION_GROUP (group), "bar"), G_VARIANT_TYPE_STRING));
226 g_assert_null (g_action_group_get_action_state_hint (G_ACTION_GROUP (group), "foo"));
227 g_assert_null (g_action_group_get_action_state_hint (G_ACTION_GROUP (group), "bar"));
228 g_assert_null (g_action_group_get_action_state (G_ACTION_GROUP (group), "foo"));
229 state = g_action_group_get_action_state (G_ACTION_GROUP (group), action_name: "bar");
230 g_assert_true (g_variant_type_equal (g_variant_get_type (state), G_VARIANT_TYPE_STRING));
231 g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "hihi");
232 g_variant_unref (value: state);
233
234 g_action_group_change_action_state (G_ACTION_GROUP (group), action_name: "bar", value: g_variant_new_string (string: "boo"));
235 state = g_action_group_get_action_state (G_ACTION_GROUP (group), action_name: "bar");
236 g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "boo");
237 g_variant_unref (value: state);
238
239 action = g_simple_action_group_lookup (simple: group, action_name: "bar");
240 g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
241 g_assert_false (g_action_group_get_action_enabled (G_ACTION_GROUP (group), "bar"));
242
243 g_simple_action_group_remove (simple: group, action_name: "bar");
244 action = g_simple_action_group_lookup (simple: group, action_name: "foo");
245 g_assert_cmpstr (g_action_get_name (action), ==, "foo");
246 action = g_simple_action_group_lookup (simple: group, action_name: "bar");
247 g_assert_null (action);
248
249 simple = g_simple_action_new (name: "foo", NULL);
250 g_simple_action_group_insert (simple: group, G_ACTION (simple));
251 g_object_unref (object: simple);
252
253 a.did_run = FALSE;
254 g_object_unref (object: group);
255 g_assert_false (a.did_run);
256}
257
258G_GNUC_END_IGNORE_DEPRECATIONS
259
260static void
261test_stateful (void)
262{
263 GSimpleAction *action;
264 GVariant *state;
265
266 action = g_simple_action_new_stateful (name: "foo", NULL, state: g_variant_new_string (string: "hihi"));
267 g_assert_true (g_action_get_enabled (G_ACTION (action)));
268 g_assert_null (g_action_get_parameter_type (G_ACTION (action)));
269 g_assert_null (g_action_get_state_hint (G_ACTION (action)));
270 g_assert_true (g_variant_type_equal (g_action_get_state_type (G_ACTION (action)),
271 G_VARIANT_TYPE_STRING));
272 state = g_action_get_state (G_ACTION (action));
273 g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "hihi");
274 g_variant_unref (value: state);
275
276 if (g_test_undefined ())
277 {
278 g_test_expect_message (G_LOG_DOMAIN, log_level: G_LOG_LEVEL_CRITICAL,
279 pattern: "*assertion*g_variant_is_of_type*failed*");
280 g_simple_action_set_state (simple: action, value: g_variant_new_int32 (value: 123));
281 g_test_assert_expected_messages ();
282 }
283
284 g_simple_action_set_state (simple: action, value: g_variant_new_string (string: "hello"));
285 state = g_action_get_state (G_ACTION (action));
286 g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "hello");
287 g_variant_unref (value: state);
288
289 g_object_unref (object: action);
290
291 action = g_simple_action_new (name: "foo", NULL);
292
293 if (g_test_undefined ())
294 {
295 g_test_expect_message (G_LOG_DOMAIN, log_level: G_LOG_LEVEL_CRITICAL,
296 pattern: "*assertion*!= NULL*failed*");
297 g_simple_action_set_state (simple: action, value: g_variant_new_int32 (value: 123));
298 g_test_assert_expected_messages ();
299 }
300
301 g_object_unref (object: action);
302}
303
304static void
305test_default_activate (void)
306{
307 GSimpleAction *action;
308 GVariant *state;
309
310 /* Test changing state via activation with parameter */
311 action = g_simple_action_new_stateful (name: "foo", G_VARIANT_TYPE_STRING, state: g_variant_new_string (string: "hihi"));
312 g_action_activate (G_ACTION (action), parameter: g_variant_new_string (string: "bye"));
313 state = g_action_get_state (G_ACTION (action));
314 g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "bye");
315 g_variant_unref (value: state);
316 g_object_unref (object: action);
317
318 /* Test toggling a boolean action via activation with no parameter */
319 action = g_simple_action_new_stateful (name: "foo", NULL, state: g_variant_new_boolean (FALSE));
320 g_action_activate (G_ACTION (action), NULL);
321 state = g_action_get_state (G_ACTION (action));
322 g_assert_true (g_variant_get_boolean (state));
323 g_variant_unref (value: state);
324 /* and back again */
325 g_action_activate (G_ACTION (action), NULL);
326 state = g_action_get_state (G_ACTION (action));
327 g_assert_false (g_variant_get_boolean (state));
328 g_variant_unref (value: state);
329 g_object_unref (object: action);
330}
331
332static gboolean foo_activated = FALSE;
333static gboolean bar_activated = FALSE;
334
335static void
336activate_foo (GSimpleAction *simple,
337 GVariant *parameter,
338 gpointer user_data)
339{
340 g_assert_true (user_data == GINT_TO_POINTER (123));
341 g_assert_null (parameter);
342 foo_activated = TRUE;
343}
344
345static void
346activate_bar (GSimpleAction *simple,
347 GVariant *parameter,
348 gpointer user_data)
349{
350 g_assert_true (user_data == GINT_TO_POINTER (123));
351 g_assert_cmpstr (g_variant_get_string (parameter, NULL), ==, "param");
352 bar_activated = TRUE;
353}
354
355static void
356change_volume_state (GSimpleAction *action,
357 GVariant *value,
358 gpointer user_data)
359{
360 gint requested;
361
362 requested = g_variant_get_int32 (value);
363
364 /* Volume only goes from 0 to 10 */
365 if (0 <= requested && requested <= 10)
366 g_simple_action_set_state (simple: action, value);
367}
368
369G_GNUC_BEGIN_IGNORE_DEPRECATIONS
370
371static void
372test_entries (void)
373{
374 const GActionEntry entries[] = {
375 { "foo", activate_foo },
376 { "bar", activate_bar, "s" },
377 { "toggle", NULL, NULL, "false" },
378 { "volume", NULL, NULL, "0", change_volume_state }
379 };
380 GSimpleActionGroup *actions;
381 GVariant *state;
382
383 actions = g_simple_action_group_new ();
384 g_simple_action_group_add_entries (simple: actions, entries,
385 G_N_ELEMENTS (entries),
386 GINT_TO_POINTER (123));
387
388 g_assert_false (foo_activated);
389 g_action_group_activate_action (G_ACTION_GROUP (actions), action_name: "foo", NULL);
390 g_assert_true (foo_activated);
391 foo_activated = FALSE;
392
393 g_assert_false (bar_activated);
394 g_action_group_activate_action (G_ACTION_GROUP (actions), action_name: "bar",
395 parameter: g_variant_new_string (string: "param"));
396 g_assert_true (bar_activated);
397 g_assert_false (foo_activated);
398
399 if (g_test_undefined ())
400 {
401 const GActionEntry bad_type = {
402 "bad-type", NULL, "ss"
403 };
404 const GActionEntry bad_state = {
405 "bad-state", NULL, NULL, "flse"
406 };
407
408 g_test_expect_message (G_LOG_DOMAIN, log_level: G_LOG_LEVEL_CRITICAL,
409 pattern: "*not a valid GVariant type string*");
410 g_simple_action_group_add_entries (simple: actions, entries: &bad_type, n_entries: 1, NULL);
411 g_test_assert_expected_messages ();
412
413 g_test_expect_message (G_LOG_DOMAIN, log_level: G_LOG_LEVEL_CRITICAL,
414 pattern: "*could not parse*");
415 g_simple_action_group_add_entries (simple: actions, entries: &bad_state, n_entries: 1, NULL);
416 g_test_assert_expected_messages ();
417 }
418
419 state = g_action_group_get_action_state (G_ACTION_GROUP (actions), action_name: "volume");
420 g_assert_cmpint (g_variant_get_int32 (state), ==, 0);
421 g_variant_unref (value: state);
422
423 /* should change */
424 g_action_group_change_action_state (G_ACTION_GROUP (actions), action_name: "volume",
425 value: g_variant_new_int32 (value: 7));
426 state = g_action_group_get_action_state (G_ACTION_GROUP (actions), action_name: "volume");
427 g_assert_cmpint (g_variant_get_int32 (state), ==, 7);
428 g_variant_unref (value: state);
429
430 /* should not change */
431 g_action_group_change_action_state (G_ACTION_GROUP (actions), action_name: "volume",
432 value: g_variant_new_int32 (value: 11));
433 state = g_action_group_get_action_state (G_ACTION_GROUP (actions), action_name: "volume");
434 g_assert_cmpint (g_variant_get_int32 (state), ==, 7);
435 g_variant_unref (value: state);
436
437 g_object_unref (object: actions);
438}
439
440G_GNUC_END_IGNORE_DEPRECATIONS
441
442static void
443test_parse_detailed (void)
444{
445 struct {
446 const gchar *detailed;
447 const gchar *expected_name;
448 const gchar *expected_target;
449 const gchar *expected_error;
450 const gchar *detailed_roundtrip;
451 } testcases[] = {
452 { "abc", "abc", NULL, NULL, "abc" },
453 { " abc", NULL, NULL, "invalid format", NULL },
454 { " abc", NULL, NULL, "invalid format", NULL },
455 { "abc:", NULL, NULL, "invalid format", NULL },
456 { ":abc", NULL, NULL, "invalid format", NULL },
457 { "abc(", NULL, NULL, "invalid format", NULL },
458 { "abc)", NULL, NULL, "invalid format", NULL },
459 { "(abc", NULL, NULL, "invalid format", NULL },
460 { ")abc", NULL, NULL, "invalid format", NULL },
461 { "abc::xyz", "abc", "'xyz'", NULL, "abc::xyz" },
462 { "abc('xyz')", "abc", "'xyz'", NULL, "abc::xyz" },
463 { "abc(42)", "abc", "42", NULL, "abc(42)" },
464 { "abc(int32 42)", "abc", "42", NULL, "abc(42)" },
465 { "abc(@i 42)", "abc", "42", NULL, "abc(42)" },
466 { "abc (42)", NULL, NULL, "invalid format", NULL },
467 { "abc(42abc)", NULL, NULL, "invalid character in number", NULL },
468 { "abc(42, 4)", "abc", "(42, 4)", "expected end of input", NULL },
469 { "abc(42,)", "abc", "(42,)", "expected end of input", NULL }
470 };
471 gint i;
472
473 for (i = 0; i < G_N_ELEMENTS (testcases); i++)
474 {
475 GError *error = NULL;
476 GVariant *target;
477 gboolean success;
478 gchar *name;
479
480 success = g_action_parse_detailed_name (detailed_name: testcases[i].detailed, action_name: &name, target_value: &target, error: &error);
481 g_assert_true (success == (error == NULL));
482 if (success && testcases[i].expected_error)
483 g_error ("Unexpected success on '%s'. Expected error containing '%s'",
484 testcases[i].detailed, testcases[i].expected_error);
485
486 if (!success && !testcases[i].expected_error)
487 g_error ("Unexpected failure on '%s': %s", testcases[i].detailed, error->message);
488
489 if (!success)
490 {
491 if (!strstr (haystack: error->message, needle: testcases[i].expected_error))
492 g_error ("Failure message '%s' for string '%s' did not contained expected substring '%s'",
493 error->message, testcases[i].detailed, testcases[i].expected_error);
494
495 g_error_free (error);
496 continue;
497 }
498
499 g_assert_cmpstr (name, ==, testcases[i].expected_name);
500 g_assert_true ((target == NULL) == (testcases[i].expected_target == NULL));
501
502 if (success)
503 {
504 gchar *detailed;
505
506 detailed = g_action_print_detailed_name (action_name: name, target_value: target);
507 g_assert_cmpstr (detailed, ==, testcases[i].detailed_roundtrip);
508 g_free (mem: detailed);
509 }
510
511 if (target)
512 {
513 GVariant *expected;
514
515 expected = g_variant_parse (NULL, text: testcases[i].expected_target, NULL, NULL, NULL);
516 g_assert_true (expected);
517
518 g_assert_cmpvariant (expected, target);
519 g_variant_unref (value: expected);
520 g_variant_unref (value: target);
521 }
522
523 g_free (mem: name);
524 }
525}
526
527GHashTable *activation_counts;
528
529static void
530count_activation (const gchar *action)
531{
532 gint count;
533
534 if (activation_counts == NULL)
535 activation_counts = g_hash_table_new (hash_func: g_str_hash, key_equal_func: g_str_equal);
536 count = GPOINTER_TO_INT (g_hash_table_lookup (activation_counts, action));
537 count++;
538 g_hash_table_insert (hash_table: activation_counts, key: (gpointer)action, GINT_TO_POINTER (count));
539}
540
541static gint
542activation_count (const gchar *action)
543{
544 if (activation_counts == NULL)
545 return 0;
546
547 return GPOINTER_TO_INT (g_hash_table_lookup (activation_counts, action));
548}
549
550static void
551activate_action (GSimpleAction *action, GVariant *parameter, gpointer user_data)
552{
553 count_activation (action: g_action_get_name (G_ACTION (action)));
554}
555
556static void
557activate_toggle (GSimpleAction *action, GVariant *parameter, gpointer user_data)
558{
559 GVariant *old_state, *new_state;
560
561 count_activation (action: g_action_get_name (G_ACTION (action)));
562
563 old_state = g_action_get_state (G_ACTION (action));
564 new_state = g_variant_new_boolean (value: !g_variant_get_boolean (value: old_state));
565 g_simple_action_set_state (simple: action, value: new_state);
566 g_variant_unref (value: old_state);
567}
568
569static void
570activate_radio (GSimpleAction *action, GVariant *parameter, gpointer user_data)
571{
572 GVariant *new_state;
573
574 count_activation (action: g_action_get_name (G_ACTION (action)));
575
576 new_state = g_variant_new_string (string: g_variant_get_string (value: parameter, NULL));
577 g_simple_action_set_state (simple: action, value: new_state);
578}
579
580static gboolean
581compare_action_groups (GActionGroup *a, GActionGroup *b)
582{
583 gchar **alist;
584 gchar **blist;
585 gint i;
586 gboolean equal;
587 gboolean ares, bres;
588 gboolean aenabled, benabled;
589 const GVariantType *aparameter_type, *bparameter_type;
590 const GVariantType *astate_type, *bstate_type;
591 GVariant *astate_hint, *bstate_hint;
592 GVariant *astate, *bstate;
593
594 alist = g_action_group_list_actions (action_group: a);
595 blist = g_action_group_list_actions (action_group: b);
596 equal = strv_strv_cmp (a: alist, b: blist);
597
598 for (i = 0; equal && alist[i]; i++)
599 {
600 ares = g_action_group_query_action (action_group: a, action_name: alist[i], enabled: &aenabled, parameter_type: &aparameter_type, state_type: &astate_type, state_hint: &astate_hint, state: &astate);
601 bres = g_action_group_query_action (action_group: b, action_name: alist[i], enabled: &benabled, parameter_type: &bparameter_type, state_type: &bstate_type, state_hint: &bstate_hint, state: &bstate);
602
603 if (ares && bres)
604 {
605 equal = equal && (aenabled == benabled);
606 equal = equal && ((!aparameter_type && !bparameter_type) || g_variant_type_equal (type1: aparameter_type, type2: bparameter_type));
607 equal = equal && ((!astate_type && !bstate_type) || g_variant_type_equal (type1: astate_type, type2: bstate_type));
608 equal = equal && ((!astate_hint && !bstate_hint) || g_variant_equal (one: astate_hint, two: bstate_hint));
609 equal = equal && ((!astate && !bstate) || g_variant_equal (one: astate, two: bstate));
610
611 if (astate_hint)
612 g_variant_unref (value: astate_hint);
613 if (bstate_hint)
614 g_variant_unref (value: bstate_hint);
615 if (astate)
616 g_variant_unref (value: astate);
617 if (bstate)
618 g_variant_unref (value: bstate);
619 }
620 else
621 equal = FALSE;
622 }
623
624 g_strfreev (str_array: alist);
625 g_strfreev (str_array: blist);
626
627 return equal;
628}
629
630static gboolean
631stop_loop (gpointer data)
632{
633 GMainLoop *loop = data;
634
635 g_main_loop_quit (loop);
636
637 return G_SOURCE_REMOVE;
638}
639
640static GActionEntry exported_entries[] = {
641 { "undo", activate_action, NULL, NULL, NULL },
642 { "redo", activate_action, NULL, NULL, NULL },
643 { "cut", activate_action, NULL, NULL, NULL },
644 { "copy", activate_action, NULL, NULL, NULL },
645 { "paste", activate_action, NULL, NULL, NULL },
646 { "bold", activate_toggle, NULL, "true", NULL },
647 { "lang", activate_radio, "s", "'latin'", NULL },
648};
649
650static void
651list_cb (GObject *source,
652 GAsyncResult *res,
653 gpointer user_data)
654{
655 GDBusConnection *bus = G_DBUS_CONNECTION (source);
656 GMainLoop *loop = user_data;
657 GError *error = NULL;
658 GVariant *v;
659 gchar **actions;
660
661 v = g_dbus_connection_call_finish (connection: bus, res, error: &error);
662 g_assert_nonnull (v);
663 g_variant_get (value: v, format_string: "(^a&s)", &actions);
664 g_assert_cmpint (g_strv_length (actions), ==, G_N_ELEMENTS (exported_entries));
665 g_free (mem: actions);
666 g_variant_unref (value: v);
667 g_main_loop_quit (loop);
668}
669
670static gboolean
671call_list (gpointer user_data)
672{
673 GDBusConnection *bus;
674
675 bus = g_bus_get_sync (bus_type: G_BUS_TYPE_SESSION, NULL, NULL);
676 g_dbus_connection_call (connection: bus,
677 bus_name: g_dbus_connection_get_unique_name (connection: bus),
678 object_path: "/",
679 interface_name: "org.gtk.Actions",
680 method_name: "List",
681 NULL,
682 NULL,
683 flags: 0,
684 G_MAXINT,
685 NULL,
686 callback: list_cb,
687 user_data);
688 g_object_unref (object: bus);
689
690 return G_SOURCE_REMOVE;
691}
692
693static void
694describe_cb (GObject *source,
695 GAsyncResult *res,
696 gpointer user_data)
697{
698 GDBusConnection *bus = G_DBUS_CONNECTION (source);
699 GMainLoop *loop = user_data;
700 GError *error = NULL;
701 GVariant *v;
702 gboolean enabled;
703 gchar *param;
704 GVariantIter *iter;
705
706 v = g_dbus_connection_call_finish (connection: bus, res, error: &error);
707 g_assert_nonnull (v);
708 /* FIXME: there's an extra level of tuplelization in here */
709 g_variant_get (value: v, format_string: "((bgav))", &enabled, &param, &iter);
710 g_assert_true (enabled);
711 g_assert_cmpstr (param, ==, "");
712 g_assert_cmpint (g_variant_iter_n_children (iter), ==, 0);
713 g_free (mem: param);
714 g_variant_iter_free (iter);
715 g_variant_unref (value: v);
716
717 g_main_loop_quit (loop);
718}
719
720static gboolean
721call_describe (gpointer user_data)
722{
723 GDBusConnection *bus;
724
725 bus = g_bus_get_sync (bus_type: G_BUS_TYPE_SESSION, NULL, NULL);
726 g_dbus_connection_call (connection: bus,
727 bus_name: g_dbus_connection_get_unique_name (connection: bus),
728 object_path: "/",
729 interface_name: "org.gtk.Actions",
730 method_name: "Describe",
731 parameters: g_variant_new (format_string: "(s)", "copy"),
732 NULL,
733 flags: 0,
734 G_MAXINT,
735 NULL,
736 callback: describe_cb,
737 user_data);
738 g_object_unref (object: bus);
739
740 return G_SOURCE_REMOVE;
741}
742
743G_GNUC_BEGIN_IGNORE_DEPRECATIONS
744
745static void
746test_dbus_export (void)
747{
748 GDBusConnection *bus;
749 GSimpleActionGroup *group;
750 GDBusActionGroup *proxy;
751 GSimpleAction *action;
752 GMainLoop *loop;
753 GError *error = NULL;
754 GVariant *v;
755 guint id;
756 gchar **actions;
757
758 loop = g_main_loop_new (NULL, FALSE);
759
760 session_bus_up ();
761 bus = g_bus_get_sync (bus_type: G_BUS_TYPE_SESSION, NULL, NULL);
762
763 group = g_simple_action_group_new ();
764 g_simple_action_group_add_entries (simple: group,
765 entries: exported_entries,
766 G_N_ELEMENTS (exported_entries),
767 NULL);
768
769 id = g_dbus_connection_export_action_group (connection: bus, object_path: "/", G_ACTION_GROUP (group), error: &error);
770 g_assert_no_error (error);
771
772 proxy = g_dbus_action_group_get (connection: bus, bus_name: g_dbus_connection_get_unique_name (connection: bus), object_path: "/");
773
774 actions = g_action_group_list_actions (G_ACTION_GROUP (proxy));
775 g_assert_cmpint (g_strv_length (actions), ==, 0);
776 g_strfreev (str_array: actions);
777
778 g_timeout_add (interval: 100, function: stop_loop, data: loop);
779 g_main_loop_run (loop);
780
781 actions = g_action_group_list_actions (G_ACTION_GROUP (proxy));
782 g_assert_cmpint (g_strv_length (actions), ==, G_N_ELEMENTS (exported_entries));
783 g_strfreev (str_array: actions);
784
785 /* check that calling "List" works too */
786 g_idle_add (function: call_list, data: loop);
787 g_main_loop_run (loop);
788
789 /* check that calling "Describe" works */
790 g_idle_add (function: call_describe, data: loop);
791 g_main_loop_run (loop);
792
793 /* test that the initial transfer works */
794 g_assert_true (G_IS_DBUS_ACTION_GROUP (proxy));
795 g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
796
797 /* test that various changes get propagated from group to proxy */
798 action = g_simple_action_new_stateful (name: "italic", NULL, state: g_variant_new_boolean (FALSE));
799 g_simple_action_group_insert (simple: group, G_ACTION (action));
800 g_object_unref (object: action);
801
802 g_timeout_add (interval: 100, function: stop_loop, data: loop);
803 g_main_loop_run (loop);
804
805 g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
806
807 action = G_SIMPLE_ACTION (g_simple_action_group_lookup (group, "cut"));
808 g_simple_action_set_enabled (simple: action, FALSE);
809
810 g_timeout_add (interval: 100, function: stop_loop, data: loop);
811 g_main_loop_run (loop);
812
813 g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
814
815 action = G_SIMPLE_ACTION (g_simple_action_group_lookup (group, "bold"));
816 g_simple_action_set_state (simple: action, value: g_variant_new_boolean (FALSE));
817
818 g_timeout_add (interval: 100, function: stop_loop, data: loop);
819 g_main_loop_run (loop);
820
821 g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
822
823 g_simple_action_group_remove (simple: group, action_name: "italic");
824
825 g_timeout_add (interval: 100, function: stop_loop, data: loop);
826 g_main_loop_run (loop);
827
828 g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
829
830 /* test that activations and state changes propagate the other way */
831
832 g_assert_cmpint (activation_count ("copy"), ==, 0);
833 g_action_group_activate_action (G_ACTION_GROUP (proxy), action_name: "copy", NULL);
834
835 g_timeout_add (interval: 100, function: stop_loop, data: loop);
836 g_main_loop_run (loop);
837
838 g_assert_cmpint (activation_count ("copy"), ==, 1);
839 g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
840
841 g_assert_cmpint (activation_count ("bold"), ==, 0);
842 g_action_group_activate_action (G_ACTION_GROUP (proxy), action_name: "bold", NULL);
843
844 g_timeout_add (interval: 100, function: stop_loop, data: loop);
845 g_main_loop_run (loop);
846
847 g_assert_cmpint (activation_count ("bold"), ==, 1);
848 g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
849 v = g_action_group_get_action_state (G_ACTION_GROUP (group), action_name: "bold");
850 g_assert_true (g_variant_get_boolean (v));
851 g_variant_unref (value: v);
852
853 g_action_group_change_action_state (G_ACTION_GROUP (proxy), action_name: "bold", value: g_variant_new_boolean (FALSE));
854
855 g_timeout_add (interval: 100, function: stop_loop, data: loop);
856 g_main_loop_run (loop);
857
858 g_assert_cmpint (activation_count ("bold"), ==, 1);
859 g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
860 v = g_action_group_get_action_state (G_ACTION_GROUP (group), action_name: "bold");
861 g_assert_false (g_variant_get_boolean (v));
862 g_variant_unref (value: v);
863
864 g_dbus_connection_unexport_action_group (connection: bus, export_id: id);
865
866 g_object_unref (object: proxy);
867 g_object_unref (object: group);
868 g_main_loop_unref (loop);
869 g_object_unref (object: bus);
870
871 session_bus_down ();
872}
873
874static gpointer
875do_export (gpointer data)
876{
877 GActionGroup *group = data;
878 GMainContext *ctx;
879 gint i;
880 GError *error = NULL;
881 guint id;
882 GDBusConnection *bus;
883 GAction *action;
884 gchar *path;
885
886 ctx = g_main_context_new ();
887
888 g_main_context_push_thread_default (context: ctx);
889
890 bus = g_bus_get_sync (bus_type: G_BUS_TYPE_SESSION, NULL, NULL);
891 path = g_strdup_printf(format: "/%p", data);
892
893 for (i = 0; i < 10000; i++)
894 {
895 id = g_dbus_connection_export_action_group (connection: bus, object_path: path, G_ACTION_GROUP (group), error: &error);
896 g_assert_no_error (error);
897
898 action = g_simple_action_group_lookup (G_SIMPLE_ACTION_GROUP (group), action_name: "a");
899 g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
900 enabled: !g_action_get_enabled (action));
901
902 g_dbus_connection_unexport_action_group (connection: bus, export_id: id);
903
904 while (g_main_context_iteration (context: ctx, FALSE));
905 }
906
907 g_free (mem: path);
908 g_object_unref (object: bus);
909
910 g_main_context_pop_thread_default (context: ctx);
911
912 g_main_context_unref (context: ctx);
913
914 return NULL;
915}
916
917static void
918test_dbus_threaded (void)
919{
920 GSimpleActionGroup *group[10];
921 GThread *export[10];
922 static GActionEntry entries[] = {
923 { "a", activate_action, NULL, NULL, NULL },
924 { "b", activate_action, NULL, NULL, NULL },
925 };
926 gint i;
927
928 session_bus_up ();
929
930 for (i = 0; i < 10; i++)
931 {
932 group[i] = g_simple_action_group_new ();
933 g_simple_action_group_add_entries (simple: group[i], entries, G_N_ELEMENTS (entries), NULL);
934 export[i] = g_thread_new (name: "export", func: do_export, data: group[i]);
935 }
936
937 for (i = 0; i < 10; i++)
938 g_thread_join (thread: export[i]);
939
940 for (i = 0; i < 10; i++)
941 g_object_unref (object: group[i]);
942
943 session_bus_down ();
944}
945
946G_GNUC_END_IGNORE_DEPRECATIONS
947
948static void
949test_bug679509 (void)
950{
951 GDBusConnection *bus;
952 GDBusActionGroup *proxy;
953 GMainLoop *loop;
954
955 loop = g_main_loop_new (NULL, FALSE);
956
957 session_bus_up ();
958 bus = g_bus_get_sync (bus_type: G_BUS_TYPE_SESSION, NULL, NULL);
959
960 proxy = g_dbus_action_group_get (connection: bus, bus_name: g_dbus_connection_get_unique_name (connection: bus), object_path: "/");
961 g_strfreev (str_array: g_action_group_list_actions (G_ACTION_GROUP (proxy)));
962 g_object_unref (object: proxy);
963
964 g_timeout_add (interval: 100, function: stop_loop, data: loop);
965 g_main_loop_run (loop);
966
967 g_main_loop_unref (loop);
968 g_object_unref (object: bus);
969
970 session_bus_down ();
971}
972
973static gchar *state_change_log;
974
975static void
976state_changed (GActionGroup *group,
977 const gchar *action_name,
978 GVariant *value,
979 gpointer user_data)
980{
981 GString *string;
982
983 g_assert_false (state_change_log);
984
985 string = g_string_new (init: action_name);
986 g_string_append_c (string, ':');
987 g_variant_print_string (value, string, TRUE);
988 state_change_log = g_string_free (string, FALSE);
989}
990
991static void
992verify_changed (const gchar *log_entry)
993{
994 g_assert_cmpstr (state_change_log, ==, log_entry);
995 g_clear_pointer (&state_change_log, g_free);
996}
997
998static void
999ensure_state (GSimpleActionGroup *group,
1000 const gchar *action_name,
1001 const gchar *expected)
1002{
1003 GVariant *value;
1004 gchar *printed;
1005
1006 value = g_action_group_get_action_state (G_ACTION_GROUP (group), action_name);
1007 printed = g_variant_print (value, TRUE);
1008 g_variant_unref (value);
1009
1010 g_assert_cmpstr (printed, ==, expected);
1011 g_free (mem: printed);
1012}
1013
1014static void
1015test_property_actions (void)
1016{
1017 GSimpleActionGroup *group;
1018 GPropertyAction *action;
1019 GSocketClient *client;
1020 GApplication *app;
1021 gchar *name;
1022 GVariantType *ptype, *stype;
1023 gboolean enabled;
1024 GVariant *state;
1025
1026 group = g_simple_action_group_new ();
1027 g_signal_connect (group, "action-state-changed", G_CALLBACK (state_changed), NULL);
1028
1029 client = g_socket_client_new ();
1030 app = g_application_new (application_id: "org.gtk.test", flags: 0);
1031
1032 /* string... */
1033 action = g_property_action_new (name: "app-id", object: app, property_name: "application-id");
1034 g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
1035 g_object_unref (object: action);
1036
1037 /* uint... */
1038 action = g_property_action_new (name: "keepalive", object: app, property_name: "inactivity-timeout");
1039 g_object_get (object: action, first_property_name: "name", &name, "parameter-type", &ptype, "enabled", &enabled, "state-type", &stype, "state", &state, NULL);
1040 g_assert_cmpstr (name, ==, "keepalive");
1041 g_assert_true (enabled);
1042 g_free (mem: name);
1043 g_variant_type_free (type: ptype);
1044 g_variant_type_free (type: stype);
1045 g_variant_unref (value: state);
1046
1047 g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
1048 g_object_unref (object: action);
1049
1050 /* bool... */
1051 action = g_property_action_new (name: "tls", object: client, property_name: "tls");
1052 g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
1053 g_object_unref (object: action);
1054
1055 /* inverted */
1056 action = g_object_new (G_TYPE_PROPERTY_ACTION,
1057 first_property_name: "name", "disable-proxy",
1058 "object", client,
1059 "property-name", "enable-proxy",
1060 "invert-boolean", TRUE,
1061 NULL);
1062 g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
1063 g_object_unref (object: action);
1064
1065 /* enum... */
1066 action = g_property_action_new (name: "type", object: client, property_name: "type");
1067 g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
1068 g_object_unref (object: action);
1069
1070 /* the objects should be held alive by the actions... */
1071 g_object_unref (object: client);
1072 g_object_unref (object: app);
1073
1074 ensure_state (group, action_name: "app-id", expected: "'org.gtk.test'");
1075 ensure_state (group, action_name: "keepalive", expected: "uint32 0");
1076 ensure_state (group, action_name: "tls", expected: "false");
1077 ensure_state (group, action_name: "disable-proxy", expected: "false");
1078 ensure_state (group, action_name: "type", expected: "'stream'");
1079
1080 verify_changed (NULL);
1081
1082 /* some string tests... */
1083 g_action_group_change_action_state (G_ACTION_GROUP (group), action_name: "app-id", value: g_variant_new (format_string: "s", "org.gtk.test2"));
1084 verify_changed (log_entry: "app-id:'org.gtk.test2'");
1085 g_assert_cmpstr (g_application_get_application_id (app), ==, "org.gtk.test2");
1086 ensure_state (group, action_name: "app-id", expected: "'org.gtk.test2'");
1087
1088 g_action_group_activate_action (G_ACTION_GROUP (group), action_name: "app-id", parameter: g_variant_new (format_string: "s", "org.gtk.test3"));
1089 verify_changed (log_entry: "app-id:'org.gtk.test3'");
1090 g_assert_cmpstr (g_application_get_application_id (app), ==, "org.gtk.test3");
1091 ensure_state (group, action_name: "app-id", expected: "'org.gtk.test3'");
1092
1093 g_application_set_application_id (application: app, application_id: "org.gtk.test");
1094 verify_changed (log_entry: "app-id:'org.gtk.test'");
1095 ensure_state (group, action_name: "app-id", expected: "'org.gtk.test'");
1096
1097 /* uint tests */
1098 g_action_group_change_action_state (G_ACTION_GROUP (group), action_name: "keepalive", value: g_variant_new (format_string: "u", 1234));
1099 verify_changed (log_entry: "keepalive:uint32 1234");
1100 g_assert_cmpuint (g_application_get_inactivity_timeout (app), ==, 1234);
1101 ensure_state (group, action_name: "keepalive", expected: "uint32 1234");
1102
1103 g_action_group_activate_action (G_ACTION_GROUP (group), action_name: "keepalive", parameter: g_variant_new (format_string: "u", 5678));
1104 verify_changed (log_entry: "keepalive:uint32 5678");
1105 g_assert_cmpuint (g_application_get_inactivity_timeout (app), ==, 5678);
1106 ensure_state (group, action_name: "keepalive", expected: "uint32 5678");
1107
1108 g_application_set_inactivity_timeout (application: app, inactivity_timeout: 0);
1109 verify_changed (log_entry: "keepalive:uint32 0");
1110 ensure_state (group, action_name: "keepalive", expected: "uint32 0");
1111
1112 /* bool tests */
1113 g_action_group_change_action_state (G_ACTION_GROUP (group), action_name: "tls", value: g_variant_new (format_string: "b", TRUE));
1114 verify_changed (log_entry: "tls:true");
1115 g_assert_true (g_socket_client_get_tls (client));
1116 ensure_state (group, action_name: "tls", expected: "true");
1117
1118 g_action_group_change_action_state (G_ACTION_GROUP (group), action_name: "disable-proxy", value: g_variant_new (format_string: "b", TRUE));
1119 verify_changed (log_entry: "disable-proxy:true");
1120 ensure_state (group, action_name: "disable-proxy", expected: "true");
1121 g_assert_false (g_socket_client_get_enable_proxy (client));
1122
1123 /* test toggle true->false */
1124 g_action_group_activate_action (G_ACTION_GROUP (group), action_name: "tls", NULL);
1125 verify_changed (log_entry: "tls:false");
1126 g_assert_false (g_socket_client_get_tls (client));
1127 ensure_state (group, action_name: "tls", expected: "false");
1128
1129 /* and now back false->true */
1130 g_action_group_activate_action (G_ACTION_GROUP (group), action_name: "tls", NULL);
1131 verify_changed (log_entry: "tls:true");
1132 g_assert_true (g_socket_client_get_tls (client));
1133 ensure_state (group, action_name: "tls", expected: "true");
1134
1135 g_socket_client_set_tls (client, FALSE);
1136 verify_changed (log_entry: "tls:false");
1137 ensure_state (group, action_name: "tls", expected: "false");
1138
1139 /* now do the same for the inverted action */
1140 g_action_group_activate_action (G_ACTION_GROUP (group), action_name: "disable-proxy", NULL);
1141 verify_changed (log_entry: "disable-proxy:false");
1142 g_assert_true (g_socket_client_get_enable_proxy (client));
1143 ensure_state (group, action_name: "disable-proxy", expected: "false");
1144
1145 g_action_group_activate_action (G_ACTION_GROUP (group), action_name: "disable-proxy", NULL);
1146 verify_changed (log_entry: "disable-proxy:true");
1147 g_assert_false (g_socket_client_get_enable_proxy (client));
1148 ensure_state (group, action_name: "disable-proxy", expected: "true");
1149
1150 g_socket_client_set_enable_proxy (client, TRUE);
1151 verify_changed (log_entry: "disable-proxy:false");
1152 ensure_state (group, action_name: "disable-proxy", expected: "false");
1153
1154 /* enum tests */
1155 g_action_group_change_action_state (G_ACTION_GROUP (group), action_name: "type", value: g_variant_new (format_string: "s", "datagram"));
1156 verify_changed (log_entry: "type:'datagram'");
1157 g_assert_cmpint (g_socket_client_get_socket_type (client), ==, G_SOCKET_TYPE_DATAGRAM);
1158 ensure_state (group, action_name: "type", expected: "'datagram'");
1159
1160 g_action_group_activate_action (G_ACTION_GROUP (group), action_name: "type", parameter: g_variant_new (format_string: "s", "stream"));
1161 verify_changed (log_entry: "type:'stream'");
1162 g_assert_cmpint (g_socket_client_get_socket_type (client), ==, G_SOCKET_TYPE_STREAM);
1163 ensure_state (group, action_name: "type", expected: "'stream'");
1164
1165 g_socket_client_set_socket_type (client, type: G_SOCKET_TYPE_SEQPACKET);
1166 verify_changed (log_entry: "type:'seqpacket'");
1167 ensure_state (group, action_name: "type", expected: "'seqpacket'");
1168
1169 /* Check some error cases... */
1170 g_test_expect_message (log_domain: "GLib-GIO", log_level: G_LOG_LEVEL_CRITICAL, pattern: "*non-existent*");
1171 action = g_property_action_new (name: "foo", object: app, property_name: "xyz");
1172 g_test_assert_expected_messages ();
1173 g_object_unref (object: action);
1174
1175 g_test_expect_message (log_domain: "GLib-GIO", log_level: G_LOG_LEVEL_CRITICAL, pattern: "*writable*");
1176 action = g_property_action_new (name: "foo", object: app, property_name: "is-registered");
1177 g_test_assert_expected_messages ();
1178 g_object_unref (object: action);
1179
1180 g_test_expect_message (log_domain: "GLib-GIO", log_level: G_LOG_LEVEL_CRITICAL, pattern: "*type 'GSocketAddress'*");
1181 action = g_property_action_new (name: "foo", object: client, property_name: "local-address");
1182 g_test_assert_expected_messages ();
1183 g_object_unref (object: action);
1184
1185 g_object_unref (object: group);
1186}
1187
1188int
1189main (int argc, char **argv)
1190{
1191 g_test_init (argc: &argc, argv: &argv, NULL);
1192
1193 g_test_add_func (testpath: "/actions/basic", test_func: test_basic);
1194 g_test_add_func (testpath: "/actions/name", test_func: test_name);
1195 g_test_add_func (testpath: "/actions/simplegroup", test_func: test_simple_group);
1196 g_test_add_func (testpath: "/actions/stateful", test_func: test_stateful);
1197 g_test_add_func (testpath: "/actions/default-activate", test_func: test_default_activate);
1198 g_test_add_func (testpath: "/actions/entries", test_func: test_entries);
1199 g_test_add_func (testpath: "/actions/parse-detailed", test_func: test_parse_detailed);
1200 g_test_add_func (testpath: "/actions/dbus/export", test_func: test_dbus_export);
1201 g_test_add_func (testpath: "/actions/dbus/threaded", test_func: test_dbus_threaded);
1202 g_test_add_func (testpath: "/actions/dbus/bug679509", test_func: test_bug679509);
1203 g_test_add_func (testpath: "/actions/property", test_func: test_property_actions);
1204
1205 return g_test_run ();
1206}
1207

source code of gtk/subprojects/glib/gio/tests/actions.c