Merge pull request #87 from ekisu/fix/cocoa-window-decorations

Fix client-side window decorations and LeagueClient header dragging
This commit is contained in:
Kalah authored and GitHub committed 2026-10-19 07:43:05 +02:00
commit a35aecb3ec
6 files changed
+400 -43

No files matched your search

+59
View File
@@ -119,6 +119,65 @@ Expected: `CORETEXT_FONT_LIST pass=1` with nonzero family/face counts (306/1124
The font probe reproduces Chromium's installed-family enumeration, mandatory family matching and localized-name lookup with a null language output. It checks ownership of the single-match result and that a nonexistent mandatory family produces no substitute. The resize probe checks backing-layer bounds immediately after frame/bounds changes, including a scaled view and a child layer, without relying on input or coordinate conversion to refresh them. It also verifies that native parent size is current during child layout, and that adding an overlay sibling after the content view does not redirect subsequent content resizing into that overlay. The old sibling selection left content at 1600×900 while stretching the 30×20 overlay to 1280×720; the corrected layout resizes the content and preserves the overlay. Before the fixes, these checks report a stale 1280×720 layer and native parent after a 1600×900 resize. The `FRAME_SIZE_HOOK` check uses a subclass whose `setFrameSize:` publishes its viewport size. It checks growth and shrinkage through parent autoresizing, direct `setFrame:`, and direct `setFrameSize:`. Before component-setter dispatch was corrected only the final direct size setter reached the subclass (one callback); afterward all four updates do, with current geometry visible inside the callback. No probe launches or contacts Riot/League. The user confirmed that the language dropdown and League window resizing work after the final X11 correction (2026-10-15). The practice-tool crash remains unresolved.
### Local event monitors and client dragging
`python3 tools/build-riot-x11-runtime.py` builds
`build/riot-event-monitors-check`. With patched AppKit installed and clients
closed, run:
```sh
darling shell env DYLD_LIBRARY_PATH=/Users/me/RuntimeLibraries \
DYLD_FRAMEWORK_PATH=/Users/me/RuntimeFrameworks \
/Volumes/SystemRoot/path/to/darling-lol/build/riot-event-monitors-check
```
Expected: nine `PASS` lines and exit status 0. This independent probe tests
multiple monitors, mask filtering, removal, consumption, event replacement,
self-removal, and the in-flight snapshot when another monitor is removed.
Monitor order is unspecified and is deliberately not asserted. It dispatches
only synthetic events to its own unmapped window.
The same source runs against native macOS AppKit:
```sh
clang -fblocks -framework AppKit tools/riot-event-monitors-check.m \
-o /tmp/riot-event-monitors-check
/tmp/riot-event-monitors-check
```
Native reference: macOS 26.6.2. LeagueClientUx uses local
monitors for custom window movement; intercepting a fixed titlebar strip instead
would bypass its own drag policy and risk stealing its window-control clicks.
For a live regression check, drag its header, drag ordinary content (which must
not move the window), minimize and restore it, and check Riot Client header
dragging separately. No match is required.
### X11 window decorations
`python3 tools/build-riot-x11-runtime.py` also builds
`build/riot-window-decorations-check`. Install and activate both the patched
AppKit and X11 backend before running it, with clients and the game closed:
```sh
darling shell env DYLD_LIBRARY_PATH=/Users/me/RuntimeLibraries \
DYLD_FRAMEWORK_PATH=/Users/me/RuntimeFrameworks \
/Volumes/SystemRoot/path/to/darling-lol/build/riot-window-decorations-check
```
Expected: `WINDOW_DECORATIONS_CHECK cases=10 pass=1`. The independent Cocoa
probe reads `_MOTIF_WM_HINTS` on its own unmapped native windows. It covers a
normal titled window, hidden title alone, hidden plus transparent titlebar,
both reverse transitions, untitled masks with additional flags, switching to
borderless after creation, native-window recreation, and properties set before
the native window exists. It sends no input, maps no windows and does not
launch or contact Riot/League. Check the success marker, not only the launcher
exit status: some Darling versions do not propagate a guest assertion failure.
Before the fix the hidden/transparent case fails. With the corrected overlays,
the ten cases passed on KDE Wayland/Xwayland. Passive inspection of the reopened
Riot Client also confirmed the no-decoration hint and identical compositor frame
and client dimensions (1536×864), instead of the previous 28-pixel titlebar.
This validates the Riot Client window, not every LeagueClientUx or game mode.
### Presentation publisher handoff
With clients closed, run `build/riot-client-resize-check --handoff` through Darling with the normal runtime overrides. This creates an owned window and two fixture frame slots. The live software owner first supplies a 1280×720 main frame, then switches to a 262×131 popup; another slot supplies the resized main frame without the software flag. The test samples its own window beyond the original frame extent and checks generation colors across growth and shrinkage. The old selector reports black at the 1600×900 sample; the correction passes all four sizes. Repeat with `RCLOL_XSHM=0` and with `DARLING_XSHM_LIBRARY` pointing to the normal transport to cover both paths. The fixtures remove their own frame slots on exit; they do not read client pixels or send input.
+136 -41
View File
@@ -126,7 +126,7 @@ index d543e329..e320a3af 100644
{
return [NSMethodSignature signatureWithObjCTypes: "v@:"];
diff --git a/AppKit/NSApplication.m b/AppKit/NSApplication.m
index 8e7508e2..6fc86b32 100644
index 8e7508e2..5abc49b7 100644
--- a/AppKit/NSApplication.m
+++ b/AppKit/NSApplication.m
@@ -17,6 +17,9 @@ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
@@ -239,10 +239,14 @@ index 8e7508e2..6fc86b32 100644
do {
pool = [NSAutoreleasePool new];
@@ -716,6 +744,12 @@ NSApplication *NSApp = nil;
@@ -716,6 +744,16 @@ NSApplication *NSApp = nil;
}
- (void) sendEvent: (NSEvent *) event {
+ extern NSEvent *_NSApplyLocalEventMonitors(NSEvent *);
+ event = _NSApplyLocalEventMonitors(event);
+ if (event == nil)
+ return;
+ static int inputTrace = -1;
+ if (inputTrace < 0) inputTrace = getenv("RCLOL_INPUT_TRACE") != NULL;
+ if (inputTrace && ([event type] == NSKeyDown || [event type] == NSLeftMouseDown))
@@ -252,7 +256,7 @@ index 8e7508e2..6fc86b32 100644
if ([event type] == NSKeyDown) {
unsigned modifierFlags = [event modifierFlags];
@@ -742,6 +776,61 @@ NSApplication *NSApp = nil;
@@ -742,6 +780,61 @@ NSApplication *NSApp = nil;
do {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
@@ -648,10 +652,49 @@ index cacd8999..9422d869 100644
NSInvalidAbstractInvocation();
return nil;
diff --git a/AppKit/NSEvent.subproj/NSEvent.m b/AppKit/NSEvent.subproj/NSEvent.m
index d0bad986..d0b1b4aa 100644
index d0bad986..fecfa9f8 100644
--- a/AppKit/NSEvent.subproj/NSEvent.m
+++ b/AppKit/NSEvent.subproj/NSEvent.m
@@ -40,6 +40,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
@@ -29,6 +29,38 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
#import <Foundation/NSDate.h>
#import <Foundation/NSException.h>
#import <Foundation/NSRaise.h>
+#import <Foundation/NSArray.h>
+
+@interface _NSLocalEventMonitor : NSObject {
+@public
+ NSEventMask _mask;
+ NSEvent * (^_handler)(NSEvent *);
+}
+@end
+
+@implementation _NSLocalEventMonitor
+- (void) dealloc {
+ [_handler release];
+ [super dealloc];
+}
+@end
+
+static NSMutableArray *_NSLocalEventMonitors;
+
+/* Snapshot the registrations: changes made by a handler affect subsequent
+ * dispatches, not the event already in flight. Handlers may also enter a nested
+ * event loop, which takes its own snapshot. */
+NSEvent *_NSApplyLocalEventMonitors(NSEvent *event) {
+ NSArray *monitors = [[_NSLocalEventMonitors copy] autorelease];
+ for (_NSLocalEventMonitor *monitor in [monitors reverseObjectEnumerator]) {
+ if (event == nil)
+ break;
+ NSUInteger type = [event type];
+ if (type < sizeof(NSEventMask) * 8 && (monitor->_mask & ((NSEventMask)1 << type)))
+ event = monitor->_handler(event);
+ }
+ return event;
+}
@implementation NSEvent
@@ -40,6 +72,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
return [[NSDisplay currentDisplay] currentModifierFlags];
}
@@ -662,6 +705,34 @@ index d0bad986..d0b1b4aa 100644
- (instancetype) initWithType: (NSEventType) type
location: (NSPoint) location
modifierFlags: (NSEventModifierFlags) modifierFlags
@@ -350,14 +386,23 @@ static NSTimer *_periodicTimer = nil;
}
+ (id) addLocalMonitorForEventsMatchingMask: (NSEventMask) mask
- handler: (NSEvent * (^)(NSEvent *event)) block
+ handler: (NSEvent * (^)(NSEvent *event)) block
{
- NSUnimplementedMethod();
- return nil;
+ if (block == nil)
+ return nil;
+ if (_NSLocalEventMonitors == nil)
+ _NSLocalEventMonitors = [NSMutableArray new];
+ _NSLocalEventMonitor *monitor = [[_NSLocalEventMonitor alloc] init];
+ monitor->_mask = mask;
+ monitor->_handler = [block copy];
+ [_NSLocalEventMonitors addObject: monitor];
+ return [monitor autorelease];
}
+ (void) removeMonitor: (id) eventMonitor {
- NSUnimplementedMethod();
+ if ([_NSLocalEventMonitors indexOfObjectIdenticalTo: eventMonitor] != NSNotFound) {
+ [_NSLocalEventMonitors removeObjectIdenticalTo: eventMonitor];
+ }
}
@end
diff --git a/AppKit/NSEvent.subproj/NSEvent_mouse.m b/AppKit/NSEvent.subproj/NSEvent_mouse.m
index 31c65aac..f7d55ec0 100644
--- a/AppKit/NSEvent.subproj/NSEvent_mouse.m
@@ -1946,7 +2017,7 @@ index d545120a..79760f47 100644
- (BOOL) inLiveResize {
return _isInLiveResize;
@@ -1561,6 +1766,117 @@ static BOOL _allowsAutomaticWindowTabbing;
@@ -1561,6 +1766,123 @@ static BOOL _allowsAutomaticWindowTabbing;
return point;
}
@@ -1976,7 +2047,10 @@ index d545120a..79760f47 100644
+}
+
+- (void) setTitlebarAppearsTransparent: (BOOL) value {
+ if (_titlebarAppearsTransparent == value)
+ return;
+ _titlebarAppearsTransparent = value;
+ [_platformWindow setStyleMask: _styleMask];
+}
+
+- (NSInteger) titleVisibility {
@@ -1984,7 +2058,10 @@ index d545120a..79760f47 100644
+}
+
+- (void) setTitleVisibility: (NSInteger) value {
+ if (_titleVisibility == value)
+ return;
+ _titleVisibility = value;
+ [_platformWindow setStyleMask: _styleMask];
+}
+
+- (BOOL) movableByWindowBackground {
@@ -2064,7 +2141,7 @@ index d545120a..79760f47 100644
- (NSRect) frameRectForContentRect: (NSRect) contentRect {
// hasMainMenu is an instance method so we can't just use the class method
// frameRectForContentRect:styleMask:
@@ -1580,6 +1896,10 @@ static BOOL _allowsAutomaticWindowTabbing;
@@ -1580,6 +1902,10 @@ static BOOL _allowsAutomaticWindowTabbing;
}
- (NSRect) contentRectForFrameRect: (NSRect) frameRect {
@@ -2075,7 +2152,7 @@ index d545120a..79760f47 100644
NSRect result = [[NSDisplay currentDisplay] insetRect: frameRect
forNativeWindowBorderWithStyle: [self styleMask]];
@@ -2155,6 +2475,7 @@ static BOOL _allowsAutomaticWindowTabbing;
@@ -2155,6 +2481,7 @@ static BOOL _allowsAutomaticWindowTabbing;
- (void) orderWindow: (NSWindowOrderingMode) place
relativeTo: (NSInteger) relativeTo
{
@@ -2083,7 +2160,7 @@ index d545120a..79760f47 100644
// The move notifications are sent under unknown conditions around
// orderFront: in the Apple AppKit, we do them all the time here until it's
// figured out. I suspect it is a side effect of off-screen windows being at
@@ -2208,6 +2529,8 @@ static BOOL _allowsAutomaticWindowTabbing;
@@ -2208,6 +2535,8 @@ static BOOL _allowsAutomaticWindowTabbing;
break;
}
@@ -2092,7 +2169,7 @@ index d545120a..79760f47 100644
[self postNotificationName: NSWindowDidMoveNotification];
}
@@ -2257,6 +2580,12 @@ static BOOL _allowsAutomaticWindowTabbing;
@@ -2257,6 +2586,12 @@ static BOOL _allowsAutomaticWindowTabbing;
}
- (void) sendEvent: (NSEvent *) event {
@@ -2105,7 +2182,7 @@ index d545120a..79760f47 100644
// Some events can cause our window to be destroyed
// So make sure self lives at least through this current run loop...
[[self retain] autorelease];
@@ -2300,6 +2629,10 @@ static BOOL _allowsAutomaticWindowTabbing;
@@ -2300,6 +2635,10 @@ static BOOL _allowsAutomaticWindowTabbing;
case NSLeftMouseDown: {
NSView *view = [_backgroundView hitTest: [event locationInWindow]];
@@ -2116,7 +2193,7 @@ index d545120a..79760f47 100644
if ([view acceptsFirstResponder]) {
if ([view needsPanelToBecomeKey]) {
@@ -2307,6 +2640,27 @@ static BOOL _allowsAutomaticWindowTabbing;
@@ -2307,6 +2646,27 @@ static BOOL _allowsAutomaticWindowTabbing;
}
}
@@ -2144,7 +2221,7 @@ index d545120a..79760f47 100644
// Event goes to view, not first responder.
[view mouseDown: event];
_mouseDownLocationInWindow = [event locationInWindow];
@@ -2330,6 +2684,31 @@ static BOOL _allowsAutomaticWindowTabbing;
@@ -2330,6 +2690,31 @@ static BOOL _allowsAutomaticWindowTabbing;
_mouseDownLocationInWindow = NSMakePoint(NAN, NAN);
break;
@@ -2176,7 +2253,7 @@ index d545120a..79760f47 100644
case NSMouseMoved: {
NSView *hit = [_backgroundView hitTest: [event locationInWindow]];
@@ -2478,6 +2857,197 @@ static BOOL _allowsAutomaticWindowTabbing;
@@ -2478,6 +2863,197 @@ static BOOL _allowsAutomaticWindowTabbing;
_draggedTypes = nil;
}
@@ -2374,7 +2451,7 @@ index d545120a..79760f47 100644
- (void) dragImage: (NSImage *) image
at: (NSPoint) location
offset: (NSSize) offset
@@ -2486,6 +3056,10 @@ static BOOL _allowsAutomaticWindowTabbing;
@@ -2486,6 +3062,10 @@ static BOOL _allowsAutomaticWindowTabbing;
source: (id) source
slideBack: (BOOL) slideBack
{
@@ -2385,7 +2462,7 @@ index d545120a..79760f47 100644
[[NSDraggingManager draggingManager] dragImage: image
at: location
offset: offset
@@ -2536,6 +3110,7 @@ static BOOL _allowsAutomaticWindowTabbing;
@@ -2536,6 +3116,7 @@ static BOOL _allowsAutomaticWindowTabbing;
} else {
[[self platformWindow] hideWindow];
}
@@ -2393,7 +2470,7 @@ index d545120a..79760f47 100644
}
- (void) orderFront: (id) sender {
@@ -3064,6 +3639,13 @@ static BOOL _allowsAutomaticWindowTabbing;
@@ -3064,6 +3645,13 @@ static BOOL _allowsAutomaticWindowTabbing;
if (didSize) {
// Don't redraw everything unless we really have to.
[_backgroundView setFrameSize: _frame.size];
@@ -2407,7 +2484,7 @@ index d545120a..79760f47 100644
[_backgroundView setNeedsDisplay: YES];
[self resetCursorRects];
[self saveFrameUsingName: _autosaveFrameName];
@@ -3260,8 +3842,7 @@ static BOOL _allowsAutomaticWindowTabbing;
@@ -3260,8 +3848,7 @@ static BOOL _allowsAutomaticWindowTabbing;
mouseWasInside && !mouseIsInside) {
[exited addObject: area];
}
@@ -2417,7 +2494,7 @@ index d545120a..79760f47 100644
[moved addObject: area];
}
if (options & NSTrackingCursorUpdate && !mouseWasInside &&
@@ -3459,7 +4040,7 @@ static BOOL _allowsAutomaticWindowTabbing;
@@ -3459,7 +4046,7 @@ static BOOL _allowsAutomaticWindowTabbing;
}
- (CGSubWindow *) _createSubWindowWithFrame: (CGRect) frame {
@@ -5262,7 +5339,7 @@ index f4925b66..730d4692 100644
_isModal = [delegate isSheet] || [NSApp modalWindow] == delegate;
// FIXME: There should be no need for this.
@@ -254,7 +396,8 @@ static NSData *makeWindowIcon() {
@@ -254,19 +396,96 @@ static NSData *makeWindowIcon() {
XNFocusWindow, _window,
NULL);
@@ -5272,7 +5349,10 @@ index f4925b66..730d4692 100644
[(X11Display *) [NSDisplay currentDisplay] setWindow: self forID: _window];
@@ -265,8 +408,88 @@ static NSData *makeWindowIcon() {
- // FIXME: Move this call into setStyleMaskInternal below!
- if (_styleMask == NSBorderlessWindowMask) {
- [[self class] removeDecorationForWindow: _window onDisplay: _display];
- }
[self setStyleMaskInternal: _styleMask force: YES];
[self setWindowIcon];
@@ -5362,7 +5442,7 @@ index f4925b66..730d4692 100644
}
- (void) dealloc {
@@ -279,24 +502,30 @@ static NSData *makeWindowIcon() {
@@ -279,24 +498,30 @@ static NSData *makeWindowIcon() {
return _styleMask;
}
@@ -5409,7 +5489,7 @@ index f4925b66..730d4692 100644
if (!_mapped) {
long states[2];
int states_cnt = 0;
@@ -304,7 +533,7 @@ static NSData *makeWindowIcon() {
@@ -304,7 +529,7 @@ static NSData *makeWindowIcon() {
states[states_cnt++] =
(long) XInternAtom(_display, "_NET_WM_STATE_MODAL", False);
}
@@ -5418,7 +5498,7 @@ index f4925b66..730d4692 100644
states[states_cnt++] = (long) XInternAtom(
_display, "_NET_WM_STATE_FULLSCREEN", False);
}
@@ -312,14 +541,13 @@ static NSData *makeWindowIcon() {
@@ -312,14 +537,13 @@ static NSData *makeWindowIcon() {
XInternAtom(_display, "_NET_WM_STATE", False), XA_ATOM,
32, PropModeReplace, (const unsigned char *) states,
states_cnt);
@@ -5435,7 +5515,7 @@ index f4925b66..730d4692 100644
event.data.l[1] =
(long) XInternAtom(_display, "_NET_WM_STATE_FULLSCREEN", False);
event.data.l[3] = 1;
@@ -327,6 +555,35 @@ static NSData *makeWindowIcon() {
@@ -327,6 +551,47 @@ static NSData *makeWindowIcon() {
SubstructureNotifyMask | SubstructureRedirectMask,
(XEvent *) &event);
}
@@ -5444,6 +5524,18 @@ index f4925b66..730d4692 100644
+}
+
+- (void) setStyleMaskInternal: (NSUInteger) mask force: (BOOL) force {
+ // Cocoa chrome may change after the native window already exists. A hidden,
+ // transparent titlebar is client-drawn chrome, even with the titled bit set.
+ // NSWindowTitleHidden is 1; this SDK exposes titleVisibility as NSInteger.
+ BOOL clientChrome = [_delegate titlebarAppearsTransparent] &&
+ [_delegate titleVisibility] == 1;
+ if (!(mask & NSTitledWindowMask) || clientChrome) {
+ [[self class] removeDecorationForWindow: _window onDisplay: _display];
+ } else {
+ // Return decoration ownership to the WM on the reverse transition.
+ XDeleteProperty(_display, _window,
+ XInternAtom(_display, "_MOTIF_WM_HINTS", False));
+ }
+ if (force || (mask & NSWindowStyleMaskResizable) !=
+ (_styleMask & NSWindowStyleMaskResizable)) {
+ XSizeHints *sh = XAllocSizeHints();
@@ -5471,7 +5563,7 @@ index f4925b66..730d4692 100644
}
- (void) setStyleMask: (NSUInteger) mask {
@@ -338,7 +595,6 @@ static NSData *makeWindowIcon() {
@@ -338,7 +603,6 @@ static NSData *makeWindowIcon() {
}
+ (void) removeDecorationForWindow: (Window) w onDisplay: (Display *) dpy {
@@ -5479,7 +5571,7 @@ index f4925b66..730d4692 100644
struct {
unsigned long flags;
unsigned long functions;
@@ -356,8 +612,14 @@ static NSData *makeWindowIcon() {
@@ -356,8 +620,14 @@ static NSData *makeWindowIcon() {
- (void) ensureMapped {
if (!_mapped) {
@@ -5494,7 +5586,7 @@ index f4925b66..730d4692 100644
}
}
@@ -377,15 +639,27 @@ static NSData *makeWindowIcon() {
@@ -377,15 +647,27 @@ static NSData *makeWindowIcon() {
- (void) syncDelegateProperties {
long mask = KeyPressMask | KeyReleaseMask | ExposureMask |
StructureNotifyMask | EnterWindowMask | LeaveWindowMask |
@@ -5527,7 +5619,7 @@ index f4925b66..730d4692 100644
// TODO: background color
}
@@ -393,6 +667,28 @@ static NSData *makeWindowIcon() {
@@ -393,6 +675,28 @@ static NSData *makeWindowIcon() {
// This is essentially dealloc; we release our contexts
// and windows, but unlike dealloc, this method can be called
// several times, so set everything to nil/NULL/0.
@@ -5556,7 +5648,7 @@ index f4925b66..730d4692 100644
[_context release];
_context = nil;
@@ -421,6 +717,392 @@ static NSData *makeWindowIcon() {
@@ -421,6 +725,392 @@ static NSData *makeWindowIcon() {
}
}
@@ -5949,7 +6041,7 @@ index f4925b66..730d4692 100644
- (Window) windowHandle {
return _window;
}
@@ -470,14 +1152,32 @@ static NSData *makeWindowIcon() {
@@ -470,14 +1160,32 @@ static NSData *makeWindowIcon() {
const char *text = [title cString];
XStringListToTextProperty((char **) &text, 1, &prop);
XSetWMName(_display, _window, &prop);
@@ -5983,7 +6075,7 @@ index f4925b66..730d4692 100644
}
- (void) setHasShadow: (BOOL) value {
@@ -488,6 +1188,25 @@ static NSData *makeWindowIcon() {
@@ -488,6 +1196,25 @@ static NSData *makeWindowIcon() {
_level = value;
}
@@ -6009,7 +6101,7 @@ index f4925b66..730d4692 100644
- (void) setOpaque: (BOOL) value {
_isOpaque = value;
}
@@ -543,6 +1262,21 @@ static NSData *makeWindowIcon() {
@@ -543,6 +1270,21 @@ static NSData *makeWindowIcon() {
- (void) makeKey {
[self ensureMapped];
XRaiseWindow(_display, _window);
@@ -6031,7 +6123,7 @@ index f4925b66..730d4692 100644
}
- (void) makeMain {
@@ -559,7 +1293,29 @@ static NSData *makeWindowIcon() {
@@ -559,7 +1301,29 @@ static NSData *makeWindowIcon() {
XRaiseWindow(_display, _window);
}
@@ -6061,7 +6153,7 @@ index f4925b66..730d4692 100644
Atom wmState = XInternAtom(_display, "_NET_WM_STATE", FALSE);
Atom hiddenState = XInternAtom(_display, "_NET_WM_STATE_HIDDEN", FALSE);
@@ -593,8 +1349,10 @@ static NSData *makeWindowIcon() {
@@ -593,8 +1357,10 @@ static NSData *makeWindowIcon() {
if (_cglContext == NULL) {
CGLError error;
@@ -6074,7 +6166,7 @@ index f4925b66..730d4692 100644
NSLog(@"CGLCreateContext failed at %s %d with error %d", __FILE__,
__LINE__, error);
@@ -615,6 +1373,12 @@ static NSData *makeWindowIcon() {
@@ -615,6 +1381,12 @@ static NSData *makeWindowIcon() {
}
- (void) openGLFlushBuffer {
@@ -6087,7 +6179,7 @@ index f4925b66..730d4692 100644
CGLError error;
CGLContextObj prevContext = CGLGetCurrentContext();
@@ -652,7 +1416,7 @@ static NSData *makeWindowIcon() {
@@ -652,7 +1424,7 @@ static NSData *makeWindowIcon() {
}
- (O2Rect) frame {
@@ -6096,7 +6188,7 @@ index f4925b66..730d4692 100644
}
static int ignoreBadWindow(Display *display, XErrorEvent *errorEvent) {
@@ -666,6 +1430,7 @@ static int ignoreBadWindow(Display *display, XErrorEvent *errorEvent) {
@@ -666,6 +1438,7 @@ static int ignoreBadWindow(Display *display, XErrorEvent *errorEvent) {
}
- (void) frameChanged {
@@ -6104,7 +6196,7 @@ index f4925b66..730d4692 100644
XErrorHandler previousHandler = XSetErrorHandler(ignoreBadWindow);
@try {
Window root, parent;
@@ -710,19 +1475,13 @@ static int ignoreBadWindow(Display *display, XErrorEvent *errorEvent) {
@@ -710,19 +1483,13 @@ static int ignoreBadWindow(Display *display, XErrorEvent *errorEvent) {
[_deviceDictionary addEntriesFromDictionary: entries];
}
@@ -6234,7 +6326,7 @@ index 8860a087..a897e2ce 100644
- (NSArray *) orderedWindowNumbers;
diff --git a/AppKit/include/AppKit/NSEvent.h b/AppKit/include/AppKit/NSEvent.h
index 0677f2e7..d16080f4 100644
index 0677f2e7..b6daef18 100644
--- a/AppKit/include/AppKit/NSEvent.h
+++ b/AppKit/include/AppKit/NSEvent.h
@@ -83,6 +83,8 @@ typedef NS_ENUM(NSUInteger, NSEventType) {
@@ -6246,11 +6338,14 @@ index 0677f2e7..d16080f4 100644
NSAppKitSystem = 100,
@@ -212,6 +214,7 @@ enum { NSApplicationActivated = 0, NSApplicationDeactivated = 1 };
@@ -212,6 +214,10 @@ enum { NSApplicationActivated = 0, NSApplicationDeactivated = 1 };
+ (NSPoint) mouseLocation;
+ (NSEventModifierFlags) modifierFlags;
++ (NSUInteger) pressedMouseButtons;
++ (id) addLocalMonitorForEventsMatchingMask: (NSEventMask) mask
+ handler: (NSEvent * (^)(NSEvent *event)) block;
++ (void) removeMonitor: (id) eventMonitor;
- (instancetype) initWithType: (NSEventType) type
location: (NSPoint) location
+1 -1
View File
@@ -12,7 +12,7 @@ for line in (framework / 'CMakeFiles/AppKit.dir/flags.make').read_text().splitli
name, value = line.split(' = ', 1)
flags[name] = shlex.split(value)
link = shlex.split((framework / 'CMakeFiles/AppKit.dir/link.txt').read_text())
for name in ['NSResponder', 'NSApplication', 'NSWindow', 'NSDisplay', 'NSView', 'NSThemeFrame', 'NSTextInput', 'NSBitmapImageRep', 'NSEvent.subproj/NSEvent_mouse']:
for name in ['NSResponder', 'NSApplication', 'NSWindow', 'NSDisplay', 'NSView', 'NSThemeFrame', 'NSTextInput', 'NSBitmapImageRep', 'NSEvent.subproj/NSEvent', 'NSEvent.subproj/NSEvent_mouse']:
obj = project / f'build/riot-{Path(name).name}.o'
subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FLAGS'],
'-O3', '-march=native', '-mtune=generic', '-Wno-macro-redefined', '-c',
+20 -1
View File
@@ -48,4 +48,23 @@ subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FL
str(project / 'tools/riot-fullscreen-geometry-check.m'), '-o', str(probe)], check=True)
subprocess.run(['clang', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld',
'-nostdlib', '-isysroot', sdk, str(probe), '-lSystem', '-lobjc', '-framework', 'Foundation',
'-o', str(project / 'build/riot-fullscreen-geometry-check')], check=True)
'-o', str(project / 'build/riot-fullscreen-geometry-check')], check=True)
# Query the actual native hints after Cocoa title/style changes, without mapping windows.
probe = project / 'build/riot-window-decorations-check.o'
subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FLAGS'],
'-O3', '-march=native', '-mtune=generic', '-UNDEBUG', '-Wno-macro-redefined', '-c',
str(project / 'tools/riot-window-decorations-check.m'), '-o', str(probe)], check=True)
subprocess.run(['clang', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld',
'-nostdlib', '-isysroot', sdk, str(probe), '-lSystem', '-lobjc', '-framework', 'AppKit',
'-framework', 'Foundation', '-framework', 'CoreGraphics',
'-o', str(project / 'build/riot-window-decorations-check')], check=True)
# Match native AppKit's local event monitor dispatch semantics.
probe = project / 'build/riot-event-monitors-check.o'
subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FLAGS'],
'-O3', '-march=native', '-mtune=generic', '-Wno-macro-redefined', '-c',
str(project / 'tools/riot-event-monitors-check.m'), '-o', str(probe)], check=True)
subprocess.run(['clang', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld',
'-nostdlib', '-isysroot', sdk, str(probe), '-lSystem', '-lobjc', '-framework', 'AppKit',
'-framework', 'Foundation', '-o', str(project / 'build/riot-event-monitors-check')], check=True)
+98
View File
@@ -0,0 +1,98 @@
/* Local event dispatch regression; also runs unchanged against native AppKit.
* No global input, client hooks, account access, or mapped windows. */
#import <AppKit/AppKit.h>
static NSMutableString *calls;
static int failures;
@interface MonitorTestWindow : NSWindow
@end
@implementation MonitorTestWindow
- (void) sendEvent: (NSEvent *) event {
[calls appendFormat: @"W%ld", (long)[event type]];
}
@end
static void expectCalls(NSString *name, NSString *expected) {
BOOL ok = [calls isEqual: expected];
printf("%s %s got=%s expected=%s\n", ok ? "PASS" : "FAIL",
[name UTF8String], [calls UTF8String], [expected UTF8String]);
failures += !ok;
[calls setString: @""];
}
/* AppKit does not promise an order between independent registrations. Native
* ordering can change with block addresses, even on the same OS build. */
static void expectBoth(NSString *name) {
NSString *expected = [calls isEqual: @"ABW1"] ? @"ABW1" : @"BAW1";
expectCalls(name, expected);
}
int main(void) {
@autoreleasepool {
[NSApplication sharedApplication];
calls = [NSMutableString new];
NSWindow *window = [[MonitorTestWindow alloc]
initWithContentRect: NSMakeRect(100, 100, 300, 200)
styleMask: 0 backing: NSBackingStoreBuffered defer: NO];
NSEvent *down = [NSEvent mouseEventWithType: NSLeftMouseDown
location: NSMakePoint(10, 10) modifierFlags: 0 timestamp: 0
windowNumber: [window windowNumber] context: nil
eventNumber: 1 clickCount: 1 pressure: 1];
NSEvent *up = [NSEvent mouseEventWithType: NSLeftMouseUp
location: NSMakePoint(10, 10) modifierFlags: 0 timestamp: 0
windowNumber: [window windowNumber] context: nil
eventNumber: 2 clickCount: 1 pressure: 0];
id a = [NSEvent addLocalMonitorForEventsMatchingMask: NSLeftMouseDownMask
handler: ^NSEvent *(NSEvent *event) {
[calls appendString: @"A"]; return event;
}];
id b = [NSEvent addLocalMonitorForEventsMatchingMask: NSLeftMouseDownMask
handler: ^NSEvent *(NSEvent *event) {
[calls appendString: @"B"]; return event;
}];
[NSApp sendEvent: down]; expectBoth(@"multiple-monitors");
[NSApp sendEvent: up]; expectCalls(@"mask-filter", @"W2");
[NSEvent removeMonitor: b];
[NSApp sendEvent: down]; expectCalls(@"remove", @"AW1");
[NSEvent removeMonitor: a];
b = [NSEvent addLocalMonitorForEventsMatchingMask: NSLeftMouseDownMask
handler: ^NSEvent *(NSEvent *event) {
[calls appendString: @"B"]; return nil;
}];
[NSApp sendEvent: down]; expectCalls(@"consume", @"B");
[NSEvent removeMonitor: b];
b = [NSEvent addLocalMonitorForEventsMatchingMask: NSLeftMouseDownMask
handler: ^NSEvent *(NSEvent *event) {
[calls appendString: @"B"]; return up;
}];
[NSApp sendEvent: down]; expectCalls(@"replace-event", @"BW2");
[NSEvent removeMonitor: b];
__block id selfRemoving;
selfRemoving = [NSEvent addLocalMonitorForEventsMatchingMask: NSLeftMouseDownMask
handler: ^NSEvent *(NSEvent *event) {
[calls appendString: @"S"];
[NSEvent removeMonitor: selfRemoving]; return event;
}];
[NSApp sendEvent: down]; expectCalls(@"self-remove", @"SW1");
[NSApp sendEvent: down]; expectCalls(@"self-remove-next", @"W1");
__block id pendingA, pendingB;
pendingA = [NSEvent addLocalMonitorForEventsMatchingMask: NSLeftMouseDownMask
handler: ^NSEvent *(NSEvent *event) {
[calls appendString: @"A"];
[NSEvent removeMonitor: pendingB]; return event;
}];
pendingB = [NSEvent addLocalMonitorForEventsMatchingMask: NSLeftMouseDownMask
handler: ^NSEvent *(NSEvent *event) {
[calls appendString: @"B"];
[NSEvent removeMonitor: pendingA]; return event;
}];
[NSApp sendEvent: down]; expectBoth(@"remove-pending-snapshot");
[NSApp sendEvent: down]; expectCalls(@"remove-pending-next", @"W1");
[window release];
[calls release];
}
return failures ? 1 : 0;
}
+86
View File
@@ -0,0 +1,86 @@
/* Standalone Cocoa/X11 regression: no client windows, hooks or input. */
#import <AppKit/AppKit.h>
#import <AppKit/NSDisplay.h>
#include <X11/Xlib.h>
#include <assert.h>
#include <dlfcn.h>
#include <stdio.h>
@protocol DecorationProbeDisplay
- (void *)display;
@end
@interface NSObject (DecorationProbe)
- (unsigned long)windowHandle;
- (void)recreateXWindowWithAlpha:(BOOL)alpha;
@end
static BOOL undecorated(NSWindow *window) {
void *xlib = dlopen("/usr/lib/native/libX11.dylib", RTLD_LAZY);
assert(xlib);
Atom (*intern)(Display *, const char *, Bool) = dlsym(xlib, "XInternAtom");
int (*sync)(Display *, Bool) = dlsym(xlib, "XSync");
int (*get)(Display *, Window, Atom, long, long, Bool, Atom,
Atom *, int *, unsigned long *, unsigned long *, unsigned char **) =
dlsym(xlib, "XGetWindowProperty");
int (*freeData)(void *) = dlsym(xlib, "XFree");
assert(intern && sync && get && freeData);
id native = [window platformWindow];
Display *display = (Display *)[(id<DecorationProbeDisplay>)[NSDisplay currentDisplay] display];
sync(display, False);
Atom type = None;
int format = 0;
unsigned long count = 0, remaining = 0;
unsigned char *data = NULL;
int status = get(display, [native windowHandle], intern(display, "_MOTIF_WM_HINTS", False),
0, 5, False, AnyPropertyType, &type, &format, &count, &remaining, &data);
assert(status == Success);
BOOL result = format == 32 && count >= 3 && data &&
(((unsigned long *)data)[0] & 2) && ((unsigned long *)data)[2] == 0;
if (data) freeData(data);
dlclose(xlib);
return result;
}
static void assertDecoration(NSWindow *window, BOOL expected, const char *name) {
BOOL actual = undecorated(window);
printf("WINDOW_DECORATION case=%s undecorated=%d expected=%d\n", name, actual, expected);
assert(actual == expected);
}
int main(void) {
setbuf(stdout, NULL);
@autoreleasepool {
[NSApplication sharedApplication];
NSWindow *window = [[NSWindow alloc] initWithContentRect:NSMakeRect(20, 20, 320, 180)
styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO];
assertDecoration(window, NO, "ordinary titled window");
[window setTitleVisibility:1];
assertDecoration(window, NO, "hidden title alone preserves frame");
[window setTitlebarAppearsTransparent:YES];
assertDecoration(window, YES, "hidden and transparent title removes frame");
[window setTitleVisibility:0];
assertDecoration(window, NO, "visible title restores frame");
[window setTitleVisibility:1];
[window setTitlebarAppearsTransparent:NO];
assertDecoration(window, NO, "opaque title restores frame");
[window setStyleMask:NSTexturedBackgroundWindowMask];
assertDecoration(window, YES, "untitled with extra flags");
[window setStyleMask:NSTitledWindowMask];
assertDecoration(window, NO, "titled style restored");
[window setStyleMask:NSBorderlessWindowMask];
assertDecoration(window, YES, "borderless after creation");
[window setStyleMask:NSTitledWindowMask];
[window setTitlebarAppearsTransparent:YES];
[(id)[window platformWindow] recreateXWindowWithAlpha:NO];
assertDecoration(window, YES, "recreated native window retains client chrome");
NSWindow *deferred = [[NSWindow alloc] initWithContentRect:NSMakeRect(20, 20, 320, 180)
styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:YES];
[deferred setTitleVisibility:1];
[deferred setTitlebarAppearsTransparent:YES];
assertDecoration(deferred, YES, "properties set before native creation");
puts("WINDOW_DECORATIONS_CHECK cases=10 pass=1");
// Windows stay unmapped: the probe never changes compositor focus.
}
return 0;
}