1 #include <gio/gio.h>
2 #include <gio/gunixsocketaddress.h>
3 #include <glib/gstdio.h>
4 #include <string.h>
5
6 #include "gdbus-sessionbus.h"
7
8 #include "glib/glib-private.h"
9
10 static void
11 time_out (gpointer unused G_GNUC_UNUSED)
12 {
13 g_error ("Timed out");
14 }
15
16 static guint
17 add_timeout (guint seconds)
18 {
19 #ifdef G_OS_UNIX
20 /* Safety-catch against the main loop having blocked */
21 alarm (seconds + 5);
22 #endif
23 return g_timeout_add_seconds_once (seconds, time_out, NULL);
24 }
25
26 static void
27 cancel_timeout (guint timeout_id)
28 {
29 #ifdef G_OS_UNIX
30 alarm (0);
31 #endif
32 g_source_remove (timeout_id);
33 }
34
35 /* Markup printing {{{1 */
36
37 /* This used to be part of GLib, but it was removed before the stable
38 * release because it wasn't generally useful. We want it here, though.
39 */
40 static void
41 indent_string (GString *string,
42 gint indent)
43 {
44 while (indent--)
45 g_string_append_c (string, ' ');
46 }
47
48 static GString *
49 g_menu_markup_print_string (GString *string,
50 GMenuModel *model,
51 gint indent,
52 gint tabstop)
53 {
54 gboolean need_nl = FALSE;
55 gint i, n;
56
57 if G_UNLIKELY (string == NULL)
58 string = g_string_new (NULL);
59
60 n = g_menu_model_get_n_items (model);
61
62 for (i = 0; i < n; i++)
63 {
64 GMenuAttributeIter *attr_iter;
65 GMenuLinkIter *link_iter;
66 GString *contents;
67 GString *attrs;
68
69 attr_iter = g_menu_model_iterate_item_attributes (model, i);
70 link_iter = g_menu_model_iterate_item_links (model, i);
71 contents = g_string_new (NULL);
72 attrs = g_string_new (NULL);
73
74 while (g_menu_attribute_iter_next (attr_iter))
75 {
76 const char *name = g_menu_attribute_iter_get_name (attr_iter);
77 GVariant *value = g_menu_attribute_iter_get_value (attr_iter);
78
79 if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
80 {
81 gchar *str;
82 str = g_markup_printf_escaped (" %s='%s'", name, g_variant_get_string (value, NULL));
83 g_string_append (attrs, str);
84 g_free (str);
85 }
86
87 else
88 {
89 gchar *printed;
90 gchar *str;
91 const gchar *type;
92
93 printed = g_variant_print (value, TRUE);
94 type = g_variant_type_peek_string (g_variant_get_type (value));
95 str = g_markup_printf_escaped ("<attribute name='%s' type='%s'>%s</attribute>\n", name, type, printed);
96 indent_string (contents, indent + tabstop);
97 g_string_append (contents, str);
98 g_free (printed);
99 g_free (str);
100 }
101
102 g_variant_unref (value);
103 }
104 g_object_unref (attr_iter);
105
106 while (g_menu_link_iter_next (link_iter))
107 {
108 const gchar *name = g_menu_link_iter_get_name (link_iter);
109 GMenuModel *menu = g_menu_link_iter_get_value (link_iter);
110 gchar *str;
111
112 if (contents->str[0])
113 g_string_append_c (contents, '\n');
114
115 str = g_markup_printf_escaped ("<link name='%s'>\n", name);
116 indent_string (contents, indent + tabstop);
117 g_string_append (contents, str);
118 g_free (str);
119
120 g_menu_markup_print_string (contents, menu, indent + 2 * tabstop, tabstop);
121
122 indent_string (contents, indent + tabstop);
123 g_string_append (contents, "</link>\n");
124 g_object_unref (menu);
125 }
126 g_object_unref (link_iter);
127
128 if (contents->str[0])
129 {
130 indent_string (string, indent);
131 g_string_append_printf (string, "<item%s>\n", attrs->str);
132 g_string_append (string, contents->str);
133 indent_string (string, indent);
134 g_string_append (string, "</item>\n");
135 need_nl = TRUE;
136 }
137
138 else
139 {
140 if (need_nl)
141 g_string_append_c (string, '\n');
142
143 indent_string (string, indent);
144 g_string_append_printf (string, "<item%s/>\n", attrs->str);
145 need_nl = FALSE;
146 }
147
148 g_string_free (contents, TRUE);
149 g_string_free (attrs, TRUE);
150 }
151
152 return string;
153 }
154
155 /* TestItem {{{1 */
156
157 /* This utility struct is used by both the RandomMenu and MirrorMenu
158 * class implementations below.
159 */
160 typedef struct {
161 GHashTable *attributes;
162 GHashTable *links;
163 } TestItem;
164
165 static TestItem *
166 test_item_new (GHashTable *attributes,
167 GHashTable *links)
168 {
169 TestItem *item;
170
171 item = g_slice_new (TestItem);
172 item->attributes = g_hash_table_ref (attributes);
173 item->links = g_hash_table_ref (links);
174
175 return item;
176 }
177
178 static void
179 test_item_free (gpointer data)
180 {
181 TestItem *item = data;
182
183 g_hash_table_unref (item->attributes);
184 g_hash_table_unref (item->links);
185
186 g_slice_free (TestItem, item);
187 }
188
189 /* RandomMenu {{{1 */
190 #define MAX_ITEMS 5
191 #define TOP_ORDER 4
192
193 typedef struct {
194 GMenuModel parent_instance;
195
196 GSequence *items;
197 gint order;
198 } RandomMenu;
199
200 typedef GMenuModelClass RandomMenuClass;
201
202 static GType random_menu_get_type (void);
203 G_DEFINE_TYPE (RandomMenu, random_menu, G_TYPE_MENU_MODEL)
204
205 static gboolean
206 random_menu_is_mutable (GMenuModel *model)
207 {
208 return TRUE;
209 }
210
211 static gint
212 random_menu_get_n_items (GMenuModel *model)
213 {
214 RandomMenu *menu = (RandomMenu *) model;
215
216 return g_sequence_get_length (menu->items);
217 }
218
219 static void
220 random_menu_get_item_attributes (GMenuModel *model,
221 gint position,
222 GHashTable **table)
223 {
224 RandomMenu *menu = (RandomMenu *) model;
225 TestItem *item;
226
227 item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
228 *table = g_hash_table_ref (item->attributes);
229 }
230
231 static void
232 random_menu_get_item_links (GMenuModel *model,
233 gint position,
234 GHashTable **table)
235 {
236 RandomMenu *menu = (RandomMenu *) model;
237 TestItem *item;
238
239 item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
240 *table = g_hash_table_ref (item->links);
241 }
242
243 static void
244 random_menu_finalize (GObject *object)
245 {
246 RandomMenu *menu = (RandomMenu *) object;
247
248 g_sequence_free (menu->items);
249
250 G_OBJECT_CLASS (random_menu_parent_class)
251 ->finalize (object);
252 }
253
254 static void
255 random_menu_init (RandomMenu *menu)
256 {
257 }
258
259 static void
260 random_menu_class_init (GMenuModelClass *class)
261 {
262 GObjectClass *object_class = G_OBJECT_CLASS (class);
263
264 class->is_mutable = random_menu_is_mutable;
265 class->get_n_items = random_menu_get_n_items;
266 class->get_item_attributes = random_menu_get_item_attributes;
267 class->get_item_links = random_menu_get_item_links;
268
269 object_class->finalize = random_menu_finalize;
270 }
271
272 static RandomMenu * random_menu_new (GRand *rand, gint order);
273
274 static void
275 random_menu_change (RandomMenu *menu,
276 GRand *rand)
277 {
278 gint position, removes, adds;
279 GSequenceIter *point;
280 gint n_items;
281 gint i;
282
283 n_items = g_sequence_get_length (menu->items);
284
285 do
286 {
287 position = g_rand_int_range (rand, 0, n_items + 1);
288 removes = g_rand_int_range (rand, 0, n_items - position + 1);
289 adds = g_rand_int_range (rand, 0, MAX_ITEMS - (n_items - removes) + 1);
290 }
291 while (removes == 0 && adds == 0);
292
293 point = g_sequence_get_iter_at_pos (menu->items, position + removes);
294
295 if (removes)
296 {
297 GSequenceIter *start;
298
299 start = g_sequence_get_iter_at_pos (menu->items, position);
300 g_sequence_remove_range (start, point);
301 }
302
303 for (i = 0; i < adds; i++)
304 {
305 const gchar *label;
306 GHashTable *links;
307 GHashTable *attributes;
308
309 attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
310 links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
311
312 if (menu->order > 0 && g_rand_boolean (rand))
313 {
314 RandomMenu *child;
315 const gchar *subtype;
316
317 child = random_menu_new (rand, menu->order - 1);
318
319 if (g_rand_boolean (rand))
320 {
321 subtype = G_MENU_LINK_SECTION;
322 /* label some section headers */
323 if (g_rand_boolean (rand))
324 label = "Section";
325 else
326 label = NULL;
327 }
328 else
329 {
330 /* label all submenus */
331 subtype = G_MENU_LINK_SUBMENU;
332 label = "Submenu";
333 }
334
335 g_hash_table_insert (links, g_strdup (subtype), child);
336 }
337 else
338 /* label all terminals */
339 label = "Menu Item";
340
341 if (label)
342 g_hash_table_insert (attributes, g_strdup ("label"), g_variant_ref_sink (g_variant_new_string (label)));
343
344 g_sequence_insert_before (point, test_item_new (attributes, links));
345 g_hash_table_unref (links);
346 g_hash_table_unref (attributes);
347 }
348
349 g_menu_model_items_changed (G_MENU_MODEL (menu), position, removes, adds);
350 }
351
352 static RandomMenu *
353 random_menu_new (GRand *rand,
354 gint order)
355 {
356 RandomMenu *menu;
357
358 menu = g_object_new (random_menu_get_type (), NULL);
359 menu->items = g_sequence_new (test_item_free);
360 menu->order = order;
361
362 random_menu_change (menu, rand);
363
364 return menu;
365 }
366
367 /* MirrorMenu {{{1 */
368 typedef struct {
369 GMenuModel parent_instance;
370
371 GMenuModel *clone_of;
372 GSequence *items;
373 gulong handler_id;
374 } MirrorMenu;
375
376 typedef GMenuModelClass MirrorMenuClass;
377
378 static GType mirror_menu_get_type (void);
379 G_DEFINE_TYPE (MirrorMenu, mirror_menu, G_TYPE_MENU_MODEL)
380
381 static gboolean
382 mirror_menu_is_mutable (GMenuModel *model)
383 {
384 MirrorMenu *menu = (MirrorMenu *) model;
385
386 return menu->handler_id != 0;
387 }
388
389 static gint
390 mirror_menu_get_n_items (GMenuModel *model)
391 {
392 MirrorMenu *menu = (MirrorMenu *) model;
393
394 return g_sequence_get_length (menu->items);
395 }
396
397 static void
398 mirror_menu_get_item_attributes (GMenuModel *model,
399 gint position,
400 GHashTable **table)
401 {
402 MirrorMenu *menu = (MirrorMenu *) model;
403 TestItem *item;
404
405 item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
406 *table = g_hash_table_ref (item->attributes);
407 }
408
409 static void
410 mirror_menu_get_item_links (GMenuModel *model,
411 gint position,
412 GHashTable **table)
413 {
414 MirrorMenu *menu = (MirrorMenu *) model;
415 TestItem *item;
416
417 item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
418 *table = g_hash_table_ref (item->links);
419 }
420
421 static void
422 mirror_menu_finalize (GObject *object)
423 {
424 MirrorMenu *menu = (MirrorMenu *) object;
425
426 if (menu->handler_id)
427 g_signal_handler_disconnect (menu->clone_of, menu->handler_id);
428
429 g_sequence_free (menu->items);
430 g_object_unref (menu->clone_of);
431
432 G_OBJECT_CLASS (mirror_menu_parent_class)
433 ->finalize (object);
434 }
435
436 static void
437 mirror_menu_init (MirrorMenu *menu)
438 {
439 }
440
441 static void
442 mirror_menu_class_init (GMenuModelClass *class)
443 {
444 GObjectClass *object_class = G_OBJECT_CLASS (class);
445
446 class->is_mutable = mirror_menu_is_mutable;
447 class->get_n_items = mirror_menu_get_n_items;
448 class->get_item_attributes = mirror_menu_get_item_attributes;
449 class->get_item_links = mirror_menu_get_item_links;
450
451 object_class->finalize = mirror_menu_finalize;
452 }
453
454 static MirrorMenu * mirror_menu_new (GMenuModel *clone_of);
455
456 static void
457 mirror_menu_changed (GMenuModel *model,
458 gint position,
459 gint removed,
460 gint added,
461 gpointer user_data)
462 {
463 MirrorMenu *menu = user_data;
464 GSequenceIter *point;
465 gint i;
466
467 g_assert (model == menu->clone_of);
468
469 point = g_sequence_get_iter_at_pos (menu->items, position + removed);
470
471 if (removed)
472 {
473 GSequenceIter *start;
474
475 start = g_sequence_get_iter_at_pos (menu->items, position);
476 g_sequence_remove_range (start, point);
477 }
478
479 for (i = position; i < position + added; i++)
480 {
481 GMenuAttributeIter *attr_iter;
482 GMenuLinkIter *link_iter;
483 GHashTable *links;
484 GHashTable *attributes;
485 const gchar *name;
486 GMenuModel *child;
487 GVariant *value;
488
489 attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
490 links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
491
492 attr_iter = g_menu_model_iterate_item_attributes (model, i);
493 while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
494 {
495 g_hash_table_insert (attributes, g_strdup (name), value);
496 }
497 g_object_unref (attr_iter);
498
499 link_iter = g_menu_model_iterate_item_links (model, i);
500 while (g_menu_link_iter_get_next (link_iter, &name, &child))
501 {
502 g_hash_table_insert (links, g_strdup (name), mirror_menu_new (child));
503 g_object_unref (child);
504 }
505 g_object_unref (link_iter);
506
507 g_sequence_insert_before (point, test_item_new (attributes, links));
508 g_hash_table_unref (attributes);
509 g_hash_table_unref (links);
510 }
511
512 g_menu_model_items_changed (G_MENU_MODEL (menu), position, removed, added);
513 }
514
515 static MirrorMenu *
516 mirror_menu_new (GMenuModel *clone_of)
517 {
518 MirrorMenu *menu;
519
520 menu = g_object_new (mirror_menu_get_type (), NULL);
521 menu->items = g_sequence_new (test_item_free);
522 menu->clone_of = g_object_ref (clone_of);
523
524 if (g_menu_model_is_mutable (clone_of))
525 menu->handler_id = g_signal_connect (clone_of, "items-changed", G_CALLBACK (mirror_menu_changed), menu);
526 mirror_menu_changed (clone_of, 0, 0, g_menu_model_get_n_items (clone_of), menu);
527
528 return menu;
529 }
530
531 /* check_menus_equal(), assert_menus_equal() {{{1 */
532 static gboolean
533 check_menus_equal (GMenuModel *a,
534 GMenuModel *b)
535 {
536 gboolean equal = TRUE;
537 gint a_n, b_n;
538 gint i;
539
540 a_n = g_menu_model_get_n_items (a);
541 b_n = g_menu_model_get_n_items (b);
542
543 if (a_n != b_n)
544 return FALSE;
545
546 for (i = 0; i < a_n; i++)
547 {
548 GMenuAttributeIter *attr_iter;
549 GVariant *a_value, *b_value;
550 GMenuLinkIter *link_iter;
551 GMenuModel *a_menu, *b_menu;
552 const gchar *name;
553
554 attr_iter = g_menu_model_iterate_item_attributes (a, i);
555 while (g_menu_attribute_iter_get_next (attr_iter, &name, &a_value))
556 {
557 b_value = g_menu_model_get_item_attribute_value (b, i, name, NULL);
558 equal &= b_value && g_variant_equal (a_value, b_value);
559 if (b_value)
560 g_variant_unref (b_value);
561 g_variant_unref (a_value);
562 }
563 g_object_unref (attr_iter);
564
565 attr_iter = g_menu_model_iterate_item_attributes (b, i);
566 while (g_menu_attribute_iter_get_next (attr_iter, &name, &b_value))
567 {
568 a_value = g_menu_model_get_item_attribute_value (a, i, name, NULL);
569 equal &= a_value && g_variant_equal (a_value, b_value);
570 if (a_value)
571 g_variant_unref (a_value);
572 g_variant_unref (b_value);
573 }
574 g_object_unref (attr_iter);
575
576 link_iter = g_menu_model_iterate_item_links (a, i);
577 while (g_menu_link_iter_get_next (link_iter, &name, &a_menu))
578 {
579 b_menu = g_menu_model_get_item_link (b, i, name);
580 equal &= b_menu && check_menus_equal (a_menu, b_menu);
581 if (b_menu)
582 g_object_unref (b_menu);
583 g_object_unref (a_menu);
584 }
585 g_object_unref (link_iter);
586
587 link_iter = g_menu_model_iterate_item_links (b, i);
588 while (g_menu_link_iter_get_next (link_iter, &name, &b_menu))
589 {
590 a_menu = g_menu_model_get_item_link (a, i, name);
591 equal &= a_menu && check_menus_equal (a_menu, b_menu);
592 if (a_menu)
593 g_object_unref (a_menu);
594 g_object_unref (b_menu);
595 }
596 g_object_unref (link_iter);
597 }
598
599 return equal;
600 }
601
602 static void
603 assert_menus_equal (GMenuModel *a,
604 GMenuModel *b)
605 {
606 if (!check_menus_equal (a, b))
607 {
608 GString *string;
609
610 string = g_string_new ("\n <a>\n");
611 g_menu_markup_print_string (string, G_MENU_MODEL (a), 4, 2);
612 g_string_append (string, " </a>\n\n-------------\n <b>\n");
613 g_menu_markup_print_string (string, G_MENU_MODEL (b), 4, 2);
614 g_string_append (string, " </b>\n");
615 g_error ("%s", string->str);
616 }
617 }
618
619 static void
620 assert_menuitem_equal (GMenuItem *item,
621 GMenuModel *model,
622 gint index)
623 {
624 GMenuAttributeIter *attr_iter;
625 GMenuLinkIter *link_iter;
626 const gchar *name;
627 GVariant *value;
628 GMenuModel *linked_model;
629
630 /* NOTE we can't yet test whether item has attributes or links that
631 * are not in the model, because there's no iterator API for menu
632 * items */
633
634 attr_iter = g_menu_model_iterate_item_attributes (model, index);
635 while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
636 {
637 GVariant *item_value;
638
639 item_value = g_menu_item_get_attribute_value (item, name, g_variant_get_type (value));
640 g_assert (item_value && g_variant_equal (item_value, value));
641
642 g_variant_unref (item_value);
643 g_variant_unref (value);
644 }
645
646 link_iter = g_menu_model_iterate_item_links (model, index);
647 while (g_menu_link_iter_get_next (link_iter, &name, &linked_model))
648 {
649 GMenuModel *item_linked_model;
650
651 item_linked_model = g_menu_item_get_link (item, name);
652 g_assert (linked_model == item_linked_model);
653
654 g_object_unref (item_linked_model);
655 g_object_unref (linked_model);
656 }
657
658 g_object_unref (attr_iter);
659 g_object_unref (link_iter);
660 }
661
662 /* Test cases {{{1 */
663 static void
664 test_equality (void)
665 {
666 GRand *randa, *randb;
667 guint32 seed;
668 gint i;
669
670 seed = g_test_rand_int ();
671
672 randa = g_rand_new_with_seed (seed);
673 randb = g_rand_new_with_seed (seed);
674
675 for (i = 0; i < 500; i++)
676 {
677 RandomMenu *a, *b;
678
679 a = random_menu_new (randa, TOP_ORDER);
680 b = random_menu_new (randb, TOP_ORDER);
681 assert_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b));
682 g_object_unref (b);
683 g_object_unref (a);
684 }
685
686 g_rand_int (randa);
687
688 for (i = 0; i < 500;)
689 {
690 RandomMenu *a, *b;
691
692 a = random_menu_new (randa, TOP_ORDER);
693 b = random_menu_new (randb, TOP_ORDER);
694 if (check_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b)))
695 {
696 /* by chance, they may really be equal. double check. */
697 GString *as, *bs;
698
699 as = g_menu_markup_print_string (NULL, G_MENU_MODEL (a), 4, 2);
700 bs = g_menu_markup_print_string (NULL, G_MENU_MODEL (b), 4, 2);
701 g_assert_cmpstr (as->str, ==, bs->str);
702 g_string_free (bs, TRUE);
703 g_string_free (as, TRUE);
704
705 /* we're here because randa and randb just generated equal
706 * menus. they may do it again, so throw away randb and make
707 * a fresh one.
708 */
709 g_rand_free (randb);
710 randb = g_rand_new_with_seed (g_rand_int (randa));
711 }
712 else
713 /* make sure we get enough unequals (ie: no GRand failure) */
714 i++;
715
716 g_object_unref (b);
717 g_object_unref (a);
718 }
719
720 g_rand_free (randb);
721 g_rand_free (randa);
722 }
723
724 static void
725 test_random (void)
726 {
727 RandomMenu *random;
728 MirrorMenu *mirror;
729 GRand *rand;
730 gint i;
731
732 rand = g_rand_new_with_seed (g_test_rand_int ());
733 random = random_menu_new (rand, TOP_ORDER);
734 mirror = mirror_menu_new (G_MENU_MODEL (random));
735
736 for (i = 0; i < 500; i++)
737 {
738 assert_menus_equal (G_MENU_MODEL (random), G_MENU_MODEL (mirror));
739 random_menu_change (random, rand);
740 }
741
742 g_object_unref (mirror);
743 g_object_unref (random);
744
745 g_rand_free (rand);
746 }
747
748 typedef struct
749 {
750 GDBusConnection *client_connection;
751 GDBusConnection *server_connection;
752 GDBusServer *server;
753
754 GThread *service_thread;
755 /* Protects server_connection and service_loop. */
756 GMutex service_loop_lock;
757 GCond service_loop_cond;
758
759 GMainLoop *service_loop;
760 } PeerConnection;
761
762 static gboolean
763 on_new_connection (GDBusServer *server,
764 GDBusConnection *connection,
765 gpointer user_data)
766 {
767 PeerConnection *data = user_data;
768
769 g_mutex_lock (&data->service_loop_lock);
770 data->server_connection = g_object_ref (connection);
771 g_cond_broadcast (&data->service_loop_cond);
772 g_mutex_unlock (&data->service_loop_lock);
773
774 return TRUE;
775 }
776
777 static void
778 create_service_loop (GMainContext *service_context,
779 PeerConnection *data)
780 {
781 g_assert (data->service_loop == NULL);
782 g_mutex_lock (&data->service_loop_lock);
783 data->service_loop = g_main_loop_new (service_context, FALSE);
784 g_cond_broadcast (&data->service_loop_cond);
785 g_mutex_unlock (&data->service_loop_lock);
786 }
787
788 static void
789 teardown_service_loop (PeerConnection *data)
790 {
791 g_mutex_lock (&data->service_loop_lock);
792 g_clear_pointer (&data->service_loop, g_main_loop_unref);
793 g_mutex_unlock (&data->service_loop_lock);
794 }
795
796 static void
797 await_service_loop (PeerConnection *data)
798 {
799 g_mutex_lock (&data->service_loop_lock);
800 while (data->service_loop == NULL)
801 g_cond_wait (&data->service_loop_cond, &data->service_loop_lock);
802 g_mutex_unlock (&data->service_loop_lock);
803 }
804
805 static void
806 await_server_connection (PeerConnection *data)
807 {
808 g_mutex_lock (&data->service_loop_lock);
809 while (data->server_connection == NULL)
810 g_cond_wait (&data->service_loop_cond, &data->service_loop_lock);
811 g_mutex_unlock (&data->service_loop_lock);
812 }
813
814 static gpointer
815 service_thread_func (gpointer user_data)
816 {
817 PeerConnection *data = user_data;
818 GMainContext *service_context;
819 GError *error;
820 gchar *address;
821 gchar *tmpdir;
822 GDBusServerFlags flags;
823 gchar *guid;
824
825 service_context = g_main_context_new ();
826 g_main_context_push_thread_default (service_context);
827
828 tmpdir = NULL;
829 flags = G_DBUS_SERVER_FLAGS_NONE;
830
831 #ifdef G_OS_UNIX
832 tmpdir = g_dir_make_tmp ("test-dbus-peer-XXXXXX", NULL);
833 address = g_strdup_printf ("unix:tmpdir=%s", tmpdir);
834 #else
835 address = g_strdup ("nonce-tcp:");
836 flags |= G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS;
837 #endif
838
839 guid = g_dbus_generate_guid ();
840
841 error = NULL;
842 data->server = g_dbus_server_new_sync (address,
843 flags,
844 guid,
845 NULL,
846 NULL,
847 &error);
848 g_assert_no_error (error);
849 g_free (address);
850 g_free (guid);
851
852 g_signal_connect (data->server,
853 "new-connection",
854 G_CALLBACK (on_new_connection),
855 data);
856
857 g_dbus_server_start (data->server);
858
859 create_service_loop (service_context, data);
860 g_main_loop_run (data->service_loop);
861
862 g_main_context_pop_thread_default (service_context);
863
864 teardown_service_loop (data);
865 g_main_context_unref (service_context);
866
867 if (tmpdir)
868 {
869 g_rmdir (tmpdir);
870 g_free (tmpdir);
871 }
872
873 return NULL;
874 }
875
876 static void
877 peer_connection_up (PeerConnection *data)
878 {
879 GError *error;
880
881 memset (data, '\0', sizeof (PeerConnection));
882
883 g_mutex_init (&data->service_loop_lock);
884 g_cond_init (&data->service_loop_cond);
885
886 /* bring up a server - we run the server in a different thread to
887 avoid deadlocks */
888 data->service_thread = g_thread_new ("test_dbus_peer",
889 service_thread_func,
890 data);
891 await_service_loop (data);
892 g_assert (data->server != NULL);
893
894 /* bring up a connection and accept it */
895 error = NULL;
896 data->client_connection =
897 g_dbus_connection_new_for_address_sync (g_dbus_server_get_client_address (data->server),
898 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
899 NULL, /* GDBusAuthObserver */
900 NULL, /* cancellable */
901 &error);
902 g_assert_no_error (error);
903 g_assert (data->client_connection != NULL);
904 await_server_connection (data);
905 }
906
907 static void
908 peer_connection_down (PeerConnection *data)
909 {
910 g_object_unref (data->client_connection);
911 g_object_unref (data->server_connection);
912
913 g_dbus_server_stop (data->server);
914 g_object_unref (data->server);
915
916 g_main_loop_quit (data->service_loop);
917 g_thread_join (data->service_thread);
918
919 g_mutex_clear (&data->service_loop_lock);
920 g_cond_clear (&data->service_loop_cond);
921 }
922
923 struct roundtrip_state
924 {
925 RandomMenu *random;
926 MirrorMenu *proxy_mirror;
927 GDBusMenuModel *proxy;
928 GMainLoop *loop;
929 GRand *rand;
930 gint success;
931 gint count;
932 };
933
934 static gboolean
935 roundtrip_step (gpointer data)
936 {
937 struct roundtrip_state *state = data;
938
939 if (check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy)) &&
940 check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy_mirror)))
941 {
942 state->success++;
943 state->count = 0;
944
945 if (state->success < 100)
946 random_menu_change (state->random, state->rand);
947 else
948 g_main_loop_quit (state->loop);
949 }
950 else if (state->count == 100)
951 {
952 assert_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy));
953 g_assert_not_reached ();
954 }
955 else
956 state->count++;
957
958 return G_SOURCE_CONTINUE;
959 }
960
961 static void
962 do_roundtrip (GDBusConnection *exporter_connection,
963 GDBusConnection *proxy_connection)
964 {
965 struct roundtrip_state state;
966 guint export_id;
967 guint id;
968
969 state.rand = g_rand_new_with_seed (g_test_rand_int ());
970
971 state.random = random_menu_new (state.rand, 2);
972 export_id = g_dbus_connection_export_menu_model (exporter_connection,
973 "/",
974 G_MENU_MODEL (state.random),
975 NULL);
976 state.proxy = g_dbus_menu_model_get (proxy_connection,
977 g_dbus_connection_get_unique_name (proxy_connection),
978 "/");
979 state.proxy_mirror = mirror_menu_new (G_MENU_MODEL (state.proxy));
980 state.count = 0;
981 state.success = 0;
982
983 id = g_timeout_add (10, roundtrip_step, &state);
984
985 state.loop = g_main_loop_new (NULL, FALSE);
986 g_main_loop_run (state.loop);
987
988 g_main_loop_unref (state.loop);
989 g_source_remove (id);
990 g_object_unref (state.proxy);
991 g_dbus_connection_unexport_menu_model (exporter_connection, export_id);
992 g_object_unref (state.random);
993 g_object_unref (state.proxy_mirror);
994 g_rand_free (state.rand);
995 }
996
997 static void
998 test_dbus_roundtrip (void)
999 {
1000 GDBusConnection *bus;
1001
1002 bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
1003 do_roundtrip (bus, bus);
1004 g_object_unref (bus);
1005 }
1006
1007 static void
1008 test_dbus_peer_roundtrip (void)
1009 {
1010 #ifdef _GLIB_ADDRESS_SANITIZER
1011 g_test_incomplete ("FIXME: Leaks a GCancellableSource, see glib#2313");
1012 (void) peer_connection_up;
1013 (void) peer_connection_down;
1014 #else
1015 PeerConnection peer;
1016
1017 peer_connection_up (&peer);
1018 do_roundtrip (peer.server_connection, peer.client_connection);
1019 peer_connection_down (&peer);
1020 #endif
1021 }
1022
1023 static gint items_changed_count;
1024
1025 static void
1026 items_changed (GMenuModel *model,
1027 gint position,
1028 gint removed,
1029 gint added,
1030 gpointer data)
1031 {
1032 items_changed_count++;
1033 }
1034
1035 static gboolean
1036 stop_loop (gpointer data)
1037 {
1038 GMainLoop *loop = data;
1039
1040 g_main_loop_quit (loop);
1041
1042 return G_SOURCE_REMOVE;
1043 }
1044
1045 static void
1046 do_subscriptions (GDBusConnection *exporter_connection,
1047 GDBusConnection *proxy_connection)
1048 {
1049 GMenu *menu;
1050 GDBusMenuModel *proxy;
1051 GMainLoop *loop;
1052 GError *error = NULL;
1053 guint export_id;
1054 guint timeout_id;
1055
1056 timeout_id = add_timeout (60);
1057 loop = g_main_loop_new (NULL, FALSE);
1058
1059 menu = g_menu_new ();
1060
1061 export_id = g_dbus_connection_export_menu_model (exporter_connection,
1062 "/",
1063 G_MENU_MODEL (menu),
1064 &error);
1065 g_assert_no_error (error);
1066
1067 proxy = g_dbus_menu_model_get (proxy_connection,
1068 g_dbus_connection_get_unique_name (proxy_connection),
1069 "/");
1070 items_changed_count = 0;
1071 g_signal_connect (proxy, "items-changed",
1072 G_CALLBACK (items_changed), NULL);
1073
1074 g_menu_append (menu, "item1", NULL);
1075 g_menu_append (menu, "item2", NULL);
1076 g_menu_append (menu, "item3", NULL);
1077
1078 g_assert_cmpint (items_changed_count, ==, 0);
1079
1080 /* We don't subscribe to change-notification until we look at the items */
1081 g_timeout_add (100, stop_loop, loop);
1082 g_main_loop_run (loop);
1083
1084 /* Looking at the items triggers subscription */
1085 g_menu_model_get_n_items (G_MENU_MODEL (proxy));
1086
1087 while (items_changed_count < 1)
1088 g_main_context_iteration (NULL, TRUE);
1089
1090 /* We get all three items in one batch */
1091 g_assert_cmpint (items_changed_count, ==, 1);
1092 g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 3);
1093
1094 /* If we wait, we don't get any more */
1095 g_timeout_add (100, stop_loop, loop);
1096 g_main_loop_run (loop);
1097 g_assert_cmpint (items_changed_count, ==, 1);
1098 g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 3);
1099
1100 /* Now we're subscribed, we get changes individually */
1101 g_menu_append (menu, "item4", NULL);
1102 g_menu_append (menu, "item5", NULL);
1103 g_menu_append (menu, "item6", NULL);
1104 g_menu_remove (menu, 0);
1105 g_menu_remove (menu, 0);
1106
1107 while (items_changed_count < 6)
1108 g_main_context_iteration (NULL, TRUE);
1109
1110 g_assert_cmpint (items_changed_count, ==, 6);
1111
1112 g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 4);
1113
1114 /* After destroying the proxy and waiting a bit, we don't get any more
1115 * items-changed signals */
1116 g_object_unref (proxy);
1117
1118 g_timeout_add (100, stop_loop, loop);
1119 g_main_loop_run (loop);
1120
1121 g_menu_remove (menu, 0);
1122 g_menu_remove (menu, 0);
1123
1124 g_timeout_add (100, stop_loop, loop);
1125 g_main_loop_run (loop);
1126
1127 g_assert_cmpint (items_changed_count, ==, 6);
1128
1129 g_dbus_connection_unexport_menu_model (exporter_connection, export_id);
1130 g_object_unref (menu);
1131
1132 g_main_loop_unref (loop);
1133 cancel_timeout (timeout_id);
1134 }
1135
1136 static void
1137 test_dbus_subscriptions (void)
1138 {
1139 GDBusConnection *bus;
1140
1141 bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
1142 do_subscriptions (bus, bus);
1143 g_object_unref (bus);
1144 }
1145
1146 static void
1147 test_dbus_peer_subscriptions (void)
1148 {
1149 #ifdef _GLIB_ADDRESS_SANITIZER
1150 g_test_incomplete ("FIXME: Leaks a GCancellableSource, see glib#2313");
1151 (void) peer_connection_up;
1152 (void) peer_connection_down;
1153 #else
1154 PeerConnection peer;
1155
1156 peer_connection_up (&peer);
1157 do_subscriptions (peer.server_connection, peer.client_connection);
1158 peer_connection_down (&peer);
1159 #endif
1160 }
1161
1162 static gpointer
1163 do_modify (gpointer data)
1164 {
1165 RandomMenu *menu = data;
1166 GRand *rand;
1167 gint i;
1168
1169 rand = g_rand_new_with_seed (g_test_rand_int ());
1170
1171 for (i = 0; i < 10000; i++)
1172 {
1173 random_menu_change (menu, rand);
1174 }
1175
1176 g_rand_free (rand);
1177
1178 return NULL;
1179 }
1180
1181 static gpointer
1182 do_export (gpointer data)
1183 {
1184 GMenuModel *menu = data;
1185 gint i;
1186 GDBusConnection *bus;
1187 gchar *path;
1188 GError *error = NULL;
1189 guint id;
1190
1191 bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
1192 path = g_strdup_printf ("/%p", data);
1193
1194 for (i = 0; i < 10000; i++)
1195 {
1196 id = g_dbus_connection_export_menu_model (bus, path, menu, &error);
1197 g_assert_no_error (error);
1198 g_dbus_connection_unexport_menu_model (bus, id);
1199 while (g_main_context_iteration (NULL, FALSE));
1200 }
1201
1202 g_free (path);
1203
1204 g_object_unref (bus);
1205
1206 return NULL;
1207 }
1208
1209 static void
1210 test_dbus_threaded (void)
1211 {
1212 RandomMenu *menu[10];
1213 GThread *call[10];
1214 GThread *export[10];
1215 gint i;
1216
1217 for (i = 0; i < 10; i++)
1218 {
1219 GRand *rand = g_rand_new_with_seed (g_test_rand_int ());
1220 menu[i] = random_menu_new (rand, 2);
1221 call[i] = g_thread_new ("call", do_modify, menu[i]);
1222 export[i] = g_thread_new ("export", do_export, menu[i]);
1223 g_rand_free (rand);
1224 }
1225
1226 for (i = 0; i < 10; i++)
1227 {
1228 g_thread_join (call[i]);
1229 g_thread_join (export[i]);
1230 }
1231
1232 for (i = 0; i < 10; i++)
1233 g_object_unref (menu[i]);
1234 }
1235
1236 static void
1237 test_attributes (void)
1238 {
1239 GMenu *menu;
1240 GMenuItem *item;
1241 GVariant *v;
1242
1243 menu = g_menu_new ();
1244
1245 item = g_menu_item_new ("test", NULL);
1246 g_menu_item_set_attribute_value (item, "boolean", g_variant_new_boolean (FALSE));
1247 g_menu_item_set_attribute_value (item, "string", g_variant_new_string ("bla"));
1248
1249 g_menu_item_set_attribute (item, "double", "d", 1.5);
1250 v = g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three");
1251 g_menu_item_set_attribute_value (item, "complex", v);
1252 g_menu_item_set_attribute_value (item, "test-123", g_variant_new_string ("test-123"));
1253
1254 g_menu_append_item (menu, item);
1255
1256 g_menu_item_set_attribute (item, "double", "d", G_PI);
1257
1258 g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 1);
1259
1260 v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "boolean", NULL);
1261 g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN));
1262 g_variant_unref (v);
1263
1264 v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "string", NULL);
1265 g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
1266 g_variant_unref (v);
1267
1268 v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "double", NULL);
1269 g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_DOUBLE));
1270 g_variant_unref (v);
1271
1272 v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "complex", NULL);
1273 g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE("a(si)")));
1274 g_variant_unref (v);
1275
1276 g_menu_remove_all (menu);
1277
1278 g_object_unref (menu);
1279 g_object_unref (item);
1280 }
1281
1282 static void
1283 test_attribute_iter (void)
1284 {
1285 GMenu *menu;
1286 GMenuItem *item;
1287 const gchar *name;
1288 GVariant *v;
1289 GMenuAttributeIter *iter;
1290 GHashTable *found;
1291
1292 menu = g_menu_new ();
1293
1294 item = g_menu_item_new ("test", NULL);
1295 g_menu_item_set_attribute_value (item, "boolean", g_variant_new_boolean (FALSE));
1296 g_menu_item_set_attribute_value (item, "string", g_variant_new_string ("bla"));
1297
1298 g_menu_item_set_attribute (item, "double", "d", 1.5);
1299 v = g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three");
1300 g_menu_item_set_attribute_value (item, "complex", v);
1301 g_menu_item_set_attribute_value (item, "test-123", g_variant_new_string ("test-123"));
1302
1303 g_menu_append_item (menu, item);
1304
1305 g_menu_item_set_attribute (item, "double", "d", G_PI);
1306
1307 g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 1);
1308
1309 found = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
1310
1311 iter = g_menu_model_iterate_item_attributes (G_MENU_MODEL (menu), 0);
1312 while (g_menu_attribute_iter_get_next (iter, &name, &v))
1313 g_hash_table_insert (found, g_strdup (name), v);
1314 g_object_unref (iter);
1315
1316 g_assert_cmpint (g_hash_table_size (found), ==, 6);
1317
1318 v = g_hash_table_lookup (found, "label");
1319 g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
1320
1321 v = g_hash_table_lookup (found, "boolean");
1322 g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN));
1323
1324 v = g_hash_table_lookup (found, "string");
1325 g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
1326
1327 v = g_hash_table_lookup (found, "double");
1328 g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_DOUBLE));
1329
1330 v = g_hash_table_lookup (found, "complex");
1331 g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE("a(si)")));
1332
1333 v = g_hash_table_lookup (found, "test-123");
1334 g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
1335
1336 g_hash_table_unref (found);
1337
1338 g_menu_remove_all (menu);
1339
1340 g_object_unref (menu);
1341 g_object_unref (item);
1342 }
1343
1344 static void
1345 test_links (void)
1346 {
1347 GMenu *menu;
1348 GMenuModel *m;
1349 GMenuModel *x;
1350 GMenuItem *item;
1351
1352 m = G_MENU_MODEL (g_menu_new ());
1353 g_menu_append (G_MENU (m), "test", NULL);
1354
1355 menu = g_menu_new ();
1356
1357 item = g_menu_item_new ("test2", NULL);
1358 g_menu_item_set_link (item, "submenu", m);
1359 g_menu_prepend_item (menu, item);
1360 g_object_unref (item);
1361
1362 item = g_menu_item_new ("test1", NULL);
1363 g_menu_item_set_link (item, "section", m);
1364 g_menu_insert_item (menu, 0, item);
1365 g_object_unref (item);
1366
1367 item = g_menu_item_new ("test3", NULL);
1368 g_menu_item_set_link (item, "wallet", m);
1369 g_menu_insert_item (menu, 1000, item);
1370 g_object_unref (item);
1371
1372 item = g_menu_item_new ("test4", NULL);
1373 g_menu_item_set_link (item, "purse", m);
1374 g_menu_item_set_link (item, "purse", NULL);
1375 g_menu_append_item (menu, item);
1376 g_object_unref (item);
1377
1378 g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 4);
1379
1380 x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 0, "section");
1381 g_assert (x == m);
1382 g_object_unref (x);
1383
1384 x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 1, "submenu");
1385 g_assert (x == m);
1386 g_object_unref (x);
1387
1388 x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 2, "wallet");
1389 g_assert (x == m);
1390 g_object_unref (x);
1391
1392 x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 3, "purse");
1393 g_assert (x == NULL);
1394
1395 g_object_unref (m);
1396 g_object_unref (menu);
1397 }
1398
1399 static void
1400 test_mutable (void)
1401 {
1402 GMenu *menu;
1403
1404 menu = g_menu_new ();
1405 g_menu_append (menu, "test", "test");
1406
1407 g_assert (g_menu_model_is_mutable (G_MENU_MODEL (menu)));
1408 g_menu_freeze (menu);
1409 g_assert (!g_menu_model_is_mutable (G_MENU_MODEL (menu)));
1410
1411 g_object_unref (menu);
1412 }
1413
1414 static void
1415 test_convenience (void)
1416 {
1417 GMenu *m1, *m2;
1418 GMenu *sub;
1419 GMenuItem *item;
1420
1421 m1 = g_menu_new ();
1422 m2 = g_menu_new ();
1423 sub = g_menu_new ();
1424
1425 g_menu_prepend (m1, "label1", "do::something");
1426 g_menu_insert (m2, 0, "label1", "do::something");
1427
1428 g_menu_append (m1, "label2", "do::somethingelse");
1429 g_menu_insert (m2, -1, "label2", "do::somethingelse");
1430
1431 g_menu_insert_section (m1, 10, "label3", G_MENU_MODEL (sub));
1432 item = g_menu_item_new_section ("label3", G_MENU_MODEL (sub));
1433 g_menu_insert_item (m2, 10, item);
1434 g_object_unref (item);
1435
1436 g_menu_prepend_section (m1, "label4", G_MENU_MODEL (sub));
1437 g_menu_insert_section (m2, 0, "label4", G_MENU_MODEL (sub));
1438
1439 g_menu_append_section (m1, "label5", G_MENU_MODEL (sub));
1440 g_menu_insert_section (m2, -1, "label5", G_MENU_MODEL (sub));
1441
1442 g_menu_insert_submenu (m1, 5, "label6", G_MENU_MODEL (sub));
1443 item = g_menu_item_new_submenu ("label6", G_MENU_MODEL (sub));
1444 g_menu_insert_item (m2, 5, item);
1445 g_object_unref (item);
1446
1447 g_menu_prepend_submenu (m1, "label7", G_MENU_MODEL (sub));
1448 g_menu_insert_submenu (m2, 0, "label7", G_MENU_MODEL (sub));
1449
1450 g_menu_append_submenu (m1, "label8", G_MENU_MODEL (sub));
1451 g_menu_insert_submenu (m2, -1, "label8", G_MENU_MODEL (sub));
1452
1453 assert_menus_equal (G_MENU_MODEL (m1), G_MENU_MODEL (m2));
1454
1455 g_object_unref (m1);
1456 g_object_unref (m2);
1457 g_object_unref (sub);
1458 }
1459
1460 static void
1461 test_menuitem (void)
1462 {
1463 GMenu *menu;
1464 GMenu *submenu;
1465 GMenuItem *item;
1466 GIcon *icon;
1467 gboolean b;
1468 gchar *s;
1469
1470 menu = g_menu_new ();
1471 submenu = g_menu_new ();
1472
1473 item = g_menu_item_new ("label", "action");
1474 g_menu_item_set_attribute (item, "attribute", "b", TRUE);
1475 g_menu_item_set_link (item, G_MENU_LINK_SUBMENU, G_MENU_MODEL (submenu));
1476 g_menu_append_item (menu, item);
1477
1478 icon = g_themed_icon_new ("bla");
1479 g_menu_item_set_icon (item, icon);
1480 g_object_unref (icon);
1481
1482 g_assert (g_menu_item_get_attribute (item, "attribute", "b", &b));
1483 g_assert (b);
1484
1485 g_menu_item_set_action_and_target (item, "action", "(bs)", TRUE, "string");
1486 g_assert (g_menu_item_get_attribute (item, "target", "(bs)", &b, &s));
1487 g_assert (b);
1488 g_assert_cmpstr (s, ==, "string");
1489 g_free (s);
1490
1491 g_object_unref (item);
1492
1493 item = g_menu_item_new_from_model (G_MENU_MODEL (menu), 0);
1494 assert_menuitem_equal (item, G_MENU_MODEL (menu), 0);
1495 g_object_unref (item);
1496
1497 g_object_unref (menu);
1498 g_object_unref (submenu);
1499 }
1500
1501 static GDBusInterfaceInfo *
1502 org_gtk_Menus_get_interface (void)
1503 {
1504 static GDBusInterfaceInfo *interface_info;
1505
1506 if (interface_info == NULL)
1507 {
1508 GError *error = NULL;
1509 GDBusNodeInfo *info;
1510
1511 info = g_dbus_node_info_new_for_xml ("<node>"
1512 " <interface name='org.gtk.Menus'>"
1513 " <method name='Start'>"
1514 " <arg type='au' name='groups' direction='in'/>"
1515 " <arg type='a(uuaa{sv})' name='content' direction='out'/>"
1516 " </method>"
1517 " <method name='End'>"
1518 " <arg type='au' name='groups' direction='in'/>"
1519 " </method>"
1520 " <signal name='Changed'>"
1521 " arg type='a(uuuuaa{sv})' name='changes'/>"
1522 " </signal>"
1523 " </interface>"
1524 "</node>", &error);
1525 if (info == NULL)
1526 g_error ("%s\n", error->message);
1527 interface_info = g_dbus_node_info_lookup_interface (info, "org.gtk.Menus");
1528 g_assert (interface_info != NULL);
1529 g_dbus_interface_info_ref (interface_info);
1530 g_dbus_node_info_unref (info);
1531 }
1532
1533 return interface_info;
1534 }
1535
1536 static void
1537 g_menu_exporter_method_call (GDBusConnection *connection,
1538 const gchar *sender,
1539 const gchar *object_path,
1540 const gchar *interface_name,
1541 const gchar *method_name,
1542 GVariant *parameters,
1543 GDBusMethodInvocation *invocation,
1544 gpointer user_data)
1545 {
1546 const struct {
1547 guint position;
1548 guint removed;
1549 } data[] = {
1550 { -2, 4 },
1551 { 0, 3 },
1552 { 4, 1 }
1553 };
1554 gsize i;
1555 GError *error = NULL;
1556
1557 g_dbus_method_invocation_return_value (invocation, g_variant_new_parsed ("@(a(uuaa{sv})) ([(0, 0, [{ 'label': <'test'> }])],)"));
1558
1559 /* invalid signatures */
1560 g_dbus_connection_emit_signal (connection, sender, "/", "org.gtk.Menus", "Changed",
1561 g_variant_new_parsed ("([(1, 2, 3)],)"), &error);
1562 g_assert_no_error (error);
1563
1564 /* add an item at an invalid position */
1565 g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*invalid*");
1566 g_dbus_connection_emit_signal (connection, sender, "/", "org.gtk.Menus", "Changed",
1567 g_variant_new_parsed ("@(a(uuuuaa{sv})) ([(%u, %u, %u, %u, [{ 'label': <'test'> }])],)", 0, 0, 2, 0),
1568 &error);
1569 g_assert_no_error (error);
1570
1571 for (i = 0; i < G_N_ELEMENTS (data); i++)
1572 {
1573 GVariant *params;
1574
1575 g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*invalid*");
1576 params = g_variant_new_parsed ("@(a(uuuuaa{sv})) ([(%u, %u, %u, %u, [])],)", 0, 0, data[i].position, data[i].removed);
1577 g_dbus_connection_emit_signal (connection, sender, "/", "org.gtk.Menus", "Changed", params, &error);
1578 g_assert_no_error (error);
1579 }
1580 }
1581
1582 static void
1583 menu_changed (GMenuModel *menu,
1584 gint position,
1585 gint removed,
1586 gint added,
1587 gpointer user_data)
1588 {
1589 unsigned int *counter = user_data;
1590
1591 *counter += 1;
1592 }
1593
1594 static void
1595 test_input_validation (void)
1596 {
1597 const GDBusInterfaceVTable vtable = {
1598 g_menu_exporter_method_call, NULL, NULL, { NULL, }
1599 };
1600 GError *error = NULL;
1601 GDBusConnection *bus;
1602 GDBusMenuModel *proxy;
1603 guint id;
1604 const gchar *bus_name;
1605 GMainLoop *loop;
1606 unsigned int n_signal_emissions = 0;
1607 gulong signal_id;
1608
1609 g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/861");
1610
1611 bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
1612 g_assert_no_error (error);
1613
1614 id = g_dbus_connection_register_object (bus, "/", org_gtk_Menus_get_interface (),
1615 &vtable, NULL, NULL, &error);
1616 g_assert_no_error (error);
1617
1618 bus_name = g_dbus_connection_get_unique_name (bus);
1619 proxy = g_dbus_menu_model_get (bus, bus_name, "/");
1620
1621 signal_id = g_signal_connect (proxy, "items-changed", G_CALLBACK (menu_changed), &n_signal_emissions);
1622
1623 /* get over laziness */
1624 g_menu_model_get_n_items (G_MENU_MODEL (proxy));
1625
1626 loop = g_main_loop_new (NULL, FALSE);
1627 g_timeout_add (100, stop_loop, loop);
1628 g_main_loop_run (loop);
1629
1630 /* "items-changed" should only be emitted for the initial contents of
1631 * the menu. Subsequent calls are all invalid.
1632 */
1633 g_assert_cmpuint (n_signal_emissions, ==, 1);
1634
1635 g_test_assert_expected_messages ();
1636
1637 g_main_loop_unref (loop);
1638 g_dbus_connection_unregister_object (bus, id);
1639 g_signal_handler_disconnect (proxy, signal_id);
1640 g_object_unref (proxy);
1641 g_object_unref (bus);
1642 }
1643
1644 /* Epilogue {{{1 */
1645 int
1646 main (int argc, char **argv)
1647 {
1648 gboolean ret;
1649
1650 g_test_init (&argc, &argv, NULL);
1651
1652 session_bus_up ();
1653
1654 g_test_add_func ("/gmenu/equality", test_equality);
1655 g_test_add_func ("/gmenu/random", test_random);
1656 g_test_add_func ("/gmenu/dbus/roundtrip", test_dbus_roundtrip);
1657 g_test_add_func ("/gmenu/dbus/subscriptions", test_dbus_subscriptions);
1658 g_test_add_func ("/gmenu/dbus/threaded", test_dbus_threaded);
1659 g_test_add_func ("/gmenu/dbus/peer/roundtrip", test_dbus_peer_roundtrip);
1660 g_test_add_func ("/gmenu/dbus/peer/subscriptions", test_dbus_peer_subscriptions);
1661 g_test_add_func ("/gmenu/attributes", test_attributes);
1662 g_test_add_func ("/gmenu/attributes/iterate", test_attribute_iter);
1663 g_test_add_func ("/gmenu/links", test_links);
1664 g_test_add_func ("/gmenu/mutable", test_mutable);
1665 g_test_add_func ("/gmenu/convenience", test_convenience);
1666 g_test_add_func ("/gmenu/menuitem", test_menuitem);
1667 g_test_add_func ("/gmenu/input-validation", test_input_validation);
1668
1669 ret = g_test_run ();
1670
1671 session_bus_down ();
1672
1673 return ret;
1674 }
1675 /* vim:set foldmethod=marker: */