QEMU-Devel Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: "刘洪超 via qemu development" <qemu-devel@nongnu.org>
To: "qemu-devel@nongnu.org" <qemu-devel@nongnu.org>
Cc: "marcandre.lureau@redhat.com" <marcandre.lureau@redhat.com>
Subject: [RFC PATCH] ui/sdl2: replace SDL relative mouse mode with manual warp-to-center
Date: Sat, 9 May 2026 02:52:44 +0000	[thread overview]
Message-ID: <3d86e8cfc12040eca14364739b0a35db@xiaomi.com> (raw)

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!******/#

             reply	other threads:[~2026-05-09  7:32 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-09  2:52 刘洪超 via qemu development [this message]
2026-05-12  2:57 ` [RFC PATCH v2 0/1] ui/sdl2: replace SDL relative mouse mode with manual warp-to-center Hongchao Liu
2026-05-12  2:57 ` [RFC PATCH v2 1/1] " Hongchao Liu

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=3d86e8cfc12040eca14364739b0a35db@xiaomi.com \
    --to=qemu-devel@nongnu.org \
    --cc=liuhongchao@xiaomi.com \
    --cc=marcandre.lureau@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox