From d20904e3afb6a2bff9e5acf844abbef3f9e9d4a3 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Mon, 10 Aug 2026 19:29:22 +1000 Subject: [PATCH] [client] input: stop absolute cursor feedback X11's legacy guest-position synchronization warps the host cursor to delayed guest updates. With absolute input this feeds old positions back through cooked motion and pulls the cursor away from window edges. Skip display-server guest synchronization when the active input is absolute. Relative input and explicit uncapture alignment are unchanged. --- client/src/core.c | 5 ++++ client/tests/CMakeLists.txt | 1 + client/tests/mouse_test.c | 50 +++++++++++++++++++++++++++++-------- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/client/src/core.c b/client/src/core.c index a44f7315..9363b371 100644 --- a/client/src/core.c +++ b/client/src/core.c @@ -642,6 +642,11 @@ void core_stopFrameThread(void) void core_handleGuestMouseUpdate(void) { + /* The local cursor is authoritative with absolute input. Sending delayed + * guest positions back to the display server creates a warp feedback loop. */ + if (lgInput_supports(LG_INPUT_SUPPORT_MOUSE_ABSOLUTE)) + return; + struct DoublePoint localPos; if (!util_guestCurToLocal(&localPos)) { diff --git a/client/tests/CMakeLists.txt b/client/tests/CMakeLists.txt index 4dc5e96b..bd6bcdf1 100644 --- a/client/tests/CMakeLists.txt +++ b/client/tests/CMakeLists.txt @@ -120,6 +120,7 @@ set(MOUSE_CASES surface-exit exit-immediate exit-guest + absolute-sync exit-reentry view-immediate capture-pending diff --git a/client/tests/mouse_test.c b/client/tests/mouse_test.c index 6bd6774a..bf517699 100644 --- a/client/tests/mouse_test.c +++ b/client/tests/mouse_test.c @@ -262,9 +262,8 @@ void app_mouseTrace(const char * file, unsigned int line, static bool inputSupports(void * opaque, LG_InputSupport support) { - (void)opaque; - (void)support; - return false; + return support == LG_INPUT_SUPPORT_MOUSE_ABSOLUTE && + opaque && *(const bool *)opaque; } static bool inputKey(void * opaque, int key) @@ -281,6 +280,17 @@ static bool inputMouseMotion(void * opaque, int32_t x, int32_t y) return true; } +static bool inputMousePosition(void * opaque, uint32_t x, uint32_t y, + uint32_t width, uint32_t height) +{ + (void)opaque; + (void)x; + (void)y; + (void)width; + (void)height; + return true; +} + static bool inputMouseButton(void * opaque, unsigned int button) { (void)opaque; @@ -289,13 +299,14 @@ static bool inputMouseButton(void * opaque, unsigned int button) } static const LG_InputOps inputOps = { - .name = "test", - .supports = inputSupports, - .keyDown = inputKey, - .keyUp = inputKey, - .mouseMotion = inputMouseMotion, - .mousePress = inputMouseButton, - .mouseRelease = inputMouseButton, + .name = "test", + .supports = inputSupports, + .keyDown = inputKey, + .keyUp = inputKey, + .mouseMotion = inputMouseMotion, + .mousePosition = inputMousePosition, + .mousePress = inputMouseButton, + .mouseRelease = inputMouseButton, }; static void reset(void) @@ -482,6 +493,24 @@ static void testExitGuest(void) CHECK(count(EV_GUEST) == 0); } +static void testAbsoluteSync(void) +{ + reset(); + setLocal(50, 50); + + core_handleGuestMouseUpdate(); + CHECK(count(EV_GUEST) == 1); + + bool absolute = true; + lgInput_setFallback(&inputOps, &absolute); + m.count = 0; + + core_handleGuestMouseUpdate(); + CHECK(count(EV_GUEST) == 0); + + lgInput_setFallback(&inputOps, NULL); +} + static void testExitReentry(void) { startExit(); @@ -895,6 +924,7 @@ static const struct Test tests[] = { { "surface-exit" , testSurfaceExit }, { "exit-immediate" , testExitImmediate}, { "exit-guest" , testExitGuest }, + { "absolute-sync" , testAbsoluteSync }, { "exit-reentry" , testExitReentry }, { "view-immediate" , testViewImmediate}, { "capture-pending" , testCapWait },