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