From: Hongchao Liu <[email protected]> SDL2's SDL_SetRelativeMouseMode(SDL_TRUE) is the standard way to handle relative pointing devices in grab mode: SDL itself warps the host pointer back to the window center every frame, so xrel/yrel values in SDL_MOUSEMOTION events always remain meaningful and the guest cursor never gets stuck at an edge.
However, under X11 with XInput2 (the default on most Linux desktops) this relative mode switches SDL to consume only XI_RawMotion events. Pointer-injection servers such as VNC (via XTestFakeMotionEvent), nested X servers and some remote desktop setups never produce XI_RawMotion, so mouse input breaks completely under those environments as soon as the guest enters relative-input grab mode. SDL does not expose a way to detect this at runtime (both local X11 and a VNC-forwarded X session report "x11" from SDL_GetCurrentVideoDriver), and upstream SDL issues tracking this (libsdl-org/SDL#1116, #4206) have stayed open for years. Stop enabling SDL_SetRelativeMouseMode on the grab paths and replace the automatic warping it provides with the approach already used by the GTK backend (ui/gtk.c gd_motion_event): when the host pointer reaches the window edge, warp it back to the window center explicitly, letting subsequent motion events again produce non-zero xrel/yrel. Specifically: - sdl_hide_cursor() is no longer called from sdl_grab_start() or sdl_mouse_warp(); both paths open-code the cursor hide so we never enter SDL_SetRelativeMouseMode(SDL_TRUE). sdl_hide_cursor() is retained as the counterpart of sdl_show_cursor() and marked G_GNUC_UNUSED. - In handle_mousemotion(), when we are in a grab for a relative pointing device, detect edge contact using the window coordinates (ev->motion.x/y vs scr_w/h) and SDL_WarpMouseInWindow() the pointer to the window center. Window coordinates are used regardless of the window-to-surface scaling applied to the sent event, because we need to detect the host pointer hitting the SDL window edge. - Absolute pointing devices and absolute_enabled paths are untouched. Tested on Linux/X11 with a relative virtio-mouse: the guest cursor can be pushed in every direction after repeatedly entering and exiting grab, and quick drags to the window edge remain responsive. Behavior for absolute pointing devices (virtio-tablet, qemu-keymap kbd) is unchanged. Signed-off-by: Hongchao Liu <[email protected]> --- ui/sdl2.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/ui/sdl2.c b/ui/sdl2.c index 5dd612d9a6..faf610281c 100644 --- a/ui/sdl2.c +++ b/ui/sdl2.c @@ -212,7 +212,19 @@ static void sdl_update_caption(struct sdl2_console *scon) } } -static void sdl_hide_cursor(struct sdl2_console *scon) +/* + * sdl_hide_cursor - hide cursor and optionally enable SDL relative mouse mode. + * + * NOTE: This function is intentionally NOT called from sdl_grab_start() or + * sdl_mouse_warp() because SDL_SetRelativeMouseMode(SDL_TRUE) switches SDL2 + * on XI2-based X servers to consume only XI_RawMotion events. Pointer- + * injection servers (VNC via XTestFakeMotionEvent, nested X, some remote + * desktop setups) never produce XI_RawMotion, so mouse input would break + * under those servers. Those call sites set the cursor state inline + * instead. This function is retained as the counterpart of + * sdl_show_cursor() and for potential future use. + */ +static void G_GNUC_UNUSED sdl_hide_cursor(struct sdl2_console *scon) { if (scon->opts->has_show_cursor && scon->opts->show_cursor) { return; @@ -267,7 +279,16 @@ static void sdl_grab_start(struct sdl2_console *scon) SDL_WarpMouseInWindow(scon->real_window, guest_x, guest_y); } } else { - sdl_hide_cursor(scon); + /* + * Directly set cursor state, avoid calling sdl_hide_cursor + * which would enable SDL_SetRelativeMouseMode(SDL_TRUE) and + * break mouse input under XI2-based pointer-injection X servers + * (see the note on sdl_hide_cursor). + */ + if (!(scon->opts->has_show_cursor && scon->opts->show_cursor)) { + SDL_ShowCursor(SDL_DISABLE); + SDL_SetCursor(sdl_cursor_hidden); + } } SDL_SetWindowGrab(scon->real_window, SDL_TRUE); gui_grab = 1; @@ -526,6 +547,34 @@ static void handle_mousemotion(SDL_Event *ev) dy = (int64_t)ev->motion.yrel * surf_h / scr_h; if (gui_grab || qemu_input_is_absolute(scon->dcl.con) || absolute_enabled) { sdl_send_mouse_event(scon, dx, dy, x, y, ev->motion.state); + + /* + * For relative pointing devices in grab mode we do not enable + * SDL_SetRelativeMouseMode, because under XI2-based X servers + * that switches SDL to consume only XI_RawMotion events, which + * some pointer-injection servers (VNC via XTestFakeMotionEvent, + * nested X, ...) never produce. + * + * Without relative mode, SDL_SetWindowGrab clamps the host + * pointer at the window edge without wrapping, so xrel/yrel + * become zero once the pointer hits a side and the guest + * cursor gets stuck in that direction. + * + * Mirror the approach used by the GTK backend (ui/gtk.c): + * when the pointer reaches any window edge, warp it back to + * the window center so subsequent motion can generate deltas + * in every direction again. Edge detection uses window + * coordinates (ev->motion.x/y, scr_w/h) regardless of the + * window-to-surface scaling applied above. + */ + if (!qemu_input_is_absolute(scon->dcl.con) && !absolute_enabled + && gui_grab) { + if (ev->motion.x <= 0 || ev->motion.x >= scr_w - 1 || + ev->motion.y <= 0 || ev->motion.y >= scr_h - 1) { + SDL_WarpMouseInWindow(scon->real_window, + scr_w / 2, scr_h / 2); + } + } } } @@ -748,7 +797,16 @@ static void sdl_mouse_warp(DisplayChangeListener *dcl, } } } else if (gui_grab) { - sdl_hide_cursor(scon); + /* + * Directly set cursor state, avoid calling sdl_hide_cursor + * which would enable SDL_SetRelativeMouseMode(SDL_TRUE) and + * break mouse input under XI2-based pointer-injection X servers + * (see the note on sdl_hide_cursor). + */ + if (!(scon->opts->has_show_cursor && scon->opts->show_cursor)) { + SDL_ShowCursor(SDL_DISABLE); + SDL_SetCursor(sdl_cursor_hidden); + } } guest_cursor = on; guest_x = x, guest_y = y; -- 2.34.1
