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