summaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
Diffstat (limited to 'ui')
-rw-r--r--ui/cocoa.m27
-rw-r--r--ui/cursor.c6
-rw-r--r--ui/gtk.c5
-rw-r--r--ui/input.c2
-rw-r--r--ui/sdl2.c79
-rw-r--r--ui/sdl_zoom_template.h8
-rw-r--r--ui/spice-core.c14
-rw-r--r--ui/spice-display.c6
-rw-r--r--ui/trace-events7
-rw-r--r--ui/vnc-auth-sasl.c16
-rw-r--r--ui/vnc-auth-sasl.h5
-rw-r--r--ui/vnc-jobs.c5
-rw-r--r--ui/vnc.c322
-rw-r--r--ui/vnc.h28
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;
}
diff --git a/ui/gtk.c b/ui/gtk.c
index 342e96fbe9..f3b7567984 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -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);
}
}
diff --git a/ui/sdl2.c b/ui/sdl2.c
index 8718cf36b5..89c6a2633c 100644
--- a/ui/sdl2.c
+++ b/ui/sdl2.c
@@ -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);
diff --git a/ui/vnc.c b/ui/vnc.c
index 9f8d5a1b1f..665a143578 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -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 */
}
diff --git a/ui/vnc.h b/ui/vnc.h
index 694cf32ca9..0c33a5f7fe 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -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);