Merge PR #18 visible-cursor tracking with serial-aware mouse warps

This commit is contained in:
lucannez64 committed 2026-10-17 00:26:47 +02:00
commit 698d295b6d
5 files changed
+53 -19

No files matched your search

+4
View File
@@ -299,3 +299,7 @@ The X11 Chromium presenter searches shared Core Animation frame slots by surface
`tools/riot-remote-frame-ownership-check.m` exercises the cached-frame path without creating a display or window. `tools/build-riot-x11-runtime.py` builds this probe. For an unidentified overlay, `python3 tools/trace-riot-windows.py --output build/riot-window-trace.jsonl` passively records Riot/League windows, including unmanaged windows and child stacking, for one hour. It requires the host `python-xlib` package and does not send input.
If a black fullscreen game has only its normal Metal child and becomes visible after removing and restoring fullscreen, inspect the initial fullscreen transition. Automatic fullscreen now requires the window to cover an active monitor, so temporary 1x1 startup windows and ordinary windowed sizes are not promoted. Explicit Cocoa fullscreen requests are preserved. `riot-fullscreen-geometry-check` (built by the X11 builder) checks the policy against active XRandR monitor geometry using only read requests; the next game launch is still required to validate the startup sequence.
### Visible cursor while relative on Xwayland (PR #18)
When Xwayland does not honour warps, the virtual cursor follows real pointer motion and application warps leave it in place. Expected warp echoes are still discarded using their request serial and coordinates. This keeps clicks and camera edge panning aligned with the visible cursor on GNOME/gamescope. The contributor confirmed this path in a gamescope practice-tool session; plain GNOME and NVIDIA still require manual verification.
+13 -7
View File
@@ -2312,7 +2312,7 @@ index 18189404..12cde429 100644
+- (NSRect) frameForRootRect: (NSRect) rect;
@end
diff --git a/AppKit/X11.backend/X11Display.m b/AppKit/X11.backend/X11Display.m
index 5ae29bf4..203913cf 100644
index 5ae29bf4..47fb6552 100644
--- a/AppKit/X11.backend/X11Display.m
+++ b/AppKit/X11.backend/X11Display.m
@@ -48,12 +48,271 @@
@@ -3810,7 +3810,7 @@ index 5ae29bf4..203913cf 100644
+ * cursor follows and the warp's echo is dropped. Not honoured on Xwayland with a visible cursor. */
+ unsigned long serial = NextRequest(_display);
+ unsigned queued = [self warpPointerToRootX: x y: y];
+ if (!X11RelativeMouseWarp(&_relativeMouse, position.x, position.y, queued, serial))
+ if (!X11RelativeMouseWarp(&_relativeMouse, position.x, position.y, queued, serial, !_isXwayland || _cursorHidden))
+ [self suspendRelativeMouse];
+ return;
+ }
@@ -4279,10 +4279,10 @@ index 0e91a5d1..1e4cec1b 100644
- (void) selectionNotify: (XSelectionEvent *) event {
diff --git a/AppKit/X11.backend/X11RelativeMouse.h b/AppKit/X11.backend/X11RelativeMouse.h
new file mode 100644
index 0000000..e558e1f
index 0000000..31fa646
--- /dev/null
+++ b/AppKit/X11.backend/X11RelativeMouse.h
@@ -0,0 +1,183 @@
@@ -0,0 +1,189 @@
+/* darling-lol: decision logic for macOS-style relative mouse mode in the X11 backend.
+ *
+ * While CGAssociateMouseAndMouseCursorPosition(false) is in effect, macOS freezes the cursor: physical motion
@@ -4396,9 +4396,11 @@ index 0000000..e558e1f
+}
+
+static inline bool X11RelativeMouseWarp(X11RelativeMouse *mouse, double x, double y, unsigned queuedEvents,
+ unsigned long serial) {
+ mouse->virtualX = x;
+ mouse->virtualY = y;
+ unsigned long serial, bool warpsHonoured) {
+ if (warpsHonoured) {
+ mouse->virtualX = x;
+ mouse->virtualY = y;
+ }
+ return X11RelativeMouseExpectEcho(mouse, X11RelativeMouseWarpCoordinate(x), X11RelativeMouseWarpCoordinate(y),
+ queuedEvents, serial);
+}
@@ -4441,6 +4443,10 @@ index 0000000..e558e1f
+ result.deltaY = rootY - mouse->physicalY;
+ mouse->physicalX = rootX;
+ mouse->physicalY = rootY;
+ if (!warpsHonoured) {
+ mouse->virtualX = rootX;
+ mouse->virtualY = rootY;
+ }
+ result.post = result.deltaX != 0.0 || result.deltaY != 0.0;
+ int warpX = X11RelativeMouseWarpCoordinate(mouse->virtualX);
+ int warpY = X11RelativeMouseWarpCoordinate(mouse->virtualY);
+3 -2
View File
@@ -153,9 +153,10 @@ int main(void) {
X11RelativeMouse *relative = slot(d,"_relativeMouse");
X11RelativeMouseBegin(relative,3900,1000);
assert(CGWarpMouseCursorPosition(CGPointMake(100,200)) == 0);
assert(relative->virtualX == 3940 && relative->virtualY == 1010);
BOOL honoured = !*(BOOL *)slot(d,"_isXwayland") || *(BOOL *)slot(d,"_cursorHidden");
assert(relative->virtualX == (honoured ? 3940 : 3900) && relative->virtualY == (honoured ? 1010 : 1000));
NSPoint location = [d mouseLocation];
assert(location.x == 100 && location.y == 1960);
assert(location.x == (honoured ? 100 : 60) && location.y == (honoured ? 1960 : 1970));
X11RelativeMouseMotion motion = X11RelativeMouseHandleMotion(relative,3907,1009,false,NextRequest([d display]));
assert(motion.deltaX == 7 && motion.deltaY == 9);
X11RelativeMouseEnd(relative);
+1 -1
View File
@@ -79,7 +79,7 @@ int main(int argc, char **argv) {
X11RelativeMouseClamp(&m, &x, &y);
/* Send the raw target here to verify the X server clips it exactly as the backend does. */
serial = NextRequest(d); queued = warp(d, 3900, -100);
X11RelativeMouseWarp(&m, x, y, queued, serial);
X11RelativeMouseWarp(&m, x, y, queued, serial, true);
assert(XQueryPointer(d, win, &root, &child, &rx, &ry, &wx, &wy, &state));
assert(rx == 3839 && ry == 24 && m.virtualX == rx && m.virtualY == ry);
r = handle(&m, nextMotion(d));
+32 -9
View File
@@ -47,7 +47,7 @@ static void xorgHonouredWarps(void) {
r = motion(&m, 512, 400, true);
assert(r.deltaX == 12 && r.rewarp);
X11RelativeMouseExpectEcho(&m, r.warpX, r.warpY, 1, 0);
X11RelativeMouseWarp(&m, 524.0, 400.0, 2, 0); /* game: mouseLocation + 2 * delta */
X11RelativeMouseWarp(&m, 524.0, 400.0, 2, 0, true); /* game: mouseLocation + 2 * delta */
assert(m.virtualX == 524.0 && m.physicalX == 512);
r = motion(&m, 500, 400, true);
assert(r.echo && m.physicalX == 500);
@@ -65,16 +65,16 @@ static void xwaylandVisibleCursor(void) {
/* Warps are not honoured: no rewarp, the pointer moves freely, deltas follow it. */
X11RelativeMouseMotion r = motion(&m, 810, 450, false);
assert(r.post && r.deltaX == 10 && !r.rewarp);
X11RelativeMouseWarp(&m, 820.0, 450.0, 1, 0);
X11RelativeMouseWarp(&m, 820.0, 450.0, 1, 0, false);
/* The echo at the target is dropped without moving the physical position... */
r = motion(&m, 820, 450, false);
assert(r.echo && !r.post && m.physicalX == 810 && m.physicalY == 450);
/* ...so the next real motion, at the real absolute position, gives the true delta. */
r = motion(&m, 812, 453, false);
assert(r.post && r.deltaX == 2 && r.deltaY == 3 && !r.rewarp);
assert(m.virtualX == 820.0 && m.virtualY == 450.0);
assert(m.virtualX == 812.0 && m.virtualY == 453.0);
/* An expected echo that never arrives expires after the queued events and cannot eat a later motion. */
X11RelativeMouseWarp(&m, 700.0, 400.0, 1, 0);
X11RelativeMouseWarp(&m, 700.0, 400.0, 1, 0, false);
X11RelativeMouseTick(&m); /* some other X event was the one queued */
assert(m.echoCount == 0);
r = motion(&m, 700, 400, false);
@@ -83,10 +83,32 @@ static void xwaylandVisibleCursor(void) {
puts("PASS: Xwayland visible cursor keeps the physical position across unhonoured warp echoes");
}
static void leagueVisibleCursorWarps(void) {
X11RelativeMouse m = {0};
X11RelativeMouseBegin(&m, 2888, 30);
int x = 2888, y = 30;
for (int step = 0; step < 40; ++step) {
x -= 20;
y += 16;
X11RelativeMouseMotion r = motion(&m, x, y, false);
assert(r.post && r.deltaX == -20 && r.deltaY == 16 && !r.rewarp);
double gameX = m.virtualX + r.deltaX / 2, gameY = m.virtualY + r.deltaY / 2;
X11RelativeMouseWarp(&m, gameX, gameY, 1, 0, false);
r = motion(&m, (int) gameX, (int) gameY, false); /* the unhonoured warp's echo */
assert(r.echo && !r.post);
assert(m.virtualX == x && m.virtualY == y);
}
/* The pointer stops at the right edge of a 3440-pixel screen; the cursor is there too. */
X11RelativeMouseMotion r = motion(&m, 3439, 700, false);
assert(r.post && m.virtualX == 3439 && m.virtualY == 700);
X11RelativeMouseEnd(&m);
puts("PASS: League's visible-cursor warps leave the virtual cursor on the pointer, up to the screen edge");
}
static void gameWarpUpdatesVirtual(void) {
X11RelativeMouse m = {0};
X11RelativeMouseBegin(&m, 100, 100);
X11RelativeMouseWarp(&m, 150.75, 120.5, 0, 0); /* no event: the pointer was already there */
X11RelativeMouseWarp(&m, 150.75, 120.5, 0, 0, true); /* no event: the pointer was already there */
assert(m.virtualX == 150.75 && m.virtualY == 120.5 && m.echoCount == 0);
assert(m.physicalX == 100 && m.physicalY == 100);
/* mouseLocation reports the virtual cursor, Cocoa bottom-up global, for a 1080-pixel screen. */
@@ -114,7 +136,7 @@ static void deltaSign(void) {
static void ungrabRestores(void) {
X11RelativeMouse m = {0};
X11RelativeMouseBegin(&m, 10, 10);
X11RelativeMouseWarp(&m, 40, 40, 3, 0);
X11RelativeMouseWarp(&m, 40, 40, 3, 0, true);
X11RelativeMouseEnd(&m);
assert(!m.active && m.echoCount == 0);
X11RelativeMouseBegin(&m, 60, 70);
@@ -129,7 +151,7 @@ static void echoQueueGrowth(void) {
X11RelativeMouse m = {0};
X11RelativeMouseBegin(&m, 0, 0);
for (int i = 1; i <= X11_RELATIVE_MOUSE_INITIAL_ECHOES + 3; ++i)
X11RelativeMouseWarp(&m, i, 0, 100, 0);
X11RelativeMouseWarp(&m, i, 0, 100, 0, true);
assert(m.echoCount == X11_RELATIVE_MOUSE_INITIAL_ECHOES + 3 && m.echoes[0].x == 1);
X11RelativeMouseMotion r = motion(&m, 6, 0, true);
assert(r.echo && m.echoCount == X11_RELATIVE_MOUSE_INITIAL_ECHOES - 3 && m.echoes[0].x == 7);
@@ -185,9 +207,9 @@ static void confinedWarpAndApplicationReanchor(void) {
double x = 3900, y = -100;
X11RelativeMouseClamp(&m, &x, &y);
assert(x == 3839 && y == 24);
X11RelativeMouseWarp(&m, x, y, 3, 210);
X11RelativeMouseWarp(&m, x, y, 3, 210, true);
/* Another application warp must preserve both outstanding echoes, even at the same position. */
X11RelativeMouseWarp(&m, x, y, 4, 220);
X11RelativeMouseWarp(&m, x, y, 4, 220, true);
assert(m.echoCount == 2);
X11RelativeMouseMotion r = X11RelativeMouseHandleMotion(&m, 3839, 24, true, 210);
assert(r.echo && !r.post && m.echoCount == 1);
@@ -207,6 +229,7 @@ int main(void) {
confinedWarpAndApplicationReanchor();
xorgHonouredWarps();
xwaylandVisibleCursor();
leagueVisibleCursorWarps();
gameWarpUpdatesVirtual();
deltaSign();
ungrabRestores();