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