mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
Implement Cocoa local event monitors for native client dragging
This commit is contained in:
1 parent
41da6d9e0a
commit
8f97990d03
4 files changed
+220
-7
No files matched your search
@@ -119,6 +119,38 @@ 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
|
||||
|
||||
+81
-7
@@ -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
|
||||
@@ -6184,7 +6255,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) {
|
||||
@@ -6196,11 +6267,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
|
||||
|
||||
@@ -59,3 +59,12 @@ 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)
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user