5
0
mirror of git://git.proxmox.com/git/spiceterm.git synced 2024-12-22 13:34:06 +03:00
spiceterm/screen.c
2013-10-25 13:31:16 +02:00

848 lines
23 KiB
C

/*
Copyright (C) 2013 Proxmox Server Solutions GmbH
Copyright: spiceterm is under GNU GPL, the GNU General Public License.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 dated June, 1991.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
Author: Dietmar Maurer <dietmar@proxmox.com>
Note: qlx drawing code is copied from spice-server test code.
*/
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
#include <sys/select.h>
#include <sys/types.h>
#include <getopt.h>
#include <spice.h>
#include <spice/enums.h>
#include <spice/macros.h>
#include <spice/qxl_dev.h>
#include <spice/vd_agent.h>
#include <sasl/sasl.h>
#include "glyphs.h"
#include "spiceterm.h"
static int debug = 0;
#define DPRINTF(x, format, ...) { \
if (x <= debug) { \
printf("%s: " format "\n" , __FUNCTION__, ## __VA_ARGS__); \
} \
}
#define MEM_SLOT_GROUP_ID 0
/* these colours are from linux kernel drivers/char/vt.c */
/* the default colour table, for VGA+ colour systems */
int default_red[] = {0x00,0xaa,0x00,0xaa,0x00,0xaa,0x00,0xaa,
0x55,0xff,0x55,0xff,0x55,0xff,0x55,0xff};
int default_grn[] = {0x00,0x00,0xaa,0x55,0x00,0x00,0xaa,0xaa,
0x55,0x55,0xff,0xff,0x55,0x55,0xff,0xff};
int default_blu[] = {0x00,0x00,0x00,0x00,0xaa,0xaa,0xaa,0xaa,
0x55,0x55,0x55,0x55,0xff,0xff,0xff,0xff};
/* Parts cribbed from spice-display.h/.c/qxl.c */
typedef struct SimpleSpiceUpdate {
QXLCommandExt ext; // needs to be first member
QXLDrawable drawable;
QXLImage image;
uint8_t *bitmap;
int cache_id; // do not free bitmap if cache_id != 0
} SimpleSpiceUpdate;
static void
spice_screen_destroy_update(SimpleSpiceUpdate *update)
{
if (!update) {
return;
}
if (update->drawable.clip.type != SPICE_CLIP_TYPE_NONE) {
uint8_t *ptr = (uint8_t*)update->drawable.clip.data;
free(ptr);
}
if (update->bitmap && !update->cache_id) {
g_free(update->bitmap);
}
g_free(update);
}
static void
release_qxl_command_ext(QXLCommandExt *ext)
{
g_assert(ext != NULL);
switch (ext->cmd.type) {
case QXL_CMD_DRAW:
spice_screen_destroy_update((void*)ext);
break;
case QXL_CMD_SURFACE:
free(ext);
break;
case QXL_CMD_CURSOR: {
QXLCursorCmd *cmd = (QXLCursorCmd *)(unsigned long)ext->cmd.data;
if (cmd->type == QXL_CURSOR_SET) {
free(cmd);
}
free(ext);
break;
}
default:
abort();
}
}
static void
release_resource(QXLInstance *qin, struct QXLReleaseInfoExt release_info)
{
QXLCommandExt *ext = (QXLCommandExt*)(unsigned long)release_info.info->id;
g_assert(release_info.group_id == MEM_SLOT_GROUP_ID);
release_qxl_command_ext(ext);
}
static int unique = 0x0ffff + 1;
static void
set_cmd(QXLCommandExt *ext, uint32_t type, QXLPHYSICAL data)
{
ext->cmd.type = type;
ext->cmd.data = data;
ext->cmd.padding = 0;
ext->group_id = MEM_SLOT_GROUP_ID;
ext->flags = 0;
}
static void
simple_set_release_info(QXLReleaseInfo *info, intptr_t ptr)
{
info->id = ptr;
//info->group_id = MEM_SLOT_GROUP_ID;
}
/* Note: push_command/get_command are called from different threads */
static void
push_command(SpiceScreen *spice_screen, QXLCommandExt *ext)
{
int need_wakeup = 1;
g_mutex_lock(spice_screen->command_mutex);
while (spice_screen->commands_end - spice_screen->commands_start >= COMMANDS_SIZE) {
g_cond_wait(spice_screen->command_cond, spice_screen->command_mutex);
}
g_assert(spice_screen->commands_end - spice_screen->commands_start < COMMANDS_SIZE);
if ((spice_screen->commands_end - spice_screen->commands_start) > 0) {
need_wakeup = 0;
}
spice_screen->commands[spice_screen->commands_end % COMMANDS_SIZE] = ext;
spice_screen->commands_end++;
if (need_wakeup) {
spice_screen->qxl_worker->wakeup(spice_screen->qxl_worker);
}
g_mutex_unlock(spice_screen->command_mutex);
}
/* bitmap are freed, so they must be allocated with g_malloc */
static SimpleSpiceUpdate *
spice_screen_update_from_bitmap_cmd(uint32_t surface_id, QXLRect bbox, uint8_t *bitmap, int cache_id)
{
SimpleSpiceUpdate *update;
QXLDrawable *drawable;
QXLImage *image;
uint32_t bw, bh;
bh = bbox.bottom - bbox.top;
bw = bbox.right - bbox.left;
update = g_new0(SimpleSpiceUpdate, 1);
update->bitmap = bitmap;
drawable = &update->drawable;
image = &update->image;
drawable->surface_id = surface_id;
drawable->bbox = bbox;
drawable->clip.type = SPICE_CLIP_TYPE_NONE;
drawable->effect = QXL_EFFECT_OPAQUE;
simple_set_release_info(&drawable->release_info, (intptr_t)update);
drawable->type = QXL_DRAW_COPY;
drawable->surfaces_dest[0] = -1;
drawable->surfaces_dest[1] = -1;
drawable->surfaces_dest[2] = -1;
drawable->u.copy.rop_descriptor = SPICE_ROPD_OP_PUT;
drawable->u.copy.src_bitmap = (intptr_t)image;
drawable->u.copy.src_area.right = bw;
drawable->u.copy.src_area.bottom = bh;
if (cache_id) {
QXL_SET_IMAGE_ID(image, QXL_IMAGE_GROUP_DEVICE, cache_id);
image->descriptor.flags = SPICE_IMAGE_FLAGS_CACHE_ME;
update->cache_id = cache_id;
} else {
QXL_SET_IMAGE_ID(image, QXL_IMAGE_GROUP_DEVICE, ++unique);
}
image->descriptor.type = SPICE_IMAGE_TYPE_BITMAP;
image->bitmap.flags = QXL_BITMAP_DIRECT | QXL_BITMAP_TOP_DOWN;
image->bitmap.stride = bw * 4;
image->descriptor.width = image->bitmap.x = bw;
image->descriptor.height = image->bitmap.y = bh;
image->bitmap.data = (intptr_t)bitmap;
image->bitmap.palette = 0;
image->bitmap.format = SPICE_BITMAP_FMT_32BIT;
set_cmd(&update->ext, QXL_CMD_DRAW, (intptr_t)drawable);
return update;
}
static SimpleSpiceUpdate *
spice_screen_draw_char_cmd(SpiceScreen *spice_screen, int x, int y, int c,
int fg, int bg, gboolean uline)
{
int top, left;
uint8_t *dst;
uint8_t *bitmap = NULL;
int bw, bh;
int i, j;
QXLRect bbox;
int cache_id = 0;
CachedImage *ce;
if (!uline && c < 256) {
cache_id = ((fg << 12) | (bg << 8) | (c & 255)) & 0x0ffff;
if ((ce = (CachedImage *)g_hash_table_lookup(spice_screen->image_cache, &cache_id))) {
bitmap = ce->bitmap;
}
}
left = x*8;
top = y*16;
bw = 8;
bh = 16;
if (!bitmap) {
bitmap = dst = g_malloc(bw * bh * 4);
unsigned char *data = vt_font_data + c*16;
unsigned char d = *data;
g_assert(fg >= 0 && fg < 16);
g_assert(bg >= 0 && bg < 16);
unsigned char fgc_red = default_red[fg];
unsigned char fgc_blue = default_blu[fg];
unsigned char fgc_green = default_grn[fg];
unsigned char bgc_red = default_red[bg];
unsigned char bgc_blue = default_blu[bg];
unsigned char bgc_green = default_grn[bg];
for (j = 0; j < 16; j++) {
gboolean ul = (j == 14) && uline;
for (i = 0; i < 8; i++) {
if (i == 0) {
d=*data;
data++;
}
if (ul || d&0x80) {
*(dst) = fgc_blue;
*(dst+1) = fgc_green;
*(dst+2) = fgc_red;
*(dst+3) = 0;
} else {
*(dst) = bgc_blue;
*(dst+1) = bgc_green;
*(dst+2) = bgc_red;
*(dst+3) = 0;
}
d<<=1;
dst += 4;
}
}
ce = g_new(CachedImage, 1);
ce->cache_id = cache_id;
ce->bitmap = bitmap;
g_hash_table_insert(spice_screen->image_cache, &ce->cache_id, ce);
}
bbox.left = left; bbox.top = top;
bbox.right = left + bw; bbox.bottom = top + bh;
return spice_screen_update_from_bitmap_cmd(0, bbox, bitmap, cache_id);
}
void
spice_screen_scroll(SpiceScreen *spice_screen, int x1, int y1,
int x2, int y2, int src_x, int src_y)
{
SimpleSpiceUpdate *update;
QXLDrawable *drawable;
QXLRect bbox;
int surface_id = 0;
update = g_new0(SimpleSpiceUpdate, 1);
drawable = &update->drawable;
bbox.left = x1;
bbox.top = y1;
bbox.right = x2;
bbox.bottom = y2;
drawable->surface_id = surface_id;
drawable->bbox = bbox;
drawable->clip.type = SPICE_CLIP_TYPE_NONE;
drawable->effect = QXL_EFFECT_OPAQUE;
simple_set_release_info(&drawable->release_info, (intptr_t)update);
drawable->type = QXL_COPY_BITS;
drawable->surfaces_dest[0] = -1;
drawable->surfaces_dest[1] = -1;
drawable->surfaces_dest[2] = -1;
drawable->u.copy_bits.src_pos.x = src_x;
drawable->u.copy_bits.src_pos.y = src_y;
set_cmd(&update->ext, QXL_CMD_DRAW, (intptr_t)drawable);
push_command(spice_screen, &update->ext);
}
void
spice_screen_clear(SpiceScreen *spice_screen, int x1, int y1, int x2, int y2)
{
SimpleSpiceUpdate *update;
QXLDrawable *drawable;
QXLRect bbox;
int surface_id = 0;
update = g_new0(SimpleSpiceUpdate, 1);
drawable = &update->drawable;
bbox.left = x1;
bbox.top = y1;
bbox.right = x2;
bbox.bottom = y2;
drawable->surface_id = surface_id;
drawable->bbox = bbox;
drawable->clip.type = SPICE_CLIP_TYPE_NONE;
drawable->effect = QXL_EFFECT_OPAQUE;
simple_set_release_info(&drawable->release_info, (intptr_t)update);
drawable->type = QXL_DRAW_BLACKNESS;
drawable->surfaces_dest[0] = -1;
drawable->surfaces_dest[1] = -1;
drawable->surfaces_dest[2] = -1;
set_cmd(&update->ext, QXL_CMD_DRAW, (intptr_t)drawable);
push_command(spice_screen, &update->ext);
}
static void
create_primary_surface(SpiceScreen *spice_screen, uint32_t width,
uint32_t height)
{
QXLWorker *qxl_worker = spice_screen->qxl_worker;
QXLDevSurfaceCreate surface = { 0, };
g_assert(height > 0);
g_assert(width > 0);
if (height > MAX_HEIGHT)
height = MAX_HEIGHT;
if (width > MAX_WIDTH)
width = MAX_WIDTH;
surface.format = SPICE_SURFACE_FMT_32_xRGB;
surface.width = spice_screen->primary_width = width;
surface.height = spice_screen->primary_height = height;
surface.stride = -width * 4; /* negative? */
surface.mouse_mode = TRUE; /* unused by red_worker */
surface.flags = 0;
surface.type = 0; /* unused by red_worker */
surface.position = 0; /* unused by red_worker */
surface.mem = (uint64_t)&spice_screen->primary_surface;
surface.group_id = MEM_SLOT_GROUP_ID;
spice_screen->width = width;
spice_screen->height = height;
spice_screen->cursor_set = 0;
qxl_worker->create_primary_surface(qxl_worker, 0, &surface);
}
QXLDevMemSlot slot = {
.slot_group_id = MEM_SLOT_GROUP_ID,
.slot_id = 0,
.generation = 0,
.virt_start = 0,
.virt_end = ~0,
.addr_delta = 0,
.qxl_ram_size = ~0,
};
static void
attache_worker(QXLInstance *qin, QXLWorker *_qxl_worker)
{
SpiceScreen *spice_screen = SPICE_CONTAINEROF(qin, SpiceScreen, qxl_instance);
if (spice_screen->qxl_worker) {
g_assert_not_reached();
}
spice_screen->qxl_worker = _qxl_worker;
spice_screen->qxl_worker->add_memslot(spice_screen->qxl_worker, &slot);
create_primary_surface(spice_screen, spice_screen->width, spice_screen->height);
spice_screen->qxl_worker->start(spice_screen->qxl_worker);
}
static void
set_compression_level(QXLInstance *qin, int level)
{
/* not used */
}
static void
set_mm_time(QXLInstance *qin, uint32_t mm_time)
{
/* not used */
}
static void
get_init_info(QXLInstance *qin, QXLDevInitInfo *info)
{
memset(info, 0, sizeof(*info));
info->num_memslots = 1;
info->num_memslots_groups = 1;
info->memslot_id_bits = 1;
info->memslot_gen_bits = 1;
info->n_surfaces = 1;
}
/* called from spice_server thread (i.e. red_worker thread) */
static int
get_command(QXLInstance *qin, struct QXLCommandExt *ext)
{
SpiceScreen *spice_screen = SPICE_CONTAINEROF(qin, SpiceScreen, qxl_instance);
int res = FALSE;
g_mutex_lock(spice_screen->command_mutex);
if ((spice_screen->commands_end - spice_screen->commands_start) == 0) {
res = FALSE;
goto ret;
}
*ext = *spice_screen->commands[spice_screen->commands_start % COMMANDS_SIZE];
g_assert(spice_screen->commands_start < spice_screen->commands_end);
spice_screen->commands_start++;
g_cond_signal(spice_screen->command_cond);
res = TRUE;
ret:
g_mutex_unlock(spice_screen->command_mutex);
return res;
}
void
discard_pending_commands(SpiceScreen *spice_screen)
{
int pos;
g_mutex_lock(spice_screen->command_mutex);
for (pos = spice_screen->commands_start; pos < spice_screen->commands_end; pos++) {
release_qxl_command_ext(spice_screen->commands[pos % COMMANDS_SIZE]);
}
spice_screen->commands_start = spice_screen->commands_end;
g_mutex_unlock(spice_screen->command_mutex);
}
static int
req_cmd_notification(QXLInstance *qin)
{
//SpiceScreen *spice_screen = SPICE_CONTAINEROF(qin, SpiceScreen, qxl_instance);
//spice_screen->core->timer_start(spice_screen->wakeup_timer, spice_screen->wakeup_ms);
return TRUE;
}
#define CURSOR_WIDTH 8
#define CURSOR_HEIGHT 16
static struct {
QXLCursor cursor;
uint8_t data[CURSOR_WIDTH * CURSOR_HEIGHT * 4]; // 32bit per pixel
} cursor;
static void
cursor_init()
{
cursor.cursor.header.unique = 0;
cursor.cursor.header.type = SPICE_CURSOR_TYPE_COLOR32;
cursor.cursor.header.width = CURSOR_WIDTH;
cursor.cursor.header.height = CURSOR_HEIGHT;
cursor.cursor.header.hot_spot_x = 0;
cursor.cursor.header.hot_spot_y = 0;
cursor.cursor.data_size = CURSOR_WIDTH * CURSOR_HEIGHT * 4;
// X drivers addes it to the cursor size because it could be
// cursor data information or another cursor related stuffs.
// Otherwise, the code will break in client/cursor.cpp side,
// that expect the data_size plus cursor information.
// Blame cursor protocol for this. :-)
cursor.cursor.data_size += 128;
cursor.cursor.chunk.data_size = cursor.cursor.data_size;
cursor.cursor.chunk.prev_chunk = cursor.cursor.chunk.next_chunk = 0;
}
static int
get_cursor_command(QXLInstance *qin, struct QXLCommandExt *ext)
{
SpiceScreen *spice_screen = SPICE_CONTAINEROF(qin, SpiceScreen, qxl_instance);
QXLCursorCmd *cursor_cmd;
QXLCommandExt *cmd;
if (spice_screen->cursor_set)
return FALSE;
spice_screen->cursor_set = 1;
cmd = calloc(sizeof(QXLCommandExt), 1);
cursor_cmd = calloc(sizeof(QXLCursorCmd), 1);
cursor_cmd->release_info.id = (unsigned long)cmd;
cursor_cmd->type = QXL_CURSOR_SET;
cursor_cmd->u.set.position.x = 0;
cursor_cmd->u.set.position.y = 0;
cursor_cmd->u.set.visible = TRUE;
cursor_cmd->u.set.shape = (unsigned long)&cursor;
// white rect as cursor
memset(cursor.data, 255, sizeof(cursor.data));
cmd->cmd.data = (unsigned long)cursor_cmd;
cmd->cmd.type = QXL_CMD_CURSOR;
cmd->group_id = MEM_SLOT_GROUP_ID;
cmd->flags = 0;
*ext = *cmd;
return TRUE;
}
static int
req_cursor_notification(QXLInstance *qin)
{
/* not used */
return TRUE;
}
static void
notify_update(QXLInstance *qin, uint32_t update_id)
{
/* not used */
}
static int
flush_resources(QXLInstance *qin)
{
/* not used */
return TRUE;
}
static int
client_monitors_config(QXLInstance *qin, VDAgentMonitorsConfig *monitors_config)
{
/* not used */
return 0;
}
static void
set_client_capabilities(QXLInstance *qin, uint8_t client_present,
uint8_t caps[58])
{
SpiceScreen *spice_screen = SPICE_CONTAINEROF(qin, SpiceScreen, qxl_instance);
DPRINTF(1, "present %d caps %d", client_present, caps[0]);
if (spice_screen->on_client_connected && client_present) {
spice_screen->on_client_connected(spice_screen);
}
if (spice_screen->on_client_disconnected && !client_present) {
spice_screen->on_client_disconnected(spice_screen);
}
}
static int client_count = 0;
static void
client_connected(SpiceScreen *spice_screen)
{
client_count++;
DPRINTF(1, "client_count = %d", client_count);
}
static void
client_disconnected(SpiceScreen *spice_screen)
{
if (client_count > 0) {
client_count--;
DPRINTF(1, "client_count = %d", client_count);
exit(0); // fixme: cleanup?
}
}
static void
do_conn_timeout(void *opaque)
{
// SpiceScreen *spice_screen = opaque;
if (client_count <= 0) {
printf("connection timeout - stopping server\n");
exit (0); // fixme: cleanup?
}
}
QXLInterface display_sif = {
.base = {
.type = SPICE_INTERFACE_QXL,
.description = "spiceterm display server",
.major_version = SPICE_INTERFACE_QXL_MAJOR,
.minor_version = SPICE_INTERFACE_QXL_MINOR
},
.attache_worker = attache_worker,
.set_compression_level = set_compression_level,
.set_mm_time = set_mm_time,
.get_init_info = get_init_info,
/* the callbacks below are called from spice server thread context */
.get_command = get_command,
.req_cmd_notification = req_cmd_notification,
.release_resource = release_resource,
.get_cursor_command = get_cursor_command,
.req_cursor_notification = req_cursor_notification,
.notify_update = notify_update,
.flush_resources = flush_resources,
.client_monitors_config = client_monitors_config,
.set_client_capabilities = set_client_capabilities,
};
void
spice_screen_draw_char(SpiceScreen *spice_screen, int x, int y, gunichar2 ch,
TextAttributes attrib)
{
int fg, bg;
int invers;
if (attrib.invers) {
invers = attrib.selected ? 0 : 1;
} else {
invers = attrib.selected ? 1 : 0;
}
if (invers) {
bg = attrib.fgcol;
fg = attrib.bgcol;
} else {
bg = attrib.bgcol;
fg = attrib.fgcol;
}
if (attrib.bold) {
fg += 8;
}
// unsuported attributes = (attrib.blink || attrib.unvisible)
int c = vt_fontmap[ch];
SimpleSpiceUpdate *update;
update = spice_screen_draw_char_cmd(spice_screen, x, y, c, fg, bg, attrib.uline);
push_command(spice_screen, &update->ext);
}
static int
sasl_checkpass_cb(sasl_conn_t *conn,
void *context,
const char *user,
const char *pass,
unsigned passlen,
struct propctx *propctx)
{
const void *remoteport = NULL;
char *clientip = NULL;
if (sasl_getprop(conn, SASL_IPREMOTEPORT, &remoteport) == SASL_OK) {
clientip = strtok(g_strdup(remoteport), ";");
} else {
clientip = g_strdup("unknown");
}
int res = pve_auth_verify(clientip, user, pass);
g_free(clientip);
return (res == 0) ? SASL_OK : SASL_NOAUTHZ;
}
static int
sasl_getopt_cb(void *context, const char *plugin_name,
const char *option,
const char **result, unsigned *len)
{
if (strcmp(option, "mech_list") == 0) {
*result = "plain";
len = NULL;
return SASL_OK;
}
return SASL_FAIL;
}
typedef int sasl_cb_fn(void);
static sasl_callback_t sasl_callbacks[] = {
{ SASL_CB_GETOPT, (sasl_cb_fn *)sasl_getopt_cb, NULL },
{ SASL_CB_SERVER_USERDB_CHECKPASS, (sasl_cb_fn *)sasl_checkpass_cb, NULL },
{ SASL_CB_LIST_END, NULL, NULL },
};
SpiceScreen *
spice_screen_new(SpiceCoreInterface *core, uint32_t width, uint32_t height,
SpiceTermOptions *opts)
{
SpiceScreen *spice_screen = g_new0(SpiceScreen, 1);
SpiceServer* server = spice_server_new();
char *x509_key_file = "/etc/pve/local/pve-ssl.key";
char *x509_cert_file = "/etc/pve/local/pve-ssl.pem";
char *x509_cacert_file = "/etc/pve/pve-root-ca.pem";
char *x509_key_password = NULL;
char *x509_dh_file = NULL;
char *tls_ciphers = "DES-CBC3-SHA";
spice_screen->width = width;
spice_screen->height = height;
spice_screen->command_cond = g_cond_new();
spice_screen->command_mutex = g_mutex_new();
spice_screen->on_client_connected = client_connected,
spice_screen->on_client_disconnected = client_disconnected,
spice_screen->qxl_instance.base.sif = &display_sif.base;
spice_screen->qxl_instance.id = 0;
spice_screen->core = core;
spice_screen->server = server;
if (opts->addr) {
printf("listening on '%s:%d' (TLS)\n", opts->addr, opts->port);
spice_server_set_addr(server, opts->addr, 0);
} else {
printf("listening on '*:%d' (TLS)\n", opts->port);
}
// spice_server_set_port(spice_server, port);
//spice_server_set_image_compression(server, SPICE_IMAGE_COMPRESS_OFF);
spice_server_set_tls(server, opts->port,
x509_cacert_file,
x509_cert_file,
x509_key_file,
x509_key_password,
x509_dh_file,
tls_ciphers);
if (opts->noauth) {
spice_server_set_noauth(server);
} else {
if (opts->sasl) {
spice_server_set_sasl(server, 1);
spice_server_set_sasl_appname(server, NULL); // enforce pve auth
spice_server_set_sasl_callbacks(server, sasl_callbacks);
} else {
char *ticket = getenv("SPICE_TICKET");
if (ticket) {
spice_server_set_ticket(server, ticket, 300, 0, 0);
}
}
}
int res = spice_server_init(server, core);
if (res != 0) {
g_error("spice_server_init failed, res = %d\n", res);
}
cursor_init();
spice_screen->conn_timeout_timer = core->timer_add(do_conn_timeout, spice_screen);
spice_screen->core->timer_start(spice_screen->conn_timeout_timer, opts->timeout*1000);
spice_server_add_interface(spice_screen->server, &spice_screen->qxl_instance.base);
return spice_screen;
}
void
spice_screen_resize(SpiceScreen *spice_screen, uint32_t width,
uint32_t height)
{
QXLWorker *qxl_worker = spice_screen->qxl_worker;
if (spice_screen->width == width && spice_screen->height == height) {
return;
}
discard_pending_commands(spice_screen);
qxl_worker->destroy_primary_surface(qxl_worker, 0);
create_primary_surface(spice_screen, width, height);
spice_screen_clear(spice_screen, 0, 0, width, height);
}