From e9a16ef523288a6d9a3453ee53c639259e1ec81f Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Thu, 6 Aug 2026 12:07:39 +1000 Subject: [PATCH] [client] input: add cursor transition diagnostics Add opt-in sequence-numbered tracing across the core and Wayland mouse paths. --- client/displayservers/Wayland/cursor.c | 19 ++ client/displayservers/Wayland/input.c | 249 ++++++++++++++++++++++--- client/displayservers/Wayland/window.c | 17 +- client/include/app.h | 4 + client/src/app.c | 65 ++++++- client/src/config.c | 8 + client/src/core.c | 104 ++++++++++- client/src/main.h | 1 + doc/usage.rst | 2 + 9 files changed, 431 insertions(+), 38 deletions(-) diff --git a/client/displayservers/Wayland/cursor.c b/client/displayservers/Wayland/cursor.c index 876b667b..5c9c35c6 100644 --- a/client/displayservers/Wayland/cursor.c +++ b/client/displayservers/Wayland/cursor.c @@ -31,6 +31,16 @@ #include "common/debug.h" +#define MTRACE(fmt, ...) \ + do \ + { \ + const uint64_t seq = app_mouseSeq(); \ + if (seq) \ + app_mouseTrace(__FILE__, __LINE__, __FUNCTION__, seq, \ + "wl.cursor." fmt, ##__VA_ARGS__); \ + } \ + while (0) + static const uint32_t cursorBitmap[] = { 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000, @@ -184,6 +194,9 @@ void waylandCursorFree(void) void waylandCursorScaleChange(void) { int newScale = waylandScaleCeil(wlWm.scale); + MTRACE("scale old=%d new=%d ratio=%d/%d pointer=%d", + wlWm.cursorScale, newScale, wlWm.scale.num, wlWm.scale.den, + wlWm.cursorId); if (newScale == wlWm.cursorScale) return; @@ -191,7 +204,10 @@ void waylandCursorScaleChange(void) wlWm.cursorSize * newScale, wlWm.shm); if (!new) + { + MTRACE("scale drop=theme new=%d", newScale); return; + } struct wl_surface * old[LG_POINTER_COUNT]; memcpy(old, wlWm.cursors, sizeof(old)); @@ -217,6 +233,9 @@ void waylandSetPointer(LG_DSPointer pointer) wlWm.cursor = wlWm.cursors[pointer]; wlWm.cursorHotX = wlWm.cursorHot[pointer].x; wlWm.cursorHotY = wlWm.cursorHot[pointer].y; + MTRACE("set id=%d surface=%p hot=%d,%d scale=%d entered=%d", + pointer, (void *)wlWm.cursor, wlWm.cursorHotX, wlWm.cursorHotY, + wlWm.cursorScale, wlWm.pointerInSurface); if (wlWm.pointer) wl_pointer_set_cursor(wlWm.pointer, wlWm.pointerEnterSerial, wlWm.cursor, wlWm.cursorHotX, wlWm.cursorHotY); } diff --git a/client/displayservers/Wayland/input.c b/client/displayservers/Wayland/input.c index 402ecd08..2d3248ac 100644 --- a/client/displayservers/Wayland/input.c +++ b/client/displayservers/Wayland/input.c @@ -35,13 +35,39 @@ const double WL_SCROLL_STEP = 15.0; const double WL_HALF_SCROLL_STEP = WL_SCROLL_STEP / 2.0; +#define MTRACE(fmt, ...) \ + do \ + { \ + const uint64_t seq = app_mouseSeq(); \ + if (seq) \ + app_mouseTrace(__FILE__, __LINE__, __FUNCTION__, seq, \ + "wl." fmt, ##__VA_ARGS__); \ + } \ + while (0) + +#define MLOG(seq, fmt, ...) \ + do \ + { \ + if (seq) \ + app_mouseTrace(__FILE__, __LINE__, __FUNCTION__, seq, \ + "wl." fmt, ##__VA_ARGS__); \ + } \ + while (0) + +static uint32_t proxyId(void * proxy) +{ + return proxy ? wl_proxy_get_id(proxy) : 0; +} + // Mouse-handling listeners. static void pointerMotionHandler(void * data, struct wl_pointer * pointer, - uint32_t serial, wl_fixed_t sxW, wl_fixed_t syW) + uint32_t time, wl_fixed_t sxW, wl_fixed_t syW) { wlWm.cursorX = wl_fixed_to_double(sxW); wlWm.cursorY = wl_fixed_to_double(syW); + MTRACE("abs time=%u pos=%.3f,%.3f", time, wlWm.cursorX, + wlWm.cursorY); app_updateCursorPos(wlWm.cursorX, wlWm.cursorY); if (!wlWm.warpSupport && !wlWm.relativePointer) @@ -52,6 +78,10 @@ static void pointerEnterHandler(void * data, struct wl_pointer * pointer, uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW, wl_fixed_t syW) { + MTRACE("enter main=%d surface=%p serial=%u pos=%.3f,%.3f", + surface == wlWm.surface, (void *)surface, serial, + wl_fixed_to_double(sxW), wl_fixed_to_double(syW)); + if (surface != wlWm.surface) return; @@ -81,6 +111,9 @@ static void pointerEnterHandler(void * data, struct wl_pointer * pointer, static void pointerLeaveHandler(void * data, struct wl_pointer * pointer, uint32_t serial, struct wl_surface * surface) { + MTRACE("leave main=%d surface=%p serial=%u", + surface == wlWm.surface, (void *)surface, serial); + if (surface != wlWm.surface) return; @@ -153,18 +186,79 @@ static const struct wl_pointer_listener pointerListener = { .axis = pointerAxisHandler, }; +static void confinedHandler(void * data, + struct zwp_confined_pointer_v1 * pointer) +{ + MTRACE("conf active=1 id=%u", proxyId(pointer)); +} + +static void unconfinedHandler(void * data, + struct zwp_confined_pointer_v1 * pointer) +{ + MTRACE("conf active=0 id=%u", proxyId(pointer)); +} + +static const struct zwp_confined_pointer_v1_listener confinedListener = { + .confined = confinedHandler, + .unconfined = unconfinedHandler, +}; + +static void lockedHandler(void * data, + struct zwp_locked_pointer_v1 * pointer) +{ + MTRACE("lock active=1 id=%u", proxyId(pointer)); +} + +static void unlockedHandler(void * data, + struct zwp_locked_pointer_v1 * pointer) +{ + MTRACE("lock active=0 id=%u", proxyId(pointer)); +} + +static const struct zwp_locked_pointer_v1_listener lockedListener = { + .locked = lockedHandler, + .unlocked = unlockedHandler, +}; + +static struct zwp_confined_pointer_v1 * createConfine( + struct wl_region * region) +{ + struct zwp_confined_pointer_v1 * pointer = + zwp_pointer_constraints_v1_confine_pointer( + wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, region, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + zwp_confined_pointer_v1_add_listener(pointer, &confinedListener, NULL); + return pointer; +} + +static struct zwp_locked_pointer_v1 * createLock(void) +{ + struct zwp_locked_pointer_v1 * pointer = + zwp_pointer_constraints_v1_lock_pointer( + wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + zwp_locked_pointer_v1_add_listener(pointer, &lockedListener, NULL); + return pointer; +} + static void relativePointerMotionHandler(void * data, struct zwp_relative_pointer_v1 *pointer, uint32_t timeHi, uint32_t timeLo, wl_fixed_t dxW, wl_fixed_t dyW, wl_fixed_t dxUnaccelW, wl_fixed_t dyUnaccelW) { - wlWm.cursorX += wl_fixed_to_double(dxW); - wlWm.cursorY += wl_fixed_to_double(dyW); + const double dx = wl_fixed_to_double(dxW); + const double dy = wl_fixed_to_double(dyW); + wlWm.cursorX += dx; + wlWm.cursorY += dy; + MTRACE("rel time=%u:%u delta=%.3f,%.3f raw=%.3f,%.3f " + "pos=%.3f,%.3f", timeHi, timeLo, dx, dy, + wl_fixed_to_double(dxUnaccelW), wl_fixed_to_double(dyUnaccelW), + wlWm.cursorX, wlWm.cursorY); app_updateCursorPos(wlWm.cursorX, wlWm.cursorY); app_handleMouseRelative( - wl_fixed_to_double(dxW), - wl_fixed_to_double(dyW), + dx, + dy, wl_fixed_to_double(dxUnaccelW), wl_fixed_to_double(dyUnaccelW)); } @@ -322,21 +416,32 @@ static const struct wl_keyboard_listener keyboardListener = { static void waylandCleanUpPointer(void) { + uint32_t lockId = 0; + uint32_t confId = 0; + uint64_t lockSeq = 0; + uint64_t confSeq = 0; INTERLOCKED_SECTION(wlWm.surfaceLock, { if (wlWm.lockedPointer) { + lockId = proxyId(wlWm.lockedPointer); zwp_locked_pointer_v1_destroy(wlWm.lockedPointer); wlWm.lockedPointer = NULL; + lockSeq = app_mouseSeq(); } if (wlWm.confinedPointer) { + confId = proxyId(wlWm.confinedPointer); zwp_confined_pointer_v1_destroy(wlWm.confinedPointer); wlWm.confinedPointer = NULL; + confSeq = app_mouseSeq(); } }); + MLOG(lockSeq, "lock destroy id=%u why=pointer", lockId); + MLOG(confSeq, "conf destroy id=%u why=pointer", confId); + if (wlWm.relativePointer) { zwp_relative_pointer_v1_destroy(wlWm.relativePointer); @@ -409,6 +514,7 @@ bool waylandInputInit(bool allowNoInput) { if (!wlWm.seat) { + MTRACE("input seat=0 allowNone=%d", allowNoInput); if (allowNoInput) { DEBUG_WARN("Compositor missing wl_seat, input will be disabled"); @@ -439,6 +545,10 @@ bool waylandInputInit(bool allowNoInput) DEBUG_WARN("zwp_keyboard_shortcuts_inhibit_manager_v1 not exported by " "compositor, keyboard will not be grabbed"); + MTRACE("input seat=1 warp=%d relMgr=%d constraints=%d", + wlWm.warpSupport, !!wlWm.relativePointerManager, + !!wlWm.pointerConstraints); + wlWm.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!wlWm.xkb) DEBUG_WARN("Failed to initialize xkb, keyboard input will not work"); @@ -480,40 +590,63 @@ void waylandGrabPointer(void) { if (!wlWm.pointer || !wlWm.relativePointerManager || !wlWm.pointerConstraints) + { + MTRACE("grab drop pointer=%d relMgr=%d constraints=%d", + !!wlWm.pointer, !!wlWm.relativePointerManager, + !!wlWm.pointerConstraints); return; + } if (!wlWm.warpSupport && !wlWm.relativePointer) { - wlWm.relativePointer = + struct zwp_relative_pointer_v1 * relative = zwp_relative_pointer_manager_v1_get_relative_pointer( wlWm.relativePointerManager, wlWm.pointer); - zwp_relative_pointer_v1_add_listener(wlWm.relativePointer, + zwp_relative_pointer_v1_add_listener(relative, &relativePointerListener, NULL); + const uint32_t relativeId = proxyId(relative); + wlWm.relativePointer = relative; + MTRACE("rel req id=%u why=grab", relativeId); } + uint32_t confId = 0; + uint64_t confSeq = 0; + bool haveConf = false; + bool haveLock = false; INTERLOCKED_SECTION(wlWm.surfaceLock, { if (!wlWm.confinedPointer && !wlWm.lockedPointer) { - wlWm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer( - wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL, - ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + wlWm.confinedPointer = createConfine(NULL); + confId = proxyId(wlWm.confinedPointer); + confSeq = app_mouseSeq(); } + haveConf = !!wlWm.confinedPointer; + haveLock = !!wlWm.lockedPointer; }); + + if (confId) + MLOG(confSeq, "conf req id=%u why=grab", confId); + else + MTRACE("conf skip conf=%d lock=%d", haveConf, haveLock); } -inline static void internalUngrabPointer(bool lock) +inline static uint32_t internalUngrabPointer(bool lock, uint64_t * traceSeq) { + *traceSeq = 0; if (!wlWm.pointer) - return; + return 0; if (lock) LG_LOCK(wlWm.surfaceLock); + uint32_t confId = 0; if (wlWm.confinedPointer) { + confId = proxyId(wlWm.confinedPointer); zwp_confined_pointer_v1_destroy(wlWm.confinedPointer); wlWm.confinedPointer = NULL; + *traceSeq = app_mouseSeq(); } if (lock) @@ -533,43 +666,65 @@ inline static void internalUngrabPointer(bool lock) app_resyncMouseBasic(); app_handleMouseBasic(); } + + return confId; } void waylandUngrabPointer(void) { - internalUngrabPointer(true); + uint64_t confSeq; + const uint32_t confId = internalUngrabPointer(true, &confSeq); + MLOG(confSeq, "conf destroy id=%u why=ungrab", confId); } void waylandCapturePointer(void) { if (!wlWm.warpSupport) { + MTRACE("capture fallback=confine"); waylandGrabPointer(); return; } + uint32_t confId = 0; + uint32_t lockId = 0; + uint64_t confSeq = 0; + uint64_t lockSeq = 0; INTERLOCKED_SECTION(wlWm.surfaceLock, { if (wlWm.confinedPointer) { + confId = proxyId(wlWm.confinedPointer); zwp_confined_pointer_v1_destroy(wlWm.confinedPointer); wlWm.confinedPointer = NULL; + confSeq = app_mouseSeq(); } - wlWm.lockedPointer = zwp_pointer_constraints_v1_lock_pointer( - wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL, - ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + wlWm.lockedPointer = createLock(); + lockId = proxyId(wlWm.lockedPointer); + lockSeq = app_mouseSeq(); }); + + MLOG(confSeq, "conf destroy id=%u why=capture", confId); + MLOG(lockSeq, "lock req id=%u why=capture", lockId); } void waylandUncapturePointer(void) { + uint32_t lockId = 0; + uint32_t confDropId = 0; + uint32_t confReqId = 0; + uint64_t lockSeq = 0; + uint64_t confDropSeq = 0; + uint64_t confReqSeq = 0; INTERLOCKED_SECTION(wlWm.surfaceLock, { if (wlWm.lockedPointer) { + lockId = proxyId(wlWm.lockedPointer); zwp_locked_pointer_v1_destroy(wlWm.lockedPointer); wlWm.lockedPointer = NULL; + lockSeq = app_mouseSeq(); } /* we need to ungrab the pointer on the following conditions when exiting capture mode: @@ -580,14 +735,18 @@ void waylandUncapturePointer(void) * - if the user has opted to use captureInputOnly mode. */ if (!wlWm.warpSupport || !app_isFormatValid() || app_isCaptureOnlyMode()) - internalUngrabPointer(false); + confDropId = internalUngrabPointer(false, &confDropSeq); else if (wlWm.pointer) { - wlWm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer( - wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL, - ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + wlWm.confinedPointer = createConfine(NULL); + confReqId = proxyId(wlWm.confinedPointer); + confReqSeq = app_mouseSeq(); } }); + + MLOG(lockSeq, "lock destroy id=%u why=uncapture", lockId); + MLOG(confDropSeq, "conf destroy id=%u why=uncapture", confDropId); + MLOG(confReqSeq, "conf req id=%u why=uncapture", confReqId); } void waylandGrabKeyboard(void) @@ -611,13 +770,25 @@ void waylandUngrabKeyboard(void) void waylandWarpPointer(int x, int y, bool exiting) { - if (!wlWm.pointerInSurface || wlWm.lockedPointer) + const int reqX = x; + const int reqY = y; + if (!wlWm.pointerInSurface) + { + MTRACE("warp drop=surface target=%d,%d exit=%d", x, y, exiting); return; + } + + if (wlWm.lockedPointer) + { + MTRACE("warp drop=lock target=%d,%d exit=%d", x, y, exiting); + return; + } LG_LOCK(wlWm.surfaceLock); if (wlWm.lockedPointer) { LG_UNLOCK(wlWm.surfaceLock); + MTRACE("warp drop=lock-race target=%d,%d exit=%d", x, y, exiting); return; } @@ -632,6 +803,9 @@ void waylandWarpPointer(int x, int y, bool exiting) struct wl_region * region = wl_compositor_create_region(wlWm.compositor); wl_region_add(region, x, y, 1, 1); + const uint32_t confId = proxyId(wlWm.confinedPointer); + uint32_t tempId = 0; + uint64_t warpSeq = 0; if (wlWm.confinedPointer) { zwp_confined_pointer_v1_set_region(wlWm.confinedPointer, region); @@ -640,17 +814,19 @@ void waylandWarpPointer(int x, int y, bool exiting) } else { - struct zwp_confined_pointer_v1 * confine; - confine = zwp_pointer_constraints_v1_confine_pointer( - wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, region, - ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + struct zwp_confined_pointer_v1 * confine = createConfine(region); + tempId = proxyId(confine); wl_surface_commit(wlWm.surface); zwp_confined_pointer_v1_destroy(confine); } wl_surface_commit(wlWm.surface); wl_region_destroy(region); + warpSeq = app_mouseSeq(); LG_UNLOCK(wlWm.surfaceLock); + + MLOG(warpSeq, "warp req=%d,%d target=%d,%d exit=%d conf=%u temp=%u", + reqX, reqY, x, y, exiting, confId, tempId); } void waylandRealignPointer(void) @@ -661,11 +837,24 @@ void waylandRealignPointer(void) void waylandGuestPointerUpdated(double x, double y, double localX, double localY) { - if ( !wlWm.pointer || - !wlWm.warpSupport || - !wlWm.pointerInSurface || - wlWm.lockedPointer ) - return; + const char * drop = NULL; + if (!wlWm.pointer) + drop = "pointer"; + else if (!wlWm.warpSupport) + drop = "support"; + else if (!wlWm.pointerInSurface) + drop = "surface"; + else if (wlWm.lockedPointer) + drop = "lock"; + if (drop) + { + MTRACE("guest drop=%s guest=%.3f,%.3f local=%.3f,%.3f", + drop, x, y, localX, localY); + return; + } + + MTRACE("guest warp guest=%.3f,%.3f local=%.3f,%.3f", + x, y, localX, localY); waylandWarpPointer((int) localX, (int) localY, false); } diff --git a/client/displayservers/Wayland/window.c b/client/displayservers/Wayland/window.c index 546e3752..a09edd6b 100644 --- a/client/displayservers/Wayland/window.c +++ b/client/displayservers/Wayland/window.c @@ -29,15 +29,30 @@ #include "common/debug.h" #include "common/event.h" +#define MTRACE(fmt, ...) \ + do \ + { \ + const uint64_t seq = app_mouseSeq(); \ + if (seq) \ + app_mouseTrace(__FILE__, __LINE__, __FUNCTION__, seq, \ + "wl.window." fmt, ##__VA_ARGS__); \ + } \ + while (0) + // Surface-handling listeners. static void setScale(struct WaylandScale newScale) { + const struct WaylandScale oldScale = wlWm.scale; wlWm.scale = newScale; wlWm.fractionalScale = waylandScaleIsFractional(newScale); wlWm.needsResize = true; - if (wlWm.desktop->configured()) + const bool configured = wlWm.desktop->configured(); + MTRACE("scale old=%d/%d new=%d/%d configured=%d", + oldScale.num, oldScale.den, newScale.num, newScale.den, configured); + + if (configured) { waylandCursorScaleChange(); app_invalidateWindow(true); diff --git a/client/include/app.h b/client/include/app.h index b0b5d003..19ade83d 100644 --- a/client/include/app.h +++ b/client/include/app.h @@ -58,6 +58,10 @@ bool app_isCaptureMode(void); bool app_isCaptureOnlyMode(void); bool app_isFormatValid(void); bool app_isOverlayMode(void); +uint64_t app_mouseSeq(void); +void app_mouseTrace(const char * file, unsigned int line, + const char * function, uint64_t seq, const char * format, ...) + __attribute__((format (printf, 5, 6))); void app_updateCursorPos(double x, double y); void app_updateMouseState(void); void app_getMouseState(LG_MouseState * state); diff --git a/client/src/app.c b/client/src/app.c index e7f34384..a217efe9 100644 --- a/client/src/app.c +++ b/client/src/app.c @@ -37,12 +37,25 @@ #include "cimgui.h" -#include +#include #include +#include +#include +#include #include #define SHADER_MOUSE_VALID (UINT32_C(1) << 31) +#define MTRACE(fmt, ...) \ + do \ + { \ + const uint64_t seq = app_mouseSeq(); \ + if (seq) \ + app_mouseTrace(__FILE__, __LINE__, __FUNCTION__, seq, \ + "app." fmt, ##__VA_ARGS__); \ + } \ + while (0) + bool app_isRunning(void) { const enum RunState state = app_getState(); @@ -56,6 +69,33 @@ bool app_isCaptureMode(void) return g_cursor.grab; } +uint64_t app_mouseSeq(void) +{ + static atomic_uint_fast64_t seq; + + if (!g_params.mouseTrace) + return 0; + + return atomic_fetch_add_explicit(&seq, 1, memory_order_relaxed) + 1; +} + +void app_mouseTrace(const char * file, unsigned int line, + const char * function, uint64_t seq, const char * format, ...) +{ + char message[1024]; + va_list ap; + va_start(ap, format); + const int result = vsnprintf(message, sizeof(message), format, ap); + va_end(ap); + if (result < 0) + return; + + flockfile(stderr); + debug_info(file, line, function, "Mouse %06" PRIu64 ": %s", seq, + message); + funlockfile(stderr); +} + bool app_isCaptureOnlyMode(void) { return g_params.captureInputOnly; @@ -93,6 +133,9 @@ void app_updateCursorPos(double x, double y) g_cursor.pos.y = y; g_cursor.valid = true; + MTRACE("pos pos=%.3f,%.3f inWin=%d inView=%d grab=%d", + x, y, g_cursor.inWindow, g_cursor.inView, g_cursor.grab); + if (app_isOverlayMode()) g_state.io->MousePos = (ImVec2) { x, y }; } @@ -167,6 +210,10 @@ void app_getMouseState(LG_MouseState * state) void app_handleFocusEvent(bool focused) { + MTRACE("focus set=%d old=%d inWin=%d inView=%d grab=%d", + focused, g_state.focused, g_cursor.inWindow, g_cursor.inView, + g_cursor.grab); + if (g_state.focused == focused) return; @@ -211,6 +258,9 @@ void app_handleFocusEvent(bool focused) void app_handleEnterEvent(bool entered) { + MTRACE("enter set=%d inWin=%d inView=%d grab=%d", + entered, g_cursor.inWindow, g_cursor.inView, g_cursor.grab); + if (entered) { g_cursor.inWindow = true; @@ -408,6 +458,9 @@ void app_handleButtonPress(int button) g_cursor.buttons |= (1U << button); app_updateMouseButtons(); + MTRACE("button down=%d buttons=%u inView=%d grab=%d", + button, g_cursor.buttons, g_cursor.inView, g_cursor.grab); + if (app_isOverlayMode()) { int igButton = mapSpiceToImGuiButton(button); @@ -428,6 +481,9 @@ void app_handleButtonRelease(int button) g_cursor.buttons &= ~(1U << button); app_updateMouseButtons(); + MTRACE("button up=%d buttons=%u inView=%d grab=%d", + button, g_cursor.buttons, g_cursor.inView, g_cursor.grab); + if (app_isOverlayMode()) { int igButton = mapSpiceToImGuiButton(button); @@ -599,6 +655,10 @@ void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock) void app_handleMouseRelative(double normx, double normy, double rawx, double rawy) { + MTRACE("rel delta=%.3f,%.3f raw=%.3f,%.3f inWin=%d inView=%d " + "grab=%d", normx, normy, rawx, rawy, g_cursor.inWindow, + g_cursor.inView, g_cursor.grab); + if (app_isOverlayMode()) return; @@ -671,6 +731,9 @@ void app_updateWindowPos(int x, int y) void app_handleResizeEvent(int w, int h, double scale, const struct Border border) { + MTRACE("resize win=%dx%d scale=%.3f border=%d,%d,%d,%d", + w, h, scale, border.left, border.top, border.right, border.bottom); + memcpy(&g_state.border, &border, sizeof(border)); /* don't do anything else if the window dimensions have not changed */ diff --git a/client/src/config.c b/client/src/config.c index 611dd6d7..65385816 100644 --- a/client/src/config.c +++ b/client/src/config.c @@ -402,6 +402,13 @@ static struct Option options[] = .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, + { + .module = "input", + .name = "mouseTrace", + .description = "Enable mouse input diagnostics", + .type = OPTION_TYPE_BOOL, + .value.x_bool = false, + }, { .module = "input", .name = "autoCapture", @@ -727,6 +734,7 @@ bool config_load(int argc, char * argv[]) g_params.mouseSmoothing = option_get_bool("input", "mouseSmoothing" ); g_params.rawMouse = option_get_bool("input", "rawMouse" ); g_params.mouseRedraw = option_get_bool("input", "mouseRedraw" ); + g_params.mouseTrace = option_get_bool("input", "mouseTrace" ); g_params.autoCapture = option_get_bool("input", "autoCapture" ); g_params.captureInputOnly = option_get_bool("input", "captureOnly" ); diff --git a/client/src/core.c b/client/src/core.c index fef44c54..d9e3375f 100644 --- a/client/src/core.c +++ b/client/src/core.c @@ -36,6 +36,16 @@ #define RESIZE_TIMEOUT (10 * 1000) // 10ms +#define MTRACE(fmt, ...) \ + do \ + { \ + const uint64_t seq = app_mouseSeq(); \ + if (seq) \ + app_mouseTrace(__FILE__, __LINE__, __FUNCTION__, seq, \ + "core." fmt, ##__VA_ARGS__); \ + } \ + while (0) + static bool isInView(void) { return @@ -58,6 +68,10 @@ void core_invalidatePointer(bool detectInView) enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE; app_getProp(LG_DS_WARP_SUPPORT, &warpSupport); + MTRACE("invalidate detect=%d inWin=%d inView=%d grab=%d warp=%d " + "support=%d", detectInView, g_cursor.inWindow, g_cursor.inView, + g_cursor.grab, g_cursor.warpState, warpSupport); + if (detectInView) { bool inView = isInView(); @@ -101,12 +115,22 @@ void core_invalidatePointer(bool detectInView) void core_setCursorInView(bool enable) { + MTRACE("view req=%d old=%d focus=%d inWin=%d grab=%d", + enable, g_cursor.inView, g_state.focused, g_cursor.inWindow, + g_cursor.grab); + // if the state has not changed, don't do anything else if (g_cursor.inView == enable) + { + MTRACE("view skip=same value=%d", enable); return; + } if (enable && !g_state.focused) + { + MTRACE("view skip=focus"); return; + } g_cursor.inView = enable; core_invalidatePointer(false); @@ -124,6 +148,10 @@ void core_setGrab(bool enable) void core_setGrabQuiet(bool enable) { + MTRACE("grab req=%d old=%d inWin=%d inView=%d focus=%d", + enable, g_cursor.grab, g_cursor.inWindow, g_cursor.inView, + g_state.focused); + /* we always do this so that at init the cursor is in the right state */ if (g_params.captureInputOnly && g_params.hideMouse) g_state.ds->setPointer(enable ? LG_POINTER_NONE : LG_POINTER_SQUARE); @@ -149,7 +177,8 @@ void core_setGrabQuiet(bool enable) * odd UI behaviour if the user is using focus follows mouse and the window * was focused without the cursor being in window already */ struct DoublePoint local; - util_guestCurToLocal(&local); + const bool valid = util_guestCurToLocal(&local); + MTRACE("grab align valid=%d", valid); core_warpPointer(local.x, local.y, true); if (g_params.grabKeyboard) @@ -177,17 +206,35 @@ void core_setGrabQuiet(bool enable) bool core_warpPointer(int x, int y, bool exiting) { - if ((!g_cursor.inWindow && !exiting) || - app_isOverlayMode() || - g_cursor.warpState == WARP_STATE_OFF) + if (!g_cursor.inWindow && !exiting) + { + MTRACE("warp drop=window target=%d,%d exit=%d", x, y, exiting); return false; + } + + if (app_isOverlayMode()) + { + MTRACE("warp drop=overlay target=%d,%d exit=%d", x, y, exiting); + return false; + } + + if (g_cursor.warpState == WARP_STATE_OFF) + { + MTRACE("warp drop=state target=%d,%d exit=%d", x, y, exiting); + return false; + } if (exiting) g_cursor.warpState = WARP_STATE_OFF; if (g_cursor.pos.x == x && g_cursor.pos.y == y) + { + MTRACE("warp same target=%d,%d exit=%d", x, y, exiting); return true; + } + MTRACE("warp send target=%d,%d exit=%d pos=%.3f,%.3f", + x, y, exiting, g_cursor.pos.x, g_cursor.pos.y); g_state.ds->warpPointer(x, y, exiting); return true; } @@ -366,6 +413,15 @@ void core_updatePositionInfo(void) } done: + MTRACE("geometry src=%dx%d dst=%d,%d,%d,%d win=%dx%d " + "scale=%.4f,%.4f uiScale=%.4f rot=%d/%d border=%d,%d,%d,%d " + "valid=%d", g_state.srcSize.x, g_state.srcSize.y, + g_state.dstRect.x, g_state.dstRect.y, g_state.dstRect.w, + g_state.dstRect.h, g_state.windowW, g_state.windowH, + g_cursor.scale.x, g_cursor.scale.y, g_state.windowScale, + g_state.rotate, g_params.winRotate, g_state.border.left, + g_state.border.top, g_state.border.right, g_state.border.bottom, + g_state.posInfoValid); app_updateMouseState(); atomic_fetch_add(&g_state.lgrResize, 1); } @@ -438,11 +494,26 @@ void core_handleGuestMouseUpdate(void) { struct DoublePoint localPos; if (!util_guestCurToLocal(&localPos)) + { + MTRACE("guest drop=geometry guest=%d,%d hot=%d,%d valid=%d", + g_cursor.guest.x, g_cursor.guest.y, g_cursor.guest.hx, + g_cursor.guest.hy, g_cursor.guest.valid); return; + } - if (app_isOverlayMode() || !g_cursor.inView) + const bool overlay = app_isOverlayMode(); + if (overlay || !g_cursor.inView) + { + MTRACE("guest drop=%s guest=%d,%d local=%.3f,%.3f inWin=%d " + "inView=%d", overlay ? "overlay" : "view", g_cursor.guest.x, + g_cursor.guest.y, localPos.x, localPos.y, g_cursor.inWindow, + g_cursor.inView); return; + } + MTRACE("guest send guest=%d,%d local=%.3f,%.3f inWin=%d inView=%d", + g_cursor.guest.x, g_cursor.guest.y, localPos.x, localPos.y, + g_cursor.inWindow, g_cursor.inView); g_state.ds->guestPointerUpdated( g_cursor.guest.x, g_cursor.guest.y, util_clamp(localPos.x, g_state.dstRect.x, @@ -454,7 +525,11 @@ void core_handleGuestMouseUpdate(void) void core_handleMouseGrabbed(double ex, double ey) { - if (!core_inputEnabled()) + const bool enabled = core_inputEnabled(); + MTRACE("captured delta=%.3f,%.3f enabled=%d", ex, ey, + enabled); + + if (!enabled) return; int x, y; @@ -481,6 +556,10 @@ void core_handleMouseGrabbed(double ex, double ey) void core_handleMouseNormal(double ex, double ey) { + MTRACE("normal delta=%.3f,%.3f guest=%d,%d valid=%d inWin=%d " + "inView=%d", ex, ey, g_cursor.guest.x, g_cursor.guest.y, + g_cursor.guest.valid, g_cursor.inWindow, g_cursor.inView); + // prevent cursor handling outside of capture if the position is not known if (!g_cursor.guest.valid) { @@ -511,6 +590,7 @@ void core_handleMouseNormal(double ex, double ey) } bool testExit = true; + bool didExit = false; const bool inView = isInView(); if (!g_cursor.inView) { @@ -643,11 +723,18 @@ fallback: local.x + move.x >= g_state.dstRect.x + g_state.dstRect.w || local.y + move.y >= g_state.dstRect.y + g_state.dstRect.h) { + didExit = true; local.x += move.x; local.y += move.y; const int tx = (local.x <= 0.0) ? floor(local.x) : ceil(local.x); const int ty = (local.y <= 0.0) ? floor(local.y) : ceil(local.y); + MTRACE("exit local=%.3f,%.3f move=%.3f,%.3f target=%d,%d " + "dst=%d,%d,%d,%d support=%d", local.x - move.x, + local.y - move.y, move.x, move.y, tx, ty, g_state.dstRect.x, + g_state.dstRect.y, g_state.dstRect.w, g_state.dstRect.h, + warpSupport); + switch (warpSupport) { case LG_DS_WARP_NONE: @@ -684,6 +771,7 @@ fallback: else if (warpSupport == LG_DS_WARP_SURFACE && isInView()) { /* regrab the pointer in case the user did not move off the surface */ + MTRACE("regrab pos=%.3f,%.3f", g_cursor.pos.x, g_cursor.pos.y); g_state.ds->grabPointer(); g_cursor.warpState = WARP_STATE_ON; } @@ -715,6 +803,10 @@ fallback: g_cursor.guest.y += y; } + MTRACE("motion delta=%d,%d guest=%d,%d exit=%d test=%d warp=%d", + x, y, g_cursor.guest.x, g_cursor.guest.y, didExit, testExit, + g_cursor.warpState); + if (!purespice_mouseMotion(x, y)) DEBUG_ERROR("failed to send mouse motion message"); } diff --git a/client/src/main.h b/client/src/main.h index bf416b99..0c713f99 100644 --- a/client/src/main.h +++ b/client/src/main.h @@ -227,6 +227,7 @@ struct AppParams const char * windowTitle; const char * appId; bool mouseRedraw; + bool mouseTrace; int mouseSens; bool mouseSmoothing; bool rawMouse; diff --git a/doc/usage.rst b/doc/usage.rst index 69c77b6a..f9de83ff 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -326,6 +326,8 @@ All command line options +------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+ | input:mouseRedraw | | yes | Mouse movements trigger redraws (ignores FPS minimum) | +------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+ + | input:mouseTrace | | no | Enable mouse input diagnostics | + +------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+ | input:autoCapture | | no | Try to keep the mouse captured when needed | +------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+ | input:captureOnly | | no | Only enable input via SPICE if in capture mode |