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
|
First you must compile qemu with a user interface supporting
|
||||||
multihead/multiseat and input event routing. Right now this
|
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
|
./configure --enable-sdl --with-sdlabi=2.0
|
||||||
|
|
||||||
|
@ -16,16 +16,16 @@ or
|
||||||
./configure --enable-gtk
|
./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 \
|
qemu -enable-kvm -usb $memory $disk $whatever \
|
||||||
-display [ sdl | gtk ] \
|
-display [ sdl | gtk ] \
|
||||||
-vga std \
|
-vga std \
|
||||||
-device usb-tablet
|
-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
|
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 pci-bridge,addr=12.0,chassis_nr=2,id=head.2 \
|
||||||
-device secondary-vga,bus=head.2,addr=02.0,id=video.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
|
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.
|
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
|
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
|
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.
|
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);
|
void cocoa_display_init(DisplayState *ds, int full_screen);
|
||||||
|
|
||||||
/* vnc.c */
|
/* vnc.c */
|
||||||
void vnc_display_init(DisplayState *ds);
|
void vnc_display_init(const char *id);
|
||||||
void vnc_display_open(DisplayState *ds, const char *display, Error **errp);
|
void vnc_display_open(const char *id, Error **errp);
|
||||||
void vnc_display_add_client(DisplayState *ds, int csock, bool skipauth);
|
void vnc_display_add_client(const char *id, int csock, bool skipauth);
|
||||||
char *vnc_display_local_addr(DisplayState *ds);
|
char *vnc_display_local_addr(const char *id);
|
||||||
#ifdef CONFIG_VNC
|
#ifdef CONFIG_VNC
|
||||||
int vnc_display_password(DisplayState *ds, const char *password);
|
int vnc_display_password(const char *id, const char *password);
|
||||||
int vnc_display_pw_expire(DisplayState *ds, time_t expires);
|
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
|
#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;
|
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;
|
return -ENODEV;
|
||||||
};
|
};
|
||||||
|
|
|
@ -672,12 +672,15 @@
|
||||||
#
|
#
|
||||||
# @family: address family
|
# @family: address family
|
||||||
#
|
#
|
||||||
|
# @websocket: true in case the socket is a websocket (since 2.3).
|
||||||
|
#
|
||||||
# Since: 2.1
|
# Since: 2.1
|
||||||
##
|
##
|
||||||
{ 'type': 'VncBasicInfo',
|
{ 'type': 'VncBasicInfo',
|
||||||
'data': { 'host': 'str',
|
'data': { 'host': 'str',
|
||||||
'service': 'str',
|
'service': 'str',
|
||||||
'family': 'NetworkAddressFamily' } }
|
'family': 'NetworkAddressFamily',
|
||||||
|
'websocket': 'bool' } }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @VncServerInfo
|
# @VncServerInfo
|
||||||
|
@ -750,6 +753,63 @@
|
||||||
'*family': 'NetworkAddressFamily',
|
'*family': 'NetworkAddressFamily',
|
||||||
'*service': 'str', '*auth': 'str', '*clients': ['VncClientInfo']} }
|
'*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:
|
# @query-vnc:
|
||||||
#
|
#
|
||||||
|
@ -761,6 +821,17 @@
|
||||||
##
|
##
|
||||||
{ 'command': 'query-vnc', 'returns': 'VncInfo' }
|
{ '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
|
# @SpiceBasicInfo
|
||||||
#
|
#
|
||||||
|
|
|
@ -2867,6 +2867,11 @@ EQMP
|
||||||
.args_type = "",
|
.args_type = "",
|
||||||
.mhandler.cmd_new = qmp_marshal_input_query_vnc,
|
.mhandler.cmd_new = qmp_marshal_input_query_vnc,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.name = "query-vnc-servers",
|
||||||
|
.args_type = "",
|
||||||
|
.mhandler.cmd_new = qmp_marshal_input_query_vnc_servers,
|
||||||
|
},
|
||||||
|
|
||||||
SQMP
|
SQMP
|
||||||
query-spice
|
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)
|
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,
|
static void qmp_change_vnc(const char *target, bool has_arg, const char *arg,
|
||||||
|
|
598
ui/vnc.c
598
ui/vnc.c
|
@ -27,10 +27,12 @@
|
||||||
#include "vnc.h"
|
#include "vnc.h"
|
||||||
#include "vnc-jobs.h"
|
#include "vnc-jobs.h"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
|
#include "hw/qdev.h"
|
||||||
#include "sysemu/sysemu.h"
|
#include "sysemu/sysemu.h"
|
||||||
#include "qemu/sockets.h"
|
#include "qemu/sockets.h"
|
||||||
#include "qemu/timer.h"
|
#include "qemu/timer.h"
|
||||||
#include "qemu/acl.h"
|
#include "qemu/acl.h"
|
||||||
|
#include "qemu/config-file.h"
|
||||||
#include "qapi/qmp/types.h"
|
#include "qapi/qmp/types.h"
|
||||||
#include "qmp-commands.h"
|
#include "qmp-commands.h"
|
||||||
#include "qemu/osdep.h"
|
#include "qemu/osdep.h"
|
||||||
|
@ -46,7 +48,8 @@ static const struct timeval VNC_REFRESH_LOSSY = { 2, 0 };
|
||||||
#include "vnc_keysym.h"
|
#include "vnc_keysym.h"
|
||||||
#include "d3des.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 int vnc_cursor_define(VncState *vs);
|
||||||
static void vnc_release_modifiers(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]);
|
vs->csock, mn[vs->share_mode], mn[mode]);
|
||||||
#endif
|
#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--;
|
vs->vd->num_exclusive--;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
vs->share_mode = mode;
|
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++;
|
vs->vd->num_exclusive++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,10 +251,10 @@ static const char *vnc_auth_name(VncDisplay *vd) {
|
||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
static VncServerInfo *vnc_server_info_get(void)
|
static VncServerInfo *vnc_server_info_get(VncDisplay *vd)
|
||||||
{
|
{
|
||||||
VncServerInfo *info;
|
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) {
|
if (!bi) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -237,7 +262,7 @@ static VncServerInfo *vnc_server_info_get(void)
|
||||||
info = g_malloc(sizeof(*info));
|
info = g_malloc(sizeof(*info));
|
||||||
info->base = bi;
|
info->base = bi;
|
||||||
info->has_auth = true;
|
info->has_auth = true;
|
||||||
info->auth = g_strdup(vnc_auth_name(vnc_display));
|
info->auth = g_strdup(vnc_auth_name(vd));
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,7 +307,7 @@ static void vnc_qmp_event(VncState *vs, QAPIEvent event)
|
||||||
}
|
}
|
||||||
g_assert(vs->info->base);
|
g_assert(vs->info->base);
|
||||||
|
|
||||||
si = vnc_server_info_get();
|
si = vnc_server_info_get(vs->vd);
|
||||||
if (!si) {
|
if (!si) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -328,6 +353,9 @@ static VncClientInfo *qmp_query_vnc_client(const VncState *client)
|
||||||
info->base->host = g_strdup(host);
|
info->base->host = g_strdup(host);
|
||||||
info->base->service = g_strdup(serv);
|
info->base->service = g_strdup(serv);
|
||||||
info->base->family = inet_netfamily(sa.ss_family);
|
info->base->family = inet_netfamily(sa.ss_family);
|
||||||
|
#ifdef CONFIG_VNC_WS
|
||||||
|
info->base->websocket = client->websocket;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_VNC_TLS
|
#ifdef CONFIG_VNC_TLS
|
||||||
if (client->tls.session && client->tls.dname) {
|
if (client->tls.session && client->tls.dname) {
|
||||||
|
@ -345,43 +373,59 @@ static VncClientInfo *qmp_query_vnc_client(const VncState *client)
|
||||||
return info;
|
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 *qmp_query_vnc(Error **errp)
|
||||||
{
|
{
|
||||||
VncInfo *info = g_malloc0(sizeof(*info));
|
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;
|
info->enabled = false;
|
||||||
} else {
|
} else {
|
||||||
VncClientInfoList *cur_item = NULL;
|
|
||||||
struct sockaddr_storage sa;
|
struct sockaddr_storage sa;
|
||||||
socklen_t salen = sizeof(sa);
|
socklen_t salen = sizeof(sa);
|
||||||
char host[NI_MAXHOST];
|
char host[NI_MAXHOST];
|
||||||
char serv[NI_MAXSERV];
|
char serv[NI_MAXSERV];
|
||||||
VncState *client;
|
|
||||||
|
|
||||||
info->enabled = true;
|
info->enabled = true;
|
||||||
|
|
||||||
/* for compatibility with the original command */
|
/* for compatibility with the original command */
|
||||||
info->has_clients = true;
|
info->has_clients = true;
|
||||||
|
info->clients = qmp_query_client_list(vd);
|
||||||
|
|
||||||
QTAILQ_FOREACH(client, &vnc_display->clients, next) {
|
if (vd->lsock == -1) {
|
||||||
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) {
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getsockname(vnc_display->lsock, (struct sockaddr *)&sa,
|
if (getsockname(vd->lsock, (struct sockaddr *)&sa,
|
||||||
&salen) == -1) {
|
&salen) == -1) {
|
||||||
error_set(errp, QERR_UNDEFINED_ERROR);
|
error_set(errp, QERR_UNDEFINED_ERROR);
|
||||||
goto out_error;
|
goto out_error;
|
||||||
|
@ -405,7 +449,7 @@ VncInfo *qmp_query_vnc(Error **errp)
|
||||||
info->family = inet_netfamily(sa.ss_family);
|
info->family = inet_netfamily(sa.ss_family);
|
||||||
|
|
||||||
info->has_auth = true;
|
info->has_auth = true;
|
||||||
info->auth = g_strdup(vnc_auth_name(vnc_display));
|
info->auth = g_strdup(vnc_auth_name(vd));
|
||||||
}
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
|
@ -415,6 +459,142 @@ out_error:
|
||||||
return NULL;
|
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
|
/* TODO
|
||||||
1) Get the queue working for IO.
|
1) Get the queue working for IO.
|
||||||
2) there is some weirdness when using the -S option (the screen is grey
|
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,
|
static void vnc_dpy_cursor_define(DisplayChangeListener *dcl,
|
||||||
QEMUCursor *c)
|
QEMUCursor *c)
|
||||||
{
|
{
|
||||||
VncDisplay *vd = vnc_display;
|
VncDisplay *vd = container_of(dcl, VncDisplay, dcl);
|
||||||
VncState *vs;
|
VncState *vs;
|
||||||
|
|
||||||
cursor_put(vd->cursor);
|
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;
|
vs->modifiers_state[keycode] = 0;
|
||||||
break;
|
break;
|
||||||
case 0x02 ... 0x0a: /* '1' to '9' keys */
|
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 the modifiers sent to the current console */
|
||||||
reset_keys(vs);
|
reset_keys(vs);
|
||||||
console_select(keycode - 0x02);
|
console_select(keycode - 0x02);
|
||||||
|
@ -2055,8 +2236,8 @@ static void set_pixel_format(VncState *vs,
|
||||||
|
|
||||||
set_pixel_conversion(vs);
|
set_pixel_conversion(vs);
|
||||||
|
|
||||||
graphic_hw_invalidate(NULL);
|
graphic_hw_invalidate(vs->vd->dcl.con);
|
||||||
graphic_hw_update(NULL);
|
graphic_hw_update(vs->vd->dcl.con);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pixel_format_message (VncState *vs) {
|
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);
|
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_width = pixman_image_get_width(vs->vd->server);
|
||||||
vs->client_height = pixman_image_get_height(vs->vd->server);
|
vs->client_height = pixman_image_get_height(vs->vd->server);
|
||||||
vnc_write_u16(vs, vs->client_width);
|
vnc_write_u16(vs, vs->client_width);
|
||||||
|
@ -2783,7 +2969,7 @@ static void vnc_refresh(DisplayChangeListener *dcl)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
graphic_hw_update(NULL);
|
graphic_hw_update(vd->dcl.con);
|
||||||
|
|
||||||
if (vnc_trylock_display(vd)) {
|
if (vnc_trylock_display(vd)) {
|
||||||
update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE);
|
update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE);
|
||||||
|
@ -2818,6 +3004,7 @@ static void vnc_connect(VncDisplay *vd, int csock,
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
vs->csock = csock;
|
vs->csock = csock;
|
||||||
|
vs->vd = vd;
|
||||||
|
|
||||||
if (skipauth) {
|
if (skipauth) {
|
||||||
vs->auth = VNC_AUTH_NONE;
|
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_qmp_event(vs, QAPI_EVENT_VNC_CONNECTED);
|
||||||
vnc_set_share_mode(vs, VNC_SHARE_MODE_CONNECTING);
|
vnc_set_share_mode(vs, VNC_SHARE_MODE_CONNECTING);
|
||||||
|
|
||||||
vs->vd = vd;
|
|
||||||
|
|
||||||
#ifdef CONFIG_VNC_WS
|
#ifdef CONFIG_VNC_WS
|
||||||
if (!vs->websocket)
|
if (!vs->websocket)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
vnc_init_state(vs);
|
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)
|
void vnc_init_state(VncState *vs)
|
||||||
|
@ -2888,9 +3082,9 @@ void vnc_init_state(VncState *vs)
|
||||||
qemu_mutex_init(&vs->output_mutex);
|
qemu_mutex_init(&vs->output_mutex);
|
||||||
vs->bh = qemu_bh_new(vnc_jobs_bh, vs);
|
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_write(vs, "RFB 003.008\n", 12);
|
||||||
vnc_flush(vs);
|
vnc_flush(vs);
|
||||||
|
@ -2913,7 +3107,7 @@ static void vnc_listen_read(void *opaque, bool websocket)
|
||||||
int csock;
|
int csock;
|
||||||
|
|
||||||
/* Catch-up */
|
/* Catch-up */
|
||||||
graphic_hw_update(NULL);
|
graphic_hw_update(vs->dcl.con);
|
||||||
#ifdef CONFIG_VNC_WS
|
#ifdef CONFIG_VNC_WS
|
||||||
if (websocket) {
|
if (websocket) {
|
||||||
csock = qemu_accept(vs->lwebsock, (struct sockaddr *)&addr, &addrlen);
|
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,
|
.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;
|
vs->lsock = -1;
|
||||||
#ifdef CONFIG_VNC_WS
|
#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)
|
if (!vs)
|
||||||
return;
|
return;
|
||||||
g_free(vs->display);
|
g_free(vs->display);
|
||||||
|
@ -3013,9 +3211,9 @@ static void vnc_display_close(DisplayState *ds)
|
||||||
#endif
|
#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) {
|
if (!vs) {
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
@ -3032,9 +3230,9 @@ int vnc_display_password(DisplayState *ds, const char *password)
|
||||||
return 0;
|
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) {
|
if (!vs) {
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
@ -3044,21 +3242,85 @@ int vnc_display_pw_expire(DisplayState *ds, time_t expires)
|
||||||
return 0;
|
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);
|
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 = {
|
||||||
{
|
{
|
||||||
VncDisplay *vs = vnc_display;
|
.name = "vnc",
|
||||||
const char *options;
|
.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_find(id);
|
||||||
|
QemuOpts *opts = qemu_opts_find(&qemu_vnc_opts, id);
|
||||||
|
const char *display, *share, *device_id;
|
||||||
|
QemuConsole *con;
|
||||||
int password = 0;
|
int password = 0;
|
||||||
int reverse = 0;
|
int reverse = 0;
|
||||||
|
#ifdef CONFIG_VNC_WS
|
||||||
|
const char *websocket;
|
||||||
|
#endif
|
||||||
#ifdef CONFIG_VNC_TLS
|
#ifdef CONFIG_VNC_TLS
|
||||||
int tls = 0, x509 = 0;
|
int tls = 0, x509 = 0;
|
||||||
|
const char *path;
|
||||||
#endif
|
#endif
|
||||||
#ifdef CONFIG_VNC_SASL
|
#ifdef CONFIG_VNC_SASL
|
||||||
int sasl = 0;
|
int sasl = 0;
|
||||||
|
@ -3069,120 +3331,92 @@ void vnc_display_open(DisplayState *ds, const char *display, Error **errp)
|
||||||
#endif
|
#endif
|
||||||
int lock_key_sync = 1;
|
int lock_key_sync = 1;
|
||||||
|
|
||||||
if (!vnc_display) {
|
if (!vs) {
|
||||||
error_setg(errp, "VNC display not active");
|
error_setg(errp, "VNC display not active");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
vnc_display_close(ds);
|
vnc_display_close(vs);
|
||||||
if (strcmp(display, "none") == 0)
|
|
||||||
|
if (!opts) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
display = qemu_opt_get(opts, "vnc");
|
||||||
|
if (!display || strcmp(display, "none") == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
vs->display = g_strdup(display);
|
vs->display = g_strdup(display);
|
||||||
vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;
|
|
||||||
|
|
||||||
options = display;
|
password = qemu_opt_get_bool(opts, "password", false);
|
||||||
while ((options = strchr(options, ','))) {
|
if (password && fips_get_state()) {
|
||||||
options++;
|
|
||||||
if (strncmp(options, "password", 8) == 0) {
|
|
||||||
if (fips_get_state()) {
|
|
||||||
error_setg(errp,
|
error_setg(errp,
|
||||||
"VNC password auth disabled due to FIPS mode, "
|
"VNC password auth disabled due to FIPS mode, "
|
||||||
"consider using the VeNCrypt or SASL authentication "
|
"consider using the VeNCrypt or SASL authentication "
|
||||||
"methods as an alternative");
|
"methods as an alternative");
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
password = 1; /* Require password auth */
|
|
||||||
} else if (strncmp(options, "reverse", 7) == 0) {
|
reverse = qemu_opt_get_bool(opts, "reverse", false);
|
||||||
reverse = 1;
|
lock_key_sync = qemu_opt_get_bool(opts, "lock-key-sync", true);
|
||||||
} else if (strncmp(options, "no-lock-key-sync", 16) == 0) {
|
|
||||||
lock_key_sync = 0;
|
|
||||||
#ifdef CONFIG_VNC_SASL
|
#ifdef CONFIG_VNC_SASL
|
||||||
} else if (strncmp(options, "sasl", 4) == 0) {
|
sasl = qemu_opt_get_bool(opts, "sasl", false);
|
||||||
sasl = 1; /* Require SASL auth */
|
|
||||||
#endif
|
#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
|
#ifdef CONFIG_VNC_TLS
|
||||||
} else if (strncmp(options, "tls", 3) == 0) {
|
tls = qemu_opt_get_bool(opts, "tls", false);
|
||||||
tls = 1; /* Require TLS */
|
path = qemu_opt_get(opts, "x509");
|
||||||
} else if (strncmp(options, "x509", 4) == 0) {
|
if (path) {
|
||||||
char *start, *end;
|
x509 = 1;
|
||||||
x509 = 1; /* Require x509 certificates */
|
vs->tls.x509verify = qemu_opt_get_bool(opts, "x509verify", false);
|
||||||
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) {
|
if (vnc_tls_set_x509_creds_dir(vs, path) < 0) {
|
||||||
error_setg(errp, "Failed to find x509 certificates/keys in %s", path);
|
error_setg(errp, "Failed to find x509 certificates/keys in %s",
|
||||||
g_free(path);
|
path);
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
g_free(path);
|
|
||||||
} else {
|
|
||||||
error_setg(errp, "No certificate path provided");
|
|
||||||
goto fail;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
|
#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
|
||||||
} else if (strncmp(options, "acl", 3) == 0) {
|
acl = qemu_opt_get_bool(opts, "acl", false);
|
||||||
acl = 1;
|
|
||||||
#endif
|
#endif
|
||||||
} else if (strncmp(options, "lossy", 5) == 0) {
|
|
||||||
#ifdef CONFIG_VNC_JPEG
|
share = qemu_opt_get(opts, "share");
|
||||||
vs->lossy = true;
|
if (share) {
|
||||||
#endif
|
if (strcmp(share, "ignore") == 0) {
|
||||||
} 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;
|
vs->share_policy = VNC_SHARE_POLICY_IGNORE;
|
||||||
} else if (strncmp(options+6, "allow-exclusive", 15) == 0) {
|
} else if (strcmp(share, "allow-exclusive") == 0) {
|
||||||
vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;
|
vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;
|
||||||
} else if (strncmp(options+6, "force-shared", 12) == 0) {
|
} else if (strcmp(share, "force-shared") == 0) {
|
||||||
vs->share_policy = VNC_SHARE_POLICY_FORCE_SHARED;
|
vs->share_policy = VNC_SHARE_POLICY_FORCE_SHARED;
|
||||||
} else {
|
} else {
|
||||||
error_setg(errp, "unknown vnc share= option");
|
error_setg(errp, "unknown vnc share= option");
|
||||||
goto fail;
|
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
|
/* adaptive updates are only used with tight encoding and
|
||||||
* if lossy updates are enabled so we can disable all the
|
* if lossy updates are enabled so we can disable all the
|
||||||
* calculations otherwise */
|
* calculations otherwise */
|
||||||
|
@ -3192,18 +3426,36 @@ void vnc_display_open(DisplayState *ds, const char *display, Error **errp)
|
||||||
|
|
||||||
#ifdef CONFIG_VNC_TLS
|
#ifdef CONFIG_VNC_TLS
|
||||||
if (acl && x509 && vs->tls.x509verify) {
|
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");
|
fprintf(stderr, "Failed to create x509 dname ACL\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
g_free(aclname);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef CONFIG_VNC_SASL
|
#ifdef CONFIG_VNC_SASL
|
||||||
if (acl && 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");
|
fprintf(stderr, "Failed to create username ACL\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
g_free(aclname);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -3293,6 +3545,33 @@ void vnc_display_open(DisplayState *ds, const char *display, Error **errp)
|
||||||
#endif
|
#endif
|
||||||
vs->lock_key_sync = lock_key_sync;
|
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) {
|
if (reverse) {
|
||||||
/* connect to viewer */
|
/* connect to viewer */
|
||||||
int csock;
|
int csock;
|
||||||
|
@ -3366,9 +3645,52 @@ fail:
|
||||||
#endif /* CONFIG_VNC_WS */
|
#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);
|
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
|
struct VncDisplay
|
||||||
{
|
{
|
||||||
QTAILQ_HEAD(, VncState) clients;
|
QTAILQ_HEAD(, VncState) clients;
|
||||||
|
int num_connecting;
|
||||||
|
int num_shared;
|
||||||
int num_exclusive;
|
int num_exclusive;
|
||||||
|
int connections_limit;
|
||||||
VncSharePolicy share_policy;
|
VncSharePolicy share_policy;
|
||||||
int lsock;
|
int lsock;
|
||||||
#ifdef CONFIG_VNC_WS
|
#ifdef CONFIG_VNC_WS
|
||||||
|
@ -171,6 +174,8 @@ struct VncDisplay
|
||||||
struct VncSurface guest; /* guest visible surface (aka ds->surface) */
|
struct VncSurface guest; /* guest visible surface (aka ds->surface) */
|
||||||
pixman_image_t *server; /* vnc server surface */
|
pixman_image_t *server; /* vnc server surface */
|
||||||
|
|
||||||
|
const char *id;
|
||||||
|
QTAILQ_ENTRY(VncDisplay) next;
|
||||||
char *display;
|
char *display;
|
||||||
char *password;
|
char *password;
|
||||||
time_t expires;
|
time_t expires;
|
||||||
|
|
37
vl.c
37
vl.c
|
@ -158,9 +158,6 @@ int smp_cpus = 1;
|
||||||
int max_cpus = 0;
|
int max_cpus = 0;
|
||||||
int smp_cores = 1;
|
int smp_cores = 1;
|
||||||
int smp_threads = 1;
|
int smp_threads = 1;
|
||||||
#ifdef CONFIG_VNC
|
|
||||||
const char *vnc_display;
|
|
||||||
#endif
|
|
||||||
int acpi_enabled = 1;
|
int acpi_enabled = 1;
|
||||||
int no_hpet = 0;
|
int no_hpet = 0;
|
||||||
int fd_bootchk = 1;
|
int fd_bootchk = 1;
|
||||||
|
@ -2002,16 +1999,12 @@ static DisplayType select_display(const char *p)
|
||||||
#endif
|
#endif
|
||||||
} else if (strstart(p, "vnc", &opts)) {
|
} else if (strstart(p, "vnc", &opts)) {
|
||||||
#ifdef CONFIG_VNC
|
#ifdef CONFIG_VNC
|
||||||
|
if (*opts == '=') {
|
||||||
display_remote++;
|
display_remote++;
|
||||||
|
if (vnc_parse_func(opts+1) == NULL) {
|
||||||
if (*opts) {
|
exit(1);
|
||||||
const char *nextopt;
|
|
||||||
|
|
||||||
if (strstart(opts, "=", &nextopt)) {
|
|
||||||
vnc_display = nextopt;
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
if (!vnc_display) {
|
|
||||||
fprintf(stderr, "VNC requires a display argument vnc=<display>\n");
|
fprintf(stderr, "VNC requires a display argument vnc=<display>\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
@ -3479,7 +3472,9 @@ int main(int argc, char **argv, char **envp)
|
||||||
case QEMU_OPTION_vnc:
|
case QEMU_OPTION_vnc:
|
||||||
#ifdef CONFIG_VNC
|
#ifdef CONFIG_VNC
|
||||||
display_remote++;
|
display_remote++;
|
||||||
vnc_display = optarg;
|
if (vnc_parse_func(optarg) == NULL) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
fprintf(stderr, "VNC support is disabled\n");
|
fprintf(stderr, "VNC support is disabled\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
|
@ -3975,7 +3970,7 @@ int main(int argc, char **argv, char **envp)
|
||||||
#elif defined(CONFIG_SDL) || defined(CONFIG_COCOA)
|
#elif defined(CONFIG_SDL) || defined(CONFIG_COCOA)
|
||||||
display_type = DT_SDL;
|
display_type = DT_SDL;
|
||||||
#elif defined(CONFIG_VNC)
|
#elif defined(CONFIG_VNC)
|
||||||
vnc_display = "localhost:0,to=99";
|
vnc_parse_func("localhost:0,to=99,id=default");
|
||||||
show_vnc_port = 1;
|
show_vnc_port = 1;
|
||||||
#else
|
#else
|
||||||
display_type = DT_NONE;
|
display_type = DT_NONE;
|
||||||
|
@ -4286,20 +4281,10 @@ int main(int argc, char **argv, char **envp)
|
||||||
|
|
||||||
#ifdef CONFIG_VNC
|
#ifdef CONFIG_VNC
|
||||||
/* init remote displays */
|
/* init remote displays */
|
||||||
if (vnc_display) {
|
qemu_opts_foreach(qemu_find_opts("vnc"), vnc_init_func, NULL, 0);
|
||||||
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) {
|
if (show_vnc_port) {
|
||||||
printf("VNC server running on `%s'\n", vnc_display_local_addr(ds));
|
printf("VNC server running on `%s'\n",
|
||||||
}
|
vnc_display_local_addr("default"));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef CONFIG_SPICE
|
#ifdef CONFIG_SPICE
|
||||||
|
|
Loading…
Reference in a new issue