[client] cursor: improve Wayland hotspot alignment

Center the Wayland diagnostic cursor on its actual pointer hotspot and
preserve it while cursor themes are reloaded for output-scale changes.

Round fractional guest positions to the nearest logical pixel instead of
biasing synchronization toward the top-left corner.

Resynchronize the hardware pointer when a cursor shape changes its
hotspot. Only perform the extra synchronization when the hotspot
actually changes, and update the basic-mode projection at the same time.
This commit is contained in:
Geoffrey McRae
2026-08-06 20:47:21 +10:00
parent e4874ee32d
commit 5ab10614f2
3 changed files with 19 additions and 5 deletions

View File

@@ -158,6 +158,7 @@ bool waylandCursorInit(void)
if (wlWm.cursorSquareBuffer) if (wlWm.cursorSquareBuffer)
{ {
wlWm.cursors[LG_POINTER_SQUARE] = wl_compositor_create_surface(wlWm.compositor); wlWm.cursors[LG_POINTER_SQUARE] = wl_compositor_create_surface(wlWm.compositor);
wlWm.cursorHot[LG_POINTER_SQUARE] = (struct Point) { 2, 2 };
wl_surface_attach(wlWm.cursors[LG_POINTER_SQUARE], wlWm.cursorSquareBuffer, 0, 0); wl_surface_attach(wlWm.cursors[LG_POINTER_SQUARE], wlWm.cursorSquareBuffer, 0, 0);
wl_surface_commit(wlWm.cursors[LG_POINTER_SQUARE]); wl_surface_commit(wlWm.cursors[LG_POINTER_SQUARE]);
} }
@@ -213,6 +214,10 @@ void waylandCursorScaleChange(void)
memcpy(old, wlWm.cursors, sizeof(old)); memcpy(old, wlWm.cursors, sizeof(old));
memset(wlWm.cursors, 0, sizeof(wlWm.cursors)); memset(wlWm.cursors, 0, sizeof(wlWm.cursors));
/* the diagnostic cursor is not part of the cursor theme */
wlWm.cursors[LG_POINTER_SQUARE] = old[LG_POINTER_SQUARE];
old[LG_POINTER_SQUARE] = NULL;
if (wlWm.cursorTheme) if (wlWm.cursorTheme)
wl_cursor_theme_destroy(wlWm.cursorTheme); wl_cursor_theme_destroy(wlWm.cursorTheme);

View File

@@ -21,6 +21,7 @@
#include "wayland.h" #include "wayland.h"
#include <errno.h> #include <errno.h>
#include <math.h>
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
#include <sys/mman.h> #include <sys/mman.h>
@@ -1027,5 +1028,5 @@ void waylandGuestPointerUpdated(double x, double y, double localX, double localY
MTRACE("guest warp guest=%.3f,%.3f local=%.3f,%.3f", MTRACE("guest warp guest=%.3f,%.3f local=%.3f,%.3f",
x, y, localX, localY); x, y, localX, localY);
waylandWarpPointer((int) localX, (int) localY, false); waylandWarpPointer((int) round(localX), (int) round(localY), false);
} }

View File

@@ -1076,6 +1076,7 @@ int main_cursorThread(void * unused)
const bool wasRendered = g_cursor.guest.visible && const bool wasRendered = g_cursor.guest.visible &&
(g_cursor.draw || !g_params.useSpiceInput); (g_cursor.draw || !g_params.useSpiceInput);
bool hotspotChanged = false;
if (pointer.flags & LG_TRANSPORT_POINTER_VISIBLE_VALID) if (pointer.flags & LG_TRANSPORT_POINTER_VISIBLE_VALID)
g_cursor.guest.visible = g_cursor.guest.visible =
@@ -1094,8 +1095,8 @@ int main_cursorThread(void * unused)
continue; continue;
} }
g_cursor.guest.hx = pointer.hx; hotspotChanged =
g_cursor.guest.hy = pointer.hy; g_cursor.guest.hx != pointer.hx || g_cursor.guest.hy != pointer.hy;
if (!RENDERER(onMouseShape, cursorType, pointer.width, pointer.height, if (!RENDERER(onMouseShape, cursorType, pointer.width, pointer.height,
pointer.pitch, pointer.shape)) pointer.pitch, pointer.shape))
{ {
@@ -1103,6 +1104,8 @@ int main_cursorThread(void * unused)
g_state.transportOps->releasePointer(g_state.transport, &pointer); g_state.transportOps->releasePointer(g_state.transport, &pointer);
continue; continue;
} }
g_cursor.guest.hx = pointer.hx;
g_cursor.guest.hy = pointer.hy;
} }
if ((pointer.flags & LG_TRANSPORT_POINTER_COLOR_TRANSFORM) && if ((pointer.flags & LG_TRANSPORT_POINTER_COLOR_TRANSFORM) &&
@@ -1132,9 +1135,14 @@ int main_cursorThread(void * unused)
core_alignToGuest(); core_alignToGuest();
app_resyncMouseBasic(); app_resyncMouseBasic();
} }
core_handleGuestMouseUpdate();
} }
if (hotspotChanged)
app_resyncMouseBasic();
if ((pointer.flags & LG_TRANSPORT_POINTER_POSITION) || hotspotChanged)
core_handleGuestMouseUpdate();
app_updateMouseState(); app_updateMouseState();
g_cursor.redraw = false; g_cursor.redraw = false;
RENDERER(onMouseEvent, RENDERER(onMouseEvent,