Merge upstream 7f0e57f: primary monitor geometry coordinates with Wayland fullscreen and mouse confinement fixes

This commit is contained in:
Andras Laszlo committed 2026-10-16 16:16:02 +02:00
commit 6e6b0d826a
5 files changed
+774 -55

No files matched your search

+31
View File
@@ -412,3 +412,34 @@ and exercises 100 cancellations with both retained and early-released caller
references, without launching or inspecting Riot. A failure prints
`CFHOST_CANCEL_CHECK fail stage=...`; `unexpected-callback` means a completed
lookup arrived instead of the cancellation.
## Primary-monitor geometry regression
After preparing the sources and configuring/building Darling, run:
```sh
python3 scripts/prepare.py --check-only
python3 tools/check-x11-display-geometry.py
cc -std=c11 -Wall -Wextra -Werror tools/test-x11-relative-mouse.c -o build/test-x11-relative-mouse
build/test-x11-relative-mouse
```
The native probe requires the existing per-user AppKit and CoreGraphics overlays
and a running X11/Xwayland display. It rebuilds the changed X11 and CoreGraphics
objects and tests them in a temporary framework overlay, without installing them.
Six `DISPLAY_GEOMETRY ...=pass` lines are required; full output is saved in
`build/pr12-display-check.log`.
It checks the live primary screen's origin, synthetic single-monitor and offset
layouts (including vertically arranged screens and negative Cocoa coordinates),
unmapped window creation/resizing, display IDs after primary-first ordering,
CoreGraphics bounds and spatial queries, absolute and relative warp translation,
relative deltas, both RandR cache-invalidation events, and the root-screen fallback.
Warps are intercepted: the probe does not move the desktop pointer or send game
input. Synthetic layouts exercise coordinate conversion and display API behavior;
they do not simulate physical monitor hotplug or validate League rendering.
Fullscreen policy is unchanged. Manually verify game launch, window placement,
click alignment and edge panning on the affected monitor layout before rollout.
After installing the tested X11 and CoreGraphics binaries locally, the user
reported that everything was working (2026-10-16). Fullscreen policy was unchanged.
+524 -53
View File
@@ -2242,7 +2242,7 @@ index bdc97bdf..139c91eb 100644
_cursor = XcursorLibraryLoadCursor(display, "left_ptr");
return self;
diff --git a/AppKit/X11.backend/X11Display.h b/AppKit/X11.backend/X11Display.h
index 18189404..0eff2187 100644
index 18189404..5b3a34bc 100644
--- a/AppKit/X11.backend/X11Display.h
+++ b/AppKit/X11.backend/X11Display.h
@@ -10,13 +10,14 @@
@@ -2261,7 +2261,7 @@ index 18189404..0eff2187 100644
@interface X11Display : NSDisplay {
Display *_display;
@@ -36,8 +37,20 @@
@@ -36,19 +37,41 @@
NSTimeInterval lastClickTimeStamp;
int clickCount;
X11Cursor *_blankCursor, *_defaultCursor;
@@ -2282,8 +2282,11 @@ index 18189404..0eff2187 100644
+ unsigned char _modifierKeysDown[256];
int _rrEventBase;
NSArray* _lastScreens;
+ // Root-window rectangle of the primary monitor (macOS's global origin).
+ NSRect _primaryRootFrame;
+ BOOL _primaryRootFrameValid;
@@ -45,7 +58,9 @@
@public
XIM _xim;
}
@@ -2293,8 +2296,16 @@ index 18189404..0eff2187 100644
- (void) setWindow: (id) window forID: (XID) i;
- (CGFloat) doubleClickInterval;
- (int) handleError: (XErrorEvent *) errorEvent;
+
+// Conversions between X root coordinates (top-left of the root window, y down)
+// and Cocoa global coordinates (bottom-left of the primary screen, y up).
+- (NSRect) rootRectForFrame: (NSRect) frame;
+- (NSRect) frameForRootRect: (NSRect) rect;
@end
diff --git a/AppKit/X11.backend/X11Display.m b/AppKit/X11.backend/X11Display.m
index 5ae29bf4..8a66b921 100644
index 5ae29bf4..5105e7d8 100644
--- a/AppKit/X11.backend/X11Display.m
+++ b/AppKit/X11.backend/X11Display.m
@@ -48,12 +48,271 @@
@@ -2675,7 +2686,183 @@ index 5ae29bf4..8a66b921 100644
- (void) _enableDetectableAutoRepeat {
int major = XkbMajorVersion, minor = XkbMinorVersion;
XkbStateRec state;
@@ -536,12 +861,53 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -190,6 +515,35 @@ static void socketCallback(CFSocketRef s, CFSocketCallBackType type,
[_lastScreens release];
_lastScreens = nil;
}
+ _primaryRootFrameValid = NO;
+}
+
+/* Cocoa's global origin is the bottom-left corner of the primary screen, with
+ * y up; X's is the top-left corner of the root window, with y down. Screen
+ * frames and window frames are both expressed relative to the primary
+ * monitor's root rectangle. On a single monitor (or any layout whose primary
+ * sits at the root origin and is as tall as the root) this is the plain
+ * root-height flip with no offset. */
+- (NSRect) _primaryRootFrame {
+ if (!_primaryRootFrameValid)
+ [self screens];
+ return _primaryRootFrame;
+}
+
+- (NSRect) rootRectForFrame: (NSRect) frame {
+ NSRect primary = [self _primaryRootFrame];
+ return NSMakeRect(frame.origin.x + NSMinX(primary),
+ NSMaxY(primary) - frame.origin.y - frame.size.height,
+ fmax(frame.size.width, 1.0),
+ fmax(frame.size.height, 1.0));
+}
+
+- (NSRect) frameForRootRect: (NSRect) rect {
+ NSRect primary = [self _primaryRootFrame];
+ return NSMakeRect(rect.origin.x - NSMinX(primary),
+ NSMaxY(primary) - rect.origin.y - rect.size.height,
+ fmax(rect.size.width, 1.0),
+ fmax(rect.size.height, 1.0));
}
- (NSArray *) screens {
@@ -201,25 +555,60 @@ static void socketCallback(CFSocketRef s, CFSocketCallBackType type,
int eventBase, errorBase;
- if (XRRQueryExtension(_display, &eventBase, &errorBase)) {
- XRRScreenResources *screen;
-
+ XRRScreenResources *screen = NULL;
+ if (XRRQueryExtension(_display, &eventBase, &errorBase))
screen = XRRGetScreenResources(_display, DefaultRootWindow(_display));
+
+ if (screen) {
+ RROutput primaryOutput =
+ XRRGetOutputPrimary(_display, DefaultRootWindow(_display));
+ NSRect *rootFrames = calloc(screen->noutput ? screen->noutput : 1,
+ sizeof(NSRect));
+ if (!rootFrames) {
+ XRRFreeScreenResources(screen);
+ return [self _rootScreenFallback];
+ }
+ int primary = -1;
+
+ for (int i = 0; i < screen->noutput; i++) {
+ XRROutputInfo *oinfo =
+ XRRGetOutputInfo(_display, screen, screen->outputs[i]);
+ if (oinfo && oinfo->crtc) {
+ XRRCrtcInfo *crtc =
+ XRRGetCrtcInfo(_display, screen, oinfo->crtc);
+ if (crtc) {
+ rootFrames[i] =
+ NSMakeRect(crtc->x, crtc->y, crtc->width, crtc->height);
+ XRRFreeCrtcInfo(crtc);
+ }
+ }
+ // The XRandR primary, otherwise the first active output.
+ if (!NSIsEmptyRect(rootFrames[i]) &&
+ (primary < 0 || screen->outputs[i] == primaryOutput))
+ primary = i;
+ if (oinfo) XRRFreeOutputInfo(oinfo);
+ }
+
+ // Without any active output, keep the root-height flip.
+ _primaryRootFrame = primary >= 0
+ ? rootFrames[primary]
+ : NSMakeRect(0, 0, DisplayWidth(_display, DefaultScreen(_display)),
+ DisplayHeight(_display, DefaultScreen(_display)));
+ _primaryRootFrameValid = YES;
+
+ // [NSScreen screens] starts with the primary screen, as on macOS.
+ // Display IDs stay tied to the XRandR output index.
NSMutableArray<NSScreen *> *retval =
[NSMutableArray arrayWithCapacity: screen->noutput];
+ NSMutableArray<NSScreen *> *inactive = [NSMutableArray array];
Atom edidAtom = XInternAtom(_display, "EDID", FALSE);
for (int i = 0; i < screen->noutput; i++) {
- XRROutputInfo *oinfo =
- XRRGetOutputInfo(_display, screen, screen->outputs[i]);
NSScreen *nsscreen;
- if (oinfo->crtc) {
- XRRCrtcInfo *crtc =
- XRRGetCrtcInfo(_display, screen, oinfo->crtc);
- NSRect frame =
- NSMakeRect(crtc->x, crtc->y, crtc->width, crtc->height);
+ if (!NSIsEmptyRect(rootFrames[i])) {
+ NSRect frame = [self frameForRootRect: rootFrames[i]];
nsscreen =
[[[NSScreen alloc] initWithFrame: frame
@@ -228,18 +617,17 @@ static void socketCallback(CFSocketRef s, CFSocketCallBackType type,
Atom actualType;
unsigned long nitems, bytesAfter;
int actualFormat;
- unsigned char *prop;
+ unsigned char *prop = NULL;
if (XRRGetOutputProperty(_display, screen->outputs[i], edidAtom,
0, 100, FALSE, FALSE, AnyPropertyType,
&actualType, &actualFormat, &nitems,
&bytesAfter, &prop) == Success) {
- if (prop && nitems > 0)
+ if (prop && actualFormat == 8 && nitems > 0)
[nsscreen setEdid: [NSData dataWithBytes: prop
length: nitems]];
}
-
- XRRFreeCrtcInfo(crtc);
+ if (prop) XFree(prop);
} else {
nsscreen = [[[NSScreen alloc] initWithFrame: NSZeroRect
visibleFrame: NSZeroRect]
@@ -247,25 +635,35 @@ static void socketCallback(CFSocketRef s, CFSocketCallBackType type,
}
[nsscreen setCgDirectDisplayID: (i + 1)];
- [retval addObject: nsscreen];
-
- XRRFreeOutputInfo(oinfo);
+ if (i == primary)
+ [retval insertObject: nsscreen atIndex: 0];
+ else if (!NSIsEmptyRect(rootFrames[i]))
+ [retval addObject: nsscreen];
+ else
+ [inactive addObject: nsscreen];
}
+ [retval addObjectsFromArray: inactive];
+ free(rootFrames);
XRRFreeScreenResources(screen);
NSArray* array = [NSArray arrayWithArray: retval];
_lastScreens = [array retain];
return array;
- } else {
- NSRect frame = NSMakeRect(
- 0, 0, DisplayWidth(_display, DefaultScreen(_display)),
- DisplayHeight(_display, DefaultScreen(_display)));
- return [NSArray
- arrayWithObject: [[[NSScreen alloc] initWithFrame: frame
- visibleFrame: frame]
- autorelease]];
}
+ return [self _rootScreenFallback];
+}
+
+- (NSArray *) _rootScreenFallback {
+ NSRect frame = NSMakeRect(
+ 0, 0, DisplayWidth(_display, DefaultScreen(_display)),
+ DisplayHeight(_display, DefaultScreen(_display)));
+ _primaryRootFrame = frame;
+ _primaryRootFrameValid = YES;
+ NSScreen *nsscreen = [[[NSScreen alloc] initWithFrame: frame
+ visibleFrame: frame] autorelease];
+ [nsscreen setCgDirectDisplayID: 1];
+ return [NSArray arrayWithObject: nsscreen];
}
static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -536,12 +934,53 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
return &layout->layout;
}
@@ -2731,7 +2918,7 @@ index 5ae29bf4..8a66b921 100644
}
- (NSPasteboard *) pasteboardWithName: (NSString *) name {
@@ -675,6 +1041,15 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -675,6 +1114,15 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
blue: 0.60f
alpha: 1.0f];
@@ -2747,7 +2934,7 @@ index 5ae29bf4..8a66b921 100644
NSLog(@"missing color for %@", colorName);
return [NSColor redColor];
}
@@ -688,6 +1063,10 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -688,6 +1136,10 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
}
- (void) hideCursor {
@@ -2758,7 +2945,7 @@ index 5ae29bf4..8a66b921 100644
if (!_blankCursor)
_blankCursor = [[X11Cursor alloc] initBlank];
[self setCursor: _blankCursor];
@@ -695,6 +1074,10 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -695,6 +1147,10 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
- (void) unhideCursor {
NSCursor *current = [NSCursor currentCursor];
@@ -2769,7 +2956,7 @@ index 5ae29bf4..8a66b921 100644
if (current != nil) {
[current push];
[current pop];
@@ -747,12 +1130,28 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -747,12 +1203,28 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
- (void) setCursor: (id) cursor {
CGWindow *window = [[NSApp keyWindow] platformWindow];
@@ -2803,7 +2990,7 @@ index 5ae29bf4..8a66b921 100644
}
}
@@ -921,15 +1320,22 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -921,15 +1393,22 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
}
- (NSPoint) mouseLocation {
@@ -2814,19 +3001,20 @@ index 5ae29bf4..8a66b921 100644
-
- XQueryPointer(_display, root, &root, &child, &root_x, &root_y, &win_x,
- &win_y, &mask);
- int height = DisplayHeight(_display, DefaultScreen(_display));
- return NSMakePoint(root_x, height - root_y);
+ NSRect primary = [self _primaryRootFrame];
+ if (_relativeMouse.active) {
+ /* The cursor is disassociated: report the frozen (virtual) position, as macOS does. */
+ NSPoint result = NSMakePoint(_relativeMouse.virtualX,
+ DisplayHeight(_display, DefaultScreen(_display)) - _relativeMouse.virtualY);
+ NSPoint result = NSMakePoint(_relativeMouse.virtualX - NSMinX(primary),
+ NSMaxY(primary) - _relativeMouse.virtualY);
+ if (rclolTraceOn())
+ rclolTraceMouseLocation(result, YES, YES, (int) _relativeMouse.virtualX, (int) _relativeMouse.virtualY, YES);
+ return result;
+ }
+ uint64_t rclolStampBefore = rclolPointerStamp;
+ BOOL rclolQueried = rclolQueryPointer(_display);
int height = DisplayHeight(_display, DefaultScreen(_display));
- return NSMakePoint(root_x, height - root_y);
+ NSPoint result = NSMakePoint(rclolRootX, height - rclolRootY);
+ NSPoint result = NSMakePoint(rclolRootX - NSMinX(primary), NSMaxY(primary) - rclolRootY);
+ if (rclolTraceOn())
+ rclolTraceMouseLocation(result, rclolQueried, rclolStampBefore != 0 && rclolPointerStamp == rclolStampBefore,
+ rclolRootX, rclolRootY, NO);
@@ -2834,7 +3022,7 @@ index 5ae29bf4..8a66b921 100644
}
- (void) setWindow: (id) window forID: (XID) i {
@@ -948,6 +1354,16 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -948,6 +1427,16 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
// retaining the window causes it to keep popping back up endlessly even after dismissing it
if (lastFocusedWindow && [[lastFocusedWindow platformWindow] windowHandle] == i)
lastFocusedWindow = nil;
@@ -2851,7 +3039,7 @@ index 5ae29bf4..8a66b921 100644
}
}
@@ -962,20 +1378,85 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -962,20 +1451,85 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
{
#ifndef DARLING
[[NSRunLoop currentRunLoop] addInputSource: _inputSource forMode: mode];
@@ -2942,7 +3130,7 @@ index 5ae29bf4..8a66b921 100644
}
- (NSEventModifierFlags) modifierFlagsForState: (unsigned int) state {
@@ -1010,17 +1491,96 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -1010,17 +1564,96 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
return result;
}
@@ -3039,7 +3227,7 @@ index 5ae29bf4..8a66b921 100644
NSEventModifierFlags modifierFlags = [self modifierFlagsForState: ev->xkey.state];
char buf[20] = {0};
KeySym keySym;
@@ -1038,6 +1598,18 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -1038,6 +1671,18 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
buf[strLen] = 0;
}
@@ -3058,10 +3246,13 @@ index 5ae29bf4..8a66b921 100644
id str = [[NSString alloc] initWithCString: buf
encoding: NSUTF8StringEncoding];
NSPoint pos =
@@ -1077,6 +1649,30 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -1077,6 +1722,33 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
break;
case ButtonPress:;
+ if (window && !_confinedWindow) {
+ [self updateMouseConfinementForWindow: window];
+ }
+ if (ev->xbutton.button >= Button4 && ev->xbutton.button <= 7) {
+ pos = [self locationInWindow: window x: ev->xbutton.x y: ev->xbutton.y
+ rootX: ev->xbutton.x_root rootY: ev->xbutton.y_root];
@@ -3089,7 +3280,7 @@ index 5ae29bf4..8a66b921 100644
NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate];
if (now - lastClickTimeStamp < [self doubleClickInterval]) {
@@ -1086,8 +1682,8 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -1086,8 +1758,8 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
}
lastClickTimeStamp = now;
@@ -3100,7 +3291,7 @@ index 5ae29bf4..8a66b921 100644
switch (ev->xbutton.button) {
case Button1:
@@ -1096,10 +1692,6 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -1096,10 +1768,6 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
case Button3:
type = NSRightMouseDown;
break;
@@ -3111,7 +3302,7 @@ index 5ae29bf4..8a66b921 100644
default:
type = NSOtherMouseDown;
}
@@ -1113,14 +1705,22 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -1113,14 +1781,22 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
clickCount: clickCount
deltaX: 0.0
deltaY: 0.0];
@@ -3138,7 +3329,7 @@ index 5ae29bf4..8a66b921 100644
CGFloat deltaY = 0.0;
switch (ev->xbutton.button) {
@@ -1130,14 +1730,6 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -1130,14 +1806,6 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
case Button3:
type = NSRightMouseUp;
break;
@@ -3153,7 +3344,7 @@ index 5ae29bf4..8a66b921 100644
default:
type = NSOtherMouseUp;
}
@@ -1151,7 +1743,9 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -1151,7 +1819,9 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
clickCount: clickCount
deltaX: 0.0
deltaY: deltaY];
@@ -3164,7 +3355,7 @@ index 5ae29bf4..8a66b921 100644
[self postEvent: event atStart: NO];
break;
@@ -1164,45 +1758,82 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -1164,45 +1834,82 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
pos = [window
transformPoint: NSMakePoint(ev->xmotion.x, ev->xmotion.y)];
@@ -3273,7 +3464,7 @@ index 5ae29bf4..8a66b921 100644
mouseEventWithType: type
location: pos
modifierFlags:
@@ -1211,28 +1842,49 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -1211,28 +1918,49 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
clickCount: 1
deltaX: deltaX
deltaY: deltaY];
@@ -3325,7 +3516,7 @@ index 5ae29bf4..8a66b921 100644
NSLog(@"FocusIn");
if ([delegate attachedSheet]) {
[[delegate attachedSheet] makeKeyAndOrderFront: delegate];
@@ -1243,28 +1895,55 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -1243,28 +1971,55 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
checkForAppDeactivation: NO];
lastFocusedWindow = nil;
}
@@ -3383,7 +3574,7 @@ index 5ae29bf4..8a66b921 100644
O2Rect rect = NSMakeRect(ev->xexpose.x, ev->xexpose.y,
ev->xexpose.width, ev->xexpose.height);
@@ -1301,11 +1980,23 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -1301,11 +2056,23 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
break;
case UnmapNotify:
@@ -3411,7 +3602,7 @@ index 5ae29bf4..8a66b921 100644
break;
case MapRequest:
@@ -1321,6 +2012,7 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -1321,6 +2088,7 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
[delegate platformWindow: window
frameChanged: [window frame]
didSize: YES];
@@ -3419,7 +3610,7 @@ index 5ae29bf4..8a66b921 100644
break;
case ConfigureRequest:
@@ -1344,6 +2036,8 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -1344,6 +2112,8 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
break;
case PropertyNotify:
@@ -3428,7 +3619,18 @@ index 5ae29bf4..8a66b921 100644
if ([window respondsToSelector: @selector(propertyNotify:)]) {
[window propertyNotify: &ev->xproperty];
}
@@ -1433,6 +2127,10 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
@@ -1399,7 +2169,9 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
break;
default:
- if (ev->type == _rrEventBase + RRNotify) {
+ if (_rrEventBase && (ev->type == _rrEventBase + RRNotify ||
+ ev->type == _rrEventBase + RRScreenChangeNotify)) {
+ XRRUpdateConfiguration(ev);
[self _invalidateRRCache];
break;
}
@@ -1433,6 +2205,10 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
NSLog(@"Request code: %d:%d", errorEvent->request_code,
errorEvent->minor_code);
NSLog(@"Error code: %d", errorEvent->error_code);
@@ -3439,7 +3641,7 @@ index 5ae29bf4..8a66b921 100644
return 0;
}
@@ -1476,54 +2174,238 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
@@ -1476,54 +2252,271 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
return frame;
}
@@ -3461,7 +3663,11 @@ index 5ae29bf4..8a66b921 100644
+ return [window transformPoint: NSMakePoint(x, y)];
+}
+
+// CGWarpMouseCursorPosition uses the primary screen's top-left, with y down.
- (void) warpMouse: (NSPoint) position {
+ NSRect primary = [self _primaryRootFrame];
+ position.x += NSMinX(primary);
+ position.y += NSMinY(primary);
+ if (rclolTraceOn())
+ rclolTraceRated(&rclolWarpAppChannel, "target=%.1f,%.1f relative=%d/%d virtual=%.1f,%.1f physical=%d,%d "
+ "honoured=%d confined=%lu cachedRoot=%d,%d appActive=%d",
@@ -3481,8 +3687,8 @@ index 5ae29bf4..8a66b921 100644
- position.x, position.y);
- XSync(_display, False);
+ [self warpPointerToRootX: x y: y];
}
+}
+
+/* Confinement preserves absolute motion and edge panning. Relative mode holds
+ * its own grab and is left alone while requested. */
+- (void) updateMouseConfinementForWindow:(X11Window *)window {
@@ -3528,6 +3734,16 @@ index 5ae29bf4..8a66b921 100644
+ if (winTitle && [winTitle rangeOfString:@"(TM) Client" options:NSCaseInsensitiveSearch].location != NSNotFound) {
+ isGame = YES;
+ }
+ if (!isGame) {
+ XTextProperty prop;
+ if (XGetWMName(_display, win, &prop) && prop.value) {
+ NSString *xtitle = [NSString stringWithUTF8String:(const char *)prop.value];
+ if (xtitle && [xtitle rangeOfString:@"(TM) Client" options:NSCaseInsensitiveSearch].location != NSNotFound) {
+ isGame = YES;
+ }
+ XFree(prop.value);
+ }
+ }
+
+ if (isBorderless && isGame) {
+ if (!fullscreen) {
@@ -3555,9 +3771,24 @@ index 5ae29bf4..8a66b921 100644
+ unsigned long bypass = 1;
+ XChangeProperty(_display, win, XInternAtom(_display, "_NET_WM_BYPASS_COMPOSITOR", False),
+ XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&bypass, 1);
+ XFlush(_display);
+ if (getenv("RCLOL_INPUT_TRACE")) fprintf(stderr,"CONFINEMENT promoted to EWMH FULLSCREEN for window %lu\n",win);
+ }
+
+ /* Ensure window is marked active in Mutter so top bar is hidden and pointer is confined */
+ Atom activeAtom = XInternAtom(_display, "_NET_ACTIVE_WINDOW", False);
+ XClientMessageEvent act = {0};
+ act.type = ClientMessage;
+ act.window = win;
+ act.message_type = activeAtom;
+ act.format = 32;
+ act.data.l[0] = 1;
+ act.data.l[1] = CurrentTime;
+ XSendEvent(_display, DefaultRootWindow(_display), False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ (XEvent *)&act);
+ XRaiseWindow(_display, win);
+ XSetInputFocus(_display, win, RevertToPointerRoot, CurrentTime);
+ XFlush(_display);
+ if (getenv("RCLOL_INPUT_TRACE")) fprintf(stderr,"CONFINEMENT promoted to EWMH FULLSCREEN and ACTIVE for window %lu\n",win);
+ }
+
+ /* Check focus to determine whether mouse confinement should be active. */
@@ -3572,7 +3803,7 @@ index 5ae29bf4..8a66b921 100644
+ }
+ if (getenv("RCLOL_INPUT_TRACE")) fprintf(stderr,"CONFINEMENT focus=%lu target=%lu ancestor=%lu fullscreen=%d\n",focus,win,ancestor,fullscreen);
+
+ BOOL isFocused = (ancestor == win || focus == win || (isGame && (focus == PointerRoot || focus == None)));
+ BOOL isFocused = (ancestor == win || focus == win || (isGame && (focus == PointerRoot || focus == None || focus == DefaultRootWindow(_display))));
+ if ((!fullscreen && !isGame) || attrs.map_state != IsViewable || !isFocused) {
+ if (rclolTraceOn())
+ rclolTraceRated(&rclolConfineChannel, "no confinement window=%lu fullscreen=%d ewmh=%d titled=%d size=%dx%d map_state=%d %s",
@@ -3588,12 +3819,16 @@ index 5ae29bf4..8a66b921 100644
+ return;
+ }
+ unsigned mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask;
+ int status=XGrabPointer(_display, win, True, mask, GrabModeAsync, GrabModeAsync,
+ int status=XGrabPointer(_display, win, False, mask, GrabModeAsync, GrabModeAsync,
+ win, None, CurrentTime);
+ if (getenv("RCLOL_INPUT_TRACE")) fprintf(stderr,"CONFINEMENT fullscreen=%d window=%lu result=%d\n",fullscreen,win,status);
+ if (rclolTraceOn())
+ rclolTraceRated(&rclolConfineChannel, "confine window=%lu fullscreen=%d ewmh=%d size=%dx%d XGrabPointer=%d(%s)",
+ (unsigned long) win, fullscreen, rclolEwmhFullscreen, attrs.width, attrs.height, status,
+ rclolGrabResultName(status));
+ if (status == GrabSuccess) _confinedWindow=win;
+}
+
}
+/* Takes the relative-mode pointer grab on the key (else main) window, starting with the virtual cursor at the
+ * current pointer position. Returns the XGrabPointer result, or -1 without a window. */
+- (int) beginRelativeMouseWithWindow: (NSWindow **) outWindow pick: (const char **) outPick {
@@ -4063,7 +4298,7 @@ index 00000000..7fc39cc4
+
+#endif
diff --git a/AppKit/X11.backend/X11Window.h b/AppKit/X11.backend/X11Window.h
index bfe3839a..2521ffd5 100644
index bfe3839a..dbc974a0 100644
--- a/AppKit/X11.backend/X11Window.h
+++ b/AppKit/X11.backend/X11Window.h
@@ -26,7 +26,7 @@
@@ -4102,8 +4337,11 @@ index bfe3839a..2521ffd5 100644
@public
XIC _xic;
@@ -63,6 +83,7 @@
- (O2Rect) transformFrame: (O2Rect) frame;
@@ -60,9 +80,9 @@
- (Visual *) visual;
- (Drawable) drawable;
- (NSPoint) transformPoint: (NSPoint) pos;
-- (O2Rect) transformFrame: (O2Rect) frame;
- (Window) windowHandle;
+- (BOOL) handleRemoteFrameExpose: (Window) window;
@@ -4111,7 +4349,7 @@ index bfe3839a..2521ffd5 100644
- (void) frameChanged;
- (void) setLastKnownCursorPosition: (CGPoint) point;
diff --git a/AppKit/X11.backend/X11Window.m b/AppKit/X11.backend/X11Window.m
index f4925b66..22e7e9fd 100644
index f4925b66..12a75854 100644
--- a/AppKit/X11.backend/X11Window.m
+++ b/AppKit/X11.backend/X11Window.m
@@ -38,6 +38,34 @@
@@ -4149,6 +4387,15 @@ index f4925b66..22e7e9fd 100644
@implementation X11Window
@@ -158,7 +186,7 @@ static NSData *makeWindowIcon() {
X11Display* x11disp = (X11Display *) [NSDisplay currentDisplay];
_display = [x11disp display];
- _frame = [self transformFrame: [delegate frame]];
+ _frame = [x11disp rootRectForFrame: [delegate frame]];
BOOL isPanel = [delegate isKindOfClass: [NSPanel class]];
if (isPanel && _styleMask & NSDocModalWindowMask)
_styleMask = NSBorderlessWindowMask;
@@ -193,8 +221,32 @@ static NSData *makeWindowIcon() {
XSetWindowAttributes xattr;
@@ -4717,7 +4964,19 @@ index f4925b66..22e7e9fd 100644
- (Window) windowHandle {
return _window;
}
@@ -478,6 +976,20 @@ static NSData *makeWindowIcon() {
@@ -470,14 +968,31 @@ static NSData *makeWindowIcon() {
const char *text = [title cString];
XStringListToTextProperty((char **) &text, 1, &prop);
XSetWMName(_display, _window, &prop);
+ if (title && [title rangeOfString:@"(TM) Client" options:NSCaseInsensitiveSearch].location != NSNotFound) {
+ [(X11Display *)[NSDisplay currentDisplay] updateMouseConfinementForWindow:self];
+ }
}
- (void) setFrame: (O2Rect) frame {
- frame = [self transformFrame: frame];
+ frame = [(X11Display *) [NSDisplay currentDisplay] rootRectForFrame: frame];
XMoveResizeWindow(_display, _window, frame.origin.x, frame.origin.y,
frame.size.width, frame.size.height);
[self invalidateContextWithNewSize: frame.size];
_frame = frame;
@@ -4738,7 +4997,7 @@ index f4925b66..22e7e9fd 100644
}
- (void) setHasShadow: (BOOL) value {
@@ -488,6 +1000,25 @@ static NSData *makeWindowIcon() {
@@ -488,6 +1003,25 @@ static NSData *makeWindowIcon() {
_level = value;
}
@@ -4764,7 +5023,26 @@ index f4925b66..22e7e9fd 100644
- (void) setOpaque: (BOOL) value {
_isOpaque = value;
}
@@ -559,7 +1090,29 @@ static NSData *makeWindowIcon() {
@@ -543,6 +1077,18 @@ static NSData *makeWindowIcon() {
- (void) makeKey {
[self ensureMapped];
XRaiseWindow(_display, _window);
+ Atom activeAtom = XInternAtom(_display, "_NET_ACTIVE_WINDOW", False);
+ XEvent act = {0};
+ act.xclient.type = ClientMessage;
+ act.xclient.window = _window;
+ act.xclient.message_type = activeAtom;
+ act.xclient.format = 32;
+ act.xclient.data.l[0] = 1;
+ act.xclient.data.l[1] = CurrentTime;
+ XSendEvent(_display, DefaultRootWindow(_display), False,
+ SubstructureRedirectMask | SubstructureNotifyMask, &act);
+ XSetInputFocus(_display, _window, RevertToPointerRoot, CurrentTime);
+ XFlush(_display);
}
- (void) makeMain {
@@ -559,7 +1105,29 @@ static NSData *makeWindowIcon() {
XRaiseWindow(_display, _window);
}
@@ -4794,7 +5072,7 @@ index f4925b66..22e7e9fd 100644
Atom wmState = XInternAtom(_display, "_NET_WM_STATE", FALSE);
Atom hiddenState = XInternAtom(_display, "_NET_WM_STATE_HIDDEN", FALSE);
@@ -615,6 +1168,12 @@ static NSData *makeWindowIcon() {
@@ -615,6 +1183,12 @@ static NSData *makeWindowIcon() {
}
- (void) openGLFlushBuffer {
@@ -4807,7 +5085,16 @@ index f4925b66..22e7e9fd 100644
CGLError error;
CGLContextObj prevContext = CGLGetCurrentContext();
@@ -666,6 +1225,7 @@ static int ignoreBadWindow(Display *display, XErrorEvent *errorEvent) {
@@ -652,7 +1226,7 @@ static NSData *makeWindowIcon() {
}
- (O2Rect) frame {
- return [self transformFrame: _frame];
+ return [(X11Display *) [NSDisplay currentDisplay] frameForRootRect: _frame];
}
static int ignoreBadWindow(Display *display, XErrorEvent *errorEvent) {
@@ -666,6 +1240,7 @@ static int ignoreBadWindow(Display *display, XErrorEvent *errorEvent) {
}
- (void) frameChanged {
@@ -4815,6 +5102,21 @@ index f4925b66..22e7e9fd 100644
XErrorHandler previousHandler = XSetErrorHandler(ignoreBadWindow);
@try {
Window root, parent;
@@ -710,14 +1285,6 @@ static int ignoreBadWindow(Display *display, XErrorEvent *errorEvent) {
[_deviceDictionary addEntriesFromDictionary: entries];
}
-- (O2Rect) transformFrame: (O2Rect) frame {
- return NSMakeRect(frame.origin.x,
- DisplayHeight(_display, DefaultScreen(_display)) -
- frame.origin.y - frame.size.height,
- fmax(frame.size.width, 1.0),
- fmax(frame.size.height, 1.0));
-}
-
- (NSPoint) transformPoint: (NSPoint) pos; {
return NSMakePoint(pos.x, _frame.size.height - pos.y);
}
diff --git a/AppKit/include/AppKit/NSAccessibilityConstants.h b/AppKit/include/AppKit/NSAccessibilityConstants.h
index 282312de..03c75b2b 100644
--- a/AppKit/include/AppKit/NSAccessibilityConstants.h
@@ -5396,10 +5698,179 @@ index db8869c8..6fea5107 100644
+ CGContextSetTextPosition(c, savedPosition.x, savedPosition.y);
}
diff --git a/CoreGraphics/CGDirectDisplay.m b/CoreGraphics/CGDirectDisplay.m
index e213b066..94d5c7bb 100644
index e213b066..569d77f2 100644
--- a/CoreGraphics/CGDirectDisplay.m
+++ b/CoreGraphics/CGDirectDisplay.m
@@ -474,7 +474,9 @@ CGError CGAssociateMouseAndMouseCursorPosition(boolean_t connected) {
@@ -53,6 +53,29 @@ static NSDisplay *currentDisplay(void) {
return [cls currentDisplay];
}
+/* Display IDs belong to the screens themselves; [NSScreen screens] lists the
+ * primary screen first, so array positions are not IDs. */
+static NSScreen *screenForDisplayID(NSArray<NSScreen *> *screens,
+ CGDirectDisplayID displayID)
+{
+ for (NSScreen *screen in screens) {
+ if ([screen cgDirectDisplayID] == displayID)
+ return screen;
+ }
+ return nil;
+}
+
+/* NSScreen frames have y up from the primary's bottom edge; CoreGraphics
+ * global coordinates have y down from the primary's top edge. */
+static CGRect globalRectForFrame(NSArray<NSScreen *> *screens, NSRect frame) {
+ if (NSIsEmptyRect(frame) || [screens count] == 0)
+ return frame;
+ CGFloat primaryHeight = NSHeight([[screens objectAtIndex: 0] frame]);
+ return CGRectMake(frame.origin.x,
+ primaryHeight - frame.origin.y - frame.size.height,
+ frame.size.width, frame.size.height);
+}
+
CGDirectDisplayID CGMainDisplayID(void) {
NSDisplay *display = currentDisplay();
if (!display)
@@ -60,9 +83,9 @@ CGDirectDisplayID CGMainDisplayID(void) {
NSArray<NSScreen *> *screens = [display screens];
- for (int i = 0; i < [screens count]; i++) {
- if (!NSIsEmptyRect([[screens objectAtIndex: i] frame])) {
- return i + 1;
+ for (NSScreen *screen in screens) {
+ if (!NSIsEmptyRect([screen frame])) {
+ return [screen cgDirectDisplayID];
}
}
@@ -89,10 +112,11 @@ CGError CGGetOnlineDisplayList(uint32_t maxDisplays,
onlineDisplays[0] = mainDisplay;
}
- for (int i = 0; i < [screens count]; i++) {
- if ((i + 1) != mainDisplay) {
+ for (NSScreen *screen in screens) {
+ CGDirectDisplayID displayID = [screen cgDirectDisplayID];
+ if (displayID != mainDisplay) {
if (*displayCount < maxDisplays)
- onlineDisplays[*displayCount] = i + 1;
+ onlineDisplays[*displayCount] = displayID;
(*displayCount)++;
}
}
@@ -105,11 +129,11 @@ size_t CGDisplayPixelsHigh(CGDirectDisplayID displayIndex) {
if (!display)
return kCGErrorInvalidConnection;
- NSArray<NSScreen *> *screens = [display screens];
- if (displayIndex > [screens count] || displayIndex <= 0)
+ NSScreen *screen = screenForDisplayID([display screens], displayIndex);
+ if (!screen)
return 0;
- return NSHeight([[screens objectAtIndex: displayIndex - 1] frame]);
+ return NSHeight([screen frame]);
}
size_t CGDisplayPixelsWide(CGDirectDisplayID displayIndex) {
@@ -117,11 +141,11 @@ size_t CGDisplayPixelsWide(CGDirectDisplayID displayIndex) {
if (!display)
return kCGErrorInvalidConnection;
- NSArray<NSScreen *> *screens = [display screens];
- if (displayIndex > [screens count] || displayIndex <= 0)
+ NSScreen *screen = screenForDisplayID([display screens], displayIndex);
+ if (!screen)
return 0;
- return NSWidth([[screens objectAtIndex: displayIndex - 1] frame]);
+ return NSWidth([screen frame]);
}
CGError CGGetActiveDisplayList(uint32_t maxDisplays,
@@ -135,10 +159,10 @@ CGError CGGetActiveDisplayList(uint32_t maxDisplays,
NSArray<NSScreen *> *screens = [display screens];
*displayCount = 0;
- for (int i = 0; i < [screens count]; i++) {
- if (!NSIsEmptyRect([[screens objectAtIndex: i] frame])) {
+ for (NSScreen *screen in screens) {
+ if (!NSIsEmptyRect([screen frame])) {
if (*displayCount < maxDisplays)
- activeDisplays[*displayCount] = i + 1;
+ activeDisplays[*displayCount] = [screen cgDirectDisplayID];
(*displayCount)++;
}
}
@@ -169,11 +193,11 @@ CGError CGGetDisplaysWithPoint(CGPoint point, uint32_t maxDisplays,
NSArray<NSScreen *> *screens = [display screens];
*matchingDisplayCount = 0;
- for (int i = 0; i < [screens count]; i++) {
- NSRect rect = [[screens objectAtIndex: i] frame];
+ for (NSScreen *screen in screens) {
+ NSRect rect = globalRectForFrame(screens, [screen frame]);
if (NSPointInRect(point, rect)) {
if (*matchingDisplayCount < maxDisplays)
- displays[*matchingDisplayCount] = i + 1;
+ displays[*matchingDisplayCount] = [screen cgDirectDisplayID];
(*matchingDisplayCount)++;
}
}
@@ -192,11 +216,11 @@ CGError CGGetDisplaysWithRect(CGRect rect, uint32_t maxDisplays,
NSArray<NSScreen *> *screens = [display screens];
*matchingDisplayCount = 0;
- for (int i = 0; i < [screens count]; i++) {
- NSRect screenRect = [[screens objectAtIndex: i] frame];
+ for (NSScreen *screen in screens) {
+ NSRect screenRect = globalRectForFrame(screens, [screen frame]);
if (NSIntersectsRect(rect, screenRect)) {
if (*matchingDisplayCount < maxDisplays)
- displays[*matchingDisplayCount] = i + 1;
+ displays[*matchingDisplayCount] = [screen cgDirectDisplayID];
(*matchingDisplayCount)++;
}
}
@@ -218,10 +242,11 @@ CGRect CGDisplayBounds(CGDirectDisplayID displayIndex) {
return NSZeroRect;
NSArray<NSScreen *> *screens = [display screens];
- if (displayIndex > [screens count] || displayIndex <= 0)
+ NSScreen *screen = screenForDisplayID(screens, displayIndex);
+ if (!screen)
return NSZeroRect;
- return [[screens objectAtIndex: displayIndex - 1] frame];
+ return globalRectForFrame(screens, [screen frame]);
}
CGError CGDisplayHideCursor(CGDirectDisplayID displayIndex) {
@@ -256,7 +281,7 @@ boolean_t CGDisplayIsInMirrorSet(CGDirectDisplayID display) {
}
Boolean CGDisplayIsMain(CGDirectDisplayID display) {
- return display == 1;
+ return display != kCGNullDirectDisplay && display == CGMainDisplayID();
}
CGDirectDisplayID CGDisplayMirrorsDisplay(CGDirectDisplayID display) {
@@ -341,11 +366,10 @@ static NSData *edidForDisplay(CGDirectDisplayID displayId) {
if (!display)
return nil;
- NSArray<NSScreen *> *screens = [display screens];
- if (displayId <= 0 || displayId > [screens count])
+ NSScreen *screen = screenForDisplayID([display screens], displayId);
+ if (!screen)
return nil;
- NSScreen *screen = [screens objectAtIndex: displayId - 1];
NSData *edid = [screen edid];
if (edid && [edid length] >= 16)
return edid;
@@ -474,7 +498,9 @@ CGError CGAssociateMouseAndMouseCursorPosition(boolean_t connected) {
if (!display)
return kCGErrorInvalidConnection;
@@ -5410,7 +5881,7 @@ index e213b066..94d5c7bb 100644
return kCGErrorSuccess;
}
@@ -500,3 +502,38 @@ CFDictionaryRef CGDisplayCurrentMode(CGDirectDisplayID display) {
@@ -500,3 +526,38 @@ CFDictionaryRef CGDisplayCurrentMode(CGDirectDisplayID display) {
size_t CGDisplayModeGetPixelWidth(CGDisplayModeRef mode) {
return 0;
}
+5 -2
View File
@@ -1,10 +1,13 @@
#!/usr/bin/env python3
"""Compile the changed X11 event handler and relink the existing backend."""
import json
import shlex
import subprocess
from pathlib import Path
project = Path(__file__).resolve().parent.parent
framework = project / 'build/darling-src/build/src/external/cocotron/AppKit'
config_path = project / 'build/config.json'
sdk = json.loads(config_path.read_text())['sdk'] if config_path.exists() else '/usr/local/libexec/darling'
flags = {}
for line in (framework / 'CMakeFiles/X11_backend.dir/flags.make').read_text().splitlines():
if line.startswith('C_') and ' = ' in line:
@@ -17,7 +20,7 @@ for name in ('X11Display', 'X11Window'):
link[link.index(f'CMakeFiles/X11_backend.dir/X11.backend/{name}.m.o')] = str(obj)
link[link.index('-o') + 1] = str(project / 'build/riot-X11')
subprocess.run(link, cwd=framework, check=True)
print('Built isolated X11 backend with native visibility events')
print('Built isolated X11 backend')
# Exercise actual backend modifier transitions without compositor shortcuts.
probe = project / 'build/riot-modifier-check.o'
@@ -25,6 +28,6 @@ subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FL
'-O3', '-march=native', '-mtune=native', '-Wno-macro-redefined', '-c', str(project / 'tools/riot-modifier-check.m'),
'-o', str(probe)], check=True)
subprocess.run(['clang', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld',
'-nostdlib', '-isysroot', '@DARLING_SDK@', str(probe),
'-nostdlib', '-isysroot', sdk, str(probe),
'-lSystem', '-framework', 'AppKit', '-framework', 'Foundation', '-lobjc',
'-o', str(project / 'build/riot-modifier-check')], check=True)
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""Build changed display code and run a native probe in an isolated overlay.
Requires prepared sources, an existing CMake build, Darling, and the per-user
AppKit/CoreGraphics runtime overlays. Does not install binaries or send input.
"""
import json
import os
from pathlib import Path
import shlex
import shutil
import subprocess
import tempfile
root = Path(__file__).resolve().parent.parent
config = json.loads((root / 'build/config.json').read_text())
frameworks = root / 'build/darling-src/build/src/external/cocotron'
subprocess.run(['python3', str(root / 'tools/build-riot-x11-runtime.py')], check=True)
def flags_for(framework, target):
flags = {}
for line in (framework / f'CMakeFiles/{target}.dir/flags.make').read_text().splitlines():
if line.startswith('C_') and ' = ' in line:
key, value = line.split(' = ', 1)
flags[key] = shlex.split(value)
return flags['C_DEFINES'] + flags['C_INCLUDES'] + flags['C_FLAGS']
cg = frameworks / 'CoreGraphics'
obj = root / 'build/pr12-CGDirectDisplay.o'
subprocess.run(['clang', *flags_for(cg, 'CoreGraphics'), '-O2', '-c',
str(root / 'build/darling-src/src/external/cocotron/CoreGraphics/CGDirectDisplay.m'),
'-o', str(obj)], check=True)
link = shlex.split((cg / 'CMakeFiles/CoreGraphics.dir/link.txt').read_text())
link[link.index('CMakeFiles/CoreGraphics.dir/CGDirectDisplay.m.o')] = str(obj)
link[link.index('-o') + 1] = str(root / 'build/pr12-CoreGraphics')
subprocess.run(link, cwd=cg, check=True)
probe = root / 'build/riot-display-geometry-check'
subprocess.run(['clang', *flags_for(frameworks / 'AppKit', 'X11_backend'),
'-O2', '-UNDEBUG', '-Wno-macro-redefined', '-c', str(root / 'tools/riot-display-geometry-check.m'),
'-o', str(probe) + '.o'], check=True)
subprocess.run(['clang', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld',
'-nostdlib', '-isysroot', config['sdk'], str(probe) + '.o',
'-lSystem', '-framework', 'AppKit', '-framework', 'Foundation',
'-framework', 'CoreGraphics', '-lobjc', '-o', str(probe)], check=True)
original = Path(config['prefix']) / config['guest_home'].lstrip('/') / 'RuntimeFrameworks'
with tempfile.TemporaryDirectory(prefix='pr12-display-frameworks-', dir=root / 'build') as directory:
overlay = Path(directory)
for name in ('AppKit', 'CoreGraphics'):
shutil.copytree(original / f'{name}.framework', overlay / f'{name}.framework',
symlinks=True, dirs_exist_ok=True,
ignore=shutil.ignore_patterns('*.before-*'))
shutil.copy2(root / 'build/riot-X11', overlay / 'AppKit.framework/Versions/C/Resources/Backends/X11.backend/Contents/MacOS/X11')
shutil.copy2(root / 'build/pr12-CoreGraphics', overlay / 'CoreGraphics.framework/Versions/A/CoreGraphics')
env = dict(os.environ, EGL_PLATFORM='x11',
DYLD_FRAMEWORK_PATH='/Volumes/SystemRoot' + str(overlay) + ':' + config['guest_home'] + '/RuntimeFrameworks',
DYLD_LIBRARY_PATH=config['guest_home'] + '/RuntimeLibraries')
# File capture bounds the wait even if a guest service inherits the output fd.
log = root / 'build/pr12-display-check.log'
with log.open('w+') as output:
try:
result = subprocess.run(['darling', 'shell', '/Volumes/SystemRoot' + str(probe)],
env=env, stdout=output, stderr=subprocess.STDOUT, timeout=45)
except subprocess.TimeoutExpired:
raise SystemExit(f'Display probe timed out; inspect {log}')
output.seek(0)
lines = [line.rstrip() for line in output if line.startswith('DISPLAY_GEOMETRY')]
print('\n'.join(lines))
if result.returncode or len(lines) != 6:
raise SystemExit(f'Display probe failed ({result.returncode}); inspect {log}')
+144
View File
@@ -0,0 +1,144 @@
/* Exercise the real X11/CG methods with synthetic cached monitor layouts.
* No input is sent: pointer warps are captured by a temporary method override. */
#import <AppKit/AppKit.h>
#import <AppKit/NSDisplay.h>
#import <AppKit/NSScreen.h>
#import <CoreGraphics/CGDirectDisplay.h>
#import <objc/runtime.h>
#import "../build/darling-src/src/external/cocotron/AppKit/X11.backend/X11Display.h"
#import "../build/darling-src/src/external/cocotron/AppKit/X11.backend/X11Window.h"
#include <X11/extensions/Xrandr.h>
#include <assert.h>
#include <string.h>
// Implemented by Cocotron but absent from its public headers.
extern CGRect CGDisplayBounds(CGDirectDisplayID display);
extern CGError CGWarpMouseCursorPosition(CGPoint position);
@interface X11Display (GeometryProbe)
- (NSArray *)_rootScreenFallback;
- (void)postXEvent:(XEvent *)event;
@end
static void *slot(id obj, const char *name) {
Ivar ivar = class_getInstanceVariable([obj class], name);
assert(ivar);
return (char *)obj + ivar_getOffset(ivar);
}
static void rectEqual(NSRect actual, NSRect expected) {
assert(NSEqualRects(actual, expected));
}
static unsigned captureWarp(id obj, SEL sel, int x, int y) {
assert(x == 3940 && y == 1010);
return 1;
}
static NSScreen *screen(NSRect frame, unsigned displayID) {
NSScreen *s = [[[NSScreen alloc] initWithFrame:frame visibleFrame:frame] autorelease];
[s setCgDirectDisplayID:displayID];
return s;
}
int main(void) {
setbuf(stdout,NULL);
@autoreleasepool {
[NSApplication sharedApplication];
X11Display *d = (id)[NSDisplay currentDisplay];
assert([d respondsToSelector:@selector(rootRectForFrame:)]);
NSArray *actualScreens = [d screens];
assert([actualScreens count] > 0);
NSScreen *first = [actualScreens objectAtIndex:0];
if (!NSIsEmptyRect([first frame])) {
assert(NSMinX([first frame]) == 0 && NSMinY([first frame]) == 0);
assert(CGMainDisplayID() == [first cgDirectDisplayID]);
}
puts("DISPLAY_GEOMETRY live_screen_origin=pass");
NSRect layouts[] = { NSMakeRect(0,0,1920,1080), NSMakeRect(3840,810,3840,2160),
NSMakeRect(0,1200,1920,1080), NSMakeRect(1920,0,1920,1080) };
for (unsigned i = 0; i < sizeof layouts / sizeof *layouts; i++) {
*(NSRect *)slot(d,"_primaryRootFrame") = layouts[i];
*(BOOL *)slot(d,"_primaryRootFrameValid") = YES;
NSRect frame = NSMakeRect(-320,140,800,600);
NSRect root = [d rootRectForFrame:frame];
rectEqual(root, NSMakeRect(NSMinX(layouts[i])-320,NSMaxY(layouts[i])-740,800,600));
rectEqual([d frameForRootRect:root],frame);
// Test X11Window's inverse as well, using an unmapped object.
X11Window *w = [NSClassFromString(@"X11Window") alloc];
*(NSRect *)slot(w,"_frame") = root;
rectEqual([w frame],frame);
[w release];
}
puts("DISPLAY_GEOMETRY single_offset_vertical_negative_roundtrip=pass");
NSWindow *window = [[NSWindow alloc] initWithContentRect:NSMakeRect(100,100,800,600)
styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
X11Window *native = (id)[window platformWindow];
rectEqual(*(NSRect *)slot(native,"_frame"),NSMakeRect(2020,380,800,600));
[native setFrame:NSMakeRect(-100,200,640,480)];
rectEqual(*(NSRect *)slot(native,"_frame"),NSMakeRect(1820,400,640,480));
rectEqual([native frame],NSMakeRect(-100,200,640,480));
[window release];
puts("DISPLAY_GEOMETRY unmapped_window_create_resize=pass");
NSRect primary = NSMakeRect(3840,810,3840,2160);
*(NSRect *)slot(d,"_primaryRootFrame") = primary;
NSArray **cache = slot(d,"_lastScreens");
[*cache release];
*cache = [@[
screen(NSMakeRect(0,0,3840,2160),2),
screen(NSMakeRect(-3840,102,3840,2160),1),
screen(NSMakeRect(3840,-230,1800,3200),3),
screen(NSZeroRect,4)
] retain];
assert(CGMainDisplayID() == 2 && CGDisplayIsMain(2) && !CGDisplayIsMain(1));
assert(CGDisplayPixelsWide(3) == 1800 && CGDisplayPixelsHigh(3) == 3200);
assert(CGDisplayPixelsWide(99) == 0);
rectEqual(CGDisplayBounds(1),NSMakeRect(-3840,-102,3840,2160));
rectEqual(CGDisplayBounds(3),NSMakeRect(3840,-810,1800,3200));
rectEqual(CGDisplayBounds(4),NSZeroRect);
CGDirectDisplayID ids[4] = {0}; uint32_t count;
assert(CGGetActiveDisplayList(4,ids,&count) == 0 && count == 3 && ids[0] == 2 && ids[1] == 1);
assert(CGGetOnlineDisplayList(1,ids,&count) == 0 && count == 4 && ids[0] == 2);
assert(CGGetActiveDisplayList(0,NULL,&count) == 0 && count == 3);
assert(CGGetDisplaysWithPoint(CGPointMake(-100,-50),4,ids,&count) == 0 && count == 1 && ids[0] == 1);
assert(CGGetDisplaysWithRect(CGRectMake(3900,-800,10,10),4,ids,&count) == 0 && count == 1 && ids[0] == 3);
puts("DISPLAY_GEOMETRY reordered_ids_bounds_enumeration_queries=pass");
Method method = class_getInstanceMethod([d class],@selector(warpPointerToRootX:y:));
assert(method);
IMP original = method_setImplementation(method,(IMP)captureWarp);
X11RelativeMouse *relative = slot(d,"_relativeMouse");
X11RelativeMouseBegin(relative,3900,1000);
assert(CGWarpMouseCursorPosition(CGPointMake(100,200)) == 0);
assert(relative->virtualX == 3940 && relative->virtualY == 1010);
NSPoint location = [d mouseLocation];
assert(location.x == 100 && location.y == 1960);
X11RelativeMouseMotion motion = X11RelativeMouseHandleMotion(relative,3907,1009,false);
assert(motion.deltaX == 7 && motion.deltaY == 9);
X11RelativeMouseEnd(relative);
assert(CGWarpMouseCursorPosition(CGPointMake(100,200)) == 0);
method_setImplementation(method,original);
puts("DISPLAY_GEOMETRY absolute_relative_warp_and_delta=pass");
int eventBase = *(int *)slot(d,"_rrEventBase");
assert(eventBase);
XEvent notify = {0}; notify.type = eventBase + RRNotify;
notify.xany.display = [d display];
[d postXEvent:&notify];
assert(!*(BOOL *)slot(d,"_primaryRootFrameValid") && !*cache);
assert([[d screens] count] > 0 && *(BOOL *)slot(d,"_primaryRootFrameValid"));
XRRScreenChangeNotifyEvent change = {0};
Display *xdisplay = [d display];
change.type = eventBase + RRScreenChangeNotify;
change.display = xdisplay; change.root = DefaultRootWindow(xdisplay);
change.width = DisplayWidth(xdisplay,DefaultScreen(xdisplay));
change.height = DisplayHeight(xdisplay,DefaultScreen(xdisplay));
change.mwidth = DisplayWidthMM(xdisplay,DefaultScreen(xdisplay));
change.mheight = DisplayHeightMM(xdisplay,DefaultScreen(xdisplay));
change.rotation = RR_Rotate_0;
[d postXEvent:(XEvent *)&change];
assert(!*(BOOL *)slot(d,"_primaryRootFrameValid") && !*cache);
NSArray *fallback = [d _rootScreenFallback];
assert([fallback count] == 1 && [[fallback objectAtIndex:0] cgDirectDisplayID] == 1);
rectEqual(*(NSRect *)slot(d,"_primaryRootFrame"),NSMakeRect(0,0,change.width,change.height));
puts("DISPLAY_GEOMETRY cache_events_and_fallback=pass");
}
return 0;
}