1 | #include <stdlib.h> |
2 | |
3 | #include <gtk/gtk.h> |
4 | |
5 | typedef gboolean (* ValueCompareFunc) (GValue *v1, GValue *v2); |
6 | |
7 | typedef struct { |
8 | GOutputStream *ostream; |
9 | GInputStream *istream; |
10 | const char *mime_type; |
11 | GValue value; |
12 | ValueCompareFunc compare; |
13 | gboolean done; |
14 | } TestData; |
15 | |
16 | static gboolean |
17 | compare_string_values (GValue *v1, GValue *v2) |
18 | { |
19 | return G_VALUE_TYPE (v1) == G_TYPE_STRING && |
20 | G_VALUE_TYPE (v2) == G_TYPE_STRING && |
21 | strcmp (s1: g_value_get_string (value: v1), s2: g_value_get_string (value: v2)) == 0; |
22 | } |
23 | |
24 | static gboolean |
25 | compare_rgba_values (GValue *v1, GValue *v2) |
26 | { |
27 | return G_VALUE_TYPE (v1) == GDK_TYPE_RGBA && |
28 | G_VALUE_TYPE (v2) == GDK_TYPE_RGBA && |
29 | gdk_rgba_equal (p1: (GdkRGBA *)g_value_get_boxed (value: v1), |
30 | p2: (GdkRGBA *)g_value_get_boxed (value: v2)); |
31 | } |
32 | |
33 | static gboolean |
34 | textures_equal (GdkTexture *t1, GdkTexture *t2) |
35 | { |
36 | guchar *d1, *d2; |
37 | int width, height; |
38 | gboolean ret; |
39 | |
40 | width = gdk_texture_get_width (texture: t1); |
41 | height = gdk_texture_get_height (texture: t1); |
42 | |
43 | if (width != gdk_texture_get_width (texture: t2)) |
44 | return FALSE; |
45 | if (height != gdk_texture_get_height (texture: t2)) |
46 | return FALSE; |
47 | |
48 | d1 = g_malloc (n_bytes: width * height * 4); |
49 | d2 = g_malloc (n_bytes: width * height * 4); |
50 | |
51 | gdk_texture_download (texture: t1, data: d1, stride: width * 4); |
52 | gdk_texture_download (texture: t2, data: d2, stride: width * 4); |
53 | |
54 | ret = memcmp (s1: d1, s2: d2, n: width * height * 4) == 0; |
55 | |
56 | if (!ret) |
57 | { |
58 | gdk_texture_save_to_png (texture: t1, filename: "texture1.png" ); |
59 | gdk_texture_save_to_png (texture: t2, filename: "texture2.png" ); |
60 | } |
61 | g_free (mem: d1); |
62 | g_free (mem: d2); |
63 | |
64 | return ret; |
65 | } |
66 | |
67 | static gboolean |
68 | compare_texture_values (GValue *v1, GValue *v2) |
69 | { |
70 | return G_VALUE_TYPE (v1) == GDK_TYPE_TEXTURE && |
71 | G_VALUE_TYPE (v2) == GDK_TYPE_TEXTURE && |
72 | textures_equal (t1: (GdkTexture *)g_value_get_object (value: v1), |
73 | t2: (GdkTexture *)g_value_get_object (value: v2)); |
74 | } |
75 | |
76 | static gboolean |
77 | compare_file_values (GValue *v1, GValue *v2) |
78 | { |
79 | return G_VALUE_TYPE (v1) == G_TYPE_FILE && |
80 | G_VALUE_TYPE (v2) == G_TYPE_FILE && |
81 | g_file_equal (file1: (GFile *)g_value_get_object (value: v1), |
82 | file2: (GFile *)g_value_get_object (value: v2)); |
83 | } |
84 | |
85 | static gboolean |
86 | compare_file_list_values (GValue *v1, GValue *v2) |
87 | { |
88 | GSList *s1, *s2, *l1, *l2; |
89 | |
90 | if (G_VALUE_TYPE (v1) != GDK_TYPE_FILE_LIST || |
91 | G_VALUE_TYPE (v2) != GDK_TYPE_FILE_LIST) |
92 | return FALSE; |
93 | |
94 | s1 = g_value_get_boxed (value: v1); |
95 | s2 = g_value_get_boxed (value: v2); |
96 | |
97 | if (g_slist_length (list: s1) != g_slist_length (list: s2)) |
98 | return FALSE; |
99 | |
100 | for (l1 = s1, l2 = s2; l1 != NULL; l1 = l1->next, l2 = l2->next) |
101 | { |
102 | GFile *f1 = l1->data; |
103 | GFile *f2 = l2->data; |
104 | if (!g_file_equal (file1: f1, file2: f2)) |
105 | return FALSE; |
106 | } |
107 | |
108 | return TRUE; |
109 | } |
110 | |
111 | static void |
112 | deserialize_done (GObject *source, |
113 | GAsyncResult *result, |
114 | gpointer user_data) |
115 | { |
116 | GError *error = NULL; |
117 | gboolean res; |
118 | TestData *data = user_data; |
119 | GValue value = G_VALUE_INIT; |
120 | |
121 | g_value_init (value: &value, G_VALUE_TYPE (&data->value)); |
122 | |
123 | res = gdk_content_deserialize_finish (result, value: &value, error: &error); |
124 | g_assert_true (res); |
125 | g_assert_no_error (error); |
126 | |
127 | g_assert_true (data->compare (&data->value, &value)); |
128 | |
129 | g_value_unset (value: &value); |
130 | |
131 | data->done = TRUE; |
132 | g_main_context_wakeup (NULL); |
133 | } |
134 | |
135 | static void |
136 | serialize_done (GObject *source, |
137 | GAsyncResult *result, |
138 | gpointer user_data) |
139 | { |
140 | GError *error = NULL; |
141 | gboolean res; |
142 | TestData *data = user_data; |
143 | gpointer serialized_data; |
144 | gsize serialized_len; |
145 | |
146 | res = gdk_content_serialize_finish (result, error: &error); |
147 | g_assert_true (res); |
148 | g_assert_no_error (error); |
149 | |
150 | serialized_data = g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (data->ostream)); |
151 | serialized_len = g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (data->ostream)); |
152 | data->istream = g_memory_input_stream_new_from_data (data: serialized_data, len: serialized_len, NULL); |
153 | |
154 | gdk_content_deserialize_async (stream: data->istream, |
155 | mime_type: data->mime_type, |
156 | G_VALUE_TYPE (&data->value), |
157 | G_PRIORITY_DEFAULT, |
158 | NULL, |
159 | callback: deserialize_done, |
160 | user_data: data); |
161 | } |
162 | |
163 | static void |
164 | test_content_roundtrip (const GValue *value, |
165 | const char *mime_type, |
166 | ValueCompareFunc compare) |
167 | { |
168 | TestData data = { 0, }; |
169 | |
170 | data.ostream = g_memory_output_stream_new_resizable (); |
171 | data.mime_type = mime_type; |
172 | g_value_init (value: &data.value, G_VALUE_TYPE (value)); |
173 | g_value_copy (src_value: value, dest_value: &data.value); |
174 | data.compare = compare; |
175 | data.done = FALSE; |
176 | |
177 | gdk_content_serialize_async (stream: data.ostream, |
178 | mime_type: data.mime_type, |
179 | value: &data.value, |
180 | G_PRIORITY_DEFAULT, |
181 | NULL, |
182 | callback: serialize_done, |
183 | user_data: &data); |
184 | |
185 | while (!data.done) |
186 | g_main_context_iteration (NULL, TRUE); |
187 | |
188 | g_object_unref (object: data.ostream); |
189 | g_object_unref (object: data.istream); |
190 | g_value_unset (value: &data.value); |
191 | } |
192 | |
193 | static void |
194 | test_content_text_plain_utf8 (void) |
195 | { |
196 | GValue value = G_VALUE_INIT; |
197 | |
198 | g_value_init (value: &value, G_TYPE_STRING); |
199 | g_value_set_string (value: &value, v_string: "ABCDEF12345" ); |
200 | test_content_roundtrip (value: &value, mime_type: "text/plain;charset=utf-8" , compare: compare_string_values); |
201 | g_value_unset (value: &value); |
202 | } |
203 | |
204 | static void |
205 | test_content_text_plain (void) |
206 | { |
207 | GValue value = G_VALUE_INIT; |
208 | |
209 | g_value_init (value: &value, G_TYPE_STRING); |
210 | g_value_set_string (value: &value, v_string: "ABCDEF12345" ); |
211 | test_content_roundtrip (value: &value, mime_type: "text/plain" , compare: compare_string_values); |
212 | g_value_unset (value: &value); |
213 | } |
214 | |
215 | static void |
216 | test_content_color (void) |
217 | { |
218 | GdkRGBA color; |
219 | GValue value = G_VALUE_INIT; |
220 | |
221 | gdk_rgba_parse (rgba: &color, spec: "magenta" ); |
222 | g_value_init (value: &value, GDK_TYPE_RGBA); |
223 | g_value_set_boxed (value: &value, v_boxed: &color); |
224 | test_content_roundtrip (value: &value, mime_type: "application/x-color" , compare: compare_rgba_values); |
225 | g_value_unset (value: &value); |
226 | } |
227 | |
228 | static void |
229 | test_content_texture (gconstpointer data) |
230 | { |
231 | const char *mimetype = data; |
232 | GValue value = G_VALUE_INIT; |
233 | char *path; |
234 | GFile *file; |
235 | GdkTexture *texture; |
236 | GError *error = NULL; |
237 | |
238 | path = g_test_build_filename (file_type: G_TEST_DIST, first_path: "image-data" , "image.png" , NULL); |
239 | file = g_file_new_for_path (path); |
240 | texture = gdk_texture_new_from_file (file, error: &error); |
241 | g_assert_no_error (error); |
242 | g_object_unref (object: file); |
243 | g_free (mem: path); |
244 | |
245 | g_value_init (value: &value, GDK_TYPE_TEXTURE); |
246 | g_value_set_object (value: &value, v_object: texture); |
247 | test_content_roundtrip (value: &value, mime_type: mimetype, compare: compare_texture_values); |
248 | g_value_unset (value: &value); |
249 | g_object_unref (object: texture); |
250 | } |
251 | |
252 | static void |
253 | test_content_file (void) |
254 | { |
255 | GFile *file; |
256 | GValue value = G_VALUE_INIT; |
257 | |
258 | file = g_file_new_for_path (path: "/etc/passwd" ); |
259 | g_value_init (value: &value, G_TYPE_FILE); |
260 | g_value_set_object (value: &value, v_object: file); |
261 | test_content_roundtrip (value: &value, mime_type: "text/uri-list" , compare: compare_file_values); |
262 | g_value_unset (value: &value); |
263 | g_object_unref (object: file); |
264 | } |
265 | |
266 | static void |
267 | test_content_files (void) |
268 | { |
269 | GSList *files; |
270 | GValue value = G_VALUE_INIT; |
271 | |
272 | files = NULL; |
273 | files = g_slist_append (list: files, data: g_file_new_for_path (path: "/etc/passwd" )); |
274 | files = g_slist_append (list: files, data: g_file_new_for_path (path: "/boot/ostree" )); |
275 | |
276 | g_value_init (value: &value, GDK_TYPE_FILE_LIST); |
277 | g_value_set_boxed (value: &value, v_boxed: files); |
278 | test_content_roundtrip (value: &value, mime_type: "text/uri-list" , compare: compare_file_list_values); |
279 | g_value_unset (value: &value); |
280 | g_slist_free_full (list: files, free_func: g_object_unref); |
281 | } |
282 | |
283 | #define MY_TYPE_INT_LIST (my_int_list_get_type ()) |
284 | GType my_int_list_get_type (void) G_GNUC_CONST; |
285 | |
286 | typedef gpointer MyIntList; |
287 | |
288 | static gpointer |
289 | my_int_list_copy (gpointer list) |
290 | { |
291 | int size; |
292 | gpointer copy; |
293 | |
294 | size = *(int *)list; |
295 | copy = g_malloc (n_bytes: (size + 1) * sizeof (int)); |
296 | memcpy (dest: copy, src: list, n: (size + 1) * sizeof (int)); |
297 | |
298 | return copy; |
299 | } |
300 | |
301 | static void |
302 | my_int_list_free (gpointer list) |
303 | { |
304 | g_free (mem: list); |
305 | } |
306 | |
307 | G_DEFINE_BOXED_TYPE (MyIntList, my_int_list, my_int_list_copy, my_int_list_free) |
308 | |
309 | static void |
310 | int_list_serializer_finish (GObject *source, |
311 | GAsyncResult *result, |
312 | gpointer serializer) |
313 | { |
314 | GOutputStream *stream = G_OUTPUT_STREAM (source); |
315 | GError *error = NULL; |
316 | |
317 | if (!g_output_stream_write_all_finish (stream, result, NULL, error: &error)) |
318 | gdk_content_serializer_return_error (serializer, error); |
319 | else |
320 | gdk_content_serializer_return_success (serializer); |
321 | } |
322 | |
323 | static void |
324 | int_list_serializer (GdkContentSerializer *serializer) |
325 | { |
326 | GString *str; |
327 | const GValue *value; |
328 | int *data; |
329 | |
330 | str = g_string_new (NULL); |
331 | value = gdk_content_serializer_get_value (serializer); |
332 | |
333 | data = g_value_get_boxed (value); |
334 | for (int i = 0; i < data[0] + 1; i++) |
335 | { |
336 | if (i > 0) |
337 | g_string_append_c (str, ' '); |
338 | g_string_append_printf (string: str, format: "%d" , data[i]); |
339 | } |
340 | |
341 | g_output_stream_write_all_async (stream: gdk_content_serializer_get_output_stream (serializer), |
342 | buffer: str->str, |
343 | count: str->len, |
344 | io_priority: gdk_content_serializer_get_priority (serializer), |
345 | cancellable: gdk_content_serializer_get_cancellable (serializer), |
346 | callback: int_list_serializer_finish, |
347 | user_data: serializer); |
348 | gdk_content_serializer_set_task_data (serializer, data: g_string_free (string: str, FALSE), notify: g_free); |
349 | } |
350 | |
351 | static void |
352 | int_list_deserializer_finish (GObject *source, |
353 | GAsyncResult *result, |
354 | gpointer deserializer) |
355 | { |
356 | GOutputStream *stream = G_OUTPUT_STREAM (source); |
357 | GError *error = NULL; |
358 | gssize written; |
359 | GValue *value; |
360 | char *str; |
361 | char **strv; |
362 | int size; |
363 | int *data; |
364 | |
365 | written = g_output_stream_splice_finish (stream, result, error: &error); |
366 | if (written < 0) |
367 | { |
368 | gdk_content_deserializer_return_error (deserializer, error); |
369 | return; |
370 | } |
371 | |
372 | /* write terminating NULL */ |
373 | if (g_output_stream_write (stream, buffer: "" , count: 1, NULL, error: &error) < 0 || |
374 | !g_output_stream_close (stream, NULL, error: &error)) |
375 | { |
376 | gdk_content_deserializer_return_error (deserializer, error); |
377 | return; |
378 | } |
379 | |
380 | str = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (stream)); |
381 | strv = g_strsplit (string: str, delimiter: " " , max_tokens: -1); |
382 | size = atoi (nptr: strv[0]); |
383 | if (size + 1 != g_strv_length (str_array: strv)) |
384 | { |
385 | g_set_error_literal (err: &error, G_IO_ERROR, code: G_IO_ERROR_FAILED, message: "Int list corrupt" ); |
386 | gdk_content_deserializer_return_error (deserializer, error); |
387 | g_free (mem: str); |
388 | g_strfreev (str_array: strv); |
389 | return; |
390 | } |
391 | |
392 | data = g_malloc (n_bytes: (size + 1) * sizeof (int)); |
393 | for (int i = 0; i < size + 1; i++) |
394 | data[i] = atoi (nptr: strv[i]); |
395 | g_free (mem: str); |
396 | g_strfreev (str_array: strv); |
397 | |
398 | value = gdk_content_deserializer_get_value (deserializer); |
399 | g_value_take_boxed (value, v_boxed: data); |
400 | |
401 | gdk_content_deserializer_return_success (deserializer); |
402 | } |
403 | |
404 | static void |
405 | int_list_deserializer (GdkContentDeserializer *deserializer) |
406 | { |
407 | GOutputStream *output; |
408 | |
409 | output = g_memory_output_stream_new_resizable (); |
410 | |
411 | g_output_stream_splice_async (stream: output, |
412 | source: gdk_content_deserializer_get_input_stream (deserializer), |
413 | flags: G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE, |
414 | io_priority: gdk_content_deserializer_get_priority (deserializer), |
415 | cancellable: gdk_content_deserializer_get_cancellable (deserializer), |
416 | callback: int_list_deserializer_finish, |
417 | user_data: deserializer); |
418 | g_object_unref (object: output); |
419 | } |
420 | |
421 | static gboolean |
422 | compare_int_list_values (GValue *v1, GValue *v2) |
423 | { |
424 | int *d1, *d2; |
425 | |
426 | if (G_VALUE_TYPE (v1) != MY_TYPE_INT_LIST || |
427 | G_VALUE_TYPE (v2) != MY_TYPE_INT_LIST) |
428 | return FALSE; |
429 | |
430 | d1 = g_value_get_boxed (value: v1); |
431 | d2 = g_value_get_boxed (value: v2); |
432 | |
433 | if (d1[0] != d2[0]) |
434 | return FALSE; |
435 | |
436 | return memcmp (s1: d1, s2: d2, n: (d1[0] + 1) * sizeof (int)) == 0; |
437 | } |
438 | |
439 | static void |
440 | test_custom_format (void) |
441 | { |
442 | int *data; |
443 | GValue value = G_VALUE_INIT; |
444 | |
445 | gdk_content_register_serializer (MY_TYPE_INT_LIST, |
446 | mime_type: "application/x-int-list" , |
447 | serialize: int_list_serializer, |
448 | NULL, NULL); |
449 | gdk_content_register_deserializer (mime_type: "application/x-int-list" , |
450 | MY_TYPE_INT_LIST, |
451 | deserialize: int_list_deserializer, |
452 | NULL, NULL); |
453 | |
454 | data = g_malloc (n_bytes: 3 * sizeof (int)); |
455 | data[0] = 2; |
456 | data[1] = 3; |
457 | data[2] = 5; |
458 | |
459 | g_value_init (value: &value, MY_TYPE_INT_LIST); |
460 | g_value_set_boxed (value: &value, v_boxed: data); |
461 | test_content_roundtrip (value: &value, mime_type: "application/x-int-list" , compare: compare_int_list_values); |
462 | g_value_unset (value: &value); |
463 | g_free (mem: data); |
464 | } |
465 | |
466 | int |
467 | main (int argc, char *argv[]) |
468 | { |
469 | (g_test_init) (argc: &argc, argv: &argv, NULL); |
470 | |
471 | gtk_init (); |
472 | |
473 | g_test_add_func (testpath: "/content/text_plain_utf8" , test_func: test_content_text_plain_utf8); |
474 | g_test_add_func (testpath: "/content/text_plain" , test_func: test_content_text_plain); |
475 | g_test_add_func (testpath: "/content/color" , test_func: test_content_color); |
476 | g_test_add_data_func (testpath: "/content/texture/png" , test_data: "image/png" , test_func: test_content_texture); |
477 | g_test_add_data_func (testpath: "/content/texture/tiff" , test_data: "image/tiff" , test_func: test_content_texture); |
478 | g_test_add_func (testpath: "/content/file" , test_func: test_content_file); |
479 | g_test_add_func (testpath: "/content/files" , test_func: test_content_files); |
480 | g_test_add_func (testpath: "/content/custom" , test_func: test_custom_format); |
481 | |
482 | return g_test_run (); |
483 | } |
484 | |