Merge remote-tracking branch 'kraxel/vnc.2' into staging
* kraxel/vnc.2: vnc: lift modifier keys on client disconnect. vnc: implement shared flag handling. vnc: fix ctrl key in vnc terminal emulation Fix vnc memory corruption with width = 1400
This commit is contained in:
commit
cf5cfe0471
@ -1095,6 +1095,19 @@ This can be really helpful to save bandwidth when playing videos. Disabling
|
|||||||
adaptive encodings allows to restore the original static behavior of encodings
|
adaptive encodings allows to restore the original static behavior of encodings
|
||||||
like Tight.
|
like Tight.
|
||||||
|
|
||||||
|
@item share=[allow-exclusive|force-shared|ignore]
|
||||||
|
|
||||||
|
Set display sharing policy. 'allow-exclusive' allows clients to ask
|
||||||
|
for exclusive access. As suggested by the rfb spec this is
|
||||||
|
implemented by dropping other connections. Connecting multiple
|
||||||
|
clients in parallel requires all clients asking for a shared session
|
||||||
|
(vncviewer: -shared switch). This is the default. 'force-shared'
|
||||||
|
disables exclusive client access. Useful for shared desktop sessions,
|
||||||
|
where you don't want someone forgetting specify -shared disconnect
|
||||||
|
everybody else. 'ignore' completely ignores the shared flag and
|
||||||
|
allows everybody connect unconditionally. Doesn't conform to the rfb
|
||||||
|
spec but is traditional qemu behavior.
|
||||||
|
|
||||||
@end table
|
@end table
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
||||||
|
135
ui/vnc.c
135
ui/vnc.c
@ -46,6 +46,30 @@ static VncDisplay *vnc_display; /* needed for info vnc */
|
|||||||
static DisplayChangeListener *dcl;
|
static DisplayChangeListener *dcl;
|
||||||
|
|
||||||
static int vnc_cursor_define(VncState *vs);
|
static int vnc_cursor_define(VncState *vs);
|
||||||
|
static void vnc_release_modifiers(VncState *vs);
|
||||||
|
|
||||||
|
static void vnc_set_share_mode(VncState *vs, VncShareMode mode)
|
||||||
|
{
|
||||||
|
#ifdef _VNC_DEBUG
|
||||||
|
static const char *mn[] = {
|
||||||
|
[0] = "undefined",
|
||||||
|
[VNC_SHARE_MODE_CONNECTING] = "connecting",
|
||||||
|
[VNC_SHARE_MODE_SHARED] = "shared",
|
||||||
|
[VNC_SHARE_MODE_EXCLUSIVE] = "exclusive",
|
||||||
|
[VNC_SHARE_MODE_DISCONNECTED] = "disconnected",
|
||||||
|
};
|
||||||
|
fprintf(stderr, "%s/%d: %s -> %s\n", __func__,
|
||||||
|
vs->csock, mn[vs->share_mode], mn[mode]);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (vs->share_mode == VNC_SHARE_MODE_EXCLUSIVE) {
|
||||||
|
vs->vd->num_exclusive--;
|
||||||
|
}
|
||||||
|
vs->share_mode = mode;
|
||||||
|
if (vs->share_mode == VNC_SHARE_MODE_EXCLUSIVE) {
|
||||||
|
vs->vd->num_exclusive++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static char *addr_to_string(const char *format,
|
static char *addr_to_string(const char *format,
|
||||||
struct sockaddr_storage *sa,
|
struct sockaddr_storage *sa,
|
||||||
@ -997,6 +1021,7 @@ static void vnc_disconnect_start(VncState *vs)
|
|||||||
{
|
{
|
||||||
if (vs->csock == -1)
|
if (vs->csock == -1)
|
||||||
return;
|
return;
|
||||||
|
vnc_set_share_mode(vs, VNC_SHARE_MODE_DISCONNECTED);
|
||||||
qemu_set_fd_handler2(vs->csock, NULL, NULL, NULL, NULL);
|
qemu_set_fd_handler2(vs->csock, NULL, NULL, NULL, NULL);
|
||||||
closesocket(vs->csock);
|
closesocket(vs->csock);
|
||||||
vs->csock = -1;
|
vs->csock = -1;
|
||||||
@ -1027,6 +1052,7 @@ static void vnc_disconnect_finish(VncState *vs)
|
|||||||
vnc_sasl_client_cleanup(vs);
|
vnc_sasl_client_cleanup(vs);
|
||||||
#endif /* CONFIG_VNC_SASL */
|
#endif /* CONFIG_VNC_SASL */
|
||||||
audio_del(vs);
|
audio_del(vs);
|
||||||
|
vnc_release_modifiers(vs);
|
||||||
|
|
||||||
QTAILQ_REMOVE(&vs->vd->clients, vs, next);
|
QTAILQ_REMOVE(&vs->vd->clients, vs, next);
|
||||||
|
|
||||||
@ -1552,9 +1578,11 @@ static void do_key_event(VncState *vs, int down, int keycode, int sym)
|
|||||||
else
|
else
|
||||||
kbd_put_keycode(keycode | SCANCODE_UP);
|
kbd_put_keycode(keycode | SCANCODE_UP);
|
||||||
} else {
|
} else {
|
||||||
|
bool numlock = vs->modifiers_state[0x45];
|
||||||
|
bool control = (vs->modifiers_state[0x1d] ||
|
||||||
|
vs->modifiers_state[0x9d]);
|
||||||
/* QEMU console emulation */
|
/* QEMU console emulation */
|
||||||
if (down) {
|
if (down) {
|
||||||
int numlock = vs->modifiers_state[0x45];
|
|
||||||
switch (keycode) {
|
switch (keycode) {
|
||||||
case 0x2a: /* Left Shift */
|
case 0x2a: /* Left Shift */
|
||||||
case 0x36: /* Right Shift */
|
case 0x36: /* Right Shift */
|
||||||
@ -1642,13 +1670,40 @@ static void do_key_event(VncState *vs, int down, int keycode, int sym)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
kbd_put_keysym(sym);
|
if (control) {
|
||||||
|
kbd_put_keysym(sym & 0x1f);
|
||||||
|
} else {
|
||||||
|
kbd_put_keysym(sym);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void vnc_release_modifiers(VncState *vs)
|
||||||
|
{
|
||||||
|
static const int keycodes[] = {
|
||||||
|
/* shift, control, alt keys, both left & right */
|
||||||
|
0x2a, 0x36, 0x1d, 0x9d, 0x38, 0xb8,
|
||||||
|
};
|
||||||
|
int i, keycode;
|
||||||
|
|
||||||
|
if (!is_graphic_console()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (i = 0; i < ARRAY_SIZE(keycodes); i++) {
|
||||||
|
keycode = keycodes[i];
|
||||||
|
if (!vs->modifiers_state[keycode]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (keycode & SCANCODE_GREY) {
|
||||||
|
kbd_put_keycode(SCANCODE_EMUL0);
|
||||||
|
}
|
||||||
|
kbd_put_keycode(keycode | SCANCODE_UP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void key_event(VncState *vs, int down, uint32_t sym)
|
static void key_event(VncState *vs, int down, uint32_t sym)
|
||||||
{
|
{
|
||||||
int keycode;
|
int keycode;
|
||||||
@ -2048,8 +2103,67 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
|
|||||||
static int protocol_client_init(VncState *vs, uint8_t *data, size_t len)
|
static int protocol_client_init(VncState *vs, uint8_t *data, size_t len)
|
||||||
{
|
{
|
||||||
char buf[1024];
|
char buf[1024];
|
||||||
|
VncShareMode mode;
|
||||||
int size;
|
int size;
|
||||||
|
|
||||||
|
mode = data[0] ? VNC_SHARE_MODE_SHARED : VNC_SHARE_MODE_EXCLUSIVE;
|
||||||
|
switch (vs->vd->share_policy) {
|
||||||
|
case VNC_SHARE_POLICY_IGNORE:
|
||||||
|
/*
|
||||||
|
* Ignore the shared flag. Nothing to do here.
|
||||||
|
*
|
||||||
|
* Doesn't conform to the rfb spec but is traditional qemu
|
||||||
|
* behavior, thus left here as option for compatibility
|
||||||
|
* reasons.
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
case VNC_SHARE_POLICY_ALLOW_EXCLUSIVE:
|
||||||
|
/*
|
||||||
|
* Policy: Allow clients ask for exclusive access.
|
||||||
|
*
|
||||||
|
* Implementation: When a client asks for exclusive access,
|
||||||
|
* disconnect all others. Shared connects are allowed as long
|
||||||
|
* as no exclusive connection exists.
|
||||||
|
*
|
||||||
|
* This is how the rfb spec suggests to handle the shared flag.
|
||||||
|
*/
|
||||||
|
if (mode == VNC_SHARE_MODE_EXCLUSIVE) {
|
||||||
|
VncState *client;
|
||||||
|
QTAILQ_FOREACH(client, &vs->vd->clients, next) {
|
||||||
|
if (vs == client) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (client->share_mode != VNC_SHARE_MODE_EXCLUSIVE &&
|
||||||
|
client->share_mode != VNC_SHARE_MODE_SHARED) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
vnc_disconnect_start(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mode == VNC_SHARE_MODE_SHARED) {
|
||||||
|
if (vs->vd->num_exclusive > 0) {
|
||||||
|
vnc_disconnect_start(vs);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case VNC_SHARE_POLICY_FORCE_SHARED:
|
||||||
|
/*
|
||||||
|
* Policy: Shared connects only.
|
||||||
|
* Implementation: Disallow clients asking for exclusive access.
|
||||||
|
*
|
||||||
|
* Useful for shared desktop sessions where you don't want
|
||||||
|
* someone forgetting to say -shared when running the vnc
|
||||||
|
* client disconnect everybody else.
|
||||||
|
*/
|
||||||
|
if (mode == VNC_SHARE_MODE_EXCLUSIVE) {
|
||||||
|
vnc_disconnect_start(vs);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
vnc_set_share_mode(vs, mode);
|
||||||
|
|
||||||
vs->client_width = ds_get_width(vs->ds);
|
vs->client_width = ds_get_width(vs->ds);
|
||||||
vs->client_height = ds_get_height(vs->ds);
|
vs->client_height = ds_get_height(vs->ds);
|
||||||
vnc_write_u16(vs, vs->client_width);
|
vnc_write_u16(vs, vs->client_width);
|
||||||
@ -2445,7 +2559,7 @@ static int vnc_refresh_server_surface(VncDisplay *vd)
|
|||||||
guest_ptr = guest_row;
|
guest_ptr = guest_row;
|
||||||
server_ptr = server_row;
|
server_ptr = server_row;
|
||||||
|
|
||||||
for (x = 0; x < vd->guest.ds->width;
|
for (x = 0; x + 15 < vd->guest.ds->width;
|
||||||
x += 16, guest_ptr += cmp_bytes, server_ptr += cmp_bytes) {
|
x += 16, guest_ptr += cmp_bytes, server_ptr += cmp_bytes) {
|
||||||
if (!test_and_clear_bit((x / 16), vd->guest.dirty[y]))
|
if (!test_and_clear_bit((x / 16), vd->guest.dirty[y]))
|
||||||
continue;
|
continue;
|
||||||
@ -2556,6 +2670,7 @@ static void vnc_connect(VncDisplay *vd, int csock, int skipauth)
|
|||||||
|
|
||||||
vnc_client_cache_addr(vs);
|
vnc_client_cache_addr(vs);
|
||||||
vnc_qmp_event(vs, QEVENT_VNC_CONNECTED);
|
vnc_qmp_event(vs, QEVENT_VNC_CONNECTED);
|
||||||
|
vnc_set_share_mode(vs, VNC_SHARE_MODE_CONNECTING);
|
||||||
|
|
||||||
vs->vd = vd;
|
vs->vd = vd;
|
||||||
vs->ds = vd->ds;
|
vs->ds = vd->ds;
|
||||||
@ -2749,6 +2864,7 @@ int vnc_display_open(DisplayState *ds, const char *display)
|
|||||||
|
|
||||||
if (!(vs->display = strdup(display)))
|
if (!(vs->display = strdup(display)))
|
||||||
return -1;
|
return -1;
|
||||||
|
vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;
|
||||||
|
|
||||||
options = display;
|
options = display;
|
||||||
while ((options = strchr(options, ','))) {
|
while ((options = strchr(options, ','))) {
|
||||||
@ -2804,6 +2920,19 @@ int vnc_display_open(DisplayState *ds, const char *display)
|
|||||||
vs->lossy = true;
|
vs->lossy = true;
|
||||||
} else if (strncmp(options, "non-adapative", 13) == 0) {
|
} else if (strncmp(options, "non-adapative", 13) == 0) {
|
||||||
vs->non_adaptive = true;
|
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 {
|
||||||
|
fprintf(stderr, "unknown vnc share= option\n");
|
||||||
|
g_free(vs->display);
|
||||||
|
vs->display = NULL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
ui/vnc.h
16
ui/vnc.h
@ -122,9 +122,24 @@ struct VncSurface
|
|||||||
DisplaySurface *ds;
|
DisplaySurface *ds;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef enum VncShareMode {
|
||||||
|
VNC_SHARE_MODE_CONNECTING = 1,
|
||||||
|
VNC_SHARE_MODE_SHARED,
|
||||||
|
VNC_SHARE_MODE_EXCLUSIVE,
|
||||||
|
VNC_SHARE_MODE_DISCONNECTED,
|
||||||
|
} VncShareMode;
|
||||||
|
|
||||||
|
typedef enum VncSharePolicy {
|
||||||
|
VNC_SHARE_POLICY_IGNORE = 1,
|
||||||
|
VNC_SHARE_POLICY_ALLOW_EXCLUSIVE,
|
||||||
|
VNC_SHARE_POLICY_FORCE_SHARED,
|
||||||
|
} VncSharePolicy;
|
||||||
|
|
||||||
struct VncDisplay
|
struct VncDisplay
|
||||||
{
|
{
|
||||||
QTAILQ_HEAD(, VncState) clients;
|
QTAILQ_HEAD(, VncState) clients;
|
||||||
|
int num_exclusive;
|
||||||
|
VncSharePolicy share_policy;
|
||||||
QEMUTimer *timer;
|
QEMUTimer *timer;
|
||||||
int timer_interval;
|
int timer_interval;
|
||||||
int lsock;
|
int lsock;
|
||||||
@ -250,6 +265,7 @@ struct VncState
|
|||||||
int last_y;
|
int last_y;
|
||||||
int client_width;
|
int client_width;
|
||||||
int client_height;
|
int client_height;
|
||||||
|
VncShareMode share_mode;
|
||||||
|
|
||||||
uint32_t vnc_encoding;
|
uint32_t vnc_encoding;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user