8 #include <sys/select.h>
14 #include <spice/enums.h>
15 #include <spice/macros.h>
16 #include <spice/qxl_dev.h>
18 #include "test_display_base.h"
20 #define MEM_SLOT_GROUP_ID 0
22 #define NOTIFY_DISPLAY_BATCH (SINGLE_PART/2)
23 #define NOTIFY_CURSOR_BATCH 10
25 /* these colours are from linux kernel drivers/char/vt.c */
26 /* the default colour table, for VGA+ colour systems */
27 int default_red
[] = {0x00,0xaa,0x00,0xaa,0x00,0xaa,0x00,0xaa,
28 0x55,0xff,0x55,0xff,0x55,0xff,0x55,0xff};
29 int default_grn
[] = {0x00,0x00,0xaa,0x55,0x00,0x00,0xaa,0xaa,
30 0x55,0x55,0xff,0xff,0x55,0x55,0xff,0xff};
31 int default_blu
[] = {0x00,0x00,0x00,0x00,0xaa,0xaa,0xaa,0xaa,
32 0x55,0x55,0x55,0x55,0xff,0xff,0xff,0xff};
34 /* Parts cribbed from spice-display.h/.c/qxl.c */
36 typedef struct SimpleSpiceUpdate
{
37 QXLCommandExt ext
; // first
43 static void test_spice_destroy_update(SimpleSpiceUpdate
*update
)
48 if (update
->drawable
.clip
.type
!= SPICE_CLIP_TYPE_NONE
) {
49 uint8_t *ptr
= (uint8_t*)update
->drawable
.clip
.data
;
52 g_free(update
->bitmap
);
56 #define DEFAULT_WIDTH 640
57 #define DEFAULT_HEIGHT 320
60 static const int angle_parts
= 64 / SINGLE_PART
;
61 static int unique
= 1;
62 static int color
= -1;
65 __attribute__((noreturn
))
66 static void sigchld_handler(int signal_num
) // wait for the child process and exit
73 static void set_cmd(QXLCommandExt
*ext
, uint32_t type
, QXLPHYSICAL data
)
78 ext
->group_id
= MEM_SLOT_GROUP_ID
;
82 static void simple_set_release_info(QXLReleaseInfo
*info
, intptr_t ptr
)
85 //info->group_id = MEM_SLOT_GROUP_ID;
88 // We shall now have a ring of commands, so that we can update
89 // it from a separate thread - since get_command is called from
90 // the worker thread, and we need to sometimes do an update_area,
91 // which cannot be done from red_worker context (not via dispatcher,
92 // since you get a deadlock, and it isn't designed to be done
93 // any other way, so no point testing that).
96 static void push_command(Test
*test
, QXLCommandExt
*ext
)
98 g_mutex_lock(test
->command_mutex
);
100 while (test
->commands_end
- test
->commands_start
>= COMMANDS_SIZE
) {
101 g_cond_wait(test
->command_cond
, test
->command_mutex
);
103 g_assert(test
->commands_end
- test
->commands_start
< COMMANDS_SIZE
);
104 test
->commands
[test
->commands_end
% COMMANDS_SIZE
] = ext
;
105 test
->commands_end
++;
106 g_mutex_unlock(test
->command_mutex
);
108 test
->qxl_worker
->wakeup(test
->qxl_worker
);
111 /* bitmap are freed, so they must be allocated with g_malloc */
112 SimpleSpiceUpdate
*test_spice_create_update_from_bitmap(uint32_t surface_id
,
116 SimpleSpiceUpdate
*update
;
117 QXLDrawable
*drawable
;
121 bh
= bbox
.bottom
- bbox
.top
;
122 bw
= bbox
.right
- bbox
.left
;
124 update
= g_new0(SimpleSpiceUpdate
, 1);
125 update
->bitmap
= bitmap
;
126 drawable
= &update
->drawable
;
127 image
= &update
->image
;
129 drawable
->surface_id
= surface_id
;
131 drawable
->bbox
= bbox
;
132 drawable
->clip
.type
= SPICE_CLIP_TYPE_NONE
;
133 drawable
->effect
= QXL_EFFECT_OPAQUE
;
134 simple_set_release_info(&drawable
->release_info
, (intptr_t)update
);
135 drawable
->type
= QXL_DRAW_COPY
;
136 drawable
->surfaces_dest
[0] = -1;
137 drawable
->surfaces_dest
[1] = -1;
138 drawable
->surfaces_dest
[2] = -1;
140 drawable
->u
.copy
.rop_descriptor
= SPICE_ROPD_OP_PUT
;
141 drawable
->u
.copy
.src_bitmap
= (intptr_t)image
;
142 drawable
->u
.copy
.src_area
.right
= bw
;
143 drawable
->u
.copy
.src_area
.bottom
= bh
;
145 QXL_SET_IMAGE_ID(image
, QXL_IMAGE_GROUP_DEVICE
, unique
);
146 image
->descriptor
.type
= SPICE_IMAGE_TYPE_BITMAP
;
147 image
->bitmap
.flags
= QXL_BITMAP_DIRECT
| QXL_BITMAP_TOP_DOWN
;
148 image
->bitmap
.stride
= bw
* 4;
149 image
->descriptor
.width
= image
->bitmap
.x
= bw
;
150 image
->descriptor
.height
= image
->bitmap
.y
= bh
;
151 image
->bitmap
.data
= (intptr_t)bitmap
;
152 image
->bitmap
.palette
= 0;
153 image
->bitmap
.format
= SPICE_BITMAP_FMT_32BIT
;
155 set_cmd(&update
->ext
, QXL_CMD_DRAW
, (intptr_t)drawable
);
160 static SimpleSpiceUpdate
*test_draw_char(Test
*test
, int x
, int y
, int c
, int fg
, int bg
)
172 // printf("DRAWCHAR %d %d %d\n", left, top, c);
179 bitmap
= dst
= g_malloc(bw
* bh
* 4);
181 unsigned char *data
= vt_font_data
+ c
*16;
182 unsigned char d
= *data
;
184 g_assert(fg
>= 0 && fg
< 16);
185 g_assert(bg
>= 0 && bg
< 16);
187 unsigned char fgc_red
= default_red
[fg
];
188 unsigned char fgc_blue
= default_blu
[fg
];
189 unsigned char fgc_green
= default_grn
[fg
];
190 unsigned char bgc_red
= default_red
[bg
];
191 unsigned char bgc_blue
= default_blu
[bg
];
192 unsigned char bgc_green
= default_grn
[bg
];
194 for (j
= 0; j
< 16; j
++) {
195 for (i
= 0; i
< 8; i
++) {
202 *(dst
+1) = fgc_green
;
207 *(dst
+1) = bgc_green
;
216 bbox
.left
= left
; bbox
.top
= top
;
217 bbox
.right
= left
+ bw
; bbox
.bottom
= top
+ bh
;
219 return test_spice_create_update_from_bitmap(0, bbox
, bitmap
);
222 void test_spice_scroll(Test
*test
, int x1
, int y1
, int x2
, int y2
, int src_x
, int src_y
)
224 SimpleSpiceUpdate
*update
;
225 QXLDrawable
*drawable
;
228 int surface_id
= 0; // fixme
230 update
= g_new0(SimpleSpiceUpdate
, 1);
231 drawable
= &update
->drawable
;
238 drawable
->surface_id
= surface_id
;
240 drawable
->bbox
= bbox
;
241 drawable
->clip
.type
= SPICE_CLIP_TYPE_NONE
;
242 drawable
->effect
= QXL_EFFECT_OPAQUE
;
243 simple_set_release_info(&drawable
->release_info
, (intptr_t)update
);
244 drawable
->type
= QXL_COPY_BITS
;
245 drawable
->surfaces_dest
[0] = -1;
246 drawable
->surfaces_dest
[1] = -1;
247 drawable
->surfaces_dest
[2] = -1;
249 drawable
->u
.copy_bits
.src_pos
.x
= src_x
;
250 drawable
->u
.copy_bits
.src_pos
.y
= src_y
;
252 set_cmd(&update
->ext
, QXL_CMD_DRAW
, (intptr_t)drawable
);
254 push_command(test
, &update
->ext
);
257 void test_spice_clear(Test
*test
, int x1
, int y1
, int x2
, int y2
)
259 SimpleSpiceUpdate
*update
;
260 QXLDrawable
*drawable
;
263 int surface_id
= 0; // fixme
265 update
= g_new0(SimpleSpiceUpdate
, 1);
266 drawable
= &update
->drawable
;
273 drawable
->surface_id
= surface_id
;
275 drawable
->bbox
= bbox
;
276 drawable
->clip
.type
= SPICE_CLIP_TYPE_NONE
;
277 drawable
->effect
= QXL_EFFECT_OPAQUE
;
278 simple_set_release_info(&drawable
->release_info
, (intptr_t)update
);
279 drawable
->type
= QXL_DRAW_BLACKNESS
;
280 drawable
->surfaces_dest
[0] = -1;
281 drawable
->surfaces_dest
[1] = -1;
282 drawable
->surfaces_dest
[2] = -1;
284 set_cmd(&update
->ext
, QXL_CMD_DRAW
, (intptr_t)drawable
);
286 push_command(test
, &update
->ext
);
289 static void create_primary_surface(Test
*test
, uint32_t width
,
292 QXLWorker
*qxl_worker
= test
->qxl_worker
;
293 QXLDevSurfaceCreate surface
= { 0, };
295 g_assert(height
<= MAX_HEIGHT
);
296 g_assert(width
<= MAX_WIDTH
);
297 g_assert(height
> 0);
300 surface
.format
= SPICE_SURFACE_FMT_32_xRGB
;
301 surface
.width
= test
->primary_width
= width
;
302 surface
.height
= test
->primary_height
= height
;
303 surface
.stride
= -width
* 4; /* negative? */
304 surface
.mouse_mode
= TRUE
; /* unused by red_worker */
306 surface
.type
= 0; /* unused by red_worker */
307 surface
.position
= 0; /* unused by red_worker */
308 surface
.mem
= (uint64_t)&test
->primary_surface
;
309 surface
.group_id
= MEM_SLOT_GROUP_ID
;
312 test
->height
= height
;
314 qxl_worker
->create_primary_surface(qxl_worker
, 0, &surface
);
317 QXLDevMemSlot slot
= {
318 .slot_group_id
= MEM_SLOT_GROUP_ID
,
327 static void attache_worker(QXLInstance
*qin
, QXLWorker
*_qxl_worker
)
329 Test
*test
= SPICE_CONTAINEROF(qin
, Test
, qxl_instance
);
331 if (test
->qxl_worker
) {
332 if (test
->qxl_worker
!= _qxl_worker
)
333 printf("%s ignored, %p is set, ignoring new %p\n", __func__
,
334 test
->qxl_worker
, _qxl_worker
);
336 printf("%s ignored, redundant\n", __func__
);
339 printf("%s\n", __func__
);
340 test
->qxl_worker
= _qxl_worker
;
341 test
->qxl_worker
->add_memslot(test
->qxl_worker
, &slot
);
342 create_primary_surface(test
, DEFAULT_WIDTH
, DEFAULT_HEIGHT
);
343 test
->qxl_worker
->start(test
->qxl_worker
);
346 static void set_compression_level(QXLInstance
*qin
, int level
)
348 printf("%s\n", __func__
);
351 static void set_mm_time(QXLInstance
*qin
, uint32_t mm_time
)
354 static void get_init_info(QXLInstance
*qin
, QXLDevInitInfo
*info
)
356 memset(info
, 0, sizeof(*info
));
357 info
->num_memslots
= 1;
358 info
->num_memslots_groups
= 1;
359 info
->memslot_id_bits
= 1;
360 info
->memslot_gen_bits
= 1;
361 info
->n_surfaces
= 1;
364 // called from spice_server thread (i.e. red_worker thread)
365 static int get_command(QXLInstance
*qin
, struct QXLCommandExt
*ext
)
367 Test
*test
= SPICE_CONTAINEROF(qin
, Test
, qxl_instance
);
370 g_mutex_lock(test
->command_mutex
);
372 if ((test
->commands_end
- test
->commands_start
) == 0) {
377 *ext
= *test
->commands
[test
->commands_start
% COMMANDS_SIZE
];
378 g_assert(test
->commands_start
< test
->commands_end
);
379 test
->commands_start
++;
380 g_cond_signal(test
->command_cond
);
385 g_mutex_unlock(test
->command_mutex
);
389 static int req_cmd_notification(QXLInstance
*qin
)
391 Test
*test
= SPICE_CONTAINEROF(qin
, Test
, qxl_instance
);
393 //test->core->timer_start(test->wakeup_timer, test->wakeup_ms);
397 static void release_resource(QXLInstance
*qin
, struct QXLReleaseInfoExt release_info
)
399 QXLCommandExt
*ext
= (QXLCommandExt
*)(unsigned long)release_info
.info
->id
;
400 //printf("%s\n", __func__);
401 g_assert(release_info
.group_id
== MEM_SLOT_GROUP_ID
);
402 switch (ext
->cmd
.type
) {
404 test_spice_destroy_update((void*)ext
);
406 case QXL_CMD_SURFACE
:
409 case QXL_CMD_CURSOR
: {
410 QXLCursorCmd
*cmd
= (QXLCursorCmd
*)(unsigned long)ext
->cmd
.data
;
411 if (cmd
->type
== QXL_CURSOR_SET
) {
422 #define CURSOR_WIDTH 32
423 #define CURSOR_HEIGHT 32
427 uint8_t data
[CURSOR_WIDTH
* CURSOR_HEIGHT
* 4]; // 32bit per pixel
430 static void cursor_init()
432 cursor
.cursor
.header
.unique
= 0;
433 cursor
.cursor
.header
.type
= SPICE_CURSOR_TYPE_COLOR32
;
434 cursor
.cursor
.header
.width
= CURSOR_WIDTH
;
435 cursor
.cursor
.header
.height
= CURSOR_HEIGHT
;
436 cursor
.cursor
.header
.hot_spot_x
= 0;
437 cursor
.cursor
.header
.hot_spot_y
= 0;
438 cursor
.cursor
.data_size
= CURSOR_WIDTH
* CURSOR_HEIGHT
* 4;
440 // X drivers addes it to the cursor size because it could be
441 // cursor data information or another cursor related stuffs.
442 // Otherwise, the code will break in client/cursor.cpp side,
443 // that expect the data_size plus cursor information.
444 // Blame cursor protocol for this. :-)
445 cursor
.cursor
.data_size
+= 128;
446 cursor
.cursor
.chunk
.data_size
= cursor
.cursor
.data_size
;
447 cursor
.cursor
.chunk
.prev_chunk
= cursor
.cursor
.chunk
.next_chunk
= 0;
450 static int get_cursor_command(QXLInstance
*qin
, struct QXLCommandExt
*ext
)
452 Test
*test
= SPICE_CONTAINEROF(qin
, Test
, qxl_instance
);
453 static int color
= 0;
455 static int x
= 0, y
= 0;
456 QXLCursorCmd
*cursor_cmd
;
459 if (!test
->cursor_notify
) {
463 test
->cursor_notify
--;
464 cmd
= calloc(sizeof(QXLCommandExt
), 1);
465 cursor_cmd
= calloc(sizeof(QXLCursorCmd
), 1);
467 cursor_cmd
->release_info
.id
= (unsigned long)cmd
;
470 cursor_cmd
->type
= QXL_CURSOR_SET
;
471 cursor_cmd
->u
.set
.position
.x
= 0;
472 cursor_cmd
->u
.set
.position
.y
= 0;
473 cursor_cmd
->u
.set
.visible
= TRUE
;
474 cursor_cmd
->u
.set
.shape
= (unsigned long)&cursor
;
475 // Only a white rect (32x32) as cursor
476 memset(cursor
.data
, 255, sizeof(cursor
.data
));
479 cursor_cmd
->type
= QXL_CURSOR_MOVE
;
480 cursor_cmd
->u
.position
.x
= x
++ % test
->primary_width
;
481 cursor_cmd
->u
.position
.y
= y
++ % test
->primary_height
;
484 cmd
->cmd
.data
= (unsigned long)cursor_cmd
;
485 cmd
->cmd
.type
= QXL_CMD_CURSOR
;
486 cmd
->group_id
= MEM_SLOT_GROUP_ID
;
489 //printf("%s\n", __func__);
493 static int req_cursor_notification(QXLInstance
*qin
)
495 printf("%s\n", __func__
);
499 static void notify_update(QXLInstance
*qin
, uint32_t update_id
)
501 printf("%s\n", __func__
);
504 static int flush_resources(QXLInstance
*qin
)
506 printf("%s\n", __func__
);
510 static int client_monitors_config(QXLInstance
*qin
,
511 VDAgentMonitorsConfig
*monitors_config
)
513 if (!monitors_config
) {
514 printf("%s: NULL monitors_config\n", __func__
);
516 printf("%s: %d\n", __func__
, monitors_config
->num_of_monitors
);
521 static void set_client_capabilities(QXLInstance
*qin
,
522 uint8_t client_present
,
525 Test
*test
= SPICE_CONTAINEROF(qin
, Test
, qxl_instance
);
527 printf("%s: present %d caps %d\n", __func__
, client_present
, caps
[0]);
528 if (test
->on_client_connected
&& client_present
) {
529 test
->on_client_connected(test
);
531 if (test
->on_client_disconnected
&& !client_present
) {
532 test
->on_client_disconnected(test
);
536 static int client_count
= 0;
538 static void client_connected(Test
*test
)
540 printf("Client connected\n");
544 static void client_disconnected(Test
*test
)
547 if (client_count
> 0) {
549 printf("Client disconnected\n");
550 exit(0); // fixme: cleanup?
554 static void do_conn_timeout(void *opaque
)
558 if (client_count
<= 0) {
559 printf("do_conn_timeout\n");
560 exit (0); // fixme: cleanup?
565 QXLInterface display_sif
= {
567 .type
= SPICE_INTERFACE_QXL
,
568 .description
= "test",
569 .major_version
= SPICE_INTERFACE_QXL_MAJOR
,
570 .minor_version
= SPICE_INTERFACE_QXL_MINOR
572 .attache_worker
= attache_worker
,
573 .set_compression_level
= set_compression_level
,
574 .set_mm_time
= set_mm_time
,
575 .get_init_info
= get_init_info
,
577 /* the callbacks below are called from spice server thread context */
578 .get_command
= get_command
,
579 .req_cmd_notification
= req_cmd_notification
,
580 .release_resource
= release_resource
,
581 .get_cursor_command
= get_cursor_command
,
582 .req_cursor_notification
= req_cursor_notification
,
583 .notify_update
= notify_update
,
584 .flush_resources
= flush_resources
,
585 .client_monitors_config
= client_monitors_config
,
586 .set_client_capabilities
= set_client_capabilities
,
589 /* interface for tests */
590 void test_add_display_interface(Test
* test
)
592 spice_server_add_interface(test
->server
, &test
->qxl_instance
.base
);
595 static int vmc_write(SpiceCharDeviceInstance
*sin
, const uint8_t *buf
, int len
)
597 // printf("%s: %d\n", __func__, len);
601 static int vmc_read(SpiceCharDeviceInstance
*sin
, uint8_t *buf
, int len
)
603 // printf("%s: %d\n", __func__, len);
607 static void vmc_state(SpiceCharDeviceInstance
*sin
, int connected
)
609 // printf("%s: %d\n", __func__, connected);
612 static SpiceCharDeviceInterface vdagent_sif
= {
613 .base
.type
= SPICE_INTERFACE_CHAR_DEVICE
,
614 .base
.description
= "test spice virtual channel char device",
615 .base
.major_version
= SPICE_INTERFACE_CHAR_DEVICE_MAJOR
,
616 .base
.minor_version
= SPICE_INTERFACE_CHAR_DEVICE_MINOR
,
622 SpiceCharDeviceInstance vdagent_sin
= {
624 .sif
= &vdagent_sif
.base
,
626 .subtype
= "vdagent",
629 void test_add_agent_interface(SpiceServer
*server
)
631 spice_server_add_interface(server
, &vdagent_sin
.base
);
634 static int my_charcode
= 65;
635 static int my_posx
= 0;
636 static void kbd_push_key(SpiceKbdInstance
*sin
, uint8_t frag
)
638 Test
*test
= SPICE_CONTAINEROF(sin
, Test
, keyboard_sin
);
640 printf("KEYCODE %u %p\n", frag
, test
);
644 void test_draw_update_char(Test
*test
, int x
, int y
, int c
, TextAttributes attrib
)
660 // unsuported attributes = (attrib.blink || attrib.unvisible)
662 //if (attrib.uline) {
663 //rfbDrawLine (vt->screen, rx, ry + 14, rxe, ry + 14, fg);
666 SimpleSpiceUpdate
*update
;
667 update
= test_draw_char(test
, x
, y
, c
, fg
, bg
);
668 push_command(test
, &update
->ext
);
671 static uint8_t kbd_get_leds(SpiceKbdInstance
*sin
)
676 static SpiceKbdInterface keyboard_sif
= {
677 .base
.type
= SPICE_INTERFACE_KEYBOARD
,
678 .base
.description
= "spiceterm keyboard device",
679 .base
.major_version
= SPICE_INTERFACE_KEYBOARD_MAJOR
,
680 .base
.minor_version
= SPICE_INTERFACE_KEYBOARD_MINOR
,
681 .push_scan_freg
= kbd_push_key
,
682 .get_leds
= kbd_get_leds
,
685 void test_add_keyboard_interface(Test
* test
)
687 spice_server_add_interface(test
->server
, &test
->keyboard_sin
.base
);
690 Test
*test_new(SpiceCoreInterface
*core
)
693 Test
*test
= g_new0(Test
, 1);
694 SpiceServer
* server
= spice_server_new();
696 test
->command_cond
= g_cond_new();
697 test
->command_mutex
= g_mutex_new();
699 test
->on_client_connected
= client_connected
,
700 test
->on_client_disconnected
= client_disconnected
,
702 test
->qxl_instance
.base
.sif
= &display_sif
.base
;
703 test
->qxl_instance
.id
= 0;
705 test
->keyboard_sin
.base
.sif
= &keyboard_sif
.base
;
708 test
->server
= server
;
710 test
->cursor_notify
= NOTIFY_CURSOR_BATCH
;
711 // some common initialization for all display tests
712 printf("TESTER: listening on port %d (unsecure)\n", port
);
713 spice_server_set_port(server
, port
);
714 spice_server_set_noauth(server
);
715 int res
= spice_server_init(server
, core
);
717 g_error("spice_server_init failed, res = %d\n", res
);
721 test
->has_secondary
= 0;
723 int timeout
= 10; // max time to wait for client connection
724 test
->conn_timeout_timer
= core
->timer_add(do_conn_timeout
, test
);
725 test
->core
->timer_start(test
->conn_timeout_timer
, timeout
*1000);
731 __attribute__((noreturn
))
732 void usage(const char *argv0
, const int exitcode
)
735 printf("usage: %s\n", argv0
);
739 void spice_test_config_parse_args(int argc
, char **argv
)
741 struct option options
[] = {
742 // {"automated-tests", no_argument, &has_automated_tests, 1},
748 while ((val
= getopt_long(argc
, argv
, "", options
, &option_index
)) != -1) {
751 printf("unrecognized option '%s'\n", argv
[optind
- 1]);
752 usage(argv
[0], EXIT_FAILURE
);
759 printf("unknown argument '%s'\n", argv
[optind
]);
760 usage(argv
[0], EXIT_FAILURE
);