3 Copyright (C) 2013 Proxmox Server Solutions GmbH
5 Copyright: spiceterm is under GNU GPL, the GNU General Public License.
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; version 2 dated June, 1991.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
21 Author: Dietmar Maurer <dietmar@proxmox.com>
23 Note: qlx drawing code is copied from spice-server test code.
34 #include <sys/select.h>
35 #include <sys/types.h>
39 #include <spice/enums.h>
40 #include <spice/macros.h>
41 #include <spice/qxl_dev.h>
45 #include "spiceterm.h"
49 #define DPRINTF(x, format, ...) { \
51 printf("%s: " format "\n" , __FUNCTION__, ## __VA_ARGS__); \
55 #define MEM_SLOT_GROUP_ID 0
57 #define NOTIFY_DISPLAY_BATCH (SINGLE_PART/2)
58 #define NOTIFY_CURSOR_BATCH 10
60 /* these colours are from linux kernel drivers/char/vt.c */
61 /* the default colour table, for VGA+ colour systems */
62 int default_red
[] = {0x00,0xaa,0x00,0xaa,0x00,0xaa,0x00,0xaa,
63 0x55,0xff,0x55,0xff,0x55,0xff,0x55,0xff};
64 int default_grn
[] = {0x00,0x00,0xaa,0x55,0x00,0x00,0xaa,0xaa,
65 0x55,0x55,0xff,0xff,0x55,0x55,0xff,0xff};
66 int default_blu
[] = {0x00,0x00,0x00,0x00,0xaa,0xaa,0xaa,0xaa,
67 0x55,0x55,0x55,0x55,0xff,0xff,0xff,0xff};
69 /* Parts cribbed from spice-display.h/.c/qxl.c */
71 typedef struct SimpleSpiceUpdate
{
72 QXLCommandExt ext
; // first
79 spice_screen_destroy_update(SimpleSpiceUpdate
*update
)
84 if (update
->drawable
.clip
.type
!= SPICE_CLIP_TYPE_NONE
) {
85 uint8_t *ptr
= (uint8_t*)update
->drawable
.clip
.data
;
88 g_free(update
->bitmap
);
92 #define DEFAULT_WIDTH 640
93 #define DEFAULT_HEIGHT 320
95 static int unique
= 1;
98 set_cmd(QXLCommandExt
*ext
, uint32_t type
, QXLPHYSICAL data
)
100 ext
->cmd
.type
= type
;
101 ext
->cmd
.data
= data
;
102 ext
->cmd
.padding
= 0;
103 ext
->group_id
= MEM_SLOT_GROUP_ID
;
108 simple_set_release_info(QXLReleaseInfo
*info
, intptr_t ptr
)
111 //info->group_id = MEM_SLOT_GROUP_ID;
114 /* Note: push_command/get_command are called from different threads */
117 push_command(SpiceScreen
*spice_screen
, QXLCommandExt
*ext
)
119 g_mutex_lock(spice_screen
->command_mutex
);
121 while (spice_screen
->commands_end
- spice_screen
->commands_start
>= COMMANDS_SIZE
) {
122 g_cond_wait(spice_screen
->command_cond
, spice_screen
->command_mutex
);
125 g_assert(spice_screen
->commands_end
- spice_screen
->commands_start
< COMMANDS_SIZE
);
127 spice_screen
->commands
[spice_screen
->commands_end
% COMMANDS_SIZE
] = ext
;
128 spice_screen
->commands_end
++;
130 g_mutex_unlock(spice_screen
->command_mutex
);
132 spice_screen
->qxl_worker
->wakeup(spice_screen
->qxl_worker
);
135 /* bitmap are freed, so they must be allocated with g_malloc */
136 static SimpleSpiceUpdate
*
137 spice_screen_update_from_bitmap_cmd(uint32_t surface_id
, QXLRect bbox
, uint8_t *bitmap
)
139 SimpleSpiceUpdate
*update
;
140 QXLDrawable
*drawable
;
144 bh
= bbox
.bottom
- bbox
.top
;
145 bw
= bbox
.right
- bbox
.left
;
147 update
= g_new0(SimpleSpiceUpdate
, 1);
148 update
->bitmap
= bitmap
;
149 drawable
= &update
->drawable
;
150 image
= &update
->image
;
152 drawable
->surface_id
= surface_id
;
154 drawable
->bbox
= bbox
;
155 drawable
->clip
.type
= SPICE_CLIP_TYPE_NONE
;
156 drawable
->effect
= QXL_EFFECT_OPAQUE
;
157 simple_set_release_info(&drawable
->release_info
, (intptr_t)update
);
158 drawable
->type
= QXL_DRAW_COPY
;
159 drawable
->surfaces_dest
[0] = -1;
160 drawable
->surfaces_dest
[1] = -1;
161 drawable
->surfaces_dest
[2] = -1;
163 drawable
->u
.copy
.rop_descriptor
= SPICE_ROPD_OP_PUT
;
164 drawable
->u
.copy
.src_bitmap
= (intptr_t)image
;
165 drawable
->u
.copy
.src_area
.right
= bw
;
166 drawable
->u
.copy
.src_area
.bottom
= bh
;
168 QXL_SET_IMAGE_ID(image
, QXL_IMAGE_GROUP_DEVICE
, unique
);
169 image
->descriptor
.type
= SPICE_IMAGE_TYPE_BITMAP
;
170 image
->bitmap
.flags
= QXL_BITMAP_DIRECT
| QXL_BITMAP_TOP_DOWN
;
171 image
->bitmap
.stride
= bw
* 4;
172 image
->descriptor
.width
= image
->bitmap
.x
= bw
;
173 image
->descriptor
.height
= image
->bitmap
.y
= bh
;
174 image
->bitmap
.data
= (intptr_t)bitmap
;
175 image
->bitmap
.palette
= 0;
176 image
->bitmap
.format
= SPICE_BITMAP_FMT_32BIT
;
178 set_cmd(&update
->ext
, QXL_CMD_DRAW
, (intptr_t)drawable
);
183 static SimpleSpiceUpdate
*
184 spice_screen_draw_char_cmd(SpiceScreen
*spice_screen
, int x
, int y
, int c
,
202 bitmap
= dst
= g_malloc(bw
* bh
* 4);
204 unsigned char *data
= vt_font_data
+ c
*16;
205 unsigned char d
= *data
;
207 g_assert(fg
>= 0 && fg
< 16);
208 g_assert(bg
>= 0 && bg
< 16);
210 unsigned char fgc_red
= default_red
[fg
];
211 unsigned char fgc_blue
= default_blu
[fg
];
212 unsigned char fgc_green
= default_grn
[fg
];
213 unsigned char bgc_red
= default_red
[bg
];
214 unsigned char bgc_blue
= default_blu
[bg
];
215 unsigned char bgc_green
= default_grn
[bg
];
217 for (j
= 0; j
< 16; j
++) {
218 for (i
= 0; i
< 8; i
++) {
225 *(dst
+1) = fgc_green
;
230 *(dst
+1) = bgc_green
;
239 bbox
.left
= left
; bbox
.top
= top
;
240 bbox
.right
= left
+ bw
; bbox
.bottom
= top
+ bh
;
242 return spice_screen_update_from_bitmap_cmd(0, bbox
, bitmap
);
246 spice_screen_scroll(SpiceScreen
*spice_screen
, int x1
, int y1
,
247 int x2
, int y2
, int src_x
, int src_y
)
249 SimpleSpiceUpdate
*update
;
250 QXLDrawable
*drawable
;
255 update
= g_new0(SimpleSpiceUpdate
, 1);
256 drawable
= &update
->drawable
;
263 drawable
->surface_id
= surface_id
;
265 drawable
->bbox
= bbox
;
266 drawable
->clip
.type
= SPICE_CLIP_TYPE_NONE
;
267 drawable
->effect
= QXL_EFFECT_OPAQUE
;
268 simple_set_release_info(&drawable
->release_info
, (intptr_t)update
);
269 drawable
->type
= QXL_COPY_BITS
;
270 drawable
->surfaces_dest
[0] = -1;
271 drawable
->surfaces_dest
[1] = -1;
272 drawable
->surfaces_dest
[2] = -1;
274 drawable
->u
.copy_bits
.src_pos
.x
= src_x
;
275 drawable
->u
.copy_bits
.src_pos
.y
= src_y
;
277 set_cmd(&update
->ext
, QXL_CMD_DRAW
, (intptr_t)drawable
);
279 push_command(spice_screen
, &update
->ext
);
283 spice_screen_clear(SpiceScreen
*spice_screen
, int x1
, int y1
, int x2
, int y2
)
285 SimpleSpiceUpdate
*update
;
286 QXLDrawable
*drawable
;
291 update
= g_new0(SimpleSpiceUpdate
, 1);
292 drawable
= &update
->drawable
;
299 drawable
->surface_id
= surface_id
;
301 drawable
->bbox
= bbox
;
302 drawable
->clip
.type
= SPICE_CLIP_TYPE_NONE
;
303 drawable
->effect
= QXL_EFFECT_OPAQUE
;
304 simple_set_release_info(&drawable
->release_info
, (intptr_t)update
);
305 drawable
->type
= QXL_DRAW_BLACKNESS
;
306 drawable
->surfaces_dest
[0] = -1;
307 drawable
->surfaces_dest
[1] = -1;
308 drawable
->surfaces_dest
[2] = -1;
310 set_cmd(&update
->ext
, QXL_CMD_DRAW
, (intptr_t)drawable
);
312 push_command(spice_screen
, &update
->ext
);
316 create_primary_surface(SpiceScreen
*spice_screen
, uint32_t width
,
319 QXLWorker
*qxl_worker
= spice_screen
->qxl_worker
;
320 QXLDevSurfaceCreate surface
= { 0, };
322 g_assert(height
<= MAX_HEIGHT
);
323 g_assert(width
<= MAX_WIDTH
);
324 g_assert(height
> 0);
327 surface
.format
= SPICE_SURFACE_FMT_32_xRGB
;
328 surface
.width
= spice_screen
->primary_width
= width
;
329 surface
.height
= spice_screen
->primary_height
= height
;
330 surface
.stride
= -width
* 4; /* negative? */
331 surface
.mouse_mode
= TRUE
; /* unused by red_worker */
333 surface
.type
= 0; /* unused by red_worker */
334 surface
.position
= 0; /* unused by red_worker */
335 surface
.mem
= (uint64_t)&spice_screen
->primary_surface
;
336 surface
.group_id
= MEM_SLOT_GROUP_ID
;
338 spice_screen
->width
= width
;
339 spice_screen
->height
= height
;
341 qxl_worker
->create_primary_surface(qxl_worker
, 0, &surface
);
344 QXLDevMemSlot slot
= {
345 .slot_group_id
= MEM_SLOT_GROUP_ID
,
355 attache_worker(QXLInstance
*qin
, QXLWorker
*_qxl_worker
)
357 SpiceScreen
*spice_screen
= SPICE_CONTAINEROF(qin
, SpiceScreen
, qxl_instance
);
359 if (spice_screen
->qxl_worker
) {
360 g_assert_not_reached();
363 spice_screen
->qxl_worker
= _qxl_worker
;
364 spice_screen
->qxl_worker
->add_memslot(spice_screen
->qxl_worker
, &slot
);
365 create_primary_surface(spice_screen
, DEFAULT_WIDTH
, DEFAULT_HEIGHT
);
366 spice_screen
->qxl_worker
->start(spice_screen
->qxl_worker
);
370 set_compression_level(QXLInstance
*qin
, int level
)
376 set_mm_time(QXLInstance
*qin
, uint32_t mm_time
)
382 get_init_info(QXLInstance
*qin
, QXLDevInitInfo
*info
)
384 memset(info
, 0, sizeof(*info
));
385 info
->num_memslots
= 1;
386 info
->num_memslots_groups
= 1;
387 info
->memslot_id_bits
= 1;
388 info
->memslot_gen_bits
= 1;
389 info
->n_surfaces
= 1;
392 /* called from spice_server thread (i.e. red_worker thread) */
394 get_command(QXLInstance
*qin
, struct QXLCommandExt
*ext
)
396 SpiceScreen
*spice_screen
= SPICE_CONTAINEROF(qin
, SpiceScreen
, qxl_instance
);
399 g_mutex_lock(spice_screen
->command_mutex
);
401 if ((spice_screen
->commands_end
- spice_screen
->commands_start
) == 0) {
406 *ext
= *spice_screen
->commands
[spice_screen
->commands_start
% COMMANDS_SIZE
];
407 g_assert(spice_screen
->commands_start
< spice_screen
->commands_end
);
408 spice_screen
->commands_start
++;
409 g_cond_signal(spice_screen
->command_cond
);
414 g_mutex_unlock(spice_screen
->command_mutex
);
419 req_cmd_notification(QXLInstance
*qin
)
421 //SpiceScreen *spice_screen = SPICE_CONTAINEROF(qin, SpiceScreen, qxl_instance);
422 //spice_screen->core->timer_start(spice_screen->wakeup_timer, spice_screen->wakeup_ms);
428 release_resource(QXLInstance
*qin
, struct QXLReleaseInfoExt release_info
)
430 QXLCommandExt
*ext
= (QXLCommandExt
*)(unsigned long)release_info
.info
->id
;
432 g_assert(release_info
.group_id
== MEM_SLOT_GROUP_ID
);
433 switch (ext
->cmd
.type
) {
435 spice_screen_destroy_update((void*)ext
);
437 case QXL_CMD_SURFACE
:
440 case QXL_CMD_CURSOR
: {
441 QXLCursorCmd
*cmd
= (QXLCursorCmd
*)(unsigned long)ext
->cmd
.data
;
442 if (cmd
->type
== QXL_CURSOR_SET
) {
453 #define CURSOR_WIDTH 8
454 #define CURSOR_HEIGHT 16
458 uint8_t data
[CURSOR_WIDTH
* CURSOR_HEIGHT
* 4]; // 32bit per pixel
464 cursor
.cursor
.header
.unique
= 0;
465 cursor
.cursor
.header
.type
= SPICE_CURSOR_TYPE_COLOR32
;
466 cursor
.cursor
.header
.width
= CURSOR_WIDTH
;
467 cursor
.cursor
.header
.height
= CURSOR_HEIGHT
;
468 cursor
.cursor
.header
.hot_spot_x
= 0;
469 cursor
.cursor
.header
.hot_spot_y
= 0;
470 cursor
.cursor
.data_size
= CURSOR_WIDTH
* CURSOR_HEIGHT
* 4;
472 // X drivers addes it to the cursor size because it could be
473 // cursor data information or another cursor related stuffs.
474 // Otherwise, the code will break in client/cursor.cpp side,
475 // that expect the data_size plus cursor information.
476 // Blame cursor protocol for this. :-)
477 cursor
.cursor
.data_size
+= 128;
478 cursor
.cursor
.chunk
.data_size
= cursor
.cursor
.data_size
;
479 cursor
.cursor
.chunk
.prev_chunk
= cursor
.cursor
.chunk
.next_chunk
= 0;
483 get_cursor_command(QXLInstance
*qin
, struct QXLCommandExt
*ext
)
485 SpiceScreen
*spice_screen
= SPICE_CONTAINEROF(qin
, SpiceScreen
, qxl_instance
);
487 static int x
= 0, y
= 0;
488 QXLCursorCmd
*cursor_cmd
;
491 if (!spice_screen
->cursor_notify
) {
495 spice_screen
->cursor_notify
--;
496 cmd
= calloc(sizeof(QXLCommandExt
), 1);
497 cursor_cmd
= calloc(sizeof(QXLCursorCmd
), 1);
499 cursor_cmd
->release_info
.id
= (unsigned long)cmd
;
502 cursor_cmd
->type
= QXL_CURSOR_SET
;
503 cursor_cmd
->u
.set
.position
.x
= 0;
504 cursor_cmd
->u
.set
.position
.y
= 0;
505 cursor_cmd
->u
.set
.visible
= TRUE
;
506 cursor_cmd
->u
.set
.shape
= (unsigned long)&cursor
;
507 // white rect as cursor
508 memset(cursor
.data
, 255, sizeof(cursor
.data
));
511 cursor_cmd
->type
= QXL_CURSOR_MOVE
;
512 cursor_cmd
->u
.position
.x
= x
++ % spice_screen
->primary_width
;
513 cursor_cmd
->u
.position
.y
= y
++ % spice_screen
->primary_height
;
516 cmd
->cmd
.data
= (unsigned long)cursor_cmd
;
517 cmd
->cmd
.type
= QXL_CMD_CURSOR
;
518 cmd
->group_id
= MEM_SLOT_GROUP_ID
;
526 req_cursor_notification(QXLInstance
*qin
)
534 notify_update(QXLInstance
*qin
, uint32_t update_id
)
540 flush_resources(QXLInstance
*qin
)
548 client_monitors_config(QXLInstance
*qin
, VDAgentMonitorsConfig
*monitors_config
)
556 set_client_capabilities(QXLInstance
*qin
, uint8_t client_present
,
559 SpiceScreen
*spice_screen
= SPICE_CONTAINEROF(qin
, SpiceScreen
, qxl_instance
);
561 DPRINTF(1, "%s: present %d caps %d", __func__
, client_present
, caps
[0]);
563 if (spice_screen
->on_client_connected
&& client_present
) {
564 spice_screen
->on_client_connected(spice_screen
);
566 if (spice_screen
->on_client_disconnected
&& !client_present
) {
567 spice_screen
->on_client_disconnected(spice_screen
);
571 static int client_count
= 0;
574 client_connected(SpiceScreen
*spice_screen
)
578 DPRINTF(1, "%s: client_count = %d", __func__
, client_count
);
582 client_disconnected(SpiceScreen
*spice_screen
)
584 if (client_count
> 0) {
586 DPRINTF(1, "%s: client_count = %d", __func__
, client_count
);
587 exit(0); // fixme: cleanup?
592 do_conn_timeout(void *opaque
)
594 // SpiceScreen *spice_screen = opaque;
596 if (client_count
<= 0) {
597 printf("connection timeout - stopping server\n");
598 exit (0); // fixme: cleanup?
602 QXLInterface display_sif
= {
604 .type
= SPICE_INTERFACE_QXL
,
605 .description
= "spiceterm display server",
606 .major_version
= SPICE_INTERFACE_QXL_MAJOR
,
607 .minor_version
= SPICE_INTERFACE_QXL_MINOR
609 .attache_worker
= attache_worker
,
610 .set_compression_level
= set_compression_level
,
611 .set_mm_time
= set_mm_time
,
612 .get_init_info
= get_init_info
,
614 /* the callbacks below are called from spice server thread context */
615 .get_command
= get_command
,
616 .req_cmd_notification
= req_cmd_notification
,
617 .release_resource
= release_resource
,
618 .get_cursor_command
= get_cursor_command
,
619 .req_cursor_notification
= req_cursor_notification
,
620 .notify_update
= notify_update
,
621 .flush_resources
= flush_resources
,
622 .client_monitors_config
= client_monitors_config
,
623 .set_client_capabilities
= set_client_capabilities
,
627 spice_screen_add_display_interface(SpiceScreen
* spice_screen
)
629 spice_server_add_interface(spice_screen
->server
, &spice_screen
->qxl_instance
.base
);
632 /* vdagent interface - not sure why we need that? */
634 vmc_write(SpiceCharDeviceInstance
*sin
, const uint8_t *buf
, int len
)
640 vmc_read(SpiceCharDeviceInstance
*sin
, uint8_t *buf
, int len
)
646 vmc_state(SpiceCharDeviceInstance
*sin
, int connected
)
651 static SpiceCharDeviceInterface vdagent_sif
= {
652 .base
.type
= SPICE_INTERFACE_CHAR_DEVICE
,
653 .base
.description
= "spice virtual channel char device",
654 .base
.major_version
= SPICE_INTERFACE_CHAR_DEVICE_MAJOR
,
655 .base
.minor_version
= SPICE_INTERFACE_CHAR_DEVICE_MINOR
,
661 SpiceCharDeviceInstance vdagent_sin
= {
663 .sif
= &vdagent_sif
.base
,
665 .subtype
= "vdagent",
669 spice_screen_add_agent_interface(SpiceServer
*server
)
671 spice_server_add_interface(server
, &vdagent_sin
.base
);
675 spice_screen_draw_char(SpiceScreen
*spice_screen
, int x
, int y
, gunichar2 ch
, TextAttributes attrib
)
691 // unsuported attributes = (attrib.blink || attrib.unvisible)
694 //if (attrib.uline) {
695 //rfbDrawLine (vt->screen, rx, ry + 14, rxe, ry + 14, fg);
698 int c
= vt_fontmap
[ch
];
700 SimpleSpiceUpdate
*update
;
701 update
= spice_screen_draw_char_cmd(spice_screen
, x
, y
, c
, fg
, bg
);
702 push_command(spice_screen
, &update
->ext
);
706 spice_screen_new(SpiceCoreInterface
*core
)
709 SpiceScreen
*spice_screen
= g_new0(SpiceScreen
, 1);
710 SpiceServer
* server
= spice_server_new();
712 spice_screen
->command_cond
= g_cond_new();
713 spice_screen
->command_mutex
= g_mutex_new();
715 spice_screen
->on_client_connected
= client_connected
,
716 spice_screen
->on_client_disconnected
= client_disconnected
,
718 spice_screen
->qxl_instance
.base
.sif
= &display_sif
.base
;
719 spice_screen
->qxl_instance
.id
= 0;
721 spice_screen
->core
= core
;
722 spice_screen
->server
= server
;
724 spice_screen
->cursor_notify
= NOTIFY_CURSOR_BATCH
;
726 printf("listening on port %d (unsecure)\n", port
);
728 spice_server_set_port(server
, port
);
729 spice_server_set_noauth(server
);
731 int res
= spice_server_init(server
, core
);
733 g_error("spice_server_init failed, res = %d\n", res
);
738 int timeout
= 10; // max time to wait for client connection
739 spice_screen
->conn_timeout_timer
= core
->timer_add(do_conn_timeout
, spice_screen
);
740 spice_screen
->core
->timer_start(spice_screen
->conn_timeout_timer
, timeout
*1000);