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>
42 #include <spice/vd_agent.h>
46 #include "spiceterm.h"
50 #define DPRINTF(x, format, ...) { \
52 printf("%s: " format "\n" , __FUNCTION__, ## __VA_ARGS__); \
56 #define MEM_SLOT_GROUP_ID 0
59 extern unsigned char color_table
[];
61 /* these colours are from linux kernel drivers/char/vt.c */
62 /* the default colour table, for VGA+ colour systems */
63 int default_red
[] = {0x00,0xaa,0x00,0xaa,0x00,0xaa,0x00,0xaa,
64 0x55,0xff,0x55,0xff,0x55,0xff,0x55,0xff};
65 int default_grn
[] = {0x00,0x00,0xaa,0x55,0x00,0x00,0xaa,0xaa,
66 0x55,0x55,0xff,0xff,0x55,0x55,0xff,0xff};
67 int default_blu
[] = {0x00,0x00,0x00,0x00,0xaa,0xaa,0xaa,0xaa,
68 0x55,0x55,0x55,0x55,0xff,0xff,0xff,0xff};
70 /* Parts cribbed from spice-display.h/.c/qxl.c */
72 typedef struct SimpleSpiceUpdate
{
73 QXLCommandExt ext
; // needs to be first member
77 int cache_id
; // do not free bitmap if cache_id != 0
81 spice_screen_destroy_update(SimpleSpiceUpdate
*update
)
86 if (update
->drawable
.clip
.type
!= SPICE_CLIP_TYPE_NONE
) {
87 uint8_t *ptr
= (uint8_t*)update
->drawable
.clip
.data
;
90 if (update
->bitmap
&& !update
->cache_id
) {
91 g_free(update
->bitmap
);
98 release_qxl_command_ext(QXLCommandExt
*ext
)
100 g_assert(ext
!= NULL
);
102 switch (ext
->cmd
.type
) {
104 spice_screen_destroy_update((void*)ext
);
106 case QXL_CMD_SURFACE
:
109 case QXL_CMD_CURSOR
: {
110 QXLCursorCmd
*cmd
= (QXLCursorCmd
*)(unsigned long)ext
->cmd
.data
;
111 if (cmd
->type
== QXL_CURSOR_SET
) {
123 release_resource(QXLInstance
*qin
, struct QXLReleaseInfoExt release_info
)
125 QXLCommandExt
*ext
= (QXLCommandExt
*)(unsigned long)release_info
.info
->id
;
127 g_assert(release_info
.group_id
== MEM_SLOT_GROUP_ID
);
128 release_qxl_command_ext(ext
);
131 static int unique
= 0x0ffff + 1;
134 set_cmd(QXLCommandExt
*ext
, uint32_t type
, QXLPHYSICAL data
)
136 ext
->cmd
.type
= type
;
137 ext
->cmd
.data
= data
;
138 ext
->cmd
.padding
= 0;
139 ext
->group_id
= MEM_SLOT_GROUP_ID
;
144 simple_set_release_info(QXLReleaseInfo
*info
, intptr_t ptr
)
147 //info->group_id = MEM_SLOT_GROUP_ID;
150 /* Note: push_command/get_command are called from different threads */
153 push_command(SpiceScreen
*spice_screen
, QXLCommandExt
*ext
)
157 g_mutex_lock(&spice_screen
->command_mutex
);
159 while (spice_screen
->commands_end
- spice_screen
->commands_start
>= COMMANDS_SIZE
) {
160 g_cond_wait(&spice_screen
->command_cond
, &spice_screen
->command_mutex
);
163 g_assert(spice_screen
->commands_end
- spice_screen
->commands_start
< COMMANDS_SIZE
);
165 if ((spice_screen
->commands_end
- spice_screen
->commands_start
) > 0) {
169 spice_screen
->commands
[spice_screen
->commands_end
% COMMANDS_SIZE
] = ext
;
170 spice_screen
->commands_end
++;
173 spice_qxl_wakeup(&spice_screen
->qxl_instance
);
174 //spice_screen->qxl_worker->wakeup(spice_screen->qxl_worker);
177 g_mutex_unlock(&spice_screen
->command_mutex
);
181 /* bitmap are freed, so they must be allocated with g_malloc */
182 static SimpleSpiceUpdate
*
183 spice_screen_update_from_bitmap_cmd(uint32_t surface_id
, QXLRect bbox
, uint8_t *bitmap
, int cache_id
)
185 SimpleSpiceUpdate
*update
;
186 QXLDrawable
*drawable
;
190 bh
= bbox
.bottom
- bbox
.top
;
191 bw
= bbox
.right
- bbox
.left
;
193 update
= g_new0(SimpleSpiceUpdate
, 1);
194 update
->bitmap
= bitmap
;
195 drawable
= &update
->drawable
;
196 image
= &update
->image
;
198 drawable
->surface_id
= surface_id
;
200 drawable
->bbox
= bbox
;
201 drawable
->clip
.type
= SPICE_CLIP_TYPE_NONE
;
202 drawable
->effect
= QXL_EFFECT_OPAQUE
;
203 simple_set_release_info(&drawable
->release_info
, (intptr_t)update
);
204 drawable
->type
= QXL_DRAW_COPY
;
205 drawable
->surfaces_dest
[0] = -1;
206 drawable
->surfaces_dest
[1] = -1;
207 drawable
->surfaces_dest
[2] = -1;
209 drawable
->u
.copy
.rop_descriptor
= SPICE_ROPD_OP_PUT
;
210 drawable
->u
.copy
.src_bitmap
= (intptr_t)image
;
211 drawable
->u
.copy
.src_area
.right
= bw
;
212 drawable
->u
.copy
.src_area
.bottom
= bh
;
215 QXL_SET_IMAGE_ID(image
, QXL_IMAGE_GROUP_DEVICE
, cache_id
);
216 image
->descriptor
.flags
= SPICE_IMAGE_FLAGS_CACHE_ME
;
217 update
->cache_id
= cache_id
;
219 QXL_SET_IMAGE_ID(image
, QXL_IMAGE_GROUP_DEVICE
, ++unique
);
221 image
->descriptor
.type
= SPICE_IMAGE_TYPE_BITMAP
;
222 image
->bitmap
.flags
= QXL_BITMAP_DIRECT
| QXL_BITMAP_TOP_DOWN
;
223 image
->bitmap
.stride
= bw
* 4;
224 image
->descriptor
.width
= image
->bitmap
.x
= bw
;
225 image
->descriptor
.height
= image
->bitmap
.y
= bh
;
226 image
->bitmap
.data
= (intptr_t)bitmap
;
227 image
->bitmap
.palette
= 0;
228 image
->bitmap
.format
= SPICE_BITMAP_FMT_32BIT
;
230 set_cmd(&update
->ext
, QXL_CMD_DRAW
, (intptr_t)drawable
);
235 static SimpleSpiceUpdate
*
236 spice_screen_draw_char_cmd(SpiceScreen
*spice_screen
, int x
, int y
, int c
,
237 int fg
, int bg
, gboolean uline
)
241 uint8_t *bitmap
= NULL
;
248 if (!uline
&& c
< 256) {
249 cache_id
= ((fg
<< 12) | (bg
<< 8) | (c
& 255)) & 0x0ffff;
250 if ((ce
= (CachedImage
*)g_hash_table_lookup(spice_screen
->image_cache
, &cache_id
))) {
262 bitmap
= dst
= g_malloc(bw
* bh
* 4);
264 unsigned char *data
= vt_font_data
+ c
*16;
265 unsigned char d
= *data
;
267 g_assert(fg
>= 0 && fg
< 16);
268 g_assert(bg
>= 0 && bg
< 16);
270 unsigned char fgc_red
= default_red
[color_table
[fg
]];
271 unsigned char fgc_blue
= default_blu
[color_table
[fg
]];
272 unsigned char fgc_green
= default_grn
[color_table
[fg
]];
273 unsigned char bgc_red
= default_red
[color_table
[bg
]];
274 unsigned char bgc_blue
= default_blu
[color_table
[bg
]];
275 unsigned char bgc_green
= default_grn
[color_table
[bg
]];
277 for (j
= 0; j
< 16; j
++) {
278 gboolean ul
= (j
== 14) && uline
;
279 for (i
= 0; i
< 8; i
++) {
286 *(dst
+1) = fgc_green
;
291 *(dst
+1) = bgc_green
;
301 ce
= g_new(CachedImage
, 1);
302 ce
->cache_id
= cache_id
;
304 g_hash_table_insert(spice_screen
->image_cache
, &ce
->cache_id
, ce
);
308 bbox
.left
= left
; bbox
.top
= top
;
309 bbox
.right
= left
+ bw
; bbox
.bottom
= top
+ bh
;
311 return spice_screen_update_from_bitmap_cmd(0, bbox
, bitmap
, cache_id
);
315 spice_screen_scroll(SpiceScreen
*spice_screen
, int x1
, int y1
,
316 int x2
, int y2
, int src_x
, int src_y
)
318 SimpleSpiceUpdate
*update
;
319 QXLDrawable
*drawable
;
324 update
= g_new0(SimpleSpiceUpdate
, 1);
325 drawable
= &update
->drawable
;
332 drawable
->surface_id
= surface_id
;
334 drawable
->bbox
= bbox
;
335 drawable
->clip
.type
= SPICE_CLIP_TYPE_NONE
;
336 drawable
->effect
= QXL_EFFECT_OPAQUE
;
337 simple_set_release_info(&drawable
->release_info
, (intptr_t)update
);
338 drawable
->type
= QXL_COPY_BITS
;
339 drawable
->surfaces_dest
[0] = -1;
340 drawable
->surfaces_dest
[1] = -1;
341 drawable
->surfaces_dest
[2] = -1;
343 drawable
->u
.copy_bits
.src_pos
.x
= src_x
;
344 drawable
->u
.copy_bits
.src_pos
.y
= src_y
;
346 set_cmd(&update
->ext
, QXL_CMD_DRAW
, (intptr_t)drawable
);
348 push_command(spice_screen
, &update
->ext
);
352 spice_screen_clear(SpiceScreen
*spice_screen
, int x1
, int y1
, int x2
, int y2
)
354 SimpleSpiceUpdate
*update
;
355 QXLDrawable
*drawable
;
360 update
= g_new0(SimpleSpiceUpdate
, 1);
361 drawable
= &update
->drawable
;
368 drawable
->surface_id
= surface_id
;
370 drawable
->bbox
= bbox
;
371 drawable
->clip
.type
= SPICE_CLIP_TYPE_NONE
;
372 drawable
->effect
= QXL_EFFECT_OPAQUE
;
373 simple_set_release_info(&drawable
->release_info
, (intptr_t)update
);
374 drawable
->type
= QXL_DRAW_BLACKNESS
;
375 drawable
->surfaces_dest
[0] = -1;
376 drawable
->surfaces_dest
[1] = -1;
377 drawable
->surfaces_dest
[2] = -1;
379 set_cmd(&update
->ext
, QXL_CMD_DRAW
, (intptr_t)drawable
);
381 push_command(spice_screen
, &update
->ext
);
385 create_primary_surface(SpiceScreen
*spice_screen
, uint32_t width
,
388 QXLDevSurfaceCreate surface
= { 0, };
390 g_assert(height
> 0);
393 if (height
> MAX_HEIGHT
)
396 if (width
> MAX_WIDTH
)
399 surface
.format
= SPICE_SURFACE_FMT_32_xRGB
;
400 surface
.width
= spice_screen
->primary_width
= width
;
401 surface
.height
= spice_screen
->primary_height
= height
;
402 surface
.stride
= -width
* 4; /* negative? */
403 surface
.mouse_mode
= TRUE
; /* unused by red_worker */
405 surface
.type
= 0; /* unused by red_worker */
406 surface
.position
= 0; /* unused by red_worker */
407 surface
.mem
= (uint64_t)&spice_screen
->primary_surface
;
408 surface
.group_id
= MEM_SLOT_GROUP_ID
;
410 spice_screen
->width
= width
;
411 spice_screen
->height
= height
;
413 spice_screen
->cursor_set
= 0;
415 spice_qxl_create_primary_surface(&spice_screen
->qxl_instance
, 0, &surface
);
418 QXLDevMemSlot slot
= {
419 .slot_group_id
= MEM_SLOT_GROUP_ID
,
429 attache_worker(QXLInstance
*qin
, QXLWorker
*_qxl_worker
)
431 SpiceScreen
*spice_screen
= SPICE_CONTAINEROF(qin
, SpiceScreen
, qxl_instance
);
433 if (spice_screen
->qxl_worker
) {
434 g_assert_not_reached();
437 spice_screen
->qxl_worker
= _qxl_worker
;
438 spice_qxl_add_memslot(&spice_screen
->qxl_instance
, &slot
);
439 create_primary_surface(spice_screen
, spice_screen
->width
, spice_screen
->height
);
440 spice_server_vm_start(spice_screen
->server
);
444 set_compression_level(QXLInstance
*qin
, int level
)
450 set_mm_time(QXLInstance
*qin
, uint32_t mm_time
)
456 get_init_info(QXLInstance
*qin
, QXLDevInitInfo
*info
)
458 memset(info
, 0, sizeof(*info
));
459 info
->num_memslots
= 1;
460 info
->num_memslots_groups
= 1;
461 info
->memslot_id_bits
= 1;
462 info
->memslot_gen_bits
= 1;
463 info
->n_surfaces
= 1;
466 /* called from spice_server thread (i.e. red_worker thread) */
468 get_command(QXLInstance
*qin
, struct QXLCommandExt
*ext
)
470 SpiceScreen
*spice_screen
= SPICE_CONTAINEROF(qin
, SpiceScreen
, qxl_instance
);
473 g_mutex_lock(&spice_screen
->command_mutex
);
475 if ((spice_screen
->commands_end
- spice_screen
->commands_start
) == 0) {
480 *ext
= *spice_screen
->commands
[spice_screen
->commands_start
% COMMANDS_SIZE
];
481 g_assert(spice_screen
->commands_start
< spice_screen
->commands_end
);
482 spice_screen
->commands_start
++;
483 g_cond_signal(&spice_screen
->command_cond
);
488 g_mutex_unlock(&spice_screen
->command_mutex
);
493 discard_pending_commands(SpiceScreen
*spice_screen
)
497 g_mutex_lock(&spice_screen
->command_mutex
);
498 for (pos
= spice_screen
->commands_start
; pos
< spice_screen
->commands_end
; pos
++) {
499 release_qxl_command_ext(spice_screen
->commands
[pos
% COMMANDS_SIZE
]);
501 spice_screen
->commands_start
= spice_screen
->commands_end
;
502 g_mutex_unlock(&spice_screen
->command_mutex
);
506 req_cmd_notification(QXLInstance
*qin
)
508 //SpiceScreen *spice_screen = SPICE_CONTAINEROF(qin, SpiceScreen, qxl_instance);
509 //spice_screen->core->timer_start(spice_screen->wakeup_timer, spice_screen->wakeup_ms);
514 #define CURSOR_WIDTH 8
515 #define CURSOR_HEIGHT 16
519 uint8_t data
[CURSOR_WIDTH
* CURSOR_HEIGHT
* 4]; // 32bit per pixel
525 cursor
.cursor
.header
.unique
= 0;
526 cursor
.cursor
.header
.type
= SPICE_CURSOR_TYPE_COLOR32
;
527 cursor
.cursor
.header
.width
= CURSOR_WIDTH
;
528 cursor
.cursor
.header
.height
= CURSOR_HEIGHT
;
529 cursor
.cursor
.header
.hot_spot_x
= 0;
530 cursor
.cursor
.header
.hot_spot_y
= 0;
531 cursor
.cursor
.data_size
= CURSOR_WIDTH
* CURSOR_HEIGHT
* 4;
533 // X drivers addes it to the cursor size because it could be
534 // cursor data information or another cursor related stuffs.
535 // Otherwise, the code will break in client/cursor.cpp side,
536 // that expect the data_size plus cursor information.
537 // Blame cursor protocol for this. :-)
538 cursor
.cursor
.data_size
+= 128;
539 cursor
.cursor
.chunk
.data_size
= cursor
.cursor
.data_size
;
540 cursor
.cursor
.chunk
.prev_chunk
= cursor
.cursor
.chunk
.next_chunk
= 0;
544 get_cursor_command(QXLInstance
*qin
, struct QXLCommandExt
*ext
)
546 SpiceScreen
*spice_screen
= SPICE_CONTAINEROF(qin
, SpiceScreen
, qxl_instance
);
548 QXLCursorCmd
*cursor_cmd
;
551 if (spice_screen
->cursor_set
)
554 spice_screen
->cursor_set
= 1;
556 cmd
= calloc(sizeof(QXLCommandExt
), 1);
557 cursor_cmd
= calloc(sizeof(QXLCursorCmd
), 1);
559 cursor_cmd
->release_info
.id
= (unsigned long)cmd
;
561 cursor_cmd
->type
= QXL_CURSOR_SET
;
562 cursor_cmd
->u
.set
.position
.x
= 0;
563 cursor_cmd
->u
.set
.position
.y
= 0;
564 cursor_cmd
->u
.set
.visible
= TRUE
;
565 cursor_cmd
->u
.set
.shape
= (unsigned long)&cursor
;
566 // white rect as cursor
567 memset(cursor
.data
, 255, sizeof(cursor
.data
));
569 cmd
->cmd
.data
= (unsigned long)cursor_cmd
;
570 cmd
->cmd
.type
= QXL_CMD_CURSOR
;
571 cmd
->group_id
= MEM_SLOT_GROUP_ID
;
579 req_cursor_notification(QXLInstance
*qin
)
587 notify_update(QXLInstance
*qin
, uint32_t update_id
)
593 flush_resources(QXLInstance
*qin
)
601 client_monitors_config(QXLInstance
*qin
, VDAgentMonitorsConfig
*monitors_config
)
609 set_client_capabilities(QXLInstance
*qin
, uint8_t client_present
,
612 SpiceScreen
*spice_screen
= SPICE_CONTAINEROF(qin
, SpiceScreen
, qxl_instance
);
614 DPRINTF(1, "present %d caps %d", client_present
, caps
[0]);
616 if (spice_screen
->on_client_connected
&& client_present
) {
617 spice_screen
->on_client_connected(spice_screen
);
619 if (spice_screen
->on_client_disconnected
&& !client_present
) {
620 spice_screen
->on_client_disconnected(spice_screen
);
624 static int client_count
= 0;
627 client_connected(SpiceScreen
*spice_screen
)
631 DPRINTF(1, "client_count = %d", client_count
);
635 client_disconnected(SpiceScreen
*spice_screen
)
637 if (client_count
> 0) {
639 DPRINTF(1, "client_count = %d", client_count
);
640 exit(0); // fixme: cleanup?
645 do_conn_timeout(void *opaque
)
647 // SpiceScreen *spice_screen = opaque;
649 if (client_count
<= 0) {
650 printf("connection timeout - stopping server\n");
651 exit (0); // fixme: cleanup?
656 QXLInterface display_sif
= {
658 .type
= SPICE_INTERFACE_QXL
,
659 .description
= "spiceterm display server",
660 .major_version
= SPICE_INTERFACE_QXL_MAJOR
,
661 .minor_version
= SPICE_INTERFACE_QXL_MINOR
663 .attache_worker
= attache_worker
,
664 .set_compression_level
= set_compression_level
,
665 .set_mm_time
= set_mm_time
,
666 .get_init_info
= get_init_info
,
668 /* the callbacks below are called from spice server thread context */
669 .get_command
= get_command
,
670 .req_cmd_notification
= req_cmd_notification
,
671 .release_resource
= release_resource
,
672 .get_cursor_command
= get_cursor_command
,
673 .req_cursor_notification
= req_cursor_notification
,
674 .notify_update
= notify_update
,
675 .flush_resources
= flush_resources
,
676 .client_monitors_config
= client_monitors_config
,
677 .set_client_capabilities
= set_client_capabilities
,
682 spice_screen_draw_char(SpiceScreen
*spice_screen
, int x
, int y
, gunichar2 ch
,
683 TextAttributes attrib
)
689 invers
= attrib
.selected
? 0 : 1;
691 invers
= attrib
.selected
? 1 : 0;
706 // unsuported attributes = (attrib.blink || attrib.unvisible)
708 int c
= vt_fontmap
[ch
];
710 SimpleSpiceUpdate
*update
;
711 update
= spice_screen_draw_char_cmd(spice_screen
, x
, y
, c
, fg
, bg
, attrib
.uline
);
712 push_command(spice_screen
, &update
->ext
);
716 spice_screen_new(SpiceCoreInterface
*core
, uint32_t width
, uint32_t height
,
717 SpiceTermOptions
*opts
)
719 SpiceScreen
*spice_screen
= g_new0(SpiceScreen
, 1);
720 SpiceServer
* server
= spice_server_new();
721 char *x509_key_file
= "/etc/pve/local/pve-ssl.key";
722 char *x509_cert_file
= "/etc/pve/local/pve-ssl.pem";
723 char *x509_cacert_file
= "/etc/pve/pve-root-ca.pem";
724 char *x509_key_password
= NULL
;
725 char *x509_dh_file
= NULL
;
726 char *tls_ciphers
= "HIGH";
728 spice_screen
->width
= width
;
729 spice_screen
->height
= height
;
731 g_cond_init(&spice_screen
->command_cond
);
732 g_mutex_init(&spice_screen
->command_mutex
);
734 spice_screen
->on_client_connected
= client_connected
,
735 spice_screen
->on_client_disconnected
= client_disconnected
,
737 spice_screen
->qxl_instance
.base
.sif
= &display_sif
.base
;
738 spice_screen
->qxl_instance
.id
= 0;
740 spice_screen
->core
= core
;
741 spice_screen
->server
= server
;
744 printf("listening on '%s:%d' (TLS)\n", opts
->addr
, opts
->port
);
745 spice_server_set_addr(server
, opts
->addr
, 0);
747 printf("listening on '*:%d' (TLS)\n", opts
->port
);
750 // spice_server_set_port(spice_server, port);
751 //spice_server_set_image_compression(server, SPICE_IMAGE_COMPRESS_OFF);
753 spice_server_set_tls(server
, opts
->port
,
762 spice_server_set_noauth(server
);
764 char *ticket
= getenv("SPICE_TICKET");
766 spice_server_set_ticket(server
, ticket
, 300, 0, 0);
770 int res
= spice_server_init(server
, core
);
772 g_error("spice_server_init failed, res = %d\n", res
);
777 if (opts
->timeout
> 0) {
778 spice_screen
->conn_timeout_timer
= core
->timer_add(do_conn_timeout
, spice_screen
);
779 spice_screen
->core
->timer_start(spice_screen
->conn_timeout_timer
, opts
->timeout
*1000);
782 spice_server_add_interface(spice_screen
->server
, &spice_screen
->qxl_instance
.base
);
788 spice_screen_resize(SpiceScreen
*spice_screen
, uint32_t width
,
791 if (spice_screen
->width
== width
&& spice_screen
->height
== height
) {
795 discard_pending_commands(spice_screen
);
797 spice_qxl_destroy_primary_surface(&spice_screen
->qxl_instance
, 0);
799 create_primary_surface(spice_screen
, width
, height
);
801 spice_screen_clear(spice_screen
, 0, 0, width
, height
);