diff options
Diffstat (limited to 'ui')
| -rw-r--r-- | ui/console.c | 25 | ||||
| -rw-r--r-- | ui/gtk.c | 13 | ||||
| -rw-r--r-- | ui/qemu-pixman.c | 29 | ||||
| -rw-r--r-- | ui/sdl.c | 26 | ||||
| -rw-r--r-- | ui/sdl2-2d.c | 13 | ||||
| -rw-r--r-- | ui/sdl2.c | 13 | ||||
| -rw-r--r-- | ui/spice-core.c | 9 | ||||
| -rw-r--r-- | ui/spice-display.c | 19 | ||||
| -rw-r--r-- | ui/vnc.c | 649 | ||||
| -rw-r--r-- | ui/vnc.h | 5 |
10 files changed, 610 insertions, 191 deletions
diff --git a/ui/console.c b/ui/console.c index 258af5dfff..87574a73a8 100644 --- a/ui/console.c +++ b/ui/console.c @@ -1439,6 +1439,31 @@ void dpy_gfx_replace_surface(QemuConsole *con, qemu_free_displaysurface(old_surface); } +bool dpy_gfx_check_format(QemuConsole *con, + pixman_format_code_t format) +{ + DisplayChangeListener *dcl; + DisplayState *s = con->ds; + + QLIST_FOREACH(dcl, &s->listeners, next) { + if (dcl->con && dcl->con != con) { + /* dcl bound to another console -> skip */ + continue; + } + if (dcl->ops->dpy_gfx_check_format) { + if (!dcl->ops->dpy_gfx_check_format(dcl, format)) { + return false; + } + } else { + /* default is to whitelist native 32 bpp only */ + if (format != qemu_default_pixman_format(32, true)) { + return false; + } + } + } + return true; +} + static void dpy_refresh(DisplayState *s) { DisplayChangeListener *dcl; diff --git a/ui/gtk.c b/ui/gtk.c index 0385757bf5..6a81076ffc 100644 --- a/ui/gtk.c +++ b/ui/gtk.c @@ -1654,12 +1654,13 @@ static GtkWidget *gd_create_menu_machine(GtkDisplayState *s) } static const DisplayChangeListenerOps dcl_ops = { - .dpy_name = "gtk", - .dpy_gfx_update = gd_update, - .dpy_gfx_switch = gd_switch, - .dpy_refresh = gd_refresh, - .dpy_mouse_set = gd_mouse_set, - .dpy_cursor_define = gd_cursor_define, + .dpy_name = "gtk", + .dpy_gfx_update = gd_update, + .dpy_gfx_switch = gd_switch, + .dpy_gfx_check_format = qemu_pixman_check_format, + .dpy_refresh = gd_refresh, + .dpy_mouse_set = gd_mouse_set, + .dpy_cursor_define = gd_cursor_define, }; static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc, diff --git a/ui/qemu-pixman.c b/ui/qemu-pixman.c index 1f6fea535b..4116e1507b 100644 --- a/ui/qemu-pixman.c +++ b/ui/qemu-pixman.c @@ -84,7 +84,7 @@ pixman_format_code_t qemu_default_pixman_format(int bpp, bool native_endian) break; } } - g_assert_not_reached(); + return 0; } int qemu_pixman_get_type(int rshift, int gshift, int bshift) @@ -125,6 +125,33 @@ pixman_format_code_t qemu_pixman_get_format(PixelFormat *pf) return format; } +/* + * Return true for known-good pixman conversions. + * + * UIs using pixman for format conversion can hook this into + * DisplayChangeListenerOps->dpy_gfx_check_format + */ +bool qemu_pixman_check_format(DisplayChangeListener *dcl, + pixman_format_code_t format) +{ + switch (format) { + /* 32 bpp */ + case PIXMAN_x8r8g8b8: + case PIXMAN_a8r8g8b8: + case PIXMAN_b8g8r8x8: + case PIXMAN_b8g8r8a8: + /* 24 bpp */ + case PIXMAN_r8g8b8: + case PIXMAN_b8g8r8: + /* 16 bpp */ + case PIXMAN_x1r5g5b5: + case PIXMAN_r5g6b5: + return true; + default: + return false; + } +} + pixman_image_t *qemu_pixman_linebuf_create(pixman_format_code_t format, int width) { diff --git a/ui/sdl.c b/ui/sdl.c index 3e9d81076b..138ca73407 100644 --- a/ui/sdl.c +++ b/ui/sdl.c @@ -151,6 +151,19 @@ static void sdl_switch(DisplayChangeListener *dcl, pf.bmask, pf.amask); } +static bool sdl_check_format(DisplayChangeListener *dcl, + pixman_format_code_t format) +{ + /* + * We let SDL convert for us a few more formats than, + * the native ones. Thes are the ones I have tested. + */ + return (format == PIXMAN_x8r8g8b8 || + format == PIXMAN_b8g8r8x8 || + format == PIXMAN_x1r5g5b5 || + format == PIXMAN_r5g6b5); +} + /* generic keyboard conversion */ #include "sdl_keysym.h" @@ -865,12 +878,13 @@ static void sdl_cleanup(void) } static const DisplayChangeListenerOps dcl_ops = { - .dpy_name = "sdl", - .dpy_gfx_update = sdl_update, - .dpy_gfx_switch = sdl_switch, - .dpy_refresh = sdl_refresh, - .dpy_mouse_set = sdl_mouse_warp, - .dpy_cursor_define = sdl_mouse_define, + .dpy_name = "sdl", + .dpy_gfx_update = sdl_update, + .dpy_gfx_switch = sdl_switch, + .dpy_gfx_check_format = sdl_check_format, + .dpy_refresh = sdl_refresh, + .dpy_mouse_set = sdl_mouse_warp, + .dpy_cursor_define = sdl_mouse_define, }; void sdl_display_init(DisplayState *ds, int full_screen, int no_frame) diff --git a/ui/sdl2-2d.c b/ui/sdl2-2d.c index 9264817e76..f907c21895 100644 --- a/ui/sdl2-2d.c +++ b/ui/sdl2-2d.c @@ -120,3 +120,16 @@ void sdl2_2d_redraw(struct sdl2_console *scon) surface_width(scon->surface), surface_height(scon->surface)); } + +bool sdl2_2d_check_format(DisplayChangeListener *dcl, + pixman_format_code_t format) +{ + /* + * We let SDL convert for us a few more formats than, + * the native ones. Thes are the ones I have tested. + */ + return (format == PIXMAN_x8r8g8b8 || + format == PIXMAN_b8g8r8x8 || + format == PIXMAN_x1r5g5b5 || + format == PIXMAN_r5g6b5); +} diff --git a/ui/sdl2.c b/ui/sdl2.c index 1ae2781624..60e3c3b6fa 100644 --- a/ui/sdl2.c +++ b/ui/sdl2.c @@ -668,12 +668,13 @@ static void sdl_cleanup(void) } static const DisplayChangeListenerOps dcl_2d_ops = { - .dpy_name = "sdl2-2d", - .dpy_gfx_update = sdl2_2d_update, - .dpy_gfx_switch = sdl2_2d_switch, - .dpy_refresh = sdl2_2d_refresh, - .dpy_mouse_set = sdl_mouse_warp, - .dpy_cursor_define = sdl_mouse_define, + .dpy_name = "sdl2-2d", + .dpy_gfx_update = sdl2_2d_update, + .dpy_gfx_switch = sdl2_2d_switch, + .dpy_gfx_check_format = sdl2_2d_check_format, + .dpy_refresh = sdl2_2d_refresh, + .dpy_mouse_set = sdl_mouse_warp, + .dpy_cursor_define = sdl_mouse_define, }; void sdl_display_init(DisplayState *ds, int full_screen, int no_frame) diff --git a/ui/spice-core.c b/ui/spice-core.c index fe705c1ae2..c8f7f183c6 100644 --- a/ui/spice-core.c +++ b/ui/spice-core.c @@ -436,6 +436,11 @@ static QemuOptsList qemu_spice_opts = { },{ .name = "ipv6", .type = QEMU_OPT_BOOL, +#ifdef SPICE_ADDR_FLAG_UNIX_ONLY + },{ + .name = "unix", + .type = QEMU_OPT_BOOL, +#endif },{ .name = "password", .type = QEMU_OPT_STRING, @@ -708,6 +713,10 @@ void qemu_spice_init(void) addr_flags |= SPICE_ADDR_FLAG_IPV4_ONLY; } else if (qemu_opt_get_bool(opts, "ipv6", 0)) { addr_flags |= SPICE_ADDR_FLAG_IPV6_ONLY; +#ifdef SPICE_ADDR_FLAG_UNIX_ONLY + } else if (qemu_opt_get_bool(opts, "unix", 0)) { + addr_flags |= SPICE_ADDR_FLAG_UNIX_ONLY; +#endif } spice_server = spice_server_new(); diff --git a/ui/spice-display.c b/ui/spice-display.c index d2e379379f..16441852e4 100644 --- a/ui/spice-display.c +++ b/ui/spice-display.c @@ -438,9 +438,6 @@ void qemu_spice_display_switch(SimpleSpiceDisplay *ssd, qemu_mutex_lock(&ssd->lock); need_destroy = (ssd->ds != NULL); ssd->ds = surface; - ssd->surface = pixman_image_ref(ssd->ds->image); - ssd->mirror = qemu_pixman_mirror_create(ssd->ds->format, - ssd->ds->image); while ((update = QTAILQ_FIRST(&ssd->updates)) != NULL) { QTAILQ_REMOVE(&ssd->updates, update, next); qemu_spice_destroy_update(ssd, update); @@ -450,6 +447,9 @@ void qemu_spice_display_switch(SimpleSpiceDisplay *ssd, qemu_spice_destroy_host_primary(ssd); } if (ssd->ds) { + ssd->surface = pixman_image_ref(ssd->ds->image); + ssd->mirror = qemu_pixman_mirror_create(ssd->ds->format, + ssd->ds->image); qemu_spice_create_host_primary(ssd); } @@ -760,12 +760,13 @@ static void display_mouse_define(DisplayChangeListener *dcl, } static const DisplayChangeListenerOps display_listener_ops = { - .dpy_name = "spice", - .dpy_gfx_update = display_update, - .dpy_gfx_switch = display_switch, - .dpy_refresh = display_refresh, - .dpy_mouse_set = display_mouse_set, - .dpy_cursor_define = display_mouse_define, + .dpy_name = "spice", + .dpy_gfx_update = display_update, + .dpy_gfx_switch = display_switch, + .dpy_gfx_check_format = qemu_pixman_check_format, + .dpy_refresh = display_refresh, + .dpy_mouse_set = display_mouse_set, + .dpy_cursor_define = display_mouse_define, }; static void qemu_spice_display_init_one(QemuConsole *con) diff --git a/ui/vnc.c b/ui/vnc.c index 57070150d4..a742c9071c 100644 --- a/ui/vnc.c +++ b/ui/vnc.c @@ -27,10 +27,12 @@ #include "vnc.h" #include "vnc-jobs.h" #include "trace.h" +#include "hw/qdev.h" #include "sysemu/sysemu.h" #include "qemu/sockets.h" #include "qemu/timer.h" #include "qemu/acl.h" +#include "qemu/config-file.h" #include "qapi/qmp/types.h" #include "qmp-commands.h" #include "qemu/osdep.h" @@ -46,7 +48,8 @@ static const struct timeval VNC_REFRESH_LOSSY = { 2, 0 }; #include "vnc_keysym.h" #include "d3des.h" -static VncDisplay *vnc_display; /* needed for info vnc */ +static QTAILQ_HEAD(, VncDisplay) vnc_displays = + QTAILQ_HEAD_INITIALIZER(vnc_displays); static int vnc_cursor_define(VncState *vs); static void vnc_release_modifiers(VncState *vs); @@ -65,12 +68,34 @@ static void vnc_set_share_mode(VncState *vs, VncShareMode mode) vs->csock, mn[vs->share_mode], mn[mode]); #endif - if (vs->share_mode == VNC_SHARE_MODE_EXCLUSIVE) { + switch (vs->share_mode) { + case VNC_SHARE_MODE_CONNECTING: + vs->vd->num_connecting--; + break; + case VNC_SHARE_MODE_SHARED: + vs->vd->num_shared--; + break; + case VNC_SHARE_MODE_EXCLUSIVE: vs->vd->num_exclusive--; + break; + default: + break; } + vs->share_mode = mode; - if (vs->share_mode == VNC_SHARE_MODE_EXCLUSIVE) { + + switch (vs->share_mode) { + case VNC_SHARE_MODE_CONNECTING: + vs->vd->num_connecting++; + break; + case VNC_SHARE_MODE_SHARED: + vs->vd->num_shared++; + break; + case VNC_SHARE_MODE_EXCLUSIVE: vs->vd->num_exclusive++; + break; + default: + break; } } @@ -226,10 +251,10 @@ static const char *vnc_auth_name(VncDisplay *vd) { return "unknown"; } -static VncServerInfo *vnc_server_info_get(void) +static VncServerInfo *vnc_server_info_get(VncDisplay *vd) { VncServerInfo *info; - VncBasicInfo *bi = vnc_basic_info_get_from_server_addr(vnc_display->lsock); + VncBasicInfo *bi = vnc_basic_info_get_from_server_addr(vd->lsock); if (!bi) { return NULL; } @@ -237,7 +262,7 @@ static VncServerInfo *vnc_server_info_get(void) info = g_malloc(sizeof(*info)); info->base = bi; info->has_auth = true; - info->auth = g_strdup(vnc_auth_name(vnc_display)); + info->auth = g_strdup(vnc_auth_name(vd)); return info; } @@ -282,7 +307,7 @@ static void vnc_qmp_event(VncState *vs, QAPIEvent event) } g_assert(vs->info->base); - si = vnc_server_info_get(); + si = vnc_server_info_get(vs->vd); if (!si) { return; } @@ -328,6 +353,9 @@ static VncClientInfo *qmp_query_vnc_client(const VncState *client) info->base->host = g_strdup(host); info->base->service = g_strdup(serv); info->base->family = inet_netfamily(sa.ss_family); +#ifdef CONFIG_VNC_WS + info->base->websocket = client->websocket; +#endif #ifdef CONFIG_VNC_TLS if (client->tls.session && client->tls.dname) { @@ -345,43 +373,59 @@ static VncClientInfo *qmp_query_vnc_client(const VncState *client) return info; } +static VncDisplay *vnc_display_find(const char *id) +{ + VncDisplay *vd; + + if (id == NULL) { + return QTAILQ_FIRST(&vnc_displays); + } + QTAILQ_FOREACH(vd, &vnc_displays, next) { + if (strcmp(id, vd->id) == 0) { + return vd; + } + } + return NULL; +} + +static VncClientInfoList *qmp_query_client_list(VncDisplay *vd) +{ + VncClientInfoList *cinfo, *prev = NULL; + VncState *client; + + QTAILQ_FOREACH(client, &vd->clients, next) { + cinfo = g_new0(VncClientInfoList, 1); + cinfo->value = qmp_query_vnc_client(client); + cinfo->next = prev; + prev = cinfo; + } + return prev; +} + VncInfo *qmp_query_vnc(Error **errp) { VncInfo *info = g_malloc0(sizeof(*info)); + VncDisplay *vd = vnc_display_find(NULL); - if (vnc_display == NULL || vnc_display->display == NULL) { + if (vd == NULL || vd->display == NULL) { info->enabled = false; } else { - VncClientInfoList *cur_item = NULL; struct sockaddr_storage sa; socklen_t salen = sizeof(sa); char host[NI_MAXHOST]; char serv[NI_MAXSERV]; - VncState *client; info->enabled = true; /* for compatibility with the original command */ info->has_clients = true; + info->clients = qmp_query_client_list(vd); - QTAILQ_FOREACH(client, &vnc_display->clients, next) { - VncClientInfoList *cinfo = g_malloc0(sizeof(*info)); - cinfo->value = qmp_query_vnc_client(client); - - /* XXX: waiting for the qapi to support GSList */ - if (!cur_item) { - info->clients = cur_item = cinfo; - } else { - cur_item->next = cinfo; - cur_item = cinfo; - } - } - - if (vnc_display->lsock == -1) { + if (vd->lsock == -1) { return info; } - if (getsockname(vnc_display->lsock, (struct sockaddr *)&sa, + if (getsockname(vd->lsock, (struct sockaddr *)&sa, &salen) == -1) { error_set(errp, QERR_UNDEFINED_ERROR); goto out_error; @@ -405,7 +449,7 @@ VncInfo *qmp_query_vnc(Error **errp) info->family = inet_netfamily(sa.ss_family); info->has_auth = true; - info->auth = g_strdup(vnc_auth_name(vnc_display)); + info->auth = g_strdup(vnc_auth_name(vd)); } return info; @@ -415,6 +459,142 @@ out_error: return NULL; } +static VncBasicInfoList *qmp_query_server_entry(int socket, + bool websocket, + VncBasicInfoList *prev) +{ + VncBasicInfoList *list; + VncBasicInfo *info; + struct sockaddr_storage sa; + socklen_t salen = sizeof(sa); + char host[NI_MAXHOST]; + char serv[NI_MAXSERV]; + + if (getsockname(socket, (struct sockaddr *)&sa, &salen) < 0 || + getnameinfo((struct sockaddr *)&sa, salen, + host, sizeof(host), serv, sizeof(serv), + NI_NUMERICHOST | NI_NUMERICSERV) < 0) { + return prev; + } + + info = g_new0(VncBasicInfo, 1); + info->host = g_strdup(host); + info->service = g_strdup(serv); + info->family = inet_netfamily(sa.ss_family); + info->websocket = websocket; + + list = g_new0(VncBasicInfoList, 1); + list->value = info; + list->next = prev; + return list; +} + +static void qmp_query_auth(VncDisplay *vd, VncInfo2 *info) +{ + switch (vd->auth) { + case VNC_AUTH_VNC: + info->auth = VNC_PRIMARY_AUTH_VNC; + break; + case VNC_AUTH_RA2: + info->auth = VNC_PRIMARY_AUTH_RA2; + break; + case VNC_AUTH_RA2NE: + info->auth = VNC_PRIMARY_AUTH_RA2NE; + break; + case VNC_AUTH_TIGHT: + info->auth = VNC_PRIMARY_AUTH_TIGHT; + break; + case VNC_AUTH_ULTRA: + info->auth = VNC_PRIMARY_AUTH_ULTRA; + break; + case VNC_AUTH_TLS: + info->auth = VNC_PRIMARY_AUTH_TLS; + break; + case VNC_AUTH_VENCRYPT: + info->auth = VNC_PRIMARY_AUTH_VENCRYPT; +#ifdef CONFIG_VNC_TLS + info->has_vencrypt = true; + switch (vd->subauth) { + case VNC_AUTH_VENCRYPT_PLAIN: + info->vencrypt = VNC_VENCRYPT_SUB_AUTH_PLAIN; + break; + case VNC_AUTH_VENCRYPT_TLSNONE: + info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_NONE; + break; + case VNC_AUTH_VENCRYPT_TLSVNC: + info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_VNC; + break; + case VNC_AUTH_VENCRYPT_TLSPLAIN: + info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_PLAIN; + break; + case VNC_AUTH_VENCRYPT_X509NONE: + info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_NONE; + break; + case VNC_AUTH_VENCRYPT_X509VNC: + info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_VNC; + break; + case VNC_AUTH_VENCRYPT_X509PLAIN: + info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_PLAIN; + break; + case VNC_AUTH_VENCRYPT_TLSSASL: + info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_SASL; + break; + case VNC_AUTH_VENCRYPT_X509SASL: + info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_SASL; + break; + default: + info->has_vencrypt = false; + break; + } +#endif + break; + case VNC_AUTH_SASL: + info->auth = VNC_PRIMARY_AUTH_SASL; + break; + case VNC_AUTH_NONE: + default: + info->auth = VNC_PRIMARY_AUTH_NONE; + break; + } +} + +VncInfo2List *qmp_query_vnc_servers(Error **errp) +{ + VncInfo2List *item, *prev = NULL; + VncInfo2 *info; + VncDisplay *vd; + DeviceState *dev; + + QTAILQ_FOREACH(vd, &vnc_displays, next) { + info = g_new0(VncInfo2, 1); + info->id = g_strdup(vd->id); + info->clients = qmp_query_client_list(vd); + qmp_query_auth(vd, info); + if (vd->dcl.con) { + dev = DEVICE(object_property_get_link(OBJECT(vd->dcl.con), + "device", NULL)); + info->has_display = true; + info->display = g_strdup(dev->id); + } + if (vd->lsock != -1) { + info->server = qmp_query_server_entry(vd->lsock, false, + info->server); + } +#ifdef CONFIG_VNC_WS + if (vd->lwebsock != -1) { + info->server = qmp_query_server_entry(vd->lwebsock, true, + info->server); + } +#endif + + item = g_new0(VncInfo2List, 1); + item->value = info; + item->next = prev; + prev = item; + } + return prev; +} + /* TODO 1) Get the queue working for IO. 2) there is some weirdness when using the -S option (the screen is grey @@ -853,7 +1033,7 @@ static int vnc_cursor_define(VncState *vs) static void vnc_dpy_cursor_define(DisplayChangeListener *dcl, QEMUCursor *c) { - VncDisplay *vd = vnc_display; + VncDisplay *vd = container_of(dcl, VncDisplay, dcl); VncState *vs; cursor_put(vd->cursor); @@ -1647,7 +1827,8 @@ static void do_key_event(VncState *vs, int down, int keycode, int sym) vs->modifiers_state[keycode] = 0; break; case 0x02 ... 0x0a: /* '1' to '9' keys */ - if (down && vs->modifiers_state[0x1d] && vs->modifiers_state[0x38]) { + if (vs->vd->dcl.con == NULL && + down && vs->modifiers_state[0x1d] && vs->modifiers_state[0x38]) { /* Reset the modifiers sent to the current console */ reset_keys(vs); console_select(keycode - 0x02); @@ -2055,8 +2236,8 @@ static void set_pixel_format(VncState *vs, set_pixel_conversion(vs); - graphic_hw_invalidate(NULL); - graphic_hw_update(NULL); + graphic_hw_invalidate(vs->vd->dcl.con); + graphic_hw_update(vs->vd->dcl.con); } static void pixel_format_message (VncState *vs) { @@ -2317,6 +2498,11 @@ static int protocol_client_init(VncState *vs, uint8_t *data, size_t len) } vnc_set_share_mode(vs, mode); + if (vs->vd->num_shared > vs->vd->connections_limit) { + vnc_disconnect_start(vs); + return 0; + } + vs->client_width = pixman_image_get_width(vs->vd->server); vs->client_height = pixman_image_get_height(vs->vd->server); vnc_write_u16(vs, vs->client_width); @@ -2783,7 +2969,7 @@ static void vnc_refresh(DisplayChangeListener *dcl) return; } - graphic_hw_update(NULL); + graphic_hw_update(vd->dcl.con); if (vnc_trylock_display(vd)) { update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE); @@ -2818,6 +3004,7 @@ static void vnc_connect(VncDisplay *vd, int csock, int i; vs->csock = csock; + vs->vd = vd; if (skipauth) { vs->auth = VNC_AUTH_NONE; @@ -2862,14 +3049,21 @@ static void vnc_connect(VncDisplay *vd, int csock, vnc_qmp_event(vs, QAPI_EVENT_VNC_CONNECTED); vnc_set_share_mode(vs, VNC_SHARE_MODE_CONNECTING); - vs->vd = vd; - #ifdef CONFIG_VNC_WS if (!vs->websocket) #endif { vnc_init_state(vs); } + + if (vd->num_connecting > vd->connections_limit) { + QTAILQ_FOREACH(vs, &vd->clients, next) { + if (vs->share_mode == VNC_SHARE_MODE_CONNECTING) { + vnc_disconnect_start(vs); + return; + } + } + } } void vnc_init_state(VncState *vs) @@ -2888,9 +3082,9 @@ void vnc_init_state(VncState *vs) qemu_mutex_init(&vs->output_mutex); vs->bh = qemu_bh_new(vnc_jobs_bh, vs); - QTAILQ_INSERT_HEAD(&vd->clients, vs, next); + QTAILQ_INSERT_TAIL(&vd->clients, vs, next); - graphic_hw_update(NULL); + graphic_hw_update(vd->dcl.con); vnc_write(vs, "RFB 003.008\n", 12); vnc_flush(vs); @@ -2913,7 +3107,7 @@ static void vnc_listen_read(void *opaque, bool websocket) int csock; /* Catch-up */ - graphic_hw_update(NULL); + graphic_hw_update(vs->dcl.con); #ifdef CONFIG_VNC_WS if (websocket) { csock = qemu_accept(vs->lwebsock, (struct sockaddr *)&addr, &addrlen); @@ -2942,20 +3136,27 @@ static void vnc_listen_websocket_read(void *opaque) #endif /* CONFIG_VNC_WS */ static const DisplayChangeListenerOps dcl_ops = { - .dpy_name = "vnc", - .dpy_refresh = vnc_refresh, - .dpy_gfx_copy = vnc_dpy_copy, - .dpy_gfx_update = vnc_dpy_update, - .dpy_gfx_switch = vnc_dpy_switch, - .dpy_mouse_set = vnc_mouse_set, - .dpy_cursor_define = vnc_dpy_cursor_define, + .dpy_name = "vnc", + .dpy_refresh = vnc_refresh, + .dpy_gfx_copy = vnc_dpy_copy, + .dpy_gfx_update = vnc_dpy_update, + .dpy_gfx_switch = vnc_dpy_switch, + .dpy_gfx_check_format = qemu_pixman_check_format, + .dpy_mouse_set = vnc_mouse_set, + .dpy_cursor_define = vnc_dpy_cursor_define, }; -void vnc_display_init(DisplayState *ds) +void vnc_display_init(const char *id) { - VncDisplay *vs = g_malloc0(sizeof(*vs)); + VncDisplay *vs; + + if (vnc_display_find(id) != NULL) { + return; + } + vs = g_malloc0(sizeof(*vs)); - vnc_display = vs; + vs->id = strdup(id); + QTAILQ_INSERT_TAIL(&vnc_displays, vs, next); vs->lsock = -1; #ifdef CONFIG_VNC_WS @@ -2983,10 +3184,8 @@ void vnc_display_init(DisplayState *ds) } -static void vnc_display_close(DisplayState *ds) +static void vnc_display_close(VncDisplay *vs) { - VncDisplay *vs = vnc_display; - if (!vs) return; g_free(vs->display); @@ -3012,9 +3211,9 @@ static void vnc_display_close(DisplayState *ds) #endif } -int vnc_display_password(DisplayState *ds, const char *password) +int vnc_display_password(const char *id, const char *password) { - VncDisplay *vs = vnc_display; + VncDisplay *vs = vnc_display_find(id); if (!vs) { return -EINVAL; @@ -3031,9 +3230,9 @@ int vnc_display_password(DisplayState *ds, const char *password) return 0; } -int vnc_display_pw_expire(DisplayState *ds, time_t expires) +int vnc_display_pw_expire(const char *id, time_t expires) { - VncDisplay *vs = vnc_display; + VncDisplay *vs = vnc_display_find(id); if (!vs) { return -EINVAL; @@ -3043,21 +3242,85 @@ int vnc_display_pw_expire(DisplayState *ds, time_t expires) return 0; } -char *vnc_display_local_addr(DisplayState *ds) +char *vnc_display_local_addr(const char *id) { - VncDisplay *vs = vnc_display; - + VncDisplay *vs = vnc_display_find(id); + return vnc_socket_local_addr("%s:%s", vs->lsock); } -void vnc_display_open(DisplayState *ds, const char *display, Error **errp) +static QemuOptsList qemu_vnc_opts = { + .name = "vnc", + .head = QTAILQ_HEAD_INITIALIZER(qemu_vnc_opts.head), + .implied_opt_name = "vnc", + .desc = { + { + .name = "vnc", + .type = QEMU_OPT_STRING, + },{ + .name = "websocket", + .type = QEMU_OPT_STRING, + },{ + .name = "x509", + .type = QEMU_OPT_STRING, + },{ + .name = "share", + .type = QEMU_OPT_STRING, + },{ + .name = "display", + .type = QEMU_OPT_STRING, + },{ + .name = "head", + .type = QEMU_OPT_NUMBER, + },{ + .name = "connections", + .type = QEMU_OPT_NUMBER, + },{ + .name = "password", + .type = QEMU_OPT_BOOL, + },{ + .name = "reverse", + .type = QEMU_OPT_BOOL, + },{ + .name = "lock-key-sync", + .type = QEMU_OPT_BOOL, + },{ + .name = "sasl", + .type = QEMU_OPT_BOOL, + },{ + .name = "tls", + .type = QEMU_OPT_BOOL, + },{ + .name = "x509verify", + .type = QEMU_OPT_BOOL, + },{ + .name = "acl", + .type = QEMU_OPT_BOOL, + },{ + .name = "lossy", + .type = QEMU_OPT_BOOL, + },{ + .name = "non-adaptive", + .type = QEMU_OPT_BOOL, + }, + { /* end of list */ } + }, +}; + +void vnc_display_open(const char *id, Error **errp) { - VncDisplay *vs = vnc_display; - const char *options; + VncDisplay *vs = vnc_display_find(id); + QemuOpts *opts = qemu_opts_find(&qemu_vnc_opts, id); + const char *display, *share, *device_id; + QemuConsole *con; int password = 0; int reverse = 0; +#ifdef CONFIG_VNC_WS + const char *websocket; +#endif #ifdef CONFIG_VNC_TLS int tls = 0, x509 = 0; + const char *path; #endif #ifdef CONFIG_VNC_SASL int sasl = 0; @@ -3068,120 +3331,92 @@ void vnc_display_open(DisplayState *ds, const char *display, Error **errp) #endif int lock_key_sync = 1; - if (!vnc_display) { + if (!vs) { error_setg(errp, "VNC display not active"); return; } - vnc_display_close(ds); - if (strcmp(display, "none") == 0) - return; + vnc_display_close(vs); + if (!opts) { + return; + } + display = qemu_opt_get(opts, "vnc"); + if (!display || strcmp(display, "none") == 0) { + return; + } vs->display = g_strdup(display); - vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE; - - options = display; - while ((options = strchr(options, ','))) { - options++; - if (strncmp(options, "password", 8) == 0) { - if (fips_get_state()) { - error_setg(errp, - "VNC password auth disabled due to FIPS mode, " - "consider using the VeNCrypt or SASL authentication " - "methods as an alternative"); - goto fail; - } - password = 1; /* Require password auth */ - } else if (strncmp(options, "reverse", 7) == 0) { - reverse = 1; - } else if (strncmp(options, "no-lock-key-sync", 16) == 0) { - lock_key_sync = 0; + + password = qemu_opt_get_bool(opts, "password", false); + if (password && fips_get_state()) { + error_setg(errp, + "VNC password auth disabled due to FIPS mode, " + "consider using the VeNCrypt or SASL authentication " + "methods as an alternative"); + goto fail; + } + + reverse = qemu_opt_get_bool(opts, "reverse", false); + lock_key_sync = qemu_opt_get_bool(opts, "lock-key-sync", true); #ifdef CONFIG_VNC_SASL - } else if (strncmp(options, "sasl", 4) == 0) { - sasl = 1; /* Require SASL auth */ + sasl = qemu_opt_get_bool(opts, "sasl", false); #endif -#ifdef CONFIG_VNC_WS - } else if (strncmp(options, "websocket", 9) == 0) { - char *start, *end; - vs->websocket = 1; - - /* Check for 'websocket=<port>' */ - start = strchr(options, '='); - end = strchr(options, ','); - if (start && (!end || (start < end))) { - int len = end ? end-(start+1) : strlen(start+1); - if (len < 6) { - /* extract the host specification from display */ - char *host = NULL, *port = NULL, *host_end = NULL; - port = g_strndup(start + 1, len); - - /* ipv6 hosts have colons */ - end = strchr(display, ','); - host_end = g_strrstr_len(display, end - display, ":"); - - if (host_end) { - host = g_strndup(display, host_end - display + 1); - } else { - host = g_strndup(":", 1); - } - vs->ws_display = g_strconcat(host, port, NULL); - g_free(host); - g_free(port); - } - } -#endif /* CONFIG_VNC_WS */ #ifdef CONFIG_VNC_TLS - } else if (strncmp(options, "tls", 3) == 0) { - tls = 1; /* Require TLS */ - } else if (strncmp(options, "x509", 4) == 0) { - char *start, *end; - x509 = 1; /* Require x509 certificates */ - if (strncmp(options, "x509verify", 10) == 0) - vs->tls.x509verify = 1; /* ...and verify client certs */ - - /* Now check for 'x509=/some/path' postfix - * and use that to setup x509 certificate/key paths */ - start = strchr(options, '='); - end = strchr(options, ','); - if (start && (!end || (start < end))) { - int len = end ? end-(start+1) : strlen(start+1); - char *path = g_strndup(start + 1, len); - - VNC_DEBUG("Trying certificate path '%s'\n", path); - if (vnc_tls_set_x509_creds_dir(vs, path) < 0) { - error_setg(errp, "Failed to find x509 certificates/keys in %s", path); - g_free(path); - goto fail; - } - g_free(path); - } else { - error_setg(errp, "No certificate path provided"); - goto fail; - } + tls = qemu_opt_get_bool(opts, "tls", false); + path = qemu_opt_get(opts, "x509"); + if (path) { + x509 = 1; + vs->tls.x509verify = qemu_opt_get_bool(opts, "x509verify", false); + if (vnc_tls_set_x509_creds_dir(vs, path) < 0) { + error_setg(errp, "Failed to find x509 certificates/keys in %s", + path); + goto fail; + } + } #endif #if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL) - } else if (strncmp(options, "acl", 3) == 0) { - acl = 1; + acl = qemu_opt_get_bool(opts, "acl", false); #endif - } else if (strncmp(options, "lossy", 5) == 0) { -#ifdef CONFIG_VNC_JPEG - vs->lossy = true; -#endif - } else if (strncmp(options, "non-adaptive", 12) == 0) { - vs->non_adaptive = true; - } else if (strncmp(options, "share=", 6) == 0) { - if (strncmp(options+6, "ignore", 6) == 0) { - vs->share_policy = VNC_SHARE_POLICY_IGNORE; - } else if (strncmp(options+6, "allow-exclusive", 15) == 0) { - vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE; - } else if (strncmp(options+6, "force-shared", 12) == 0) { - vs->share_policy = VNC_SHARE_POLICY_FORCE_SHARED; - } else { - error_setg(errp, "unknown vnc share= option"); - goto fail; - } + + share = qemu_opt_get(opts, "share"); + if (share) { + if (strcmp(share, "ignore") == 0) { + vs->share_policy = VNC_SHARE_POLICY_IGNORE; + } else if (strcmp(share, "allow-exclusive") == 0) { + vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE; + } else if (strcmp(share, "force-shared") == 0) { + vs->share_policy = VNC_SHARE_POLICY_FORCE_SHARED; + } else { + error_setg(errp, "unknown vnc share= option"); + goto fail; + } + } else { + vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE; + } + vs->connections_limit = qemu_opt_get_number(opts, "connections", 32); + + #ifdef CONFIG_VNC_WS + websocket = qemu_opt_get(opts, "websocket"); + if (websocket) { + /* extract the host specification from display */ + char *host = NULL, *host_end = NULL; + vs->websocket = 1; + + /* ipv6 hosts have colons */ + host_end = strrchr(display, ':'); + if (host_end) { + host = g_strndup(display, host_end - display + 1); + } else { + host = g_strdup(":"); } + vs->ws_display = g_strconcat(host, websocket, NULL); + g_free(host); } +#endif /* CONFIG_VNC_WS */ +#ifdef CONFIG_VNC_JPEG + vs->lossy = qemu_opt_get_bool(opts, "lossy", false); +#endif + vs->non_adaptive = qemu_opt_get_bool(opts, "non-adaptive", false); /* adaptive updates are only used with tight encoding and * if lossy updates are enabled so we can disable all the * calculations otherwise */ @@ -3191,18 +3426,36 @@ void vnc_display_open(DisplayState *ds, const char *display, Error **errp) #ifdef CONFIG_VNC_TLS if (acl && x509 && vs->tls.x509verify) { - if (!(vs->tls.acl = qemu_acl_init("vnc.x509dname"))) { + char *aclname; + + if (strcmp(vs->id, "default") == 0) { + aclname = g_strdup("vnc.x509dname"); + } else { + aclname = g_strdup_printf("vnc.%s.x509dname", vs->id); + } + vs->tls.acl = qemu_acl_init(aclname); + if (!vs->tls.acl) { fprintf(stderr, "Failed to create x509 dname ACL\n"); exit(1); } + g_free(aclname); } #endif #ifdef CONFIG_VNC_SASL if (acl && sasl) { - if (!(vs->sasl.acl = qemu_acl_init("vnc.username"))) { + char *aclname; + + if (strcmp(vs->id, "default") == 0) { + aclname = g_strdup("vnc.username"); + } else { + aclname = g_strdup_printf("vnc.%s.username", vs->id); + } + vs->sasl.acl = qemu_acl_init(aclname); + if (!vs->sasl.acl) { fprintf(stderr, "Failed to create username ACL\n"); exit(1); } + g_free(aclname); } #endif @@ -3292,6 +3545,33 @@ void vnc_display_open(DisplayState *ds, const char *display, Error **errp) #endif vs->lock_key_sync = lock_key_sync; + device_id = qemu_opt_get(opts, "display"); + if (device_id) { + DeviceState *dev; + int head = qemu_opt_get_number(opts, "head", 0); + + dev = qdev_find_recursive(sysbus_get_default(), device_id); + if (dev == NULL) { + error_set(errp, QERR_DEVICE_NOT_FOUND, device_id); + goto fail; + } + + con = qemu_console_lookup_by_device(dev, head); + if (con == NULL) { + error_setg(errp, "Device %s is not bound to a QemuConsole", + device_id); + goto fail; + } + } else { + con = NULL; + } + + if (con != vs->dcl.con) { + unregister_displaychangelistener(&vs->dcl); + vs->dcl.con = con; + register_displaychangelistener(&vs->dcl); + } + if (reverse) { /* connect to viewer */ int csock; @@ -3365,9 +3645,52 @@ fail: #endif /* CONFIG_VNC_WS */ } -void vnc_display_add_client(DisplayState *ds, int csock, bool skipauth) +void vnc_display_add_client(const char *id, int csock, bool skipauth) { - VncDisplay *vs = vnc_display; + VncDisplay *vs = vnc_display_find(id); + if (!vs) { + return; + } vnc_connect(vs, csock, skipauth, false); } + +QemuOpts *vnc_parse_func(const char *str) +{ + return qemu_opts_parse(qemu_find_opts("vnc"), str, 1); +} + +int vnc_init_func(QemuOpts *opts, void *opaque) +{ + Error *local_err = NULL; + QemuOptsList *olist = qemu_find_opts("vnc"); + char *id = (char *)qemu_opts_id(opts); + + if (!id) { + /* auto-assign id if not present */ + int i = 2; + id = g_strdup("default"); + while (qemu_opts_find(olist, id)) { + g_free(id); + id = g_strdup_printf("vnc%d", i++); + } + qemu_opts_set_id(opts, id); + } + + vnc_display_init(id); + vnc_display_open(id, &local_err); + if (local_err != NULL) { + error_report("Failed to start VNC server on `%s': %s", + qemu_opt_get(opts, "display"), + error_get_pretty(local_err)); + error_free(local_err); + exit(1); + } + return 0; +} + +static void vnc_register_config(void) +{ + qemu_add_opts(&qemu_vnc_opts); +} +machine_init(vnc_register_config); diff --git a/ui/vnc.h b/ui/vnc.h index 334de9ddb1..5e2b1a561e 100644 --- a/ui/vnc.h +++ b/ui/vnc.h @@ -150,7 +150,10 @@ typedef enum VncSharePolicy { struct VncDisplay { QTAILQ_HEAD(, VncState) clients; + int num_connecting; + int num_shared; int num_exclusive; + int connections_limit; VncSharePolicy share_policy; int lsock; #ifdef CONFIG_VNC_WS @@ -171,6 +174,8 @@ struct VncDisplay struct VncSurface guest; /* guest visible surface (aka ds->surface) */ pixman_image_t *server; /* vnc server surface */ + const char *id; + QTAILQ_ENTRY(VncDisplay) next; char *display; char *password; time_t expires; |