mirror of https://gitee.com/openkylin/qemu.git
vnc: add support for multiple vnc displays
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (GNU/Linux) iQIcBAABAgAGBQJUwNhmAAoJEEy22O7T6HE4qNEP/3wE79x34VFtKUZ3k1hsUxOZ 8Rbb8lbPhCeQjEF3pcxVF0QEjJidk/OKSyzLJbJXjq0L+6EmiNfII1bzBseUDvFe XsWLKuCnNwMr6fuZ34C4PPbRSYgBZhxQywsIV/RKdTxKxs5suPDoASOGI1ZAd1Fo y0fPBe/z3ecVSQcuKNiDQ7jdbJT7gQ65yn5DpdeM/LSHrW/Xo700WCZtQbc+ZcxW 78SMj0SxhbfLgHB91xYw3yZT6L6xXed5HcJyqfboeGYIHrpRaHrEwkghumQ12XA1 ZHRFkiaOZS8y565T0KMJxx1+sZerG9q/1xLCLohziR0u4BZ4H2txqtUviujQ00Dl 83pVGWNlRvHgVgVuH7sfP1N3TqdmwOLr4UttzCtWxSJjDeK++z/zEHHB/aB4UZSr WHUG9cPLP/+trckE86CXbJ4LzAq1viiIin3i98tUh/jwljrJXW1jhd9qnoouFoAF VBxQ71G8BIVnm+TJuikcUSaf3VeRy1C1CT1XEsBy2jN4CYn7cDhnju+JdECgi2G0 ZZrBfDWY3xKBcXA5t1aOJasSHO7PsPOPfZr/fIs6koPWiUL67GMRyZPNXYAn5xrz 918WMWQ2KI93DT+uMLBT0veOG4du4MhadSnq1lXZnalFfYABrvzWiVNE97UF5aKE g/NVldhYivZP7uIhmLK5 =+MFe -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/kraxel/tags/pull-vnc-20150122-1' into staging vnc: add support for multiple vnc displays # gpg: Signature made Thu 22 Jan 2015 11:00:54 GMT using RSA key ID D3E87138 # gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>" # gpg: aka "Gerd Hoffmann <gerd@kraxel.org>" # gpg: aka "Gerd Hoffmann (private) <kraxel@gmail.com>" * remotes/kraxel/tags/pull-vnc-20150122-1: monitor: add vnc websockets monitor: add query-vnc-servers command vnc: factor out qmp_query_client_list vnc: track & limit connections vnc: update docs/multiseat.txt vnc: allow binding servers to qemu consoles vnc: switch to QemuOpts, allow multiple servers vnc: add display id to acl names vnc: remove unused DisplayState parameter, add id instead. vnc: remove vnc_display global Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
b3a4755a67
|
@ -7,7 +7,7 @@ host side
|
|||
|
||||
First you must compile qemu with a user interface supporting
|
||||
multihead/multiseat and input event routing. Right now this
|
||||
list includes sdl2 and gtk (both 2+3):
|
||||
list includes sdl2, gtk (both 2+3) and vnc:
|
||||
|
||||
./configure --enable-sdl --with-sdlabi=2.0
|
||||
|
||||
|
@ -16,16 +16,16 @@ or
|
|||
./configure --enable-gtk
|
||||
|
||||
|
||||
Next put together the qemu command line:
|
||||
Next put together the qemu command line (sdk/gtk):
|
||||
|
||||
qemu -enable-kvm -usb $memory $disk $whatever \
|
||||
-display [ sdl | gtk ] \
|
||||
-vga std \
|
||||
-device usb-tablet
|
||||
|
||||
That is it for the first head, which will use the standard vga, the
|
||||
That is it for the first seat, which will use the standard vga, the
|
||||
standard ps/2 keyboard (implicitly there) and the usb-tablet. Now the
|
||||
additional switches for the second head:
|
||||
additional switches for the second seat:
|
||||
|
||||
-device pci-bridge,addr=12.0,chassis_nr=2,id=head.2 \
|
||||
-device secondary-vga,bus=head.2,addr=02.0,id=video.2 \
|
||||
|
@ -47,6 +47,16 @@ in a separate tab. You can either simply switch tabs to switch heads,
|
|||
or use the "View / Detach tab" menu item to move one of the displays
|
||||
to its own window so you can see both display devices side-by-side.
|
||||
|
||||
For vnc some additional configuration on the command line is needed.
|
||||
We'll create two vnc server instances, and bind the second one to the
|
||||
second seat, simliar to input devices:
|
||||
|
||||
-display vnc=:1,id=primary \
|
||||
-display vnc=:2,id=secondary,display=video.2
|
||||
|
||||
Connecting to vnc display :1 gives you access to the first seat, and
|
||||
likewise connecting to vnc display :2 shows the second seat.
|
||||
|
||||
Note on spice: Spice handles multihead just fine. But it can't do
|
||||
multiseat. For tablet events the event source is sent to the spice
|
||||
agent. But qemu can't figure it, so it can't do input routing.
|
||||
|
|
|
@ -331,19 +331,21 @@ void sdl_display_init(DisplayState *ds, int full_screen, int no_frame);
|
|||
void cocoa_display_init(DisplayState *ds, int full_screen);
|
||||
|
||||
/* vnc.c */
|
||||
void vnc_display_init(DisplayState *ds);
|
||||
void vnc_display_open(DisplayState *ds, const char *display, Error **errp);
|
||||
void vnc_display_add_client(DisplayState *ds, int csock, bool skipauth);
|
||||
char *vnc_display_local_addr(DisplayState *ds);
|
||||
void vnc_display_init(const char *id);
|
||||
void vnc_display_open(const char *id, Error **errp);
|
||||
void vnc_display_add_client(const char *id, int csock, bool skipauth);
|
||||
char *vnc_display_local_addr(const char *id);
|
||||
#ifdef CONFIG_VNC
|
||||
int vnc_display_password(DisplayState *ds, const char *password);
|
||||
int vnc_display_pw_expire(DisplayState *ds, time_t expires);
|
||||
int vnc_display_password(const char *id, const char *password);
|
||||
int vnc_display_pw_expire(const char *id, time_t expires);
|
||||
QemuOpts *vnc_parse_func(const char *str);
|
||||
int vnc_init_func(QemuOpts *opts, void *opaque);
|
||||
#else
|
||||
static inline int vnc_display_password(DisplayState *ds, const char *password)
|
||||
static inline int vnc_display_password(const char *id, const char *password)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
static inline int vnc_display_pw_expire(DisplayState *ds, time_t expires)
|
||||
static inline int vnc_display_pw_expire(const char *id, time_t expires)
|
||||
{
|
||||
return -ENODEV;
|
||||
};
|
||||
|
|
|
@ -672,12 +672,15 @@
|
|||
#
|
||||
# @family: address family
|
||||
#
|
||||
# @websocket: true in case the socket is a websocket (since 2.3).
|
||||
#
|
||||
# Since: 2.1
|
||||
##
|
||||
{ 'type': 'VncBasicInfo',
|
||||
'data': { 'host': 'str',
|
||||
'service': 'str',
|
||||
'family': 'NetworkAddressFamily' } }
|
||||
'family': 'NetworkAddressFamily',
|
||||
'websocket': 'bool' } }
|
||||
|
||||
##
|
||||
# @VncServerInfo
|
||||
|
@ -750,6 +753,63 @@
|
|||
'*family': 'NetworkAddressFamily',
|
||||
'*service': 'str', '*auth': 'str', '*clients': ['VncClientInfo']} }
|
||||
|
||||
##
|
||||
# @VncPriAuth:
|
||||
#
|
||||
# vnc primary authentication method.
|
||||
#
|
||||
# Since: 2.3
|
||||
##
|
||||
{ 'enum': 'VncPrimaryAuth',
|
||||
'data': [ 'none', 'vnc', 'ra2', 'ra2ne', 'tight', 'ultra',
|
||||
'tls', 'vencrypt', 'sasl' ] }
|
||||
|
||||
##
|
||||
# @VncVencryptSubAuth:
|
||||
#
|
||||
# vnc sub authentication method with vencrypt.
|
||||
#
|
||||
# Since: 2.3
|
||||
##
|
||||
{ 'enum': 'VncVencryptSubAuth',
|
||||
'data': [ 'plain',
|
||||
'tls-none', 'x509-none',
|
||||
'tls-vnc', 'x509-vnc',
|
||||
'tls-plain', 'x509-plain',
|
||||
'tls-sasl', 'x509-sasl' ] }
|
||||
|
||||
##
|
||||
# @VncInfo2:
|
||||
#
|
||||
# Information about a vnc server
|
||||
#
|
||||
# @id: vnc server name.
|
||||
#
|
||||
# @server: A list of @VncBasincInfo describing all listening sockets.
|
||||
# The list can be empty (in case the vnc server is disabled).
|
||||
# It also may have multiple entries: normal + websocket,
|
||||
# possibly also ipv4 + ipv6 in the future.
|
||||
#
|
||||
# @clients: A list of @VncClientInfo of all currently connected clients.
|
||||
# The list can be empty, for obvious reasons.
|
||||
#
|
||||
# @auth: The current authentication type used by the server
|
||||
#
|
||||
# @vencrypt: #optional The vencrypt sub authentication type used by the server,
|
||||
# only specified in case auth == vencrypt.
|
||||
#
|
||||
# @display: #optional The display device the vnc server is linked to.
|
||||
#
|
||||
# Since: 2.3
|
||||
##
|
||||
{ 'type': 'VncInfo2',
|
||||
'data': { 'id' : 'str',
|
||||
'server' : ['VncBasicInfo'],
|
||||
'clients' : ['VncClientInfo'],
|
||||
'auth' : 'VncPrimaryAuth',
|
||||
'*vencrypt' : 'VncVencryptSubAuth',
|
||||
'*display' : 'str' } }
|
||||
|
||||
##
|
||||
# @query-vnc:
|
||||
#
|
||||
|
@ -761,6 +821,17 @@
|
|||
##
|
||||
{ 'command': 'query-vnc', 'returns': 'VncInfo' }
|
||||
|
||||
##
|
||||
# @query-vnc-servers:
|
||||
#
|
||||
# Returns a list of vnc servers. The list can be empty.
|
||||
#
|
||||
# Returns: a list of @VncInfo2
|
||||
#
|
||||
# Since: 2.3
|
||||
##
|
||||
{ 'command': 'query-vnc-servers', 'returns': ['VncInfo2'] }
|
||||
|
||||
##
|
||||
# @SpiceBasicInfo
|
||||
#
|
||||
|
|
|
@ -2867,6 +2867,11 @@ EQMP
|
|||
.args_type = "",
|
||||
.mhandler.cmd_new = qmp_marshal_input_query_vnc,
|
||||
},
|
||||
{
|
||||
.name = "query-vnc-servers",
|
||||
.args_type = "",
|
||||
.mhandler.cmd_new = qmp_marshal_input_query_vnc_servers,
|
||||
},
|
||||
|
||||
SQMP
|
||||
query-spice
|
||||
|
|
15
qmp.c
15
qmp.c
|
@ -368,7 +368,20 @@ void qmp_change_vnc_password(const char *password, Error **errp)
|
|||
|
||||
static void qmp_change_vnc_listen(const char *target, Error **errp)
|
||||
{
|
||||
vnc_display_open(NULL, target, errp);
|
||||
QemuOptsList *olist = qemu_find_opts("vnc");
|
||||
QemuOpts *opts;
|
||||
|
||||
if (strstr(target, "id=")) {
|
||||
error_setg(errp, "id not supported");
|
||||
return;
|
||||
}
|
||||
|
||||
opts = qemu_opts_find(olist, "default");
|
||||
if (opts) {
|
||||
qemu_opts_del(opts);
|
||||
}
|
||||
opts = vnc_parse_func(target);
|
||||
vnc_display_open("default", errp);
|
||||
}
|
||||
|
||||
static void qmp_change_vnc(const char *target, bool has_arg, const char *arg,
|
||||
|
|
646
ui/vnc.c
646
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);
|
||||
|
@ -2952,11 +3146,17 @@ static const DisplayChangeListenerOps dcl_ops = {
|
|||
.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;
|
||||
|
||||
vnc_display = vs;
|
||||
if (vnc_display_find(id) != NULL) {
|
||||
return;
|
||||
}
|
||||
vs = g_malloc0(sizeof(*vs));
|
||||
|
||||
vs->id = strdup(id);
|
||||
QTAILQ_INSERT_TAIL(&vnc_displays, vs, next);
|
||||
|
||||
vs->lsock = -1;
|
||||
#ifdef CONFIG_VNC_WS
|
||||
|
@ -2984,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);
|
||||
|
@ -3013,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;
|
||||
|
@ -3032,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;
|
||||
|
@ -3044,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;
|
||||
|
@ -3069,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)
|
||||
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;
|
||||
#ifdef CONFIG_VNC_SASL
|
||||
} else if (strncmp(options, "sasl", 4) == 0) {
|
||||
sasl = 1; /* Require SASL auth */
|
||||
#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;
|
||||
}
|
||||
#endif
|
||||
#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
|
||||
} else if (strncmp(options, "acl", 3) == 0) {
|
||||
acl = 1;
|
||||
#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;
|
||||
}
|
||||
}
|
||||
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
|
||||
sasl = qemu_opt_get_bool(opts, "sasl", false);
|
||||
#endif
|
||||
#ifdef CONFIG_VNC_TLS
|
||||
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)
|
||||
acl = qemu_opt_get_bool(opts, "acl", false);
|
||||
#endif
|
||||
|
||||
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 */
|
||||
|
@ -3192,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
|
||||
|
||||
|
@ -3293,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;
|
||||
|
@ -3366,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);
|
||||
|
|
5
ui/vnc.h
5
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;
|
||||
|
|
41
vl.c
41
vl.c
|
@ -158,9 +158,6 @@ int smp_cpus = 1;
|
|||
int max_cpus = 0;
|
||||
int smp_cores = 1;
|
||||
int smp_threads = 1;
|
||||
#ifdef CONFIG_VNC
|
||||
const char *vnc_display;
|
||||
#endif
|
||||
int acpi_enabled = 1;
|
||||
int no_hpet = 0;
|
||||
int fd_bootchk = 1;
|
||||
|
@ -2002,16 +1999,12 @@ static DisplayType select_display(const char *p)
|
|||
#endif
|
||||
} else if (strstart(p, "vnc", &opts)) {
|
||||
#ifdef CONFIG_VNC
|
||||
display_remote++;
|
||||
|
||||
if (*opts) {
|
||||
const char *nextopt;
|
||||
|
||||
if (strstart(opts, "=", &nextopt)) {
|
||||
vnc_display = nextopt;
|
||||
if (*opts == '=') {
|
||||
display_remote++;
|
||||
if (vnc_parse_func(opts+1) == NULL) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if (!vnc_display) {
|
||||
} else {
|
||||
fprintf(stderr, "VNC requires a display argument vnc=<display>\n");
|
||||
exit(1);
|
||||
}
|
||||
|
@ -3479,7 +3472,9 @@ int main(int argc, char **argv, char **envp)
|
|||
case QEMU_OPTION_vnc:
|
||||
#ifdef CONFIG_VNC
|
||||
display_remote++;
|
||||
vnc_display = optarg;
|
||||
if (vnc_parse_func(optarg) == NULL) {
|
||||
exit(1);
|
||||
}
|
||||
#else
|
||||
fprintf(stderr, "VNC support is disabled\n");
|
||||
exit(1);
|
||||
|
@ -3975,7 +3970,7 @@ int main(int argc, char **argv, char **envp)
|
|||
#elif defined(CONFIG_SDL) || defined(CONFIG_COCOA)
|
||||
display_type = DT_SDL;
|
||||
#elif defined(CONFIG_VNC)
|
||||
vnc_display = "localhost:0,to=99";
|
||||
vnc_parse_func("localhost:0,to=99,id=default");
|
||||
show_vnc_port = 1;
|
||||
#else
|
||||
display_type = DT_NONE;
|
||||
|
@ -4286,20 +4281,10 @@ int main(int argc, char **argv, char **envp)
|
|||
|
||||
#ifdef CONFIG_VNC
|
||||
/* init remote displays */
|
||||
if (vnc_display) {
|
||||
Error *local_err = NULL;
|
||||
vnc_display_init(ds);
|
||||
vnc_display_open(ds, vnc_display, &local_err);
|
||||
if (local_err != NULL) {
|
||||
error_report("Failed to start VNC server on `%s': %s",
|
||||
vnc_display, error_get_pretty(local_err));
|
||||
error_free(local_err);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (show_vnc_port) {
|
||||
printf("VNC server running on `%s'\n", vnc_display_local_addr(ds));
|
||||
}
|
||||
qemu_opts_foreach(qemu_find_opts("vnc"), vnc_init_func, NULL, 0);
|
||||
if (show_vnc_port) {
|
||||
printf("VNC server running on `%s'\n",
|
||||
vnc_display_local_addr("default"));
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_SPICE
|
||||
|
|
Loading…
Reference in New Issue