ui/sdl2.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 3 deletions(-)
Hi all,
This is an RFC because the change touches the defaults for relative
pointer handling in the SDL2 UI backend; I would like feedback on the
overall direction before posting a non-RFC version.
Motivation
----------
SDL_SetRelativeMouseMode(SDL_TRUE), which ui/sdl2.c currently enables
on the grab paths via sdl_hide_cursor(), switches SDL on X11/XInput2
to consume only XI_RawMotion events. X servers that can only inject
MotionNotify (VNC via XTestFakeMotionEvent, nested X sessions, some
remote desktop setups) therefore never deliver input to the guest once
it enters a relative-pointer grab. We hit this in practice when
running qemu-system-arm with the SDL display forwarded through VNC
for remote development.
The environments can't be distinguished at runtime: both local X11
and a VNC-forwarded X session report "x11" from
SDL_GetCurrentVideoDriver(). Upstream SDL issues about this
(libsdl-org/SDL#1116, #4206) have been open for years with no fix in
sight.
The GTK backend in QEMU already side-steps the problem by doing
manual warp-to-center in gd_motion_event (ui/gtk.c); this patch
applies the same pattern to the SDL2 backend.
What the patch does
-------------------
1. Stop calling sdl_hide_cursor() from sdl_grab_start() and
sdl_mouse_warp(): both paths now 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.
2. handle_mousemotion(): for relative pointing devices in grab mode,
detect the host pointer at the window edge and
SDL_WarpMouseInWindow() it back to the window center, mirroring
ui/gtk.c:gd_motion_event. Edge detection uses window coordinates
(ev->motion.x/y vs scr_w/h) independently of the window-to-surface
scaling introduced for the sent event.
3. Absolute pointing devices and absolute_enabled paths are untouched.
Why one commit
--------------
Splitting "open-code the cursor hide" from "add edge-warp" is possible
but (1) alone would immediately regress to "host pointer stuck at
window edge" under relative grab, because SDL_SetWindowGrab by itself
has no wraparound. They are semantically one change, so it is posted
as a single patch.
Points I would especially like feedback on
------------------------------------------
1. Keeping sdl_hide_cursor() around with G_GNUC_UNUSED vs just
deleting it. I kept it for symmetry with sdl_show_cursor() and
so the XI2 rationale has an obvious place to live; I have no
strong preference.
2. Whether the edge-detection threshold (<= 0, >= size - 1) is
agreeable given SDL_SetWindowGrab clamping semantics.
3. Any concerns about the default-behavior change for local-X11
users. In my testing, local-X11 behavior is equivalent: the host
pointer just gets warped by a different code path, while
xrel/yrel delivered to the guest are unchanged.
Tested on
---------
- Linux/X11 (local Xorg), SDL2 2.30.x, qemu-system-arm with a
relative virtio-mouse: pointer is no longer stuck at the window
edge in grab mode; release after quick drag delivers correctly;
absolute (virtio-tablet) path unchanged.
- Same host reached via VNC: no regression; mouse input stays
functional inside grab (this is the scenario that motivated the
change).
Not tested: macOS Cocoa, Windows, Wayland-only setups. The change
does not rely on XI2 specifics so behavior there should at most match
the previous SDL_SetRelativeMouseMode path, but feedback from anyone
with those hosts would be very welcome.
Thanks,
liuhongchao
---
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
#/******本邮件及其附件含有小米公司的保密信息,仅限于发送给上面地址中列出的个人或群组。禁止任何其他人以任何形式使用(包括但不限于全部或部分地泄露、复制、或散发)本邮件中的信息。如果您错收了本邮件,请您立即电话或邮件通知发件人并删除本邮件! This e-mail and its attachments contain confidential information from XIAOMI, which is intended only for the person or entity whose address is listed above. Any use of the information contained herein in any way (including, but not limited to, total or partial disclosure, reproduction, or dissemination) by persons other than the intended recipient(s) is prohibited. If you receive this e-mail in error, please notify the sender by phone or email immediately and delete it!******/#
From: Hongchao Liu <liuhongchao@xiaomi.com>
Resend of an earlier RFC [1] which unfortunately reached qemu-devel
with a GB2312 charset (due to a corporate e-mail footer my previous
mailer silently appended in Chinese), so patchew could not parse the
patch ("error: cannot convert from gb2312 to UTF-8"). This v2 is the
same code change, re-sent via a mailer that preserves UTF-8 and does
not inject any footer; no source changes relative to v1.
v1: https://lists.nongnu.org/archive/html/qemu-devel/2026-05/msg01701.html
Summary (unchanged from v1):
SDL_SetRelativeMouseMode(SDL_TRUE), which ui/sdl2.c currently enables
on the grab paths via sdl_hide_cursor(), switches SDL on X11/XInput2
to consume only XI_RawMotion events. X servers that can only inject
MotionNotify (VNC via XTestFakeMotionEvent, nested X sessions, some
remote desktop setups) therefore never deliver input to the guest once
it enters a relative-pointer grab. The GTK backend in QEMU already
side-steps the problem by doing manual warp-to-center in gd_motion_event
(ui/gtk.c); this patch applies the same pattern to the SDL2 backend.
Points I would especially like feedback on:
1. Keeping sdl_hide_cursor() around with G_GNUC_UNUSED vs just
deleting it. I kept it for symmetry with sdl_show_cursor() and so
the XI2 rationale has an obvious place to live; I have no strong
preference.
2. Whether the edge-detection threshold (<= 0, >= size - 1) is
agreeable given SDL_SetWindowGrab clamping semantics.
3. Any concerns about the default-behavior change for local-X11
users. In my testing, local-X11 behavior is equivalent: the host
pointer just gets warped by a different code path, while xrel/yrel
delivered to the guest are unchanged.
Tested on:
- Linux/X11 (local Xorg), SDL2 2.30.x, qemu-system-arm with a
relative virtio-mouse: pointer is no longer stuck at the window
edge in grab mode; release after quick drag delivers correctly;
absolute (virtio-tablet) path unchanged.
- Same host reached via VNC: no regression; mouse input stays
functional inside grab.
Not tested: macOS Cocoa, Windows, Wayland-only setups.
Hongchao Liu (1):
ui/sdl2: replace SDL relative mouse mode with manual warp-to-center
ui/sdl2.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 61 insertions(+), 3 deletions(-)
--
2.34.1
From: Hongchao Liu <liuhongchao@xiaomi.com>
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 <liuhongchao@xiaomi.com>
---
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
© 2016 - 2026 Red Hat, Inc.