Commit cf5cfe04 authored by Anthony Liguori's avatar Anthony Liguori
Browse files

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
parents 006c891f 7bc9318b
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -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
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
ETEXI

+132 −3
Original line number Diff line number Diff line
@@ -46,6 +46,30 @@ static VncDisplay *vnc_display; /* needed for info vnc */
static DisplayChangeListener *dcl;

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,
                            struct sockaddr_storage *sa,
@@ -997,6 +1021,7 @@ static void vnc_disconnect_start(VncState *vs)
{
    if (vs->csock == -1)
        return;
    vnc_set_share_mode(vs, VNC_SHARE_MODE_DISCONNECTED);
    qemu_set_fd_handler2(vs->csock, NULL, NULL, NULL, NULL);
    closesocket(vs->csock);
    vs->csock = -1;
@@ -1027,6 +1052,7 @@ static void vnc_disconnect_finish(VncState *vs)
    vnc_sasl_client_cleanup(vs);
#endif /* CONFIG_VNC_SASL */
    audio_del(vs);
    vnc_release_modifiers(vs);

    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
            kbd_put_keycode(keycode | SCANCODE_UP);
    } else {
        bool numlock = vs->modifiers_state[0x45];
        bool control = (vs->modifiers_state[0x1d] ||
                        vs->modifiers_state[0x9d]);
        /* QEMU console emulation */
        if (down) {
            int numlock = vs->modifiers_state[0x45];
            switch (keycode) {
            case 0x2a:                          /* Left Shift */
            case 0x36:                          /* Right Shift */
@@ -1642,13 +1670,40 @@ static void do_key_event(VncState *vs, int down, int keycode, int sym)
                break;

            default:
                if (control) {
                    kbd_put_keysym(sym & 0x1f);
                } else {
                    kbd_put_keysym(sym);
                }
                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)
{
    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)
{
    char buf[1024];
    VncShareMode mode;
    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_height = ds_get_height(vs->ds);
    vnc_write_u16(vs, vs->client_width);
@@ -2445,7 +2559,7 @@ static int vnc_refresh_server_surface(VncDisplay *vd)
            guest_ptr  = guest_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) {
                if (!test_and_clear_bit((x / 16), vd->guest.dirty[y]))
                    continue;
@@ -2556,6 +2670,7 @@ static void vnc_connect(VncDisplay *vd, int csock, int skipauth)

    vnc_client_cache_addr(vs);
    vnc_qmp_event(vs, QEVENT_VNC_CONNECTED);
    vnc_set_share_mode(vs, VNC_SHARE_MODE_CONNECTING);

    vs->vd = vd;
    vs->ds = vd->ds;
@@ -2749,6 +2864,7 @@ int vnc_display_open(DisplayState *ds, const char *display)

    if (!(vs->display = strdup(display)))
        return -1;
    vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;

    options = display;
    while ((options = strchr(options, ','))) {
@@ -2804,6 +2920,19 @@ int vnc_display_open(DisplayState *ds, const char *display)
            vs->lossy = true;
        } else if (strncmp(options, "non-adapative", 13) == 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 {
                fprintf(stderr, "unknown vnc share= option\n");
                g_free(vs->display);
                vs->display = NULL;
                return -1;
            }
        }
    }

+16 −0
Original line number Diff line number Diff line
@@ -122,9 +122,24 @@ struct VncSurface
    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
{
    QTAILQ_HEAD(, VncState) clients;
    int num_exclusive;
    VncSharePolicy share_policy;
    QEMUTimer *timer;
    int timer_interval;
    int lsock;
@@ -250,6 +265,7 @@ struct VncState
    int last_y;
    int client_width;
    int client_height;
    VncShareMode share_mode;

    uint32_t vnc_encoding;