1 /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 Copyright (C) 2009-2015 Red Hat, Inc.
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 */
18 #include <config.h>
19 #include <stdlib.h>
20 #include <math.h>
21 #include <string.h>
22 #include <stdio.h>
23 #include <unistd.h>
24 #ifndef _WIN32
25 #include <sys/wait.h>
26 #include <sys/select.h>
27 #endif
28 #include <sys/types.h>
29 #include <getopt.h>
30 #include <pthread.h>
31
32 #include "spice-wrapped.h"
33 #include <spice/qxl_dev.h>
34
35 #include "test-display-base.h"
36 #include "test-glib-compat.h"
37 #include "red-channel.h"
38
39 #ifndef PATH_MAX
40 #define PATH_MAX 4096
41 #endif
42
43 #define MEM_SLOT_GROUP_ID 0
44
45 #define NOTIFY_DISPLAY_BATCH (SINGLE_PART/2)
46 #define NOTIFY_CURSOR_BATCH 10
47
48 /* Parts cribbed from spice-display.h/.c/qxl.c */
49
50 struct SimpleSpiceUpdate {
51 QXLCommandExt ext; // first
52 QXLDrawable drawable;
53 QXLImage image;
54 uint8_t *bitmap;
55 };
56
57 struct SimpleSurfaceCmd {
58 QXLCommandExt ext; // first
59 QXLSurfaceCmd surface_cmd;
60 };
61
test_spice_destroy_update(SimpleSpiceUpdate * update)62 static void test_spice_destroy_update(SimpleSpiceUpdate *update)
63 {
64 if (!update) {
65 return;
66 }
67 if (update->drawable.clip.type != SPICE_CLIP_TYPE_NONE) {
68 auto ptr = (uint8_t*)(uintptr_t)update->drawable.clip.data;
69 g_free(ptr);
70 }
71 g_free(update->bitmap);
72 g_free(update);
73 }
74
75 #define DEFAULT_WIDTH 640
76 #define DEFAULT_HEIGHT 320
77
78 #define SINGLE_PART 4
79 static const int angle_parts = 64 / SINGLE_PART;
80 static int unique = 1;
81 static int color = -1;
82 static int c_i = 0;
83
84 /* Used for automated tests */
85 static int control = 3; //used to know when we can take a screenshot
86 static int rects = 16; //number of rects that will be draw
87 static int has_automated_tests = 0; //automated test flag
88
89 // SPICE implementation is not designed to have a timer shared
90 // between multiple threads so use a mutex
91 static pthread_mutex_t timer_mutex = PTHREAD_MUTEX_INITIALIZER;
92
93 // wait for the child process and exit
child_exited(GPid pid,gint status,gpointer user_data)94 static void child_exited(GPid pid, gint status, gpointer user_data)
95 {
96 g_spawn_close_pid(pid);
97 exit(0);
98 }
99
regression_test()100 static void regression_test()
101 {
102 GPid pid;
103 GError *error = nullptr;
104 gboolean retval;
105 gchar **argv;
106
107 if (--rects != 0) {
108 return;
109 }
110
111 rects = 16;
112
113 if (--control != 0) {
114 return;
115 }
116
117 argv = g_strsplit("./regression-test.py", " ", -1);
118 retval = g_spawn_async(nullptr, argv, nullptr, (GSpawnFlags) (G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD),
119 nullptr, nullptr, &pid, &error);
120 g_strfreev(argv);
121 g_assert(retval);
122
123 GSource *source = g_child_watch_source_new(pid);
124 g_source_set_callback(source, (GSourceFunc)(void*)child_exited, nullptr, nullptr);
125 guint id = g_source_attach(source, basic_event_loop_get_context());
126 g_assert(id != 0);
127 g_source_unref(source);
128 }
129
set_cmd(QXLCommandExt * ext,uint32_t type,QXLPHYSICAL data)130 static void set_cmd(QXLCommandExt *ext, uint32_t type, QXLPHYSICAL data)
131 {
132 ext->cmd.type = type;
133 ext->cmd.data = data;
134 ext->cmd.padding = 0;
135 ext->group_id = MEM_SLOT_GROUP_ID;
136 ext->flags = 0;
137 }
138
simple_set_release_info(QXLReleaseInfo * info,intptr_t ptr)139 static void simple_set_release_info(QXLReleaseInfo *info, intptr_t ptr)
140 {
141 info->id = ptr;
142 //info->group_id = MEM_SLOT_GROUP_ID;
143 }
144
145 struct Path {
146 int t;
147 int min_t;
148 int max_t;
149 };
150
path_init(Path * path,int min,int max)151 static void path_init(Path *path, int min, int max)
152 {
153 path->t = min;
154 path->min_t = min;
155 path->max_t = max;
156 }
157
path_progress(Path * path)158 static void path_progress(Path *path)
159 {
160 path->t = (path->t+1)% (path->max_t - path->min_t) + path->min_t;
161 }
162
163 Path path;
164
draw_pos(Test * test,int t,int * x,int * y)165 static void draw_pos(Test *test, int t, int *x, int *y)
166 {
167 #ifdef CIRCLE
168 *y = test->primary_height/2 + (test->primary_height/3)*cos(t*2*M_PI/angle_parts);
169 *x = test->primary_width/2 + (test->primary_width/3)*sin(t*2*M_PI/angle_parts);
170 #else
171 *y = test->primary_height*(t % SINGLE_PART)/SINGLE_PART;
172 *x = ((test->primary_width/SINGLE_PART)*(t / SINGLE_PART)) % test->primary_width;
173 #endif
174 }
175
176 /* bitmap and rects are freed, so they must be allocated with malloc */
177 static SimpleSpiceUpdate *
test_spice_create_update_from_bitmap(uint32_t surface_id,QXLRect bbox,uint8_t * bitmap,uint32_t num_clip_rects,QXLRect * clip_rects)178 test_spice_create_update_from_bitmap(uint32_t surface_id,
179 QXLRect bbox,
180 uint8_t *bitmap,
181 uint32_t num_clip_rects,
182 QXLRect *clip_rects)
183 {
184 SimpleSpiceUpdate *update;
185 QXLDrawable *drawable;
186 QXLImage *image;
187 uint32_t bw, bh;
188
189 bh = bbox.bottom - bbox.top;
190 bw = bbox.right - bbox.left;
191
192 update = g_new0(SimpleSpiceUpdate, 1);
193 update->bitmap = bitmap;
194 drawable = &update->drawable;
195 image = &update->image;
196
197 drawable->surface_id = surface_id;
198
199 drawable->bbox = bbox;
200 if (num_clip_rects == 0) {
201 drawable->clip.type = SPICE_CLIP_TYPE_NONE;
202 } else {
203 QXLClipRects *cmd_clip;
204
205 cmd_clip = (QXLClipRects*) g_malloc0(sizeof(QXLClipRects) + num_clip_rects*sizeof(QXLRect));
206 cmd_clip->num_rects = num_clip_rects;
207 cmd_clip->chunk.data_size = num_clip_rects*sizeof(QXLRect);
208 cmd_clip->chunk.prev_chunk = cmd_clip->chunk.next_chunk = 0;
209 memcpy(cmd_clip + 1, clip_rects, cmd_clip->chunk.data_size);
210
211 drawable->clip.type = SPICE_CLIP_TYPE_RECTS;
212 drawable->clip.data = (intptr_t)cmd_clip;
213
214 g_free(clip_rects);
215 }
216 drawable->effect = QXL_EFFECT_OPAQUE;
217 simple_set_release_info(&drawable->release_info, (intptr_t)update);
218 drawable->type = QXL_DRAW_COPY;
219 drawable->surfaces_dest[0] = -1;
220 drawable->surfaces_dest[1] = -1;
221 drawable->surfaces_dest[2] = -1;
222
223 drawable->u.copy.rop_descriptor = SPICE_ROPD_OP_PUT;
224 drawable->u.copy.src_bitmap = (intptr_t)image;
225 drawable->u.copy.src_area.right = bw;
226 drawable->u.copy.src_area.bottom = bh;
227
228 QXL_SET_IMAGE_ID(image, QXL_IMAGE_GROUP_DEVICE, unique);
229 image->descriptor.type = SPICE_IMAGE_TYPE_BITMAP;
230 image->bitmap.flags = QXL_BITMAP_DIRECT | QXL_BITMAP_TOP_DOWN;
231 image->bitmap.stride = bw * 4;
232 image->descriptor.width = image->bitmap.x = bw;
233 image->descriptor.height = image->bitmap.y = bh;
234 image->bitmap.data = (intptr_t)bitmap;
235 image->bitmap.palette = 0;
236 image->bitmap.format = SPICE_BITMAP_FMT_32BIT;
237
238 set_cmd(&update->ext, QXL_CMD_DRAW, (intptr_t)drawable);
239
240 return update;
241 }
242
test_spice_create_update_solid(uint32_t surface_id,QXLRect bbox,uint32_t solid_color)243 static SimpleSpiceUpdate *test_spice_create_update_solid(uint32_t surface_id, QXLRect bbox,
244 uint32_t solid_color)
245 {
246 uint8_t *bitmap;
247 uint32_t *dst;
248 uint32_t bw;
249 uint32_t bh;
250 uint32_t i;
251
252 bw = bbox.right - bbox.left;
253 bh = bbox.bottom - bbox.top;
254
255 bitmap = (uint8_t*) g_malloc(bw * bh * 4);
256 dst = SPICE_ALIGNED_CAST(uint32_t *, bitmap);
257
258 for (i = 0 ; i < bh * bw ; ++i, ++dst) {
259 *dst = solid_color;
260 }
261
262 return test_spice_create_update_from_bitmap(surface_id, bbox, bitmap, 0, nullptr);
263 }
264
test_spice_create_update_draw(Test * test,uint32_t surface_id,int t)265 static SimpleSpiceUpdate *test_spice_create_update_draw(Test *test, uint32_t surface_id, int t)
266 {
267 int top, left;
268 uint8_t *dst;
269 uint8_t *bitmap;
270 int bw, bh;
271 int i;
272 QXLRect bbox;
273
274 draw_pos(test, t, &left, &top);
275 if ((t % angle_parts) == 0) {
276 c_i++;
277 }
278
279 if (surface_id != 0) {
280 color = (color + 1) % 2;
281 } else {
282 color = surface_id;
283 }
284
285 unique++;
286
287 bw = test->primary_width/SINGLE_PART;
288 bh = 48;
289
290 bitmap = dst = (uint8_t*) g_malloc(bw * bh * 4);
291 //printf("allocated %p\n", dst);
292
293 for (i = 0 ; i < bh * bw ; ++i, dst+=4) {
294 *dst = (color+i % 255);
295 *(dst+((1+c_i)%3)) = 255 - color;
296 *(dst+((2+c_i)%3)) = (color * (color + i)) & 0xff;
297 *(dst+((3+c_i)%3)) = 0;
298 }
299
300 bbox.left = left; bbox.top = top;
301 bbox.right = left + bw; bbox.bottom = top + bh;
302 return test_spice_create_update_from_bitmap(surface_id, bbox, bitmap, 0, nullptr);
303 }
304
test_spice_create_update_copy_bits(Test * test,uint32_t surface_id)305 static SimpleSpiceUpdate *test_spice_create_update_copy_bits(Test *test, uint32_t surface_id)
306 {
307 SimpleSpiceUpdate *update;
308 QXLDrawable *drawable;
309 int bw, bh;
310 QXLRect bbox = {
311 .top = 0,
312 .left = 10,
313 };
314
315 update = g_new0(SimpleSpiceUpdate, 1);
316 drawable = &update->drawable;
317
318 bw = test->primary_width/SINGLE_PART;
319 bh = 48;
320 bbox.right = bbox.left + bw;
321 bbox.bottom = bbox.top + bh;
322 //printf("allocated %p, %p\n", update, update->bitmap);
323
324 drawable->surface_id = surface_id;
325
326 drawable->bbox = bbox;
327 drawable->clip.type = SPICE_CLIP_TYPE_NONE;
328 drawable->effect = QXL_EFFECT_OPAQUE;
329 simple_set_release_info(&drawable->release_info, (intptr_t)update);
330 drawable->type = QXL_COPY_BITS;
331 drawable->surfaces_dest[0] = -1;
332 drawable->surfaces_dest[1] = -1;
333 drawable->surfaces_dest[2] = -1;
334
335 drawable->u.copy_bits.src_pos.x = 0;
336 drawable->u.copy_bits.src_pos.y = 0;
337
338 set_cmd(&update->ext, QXL_CMD_DRAW, (intptr_t)drawable);
339
340 return update;
341 }
342
format_to_bpp(int format)343 static int format_to_bpp(int format)
344 {
345 switch (format) {
346 case SPICE_SURFACE_FMT_8_A:
347 return 1;
348 case SPICE_SURFACE_FMT_16_555:
349 case SPICE_SURFACE_FMT_16_565:
350 return 2;
351 case SPICE_SURFACE_FMT_32_xRGB:
352 case SPICE_SURFACE_FMT_32_ARGB:
353 return 4;
354 }
355 abort();
356 }
357
create_surface(int surface_id,int format,int width,int height,uint8_t * data)358 static SimpleSurfaceCmd *create_surface(int surface_id, int format, int width, int height, uint8_t *data)
359 {
360 auto simple_cmd = g_new0(SimpleSurfaceCmd, 1);
361 QXLSurfaceCmd *surface_cmd = &simple_cmd->surface_cmd;
362 int bpp = format_to_bpp(format);
363
364 set_cmd(&simple_cmd->ext, QXL_CMD_SURFACE, (intptr_t)surface_cmd);
365 simple_set_release_info(&surface_cmd->release_info, (intptr_t)simple_cmd);
366 surface_cmd->type = QXL_SURFACE_CMD_CREATE;
367 surface_cmd->flags = 0; // ?
368 surface_cmd->surface_id = surface_id;
369 surface_cmd->u.surface_create.format = format;
370 surface_cmd->u.surface_create.width = width;
371 surface_cmd->u.surface_create.height = height;
372 surface_cmd->u.surface_create.stride = -width * bpp;
373 surface_cmd->u.surface_create.data = (intptr_t)data;
374 return simple_cmd;
375 }
376
destroy_surface(int surface_id)377 static SimpleSurfaceCmd *destroy_surface(int surface_id)
378 {
379 auto simple_cmd = g_new0(SimpleSurfaceCmd, 1);
380 QXLSurfaceCmd *surface_cmd = &simple_cmd->surface_cmd;
381
382 set_cmd(&simple_cmd->ext, QXL_CMD_SURFACE, (intptr_t)surface_cmd);
383 simple_set_release_info(&surface_cmd->release_info, (intptr_t)simple_cmd);
384 surface_cmd->type = QXL_SURFACE_CMD_DESTROY;
385 surface_cmd->flags = 0; // ?
386 surface_cmd->surface_id = surface_id;
387 return simple_cmd;
388 }
389
create_primary_surface(Test * test,uint32_t width,uint32_t height)390 static void create_primary_surface(Test *test, uint32_t width,
391 uint32_t height)
392 {
393 QXLDevSurfaceCreate surface = { 0, };
394
395 spice_assert(height <= MAX_HEIGHT);
396 spice_assert(width <= MAX_WIDTH);
397 spice_assert(height > 0);
398 spice_assert(width > 0);
399
400 surface.format = SPICE_SURFACE_FMT_32_xRGB;
401 surface.width = test->primary_width = width;
402 surface.height = test->primary_height = height;
403 surface.stride = -width * 4; /* negative? */
404 surface.mouse_mode = TRUE; /* unused by red_worker */
405 surface.flags = 0;
406 surface.type = 0; /* unused by red_worker */
407 surface.position = 0; /* unused by red_worker */
408 surface.mem = (uintptr_t)&test->primary_surface;
409 surface.group_id = MEM_SLOT_GROUP_ID;
410
411 test->width = width;
412 test->height = height;
413
414 spice_qxl_create_primary_surface(&test->qxl_instance, 0, &surface);
415 }
416
417 static QXLDevMemSlot slot = {
418 .slot_group_id = MEM_SLOT_GROUP_ID,
419 .slot_id = 0,
420 .generation = 0,
421 .virt_start = 0,
422 .virt_end = ~0,
423 .addr_delta = 0,
424 .qxl_ram_size = ~0,
425 };
426
attached_worker(QXLInstance * qin)427 static void attached_worker(QXLInstance *qin)
428 {
429 Test *test = SPICE_CONTAINEROF(qin, Test, qxl_instance);
430
431 printf("%s\n", __func__);
432 spice_qxl_add_memslot(&test->qxl_instance, &slot);
433 create_primary_surface(test, DEFAULT_WIDTH, DEFAULT_HEIGHT);
434 spice_server_vm_start(test->server);
435 }
436
set_compression_level(SPICE_GNUC_UNUSED QXLInstance * qin,SPICE_GNUC_UNUSED int level)437 static void set_compression_level(SPICE_GNUC_UNUSED QXLInstance *qin,
438 SPICE_GNUC_UNUSED int level)
439 {
440 printf("%s\n", __func__);
441 }
442
443 // we now have a secondary surface
444 #define MAX_SURFACE_NUM 2
445
get_init_info(SPICE_GNUC_UNUSED QXLInstance * qin,QXLDevInitInfo * info)446 static void get_init_info(SPICE_GNUC_UNUSED QXLInstance *qin,
447 QXLDevInitInfo *info)
448 {
449 memset(info, 0, sizeof(*info));
450 info->num_memslots = 1;
451 info->num_memslots_groups = 1;
452 info->memslot_id_bits = 1;
453 info->memslot_gen_bits = 1;
454 info->n_surfaces = MAX_SURFACE_NUM;
455 }
456
457 // We shall now have a ring of commands, so that we can update
458 // it from a separate thread - since get_command is called from
459 // the worker thread, and we need to sometimes do an update_area,
460 // which cannot be done from red_worker context (not via dispatcher,
461 // since you get a deadlock, and it isn't designed to be done
462 // any other way, so no point testing that).
463 static unsigned int commands_end = 0;
464 static unsigned int commands_start = 0;
465 static struct QXLCommandExt* commands[1024];
466 static pthread_mutex_t command_mutex = PTHREAD_MUTEX_INITIALIZER;
467
468 #define COMMANDS_SIZE G_N_ELEMENTS(commands)
469
push_command(QXLCommandExt * ext)470 static void push_command(QXLCommandExt *ext)
471 {
472 pthread_mutex_lock(&command_mutex);
473 spice_assert(commands_end - commands_start < COMMANDS_SIZE);
474 commands[commands_end % COMMANDS_SIZE] = ext;
475 commands_end++;
476 pthread_mutex_unlock(&command_mutex);
477 }
478
get_num_commands()479 static int get_num_commands()
480 {
481 return commands_end - commands_start;
482 }
483
get_simple_command()484 static struct QXLCommandExt *get_simple_command()
485 {
486 pthread_mutex_lock(&command_mutex);
487 struct QXLCommandExt *ret = nullptr;
488 if (get_num_commands() > 0) {
489 ret = commands[commands_start % COMMANDS_SIZE];
490 commands_start++;
491 }
492 pthread_mutex_unlock(&command_mutex);
493 return ret;
494 }
495
496 // called from spice_server thread (i.e. red_worker thread)
get_command(SPICE_GNUC_UNUSED QXLInstance * qin,struct QXLCommandExt * ext)497 static int get_command(SPICE_GNUC_UNUSED QXLInstance *qin,
498 struct QXLCommandExt *ext)
499 {
500 struct QXLCommandExt *cmd = get_simple_command();
501 if (!cmd) {
502 return FALSE;
503 }
504 *ext = *cmd;
505 return TRUE;
506 }
507
produce_command(Test * test)508 static void produce_command(Test *test)
509 {
510 Command *command;
511
512 if (test->has_secondary)
513 test->target_surface = 1;
514
515 if (!test->num_commands) {
516 usleep(1000);
517 return;
518 }
519
520 command = &test->commands[test->cmd_index];
521 if (command->cb) {
522 command->cb(test, command);
523 }
524 switch (command->command) {
525 case SLEEP:
526 printf("sleep %u seconds\n", command->sleep.secs);
527 sleep(command->sleep.secs);
528 break;
529 case PATH_PROGRESS:
530 path_progress(&path);
531 break;
532 case SIMPLE_UPDATE: {
533 QXLRect rect = {
534 .top = 0,
535 .left = 0,
536 .bottom = (test->target_surface == 0 ? test->primary_height : test->height),
537 .right = (test->target_surface == 0 ? test->primary_width : test->width),
538 };
539 if (rect.right > 0 && rect.bottom > 0) {
540 spice_qxl_update_area(&test->qxl_instance, test->target_surface, &rect, nullptr, 0, 1);
541 }
542 break;
543 }
544
545 /* Drawing commands, they all push a command to the command ring */
546 case SIMPLE_COPY_BITS:
547 case SIMPLE_DRAW_SOLID:
548 case SIMPLE_DRAW_BITMAP:
549 case SIMPLE_DRAW: {
550 SimpleSpiceUpdate *update;
551
552 if (has_automated_tests)
553 {
554 if (control == 0) {
555 return;
556 }
557
558 regression_test();
559 }
560
561 switch (command->command) {
562 case SIMPLE_COPY_BITS:
563 update = test_spice_create_update_copy_bits(test, 0);
564 break;
565 case SIMPLE_DRAW:
566 update = test_spice_create_update_draw(test, 0, path.t);
567 break;
568 case SIMPLE_DRAW_BITMAP:
569 update = test_spice_create_update_from_bitmap(command->bitmap.surface_id,
570 command->bitmap.bbox, command->bitmap.bitmap,
571 command->bitmap.num_clip_rects, command->bitmap.clip_rects);
572 break;
573 case SIMPLE_DRAW_SOLID:
574 update = test_spice_create_update_solid(command->solid.surface_id,
575 command->solid.bbox, command->solid.color);
576 break;
577 default:
578 /* Terminate on unhandled cases - never actually reached */
579 g_assert_not_reached();
580 break;
581 }
582 push_command(&update->ext);
583 break;
584 }
585
586 case SIMPLE_CREATE_SURFACE: {
587 SimpleSurfaceCmd *update;
588 if (command->create_surface.data) {
589 spice_assert(command->create_surface.surface_id > 0);
590 spice_assert(command->create_surface.surface_id < MAX_SURFACE_NUM);
591 spice_assert(command->create_surface.surface_id == 1);
592 update = create_surface(command->create_surface.surface_id,
593 command->create_surface.format,
594 command->create_surface.width,
595 command->create_surface.height,
596 command->create_surface.data);
597 } else {
598 update = create_surface(test->target_surface, SPICE_SURFACE_FMT_32_xRGB,
599 SURF_WIDTH, SURF_HEIGHT,
600 test->secondary_surface);
601 }
602 push_command(&update->ext);
603 test->has_secondary = 1;
604 break;
605 }
606
607 case SIMPLE_DESTROY_SURFACE: {
608 SimpleSurfaceCmd *update;
609 test->has_secondary = 0;
610 update = destroy_surface(test->target_surface);
611 test->target_surface = 0;
612 push_command(&update->ext);
613 break;
614 }
615
616 case DESTROY_PRIMARY:
617 spice_qxl_destroy_primary_surface(&test->qxl_instance, 0);
618 break;
619
620 case CREATE_PRIMARY:
621 create_primary_surface(test,
622 command->create_primary.width, command->create_primary.height);
623 break;
624 }
625 test->cmd_index = (test->cmd_index + 1) % test->num_commands;
626 }
627
req_cmd_notification(QXLInstance * qin)628 static int req_cmd_notification(QXLInstance *qin)
629 {
630 Test *test = SPICE_CONTAINEROF(qin, Test, qxl_instance);
631
632 pthread_mutex_lock(&timer_mutex);
633 test->core->timer_start(test->wakeup_timer, test->wakeup_ms);
634 pthread_mutex_unlock(&timer_mutex);
635 return TRUE;
636 }
637
do_wakeup(void * opaque)638 static void do_wakeup(void *opaque)
639 {
640 auto test = (Test*) opaque;
641 int notify;
642
643 test->cursor_notify = NOTIFY_CURSOR_BATCH;
644 for (notify = NOTIFY_DISPLAY_BATCH; notify > 0;--notify) {
645 produce_command(test);
646 }
647
648 pthread_mutex_lock(&timer_mutex);
649 test->core->timer_start(test->wakeup_timer, test->wakeup_ms);
650 pthread_mutex_unlock(&timer_mutex);
651 spice_qxl_wakeup(&test->qxl_instance);
652 }
653
release_resource(SPICE_GNUC_UNUSED QXLInstance * qin,struct QXLReleaseInfoExt release_info)654 static void release_resource(SPICE_GNUC_UNUSED QXLInstance *qin,
655 struct QXLReleaseInfoExt release_info)
656 {
657 auto ext = (QXLCommandExt*)(uintptr_t)release_info.info->id;
658 //printf("%s\n", __func__);
659 spice_assert(release_info.group_id == MEM_SLOT_GROUP_ID);
660 switch (ext->cmd.type) {
661 case QXL_CMD_DRAW:
662 test_spice_destroy_update(SPICE_CONTAINEROF(ext, SimpleSpiceUpdate, ext));
663 break;
664 case QXL_CMD_SURFACE:
665 g_free(ext);
666 break;
667 case QXL_CMD_CURSOR: {
668 auto cmd = (QXLCursorCmd *)(uintptr_t)ext->cmd.data;
669 if (cmd->type == QXL_CURSOR_SET || cmd->type == QXL_CURSOR_MOVE) {
670 g_free(cmd);
671 }
672 g_free(ext);
673 break;
674 }
675 default:
676 abort();
677 }
678 }
679
680 #define CURSOR_WIDTH 32
681 #define CURSOR_HEIGHT 32
682
683 static struct {
684 QXLCursor cursor;
685 uint8_t data[CURSOR_WIDTH * CURSOR_HEIGHT * 4 + 128]; // 32bit per pixel
686 } cursor;
687
cursor_init()688 static void cursor_init()
689 {
690 cursor.cursor.header.unique = 0;
691 cursor.cursor.header.type = SPICE_CURSOR_TYPE_COLOR32;
692 cursor.cursor.header.width = CURSOR_WIDTH;
693 cursor.cursor.header.height = CURSOR_HEIGHT;
694 cursor.cursor.header.hot_spot_x = 0;
695 cursor.cursor.header.hot_spot_y = 0;
696 cursor.cursor.data_size = CURSOR_WIDTH * CURSOR_HEIGHT * 4;
697
698 // X drivers addes it to the cursor size because it could be
699 // cursor data information or another cursor related stuffs.
700 // Otherwise, the code will break in client/cursor.cpp side,
701 // that expect the data_size plus cursor information.
702 // Blame cursor protocol for this. :-)
703 cursor.cursor.data_size += 128;
704 cursor.cursor.chunk.data_size = cursor.cursor.data_size;
705 cursor.cursor.chunk.prev_chunk = cursor.cursor.chunk.next_chunk = 0;
706 }
707
get_cursor_command(QXLInstance * qin,struct QXLCommandExt * ext)708 static int get_cursor_command(QXLInstance *qin, struct QXLCommandExt *ext)
709 {
710 Test *test = SPICE_CONTAINEROF(qin, Test, qxl_instance);
711 static int set = 1;
712 static int x = 0, y = 0;
713 QXLCursorCmd *cursor_cmd;
714 QXLCommandExt *cmd;
715
716 if (!test->cursor_notify) {
717 return FALSE;
718 }
719
720 test->cursor_notify--;
721 cmd = g_new0(QXLCommandExt, 1);
722 cursor_cmd = g_new0(QXLCursorCmd, 1);
723
724 cursor_cmd->release_info.id = (uintptr_t)cmd;
725
726 if (set) {
727 cursor_cmd->type = QXL_CURSOR_SET;
728 cursor_cmd->u.set.position.x = 0;
729 cursor_cmd->u.set.position.y = 0;
730 cursor_cmd->u.set.visible = TRUE;
731 cursor_cmd->u.set.shape = (uintptr_t)&cursor;
732 // Only a white rect (32x32) as cursor
733 memset(cursor.data, 255, sizeof(cursor.data));
734 set = 0;
735 } else {
736 cursor_cmd->type = QXL_CURSOR_MOVE;
737 cursor_cmd->u.position.x = x++ % test->primary_width;
738 cursor_cmd->u.position.y = y++ % test->primary_height;
739 }
740
741 cmd->cmd.data = (uintptr_t)cursor_cmd;
742 cmd->cmd.type = QXL_CMD_CURSOR;
743 cmd->group_id = MEM_SLOT_GROUP_ID;
744 cmd->flags = 0;
745 *ext = *cmd;
746 //printf("%s\n", __func__);
747 return TRUE;
748 }
749
req_cursor_notification(SPICE_GNUC_UNUSED QXLInstance * qin)750 static int req_cursor_notification(SPICE_GNUC_UNUSED QXLInstance *qin)
751 {
752 printf("%s\n", __func__);
753 return TRUE;
754 }
755
notify_update(SPICE_GNUC_UNUSED QXLInstance * qin,SPICE_GNUC_UNUSED uint32_t update_id)756 static void notify_update(SPICE_GNUC_UNUSED QXLInstance *qin,
757 SPICE_GNUC_UNUSED uint32_t update_id)
758 {
759 printf("%s\n", __func__);
760 }
761
flush_resources(SPICE_GNUC_UNUSED QXLInstance * qin)762 static int flush_resources(SPICE_GNUC_UNUSED QXLInstance *qin)
763 {
764 printf("%s\n", __func__);
765 return TRUE;
766 }
767
client_monitors_config(SPICE_GNUC_UNUSED QXLInstance * qin,VDAgentMonitorsConfig * monitors_config)768 static int client_monitors_config(SPICE_GNUC_UNUSED QXLInstance *qin,
769 VDAgentMonitorsConfig *monitors_config)
770 {
771 if (!monitors_config) {
772 printf("%s: NULL monitors_config\n", __func__);
773 } else {
774 printf("%s: %d\n", __func__, monitors_config->num_of_monitors);
775 }
776 return 0;
777 }
778
set_client_capabilities(QXLInstance * qin,uint8_t client_present,uint8_t caps[58])779 static void set_client_capabilities(QXLInstance *qin,
780 uint8_t client_present,
781 uint8_t caps[58])
782 {
783 Test *test = SPICE_CONTAINEROF(qin, Test, qxl_instance);
784
785 printf("%s: present %d caps %d\n", __func__, client_present, caps[0]);
786 if (test->on_client_connected && client_present) {
787 test->on_client_connected(test);
788 }
789 if (test->on_client_disconnected && !client_present) {
790 test->on_client_disconnected(test);
791 }
792 }
793
794 static QXLInterface display_sif = {
795 .base = {
796 .type = SPICE_INTERFACE_QXL,
797 .description = "test",
798 .major_version = SPICE_INTERFACE_QXL_MAJOR,
799 .minor_version = SPICE_INTERFACE_QXL_MINOR
800 },
801 { .attached_worker = attached_worker },
802 .set_compression_level = set_compression_level,
803 .set_mm_time = nullptr,
804 .get_init_info = get_init_info,
805
806 /* the callbacks below are called from spice server thread context */
807 .get_command = get_command,
808 .req_cmd_notification = req_cmd_notification,
809 .release_resource = release_resource,
810 .get_cursor_command = get_cursor_command,
811 .req_cursor_notification = req_cursor_notification,
812 .notify_update = notify_update,
813 .flush_resources = flush_resources,
814 .async_complete = nullptr,
815 .update_area_complete = nullptr,
816 .set_client_capabilities = set_client_capabilities,
817 .client_monitors_config = client_monitors_config,
818 };
819
820 /* interface for tests */
test_add_display_interface(Test * test)821 void test_add_display_interface(Test* test)
822 {
823 spice_server_add_interface(test->server, &test->qxl_instance.base);
824 }
825
vmc_write(SPICE_GNUC_UNUSED SpiceCharDeviceInstance * sin,SPICE_GNUC_UNUSED const uint8_t * buf,int len)826 static int vmc_write(SPICE_GNUC_UNUSED SpiceCharDeviceInstance *sin,
827 SPICE_GNUC_UNUSED const uint8_t *buf,
828 int len)
829 {
830 printf("%s: %d\n", __func__, len);
831 return len;
832 }
833
vmc_read(SPICE_GNUC_UNUSED SpiceCharDeviceInstance * sin,SPICE_GNUC_UNUSED uint8_t * buf,int len)834 static int vmc_read(SPICE_GNUC_UNUSED SpiceCharDeviceInstance *sin,
835 SPICE_GNUC_UNUSED uint8_t *buf,
836 int len)
837 {
838 printf("%s: %d\n", __func__, len);
839 return 0;
840 }
841
vmc_state(SPICE_GNUC_UNUSED SpiceCharDeviceInstance * sin,int connected)842 static void vmc_state(SPICE_GNUC_UNUSED SpiceCharDeviceInstance *sin,
843 int connected)
844 {
845 printf("%s: %d\n", __func__, connected);
846 }
847
848
849 static SpiceCharDeviceInterface vdagent_sif = {
850 .base = {
851 .type = SPICE_INTERFACE_CHAR_DEVICE,
852 .description = "test spice virtual channel char device",
853 .major_version = SPICE_INTERFACE_CHAR_DEVICE_MAJOR,
854 .minor_version = SPICE_INTERFACE_CHAR_DEVICE_MINOR,
855 },
856 .state = vmc_state,
857 .write = vmc_write,
858 .read = vmc_read,
859
860 };
861
862 static SpiceCharDeviceInstance vdagent_sin = {
863 .base = {
864 .sif = &vdagent_sif.base,
865 },
866 .subtype = "vdagent",
867 };
868
test_add_agent_interface(SpiceServer * server)869 void test_add_agent_interface(SpiceServer *server)
870 {
871 spice_server_add_interface(server, &vdagent_sin.base);
872 }
873
test_set_simple_command_list(Test * test,const int * simple_commands,int num_commands)874 void test_set_simple_command_list(Test *test, const int *simple_commands, int num_commands)
875 {
876 int i;
877
878 g_free(test->commands);
879 test->commands = g_new0(Command, num_commands);
880 test->num_commands = num_commands;
881 for (i = 0 ; i < num_commands; ++i) {
882 test->commands[i].command = (CommandType) simple_commands[i];
883 }
884 }
885
test_set_command_list(Test * test,Command * new_commands,int num_commands)886 void test_set_command_list(Test *test, Command *new_commands, int num_commands)
887 {
888 test->commands = new_commands;
889 test->num_commands = num_commands;
890 }
891
ignore_in_use_failures(const gchar * log_domain,GLogLevelFlags log_level,const gchar * message,gpointer user_data)892 static gboolean ignore_in_use_failures(const gchar *log_domain,
893 GLogLevelFlags log_level,
894 const gchar *message,
895 gpointer user_data)
896 {
897 if (!g_str_equal (log_domain, G_LOG_DOMAIN)) {
898 return true;
899 }
900 if ((log_level & G_LOG_LEVEL_WARNING) == 0) {
901 return true;
902 }
903 if (strstr(message, "reds_init_socket: binding socket to ") == nullptr && // bind failure
904 strstr(message, "reds_init_socket: listen: ") == nullptr && // listen failure
905 strstr(message, "Failed to open SPICE sockets") == nullptr) { // global
906 g_print("XXX [%s]\n", message);
907 return true;
908 }
909
910 return false;
911 }
912
913 #define BASE_PORT 5912
914
test_new(SpiceCoreInterface * core)915 Test* test_new(SpiceCoreInterface* core)
916 {
917 auto test = g_new0(Test, 1);
918 int port = -1;
919
920 test->qxl_instance.base.sif = &display_sif.base;
921 test->qxl_instance.id = 0;
922
923 test->core = core;
924 test->wakeup_ms = 1;
925 test->cursor_notify = NOTIFY_CURSOR_BATCH;
926 // some common initialization for all display tests
927 port = BASE_PORT;
928
929 g_test_log_set_fatal_handler(ignore_in_use_failures, nullptr);
930 for (port = BASE_PORT; port < BASE_PORT + 10; port++) {
931 SpiceServer* server = spice_server_new();
932 spice_server_set_noauth(server);
933 spice_server_set_port(server, port);
934 if (spice_server_init(server, core) == 0) {
935 test->server = server;
936 break;
937 }
938 spice_server_destroy(server);
939 }
940
941 g_assert_nonnull(test->server);
942
943 printf("TESTER: listening on port %d (unsecure)\n", port);
944 g_test_log_set_fatal_handler(nullptr, nullptr);
945
946 cursor_init();
947 path_init(&path, 0, angle_parts);
948 test->has_secondary = 0;
949 test->wakeup_timer = core->timer_add(do_wakeup, test);
950 return test;
951 }
952
test_destroy(Test * test)953 void test_destroy(Test *test)
954 {
955 spice_server_destroy(test->server);
956 // this timer is used by spice server so
957 // avoid to free it while is running
958 test->core->timer_remove(test->wakeup_timer);
959 g_free(test->commands);
960 g_free(test);
961 }
962
init_automated()963 static void init_automated()
964 {
965 }
966
967 static SPICE_GNUC_NORETURN
usage(const char * argv0,const int exitcode)968 void usage(const char *argv0, const int exitcode)
969 {
970 const char *autoopt=" [--automated-tests]";
971
972 printf("usage: %s%s\n", argv0, autoopt);
973 exit(exitcode);
974 }
975
spice_test_config_parse_args(int argc,char ** argv)976 void spice_test_config_parse_args(int argc, char **argv)
977 {
978 struct option options[] = {
979 {"automated-tests", no_argument, &has_automated_tests, 1},
980 {nullptr, 0, nullptr, 0},
981 };
982 int option_index;
983 int val;
984
985 while ((val = getopt_long(argc, argv, "", options, &option_index)) != -1) {
986 switch (val) {
987 case '?':
988 printf("unrecognized option '%s'\n", argv[optind - 1]);
989 usage(argv[0], EXIT_FAILURE);
990 case 0:
991 break;
992 }
993 }
994
995 if (argc > optind) {
996 printf("unknown argument '%s'\n", argv[optind]);
997 usage(argv[0], EXIT_FAILURE);
998 }
999 if (has_automated_tests) {
1000 init_automated();
1001 }
1002 return;
1003 }
1004