diff options
Diffstat (limited to 'ui')
-rw-r--r-- | ui/cocoa.m | 27 | ||||
-rw-r--r-- | ui/cursor.c | 6 | ||||
-rw-r--r-- | ui/gtk.c | 5 | ||||
-rw-r--r-- | ui/input.c | 2 | ||||
-rw-r--r-- | ui/sdl2.c | 79 | ||||
-rw-r--r-- | ui/sdl_zoom_template.h | 8 | ||||
-rw-r--r-- | ui/spice-core.c | 14 | ||||
-rw-r--r-- | ui/spice-display.c | 6 | ||||
-rw-r--r-- | ui/trace-events | 7 | ||||
-rw-r--r-- | ui/vnc-auth-sasl.c | 16 | ||||
-rw-r--r-- | ui/vnc-auth-sasl.h | 5 | ||||
-rw-r--r-- | ui/vnc-jobs.c | 5 | ||||
-rw-r--r-- | ui/vnc.c | 322 | ||||
-rw-r--r-- | ui/vnc.h | 28 |
14 files changed, 360 insertions, 170 deletions
diff --git a/ui/cocoa.m b/ui/cocoa.m index 330ccebf90..6be9848391 100644 --- a/ui/cocoa.m +++ b/ui/cocoa.m @@ -786,11 +786,24 @@ QemuCocoaView *cocoaView; mouse_event = true; break; case NSEventTypeScrollWheel: - if (isMouseGrabbed) { - buttons |= ([event deltaY] < 0) ? - MOUSE_EVENT_WHEELUP : MOUSE_EVENT_WHEELDN; - } - mouse_event = true; + /* + * Send wheel events to the guest regardless of window focus. + * This is in-line with standard Mac OS X UI behaviour. + */ + + /* Determine if this is a scroll up or scroll down event */ + buttons = ([event scrollingDeltaY] > 0) ? + INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN; + qemu_input_queue_btn(dcl->con, buttons, true); + qemu_input_event_sync(); + qemu_input_queue_btn(dcl->con, buttons, false); + qemu_input_event_sync(); + + /* + * Since deltaY also reports scroll wheel events we prevent mouse + * movement code from executing. + */ + mouse_event = false; break; default: [NSApp sendEvent:event]; @@ -809,9 +822,7 @@ QemuCocoaView *cocoaView; static uint32_t bmap[INPUT_BUTTON__MAX] = { [INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON, [INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON, - [INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON, - [INPUT_BUTTON_WHEEL_UP] = MOUSE_EVENT_WHEELUP, - [INPUT_BUTTON_WHEEL_DOWN] = MOUSE_EVENT_WHEELDN, + [INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON }; qemu_input_update_buttons(dcl->con, bmap, last_buttons, buttons); last_buttons = buttons; diff --git a/ui/cursor.c b/ui/cursor.c index 2e2fe13fa6..f3da0cee79 100644 --- a/ui/cursor.c +++ b/ui/cursor.c @@ -19,11 +19,11 @@ static QEMUCursor *cursor_parse_xpm(const char *xpm[]) if (sscanf(xpm[line], "%u %u %u %u", &width, &height, &colors, &chars) != 4) { fprintf(stderr, "%s: header parse error: \"%s\"\n", - __FUNCTION__, xpm[line]); + __func__, xpm[line]); return NULL; } if (chars != 1) { - fprintf(stderr, "%s: chars != 1 not supported\n", __FUNCTION__); + fprintf(stderr, "%s: chars != 1 not supported\n", __func__); return NULL; } line++; @@ -41,7 +41,7 @@ static QEMUCursor *cursor_parse_xpm(const char *xpm[]) } } fprintf(stderr, "%s: color parse error: \"%s\"\n", - __FUNCTION__, xpm[line]); + __func__, xpm[line]); return NULL; } @@ -2248,6 +2248,11 @@ void gtk_display_init(DisplayState *ds, bool full_screen, bool grab_on_hover) exit(1); } +#if !GTK_CHECK_VERSION(3, 0, 0) + g_printerr("Running QEMU with GTK 2.x is deprecated, and will be removed\n" + "in a future release. Please switch to GTK 3.x instead\n"); +#endif + s->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); #if GTK_CHECK_VERSION(3, 2, 0) s->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); diff --git a/ui/input.c b/ui/input.c index 3e2d324278..e5b78aae9e 100644 --- a/ui/input.c +++ b/ui/input.c @@ -421,6 +421,8 @@ void qemu_input_event_send_key(QemuConsole *src, KeyValue *key, bool down) } else if (queue_count < queue_limit) { qemu_input_queue_event(&kbd_queue, src, evt); qemu_input_queue_sync(&kbd_queue); + } else { + qapi_free_InputEvent(evt); } } @@ -276,32 +276,10 @@ static void sdl_send_mouse_event(struct sdl2_console *scon, int dx, int dy, } if (qemu_input_is_absolute()) { - int scr_w, scr_h; - int max_w = 0, max_h = 0; - int off_x = 0, off_y = 0; - int cur_off_x = 0, cur_off_y = 0; - int i; - - for (i = 0; i < sdl2_num_outputs; i++) { - struct sdl2_console *thiscon = &sdl2_console[i]; - if (thiscon->real_window && thiscon->surface) { - SDL_GetWindowSize(thiscon->real_window, &scr_w, &scr_h); - cur_off_x = thiscon->x; - cur_off_y = thiscon->y; - if (scr_w + cur_off_x > max_w) { - max_w = scr_w + cur_off_x; - } - if (scr_h + cur_off_y > max_h) { - max_h = scr_h + cur_off_y; - } - if (i == scon->idx) { - off_x = cur_off_x; - off_y = cur_off_y; - } - } - } - qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_X, off_x + x, 0, max_w); - qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_Y, off_y + y, 0, max_h); + qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_X, + x, 0, surface_width(scon->surface)); + qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_Y, + y, 0, surface_height(scon->surface)); } else { if (guest_cursor) { x -= guest_x; @@ -334,22 +312,28 @@ static void toggle_full_screen(struct sdl2_console *scon) sdl2_redraw(scon); } -static void handle_keydown(SDL_Event *ev) +static int get_mod_state(void) { - int mod_state, win; - struct sdl2_console *scon = get_scon_from_window(ev->key.windowID); + SDL_Keymod mod = SDL_GetModState(); if (alt_grab) { - mod_state = (SDL_GetModState() & (gui_grab_code | KMOD_LSHIFT)) == + return (mod & (gui_grab_code | KMOD_LSHIFT)) == (gui_grab_code | KMOD_LSHIFT); } else if (ctrl_grab) { - mod_state = (SDL_GetModState() & KMOD_RCTRL) == KMOD_RCTRL; + return (mod & KMOD_RCTRL) == KMOD_RCTRL; } else { - mod_state = (SDL_GetModState() & gui_grab_code) == gui_grab_code; + return (mod & gui_grab_code) == gui_grab_code; } - gui_key_modifier_pressed = mod_state; +} + +static void handle_keydown(SDL_Event *ev) +{ + int win; + struct sdl2_console *scon = get_scon_from_window(ev->key.windowID); + + gui_key_modifier_pressed = get_mod_state(); - if (gui_key_modifier_pressed) { + if (!scon->ignore_hotkeys && gui_key_modifier_pressed && !ev->key.repeat) { switch (ev->key.keysym.scancode) { case SDL_SCANCODE_2: case SDL_SCANCODE_3: @@ -423,6 +407,8 @@ static void handle_keyup(SDL_Event *ev) int mod_state; struct sdl2_console *scon = get_scon_from_window(ev->key.windowID); + scon->ignore_hotkeys = false; + if (!alt_grab) { mod_state = (ev->key.keysym.mod & gui_grab_code); } else { @@ -466,6 +452,10 @@ static void handle_mousemotion(SDL_Event *ev) int max_x, max_y; struct sdl2_console *scon = get_scon_from_window(ev->key.windowID); + if (!qemu_console_is_graphic(scon->dcl.con)) { + return; + } + if (qemu_input_is_absolute() || absolute_enabled) { int scr_w, scr_h; SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h); @@ -494,6 +484,10 @@ static void handle_mousebutton(SDL_Event *ev) SDL_MouseButtonEvent *bev; struct sdl2_console *scon = get_scon_from_window(ev->key.windowID); + if (!qemu_console_is_graphic(scon->dcl.con)) { + return; + } + bev = &ev->button; if (!gui_grab && !qemu_input_is_absolute()) { if (ev->type == SDL_MOUSEBUTTONUP && bev->button == SDL_BUTTON_LEFT) { @@ -516,6 +510,10 @@ static void handle_mousewheel(SDL_Event *ev) SDL_MouseWheelEvent *wev = &ev->wheel; InputButton btn; + if (!qemu_console_is_graphic(scon->dcl.con)) { + return; + } + if (wev->y > 0) { btn = INPUT_BUTTON_WHEEL_UP; } else if (wev->y < 0) { @@ -557,6 +555,14 @@ static void handle_windowevent(SDL_Event *ev) if (!gui_grab && (qemu_input_is_absolute() || absolute_enabled)) { absolute_mouse_grab(scon); } + /* If a new console window opened using a hotkey receives the + * focus, SDL sends another KEYDOWN event to the new window, + * closing the console window immediately after. + * + * Work around this by ignoring further hotkey events until a + * key is released. + */ + scon->ignore_hotkeys = get_mod_state(); break; case SDL_WINDOWEVENT_FOCUS_LOST: if (gui_grab && !gui_fullscreen) { @@ -657,6 +663,11 @@ static void sdl_mouse_warp(DisplayChangeListener *dcl, int x, int y, int on) { struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl); + + if (!qemu_console_is_graphic(scon->dcl.con)) { + return; + } + if (on) { if (!guest_cursor) { sdl_show_cursor(); diff --git a/ui/sdl_zoom_template.h b/ui/sdl_zoom_template.h index 3bb508b51e..6a424adfb4 100644 --- a/ui/sdl_zoom_template.h +++ b/ui/sdl_zoom_template.h @@ -34,22 +34,22 @@ #define setRed(r, pcolor) do { \ *pcolor = ((*pcolor) & (~(dpf->Rmask))) + \ (((r) & (dpf->Rmask >> dpf->Rshift)) << dpf->Rshift); \ -} while (0); +} while (0) #define setGreen(g, pcolor) do { \ *pcolor = ((*pcolor) & (~(dpf->Gmask))) + \ (((g) & (dpf->Gmask >> dpf->Gshift)) << dpf->Gshift); \ -} while (0); +} while (0) #define setBlue(b, pcolor) do { \ *pcolor = ((*pcolor) & (~(dpf->Bmask))) + \ (((b) & (dpf->Bmask >> dpf->Bshift)) << dpf->Bshift); \ -} while (0); +} while (0) #define setAlpha(a, pcolor) do { \ *pcolor = ((*pcolor) & (~(dpf->Amask))) + \ (((a) & (dpf->Amask >> dpf->Ashift)) << dpf->Ashift); \ -} while (0); +} while (0) static void glue(sdl_zoom_rgb, BPP)(SDL_Surface *src, SDL_Surface *dst, int smooth, SDL_Rect *dst_rect) diff --git a/ui/spice-core.c b/ui/spice-core.c index ea04dc69b5..2baf0c7120 100644 --- a/ui/spice-core.c +++ b/ui/spice-core.c @@ -55,9 +55,7 @@ static QemuThread me; struct SpiceTimer { QEMUTimer *timer; - QTAILQ_ENTRY(SpiceTimer) next; }; -static QTAILQ_HEAD(, SpiceTimer) timers = QTAILQ_HEAD_INITIALIZER(timers); static SpiceTimer *timer_add(SpiceTimerFunc func, void *opaque) { @@ -65,7 +63,6 @@ static SpiceTimer *timer_add(SpiceTimerFunc func, void *opaque) timer = g_malloc0(sizeof(*timer)); timer->timer = timer_new_ms(QEMU_CLOCK_REALTIME, func, opaque); - QTAILQ_INSERT_TAIL(&timers, timer, next); return timer; } @@ -83,18 +80,14 @@ static void timer_remove(SpiceTimer *timer) { timer_del(timer->timer); timer_free(timer->timer); - QTAILQ_REMOVE(&timers, timer, next); g_free(timer); } struct SpiceWatch { int fd; - int event_mask; SpiceWatchFunc func; void *opaque; - QTAILQ_ENTRY(SpiceWatch) next; }; -static QTAILQ_HEAD(, SpiceWatch) watches = QTAILQ_HEAD_INITIALIZER(watches); static void watch_read(void *opaque) { @@ -113,11 +106,10 @@ static void watch_update_mask(SpiceWatch *watch, int event_mask) IOHandler *on_read = NULL; IOHandler *on_write = NULL; - watch->event_mask = event_mask; - if (watch->event_mask & SPICE_WATCH_EVENT_READ) { + if (event_mask & SPICE_WATCH_EVENT_READ) { on_read = watch_read; } - if (watch->event_mask & SPICE_WATCH_EVENT_WRITE) { + if (event_mask & SPICE_WATCH_EVENT_WRITE) { on_write = watch_write; } qemu_set_fd_handler(watch->fd, on_read, on_write, watch); @@ -131,7 +123,6 @@ static SpiceWatch *watch_add(int fd, int event_mask, SpiceWatchFunc func, void * watch->fd = fd; watch->func = func; watch->opaque = opaque; - QTAILQ_INSERT_TAIL(&watches, watch, next); watch_update_mask(watch, event_mask); return watch; @@ -140,7 +131,6 @@ static SpiceWatch *watch_add(int fd, int event_mask, SpiceWatchFunc func, void * static void watch_remove(SpiceWatch *watch) { qemu_set_fd_handler(watch->fd, NULL, NULL, NULL); - QTAILQ_REMOVE(&watches, watch, next); g_free(watch); } diff --git a/ui/spice-display.c b/ui/spice-display.c index ad1ceafb3f..efe9c57eb5 100644 --- a/ui/spice-display.c +++ b/ui/spice-display.c @@ -519,7 +519,6 @@ static void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker) SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl); dprint(1, "%s/%d:\n", __func__, ssd->qxl.id); - ssd->worker = qxl_worker; } static void interface_set_compression_level(QXLInstance *sin, int level) @@ -630,13 +629,13 @@ static int interface_req_cursor_notification(QXLInstance *sin) static void interface_notify_update(QXLInstance *sin, uint32_t update_id) { - fprintf(stderr, "%s: abort()\n", __FUNCTION__); + fprintf(stderr, "%s: abort()\n", __func__); abort(); } static int interface_flush_resources(QXLInstance *sin) { - fprintf(stderr, "%s: abort()\n", __FUNCTION__); + fprintf(stderr, "%s: abort()\n", __func__); abort(); return 0; } @@ -1028,7 +1027,6 @@ static void qemu_spice_display_init_one(QemuConsole *con) ssd->qxl.base.sif = &dpy_interface.base; qemu_spice_add_display_interface(&ssd->qxl, con); - assert(ssd->worker); qemu_spice_create_host_memslot(ssd); register_displaychangelistener(&ssd->dcl); diff --git a/ui/trace-events b/ui/trace-events index 1a9f126330..85f74f948b 100644 --- a/ui/trace-events +++ b/ui/trace-events @@ -35,6 +35,13 @@ vnc_client_connect(void *state, void *ioc) "VNC client connect state=%p ioc=%p" vnc_client_disconnect_start(void *state, void *ioc) "VNC client disconnect start state=%p ioc=%p" vnc_client_disconnect_finish(void *state, void *ioc) "VNC client disconnect finish state=%p ioc=%p" vnc_client_io_wrap(void *state, void *ioc, const char *type) "VNC client I/O wrap state=%p ioc=%p type=%s" +vnc_client_throttle_threshold(void *state, void *ioc, size_t oldoffset, size_t offset, int client_width, int client_height, int bytes_per_pixel, void *audio_cap) "VNC client throttle threshold state=%p ioc=%p oldoffset=%zu newoffset=%zu width=%d height=%d bpp=%d audio=%p" +vnc_client_throttle_incremental(void *state, void *ioc, int job_update, size_t offset) "VNC client throttle incremental state=%p ioc=%p job-update=%d offset=%zu" +vnc_client_throttle_forced(void *state, void *ioc, int job_update, size_t offset) "VNC client throttle forced state=%p ioc=%p job-update=%d offset=%zu" +vnc_client_throttle_audio(void *state, void *ioc, size_t offset) "VNC client throttle audio state=%p ioc=%p offset=%zu" +vnc_client_unthrottle_forced(void *state, void *ioc) "VNC client unthrottle forced offset state=%p ioc=%p" +vnc_client_unthrottle_incremental(void *state, void *ioc, size_t offset) "VNC client unthrottle incremental state=%p ioc=%p offset=%zu" +vnc_client_output_limit(void *state, void *ioc, size_t offset, size_t threshold) "VNC client output limit state=%p ioc=%p offset=%zu threshold=%zu" vnc_auth_init(void *display, int websock, int auth, int subauth) "VNC auth init state=%p websock=%d auth=%d subauth=%d" vnc_auth_start(void *state, int method) "VNC client auth start state=%p method=%d" vnc_auth_pass(void *state, int method) "VNC client auth passed state=%p method=%d" diff --git a/ui/vnc-auth-sasl.c b/ui/vnc-auth-sasl.c index 23f28280e7..74a5f513f2 100644 --- a/ui/vnc-auth-sasl.c +++ b/ui/vnc-auth-sasl.c @@ -48,9 +48,9 @@ void vnc_sasl_client_cleanup(VncState *vs) } -long vnc_client_write_sasl(VncState *vs) +size_t vnc_client_write_sasl(VncState *vs) { - long ret; + size_t ret; VNC_DEBUG("Write SASL: Pending output %p size %zd offset %zd " "Encoded: %p size %d offset %d\n", @@ -67,6 +67,7 @@ long vnc_client_write_sasl(VncState *vs) if (err != SASL_OK) return vnc_client_io_error(vs, -1, NULL); + vs->sasl.encodedRawLength = vs->output.offset; vs->sasl.encodedOffset = 0; } @@ -78,7 +79,12 @@ long vnc_client_write_sasl(VncState *vs) vs->sasl.encodedOffset += ret; if (vs->sasl.encodedOffset == vs->sasl.encodedLength) { - vs->output.offset = 0; + if (vs->sasl.encodedRawLength >= vs->force_update_offset) { + vs->force_update_offset = 0; + } else { + vs->force_update_offset -= vs->sasl.encodedRawLength; + } + vs->output.offset -= vs->sasl.encodedRawLength; vs->sasl.encoded = NULL; vs->sasl.encodedOffset = vs->sasl.encodedLength = 0; } @@ -100,9 +106,9 @@ long vnc_client_write_sasl(VncState *vs) } -long vnc_client_read_sasl(VncState *vs) +size_t vnc_client_read_sasl(VncState *vs) { - long ret; + size_t ret; uint8_t encoded[4096]; const char *decoded; unsigned int decodedLen; diff --git a/ui/vnc-auth-sasl.h b/ui/vnc-auth-sasl.h index cb42745a6b..2ae224ee3a 100644 --- a/ui/vnc-auth-sasl.h +++ b/ui/vnc-auth-sasl.h @@ -53,6 +53,7 @@ struct VncStateSASL { */ const uint8_t *encoded; unsigned int encodedLength; + unsigned int encodedRawLength; unsigned int encodedOffset; char *username; char *mechlist; @@ -64,8 +65,8 @@ struct VncDisplaySASL { void vnc_sasl_client_cleanup(VncState *vs); -long vnc_client_read_sasl(VncState *vs); -long vnc_client_write_sasl(VncState *vs); +size_t vnc_client_read_sasl(VncState *vs); +size_t vnc_client_write_sasl(VncState *vs); void start_auth_sasl(VncState *vs); diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c index f7867771ae..e326679dd0 100644 --- a/ui/vnc-jobs.c +++ b/ui/vnc-jobs.c @@ -152,6 +152,11 @@ void vnc_jobs_consume_buffer(VncState *vs) vs->ioc, G_IO_IN | G_IO_OUT, vnc_client_io, vs, NULL); } buffer_move(&vs->output, &vs->jobs_buffer); + + if (vs->job_update == VNC_STATE_UPDATE_FORCE) { + vs->force_update_offset = vs->output.offset; + } + vs->job_update = VNC_STATE_UPDATE_NONE; } flush = vs->ioc != NULL && vs->abort != true; vnc_unlock_output(vs); @@ -60,6 +60,7 @@ static QTAILQ_HEAD(, VncDisplay) vnc_displays = static int vnc_cursor_define(VncState *vs); static void vnc_release_modifiers(VncState *vs); +static void vnc_update_throttle_offset(VncState *vs); static void vnc_set_share_mode(VncState *vs, VncShareMode mode) { @@ -596,7 +597,7 @@ VncInfo2List *qmp_query_vnc_servers(Error **errp) 3) resolutions > 1024 */ -static int vnc_update_client(VncState *vs, int has_dirty, bool sync); +static int vnc_update_client(VncState *vs, int has_dirty); static void vnc_disconnect_start(VncState *vs); static void vnc_colordepth(VncState *vs); @@ -766,6 +767,7 @@ static void vnc_dpy_switch(DisplayChangeListener *dcl, vnc_set_area_dirty(vs->dirty, vd, 0, 0, vnc_width(vd), vnc_height(vd)); + vnc_update_throttle_offset(vs); } } @@ -961,85 +963,168 @@ static int find_and_clear_dirty_height(VncState *vs, return h; } -static int vnc_update_client(VncState *vs, int has_dirty, bool sync) +/* + * Figure out how much pending data we should allow in the output + * buffer before we throttle incremental display updates, and/or + * drop audio samples. + * + * We allow for equiv of 1 full display's worth of FB updates, + * and 1 second of audio samples. If audio backlog was larger + * than that the client would already suffering awful audio + * glitches, so dropping samples is no worse really). + */ +static void vnc_update_throttle_offset(VncState *vs) { - if (vs->disconnecting) { - vnc_disconnect_finish(vs); - return 0; + size_t offset = + vs->client_width * vs->client_height * vs->client_pf.bytes_per_pixel; + + if (vs->audio_cap) { + int freq = vs->as.freq; + /* We don't limit freq when reading settings from client, so + * it could be upto MAX_INT in size. 48khz is a sensible + * upper bound for trustworthy clients */ + int bps; + if (freq > 48000) { + freq = 48000; + } + switch (vs->as.fmt) { + default: + case AUD_FMT_U8: + case AUD_FMT_S8: + bps = 1; + break; + case AUD_FMT_U16: + case AUD_FMT_S16: + bps = 2; + break; + case AUD_FMT_U32: + case AUD_FMT_S32: + bps = 4; + break; + } + offset += freq * bps * vs->as.nchannels; } - vs->has_dirty += has_dirty; - if (vs->need_update && !vs->disconnecting) { - VncDisplay *vd = vs->vd; - VncJob *job; - int y; - int height, width; - int n = 0; - - if (vs->output.offset && !vs->audio_cap && !vs->force_update) - /* kernel send buffers are full -> drop frames to throttle */ - return 0; + /* Put a floor of 1MB on offset, so that if we have a large pending + * buffer and the display is resized to a small size & back again + * we don't suddenly apply a tiny send limit + */ + offset = MAX(offset, 1024 * 1024); - if (!vs->has_dirty && !vs->audio_cap && !vs->force_update) - return 0; + if (vs->throttle_output_offset != offset) { + trace_vnc_client_throttle_threshold( + vs, vs->ioc, vs->throttle_output_offset, offset, vs->client_width, + vs->client_height, vs->client_pf.bytes_per_pixel, vs->audio_cap); + } - /* - * Send screen updates to the vnc client using the server - * surface and server dirty map. guest surface updates - * happening in parallel don't disturb us, the next pass will - * send them to the client. + vs->throttle_output_offset = offset; +} + +static bool vnc_should_update(VncState *vs) +{ + switch (vs->update) { + case VNC_STATE_UPDATE_NONE: + break; + case VNC_STATE_UPDATE_INCREMENTAL: + /* Only allow incremental updates if the pending send queue + * is less than the permitted threshold, and the job worker + * is completely idle. */ - job = vnc_job_new(vs); - - height = pixman_image_get_height(vd->server); - width = pixman_image_get_width(vd->server); - - y = 0; - for (;;) { - int x, h; - unsigned long x2; - unsigned long offset = find_next_bit((unsigned long *) &vs->dirty, - height * VNC_DIRTY_BPL(vs), - y * VNC_DIRTY_BPL(vs)); - if (offset == height * VNC_DIRTY_BPL(vs)) { - /* no more dirty bits */ - break; - } - y = offset / VNC_DIRTY_BPL(vs); - x = offset % VNC_DIRTY_BPL(vs); - x2 = find_next_zero_bit((unsigned long *) &vs->dirty[y], - VNC_DIRTY_BPL(vs), x); - bitmap_clear(vs->dirty[y], x, x2 - x); - h = find_and_clear_dirty_height(vs, y, x, x2, height); - x2 = MIN(x2, width / VNC_DIRTY_PIXELS_PER_BIT); - if (x2 > x) { - n += vnc_job_add_rect(job, x * VNC_DIRTY_PIXELS_PER_BIT, y, - (x2 - x) * VNC_DIRTY_PIXELS_PER_BIT, h); - } - if (!x && x2 == width / VNC_DIRTY_PIXELS_PER_BIT) { - y += h; - if (y == height) { - break; - } - } + if (vs->output.offset < vs->throttle_output_offset && + vs->job_update == VNC_STATE_UPDATE_NONE) { + return true; } - - vnc_job_push(job); - if (sync) { - vnc_jobs_join(vs); + trace_vnc_client_throttle_incremental( + vs, vs->ioc, vs->job_update, vs->output.offset); + break; + case VNC_STATE_UPDATE_FORCE: + /* Only allow forced updates if the pending send queue + * does not contain a previous forced update, and the + * job worker is completely idle. + * + * Note this means we'll queue a forced update, even if + * the output buffer size is otherwise over the throttle + * output limit. + */ + if (vs->force_update_offset == 0 && + vs->job_update == VNC_STATE_UPDATE_NONE) { + return true; } - vs->force_update = 0; - vs->has_dirty = 0; - return n; + trace_vnc_client_throttle_forced( + vs, vs->ioc, vs->job_update, vs->force_update_offset); + break; } + return false; +} + +static int vnc_update_client(VncState *vs, int has_dirty) +{ + VncDisplay *vd = vs->vd; + VncJob *job; + int y; + int height, width; + int n = 0; if (vs->disconnecting) { vnc_disconnect_finish(vs); - } else if (sync) { - vnc_jobs_join(vs); + return 0; } - return 0; + vs->has_dirty += has_dirty; + if (!vnc_should_update(vs)) { + return 0; + } + + if (!vs->has_dirty && vs->update != VNC_STATE_UPDATE_FORCE) { + return 0; + } + + /* + * Send screen updates to the vnc client using the server + * surface and server dirty map. guest surface updates + * happening in parallel don't disturb us, the next pass will + * send them to the client. + */ + job = vnc_job_new(vs); + + height = pixman_image_get_height(vd->server); + width = pixman_image_get_width(vd->server); + + y = 0; + for (;;) { + int x, h; + unsigned long x2; + unsigned long offset = find_next_bit((unsigned long *) &vs->dirty, + height * VNC_DIRTY_BPL(vs), + y * VNC_DIRTY_BPL(vs)); + if (offset == height * VNC_DIRTY_BPL(vs)) { + /* no more dirty bits */ + break; + } + y = offset / VNC_DIRTY_BPL(vs); + x = offset % VNC_DIRTY_BPL(vs); + x2 = find_next_zero_bit((unsigned long *) &vs->dirty[y], + VNC_DIRTY_BPL(vs), x); + bitmap_clear(vs->dirty[y], x, x2 - x); + h = find_and_clear_dirty_height(vs, y, x, x2, height); + x2 = MIN(x2, width / VNC_DIRTY_PIXELS_PER_BIT); + if (x2 > x) { + n += vnc_job_add_rect(job, x * VNC_DIRTY_PIXELS_PER_BIT, y, + (x2 - x) * VNC_DIRTY_PIXELS_PER_BIT, h); + } + if (!x && x2 == width / VNC_DIRTY_PIXELS_PER_BIT) { + y += h; + if (y == height) { + break; + } + } + } + + vs->job_update = vs->update; + vs->update = VNC_STATE_UPDATE_NONE; + vnc_job_push(job); + vs->has_dirty = 0; + return n; } /* audio */ @@ -1077,11 +1162,15 @@ static void audio_capture(void *opaque, void *buf, int size) VncState *vs = opaque; vnc_lock_output(vs); - vnc_write_u8(vs, VNC_MSG_SERVER_QEMU); - vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO); - vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_DATA); - vnc_write_u32(vs, size); - vnc_write(vs, buf, size); + if (vs->output.offset < vs->throttle_output_offset) { + vnc_write_u8(vs, VNC_MSG_SERVER_QEMU); + vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO); + vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_DATA); + vnc_write_u32(vs, size); + vnc_write(vs, buf, size); + } else { + trace_vnc_client_throttle_audio(vs, vs->ioc, vs->output.offset); + } vnc_unlock_output(vs); vnc_flush(vs); } @@ -1183,7 +1272,7 @@ void vnc_disconnect_finish(VncState *vs) g_free(vs); } -ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp) +size_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp) { if (ret <= 0) { if (ret == 0) { @@ -1226,9 +1315,9 @@ void vnc_client_error(VncState *vs) * * Returns the number of bytes written, which may be less than * the requested 'datalen' if the socket would block. Returns - * -1 on error, and disconnects the client socket. + * 0 on I/O error, and disconnects the client socket. */ -ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen) +size_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen) { Error *err = NULL; ssize_t ret; @@ -1246,12 +1335,13 @@ ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen) * will switch the FD poll() handler back to read monitoring. * * Returns the number of bytes written, which may be less than - * the buffered output data if the socket would block. Returns - * -1 on error, and disconnects the client socket. + * the buffered output data if the socket would block. Returns + * 0 on I/O error, and disconnects the client socket. */ -static ssize_t vnc_client_write_plain(VncState *vs) +static size_t vnc_client_write_plain(VncState *vs) { - ssize_t ret; + size_t offset; + size_t ret; #ifdef CONFIG_VNC_SASL VNC_DEBUG("Write Plain: Pending output %p size %zd offset %zd. Wait SSF %d\n", @@ -1270,7 +1360,20 @@ static ssize_t vnc_client_write_plain(VncState *vs) if (!ret) return 0; + if (ret >= vs->force_update_offset) { + if (vs->force_update_offset != 0) { + trace_vnc_client_unthrottle_forced(vs, vs->ioc); + } + vs->force_update_offset = 0; + } else { + vs->force_update_offset -= ret; + } + offset = vs->output.offset; buffer_advance(&vs->output, ret); + if (offset >= vs->throttle_output_offset && + vs->output.offset < vs->throttle_output_offset) { + trace_vnc_client_unthrottle_incremental(vs, vs->ioc, vs->output.offset); + } if (vs->output.offset == 0) { if (vs->ioc_tag) { @@ -1339,9 +1442,9 @@ void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting) * * Returns the number of bytes read, which may be less than * the requested 'datalen' if the socket would block. Returns - * -1 on error, and disconnects the client socket. + * 0 on I/O error or EOF, and disconnects the client socket. */ -ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen) +size_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen) { ssize_t ret; Error *err = NULL; @@ -1357,12 +1460,13 @@ ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen) * when not using any SASL SSF encryption layers. Will read as much * data as possible without blocking. * - * Returns the number of bytes read. Returns -1 on error, and - * disconnects the client socket. + * Returns the number of bytes read, which may be less than + * the requested 'datalen' if the socket would block. Returns + * 0 on I/O error or EOF, and disconnects the client socket. */ -static ssize_t vnc_client_read_plain(VncState *vs) +static size_t vnc_client_read_plain(VncState *vs) { - ssize_t ret; + size_t ret; VNC_DEBUG("Read plain %p size %zd offset %zd\n", vs->input.buffer, vs->input.capacity, vs->input.offset); buffer_reserve(&vs->input, 4096); @@ -1388,7 +1492,7 @@ static void vnc_jobs_bh(void *opaque) */ static int vnc_client_read(VncState *vs) { - ssize_t ret; + size_t ret; #ifdef CONFIG_VNC_SASL if (vs->sasl.conn && vs->sasl.runSSF) @@ -1439,8 +1543,39 @@ gboolean vnc_client_io(QIOChannel *ioc G_GNUC_UNUSED, } +/* + * Scale factor to apply to vs->throttle_output_offset when checking for + * hard limit. Worst case normal usage could be x2, if we have a complete + * incremental update and complete forced update in the output buffer. + * So x3 should be good enough, but we pick x5 to be conservative and thus + * (hopefully) never trigger incorrectly. + */ +#define VNC_THROTTLE_OUTPUT_LIMIT_SCALE 5 + void vnc_write(VncState *vs, const void *data, size_t len) { + if (vs->disconnecting) { + return; + } + /* Protection against malicious client/guest to prevent our output + * buffer growing without bound if client stops reading data. This + * should rarely trigger, because we have earlier throttling code + * which stops issuing framebuffer updates and drops audio data + * if the throttle_output_offset value is exceeded. So we only reach + * this higher level if a huge number of pseudo-encodings get + * triggered while data can't be sent on the socket. + * + * NB throttle_output_offset can be zero during early protocol + * handshake, or from the job thread's VncState clone + */ + if (vs->throttle_output_offset != 0 && + vs->output.offset > (vs->throttle_output_offset * + VNC_THROTTLE_OUTPUT_LIMIT_SCALE)) { + trace_vnc_client_output_limit(vs, vs->ioc, vs->output.offset, + vs->throttle_output_offset); + vnc_disconnect_start(vs); + return; + } buffer_reserve(&vs->output, len); if (vs->ioc != NULL && buffer_empty(&vs->output)) { @@ -1876,14 +2011,14 @@ static void ext_key_event(VncState *vs, int down, static void framebuffer_update_request(VncState *vs, int incremental, int x, int y, int w, int h) { - vs->need_update = 1; - if (incremental) { - return; + if (vs->update != VNC_STATE_UPDATE_FORCE) { + vs->update = VNC_STATE_UPDATE_INCREMENTAL; + } + } else { + vs->update = VNC_STATE_UPDATE_FORCE; + vnc_set_area_dirty(vs->dirty, vs->vd, x, y, w, h); } - - vs->force_update = 1; - vnc_set_area_dirty(vs->dirty, vs->vd, x, y, w, h); } static void send_ext_key_event_ack(VncState *vs) @@ -2255,7 +2390,7 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len) } vs->as.nchannels = read_u8(data, 5); if (vs->as.nchannels != 1 && vs->as.nchannels != 2) { - VNC_DEBUG("Invalid audio channel coount %d\n", + VNC_DEBUG("Invalid audio channel count %d\n", read_u8(data, 5)); vnc_client_error(vs); break; @@ -2281,6 +2416,7 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len) break; } + vnc_update_throttle_offset(vs); vnc_read_when(vs, protocol_client_msg, 1); return 0; } @@ -2863,7 +2999,7 @@ static void vnc_refresh(DisplayChangeListener *dcl) vnc_unlock_display(vd); QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) { - rects += vnc_update_client(vs, has_dirty, false); + rects += vnc_update_client(vs, has_dirty); /* vs might be free()ed here */ } @@ -252,6 +252,12 @@ struct VncJob QTAILQ_ENTRY(VncJob) next; }; +typedef enum { + VNC_STATE_UPDATE_NONE, + VNC_STATE_UPDATE_INCREMENTAL, + VNC_STATE_UPDATE_FORCE, +} VncStateUpdate; + struct VncState { QIOChannelSocket *sioc; /* The underlying socket */ @@ -264,8 +270,8 @@ struct VncState * vnc-jobs-async.c */ VncDisplay *vd; - int need_update; - int force_update; + VncStateUpdate update; /* Most recent pending request from client */ + VncStateUpdate job_update; /* Currently processed by job thread */ int has_dirty; uint32_t features; int absolute; @@ -293,6 +299,18 @@ struct VncState VncClientInfo *info; + /* Job thread bottom half has put data for a forced update + * into the output buffer. This offset points to the end of + * the update data in the output buffer. This lets us determine + * when a force update is fully sent to the client, allowing + * us to process further forced updates. */ + size_t force_update_offset; + /* We allow multiple incremental updates or audio capture + * samples to be queued in output buffer, provided the + * buffer size doesn't exceed this threshold. The value + * is calculating dynamically based on framebuffer size + * and audio sample settings in vnc_update_throttle_offset() */ + size_t throttle_output_offset; Buffer output; Buffer input; /* current output mode information */ @@ -506,8 +524,8 @@ gboolean vnc_client_io(QIOChannel *ioc, GIOCondition condition, void *opaque); -ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen); -ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen); +size_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen); +size_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen); /* Protocol I/O functions */ void vnc_write(VncState *vs, const void *data, size_t len); @@ -526,7 +544,7 @@ uint32_t read_u32(uint8_t *data, size_t offset); /* Protocol stage functions */ void vnc_client_error(VncState *vs); -ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp); +size_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp); void start_client_init(VncState *vs); void start_auth_vnc(VncState *vs); |