mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-27 19:54:38 +00:00
Adapt the geometry fix from PR #12 to the current relative-mouse implementation. Keep display IDs independent of primary-first screen ordering, translate window and pointer coordinates consistently, and invalidate geometry on both RandR event types. Handle failed RandR snapshots and free EDID data. Add isolated native geometry regression coverage and use the configured SDK when linking the X11 probe. Validated all 30 component patches, the X11 and CoreGraphics builds, six native geometry groups, and the existing relative-mouse checks. The user confirmed the installed fix works on 2026-10-16. Co-authored-by: Maka <[email protected]>
9897 lines
395 KiB
Diff
9897 lines
395 KiB
Diff
diff --git a/AppKit/CMakeLists.txt b/AppKit/CMakeLists.txt
|
|
index 393a1eb3..6a47ac89 100644
|
|
--- a/AppKit/CMakeLists.txt
|
|
+++ b/AppKit/CMakeLists.txt
|
|
@@ -1,5 +1,8 @@
|
|
project(AppKit)
|
|
|
|
+# NSSpellChecker's asynchronous request API accepts a native completion block.
|
|
+set_source_files_properties(NSSpellChecker.m PROPERTIES COMPILE_FLAGS "-fblocks")
|
|
+
|
|
include(darling_framework)
|
|
|
|
remove_sdk_framework(AppKit)
|
|
@@ -619,6 +622,7 @@ add_backend(X11
|
|
X11
|
|
XRandR
|
|
Xcursor
|
|
+ Xext
|
|
fontconfig
|
|
xkbfile
|
|
)
|
|
diff --git a/AppKit/MacWorkspace.m b/AppKit/MacWorkspace.m
|
|
index ba52cd3b..d566b946 100644
|
|
--- a/AppKit/MacWorkspace.m
|
|
+++ b/AppKit/MacWorkspace.m
|
|
@@ -13,6 +13,32 @@
|
|
|
|
@implementation MacWorkspace
|
|
|
|
+/* darling-lol M57: Chromium's NativeThemeMac / accessibility display code
|
|
+ * calls these NSWorkspace accessibility-display getters. Cocotron did not
|
|
+ * implement them, so the first call raised -[MacWorkspace
|
|
+ * accessibilityDisplayShouldIncreaseContrast]: unrecognized selector and
|
|
+ * aborted the Electron UX right after createWindow(). Report the default
|
|
+ * (no accessibility overrides) like macOS does when the features are off. */
|
|
+- (BOOL) accessibilityDisplayShouldIncreaseContrast {
|
|
+ return NO;
|
|
+}
|
|
+
|
|
+- (BOOL) accessibilityDisplayShouldReduceMotion {
|
|
+ return NO;
|
|
+}
|
|
+
|
|
+- (BOOL) accessibilityDisplayShouldReduceTransparency {
|
|
+ return NO;
|
|
+}
|
|
+
|
|
+- (BOOL) accessibilityDisplayShouldDifferentiateWithoutColor {
|
|
+ return NO;
|
|
+}
|
|
+
|
|
+- (BOOL) accessibilityDisplayShouldInvertColors {
|
|
+ return NO;
|
|
+}
|
|
+
|
|
- (NSImage *) iconForFile: (NSString *) path {
|
|
// TODO: call GetIconRefFromFileInfo()
|
|
NSUnimplementedMethod();
|
|
@@ -95,9 +121,9 @@
|
|
}
|
|
|
|
- (BOOL) openURL: (NSURL *) url {
|
|
- // TODO: Call LSOpenFromURLSpec()
|
|
- NSUnimplementedMethod();
|
|
- return NO;
|
|
+ if (url == nil)
|
|
+ return NO;
|
|
+ return LSOpenCFURLRef((CFURLRef) url, NULL) == noErr;
|
|
}
|
|
|
|
- (BOOL) selectFile: (NSString *) path
|
|
diff --git a/AppKit/NSAccessibility.m b/AppKit/NSAccessibility.m
|
|
index 39d1b104..3662bc4b 100644
|
|
--- a/AppKit/NSAccessibility.m
|
|
+++ b/AppKit/NSAccessibility.m
|
|
@@ -13,6 +13,8 @@ NSString *const NSAccessibilityEnabledAttribute =
|
|
@"NSAccessibilityEnabledAttribute";
|
|
NSString *const NSAccessibilityFocusedAttribute =
|
|
@"NSAccessibilityFocusedAttribute";
|
|
+NSString *const NSAccessibilityRequiredAttribute =
|
|
+ @"AXRequired";
|
|
NSString *const NSAccessibilityHelpAttribute = @"NSAccessibilityHelpAttribute";
|
|
NSString *const NSAccessibilityMaxValueAttribute =
|
|
@"NSAccessibilityMaxValueAttribute";
|
|
diff --git a/AppKit/NSAccessibilityRemoteUIElement.m b/AppKit/NSAccessibilityRemoteUIElement.m
|
|
index d543e329..e320a3af 100644
|
|
--- a/AppKit/NSAccessibilityRemoteUIElement.m
|
|
+++ b/AppKit/NSAccessibilityRemoteUIElement.m
|
|
@@ -23,6 +23,38 @@
|
|
|
|
@implementation NSAccessibilityRemoteUIElement
|
|
|
|
+/* darling-lol M57: these must return a real object. The previous
|
|
+ * forwardInvocation fallback returned an uninitialized return buffer, so
|
|
+ * Chromium's RemoteAccessibility got a garbage pointer from -initWithRemoteToken:
|
|
+ * and crashed shortly after. */
|
|
+- (id) init
|
|
+{
|
|
+ return [super init];
|
|
+}
|
|
+
|
|
+- (id) initWithRemoteToken: (id) token
|
|
+{
|
|
+ return [self init];
|
|
+}
|
|
+
|
|
++ (id) remoteTokenForLocalUIElement: (id) element
|
|
+{
|
|
+ /* Chromium treats the return value as NSData and reads [data bytes] /
|
|
+ * [data length]. Returning a non-NSData object made those land in
|
|
+ * -forwardInvocation: (uninitialized return) -> a huge length ->
|
|
+ * std::vector OOM. An empty NSData cleanly disables remote accessibility. */
|
|
+ return [NSData data];
|
|
+}
|
|
+
|
|
++ (void) setRemoteUIApp: (id) app
|
|
+{
|
|
+ /* Remote (cross-process) accessibility is not supported; accept and ignore. */
|
|
+}
|
|
+
|
|
++ (void) registerRemoteUIProcessIdentifier: (int) pid
|
|
+{
|
|
+}
|
|
+
|
|
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
|
|
{
|
|
return [NSMethodSignature signatureWithObjCTypes: "v@:"];
|
|
diff --git a/AppKit/NSApplication.m b/AppKit/NSApplication.m
|
|
index 8e7508e2..6fc86b32 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
|
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
|
|
+#if defined(DARLING)
|
|
+#include <mach/mach_time.h>
|
|
+#endif
|
|
#import <AppKit/NSAlert.h>
|
|
#import <AppKit/NSApplication.h>
|
|
#import <AppKit/NSColorPanel.h>
|
|
@@ -271,6 +274,11 @@ NSApplication *NSApp = nil;
|
|
return _isRunning;
|
|
}
|
|
|
|
+/* darling-lol M57: Chromium/Electron queries NSApp for full keyboard access. */
|
|
+- (BOOL) isFullKeyboardAccessEnabled {
|
|
+ return NO;
|
|
+}
|
|
+
|
|
- (NSWindow *) makeWindowsPerform: (SEL) selector inOrder: (BOOL) inOrder {
|
|
NSUnimplementedMethod();
|
|
return nil;
|
|
@@ -382,7 +390,18 @@ NSApplication *NSApp = nil;
|
|
- (void) setDelegate: delegate {
|
|
if (delegate != _delegate) {
|
|
[self unregisterDelegate];
|
|
- _delegate = delegate;
|
|
+ /* darling-lol: retain the delegate (Cocoa specifies assign, but a
|
|
+ * non-retained delegate is a dangling-observer trap here).
|
|
+ * RiotClientServices' nib-loaded delegate was freed between
|
|
+ * NSApplicationWillFinishLaunching and NSApplicationDidFinishLaunching
|
|
+ * (pool drain); the recycled memory (an __NSCFString) then received
|
|
+ * -applicationDidFinishLaunching:, whose caught exception stalled the
|
|
+ * app before Electron ever started. Retaining turns that crash into
|
|
+ * at most a one-object leak, and keeps unregisterDelegate messaging
|
|
+ * a live object. NSApplication is a singleton (no -dealloc), so no
|
|
+ * matching release is required. */
|
|
+ [_delegate release];
|
|
+ _delegate = [delegate retain];
|
|
[self registerDelegate];
|
|
}
|
|
}
|
|
@@ -550,6 +569,13 @@ NSApplication *NSApp = nil;
|
|
}
|
|
|
|
- (void) finishLaunching {
|
|
+ // AppKit guarantees that launch notifications are delivered once. Some
|
|
+ // clients call finishLaunching from their delegate path and NSApplication
|
|
+ // -run calls it as well, so make the operation explicitly idempotent.
|
|
+ if (_finishedLaunching)
|
|
+ return;
|
|
+ _finishedLaunching = YES;
|
|
+
|
|
NSAutoreleasePool *pool = [NSAutoreleasePool new];
|
|
BOOL needsUntitled = YES;
|
|
|
|
@@ -558,7 +584,6 @@ NSApplication *NSApp = nil;
|
|
object: self];
|
|
NS_HANDLER [self reportException: localException];
|
|
NS_ENDHANDLER
|
|
-
|
|
// Load the application icon if we have one
|
|
NSString *iconName = [[[NSBundle mainBundle] infoDictionary]
|
|
objectForKey: @"CFBundleIconFile"];
|
|
@@ -614,7 +639,6 @@ NSApplication *NSApp = nil;
|
|
object: self];
|
|
NS_HANDLER [self reportException: localException];
|
|
NS_ENDHANDLER
|
|
-
|
|
[pool release];
|
|
}
|
|
|
|
@@ -652,7 +676,15 @@ NSApplication *NSApp = nil;
|
|
}
|
|
}
|
|
|
|
- [self terminate: self];
|
|
+ // darling-lol: match AppKit and only terminate when the delegate opts
|
|
+ // in via applicationShouldTerminateAfterLastWindowClosed:. The previous
|
|
+ // unconditional terminate killed faceless/splash-screen apps (notably
|
|
+ // the Riot launcher, whose splash closes right after install verify)
|
|
+ // on the first runloop turn with no visible window.
|
|
+ if ([_delegate respondsToSelector: @selector(applicationShouldTerminateAfterLastWindowClosed:)] &&
|
|
+ [_delegate applicationShouldTerminateAfterLastWindowClosed: self]) {
|
|
+ [self terminate: self];
|
|
+ }
|
|
}
|
|
|
|
- (void) _checkForAppActivation {
|
|
@@ -666,17 +698,13 @@ NSApplication *NSApp = nil;
|
|
}
|
|
|
|
- (void) run {
|
|
- static BOOL didlaunch = NO;
|
|
NSAutoreleasePool *pool;
|
|
|
|
_isRunning = YES;
|
|
|
|
- if (!didlaunch) {
|
|
- didlaunch = YES;
|
|
- pool = [NSAutoreleasePool new];
|
|
- [self finishLaunching];
|
|
- [pool release];
|
|
- }
|
|
+ pool = [NSAutoreleasePool new];
|
|
+ [self finishLaunching];
|
|
+ [pool release];
|
|
|
|
do {
|
|
pool = [NSAutoreleasePool new];
|
|
@@ -716,6 +744,12 @@ NSApplication *NSApp = nil;
|
|
}
|
|
|
|
- (void) sendEvent: (NSEvent *) event {
|
|
+ static int inputTrace = -1;
|
|
+ if (inputTrace < 0) inputTrace = getenv("RCLOL_INPUT_TRACE") != NULL;
|
|
+ if (inputTrace && ([event type] == NSKeyDown || [event type] == NSLeftMouseDown))
|
|
+ NSLog(@"LOLINPUT app type=%ld eventWindow=%@ number=%ld", (long)[event type],
|
|
+ [[event window] class], (long)[event windowNumber]);
|
|
+
|
|
if ([event type] == NSKeyDown) {
|
|
unsigned modifierFlags = [event modifierFlags];
|
|
|
|
@@ -742,6 +776,61 @@ NSApplication *NSApp = nil;
|
|
do {
|
|
NSAutoreleasePool *pool = [NSAutoreleasePool new];
|
|
|
|
+ /* Service the run loop (and with it the main libdispatch queue)
|
|
+ * on every poll, matching AppKit: manual event pumping must
|
|
+ * still deliver main-queue blocks. Without this, apps that
|
|
+ * poll with distantPast while awaiting dispatch_async(main)
|
|
+ * completions deadlock. Zero timeout: never block here, the
|
|
+ * X11 wait below handles untilDate.
|
|
+ * Note: map NSDefaultRunLoopMode to kCFRunLoopDefaultMode;
|
|
+ * they are distinct strings and the main-queue source lives
|
|
+ * in the latter, so passing NSDefaultRunLoopMode through
|
|
+ * verbatim would service nothing. */
|
|
+ {
|
|
+#if defined(DARLING)
|
|
+ /* darling-lol: the game polls this method ~10x per frame. Each run-loop pass costs several darlingserver
|
|
+ * round trips, so service the CF run loop at most once per 4 ms on zero-timeout polls;
|
|
+ * X11 input is still read on every call. Blocking waits (future deadline) always service it. */
|
|
+ extern BOOL _NSDisplayRunLoopServicedByApp;
|
|
+ static uint64_t lastService;
|
|
+ static mach_timebase_info_data_t tb;
|
|
+ if (tb.denom == 0) mach_timebase_info(&tb);
|
|
+ uint64_t nowTicks = mach_absolute_time();
|
|
+ BOOL poll = [untilDate timeIntervalSinceNow] <= 0.0;
|
|
+ BOOL due = !poll || (nowTicks - lastService) * tb.numer / tb.denom >= 4000000ULL;
|
|
+ if (due) lastService = nowTicks;
|
|
+ _NSDisplayRunLoopServicedByApp = YES; /* NSDisplay skips its own zero-timeout pass after this poll */
|
|
+ if (due) {
|
|
+#endif
|
|
+ CFStringRef rlMode;
|
|
+ if (mode == nil || [mode isEqual:NSDefaultRunLoopMode])
|
|
+ rlMode = kCFRunLoopDefaultMode;
|
|
+ else
|
|
+ rlMode = (CFStringRef)mode;
|
|
+ (void)CFRunLoopRunInMode(rlMode, 0.0, true);
|
|
+#if defined(DARLING)
|
|
+ }
|
|
+ /* Deliver ready main-queue work on every event poll. A render
|
|
+ * worker can queue a presentation just after the last CF pass;
|
|
+ * gating this drain for 4 ms can defer it to the next game frame
|
|
+ * and produce a late frame followed by a burst. The empty-queue
|
|
+ * drain returns immediately; keep only CFRunLoop throttled.
|
|
+ *
|
|
+ * darling-lol M14i: drain the libdispatch main queue explicitly.
|
|
+ * Evidence (live counters, 2026-09-16): LOLPUSH>0 with
|
|
+ * LOLDRAIN==0 and CFRunLoopRunInMode(0s)==Finished in the
|
|
+ * launcher hot path — Darling's dispatch->CF wakeup never
|
|
+ * connects the main-queue handle to this runloop's source
|
|
+ * set, so queued main-queue blocks are unreachable while an
|
|
+ * app poll-loops instead of -run. Apple's CF services them
|
|
+ * during manual pumping via this exact hook; do the same. */
|
|
+ {
|
|
+ extern void _dispatch_main_queue_callback_4CF(void *);
|
|
+ _dispatch_main_queue_callback_4CF(NULL);
|
|
+ }
|
|
+#endif
|
|
+ }
|
|
+
|
|
NS_DURING [NSClassFromString(@"Win32RunningCopyPipe")
|
|
performSelector: @selector(createRunningCopyPipe)];
|
|
|
|
diff --git a/AppKit/NSBitmapImageRep.m b/AppKit/NSBitmapImageRep.m
|
|
index 9a3f6a5d..dafb0cbe 100644
|
|
--- a/AppKit/NSBitmapImageRep.m
|
|
+++ b/AppKit/NSBitmapImageRep.m
|
|
@@ -795,6 +795,11 @@ NSBitmapImageRepPropertyKey NSImageCurrentFrame = @"NSImageCurrentFrame";
|
|
|
|
- (CGBitmapInfo) CGBitmapInfo {
|
|
CGBitmapInfo result = kCGBitmapByteOrderDefault;
|
|
+ // AppKit's byte samples are stored in component order (RGBA), whereas
|
|
+ // Quartz's default word order becomes ABGR on a little-endian host.
|
|
+ if (_bitsPerSample == 8 && _bitsPerPixel == 32 &&
|
|
+ !(_bitmapFormat & NSAlphaFirstBitmapFormat))
|
|
+ result = kCGBitmapByteOrder32Big;
|
|
|
|
if (![self hasAlpha])
|
|
result |= kCGImageAlphaNone;
|
|
diff --git a/AppKit/NSColor.subproj/NSColor.m b/AppKit/NSColor.subproj/NSColor.m
|
|
index fb72c982..6a80b13f 100644
|
|
--- a/AppKit/NSColor.subproj/NSColor.m
|
|
+++ b/AppKit/NSColor.subproj/NSColor.m
|
|
@@ -21,6 +21,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
#import <AppKit/NSColor.h>
|
|
#import <AppKit/NSColor_CGColor.h>
|
|
#import <AppKit/NSColor_catalog.h>
|
|
+#import <AppKit/NSColorSpace.h>
|
|
#import <AppKit/NSImage.h>
|
|
#import <AppKit/NSRaise.h>
|
|
|
|
@@ -43,6 +44,12 @@ NSNotificationName const NSSystemColorsDidChangeNotification = @"NSSystemColorsD
|
|
|
|
@implementation NSColor
|
|
|
|
+/* darling-lol M57: Chromium queries the system control tint to pick the
|
|
+ * native theme; Cocotron lacked the class method entirely. */
|
|
++ (NSUInteger) currentControlTint {
|
|
+ return NSBlueControlTint;
|
|
+}
|
|
+
|
|
- (void) encodeWithCoder: (NSCoder *) coder {
|
|
|
|
if ([coder allowsKeyedCoding]) {
|
|
@@ -916,6 +923,39 @@ static void releasePatternInfo(void *info) {
|
|
return nil;
|
|
}
|
|
|
|
+- (NSColor *) colorUsingColorSpace: (NSColorSpace *) space
|
|
+{
|
|
+ /* darling-lol: bridge NSColorSpace objects to the Name-based conversion
|
|
+ * subclasses implement. Cocotron's NSColorSpace carries only a
|
|
+ * CGColorSpaceRef, so map by color model to the closest space our
|
|
+ * conversion matrix handles. Chromium calls this on catalog colors
|
|
+ * during UI setup; previously it raised "unrecognized selector" and
|
|
+ * killed the app. Fail soft: unknown/nil space returns self. */
|
|
+ if (space == nil)
|
|
+ return self;
|
|
+
|
|
+ CGColorSpaceRef cgSpace = [space CGColorSpace];
|
|
+ if (cgSpace == NULL)
|
|
+ return self;
|
|
+
|
|
+ NSColorSpaceName name = nil;
|
|
+ switch (CGColorSpaceGetModel(cgSpace)) {
|
|
+ case kCGColorSpaceModelRGB:
|
|
+ name = NSDeviceRGBColorSpace;
|
|
+ break;
|
|
+ case kCGColorSpaceModelMonochrome:
|
|
+ name = NSDeviceWhiteColorSpace;
|
|
+ break;
|
|
+ case kCGColorSpaceModelCMYK:
|
|
+ name = NSDeviceCMYKColorSpace;
|
|
+ break;
|
|
+ default:
|
|
+ return self;
|
|
+ }
|
|
+ NSColor *converted = [self colorUsingColorSpaceName: name];
|
|
+ return (converted != nil) ? converted : self;
|
|
+}
|
|
+
|
|
- (NSColor *) blendedColorWithFraction: (CGFloat) fraction
|
|
ofColor: (NSColor *) color
|
|
{
|
|
diff --git a/AppKit/NSColor.subproj/NSColor_CGColor.m b/AppKit/NSColor.subproj/NSColor_CGColor.m
|
|
index 0aac5eec..1aa01fc8 100644
|
|
--- a/AppKit/NSColor.subproj/NSColor_CGColor.m
|
|
+++ b/AppKit/NSColor.subproj/NSColor_CGColor.m
|
|
@@ -188,6 +188,15 @@ static inline CGFloat calibratedWhiteFromRGB(CGFloat r, CGFloat g, CGFloat b) {
|
|
REC601_LUMINANCE_B * b);
|
|
}
|
|
|
|
+- (NSColor *) colorUsingColorSpaceName: (NSString *) otherSpaceName
|
|
+{
|
|
+ /* darling-lol: the single-arg variant was missing, so any caller
|
|
+ * (including -[NSColor colorUsingColorSpace:] and NSColor_catalog)
|
|
+ * fell through to the abstract base and raised. Forward to the
|
|
+ * two-arg implementation. */
|
|
+ return [self colorUsingColorSpaceName: otherSpaceName device: nil];
|
|
+}
|
|
+
|
|
- (NSColor *) colorUsingColorSpaceName: (NSString *) otherSpaceName
|
|
device: (NSDictionary *) device
|
|
{
|
|
@@ -224,6 +233,35 @@ static inline CGFloat calibratedWhiteFromRGB(CGFloat r, CGFloat g, CGFloat b) {
|
|
yellow: 0
|
|
black: 1 - white
|
|
alpha: alpha];
|
|
+ } else if ([_colorSpaceName isEqualToString: NSNamedColorSpace]) {
|
|
+ /* darling-lol: +colorWithSRGBRed:... stores a device-RGB CGColor
|
|
+ * tagged NSNamedColorSpace. Treat components as RGB so catalog
|
|
+ * colors (e.g. controlAccentBlueColor) convert instead of
|
|
+ * returning nil. */
|
|
+ CGFloat red = components[0];
|
|
+ CGFloat green = components[1];
|
|
+ CGFloat blue = components[2];
|
|
+ CGFloat alpha = components[3];
|
|
+
|
|
+ if ([otherSpaceName isEqualToString: NSDeviceRGBColorSpace] ||
|
|
+ [otherSpaceName isEqualToString: NSCalibratedRGBColorSpace])
|
|
+ return [NSColor colorWithCalibratedRed: red
|
|
+ green: green
|
|
+ blue: blue
|
|
+ alpha: alpha];
|
|
+
|
|
+ if ([otherSpaceName isEqualToString: NSDeviceWhiteColorSpace] ||
|
|
+ [otherSpaceName isEqualToString: NSCalibratedWhiteColorSpace])
|
|
+ return [NSColor colorWithCalibratedWhite: calibratedWhiteFromRGB(
|
|
+ red, green, blue)
|
|
+ alpha: alpha];
|
|
+
|
|
+ if ([otherSpaceName isEqualToString: NSDeviceCMYKColorSpace])
|
|
+ return [NSColor colorWithDeviceCyan: 1.0 - red
|
|
+ magenta: 1.0 - green
|
|
+ yellow: 1.0 - blue
|
|
+ black: 0.0
|
|
+ alpha: alpha];
|
|
} else if ([_colorSpaceName isEqualToString: NSDeviceRGBColorSpace]) {
|
|
CGFloat red = components[0];
|
|
CGFloat green = components[1];
|
|
diff --git a/AppKit/NSColor.subproj/NSColor_catalog.m b/AppKit/NSColor.subproj/NSColor_catalog.m
|
|
index 7361d09a..4bdd88cf 100644
|
|
--- a/AppKit/NSColor.subproj/NSColor_catalog.m
|
|
+++ b/AppKit/NSColor.subproj/NSColor_catalog.m
|
|
@@ -19,6 +19,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
|
|
#import <AppKit/NSColor_CGColor.h>
|
|
#import <AppKit/NSColor_catalog.h>
|
|
+#import <AppKit/NSColorSpace.h>
|
|
#import <AppKit/NSDisplay.h>
|
|
#import <AppKit/NSGraphics.h>
|
|
|
|
@@ -128,11 +129,49 @@ NSColor *NSColorGetCatalogColor(NSColorListName catalogName,
|
|
if ([colorSpace isEqualToString: [self colorSpaceName]])
|
|
return self;
|
|
|
|
- _color = [_color colorUsingColorSpaceName: colorSpace device: device];
|
|
+ /* darling-lol: never let a failed conversion nil out _color; keep the
|
|
+ * original so later setFill/CGColor still work. */
|
|
+ NSColor *converted =
|
|
+ [_color colorUsingColorSpaceName: colorSpace device: device];
|
|
+ if (converted != nil)
|
|
+ _color = converted;
|
|
|
|
return _color;
|
|
}
|
|
|
|
+- (NSColor *) colorUsingColorSpace: (NSColorSpace *) space
|
|
+{
|
|
+ /* darling-lol: NSColor_catalog never implemented the NSColorSpace-object
|
|
+ * variant, so Chromium's accent-color lookup raised "unrecognized
|
|
+ * selector" and terminated the app. Convert the wrapped color without
|
|
+ * mutating _color (unlike the Name variant above); fall back to self so
|
|
+ * callers always get a usable color. */
|
|
+ if (space == nil || _color == nil)
|
|
+ return self;
|
|
+
|
|
+ CGColorSpaceRef cgSpace = [space CGColorSpace];
|
|
+ NSColor *converted = nil;
|
|
+ if (cgSpace != NULL) {
|
|
+ NSColorSpaceName name = nil;
|
|
+ switch (CGColorSpaceGetModel(cgSpace)) {
|
|
+ case kCGColorSpaceModelRGB:
|
|
+ name = NSDeviceRGBColorSpace;
|
|
+ break;
|
|
+ case kCGColorSpaceModelMonochrome:
|
|
+ name = NSDeviceWhiteColorSpace;
|
|
+ break;
|
|
+ case kCGColorSpaceModelCMYK:
|
|
+ name = NSDeviceCMYKColorSpace;
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+ if (name != nil)
|
|
+ converted = [_color colorUsingColorSpaceName: name];
|
|
+ }
|
|
+ return (converted != nil) ? converted : self;
|
|
+}
|
|
+
|
|
- (CGColorRef) CGColorRef {
|
|
return [self CGColor];
|
|
}
|
|
diff --git a/AppKit/NSCursor.m b/AppKit/NSCursor.m
|
|
index 4cd74633..9be5239a 100644
|
|
--- a/AppKit/NSCursor.m
|
|
+++ b/AppKit/NSCursor.m
|
|
@@ -365,12 +365,9 @@ static NSMutableArray *_cursorStack = nil;
|
|
}
|
|
|
|
+ (void) setHiddenUntilMouseMoves: (BOOL) flag {
|
|
- if (flag)
|
|
- [self hide];
|
|
- else
|
|
- [self unhide];
|
|
+ [[NSDisplay currentDisplay] setCursorHiddenUntilMouseMoves: flag];
|
|
}
|
|
|
|
- initWithImage: (NSImage *) image
|
|
foregroundColorHint: (NSColor *) foregroundHint
|
|
backgroundColorHint: (NSColor *) backgroundHint
|
|
diff --git a/AppKit/NSDisplay.m b/AppKit/NSDisplay.m
|
|
index cacd8999..13faf0f7 100644
|
|
--- a/AppKit/NSDisplay.m
|
|
+++ b/AppKit/NSDisplay.m
|
|
@@ -43,6 +43,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
return NSThreadSharedInstance(@"NSDisplay");
|
|
}
|
|
|
|
+- (BOOL) activateProcessIdentifier: (pid_t) processIdentifier {
|
|
+ return NO;
|
|
+}
|
|
+
|
|
- (instancetype) init {
|
|
if ([self class] != [NSDisplay class]) {
|
|
// Initializing a concrete subclass.
|
|
@@ -135,6 +139,26 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
- (void) unhideCursor {
|
|
NSInvalidAbstractInvocation();
|
|
}
|
|
|
|
+- (void) setCursorHiddenUntilMouseMoves: (BOOL) flag {
|
|
+ if (_cursorHiddenUntilMouseMoves == flag)
|
|
+ return;
|
|
+
|
|
+ _cursorHiddenUntilMouseMoves = flag;
|
|
+ if (flag)
|
|
+ [self hideCursor];
|
|
+ else
|
|
+ [self unhideCursor];
|
|
+}
|
|
+
|
|
+// A backend calls this when the pointer genuinely moves; it cancels a
|
|
+// pending -setCursorHiddenUntilMouseMoves:YES, matching AppKit.
|
|
+- (void) pointerMoved {
|
|
+ if (_cursorHiddenUntilMouseMoves) {
|
|
+ _cursorHiddenUntilMouseMoves = NO;
|
|
+ [self unhideCursor];
|
|
+ }
|
|
+}
|
|
+
|
|
// Arrow, IBeam, HorizontalResize, VerticalResize
|
|
- (id) cursorWithName: (NSString *) name {
|
|
@@ -146,28 +170,46 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
NSInvalidAbstractInvocation();
|
|
}
|
|
|
|
+#ifdef DARLING
|
|
+BOOL _NSDisplayRunLoopServicedByApp = NO;
|
|
+#endif
|
|
+
|
|
- (NSEvent *) nextEventMatchingMask: (NSEventMask) mask
|
|
untilDate: (NSDate *) untilDate
|
|
inMode: (NSString *) mode
|
|
dequeue: (BOOL) dequeue
|
|
{
|
|
NSEvent *result = nil;
|
|
+ static int inputTrace = -1;
|
|
+ if (inputTrace < 0) inputTrace = getenv("RCLOL_INPUT_TRACE") != NULL;
|
|
|
|
if ([_eventQueue count])
|
|
untilDate = [NSDate date];
|
|
|
|
+#ifdef DARLING
|
|
+ /* darling-lol: NSApplication already ran the run loop (zero timeout) and drained the main queue for this poll;
|
|
+ * a second zero-timeout pass costs two darlingserver round trips (mach_msg poll) per call for nothing. */
|
|
+ BOOL serviced = _NSDisplayRunLoopServicedByApp;
|
|
+ _NSDisplayRunLoopServicedByApp = NO;
|
|
+ if (!(serviced && [untilDate timeIntervalSinceNow] <= 0.0005))
|
|
+#endif
|
|
[[NSRunLoop currentRunLoop] runMode: mode beforeDate: untilDate];
|
|
|
|
- while (result == nil && [_eventQueue count] > 0) {
|
|
- NSEvent *event = _eventQueue[0];
|
|
+ // A filtered poll must leave events of other types queued. Game loops
|
|
+ // interleave mouse and keyboard masks; discarding unmatched events here
|
|
+ // silently loses keys and button transitions.
|
|
+ for (NSUInteger index = 0; index < [_eventQueue count]; ++index) {
|
|
+ NSEvent *event = _eventQueue[index];
|
|
+ if (inputTrace && ([event type] == NSKeyDown || [event type] == NSLeftMouseDown))
|
|
+ NSLog(@"LOLINPUT queue type=%ld mask=%llu eventWindow=%@ number=%ld",
|
|
+ (long)[event type], (unsigned long long)mask, [[event window] class],
|
|
+ (long)[event windowNumber]);
|
|
|
|
- if (!(NSEventMaskFromType([event type]) & mask)) {
|
|
- [_eventQueue removeObjectAtIndex: 0];
|
|
- } else {
|
|
+ if (NSEventMaskFromType([event type]) & mask) {
|
|
result = [[event retain] autorelease];
|
|
-
|
|
if (dequeue)
|
|
- [_eventQueue removeObjectAtIndex: 0];
|
|
+ [_eventQueue removeObjectAtIndex: index];
|
|
+ break;
|
|
}
|
|
}
|
|
|
|
@@ -193,7 +215,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
}
|
|
|
|
while (--count >= 0) {
|
|
- if (NSEventMaskFromType([event type]) & mask) {
|
|
+ NSEvent *queuedEvent = [_eventQueue objectAtIndex: count];
|
|
+ if (NSEventMaskFromType([queuedEvent type]) & mask) {
|
|
[_eventQueue removeObjectAtIndex: count];
|
|
}
|
|
}
|
|
@@ -310,6 +333,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
return 0;
|
|
}
|
|
|
|
+- (NSUInteger) pressedMouseButtons {
|
|
+ return 0;
|
|
+}
|
|
+
|
|
- (NSArray *) orderedWindowNumbers {
|
|
NSInvalidAbstractInvocation();
|
|
return nil;
|
|
diff --git a/AppKit/NSEvent.subproj/NSEvent.m b/AppKit/NSEvent.subproj/NSEvent.m
|
|
index d0bad986..d0b1b4aa 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. */
|
|
return [[NSDisplay currentDisplay] currentModifierFlags];
|
|
}
|
|
|
|
++ (NSUInteger) pressedMouseButtons {
|
|
+ return [[NSDisplay currentDisplay] pressedMouseButtons];
|
|
+}
|
|
+
|
|
- (instancetype) initWithType: (NSEventType) type
|
|
location: (NSPoint) location
|
|
modifierFlags: (NSEventModifierFlags) modifierFlags
|
|
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
|
|
+++ b/AppKit/NSEvent.subproj/NSEvent_mouse.m
|
|
@@ -18,6 +18,11 @@ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
#import <AppKit/NSEvent_mouse.h>
|
|
+#import <CoreGraphics/CGEvent.h>
|
|
+
|
|
+@interface NSObject (MouseCGEventFlags)
|
|
+- (void)setFlags:(CGEventFlags)flags;
|
|
+@end
|
|
|
|
@implementation NSEvent_mouse
|
|
|
|
@@ -34,6 +39,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
modifierFlags: modifierFlags
|
|
window: window];
|
|
|
|
+ _buttonNumber = (type == NSRightMouseDown || type == NSRightMouseUp || type == NSRightMouseDragged) ? 1 :
|
|
+ (type == NSOtherMouseDown || type == NSOtherMouseUp || type == NSOtherMouseDragged) ? 2 : 0;
|
|
_clickCount = clickCount;
|
|
_deltaX = deltaX;
|
|
_deltaY = deltaY;
|
|
@@ -45,6 +52,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
return _clickCount;
|
|
}
|
|
|
|
+- (short) subtype {
|
|
+ // Ordinary mouse events use NSMouseEventSubtype (0). The base method
|
|
+ // raises for event classes that do not have a subtype; Chromium queries
|
|
+ // this before forwarding every mouse event to its renderer.
|
|
+ return 0;
|
|
+}
|
|
+
|
|
- (instancetype) initWithType: (NSEventType) type
|
|
location: (NSPoint) location
|
|
modifierFlags: (NSEventModifierFlags) modifierFlags
|
|
@@ -96,6 +110,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
location: location
|
|
modifierFlags: modifierFlags
|
|
window: (id) windowNumber];
|
|
+ _buttonNumber = (type == NSRightMouseDown || type == NSRightMouseUp || type == NSRightMouseDragged) ? 1 :
|
|
+ (type == NSOtherMouseDown || type == NSOtherMouseUp || type == NSOtherMouseDragged) ? 2 : 0;
|
|
_clickCount = clickCount;
|
|
return self;
|
|
}
|
|
@@ -112,6 +128,37 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
return 0.0;
|
|
}
|
|
|
|
+// X11 wheel buttons are discrete mouse-wheel notches, without trackpad
|
|
+// gesture phases or momentum. Chromium queries these before forwarding them.
|
|
+- (NSUInteger) phase { return 0; }
|
|
+- (NSUInteger) momentumPhase { return 0; }
|
|
+- (BOOL) hasPreciseScrollingDeltas { return NO; }
|
|
+- (BOOL) isDirectionInvertedFromDevice { return NO; }
|
|
+- (CGFloat) scrollingDeltaX { return _deltaX; }
|
|
+- (CGFloat) scrollingDeltaY { return _deltaY; }
|
|
+
|
|
+- (CGEventRef) CGEvent {
|
|
+ if (_type != NSScrollWheel) {
|
|
+ switch (_type) {
|
|
+ case NSLeftMouseDown: case NSLeftMouseUp: case NSRightMouseDown: case NSRightMouseUp:
|
|
+ case NSMouseMoved: case NSLeftMouseDragged: case NSRightMouseDragged:
|
|
+ case NSOtherMouseDown: case NSOtherMouseUp: case NSOtherMouseDragged: {
|
|
+ CGEventRef event = CGEventCreateMouseEvent(NULL, (CGEventType)_type,
|
|
+ NSPointToCGPoint([self locationInWindow]), (CGMouseButton)_buttonNumber);
|
|
+ CGEventSetIntegerValueField(event, kCGMouseEventButtonNumber, _buttonNumber);
|
|
+ CGEventSetIntegerValueField(event, kCGMouseEventClickState, _clickCount);
|
|
+ [(id)event setFlags:(CGEventFlags)[self modifierFlags]];
|
|
+ return (CGEventRef)[(id)event autorelease];
|
|
+ }
|
|
+ default: return NULL;
|
|
+ }
|
|
+ }
|
|
+ CGEventRef event = CGEventCreateScrollWheelEvent(NULL,
|
|
+ kCGScrollEventUnitLine, 2, (int32_t)_deltaY, (int32_t)_deltaX);
|
|
+ CGEventSetIntegerValueField(event, kCGScrollWheelEventIsContinuous, 0);
|
|
+ return (CGEventRef)[(id)event autorelease];
|
|
+}
|
|
+
|
|
// Apple's documentation is a bit confusing here. NSEvent exposes no API to pass
|
|
// a NSTrackingArea object on creation, yet they expose API to get that pointer.
|
|
// Additionally, they state -trackingArea would deliver meaningful returns only
|
|
diff --git a/AppKit/NSFont.m b/AppKit/NSFont.m
|
|
index 2251b642..8b29725d 100644
|
|
--- a/AppKit/NSFont.m
|
|
+++ b/AppKit/NSFont.m
|
|
@@ -288,6 +288,14 @@ static NSLock *_cacheLock = nil;
|
|
return [self messageFontOfSize: size];
|
|
}
|
|
|
|
++ (NSFont *) systemFontOfSize: (CGFloat) size weight: (CGFloat) weight {
|
|
+ /* The current font backend provides regular and bold system faces.
|
|
+ * Match semibold and heavier requests to the available bold face. */
|
|
+ if (weight >= 0.3)
|
|
+ return [self boldSystemFontOfSize: size];
|
|
+ return [self systemFontOfSize: size];
|
|
+}
|
|
+
|
|
+ (NSFont *) titleBarFontOfSize: (CGFloat) size {
|
|
return [self boldSystemFontOfSize: size];
|
|
}
|
|
@@ -322,6 +330,30 @@ static NSLock *_cacheLock = nil;
|
|
return [O2Font preferredFontNames];
|
|
}
|
|
|
|
+/* Chromium uses this AppKit fallback entrypoint for text outside a face's
|
|
+ * coverage. Fontconfig's charset matcher selects an installed covering face. */
|
|
++ (NSFont *)findFontLike:(id)font forString:(NSString *)string
|
|
+ withRange:(NSRange)range inLanguage:(NSString *)language
|
|
+{
|
|
+ if (!font || !string || range.location > [string length] ||
|
|
+ range.length > [string length] - range.location)
|
|
+ return nil;
|
|
+ NSMutableString *pattern = [NSMutableString stringWithString:@"sans:charset="];
|
|
+ for (NSUInteger i = range.location; i < NSMaxRange(range); ++i) {
|
|
+ unsigned code = [string characterAtIndex:i];
|
|
+ if (code >= 0xd800 && code <= 0xdbff && i + 1 < NSMaxRange(range)) {
|
|
+ unsigned low = [string characterAtIndex:i + 1];
|
|
+ if (low >= 0xdc00 && low <= 0xdfff) {
|
|
+ code = 0x10000 + ((code - 0xd800) << 10) + (low - 0xdc00);
|
|
+ ++i;
|
|
+ }
|
|
+ }
|
|
+ [pattern appendFormat:@"%x ", code];
|
|
+ }
|
|
+ (void)language;
|
|
+ return [NSFont fontWithName:pattern size:[font pointSize]];
|
|
+}
|
|
+
|
|
+ (void) setPreferredFontNames: (NSArray *) fontNames {
|
|
[O2Font setPreferredFontNames: fontNames];
|
|
}
|
|
@@ -565,6 +597,10 @@ static NSLock *_cacheLock = nil;
|
|
return [self retain];
|
|
}
|
|
|
|
+- (CTFontRef) _coreTextFont {
|
|
+ return _ctFont;
|
|
+}
|
|
+
|
|
- (CGFloat) pointSize {
|
|
return _pointSize;
|
|
}
|
|
@@ -681,8 +717,10 @@ static NSLock *_cacheLock = nil;
|
|
}
|
|
|
|
- (NSFont *) printerFont {
|
|
- NSUnimplementedMethod();
|
|
- return nil;
|
|
+ /* Quartz-backed fonts do not need the old screen/printer font split.
|
|
+ * Chromium asks for printerFont while constructing its Skia typeface and
|
|
+ * requires a valid, non-owned NSFont result. */
|
|
+ return self;
|
|
}
|
|
|
|
- (NSFont *) screenFont {
|
|
@@ -694,6 +732,10 @@ static NSLock *_cacheLock = nil;
|
|
return [[self copy] autorelease];
|
|
}
|
|
|
|
+- (CGFontRef) copyGraphicsFont {
|
|
+ return CGFontRetain(_cgFont);
|
|
+}
|
|
+
|
|
- (NSRect) boundingRectForFont {
|
|
return CTFontGetBoundingBox(_ctFont);
|
|
}
|
|
diff --git a/AppKit/NSImage.m b/AppKit/NSImage.m
|
|
index d2ac8e05..a4164f0a 100644
|
|
--- a/AppKit/NSImage.m
|
|
+++ b/AppKit/NSImage.m
|
|
@@ -1124,6 +1124,22 @@ NSImageName const NSImageNameTouchBarVolumeUpTemplate =
|
|
NSUnimplementedMethod();
|
|
}
|
|
|
|
+- (void) drawAtPoint: (NSPoint) point
|
|
+{
|
|
+ [self drawAtPoint: point
|
|
+ fromRect: NSZeroRect
|
|
+ operation: NSCompositeSourceOver
|
|
+ fraction: 1.0];
|
|
+}
|
|
+
|
|
+- (void) drawInRect: (NSRect) rect
|
|
+{
|
|
+ [self drawInRect: rect
|
|
+ fromRect: NSZeroRect
|
|
+ operation: NSCompositeSourceOver
|
|
+ fraction: 1.0];
|
|
+}
|
|
+
|
|
- (void) drawAtPoint: (NSPoint) point
|
|
fromRect: (NSRect) source
|
|
operation: (NSCompositingOperation) operation
|
|
diff --git a/AppKit/NSMenu.subproj/NSMenu.m b/AppKit/NSMenu.subproj/NSMenu.m
|
|
index e80e49d5..b72c4c0e 100644
|
|
--- a/AppKit/NSMenu.subproj/NSMenu.m
|
|
+++ b/AppKit/NSMenu.subproj/NSMenu.m
|
|
@@ -493,6 +493,11 @@ BOOL itemIsEnabled(NSMenuItem *item) {
|
|
_name = copied;
|
|
}
|
|
|
|
+/* Private AppKit API — no-op: Cocotron does not implement menu-edge
|
|
+ * padding. Present so Chromium/Electron's call does not raise. */
|
|
+- (void) _setHasPadding: (BOOL) padding onEdge: (NSInteger) edge {
|
|
+}
|
|
+
|
|
- (NSMenu *) _menuWithName: (NSString *) name {
|
|
if ([_name isEqual: name])
|
|
return self;
|
|
diff --git a/AppKit/NSMenu.subproj/NSMenuItem.m b/AppKit/NSMenu.subproj/NSMenuItem.m
|
|
index fd488111..cdb66811 100644
|
|
--- a/AppKit/NSMenu.subproj/NSMenuItem.m
|
|
+++ b/AppKit/NSMenu.subproj/NSMenuItem.m
|
|
@@ -139,10 +139,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
|
|
- (void) dealloc {
|
|
[_title release];
|
|
+ [_toolTip release];
|
|
[_atitle release];
|
|
[_keyEquivalent release];
|
|
[_submenu release];
|
|
[_image release];
|
|
+ [_view release];
|
|
[_onStateImage release];
|
|
[_mixedStateImage release];
|
|
[_offStateImage release];
|
|
@@ -155,11 +157,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
NSMenuItem *copy = NSCopyObject(self, 0, zone);
|
|
|
|
copy->_title = [_title copyWithZone: zone];
|
|
+ copy->_toolTip = [_toolTip copyWithZone: zone];
|
|
copy->_atitle = [_atitle copyWithZone: zone];
|
|
copy->_submenu = [_submenu copyWithZone: zone];
|
|
copy->_keyEquivalent = [_keyEquivalent copyWithZone: zone];
|
|
;
|
|
copy->_image = [_image retain];
|
|
+ copy->_view = [_view retain];
|
|
copy->_onStateImage = [_onStateImage retain];
|
|
copy->_mixedStateImage = [_mixedStateImage retain];
|
|
copy->_offStateImage = [_offStateImage retain];
|
|
@@ -224,6 +228,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
return _image;
|
|
}
|
|
|
|
+- (NSView *) view {
|
|
+ return _view;
|
|
+}
|
|
+
|
|
- (NSImage *) onStateImage {
|
|
return _onStateImage;
|
|
}
|
|
@@ -266,6 +274,18 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
_title = title;
|
|
}
|
|
|
|
+- (NSString *) toolTip {
|
|
+ return _toolTip;
|
|
+}
|
|
+
|
|
+- (void) setToolTip: (NSString *) toolTip {
|
|
+ /* darling-lol M42b: Electron sets menu item tooltips; previously
|
|
+ * "unrecognized selector" and a terminate. Stored, not displayed. */
|
|
+ toolTip = [toolTip copy];
|
|
+ [_toolTip release];
|
|
+ _toolTip = toolTip;
|
|
+}
|
|
+
|
|
- (void) setAttributedTitle: (NSAttributedString *) title {
|
|
title = [title copy];
|
|
[_atitle release];
|
|
@@ -319,6 +339,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
_image = image;
|
|
}
|
|
|
|
+- (void) setView: (NSView *) view {
|
|
+ view = [view retain];
|
|
+ [_view release];
|
|
+ _view = view;
|
|
+}
|
|
+
|
|
- (void) setOnStateImage: (NSImage *) image {
|
|
[_onStateImage release];
|
|
_onStateImage = [image retain];
|
|
diff --git a/AppKit/NSResponder.m b/AppKit/NSResponder.m
|
|
index f102391d..8ea0778f 100644
|
|
--- a/AppKit/NSResponder.m
|
|
+++ b/AppKit/NSResponder.m
|
|
@@ -30,6 +30,24 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
|
|
@implementation NSResponder
|
|
|
|
+- (id) touchBar {
|
|
+ return _touchBar;
|
|
+}
|
|
+
|
|
+- (void) setTouchBar: (id) touchBar {
|
|
+ if (_touchBar != touchBar) {
|
|
+ [touchBar retain];
|
|
+ [_touchBar release];
|
|
+ _touchBar = touchBar;
|
|
+ }
|
|
+}
|
|
+
|
|
+- (void) dealloc {
|
|
+ [_touchBar release];
|
|
+ [super dealloc];
|
|
+}
|
|
+
|
|
+
|
|
- (void) encodeWithCoder: (NSCoder *) coder {
|
|
if (coder.allowsKeyedCoding) {
|
|
[coder encodeObject: _nextResponder forKey: @"NSNextResponder"];
|
|
@@ -286,6 +304,16 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
[_nextResponder performSelector: _cmd withObject: event];
|
|
}
|
|
|
|
+- (void) otherMouseDown: (NSEvent *) event {
|
|
+ [_nextResponder performSelector:_cmd withObject:event];
|
|
+}
|
|
+- (void) otherMouseUp: (NSEvent *) event {
|
|
+ [_nextResponder performSelector:_cmd withObject:event];
|
|
+}
|
|
+- (void) otherMouseDragged: (NSEvent *) event {
|
|
+ [_nextResponder performSelector:_cmd withObject:event];
|
|
+}
|
|
+
|
|
- (void) rightMouseUp: (NSEvent *) event {
|
|
[_nextResponder performSelector: _cmd withObject: event];
|
|
}
|
|
diff --git a/AppKit/NSRunningApplication.m b/AppKit/NSRunningApplication.m
|
|
index 6c3d2688..2a7d078a 100644
|
|
--- a/AppKit/NSRunningApplication.m
|
|
+++ b/AppKit/NSRunningApplication.m
|
|
@@ -1,4 +1,10 @@
|
|
#import <AppKit/NSRunningApplication.h>
|
|
+#import <AppKit/NSDisplay.h>
|
|
+#import <AppKit/NSErrors.h>
|
|
+#import <Foundation/NSException.h>
|
|
+#import <Foundation/NSString.h>
|
|
+#include <errno.h>
|
|
+#include <signal.h>
|
|
|
|
// DUMMY
|
|
|
|
@@ -19,6 +25,38 @@
|
|
|
|
@implementation NSRunningApplication
|
|
|
|
+- (instancetype) initWithProcessIdentifier: (pid_t) processIdentifier {
|
|
+ self = [super init];
|
|
+ if (self) _processIdentifier = processIdentifier;
|
|
+ return self;
|
|
+}
|
|
+
|
|
++ (NSRunningApplication *) runningApplicationWithProcessIdentifier: (pid_t) processIdentifier {
|
|
+ if (processIdentifier <= 0 || (kill(processIdentifier, 0) != 0 && errno == ESRCH))
|
|
+ return nil;
|
|
+ return [[[self alloc] initWithProcessIdentifier: processIdentifier] autorelease];
|
|
+}
|
|
+
|
|
+- (pid_t) processIdentifier {
|
|
+ return _processIdentifier;
|
|
+}
|
|
+
|
|
+- (BOOL) isTerminated {
|
|
+ return kill(_processIdentifier, 0) != 0 && errno == ESRCH;
|
|
+}
|
|
+
|
|
+- (BOOL) activateWithOptions: (NSUInteger) options {
|
|
+ (void)options;
|
|
+ if ([self isTerminated]) return NO;
|
|
+ @try {
|
|
+ return [[NSDisplay currentDisplay] activateProcessIdentifier: _processIdentifier];
|
|
+ } @catch (NSException *exception) {
|
|
+ if ([[exception name] isEqualToString:NSWindowServerCommunicationException])
|
|
+ return NO;
|
|
+ @throw;
|
|
+ }
|
|
+}
|
|
+
|
|
+ (NSArray<NSRunningApplication *> *) runningApplicationsWithBundleIdentifier: (NSString *) bundleIdentifier {
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return [NSArray array];
|
|
diff --git a/AppKit/NSScreen.m b/AppKit/NSScreen.m
|
|
index 5ca58772..54b60ef5 100644
|
|
--- a/AppKit/NSScreen.m
|
|
+++ b/AppKit/NSScreen.m
|
|
@@ -22,6 +22,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
#import <AppKit/NSDisplay.h>
|
|
#import <AppKit/NSScreen.h>
|
|
#import <AppKit/NSWindow.h>
|
|
+#import <AppKit/NSColorSpace.h>
|
|
|
|
NSNotificationName const NSScreenColorSpaceDidChangeNotification = @"NSScreenColorSpaceDidChangeNotification";
|
|
|
|
@@ -68,6 +69,19 @@ NSNotificationName const NSScreenColorSpaceDidChangeNotification = @"NSScreenCol
|
|
return 1.0;
|
|
}
|
|
|
|
+- (CGFloat) backingScaleFactor {
|
|
+ /* X11 has no per-screen backing scale concept; report 1.0 so
|
|
+ * clients (e.g. Chromium display setup) proceed with unscaled
|
|
+ * coordinates. HiDPI VNC/Xvfb could upgrade this later. */
|
|
+ return 1.0;
|
|
+}
|
|
+
|
|
+- (NSColorSpace *) colorSpace {
|
|
+ /* No EDID/ICC profile plumbing from X11 yet; report sRGB so
|
|
+ * color-management clients (e.g. Chromium) get a valid space. */
|
|
+ return [NSColorSpace sRGBColorSpace];
|
|
+}
|
|
+
|
|
- (id) description {
|
|
return [NSString stringWithFormat: @"< %@ - frame %@, visible %@ >",
|
|
[super description],
|
|
@@ -76,7 +90,12 @@ NSNotificationName const NSScreenColorSpaceDidChangeNotification = @"NSScreenCol
|
|
}
|
|
|
|
- (NSDictionary<NSDeviceDescriptionKey, id> *) deviceDescription {
|
|
- return @{};
|
|
+ /* darling-lol M57: Chromium reads [screen deviceDescription]
|
|
+ * [@"NSScreenNumber"] to get the CGDirectDisplayID and rejects a zero id
|
|
+ * (DisplayLinkMac::GetForDisplay). Provide the screen number. */
|
|
+ if (_directDisplayID != 0)
|
|
+ return @{ @"NSScreenNumber": [NSNumber numberWithUnsignedInt: _directDisplayID] };
|
|
+ return @{ @"NSScreenNumber": [NSNumber numberWithUnsignedInt: 1] };
|
|
}
|
|
|
|
@end
|
|
diff --git a/AppKit/NSScrubberItemView.m b/AppKit/NSScrubberItemView.m
|
|
index dea5e1d3..7b3a877b 100644
|
|
--- a/AppKit/NSScrubberItemView.m
|
|
+++ b/AppKit/NSScrubberItemView.m
|
|
@@ -60,3 +60,17 @@
|
|
}
|
|
|
|
@end
|
|
+
|
|
+@implementation NSScrubberImageItemView
|
|
+
|
|
+- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
|
|
+{
|
|
+ return [NSMethodSignature signatureWithObjCTypes: "v@:"];
|
|
+}
|
|
+
|
|
+- (void)forwardInvocation:(NSInvocation *)anInvocation
|
|
+{
|
|
+ NSLog(@"Stub called: %@ in %@", NSStringFromSelector([anInvocation selector]), [self class]);
|
|
+}
|
|
+
|
|
+@end
|
|
diff --git a/AppKit/NSSpeechSynthesizer.m b/AppKit/NSSpeechSynthesizer.m
|
|
index 359607fc..2d365b8f 100644
|
|
--- a/AppKit/NSSpeechSynthesizer.m
|
|
+++ b/AppKit/NSSpeechSynthesizer.m
|
|
@@ -18,6 +18,8 @@
|
|
*/
|
|
|
|
#import <AppKit/NSSpeechSynthesizer.h>
|
|
+#import <Foundation/NSArray.h>
|
|
+#import <Foundation/NSDictionary.h>
|
|
|
|
NSVoiceGenderName const NSVoiceGenderFemale = @"VoiceGenderFemale";
|
|
NSVoiceGenderName const NSVoiceGenderMale = @"VoiceGenderMale";
|
|
@@ -32,6 +34,23 @@ NSSpeechPropertyKey const NSSpeechVolumeProperty = @"volm";
|
|
|
|
@implementation NSSpeechSynthesizer
|
|
|
|
++ (NSArray *)availableVoices
|
|
+{
|
|
+ /* Darling has no speech synthesis backend or installed native voices. */
|
|
+ return [NSArray array];
|
|
+}
|
|
+
|
|
++ (NSString *)defaultVoice
|
|
+{
|
|
+ return nil;
|
|
+}
|
|
+
|
|
++ (NSDictionary *)attributesForVoice:(NSString *)voice
|
|
+{
|
|
+ return nil;
|
|
+}
|
|
+
|
|
+
|
|
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
|
|
{
|
|
return [NSMethodSignature signatureWithObjCTypes: "v@:"];
|
|
diff --git a/AppKit/NSSpellChecker.m b/AppKit/NSSpellChecker.m
|
|
index 8fecc9d6..1b1a5783 100644
|
|
--- a/AppKit/NSSpellChecker.m
|
|
+++ b/AppKit/NSSpellChecker.m
|
|
@@ -28,6 +28,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
#import <Foundation/NSNumber.h>
|
|
#import <Foundation/NSOrthography.h>
|
|
#import <Foundation/NSSpellEngine.h>
|
|
+#import <dispatch/dispatch.h>
|
|
|
|
NSString *const NSSpellCheckerDidChangeAutomaticTextReplacementNotification =
|
|
@"NSSpellCheckerDidChangeAutomaticTextReplacementNotification";
|
|
@@ -345,7 +346,7 @@ static NSSpellChecker *shared = nil;
|
|
return results;
|
|
}
|
|
|
|
-#ifdef NS_BLOCKS
|
|
+#if defined(NS_BLOCKS) || defined(__BLOCKS__)
|
|
- (NSInteger) requestCheckingOfString: (NSString *) stringToCheck
|
|
range: (NSRange) range
|
|
types: (NSTextCheckingTypes) checkingTypes
|
|
@@ -357,8 +358,25 @@ static NSSpellChecker *shared = nil;
|
|
NSOrthography *orthography,
|
|
NSInteger wordCount)) completionHandler
|
|
{
|
|
- NSUnimplementedMethod();
|
|
- return 0;
|
|
+ static NSInteger nextSequence = 0;
|
|
+ NSInteger sequence = __sync_add_and_fetch(&nextSequence, 1);
|
|
+ NSString *text = [stringToCheck copy];
|
|
+ NSDictionary *settings = [options copy];
|
|
+ // Serialize access to the existing spell engine and document state on
|
|
+ // the UI queue. Dispatch copies the block and retains its captured values.
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
+ NSOrthography *orthography = nil;
|
|
+ NSInteger wordCount = 0;
|
|
+ NSArray *results = [self checkString: text range: range
|
|
+ types: checkingTypes options: settings
|
|
+ inSpellDocumentWithTag: tag orthography: &orthography
|
|
+ wordCount: &wordCount];
|
|
+ if (completionHandler)
|
|
+ completionHandler(sequence, results ?: @[], orthography, wordCount);
|
|
+ });
|
|
+ [text release];
|
|
+ [settings release];
|
|
+ return sequence;
|
|
}
|
|
#endif
|
|
|
|
diff --git a/AppKit/NSTextInput.m b/AppKit/NSTextInput.m
|
|
index 9177a986..9c3c5ca3 100644
|
|
--- a/AppKit/NSTextInput.m
|
|
+++ b/AppKit/NSTextInput.m
|
|
@@ -1,10 +1,59 @@
|
|
#include <AppKit/NSTextInput.h>
|
|
#include <AppKit/NSTextInput_Internal.h>
|
|
+#import <AppKit/NSEvent.h>
|
|
+
|
|
+@interface NSObject (TextInputClientDelivery)
|
|
+- (void)insertText:(id)text replacementRange:(NSRange)range;
|
|
+- (void)insertText:(id)text;
|
|
+- (void)doCommandBySelector:(SEL)selector;
|
|
+- (void)unmarkText;
|
|
+@end
|
|
|
|
NSString *const NSTextInputContextKeyboardSelectionDidChangeNotification = @"NSTextInputContextKeyboardSelectionDidChangeNotification";
|
|
|
|
NSString *const NSTextInputReplacementRangeAttributeName = @"NSTextInputReplacementRangeAttributeName";
|
|
|
|
@implementation NSTextInputContext
|
|
+- (instancetype)initWithClient:(id)client {
|
|
+ if ((self = [super init])) _client = client;
|
|
+ return self;
|
|
+}
|
|
+- (id)client { return _client; }
|
|
+- (void)activate {}
|
|
+- (void)deactivate {}
|
|
+- (void)invalidateCharacterCoordinates {}
|
|
+- (void)discardMarkedText {
|
|
+ if ([_client respondsToSelector:@selector(unmarkText)]) [_client unmarkText];
|
|
+}
|
|
+- (BOOL)handleEvent:(NSEvent *)event {
|
|
+ if ([event type] != NSKeyDown || ([event modifierFlags] & NSCommandKeyMask)) return NO;
|
|
+ NSString *text = [event characters];
|
|
+ if (![text length]) return NO;
|
|
+ unichar character = [text characterAtIndex:0];
|
|
+ SEL command = NULL;
|
|
+ switch (character) {
|
|
+ case 0x7f: case 0x08: command = @selector(deleteBackward:); break;
|
|
+ case '\r': case '\n': command = @selector(insertNewline:); break;
|
|
+ case '\t': command = @selector(insertTab:); break;
|
|
+ case 0x1b: command = @selector(cancelOperation:); break;
|
|
+ case NSLeftArrowFunctionKey: command = @selector(moveLeft:); break;
|
|
+ case NSRightArrowFunctionKey: command = @selector(moveRight:); break;
|
|
+ case NSUpArrowFunctionKey: command = @selector(moveUp:); break;
|
|
+ case NSDownArrowFunctionKey: command = @selector(moveDown:); break;
|
|
+ case NSDeleteFunctionKey: command = @selector(deleteForward:); break;
|
|
+ }
|
|
+ if (command) {
|
|
+ if (![_client respondsToSelector:@selector(doCommandBySelector:)]) return NO;
|
|
+ [_client doCommandBySelector:command];
|
|
+ } else {
|
|
+ if (character < 0x20 || (character >= 0xf700 && character <= 0xf8ff) ||
|
|
+ ([event modifierFlags] & NSControlKeyMask)) return NO;
|
|
+ if ([_client respondsToSelector:@selector(insertText:replacementRange:)])
|
|
+ [_client insertText:text replacementRange:NSMakeRange(NSNotFound, 0)];
|
|
+ else if ([_client respondsToSelector:@selector(insertText:)]) [_client insertText:text];
|
|
+ else return NO;
|
|
+ }
|
|
+ return YES;
|
|
+}
|
|
|
|
@end
|
|
diff --git a/AppKit/NSThemeFrame.m b/AppKit/NSThemeFrame.m
|
|
index 0ec827c0..a24cd9f5 100644
|
|
--- a/AppKit/NSThemeFrame.m
|
|
+++ b/AppKit/NSThemeFrame.m
|
|
@@ -85,7 +85,14 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
- (void) resizeSubviewsWithOldSize: (NSSize) oldSize {
|
|
NSView *menuView = nil;
|
|
NSToolbarView *toolbarView = nil;
|
|
- NSView *contentView = nil;
|
|
+ /* Browser windows can add overlay/titlebar siblings after their content.
|
|
+ * Identify the window's actual content owner rather than choosing the
|
|
+ * last ordinary subview. Full-size content wrappers can own it indirectly. */
|
|
+ NSView *contentView = [[self window] contentView];
|
|
+ while (contentView && [contentView superview] && [contentView superview] != self)
|
|
+ contentView = [contentView superview];
|
|
+ if ([contentView superview] != self)
|
|
+ contentView = nil;
|
|
|
|
// tile the subviews, when/if we add titlebars and such do it here
|
|
for (NSView *view in _subviews) {
|
|
@@ -93,10 +100,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
menuView = view;
|
|
else if ([view isKindOfClass: [NSToolbarView class]])
|
|
toolbarView = (NSToolbarView *) view;
|
|
- else
|
|
- contentView = view;
|
|
}
|
|
|
|
+ // Other decorations still follow their autoresizing masks.
|
|
+ for (NSView *view in [[_subviews copy] autorelease])
|
|
+ if (view != contentView && view != menuView && view != toolbarView)
|
|
+ [view resizeWithOldSuperviewSize: oldSize];
|
|
+
|
|
// subtracts menu height but not toolbar height
|
|
NSRect contentFrame = [[[self window] class]
|
|
contentRectForFrameRect: [self bounds]
|
|
@@ -133,8 +143,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
}
|
|
|
|
- (void) mouseDown: (NSEvent *) event {
|
|
- if (![[self window] isMovableByWindowBackground])
|
|
+ if (![[self window] isMovableByWindowBackground]) {
|
|
+ [super mouseDown:event];
|
|
return;
|
|
+ }
|
|
|
|
NSPoint origin = [[self window] frame].origin;
|
|
NSPoint firstLocation =
|
|
diff --git a/AppKit/NSTouchBarItem.m b/AppKit/NSTouchBarItem.m
|
|
index c3f5d056..dbd93174 100644
|
|
--- a/AppKit/NSTouchBarItem.m
|
|
+++ b/AppKit/NSTouchBarItem.m
|
|
@@ -27,6 +27,8 @@ NSTouchBarItemIdentifier const NSTouchBarItemIdentifierFlexibleSpace =
|
|
@"NSTouchBarItemIdentifierFlexibleSpace";
|
|
NSTouchBarItemIdentifier const NSTouchBarItemIdentifierOtherItemsProxy =
|
|
@"NSTouchBarItemIdentifierOtherItemsProxy";
|
|
+NSTouchBarItemIdentifier const NSTouchBarItemIdentifierCandidateList =
|
|
+ @"NSTouchBarItemIdentifierCandidateList";
|
|
NSTouchBarItemIdentifier const NSTouchBarItemIdentifierCharacterPicker =
|
|
@"NSTouchBarItemIdentifierCharacterPicker";
|
|
|
|
diff --git a/AppKit/NSView.m b/AppKit/NSView.m
|
|
index 8ac2bbdc..08197fbc 100644
|
|
--- a/AppKit/NSView.m
|
|
+++ b/AppKit/NSView.m
|
|
@@ -46,6 +46,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
#import <QuartzCore/CALayerContext.h>
|
|
#import <QuartzCore/CATransaction.h>
|
|
#import <AppKit/NSLayoutConstraint.h>
|
|
+#import <AppKit/NSTextInput.h>
|
|
+#import <objc/runtime.h>
|
|
+#include "NSGeometryTrace.h"
|
|
+#include <stdio.h>
|
|
+#include <stdlib.h>
|
|
+#include <unistd.h>
|
|
|
|
@class IBMetricsTable;
|
|
|
|
@@ -66,6 +72,7 @@ const NSViewFullScreenModeOptionKey NSFullScreenModeApplicationPresentationOptio
|
|
- (CGAffineTransform) transformToWindow;
|
|
- (CGAffineTransform) transformToLayer;
|
|
- (void) _trackingAreasChanged;
|
|
+- (void) _darlingSetFrame: (NSRect) frame;
|
|
@end
|
|
|
|
@implementation NSView
|
|
@@ -955,6 +962,54 @@ static inline void buildTransformsIfNeeded(NSView *self) {
|
|
CGPointApplyAffineTransform(point, toWindow), fromWindow);
|
|
}
|
|
|
|
+- (NSUInteger) allowedTouchTypes {
|
|
+ return _allowedTouchTypes;
|
|
+}
|
|
+
|
|
+- (void) setAllowedTouchTypes: (NSUInteger) value {
|
|
+ _allowedTouchTypes = value;
|
|
+}
|
|
+
|
|
+- (BOOL) wantsUpdateLayer {
|
|
+ return _wantsUpdateLayer;
|
|
+}
|
|
+
|
|
+- (void) setWantsUpdateLayer: (BOOL) value {
|
|
+ _wantsUpdateLayer = value;
|
|
+}
|
|
+
|
|
+- (BOOL) canDrawSubviewsIntoLayer {
|
|
+ return _canDrawSubviewsIntoLayer;
|
|
+}
|
|
+
|
|
+- (void) setCanDrawSubviewsIntoLayer: (BOOL) value {
|
|
+ _canDrawSubviewsIntoLayer = value;
|
|
+}
|
|
+
|
|
+- (BOOL) wantsBestResolutionOpenGLSurface {
|
|
+ return _wantsBestResolutionOpenGLSurface;
|
|
+}
|
|
+
|
|
+- (void) setWantsBestResolutionOpenGLSurface: (BOOL) value {
|
|
+ _wantsBestResolutionOpenGLSurface = value;
|
|
+}
|
|
+
|
|
+- (NSPoint) convertPointFromBacking: (NSPoint) point {
|
|
+ return point;
|
|
+}
|
|
+
|
|
+- (NSPoint) convertPointToBacking: (NSPoint) point {
|
|
+ return point;
|
|
+}
|
|
+
|
|
+- (NSRect) convertRectFromBacking: (NSRect) rect {
|
|
+ return rect;
|
|
+}
|
|
+
|
|
+- (NSRect) convertRectToBacking: (NSRect) rect {
|
|
+ return rect;
|
|
+}
|
|
+
|
|
- (NSSize) convertSize: (NSSize) size fromView: (NSView *) viewOrNil {
|
|
NSView *fromView =
|
|
(viewOrNil != nil) ? viewOrNil : [[self window] _backgroundView];
|
|
@@ -1029,6 +1084,16 @@ static inline void buildTransformsIfNeeded(NSView *self) {
|
|
}
|
|
|
|
- (void) setFrame: (NSRect) frame {
|
|
+ /* AppKit dispatches through the component setters. Subclasses use
|
|
+ * setFrameSize: to notify their renderer even when callers set a full
|
|
+ * frame (including autoresizing through resizeWithOldSuperviewSize:). */
|
|
+ if (!NSEqualPoints(_frame.origin, frame.origin))
|
|
+ [self setFrameOrigin: frame.origin];
|
|
+ if (!NSEqualSizes(_frame.size, frame.size))
|
|
+ [self setFrameSize: frame.size];
|
|
+}
|
|
+
|
|
+- (void) _darlingSetFrame: (NSRect) frame {
|
|
// Cocoa does not post the notification if the frames are equal
|
|
// Possible that resizeSubviewsWithOldSize is not called if the sizes are
|
|
// equal
|
|
@@ -1052,6 +1117,12 @@ static inline void buildTransformsIfNeeded(NSView *self) {
|
|
}
|
|
_frame = frame;
|
|
|
|
+ /* Resize callbacks can convert child frames to window coordinates. They
|
|
+ * must see the new parent transform, and compositor submissions must see
|
|
+ * the new layer bounds even before the next coordinate conversion. */
|
|
+ invalidateTransform(self);
|
|
+ configureLayerGeometry(self);
|
|
+
|
|
[_window invalidateCursorRectsForView: self]; // this also invalidates
|
|
// tracking areas
|
|
|
|
@@ -1068,6 +1139,14 @@ static inline void buildTransformsIfNeeded(NSView *self) {
|
|
|
|
invalidateTransform(self);
|
|
|
|
+ if (getenv("DARLING_UI_GEOMETRY_TRACE") &&
|
|
+ (_frame.size.width >= 256 || _frame.size.height >= 256))
|
|
+ DarlingGeometryTrace("UI_GEOMETRY view pid=%d class=%s parent=%s frame=%.0fx%.0f bounds=%.0fx%.0f mask=%lu autoresizes=%d\n",
|
|
+ getpid(), class_getName([self class]),
|
|
+ _superview ? class_getName([_superview class]) : "none",
|
|
+ _frame.size.width, _frame.size.height, _bounds.size.width, _bounds.size.height,
|
|
+ (unsigned long)_autoresizingMask, _autoresizesSubviews);
|
|
+
|
|
if (_postsNotificationOnFrameChange)
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName: NSViewFrameDidChangeNotification
|
|
@@ -1078,14 +1157,14 @@ static inline void buildTransformsIfNeeded(NSView *self) {
|
|
NSRect frame = _frame;
|
|
|
|
frame.size = size;
|
|
- [self setFrame: frame];
|
|
+ [self _darlingSetFrame: frame];
|
|
}
|
|
|
|
- (void) setFrameOrigin: (NSPoint) origin {
|
|
NSRect frame = [self frame];
|
|
|
|
frame.origin = origin;
|
|
- [self setFrame: frame];
|
|
+ [self _darlingSetFrame: frame];
|
|
}
|
|
|
|
- (void) setFrameRotation: (CGFloat) angle {
|
|
@@ -1100,6 +1179,7 @@ static inline void buildTransformsIfNeeded(NSView *self) {
|
|
if (!NSEqualRects(bounds, _bounds)) {
|
|
_bounds = bounds;
|
|
invalidateTransform(self);
|
|
+ configureLayerGeometry(self);
|
|
|
|
[_window invalidateCursorRectsForView: self]; // this also invalidates
|
|
// tracking areas
|
|
@@ -1254,7 +1334,11 @@ static inline void buildTransformsIfNeeded(NSView *self) {
|
|
(NSComparisonResult (*)(id, id, void *)) compareFunction
|
|
context: (void *) context
|
|
{
|
|
- NSUnimplementedMethod();
|
|
+ if (compareFunction == NULL || [_subviews count] < 2)
|
|
+ return;
|
|
+
|
|
+ [_subviews sortUsingFunction: compareFunction context: context];
|
|
+ [self setNeedsDisplay: YES];
|
|
}
|
|
|
|
- (void) didAddSubview: (NSView *) subview {
|
|
@@ -1511,8 +1595,16 @@ static inline void buildTransformsIfNeeded(NSView *self) {
|
|
}
|
|
|
|
- (NSTextInputContext *) inputContext {
|
|
- NSUnimplementedMethod();
|
|
- return nil;
|
|
+ if (![self respondsToSelector:@selector(insertText:replacementRange:)] &&
|
|
+ ![self respondsToSelector:@selector(insertText:)]) return nil;
|
|
+ static char contextKey;
|
|
+ NSTextInputContext *context = objc_getAssociatedObject(self, &contextKey);
|
|
+ if (!context) {
|
|
+ context = [[NSTextInputContext alloc] initWithClient:self];
|
|
+ objc_setAssociatedObject(self, &contextKey, context, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
+ [context release];
|
|
+ }
|
|
+ return context;
|
|
}
|
|
|
|
- (void) registerForDraggedTypes: (NSArray *) types {
|
|
@@ -1819,15 +1911,21 @@ static inline void buildTransformsIfNeeded(NSView *self) {
|
|
|
|
- (void) _createLayerContextIfNeeded {
|
|
if ([_superview layer] == nil) {
|
|
- _layerContext = [[CALayerContext alloc] initWithFrame: [self frame]];
|
|
+ if (_layerContext == nil)
|
|
+ _layerContext = [[CALayerContext alloc] initWithFrame: [self frame]];
|
|
[_layerContext setLayer: _layer];
|
|
- if (_window) {
|
|
+ if (_window && [_layerContext subwindow] == nil) {
|
|
[_layerContext setSubwindow: [_window _createSubWindowWithFrame: [self frame]]];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) _addLayerToSuperlayer {
|
|
+ if ([_superview layer] != nil && _layerContext != nil) {
|
|
+ [_layerContext invalidate];
|
|
+ [_layerContext release];
|
|
+ _layerContext = nil;
|
|
+ }
|
|
[[_superview layer] addSublayer: _layer];
|
|
[self _createLayerContextIfNeeded];
|
|
}
|
|
@@ -1905,6 +2003,7 @@ static inline void buildTransformsIfNeeded(NSView *self) {
|
|
[[_superview layer] replaceSublayer: _layer with: value];
|
|
[_layer release];
|
|
_layer = value;
|
|
+ [_layerContext setLayer: _layer];
|
|
}
|
|
|
|
[_subviews
|
|
diff --git a/AppKit/NSWindow.m b/AppKit/NSWindow.m
|
|
index d545120a..b2845478 100644
|
|
--- a/AppKit/NSWindow.m
|
|
+++ b/AppKit/NSWindow.m
|
|
@@ -45,6 +45,21 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
#import <AppKit/NSWindowAnimationContext.h>
|
|
#import <ApplicationServices/ApplicationServices.h>
|
|
#import <CoreGraphics/CGWindow.h>
|
|
+#import <objc/runtime.h>
|
|
+#include "NSGeometryTrace.h"
|
|
+#include <unistd.h>
|
|
+#include <string.h>
|
|
+
|
|
+
|
|
+/* darling-lol M57: the initializer must not dispatch -contentRectForFrameRect:
|
|
+ * to a subclass override. Electron's ElectronNSWindow overrides it and reads
|
|
+ * its shell_ ivar, which is only assigned after [super init...] returns; on
|
|
+ * macOS NSWindow's initializer does not call that overridable method, but
|
|
+ * Cocotron did, so the UX SIGSEGV'd on nil shell_ (+0x198) while constructing
|
|
+ * the BrowserWindow. Route both through a private helper instead. */
|
|
+@interface NSWindow (DarlingM57Private)
|
|
+- (NSRect) _darlingContentRectForFrameRect: (NSRect) frameRect;
|
|
+@end
|
|
|
|
const NSNotificationName NSWindowDidBecomeKeyNotification =
|
|
@"NSWindowDidBecomeKeyNotification";
|
|
@@ -204,6 +219,25 @@ NSInteger NSBitsPerPixelFromDepth(NSWindowDepth depth) {
|
|
|
|
@end
|
|
|
|
+/* darling-lol: whether a mouse-down may move the window must be decided by the frontmost view
|
|
+ * under the pointer by GEOMETRY, not by -hitTest:. Electron implements draggable regions as a
|
|
+ * web view that can move the window plus ControlRegionView overlays (mouseDownCanMoveWindow == NO)
|
|
+ * on top of everything that must not drag; those overlays return nil from -hitTest: so the click
|
|
+ * still reaches the page. Using the hitTest result here always found the web view, so every click
|
|
+ * started a window drag and never reached the page. `point` is in `view`'s own coordinates. */
|
|
+static NSView *DarlingFrontmostViewAtPoint(NSView *view, NSPoint point) {
|
|
+ if (view == nil || [view isHidden] || !NSPointInRect(point, [view bounds]))
|
|
+ return nil;
|
|
+ NSArray *subviews = [view subviews];
|
|
+ for (NSInteger i = (NSInteger)[subviews count] - 1; i >= 0; i--) {
|
|
+ NSView *subview = [subviews objectAtIndex: i];
|
|
+ NSView *found = DarlingFrontmostViewAtPoint(subview, [subview convertPoint: point fromView: view]);
|
|
+ if (found != nil)
|
|
+ return found;
|
|
+ }
|
|
+ return view;
|
|
+}
|
|
+
|
|
@implementation NSWindow
|
|
|
|
@synthesize identifier = _identifier;
|
|
@@ -218,7 +252,8 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
/* This method is Cococtron specific and can be override by subclasses, do not
|
|
* change method name */
|
|
+ (BOOL) hasMainMenuForStyleMask: (NSWindowStyleMask) styleMask {
|
|
- return styleMask & NSTitledWindowMask;
|
|
+ return (styleMask & NSTitledWindowMask) &&
|
|
+ !(styleMask & NSWindowStyleMaskFullSizeContentView);
|
|
}
|
|
|
|
/* This method is Cococtron specific and can be override by subclasses, do not
|
|
@@ -318,6 +353,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
[NSApplication sharedApplication];
|
|
}
|
|
_styleMask = styleMask;
|
|
+ _isMovable = YES;
|
|
_backingType = backing;
|
|
_isDeferred = defer;
|
|
|
|
@@ -351,7 +387,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
[_backgroundView _setWindow: self];
|
|
[_backgroundView setNextResponder: self];
|
|
|
|
- NSRect contentViewFrame = [self contentRectForFrameRect: backgroundFrame];
|
|
+ NSRect contentViewFrame = [self _darlingContentRectForFrameRect: backgroundFrame];
|
|
_contentView = [[NSView alloc] initWithFrame: contentViewFrame];
|
|
[_contentView setAutoresizesSubviews: YES];
|
|
[_contentView
|
|
@@ -399,6 +435,8 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
}
|
|
|
|
- (void) dealloc {
|
|
+ for (NSUInteger i = 0; i < 3; ++i)
|
|
+ [_standardWindowButtons[i] release];
|
|
[[NSNotificationCenter defaultCenter] removeObserver: self];
|
|
[_childWindows release];
|
|
[_title release];
|
|
@@ -934,6 +972,22 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
return _animationContext;
|
|
}
|
|
|
|
+/* Dump geometry only, including children which did not receive a resize.
|
|
+ * The node/depth bounds keep malformed or unexpectedly large trees bounded. */
|
|
+static void traceGeometryTree(NSView *view, unsigned depth, unsigned *remaining) {
|
|
+ if (!view || depth > 12 || !*remaining)
|
|
+ return;
|
|
+ --*remaining;
|
|
+ NSRect frame = [view frame], bounds = [view bounds];
|
|
+ DarlingGeometryTrace("UI_GEOMETRY tree pid=%d depth=%u view=%p class=%s parent=%p frame=%.0f,%.0f,%.0fx%.0f bounds=%.0f,%.0f,%.0fx%.0f mask=%lu autoresizes=%d layer=%d\n",
|
|
+ getpid(), depth, view, class_getName([view class]), [view superview],
|
|
+ frame.origin.x, frame.origin.y, frame.size.width, frame.size.height,
|
|
+ bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height,
|
|
+ (unsigned long)[view autoresizingMask], [view autoresizesSubviews], [view wantsLayer]);
|
|
+ for (NSView *child in [view subviews])
|
|
+ traceGeometryTree(child, depth + 1, remaining);
|
|
+}
|
|
+
|
|
- (void) setFrame: (NSRect) newFrame
|
|
display: (BOOL) display
|
|
animate: (BOOL) animate
|
|
@@ -944,8 +998,19 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
_frame = newFrame;
|
|
_makeSureIsOnAScreen = YES;
|
|
|
|
- [_backgroundView setFrameSize: _frame.size];
|
|
+ /* Native child windows convert their Y origin using the platform
|
|
+ * window's height. Update it before laying out/resizing those children. */
|
|
[_platformWindow setFrame: _frame];
|
|
+ [_backgroundView setFrameSize: _frame.size];
|
|
+
|
|
+ if (didSize && getenv("DARLING_UI_GEOMETRY_TRACE")) {
|
|
+ NSSize contentSize = [_contentView frame].size;
|
|
+ DarlingGeometryTrace("UI_GEOMETRY window pid=%d class=%s frame=%.0fx%.0f content=%.0fx%.0f style=%lu window=%p contentView=%p\n",
|
|
+ getpid(), class_getName([self class]), _frame.size.width, _frame.size.height,
|
|
+ contentSize.width, contentSize.height, (unsigned long)_styleMask, self, [self contentView]);
|
|
+ unsigned remaining = 128;
|
|
+ traceGeometryTree(_backgroundView, 0, &remaining);
|
|
+ }
|
|
|
|
if (didSize) {
|
|
[self resetCursorRects];
|
|
@@ -1025,7 +1090,34 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
}
|
|
|
|
- (void) setMovable: (BOOL) movable {
|
|
- NSUnimplementedMethod();
|
|
+ _isMovable = movable;
|
|
+}
|
|
+
|
|
+- (BOOL) isMovable {
|
|
+ return _isMovable;
|
|
+}
|
|
+
|
|
+/* Chromium's frameless titlebar explicitly asks NSWindow to drag itself. */
|
|
+- (void) performWindowDragWithEvent:(NSEvent *)event {
|
|
+ if (getenv("RCLOL_INPUT_TRACE")) {
|
|
+ FILE *trace=fopen("/tmp/rclol-window-drag.log","a");
|
|
+ if(trace) {fprintf(trace,"DRAG pid=%d movable=%d type=%ld\n",getpid(),_isMovable,(long)[event type]);fclose(trace);}
|
|
+ }
|
|
+ if (!_isMovable || [event type] != NSLeftMouseDown) return;
|
|
+ /* darling-lol: follow the pointer in absolute screen coordinates. The window-relative location of every drag event is
|
|
+ * measured against where the window really is, which lags behind setFrameOrigin: (the compositor moves the window
|
|
+ * later), so converting it back with the new frame fed the lag into the next position and the window jumped (League's
|
|
+ * client window ended 120-190 px away from where it was dragged to). The pointer's screen position does not depend on
|
|
+ * the window at all. */
|
|
+ NSPoint origin = [self frame].origin;
|
|
+ NSPoint first = [NSEvent mouseLocation];
|
|
+ for (;;) {
|
|
+ NSEvent *next = [self nextEventMatchingMask:NSLeftMouseUpMask | NSLeftMouseDraggedMask];
|
|
+ if (!next || [next type] == NSLeftMouseUp) break;
|
|
+ NSPoint point = [NSEvent mouseLocation];
|
|
+ [self setFrameOrigin:NSMakePoint(origin.x + point.x - first.x,
|
|
+ origin.y + point.y - first.y)];
|
|
+ }
|
|
}
|
|
|
|
- (void) setBackingType: (NSBackingStoreType) value {
|
|
@@ -1234,6 +1326,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
|
|
- (void) setIgnoresMouseEvents: (BOOL) value {
|
|
_ignoresMouseEvents = value;
|
|
+ [_platformWindow syncDelegateProperties];
|
|
}
|
|
|
|
- (void) setAspectRatio: (NSSize) value {
|
|
@@ -1413,8 +1506,14 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
}
|
|
|
|
- (NSButton *) standardWindowButton: (NSWindowButton) value {
|
|
- NSUnimplementedMethod();
|
|
- return nil;
|
|
+ if (value > NSWindowZoomButton || !(_styleMask & NSTitledWindowMask))
|
|
+ return nil;
|
|
+ if (_standardWindowButtons[value] == nil) {
|
|
+ _standardWindowButtons[value] = [[NSButton alloc]
|
|
+ initWithFrame: NSMakeRect(0, 0, 14, 14)];
|
|
+ [_standardWindowButtons[value] setBordered: NO];
|
|
+ }
|
|
+ return _standardWindowButtons[value];
|
|
}
|
|
|
|
- (NSButtonCell *) defaultButtonCell {
|
|
@@ -1425,6 +1524,17 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
return [_sheetContext sheet];
|
|
}
|
|
|
|
+- (NSWindow *) sheetParent {
|
|
+ // Sheet ownership is recorded on the presenting window, independently
|
|
+ // of ordinary child-window relationships. Query that existing state so
|
|
+ // attachment and detachment cannot leave a stale parent pointer behind.
|
|
+ for (NSWindow *window in [NSApp windows]) {
|
|
+ if (window != self && [window attachedSheet] == self)
|
|
+ return window;
|
|
+ }
|
|
+ return nil;
|
|
+}
|
|
+
|
|
- (id) windowController {
|
|
return _windowController;
|
|
}
|
|
@@ -1511,10 +1621,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
return [_platformWindow isMiniaturized];
|
|
}
|
|
|
|
-- (BOOL) isMovable {
|
|
- NSUnimplementedMethod();
|
|
- return NO;
|
|
-}
|
|
+
|
|
|
|
- (BOOL) inLiveResize {
|
|
return _isInLiveResize;
|
|
@@ -1561,6 +1668,117 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
return point;
|
|
}
|
|
|
|
+/* darling-lol M57: modern screen-conversion API. Chromium calls these on the
|
|
+ * NSWindow; without them the struct-returning messages hit CF's stret
|
|
+ * forwarding and crash. */
|
|
+- (NSPoint) convertPointToScreen: (NSPoint) point {
|
|
+ return [self convertBaseToScreen: point];
|
|
+}
|
|
+
|
|
+- (NSPoint) convertPointFromScreen: (NSPoint) point {
|
|
+ return [self convertScreenToBase: point];
|
|
+}
|
|
+
|
|
+- (NSRect) convertRectToScreen: (NSRect) rect {
|
|
+ NSPoint origin = [self convertBaseToScreen: rect.origin];
|
|
+ return NSMakeRect(origin.x, origin.y, rect.size.width, rect.size.height);
|
|
+}
|
|
+
|
|
+- (NSRect) convertRectFromScreen: (NSRect) rect {
|
|
+ NSPoint origin = [self convertScreenToBase: rect.origin];
|
|
+ return NSMakeRect(origin.x, origin.y, rect.size.width, rect.size.height);
|
|
+}
|
|
+
|
|
+- (BOOL) titlebarAppearsTransparent {
|
|
+ return _titlebarAppearsTransparent;
|
|
+}
|
|
+
|
|
+- (void) setTitlebarAppearsTransparent: (BOOL) value {
|
|
+ _titlebarAppearsTransparent = value;
|
|
+}
|
|
+
|
|
+- (NSInteger) titleVisibility {
|
|
+ return _titleVisibility;
|
|
+}
|
|
+
|
|
+- (void) setTitleVisibility: (NSInteger) value {
|
|
+ _titleVisibility = value;
|
|
+}
|
|
+
|
|
+- (BOOL) movableByWindowBackground {
|
|
+ return _isMovableByWindowBackground;
|
|
+}
|
|
+
|
|
+/* darling-lol M57: NSWindowOcclusionStateVisible == 1 << 1.
|
|
+ *
|
|
+ * Apple's contract is a bitmask: an on-screen window's state *includes* the
|
|
+ * Visible bit, tested with &. Electron 22.3.27's NativeWindowMac::IsVisible()
|
|
+ * instead compares for exact equality, so an Apple-correct value makes every
|
|
+ * ordered-in window look occluded and the client's visible checks fail. Also
|
|
+ * report the reserved 1 << 2 bit: &-based readers (Electron's own
|
|
+ * windowDidChangeOcclusionState:, Chromium) still see the Visible bit set,
|
|
+ * while the exact == test is false and unpatched Electron resolves IsVisible()
|
|
+ * correctly without a Riot binary patch. */
|
|
+- (NSUInteger) occlusionState {
|
|
+ return _isVisible ? (NSUInteger)((1 << 1) | (1 << 2)) : (NSUInteger)0;
|
|
+}
|
|
+
|
|
+- (BOOL) isRestorable {
|
|
+ return _restorable;
|
|
+}
|
|
+
|
|
+- (BOOL) restorable {
|
|
+ return _restorable;
|
|
+}
|
|
+
|
|
+- (void) setRestorable: (BOOL) value {
|
|
+ _restorable = value;
|
|
+}
|
|
+
|
|
+- (NSString *) subtitle {
|
|
+ return _subtitle;
|
|
+}
|
|
+
|
|
+- (void) setSubtitle: (NSString *) value {
|
|
+ value = [value copy];
|
|
+ [_subtitle release];
|
|
+ _subtitle = value;
|
|
+}
|
|
+
|
|
+- (NSInteger) tabbingMode {
|
|
+ return _tabbingMode;
|
|
+}
|
|
+
|
|
+- (void) setTabbingMode: (NSInteger) value {
|
|
+ _tabbingMode = value;
|
|
+}
|
|
+
|
|
+- (NSString *) tabbingIdentifier {
|
|
+ return _tabbingIdentifier;
|
|
+}
|
|
+
|
|
+- (void) setTabbingIdentifier: (NSString *) value {
|
|
+ value = [value copy];
|
|
+ [_tabbingIdentifier release];
|
|
+ _tabbingIdentifier = value;
|
|
+}
|
|
+
|
|
+- (NSInteger) titlebarSeparatorStyle {
|
|
+ return _titlebarSeparatorStyle;
|
|
+}
|
|
+
|
|
+- (void) setTitlebarSeparatorStyle: (NSInteger) value {
|
|
+ _titlebarSeparatorStyle = value;
|
|
+}
|
|
+
|
|
+- (NSInteger) toolbarStyle {
|
|
+ return _toolbarStyle;
|
|
+}
|
|
+
|
|
+- (void) setToolbarStyle: (NSInteger) value {
|
|
+ _toolbarStyle = value;
|
|
+}
|
|
+
|
|
- (NSRect) frameRectForContentRect: (NSRect) contentRect {
|
|
// hasMainMenu is an instance method so we can't just use the class method
|
|
// frameRectForContentRect:styleMask:
|
|
@@ -1580,6 +1798,10 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
}
|
|
|
|
- (NSRect) contentRectForFrameRect: (NSRect) frameRect {
|
|
+ return [self _darlingContentRectForFrameRect: frameRect];
|
|
+}
|
|
+
|
|
+- (NSRect) _darlingContentRectForFrameRect: (NSRect) frameRect {
|
|
NSRect result = [[NSDisplay currentDisplay] insetRect: frameRect
|
|
forNativeWindowBorderWithStyle: [self styleMask]];
|
|
|
|
@@ -2155,6 +2377,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
- (void) orderWindow: (NSWindowOrderingMode) place
|
|
relativeTo: (NSInteger) relativeTo
|
|
{
|
|
+ BOOL wasVisible = _isVisible;
|
|
// 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 +2431,8 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
break;
|
|
}
|
|
|
|
+ if (wasVisible != _isVisible)
|
|
+ [self postNotificationName: NSWindowDidChangeOcclusionStateNotification];
|
|
[self postNotificationName: NSWindowDidMoveNotification];
|
|
}
|
|
|
|
@@ -2257,6 +2482,12 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
}
|
|
|
|
- (void) sendEvent: (NSEvent *) event {
|
|
+ static int inputTrace = -1;
|
|
+ if (inputTrace < 0) inputTrace = getenv("RCLOL_INPUT_TRACE") != NULL;
|
|
+ if (inputTrace && ([event type] == NSKeyDown || [event type] == NSLeftMouseDown))
|
|
+ NSLog(@"LOLINPUT NS type=%ld window=%ld responder=%@ visible=%d", (long)[event type],
|
|
+ (long)[self windowNumber], [_firstResponder class], _isVisible);
|
|
+
|
|
// 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 +2531,10 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
|
|
case NSLeftMouseDown: {
|
|
NSView *view = [_backgroundView hitTest: [event locationInWindow]];
|
|
+ if (inputTrace) {
|
|
+ FILE *trace=fopen("/tmp/rclol-window-drag.log","a");
|
|
+ if(trace) {fprintf(trace,"DOWN pid=%d view=%s movable=%d at=%.0f,%.0f\n",getpid(),class_getName([view class]),_isMovable,[event locationInWindow].x,[event locationInWindow].y);fclose(trace);}
|
|
+ }
|
|
|
|
if ([view acceptsFirstResponder]) {
|
|
if ([view needsPanelToBecomeKey]) {
|
|
@@ -2307,6 +2542,27 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
}
|
|
}
|
|
|
|
+ // A view's permission to pass a click through is not a request to
|
|
+ // drag. Only windows opting into background dragging intercept it;
|
|
+ // CEF otherwise handles clicks and invokes performWindowDragWithEvent:
|
|
+ // itself for its native titlebar regions.
|
|
+ if (_isMovable && _isMovableByWindowBackground) {
|
|
+ NSView *dragView = DarlingFrontmostViewAtPoint(_backgroundView,
|
|
+ [_backgroundView convertPoint: [event locationInWindow] fromView: nil]);
|
|
+ if (dragView == nil)
|
|
+ dragView = view;
|
|
+ if (inputTrace) {
|
|
+ static int dragTrace = -1;
|
|
+ if (dragTrace < 0) dragTrace = getenv("RCLOL_INPUT_TRACE") != NULL;
|
|
+ if (dragTrace)
|
|
+ NSLog(@"LOLDRAG hit=%s dragView=%s canMove=%d", class_getName([view class]),
|
|
+ class_getName([dragView class]), (int)[dragView mouseDownCanMoveWindow]);
|
|
+ }
|
|
+ if ([dragView mouseDownCanMoveWindow]) {
|
|
+ [self performWindowDragWithEvent:event];
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
// Event goes to view, not first responder.
|
|
[view mouseDown: event];
|
|
_mouseDownLocationInWindow = [event locationInWindow];
|
|
@@ -2330,6 +2586,31 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
_mouseDownLocationInWindow = NSMakePoint(NAN, NAN);
|
|
break;
|
|
|
|
+ case NSOtherMouseDown: {
|
|
+ NSMutableDictionary *locations = objc_getAssociatedObject(self, @selector(otherMouseDown:));
|
|
+ if (!locations) {
|
|
+ locations = [NSMutableDictionary dictionary];
|
|
+ objc_setAssociatedObject(self, @selector(otherMouseDown:), locations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
+ }
|
|
+ [locations setObject:[NSValue valueWithPoint:[event locationInWindow]]
|
|
+ forKey:[NSNumber numberWithInteger:[event buttonNumber]]];
|
|
+ [[_backgroundView hitTest:[event locationInWindow]] otherMouseDown:event];
|
|
+ break;
|
|
+ }
|
|
+ case NSOtherMouseUp:
|
|
+ case NSOtherMouseDragged: {
|
|
+ NSMutableDictionary *locations = objc_getAssociatedObject(self, @selector(otherMouseDown:));
|
|
+ NSNumber *button = [NSNumber numberWithInteger:[event buttonNumber]];
|
|
+ NSValue *location = [locations objectForKey:button];
|
|
+ NSPoint point = location ? [location pointValue] : [event locationInWindow];
|
|
+ NSView *view = [_backgroundView hitTest:point];
|
|
+ if ([event type] == NSOtherMouseUp) {
|
|
+ [locations removeObjectForKey:button];
|
|
+ [view otherMouseUp:event];
|
|
+ } else [view otherMouseDragged:event];
|
|
+ break;
|
|
+ }
|
|
+
|
|
case NSMouseMoved: {
|
|
NSView *hit = [_backgroundView hitTest: [event locationInWindow]];
|
|
|
|
@@ -2536,6 +2817,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
} else {
|
|
[[self platformWindow] hideWindow];
|
|
}
|
|
+ [self postNotificationName: NSWindowDidChangeOcclusionStateNotification];
|
|
}
|
|
|
|
- (void) orderFront: (id) sender {
|
|
@@ -3064,6 +3346,13 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
if (didSize) {
|
|
// Don't redraw everything unless we really have to.
|
|
[_backgroundView setFrameSize: _frame.size];
|
|
+ if (getenv("DARLING_UI_GEOMETRY_TRACE")) {
|
|
+ DarlingGeometryTrace("UI_GEOMETRY native_window pid=%d window=%p contentView=%p frame=%.0fx%.0f\n",
|
|
+ getpid(), self, [self contentView],
|
|
+ _frame.size.width, _frame.size.height);
|
|
+ unsigned remaining = 128;
|
|
+ traceGeometryTree(_backgroundView, 0, &remaining);
|
|
+ }
|
|
[_backgroundView setNeedsDisplay: YES];
|
|
[self resetCursorRects];
|
|
[self saveFrameUsingName: _autosaveFrameName];
|
|
@@ -3260,8 +3549,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
mouseWasInside && !mouseIsInside) {
|
|
[exited addObject: area];
|
|
}
|
|
- if (options & NSTrackingMouseMoved &&
|
|
- [self acceptsMouseMovedEvents]) {
|
|
+ if (options & NSTrackingMouseMoved && mouseIsInside) {
|
|
[moved addObject: area];
|
|
}
|
|
if (options & NSTrackingCursorUpdate && !mouseWasInside &&
|
|
@@ -3459,7 +3747,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
|
}
|
|
|
|
- (CGSubWindow *) _createSubWindowWithFrame: (CGRect) frame {
|
|
- return [_platformWindow createSubWindowWithFrame: frame];
|
|
+ return [[self platformWindow] createSubWindowWithFrame: frame];
|
|
}
|
|
|
|
+ (BOOL) allowsAutomaticWindowTabbing {
|
|
diff --git a/AppKit/X11.backend/CarbonKeys.h b/AppKit/X11.backend/CarbonKeys.h
|
|
index e999cbf2..14f9cb6a 100644
|
|
--- a/AppKit/X11.backend/CarbonKeys.h
|
|
+++ b/AppKit/X11.backend/CarbonKeys.h
|
|
@@ -90,13 +90,14 @@ static const uint8_t x11ToCarbon[256] = {
|
|
[65] = kVK_Space,
|
|
[22] = kVK_Delete,
|
|
[9] = kVK_Escape,
|
|
- [64] = kVK_Command,
|
|
+ [64] = kVK_Option,
|
|
[50] = kVK_Shift,
|
|
[66] = kVK_CapsLock,
|
|
- [133] = kVK_Option,
|
|
+ [133] = kVK_Command,
|
|
[37] = kVK_Control,
|
|
[62] = kVK_RightShift,
|
|
- [134] = kVK_RightOption,
|
|
+ [134] = 0x36, // right Command
|
|
+ [108] = kVK_RightOption,
|
|
[105] = kVK_RightControl,
|
|
[123] = kVK_VolumeUp,
|
|
[122] = kVK_VolumeDown,
|
|
@@ -197,13 +198,14 @@ static const uint8_t carbonToX11[256] = {
|
|
[kVK_Space] = 65,
|
|
[kVK_Delete] = 22,
|
|
[kVK_Escape] = 9,
|
|
- [kVK_Command] = 64,
|
|
+ [kVK_Command] = 133,
|
|
+ [0x36] = 134, // right Command
|
|
[kVK_Shift] = 50,
|
|
[kVK_CapsLock] = 66,
|
|
- [kVK_Option] = 133,
|
|
+ [kVK_Option] = 64,
|
|
[kVK_Control] = 37,
|
|
[kVK_RightShift] = 62,
|
|
- [kVK_RightOption] = 134,
|
|
+ [kVK_RightOption] = 108,
|
|
[kVK_RightControl] = 105,
|
|
[kVK_VolumeUp] = 123,
|
|
[kVK_VolumeDown] = 122,
|
|
diff --git a/AppKit/X11.backend/X11Cursor.m b/AppKit/X11.backend/X11Cursor.m
|
|
index bdc97bdf..139c91eb 100644
|
|
--- a/AppKit/X11.backend/X11Cursor.m
|
|
+++ b/AppKit/X11.backend/X11Cursor.m
|
|
@@ -67,16 +67,16 @@
|
|
const uint8_t *rowBytes = CGBitmapContextGetData(context);
|
|
const size_t bytesPerRow = CGBitmapContextGetBytesPerRow(context);
|
|
|
|
- for (int row = 0; row < height; row++, rowBytes += bytesPerRow) {
|
|
- for (int column = 0; column < width; column++) {
|
|
- memcpy(ximage->pixels + row * width, &rowBytes[column * 4],
|
|
- bytesPerRow);
|
|
- }
|
|
- }
|
|
+ /* Xcursor storage is tightly packed. Copy each row once; the old inner
|
|
+ * loop copied an entire row at every pixel and wrote past the allocation. */
|
|
+ for (size_t row = 0; row < height; ++row)
|
|
+ memcpy(ximage->pixels + row * width, rowBytes + row * bytesPerRow,
|
|
+ width * sizeof(XcursorPixel));
|
|
|
|
CGContextRelease(context);
|
|
|
|
_cursor = XcursorImageLoadCursor(display, ximage);
|
|
+ XcursorImageDestroy(ximage);
|
|
if (_cursor == None)
|
|
_cursor = XcursorLibraryLoadCursor(display, "left_ptr");
|
|
return self;
|
|
diff --git a/AppKit/X11.backend/X11Display.h b/AppKit/X11.backend/X11Display.h
|
|
index 18189404..5b3a34bc 100644
|
|
--- a/AppKit/X11.backend/X11Display.h
|
|
+++ b/AppKit/X11.backend/X11Display.h
|
|
@@ -10,13 +10,14 @@
|
|
#import <X11/Xlib.h>
|
|
#import <X11/Xresource.h>
|
|
#import <X11/Xlocale.h>
|
|
+#import "X11RelativeMouse.h"
|
|
|
|
#ifdef DARLING
|
|
#import <CoreFoundation/CFRunLoop.h>
|
|
#import <CoreFoundation/CFSocket.h>
|
|
#endif
|
|
|
|
-@class X11Cursor;
|
|
+@class X11Cursor, X11Window;
|
|
|
|
@interface X11Display : NSDisplay {
|
|
Display *_display;
|
|
@@ -36,19 +37,41 @@
|
|
NSTimeInterval lastClickTimeStamp;
|
|
int clickCount;
|
|
X11Cursor *_blankCursor, *_defaultCursor;
|
|
- BOOL _cursorGrabbed;
|
|
+ /* Relative mouse mode: requested by CGAssociateMouseAndMouseCursorPosition(false); _relativeMouse.active
|
|
+ * while its pointer grab on _relativeWindow is held (released on focus loss, retaken on focus). */
|
|
+ BOOL _relativeRequested;
|
|
+ Window _relativeWindow;
|
|
+ X11RelativeMouse _relativeMouse;
|
|
+ BOOL _isXwayland;
|
|
+ /* The cursor last defined by setCursor:, retained so its X id cannot be freed and reused while cached. */
|
|
+ Window _definedCursorWindow;
|
|
+ X11Cursor *_definedCursor;
|
|
+ BOOL _cursorHidden;
|
|
+ Window _confinedWindow;
|
|
+ unsigned int _otherButtonsDown;
|
|
KeySym _lastKeySym;
|
|
+ unsigned char _modifierKeysDown[256];
|
|
int _rrEventBase;
|
|
NSArray* _lastScreens;
|
|
+ // Root-window rectangle of the primary monitor (macOS's global origin).
|
|
+ NSRect _primaryRootFrame;
|
|
+ BOOL _primaryRootFrameValid;
|
|
|
|
@public
|
|
XIM _xim;
|
|
}
|
|
|
|
+- (void) updateMouseConfinementForWindow:(X11Window *)window;
|
|
- (Display *) display;
|
|
+- (BOOL) activateProcessIdentifier: (pid_t) processIdentifier;
|
|
|
|
- (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..86a647ff 100644
|
|
--- a/AppKit/X11.backend/X11Display.m
|
|
+++ b/AppKit/X11.backend/X11Display.m
|
|
@@ -48,12 +48,271 @@
|
|
#import "X11KeySymToUCS.h"
|
|
#import <X11/XKBlib.h>
|
|
#import <X11/Xutil.h>
|
|
+#import <X11/Xatom.h>
|
|
#import <X11/extensions/XKBrules.h>
|
|
#import <X11/extensions/Xrandr.h>
|
|
#import <X11/keysym.h>
|
|
#import <fcntl.h>
|
|
#import <fontconfig/fontconfig.h>
|
|
#import <stddef.h>
|
|
+#include <stdlib.h>
|
|
+
|
|
+/* darling-lol: passive, opt-in input trace for diagnosing relative mouse mode (e.g. League on Xwayland).
|
|
+ * Enabled only when the file /tmp/rclol-input-trace exists (checked once per process); lines are appended to
|
|
+ * /tmp/rclol-input-trace.log with the pid and a monotonic millisecond timestamp. It records pointer grabs,
|
|
+ * warps, cursor visibility, motion handling and pointer-position queries, and never changes behaviour. When
|
|
+ * disabled the cost is one load and branch; when enabled no X requests are added except the one-time startup
|
|
+ * extension queries. High-rate sources log a few lines verbatim and are then summarised once per second.
|
|
+ * Never logs key codes, text or window titles. Independent of the older RCLOL_INPUT_TRACE stderr logging. */
|
|
+#include <mach/mach_time.h>
|
|
+#include <objc/runtime.h>
|
|
+#include <pthread.h>
|
|
+#include <stdarg.h>
|
|
+#include <stdio.h>
|
|
+#include <string.h>
|
|
+#include <unistd.h>
|
|
+
|
|
+static int rclolTraceState = -1;
|
|
+static FILE *rclolTraceFile;
|
|
+static pthread_once_t rclolTraceOnce = PTHREAD_ONCE_INIT;
|
|
+static pthread_mutex_t rclolTraceLock = PTHREAD_MUTEX_INITIALIZER;
|
|
+static unsigned rclolGrabGeneration;
|
|
+
|
|
+static void rclolTraceInit(void) {
|
|
+ int on = 0;
|
|
+ if (access("/tmp/rclol-input-trace", F_OK) == 0) {
|
|
+ rclolTraceFile = fopen("/tmp/rclol-input-trace.log", "a");
|
|
+ if (rclolTraceFile) {
|
|
+ setvbuf(rclolTraceFile, NULL, _IOLBF, 0);
|
|
+ on = 1;
|
|
+ }
|
|
+ }
|
|
+ __atomic_store_n(&rclolTraceState, on, __ATOMIC_RELEASE);
|
|
+}
|
|
+
|
|
+static inline BOOL rclolTraceOn(void) {
|
|
+ int state = __atomic_load_n(&rclolTraceState, __ATOMIC_ACQUIRE);
|
|
+ if (__builtin_expect(state >= 0, 1))
|
|
+ return state;
|
|
+ pthread_once(&rclolTraceOnce, rclolTraceInit);
|
|
+ return __atomic_load_n(&rclolTraceState, __ATOMIC_ACQUIRE);
|
|
+}
|
|
+
|
|
+static unsigned long long rclolTraceNowMs(void) {
|
|
+ static mach_timebase_info_data_t tb;
|
|
+ if (tb.denom == 0) mach_timebase_info(&tb);
|
|
+ return (unsigned long long) ((__uint128_t) mach_absolute_time() * tb.numer / tb.denom / 1000000u);
|
|
+}
|
|
+
|
|
+static void rclolTraceLine(const char *format, ...) __attribute__((format(printf, 1, 2)));
|
|
+static void rclolTraceLine(const char *format, ...) {
|
|
+ char message[640];
|
|
+ va_list args;
|
|
+ va_start(args, format);
|
|
+ vsnprintf(message, sizeof message, format, args);
|
|
+ va_end(args);
|
|
+ fprintf(rclolTraceFile, "pid=%d t=%llums %s\n", (int) getpid(), rclolTraceNowMs(), message);
|
|
+}
|
|
+
|
|
+/* A rate-limited source: the first `limit` calls are logged verbatim, later ones are counted and summarised
|
|
+ * (count and last message) once at least a second has passed. */
|
|
+typedef struct {
|
|
+ const char *name;
|
|
+ unsigned limit, verbatim, pending;
|
|
+ unsigned long long windowStart;
|
|
+ char last[320];
|
|
+} RclolTraceChannel;
|
|
+#define RCLOL_CHANNEL(n, l) { n, l, 0, 0, 0, "" }
|
|
+static RclolTraceChannel rclolWarpAppChannel = RCLOL_CHANNEL("warp[app]", 20);
|
|
+static RclolTraceChannel rclolWarpRewarpChannel = RCLOL_CHANNEL("warp[motion-rewarp]", 20);
|
|
+static RclolTraceChannel rclolHideChannel = RCLOL_CHANNEL("hideCursor", 20);
|
|
+static RclolTraceChannel rclolUnhideChannel = RCLOL_CHANNEL("unhideCursor", 20);
|
|
+static RclolTraceChannel rclolSetCursorChannel = RCLOL_CHANNEL("setCursor", 20);
|
|
+static RclolTraceChannel rclolFocusChannel = RCLOL_CHANNEL("focus", 60);
|
|
+static RclolTraceChannel rclolConfineChannel = RCLOL_CHANNEL("confinement", 60);
|
|
+static RclolTraceChannel rclolErrorChannel = RCLOL_CHANNEL("xerror", 40);
|
|
+static RclolTraceChannel *rclolChannels[] = {
|
|
+ &rclolWarpAppChannel, &rclolWarpRewarpChannel, &rclolHideChannel,
|
|
+ &rclolUnhideChannel, &rclolSetCursorChannel, &rclolFocusChannel, &rclolConfineChannel, &rclolErrorChannel,
|
|
+};
|
|
+
|
|
+static struct {
|
|
+ unsigned verbatim, events, echoes, rewarps, relative, absolute;
|
|
+ unsigned long long windowStart;
|
|
+ unsigned long window;
|
|
+ int x, y, rootX, rootY, grabbed;
|
|
+ double dx, dy, lastX, lastY;
|
|
+} rclolMotion;
|
|
+
|
|
+static struct {
|
|
+ unsigned verbatim, calls, hits, queries, failures;
|
|
+ unsigned long long windowStart;
|
|
+ double x, y;
|
|
+ int rootX, rootY;
|
|
+} rclolLocation;
|
|
+
|
|
+static void rclolTraceFlushChannelLocked(RclolTraceChannel *channel, unsigned long long now) {
|
|
+ if (channel->pending)
|
|
+ rclolTraceLine("%s summary: %u calls in %llums, last: %s", channel->name, channel->pending,
|
|
+ now - channel->windowStart, channel->last);
|
|
+ channel->pending = 0;
|
|
+ channel->windowStart = now;
|
|
+}
|
|
+
|
|
+static void rclolTraceTickLocked(unsigned long long now, BOOL force) {
|
|
+ for (size_t i = 0; i < sizeof rclolChannels / sizeof *rclolChannels; ++i)
|
|
+ if (rclolChannels[i]->pending && (force || now - rclolChannels[i]->windowStart >= 1000))
|
|
+ rclolTraceFlushChannelLocked(rclolChannels[i], now);
|
|
+ if (rclolMotion.events && (force || now - rclolMotion.windowStart >= 1000)) {
|
|
+ rclolTraceLine("motion summary: %u events (%u warp-echo, %u relative+rewarp, %u relative, %u absolute) in "
|
|
+ "%llums window=%lu relative=%d last ev=%d,%d root=%d,%d delta=%.1f,%.1f last=%.1f,%.1f",
|
|
+ rclolMotion.events, rclolMotion.echoes, rclolMotion.rewarps, rclolMotion.relative,
|
|
+ rclolMotion.absolute,
|
|
+ now - rclolMotion.windowStart, rclolMotion.window, rclolMotion.grabbed, rclolMotion.x,
|
|
+ rclolMotion.y, rclolMotion.rootX, rclolMotion.rootY, rclolMotion.dx, rclolMotion.dy,
|
|
+ rclolMotion.lastX, rclolMotion.lastY);
|
|
+ rclolMotion.events = rclolMotion.echoes = rclolMotion.rewarps = rclolMotion.relative = 0;
|
|
+ rclolMotion.absolute = 0;
|
|
+ }
|
|
+ if (rclolLocation.calls && (force || now - rclolLocation.windowStart >= 1000)) {
|
|
+ rclolTraceLine("mouseLocation summary: %u calls (%u cache hits, %u queries, %u failed) in %llums "
|
|
+ "last=%.1f,%.1f root=%d,%d",
|
|
+ rclolLocation.calls, rclolLocation.hits, rclolLocation.queries, rclolLocation.failures,
|
|
+ now - rclolLocation.windowStart, rclolLocation.x, rclolLocation.y, rclolLocation.rootX,
|
|
+ rclolLocation.rootY);
|
|
+ rclolLocation.calls = rclolLocation.hits = rclolLocation.queries = rclolLocation.failures = 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void rclolTraceRated(RclolTraceChannel *channel, const char *format, ...) __attribute__((format(printf, 2, 3)));
|
|
+static void rclolTraceRated(RclolTraceChannel *channel, const char *format, ...) {
|
|
+ char message[320];
|
|
+ va_list args;
|
|
+ va_start(args, format);
|
|
+ vsnprintf(message, sizeof message, format, args);
|
|
+ va_end(args);
|
|
+ pthread_mutex_lock(&rclolTraceLock);
|
|
+ unsigned long long now = rclolTraceNowMs();
|
|
+ if (channel->verbatim < channel->limit) {
|
|
+ channel->verbatim++;
|
|
+ rclolTraceLine("%s #%u gen=%u: %s", channel->name, channel->verbatim, rclolGrabGeneration, message);
|
|
+ } else {
|
|
+ if (!channel->pending) channel->windowStart = now;
|
|
+ channel->pending++;
|
|
+ memcpy(channel->last, message, sizeof message);
|
|
+ }
|
|
+ rclolTraceTickLocked(now, NO);
|
|
+ pthread_mutex_unlock(&rclolTraceLock);
|
|
+}
|
|
+
|
|
+/* Each grabMouse: call starts a new generation: flush pending summaries and re-arm the verbatim budgets for
|
|
+ * warps, motion and mouseLocation so the loop's first steps after every grab change are visible. */
|
|
+static void rclolTraceGrabChanged(void) {
|
|
+ pthread_mutex_lock(&rclolTraceLock);
|
|
+ rclolTraceTickLocked(rclolTraceNowMs(), YES);
|
|
+ rclolGrabGeneration++;
|
|
+ rclolWarpAppChannel.verbatim = rclolWarpRewarpChannel.verbatim = 0;
|
|
+ rclolMotion.verbatim = rclolLocation.verbatim = 0;
|
|
+ pthread_mutex_unlock(&rclolTraceLock);
|
|
+}
|
|
+
|
|
+/* pos/last are the window-local event position and previous position for absolute motion, and the virtual cursor
|
|
+ * and previous physical pointer (root coordinates) for relative motion. */
|
|
+static void rclolTraceMotion(XEvent *ev, BOOL grabbed, CGPoint pos, CGPoint last, CGFloat dx, CGFloat dy,
|
|
+ const char *decision, int forwarded)
|
|
+{
|
|
+ pthread_mutex_lock(&rclolTraceLock);
|
|
+ unsigned long long now = rclolTraceNowMs();
|
|
+ if (rclolMotion.verbatim < 30) {
|
|
+ rclolMotion.verbatim++;
|
|
+ rclolTraceLine("motion #%u gen=%u: window=%lu ev=%d,%d root=%d,%d pos=%.1f,%.1f last=%.1f,%.1f "
|
|
+ "delta=%.1f,%.1f relative=%d decision=%s forwarded=%d send_event=%d time=%lu state=0x%x",
|
|
+ rclolMotion.verbatim, rclolGrabGeneration, (unsigned long) ev->xany.window, ev->xmotion.x,
|
|
+ ev->xmotion.y, ev->xmotion.x_root, ev->xmotion.y_root, pos.x, pos.y, last.x, last.y, dx, dy,
|
|
+ grabbed, decision, forwarded, ev->xany.send_event, (unsigned long) ev->xmotion.time,
|
|
+ ev->xmotion.state);
|
|
+ }
|
|
+ if (!rclolMotion.events) rclolMotion.windowStart = now;
|
|
+ rclolMotion.events++;
|
|
+ if (!strcmp(decision, "echo")) rclolMotion.echoes++;
|
|
+ else if (!strcmp(decision, "relative+rewarp")) rclolMotion.rewarps++;
|
|
+ else if (!strncmp(decision, "relative", 8)) rclolMotion.relative++;
|
|
+ else rclolMotion.absolute++;
|
|
+ rclolMotion.window = ev->xany.window;
|
|
+ rclolMotion.x = ev->xmotion.x;
|
|
+ rclolMotion.y = ev->xmotion.y;
|
|
+ rclolMotion.rootX = ev->xmotion.x_root;
|
|
+ rclolMotion.rootY = ev->xmotion.y_root;
|
|
+ rclolMotion.grabbed = grabbed;
|
|
+ rclolMotion.dx = dx;
|
|
+ rclolMotion.dy = dy;
|
|
+ rclolMotion.lastX = last.x;
|
|
+ rclolMotion.lastY = last.y;
|
|
+ rclolTraceTickLocked(now, NO);
|
|
+ pthread_mutex_unlock(&rclolTraceLock);
|
|
+}
|
|
+
|
|
+static void rclolTraceMouseLocation(NSPoint result, BOOL ok, BOOL hit, int rootX, int rootY, BOOL grabbed) {
|
|
+ pthread_mutex_lock(&rclolTraceLock);
|
|
+ unsigned long long now = rclolTraceNowMs();
|
|
+ const char *source = grabbed ? "virtual" : !ok ? "query-failed" : hit ? "cache-hit" : "XQueryPointer";
|
|
+ if (rclolLocation.verbatim < 30) {
|
|
+ rclolLocation.verbatim++;
|
|
+ rclolTraceLine("mouseLocation #%u gen=%u: -> %.1f,%.1f root=%d,%d source=%s relative=%d",
|
|
+ rclolLocation.verbatim, rclolGrabGeneration, result.x, result.y, rootX, rootY, source,
|
|
+ grabbed);
|
|
+ }
|
|
+ if (!rclolLocation.calls) rclolLocation.windowStart = now;
|
|
+ rclolLocation.calls++;
|
|
+ if (!ok) rclolLocation.failures++;
|
|
+ else if (hit) rclolLocation.hits++;
|
|
+ else rclolLocation.queries++;
|
|
+ rclolLocation.x = result.x;
|
|
+ rclolLocation.y = result.y;
|
|
+ rclolLocation.rootX = rootX;
|
|
+ rclolLocation.rootY = rootY;
|
|
+ rclolTraceTickLocked(now, NO);
|
|
+ pthread_mutex_unlock(&rclolTraceLock);
|
|
+}
|
|
+
|
|
+static void rclolTraceStartup(Display *display) {
|
|
+ if (!rclolTraceOn()) return;
|
|
+ int opcode, event, error;
|
|
+ Bool xwayland = XQueryExtension(display, "XWAYLAND", &opcode, &event, &error);
|
|
+ int xiOpcode = 0, xiEvent = 0, xiError = 0;
|
|
+ Bool xi = XQueryExtension(display, "XInputExtension", &xiOpcode, &xiEvent, &xiError);
|
|
+ int screen = DefaultScreen(display);
|
|
+ rclolTraceLine("startup: input trace enabled; vendor=\"%s\" release=%d protocol=%d.%d xwayland=%d "
|
|
+ "XInputExtension=%d opcode=%d (libXi not linked; version not queried) screens=%d default=%d "
|
|
+ "size=%dx%d (%dx%dmm) RCLOL_INPUT_TRACE=%d",
|
|
+ ServerVendor(display), VendorRelease(display), ProtocolVersion(display),
|
|
+ ProtocolRevision(display), xwayland, xi, xiOpcode, ScreenCount(display), screen,
|
|
+ DisplayWidth(display, screen), DisplayHeight(display, screen),
|
|
+ DisplayWidthMM(display, screen), DisplayHeightMM(display, screen),
|
|
+ getenv("RCLOL_INPUT_TRACE") != NULL);
|
|
+}
|
|
+
|
|
+static const char *rclolGrabResultName(int result) {
|
|
+ switch (result) {
|
|
+ case GrabSuccess: return "GrabSuccess";
|
|
+ case AlreadyGrabbed: return "AlreadyGrabbed";
|
|
+ case GrabInvalidTime: return "GrabInvalidTime";
|
|
+ case GrabNotViewable: return "GrabNotViewable";
|
|
+ case GrabFrozen: return "GrabFrozen";
|
|
+ default: return "not-called";
|
|
+ }
|
|
+}
|
|
+
|
|
+static const char *rclolFocusModeName(int mode) {
|
|
+ static const char *names[] = {"Normal", "Grab", "Ungrab", "WhileGrabbed"};
|
|
+ return mode >= 0 && mode < 4 ? names[mode] : "?";
|
|
+}
|
|
+
|
|
+static const char *rclolFocusDetailName(int detail) {
|
|
+ static const char *names[] = {"Ancestor", "Virtual", "Inferior", "Nonlinear", "NonlinearVirtual",
|
|
+ "Pointer", "PointerRoot", "DetailNone"};
|
|
+ return detail >= 0 && detail < 8 ? names[detail] : "?";
|
|
+}
|
|
|
|
void NSColorSetCatalogColor(NSColorListName catalogName, NSColorName colorName, NSColor *color);
|
|
|
|
@@ -75,6 +334,15 @@ static void socketCallback(CFSocketRef s, CFSocketCallBackType type,
|
|
- init {
|
|
if (self = [super init]) {
|
|
|
|
+#ifdef DARLING
|
|
+ // EGL also uses this Display from compositor threads. Enable Xlib's
|
|
+ // connection locks before opening it or issuing any Xlib requests.
|
|
+ if (!XInitThreads()) {
|
|
+ NSLog(@"Unable to initialize Xlib thread support");
|
|
+ [self release];
|
|
+ return nil;
|
|
+ }
|
|
+#endif
|
|
_display = XOpenDisplay(NULL);
|
|
|
|
if (_display == NULL) {
|
|
@@ -136,6 +404,10 @@ static void socketCallback(CFSocketRef s, CFSocketCallBackType type,
|
|
if (XRRQueryExtension(_display, &_rrEventBase, &errorBase)) {
|
|
XRRSelectInput(_display, DefaultRootWindow(_display), RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask | RROutputChangeNotifyMask | RROutputPropertyNotifyMask);
|
|
}
|
|
+ /* Xwayland forwards pointer warps to the compositor only while the X cursor is hidden. */
|
|
+ int xwaylandOpcode, xwaylandEvent, xwaylandError;
|
|
+ _isXwayland = XQueryExtension(_display, "XWAYLAND", &xwaylandOpcode, &xwaylandEvent, &xwaylandError);
|
|
+ rclolTraceStartup(_display);
|
|
|
|
lastFocusedWindow = nil;
|
|
lastClickTimeStamp = 0.0;
|
|
@@ -147,15 +419,18 @@ static void socketCallback(CFSocketRef s, CFSocketCallBackType type,
|
|
- (void) dealloc {
|
|
[_blankCursor release];
|
|
[_defaultCursor release];
|
|
+ [_definedCursor release];
|
|
|
|
- XCloseIM(_xim);
|
|
+ if (_xim)
|
|
+ XCloseIM(_xim);
|
|
|
|
if (_display)
|
|
XCloseDisplay(_display);
|
|
#ifdef DARLING
|
|
- CFRunLoopRemoveSource(CFRunLoopGetMain(), _source, kCFRunLoopCommonModes);
|
|
- if (_source != NULL)
|
|
+ if (_source != NULL) {
|
|
+ CFRunLoopRemoveSource(CFRunLoopGetMain(), _source, kCFRunLoopCommonModes);
|
|
CFRelease(_source);
|
|
+ }
|
|
if (_cfSocket != NULL)
|
|
CFRelease(_cfSocket);
|
|
#endif
|
|
@@ -172,6 +447,56 @@ static void socketCallback(CFSocketRef s, CFSocketCallBackType type,
|
|
return _display;
|
|
}
|
|
|
|
+- (BOOL) activateProcessIdentifier: (pid_t) processIdentifier {
|
|
+ if (!_display || processIdentifier <= 0) return NO;
|
|
+
|
|
+ Window root = DefaultRootWindow(_display), parent = None;
|
|
+ Window *children = NULL;
|
|
+ unsigned int count = 0;
|
|
+ if (!XQueryTree(_display, root, &root, &parent, &children, &count))
|
|
+ return NO;
|
|
+
|
|
+ Atom pidAtom = XInternAtom(_display, "_NET_WM_PID", False);
|
|
+ Atom activeAtom = XInternAtom(_display, "_NET_ACTIVE_WINDOW", False);
|
|
+ BOOL activated = NO;
|
|
+ for (unsigned int index = count; index > 0; --index) {
|
|
+ Window window = children[index - 1];
|
|
+ Atom actualType = None;
|
|
+ int actualFormat = 0;
|
|
+ unsigned long items = 0, remaining = 0;
|
|
+ unsigned char *value = NULL;
|
|
+ int status = XGetWindowProperty(_display, window, pidAtom, 0, 1, False,
|
|
+ XA_CARDINAL, &actualType, &actualFormat,
|
|
+ &items, &remaining, &value);
|
|
+ BOOL matches = status == Success && actualType == XA_CARDINAL &&
|
|
+ actualFormat == 32 && items == 1 && value &&
|
|
+ *(unsigned long *)value == (unsigned long)processIdentifier;
|
|
+ if (value) XFree(value);
|
|
+ if (!matches) continue;
|
|
+
|
|
+ XWindowAttributes attributes;
|
|
+ if (!XGetWindowAttributes(_display, window, &attributes) ||
|
|
+ attributes.map_state != IsViewable) continue;
|
|
+
|
|
+ XEvent event = {0};
|
|
+ event.xclient.type = ClientMessage;
|
|
+ event.xclient.window = window;
|
|
+ event.xclient.message_type = activeAtom;
|
|
+ event.xclient.format = 32;
|
|
+ event.xclient.data.l[0] = 1;
|
|
+ event.xclient.data.l[1] = CurrentTime;
|
|
+ XSendEvent(_display, root, False,
|
|
+ SubstructureRedirectMask | SubstructureNotifyMask, &event);
|
|
+ XRaiseWindow(_display, window);
|
|
+ XSetInputFocus(_display, window, RevertToPointerRoot, CurrentTime);
|
|
+ XFlush(_display);
|
|
+ activated = YES;
|
|
+ break;
|
|
+ }
|
|
+ if (children) XFree(children);
|
|
+ return activated;
|
|
+}
|
|
+
|
|
- (void) _enableDetectableAutoRepeat {
|
|
int major = XkbMajorVersion, minor = XkbMinorVersion;
|
|
XkbStateRec state;
|
|
@@ -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,52 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
return &layout->layout;
|
|
}
|
|
|
|
+/* darling-lol: the game asks for the pointer position, the pressed buttons and the modifier state about a thousand
|
|
+ * times per second. Each answer was a synchronous X round trip (XQueryPointer / XkbGetState) that puts the main thread
|
|
+ * to sleep until XWayland replies; while the mouse moves they dominated the slow frames. Reuse a reply for 8 ms, keep it
|
|
+ * current from the input events themselves (a motion event carries position and button state), and drop it on every
|
|
+ * button, key, crossing or focus event so the next question after a state change is always asked for real. */
|
|
+#include <mach/mach_time.h>
|
|
+static BOOL rclolFresh(uint64_t *stamp) {
|
|
+ static mach_timebase_info_data_t tb;
|
|
+ if (tb.denom == 0) mach_timebase_info(&tb);
|
|
+ uint64_t now = mach_absolute_time();
|
|
+ if (*stamp != 0 && (now - *stamp) * tb.numer / tb.denom < 8000000ULL) return YES;
|
|
+ *stamp = now;
|
|
+ return NO;
|
|
+}
|
|
+static uint64_t rclolPointerStamp, rclolModsStamp;
|
|
+static int rclolRootX, rclolRootY;
|
|
+static unsigned int rclolPointerState;
|
|
+static NSUInteger rclolMods;
|
|
+static BOOL rclolQueryPointer(Display *display) {
|
|
+ if (rclolFresh(&rclolPointerStamp)) return YES;
|
|
+ for (int screen = 0; screen < ScreenCount(display); ++screen) {
|
|
+ Window root, child;
|
|
+ int windowX, windowY;
|
|
+ if (XQueryPointer(display, RootWindow(display, screen), &root, &child, &rclolRootX, &rclolRootY,
|
|
+ &windowX, &windowY, &rclolPointerState))
|
|
+ return YES;
|
|
+ }
|
|
+ rclolPointerStamp = 0;
|
|
+ return NO;
|
|
+}
|
|
+
|
|
- (NSUInteger) currentModifierFlags {
|
|
+ if (rclolFresh(&rclolModsStamp)) return rclolMods;
|
|
XkbStateRec r;
|
|
- if (XkbGetState(_display, XkbUseCoreKbd, &r) != Success)
|
|
+ if (XkbGetState(_display, XkbUseCoreKbd, &r) != Success) {
|
|
+ rclolModsStamp = 0;
|
|
return 0;
|
|
+ }
|
|
+ return rclolMods = [self modifierFlagsForState: r.mods];
|
|
+}
|
|
|
|
- return [self modifierFlagsForState: r.mods];
|
|
+- (NSUInteger) pressedMouseButtons {
|
|
+ if (!rclolQueryPointer(_display)) return 0;
|
|
+ unsigned int state = rclolPointerState;
|
|
+ // AppKit numbers left/right/middle as 0/1/2; X11 uses 1/3/2.
|
|
+ return ((state & Button1Mask) ? 1u : 0u) | ((state & Button3Mask) ? 2u : 0u) | ((state & Button2Mask) ? 4u : 0u);
|
|
}
|
|
|
|
- (NSPasteboard *) pasteboardWithName: (NSString *) name {
|
|
@@ -675,6 +1113,15 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
blue: 0.60f
|
|
alpha: 1.0f];
|
|
|
|
+ if ([colorName isEqual: @"controlAccentBlueColor"])
|
|
+ /* darling-lol: macOS 10.14+ system accent blue (sRGB 0,122,255);
|
|
+ * Chromium queries it during UI setup and previously fell back to
|
|
+ * red. */
|
|
+ return [NSColor colorWithSRGBRed: 0.0f
|
|
+ green: 0.478431f
|
|
+ blue: 1.0f
|
|
+ alpha: 1.0f];
|
|
+
|
|
NSLog(@"missing color for %@", colorName);
|
|
return [NSColor redColor];
|
|
}
|
|
@@ -688,6 +1135,10 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
}
|
|
|
|
- (void) hideCursor {
|
|
+ if (rclolTraceOn())
|
|
+ rclolTraceRated(&rclolHideChannel, "blankExisted=%d relative=%d/%d hidden=%d confined=%lu",
|
|
+ _blankCursor != nil, _relativeRequested, _relativeMouse.active, _cursorHidden,
|
|
+ (unsigned long) _confinedWindow);
|
|
if (!_blankCursor)
|
|
_blankCursor = [[X11Cursor alloc] initBlank];
|
|
[self setCursor: _blankCursor];
|
|
@@ -695,6 +1146,10 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
|
|
- (void) unhideCursor {
|
|
NSCursor *current = [NSCursor currentCursor];
|
|
+ if (rclolTraceOn())
|
|
+ rclolTraceRated(&rclolUnhideChannel, "path=%s current=%p relative=%d/%d hidden=%d",
|
|
+ current ? "push-pop" : "default", current, _relativeRequested, _relativeMouse.active,
|
|
+ _cursorHidden);
|
|
if (current != nil) {
|
|
[current push];
|
|
[current pop];
|
|
@@ -747,12 +1202,28 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
|
|
- (void) setCursor: (id) cursor {
|
|
CGWindow *window = [[NSApp keyWindow] platformWindow];
|
|
- if (window != nil) {
|
|
- Window w = [(X11Window *) window windowHandle];
|
|
- Cursor c = ((X11Cursor *) cursor).cursor;
|
|
-
|
|
+ Window w = window ? [(X11Window *) window windowHandle] : None;
|
|
+ Cursor c = ((X11Cursor *) cursor).cursor;
|
|
+ /* unhideCursor re-sets the current cursor and games call it every frame: only send actual changes. The
|
|
+ * defined cursor stays retained, so its X id cannot be freed and handed to a different cursor meanwhile. */
|
|
+ BOOL unchanged = w != None && w == _definedCursorWindow && c == _definedCursor.cursor;
|
|
+ if (rclolTraceOn())
|
|
+ rclolTraceRated(&rclolSetCursorChannel, "cursor=%p blank=%d default=%d xcursor=%lu keyWindow=%d xwin=%lu applied=%d relative=%d/%d",
|
|
+ cursor, cursor != nil && cursor == _blankCursor, cursor != nil && cursor == _defaultCursor,
|
|
+ (unsigned long) c, [NSApp keyWindow] != nil, (unsigned long) w, w != None && !unchanged,
|
|
+ _relativeRequested, _relativeMouse.active);
|
|
+ if (w != None && !unchanged) {
|
|
XDefineCursor(_display, w, c);
|
|
- XSync(_display, False);
|
|
+ /* Send cursor changes immediately without waiting for an X server
|
|
+ * reply on the input thread. X requests on this connection remain
|
|
+ * ordered; no server result is consumed here. */
|
|
+ XFlush(_display);
|
|
+ [cursor retain];
|
|
+ [_definedCursor release];
|
|
+ _definedCursor = cursor;
|
|
+ _definedCursorWindow = w;
|
|
+ /* initBlank's empty-mask cursor counts as hidden for Xwayland's pointer warp emulation. */
|
|
+ _cursorHidden = cursor != nil && cursor == _blankCursor && c != None;
|
|
}
|
|
}
|
|
|
|
@@ -921,15 +1392,22 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
}
|
|
|
|
- (NSPoint) mouseLocation {
|
|
- Window child, root = DefaultRootWindow(_display);
|
|
- int root_x, root_y;
|
|
- int win_x, win_y;
|
|
- unsigned int mask;
|
|
-
|
|
- 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 - 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);
|
|
+ NSPoint result = NSMakePoint(rclolRootX - NSMinX(primary), NSMaxY(primary) - rclolRootY);
|
|
+ if (rclolTraceOn())
|
|
+ rclolTraceMouseLocation(result, rclolQueried, rclolStampBefore != 0 && rclolPointerStamp == rclolStampBefore,
|
|
+ rclolRootX, rclolRootY, NO);
|
|
+ return result;
|
|
}
|
|
|
|
- (void) setWindow: (id) window forID: (XID) i {
|
|
@@ -948,6 +1426,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;
|
|
+ if (_definedCursorWindow == i) {
|
|
+ _definedCursorWindow = None;
|
|
+ [_definedCursor release];
|
|
+ _definedCursor = nil;
|
|
+ }
|
|
+ /* The server released any grab on a destroyed window. */
|
|
+ if (_confinedWindow == i)
|
|
+ _confinedWindow = None;
|
|
+ if (_relativeWindow == i)
|
|
+ [self suspendRelativeMouse];
|
|
}
|
|
}
|
|
|
|
@@ -962,20 +1450,82 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
{
|
|
#ifndef DARLING
|
|
[[NSRunLoop currentRunLoop] addInputSource: _inputSource forMode: mode];
|
|
-#else
|
|
- [self processPendingEvents];
|
|
-#endif
|
|
|
|
NSEvent *result = [super nextEventMatchingMask: mask
|
|
untilDate: untilDate
|
|
inMode: mode
|
|
dequeue: dequeue];
|
|
|
|
-#ifndef DARLING
|
|
[[NSRunLoop currentRunLoop] removeInputSource: _inputSource forMode: mode];
|
|
-#endif
|
|
|
|
return result;
|
|
+#else
|
|
+ // Darling: the CFSocket runloop source watching the X connection is not
|
|
+ // guaranteed to wake us, so a single blocking wait can sleep through
|
|
+ // input forever (X events queue unread, app looks frozen). Poll with a
|
|
+ // short slice instead. (darling-lol M2b)
|
|
+ /* A past deadline means poll, not skip queued input. Chromium's event
|
|
+ * pump regularly uses distantPast while checking for pending events. */
|
|
+ [self processPendingEvents];
|
|
+ NSEvent *result = [super nextEventMatchingMask:mask untilDate:[NSDate date]
|
|
+ inMode:mode dequeue:dequeue];
|
|
+ if (result != nil && [result type] == NSAppKitSystem)
|
|
+ result = nil;
|
|
+ NSTimeInterval remaining;
|
|
+ while (result == nil && (remaining = [untilDate timeIntervalSinceNow]) > 0) {
|
|
+ /* Display-link and input sources have no CF timer deadline. A 20ms
|
|
+ * fallback can sleep past every 60Hz source wakeup; bound its latency
|
|
+ * below a refresh interval while the socket wakeup path is polled. */
|
|
+ NSTimeInterval slice = remaining < 0.004 ? remaining : 0.004;
|
|
+ /* A quiet X connection must not prevent main-queue callbacks or
|
|
+ * CF timers from running while AppKit waits for its next event. */
|
|
+ CFStringRef cfMode = (mode == nil || [mode isEqual:NSDefaultRunLoopMode])
|
|
+ ? kCFRunLoopDefaultMode : (CFStringRef)mode;
|
|
+ SInt32 runResult = CFRunLoopRunInMode(cfMode, 0.0, true);
|
|
+ extern void _dispatch_main_queue_callback_4CF(void *);
|
|
+ _dispatch_main_queue_callback_4CF(NULL);
|
|
+ [self processPendingEvents];
|
|
+ /* Do not let NSRunLoop perform the timed wait here. Darling's
|
|
+ * socket-backed Mach receive can ignore the CF timer deadline and
|
|
+ * sleep forever, preventing Electron readiness promises, dispatch
|
|
+ * work, and repeating timers from being serviced. Poll the nested
|
|
+ * run loop without blocking, then bound the idle wait ourselves. */
|
|
+ result = [super nextEventMatchingMask: mask
|
|
+ untilDate: [NSDate date]
|
|
+ inMode: mode
|
|
+ dequeue: dequeue];
|
|
+ if (result != nil && [result type] == NSAppKitSystem) {
|
|
+ result = nil;
|
|
+ }
|
|
+ /* A handled source can immediately queue more work (Chromium and
|
|
+ * libuv do this). Drain it before the idle poll delay; sleeping after
|
|
+ * every callback caps a busy source at 50 callbacks per second. */
|
|
+ if (result == nil && runResult == kCFRunLoopRunHandledSource)
|
|
+ continue;
|
|
+ if (result == nil) {
|
|
+ /* Polling must honor timers in both the Cocoa mode and the CF
|
|
+ * main-queue mode. A fixed 20ms sleep misses 60Hz deadlines. */
|
|
+ CFRunLoopRef loop = CFRunLoopGetCurrent();
|
|
+ CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
|
|
+ CFAbsoluteTime next = CFRunLoopGetNextTimerFireDate(loop, cfMode);
|
|
+ if (mode && !CFEqual((CFStringRef)mode, cfMode)) {
|
|
+ CFAbsoluteTime cocoaNext = CFRunLoopGetNextTimerFireDate(loop, (CFStringRef)mode);
|
|
+ if (cocoaNext > 0 && (next <= 0 || cocoaNext < next)) next = cocoaNext;
|
|
+ }
|
|
+ if (next > 0 && next - now < slice)
|
|
+ slice = next > now ? next - now : 0;
|
|
+ }
|
|
+ if (result == nil && slice > 0) {
|
|
+ /* Keep this polling deadline independent of Mach wait timers. */
|
|
+ struct { long seconds, nanoseconds; } delay = {
|
|
+ 0, (long)(slice * 1000000000.0)
|
|
+ };
|
|
+ extern long __lolsys_syscall(long, long, long, long, long, long, int);
|
|
+ __lolsys_syscall((long)&delay, 0, 0, 0, 0, 0, 35 /* Linux nanosleep */);
|
|
+ }
|
|
+ }
|
|
+ return result;
|
|
+#endif
|
|
}
|
|
|
|
- (NSEventModifierFlags) modifierFlagsForState: (unsigned int) state {
|
|
@@ -1010,17 +1560,96 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
return result;
|
|
}
|
|
|
|
+/* X key events carry the state BEFORE the transition. Cocoa flagsChanged
|
|
+ * carries the state AFTER it. Keep both sides separately so releasing one
|
|
+ * Shift/Command key does not clear the other, and reconcile missed releases
|
|
+ * when the compositor intercepts a shortcut or changes keyboard focus. */
|
|
+static unsigned int rclolModifierMask(unsigned int key) {
|
|
+ switch (key) {
|
|
+ case 50: case 62: return ShiftMask;
|
|
+ case 37: case 105: return ControlMask;
|
|
+ case 64: return Mod1Mask;
|
|
+ case 108: return Mod5Mask;
|
|
+ case 133: case 134: return Mod4Mask;
|
|
+ default: return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+- (void) postModifierKey:(unsigned int)key state:(unsigned int)state window:(id)delegate {
|
|
+ NSEvent *event = [NSEvent keyEventWithType:NSFlagsChanged
|
|
+ location:NSZeroPoint modifierFlags:[self modifierFlagsForState:state]
|
|
+ timestamp:0 windowNumber:[delegate windowNumber] context:nil
|
|
+ characters:@"" charactersIgnoringModifiers:@"" isARepeat:NO
|
|
+ keyCode:x11ToCarbon[key]];
|
|
+ [self postEvent:event atStart:NO];
|
|
+}
|
|
+
|
|
+- (void) reconcileModifierKeys:(const char *)keys window:(id)delegate {
|
|
+ unsigned int state = 0;
|
|
+ for (unsigned int key = 0; key < 256; ++key)
|
|
+ if (_modifierKeysDown[key]) state |= rclolModifierMask(key);
|
|
+ for (unsigned int key = 0; key < 256; ++key) {
|
|
+ unsigned int mask = rclolModifierMask(key);
|
|
+ if (!mask) continue;
|
|
+ BOOL down = keys && (keys[key / 8] & (1 << (key % 8)));
|
|
+ if (down == _modifierKeysDown[key]) continue;
|
|
+ _modifierKeysDown[key] = down;
|
|
+ state &= ~mask;
|
|
+ for (unsigned int other = 0; other < 256; ++other)
|
|
+ if (_modifierKeysDown[other]) state |= rclolModifierMask(other);
|
|
+ [self postModifierKey:key state:state window:delegate];
|
|
+ }
|
|
+}
|
|
+
|
|
- (void) postXEvent: (XEvent *) ev {
|
|
id event = nil;
|
|
NSEventType type;
|
|
X11Window* window = (X11Window*) [self windowForID: ev->xany.window];
|
|
|
|
id delegate = [window delegate];
|
|
+ static int inputTrace = -1;
|
|
+ if (inputTrace < 0) inputTrace = getenv("RCLOL_INPUT_TRACE") != NULL;
|
|
+ if (inputTrace && (ev->type == KeyPress || ev->type == KeyRelease || ev->type == ButtonPress))
|
|
+ NSLog(@"LOLINPUT X type=%d window=%lu mapped=%@ number=%ld", ev->type,
|
|
+ (unsigned long)ev->xany.window, [delegate class], (long)[delegate windowNumber]);
|
|
+
|
|
+ switch (ev->type) {
|
|
+ case MotionNotify:
|
|
+ rclolRootX = ev->xmotion.x_root;
|
|
+ rclolRootY = ev->xmotion.y_root;
|
|
+ rclolPointerState = ev->xmotion.state;
|
|
+ rclolPointerStamp = mach_absolute_time();
|
|
+ break;
|
|
+ case KeyPress: case KeyRelease: case ButtonPress: case ButtonRelease:
|
|
+ case EnterNotify: case LeaveNotify: case FocusIn: case FocusOut: case KeymapNotify:
|
|
+ rclolPointerStamp = rclolModsStamp = 0;
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+ /* Expected warp echoes are among the events queued when the warp was synced; motion ages them itself. */
|
|
+ if (_relativeMouse.active && ev->type != MotionNotify)
|
|
+ X11RelativeMouseTick(&_relativeMouse);
|
|
|
|
switch (ev->type) {
|
|
case KeyPress:
|
|
case KeyRelease:;
|
|
|
|
+ unsigned int key = ev->xkey.keycode;
|
|
+ unsigned int mask = rclolModifierMask(key);
|
|
+ if (mask || key == 66) {
|
|
+ unsigned int state = ev->xkey.state;
|
|
+ if (key == 66) {
|
|
+ if (ev->type == KeyPress) state ^= LockMask;
|
|
+ } else {
|
|
+ _modifierKeysDown[key] = ev->type == KeyPress;
|
|
+ state &= ~mask;
|
|
+ for (unsigned int other = 0; other < 256; ++other)
|
|
+ if (_modifierKeysDown[other]) state |= rclolModifierMask(other);
|
|
+ }
|
|
+ [self postModifierKey:key state:state window:delegate];
|
|
+ break;
|
|
+ }
|
|
NSEventModifierFlags modifierFlags = [self modifierFlagsForState: ev->xkey.state];
|
|
char buf[20] = {0};
|
|
KeySym keySym;
|
|
@@ -1038,6 +1667,18 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
buf[strLen] = 0;
|
|
}
|
|
|
|
+ /* Host clipboard shortcuts use Control; the guest app expects Command.
|
|
+ * Preserve other Control bindings, AltGr and explicit Super shortcuts. */
|
|
+ if ((modifierFlags & NSControlKeyMask) &&
|
|
+ !(modifierFlags & (NSCommandKeyMask | NSAlternateKeyMask)) &&
|
|
+ (keySym == XK_v || keySym == XK_V || keySym == XK_c ||
|
|
+ keySym == XK_C || keySym == XK_x || keySym == XK_X)) {
|
|
+ modifierFlags = (modifierFlags & ~NSControlKeyMask) | NSCommandKeyMask;
|
|
+ buf[0] = (char) ((keySym >= XK_A && keySym <= XK_Z) ?
|
|
+ keySym - XK_A + 'a' : keySym);
|
|
+ buf[1] = 0;
|
|
+ }
|
|
+
|
|
id str = [[NSString alloc] initWithCString: buf
|
|
encoding: NSUTF8StringEncoding];
|
|
NSPoint pos =
|
|
@@ -1077,6 +1718,30 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
break;
|
|
|
|
case ButtonPress:;
|
|
+ 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];
|
|
+ CGFloat wheelX = ev->xbutton.button == 6 ? 1.0 :
|
|
+ ev->xbutton.button == 7 ? -1.0 : 0.0;
|
|
+ CGFloat wheelY = ev->xbutton.button == Button4 ? 1.0 :
|
|
+ ev->xbutton.button == Button5 ? -1.0 : 0.0;
|
|
+ static int scrollLines;
|
|
+ if (!scrollLines) {
|
|
+ const char *setting = getenv("RCLOL_SCROLL_SPEED");
|
|
+ int requested = setting ? atoi(setting) : 3;
|
|
+ scrollLines = requested >= 1 && requested <= 20 ? requested : 3;
|
|
+ }
|
|
+ wheelX *= scrollLines; wheelY *= scrollLines;
|
|
+ event = [NSEvent mouseEventWithType:NSScrollWheel location:pos
|
|
+ modifierFlags:[self modifierFlagsForState:ev->xbutton.state]
|
|
+ window:delegate clickCount:0 deltaX:wheelX deltaY:wheelY];
|
|
+ [self postEvent:event atStart:NO];
|
|
+ break;
|
|
+ }
|
|
+ if (ev->xbutton.button == Button2 || ev->xbutton.button >= 8) {
|
|
+ unsigned b = ev->xbutton.button == Button2 ? 2 : ev->xbutton.button - 5;
|
|
+ if (b < 32) _otherButtonsDown |= 1u << b;
|
|
+ }
|
|
NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate];
|
|
|
|
if (now - lastClickTimeStamp < [self doubleClickInterval]) {
|
|
@@ -1086,8 +1751,8 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
}
|
|
lastClickTimeStamp = now;
|
|
|
|
- pos = [window
|
|
- transformPoint: NSMakePoint(ev->xbutton.x, ev->xbutton.y)];
|
|
+ pos = [self locationInWindow: window x: ev->xbutton.x y: ev->xbutton.y
|
|
+ rootX: ev->xbutton.x_root rootY: ev->xbutton.y_root];
|
|
|
|
switch (ev->xbutton.button) {
|
|
case Button1:
|
|
@@ -1096,10 +1761,6 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
case Button3:
|
|
type = NSRightMouseDown;
|
|
break;
|
|
- case Button4:
|
|
- case Button5:
|
|
- // Skip these, we'll send NSScrollWheel on release.
|
|
- return;
|
|
default:
|
|
type = NSOtherMouseDown;
|
|
}
|
|
@@ -1113,14 +1774,22 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
clickCount: clickCount
|
|
deltaX: 0.0
|
|
deltaY: 0.0];
|
|
- [(NSEvent_mouse *) event _setButtonNumber: ev->xbutton.button];
|
|
+ [(NSEvent_mouse *) event _setButtonNumber:
|
|
+ ev->xbutton.button == Button1 ? 0 : ev->xbutton.button == Button3 ? 1 :
|
|
+ ev->xbutton.button == Button2 ? 2 : ev->xbutton.button - 5];
|
|
[self postEvent: event atStart: NO];
|
|
break;
|
|
|
|
case ButtonRelease:
|
|
- pos = [window
|
|
- transformPoint: NSMakePoint(ev->xbutton.x, ev->xbutton.y)];
|
|
-
|
|
+ // One scroll event per notch, sent on press; releases are not clicks.
|
|
+ if (ev->xbutton.button >= Button4 && ev->xbutton.button <= 7) break;
|
|
+ pos = [self locationInWindow: window x: ev->xbutton.x y: ev->xbutton.y
|
|
+ rootX: ev->xbutton.x_root rootY: ev->xbutton.y_root];
|
|
+
|
|
+ if (ev->xbutton.button == Button2 || ev->xbutton.button >= 8) {
|
|
+ unsigned b = ev->xbutton.button == Button2 ? 2 : ev->xbutton.button - 5;
|
|
+ if (b < 32) _otherButtonsDown &= ~(1u << b);
|
|
+ }
|
|
CGFloat deltaY = 0.0;
|
|
|
|
switch (ev->xbutton.button) {
|
|
@@ -1130,14 +1799,6 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
case Button3:
|
|
type = NSRightMouseUp;
|
|
break;
|
|
- case Button4:
|
|
- type = NSScrollWheel;
|
|
- deltaY = 1.0;
|
|
- break;
|
|
- case Button5:
|
|
- type = NSScrollWheel;
|
|
- deltaY = -1.0;
|
|
- break;
|
|
default:
|
|
type = NSOtherMouseUp;
|
|
}
|
|
@@ -1151,7 +1812,9 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
clickCount: clickCount
|
|
deltaX: 0.0
|
|
deltaY: deltaY];
|
|
- [event _setButtonNumber: ev->xbutton.button];
|
|
+ [event _setButtonNumber:
|
|
+ ev->xbutton.button == Button1 ? 0 : ev->xbutton.button == Button3 ? 1 :
|
|
+ ev->xbutton.button == Button2 ? 2 : ev->xbutton.button - 5];
|
|
[self postEvent: event atStart: NO];
|
|
break;
|
|
|
|
@@ -1164,45 +1827,82 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
pos = [window
|
|
transformPoint: NSMakePoint(ev->xmotion.x, ev->xmotion.y)];
|
|
|
|
- CGFloat deltaX = pos.x - lastMotionPos.x;
|
|
- CGFloat deltaY = pos.y - lastMotionPos.y;
|
|
-
|
|
- // NSLog(@"cursorGrabbed=%d, deltaX=%f, deltaY=%f\n",
|
|
- // _cursorGrabbed, deltaX, deltaY);
|
|
- if (_cursorGrabbed) {
|
|
- if (pos.x != lastMotionPos.x || pos.y != lastMotionPos.y) {
|
|
- CGPoint globalPos = [window transformPoint: lastMotionPos];
|
|
- // NSLog(@"last known pos in window: x=%f, y=%f",
|
|
- // globalPos.x, globalPos.y);
|
|
- CGRect frame = [window transformFrame: [window frame]];
|
|
-
|
|
- globalPos.x += frame.origin.x;
|
|
- globalPos.y += frame.origin.y;
|
|
-
|
|
- [self warpMouse: globalPos];
|
|
-
|
|
- pos.x = lastMotionPos.x;
|
|
- pos.y = lastMotionPos.y;
|
|
- } else {
|
|
- // This is an event generated by [self warpMouse:] above
|
|
+ double deltaX, deltaY;
|
|
+ CGPoint rclolEventPos = pos, rclolLastPos = lastMotionPos;
|
|
+ const char *rclolDecision = "absolute";
|
|
+
|
|
+ if (_relativeMouse.active) {
|
|
+ /* The cursor is disassociated (CGAssociateMouseAndMouseCursorPosition(false)): physical motion
|
|
+ * only produces deltas and the event reports the frozen virtual cursor. Where warps are honoured
|
|
+ * the pointer is put back under it, so the visible cursor stays frozen as on macOS. */
|
|
+ BOOL warpsHonoured = !_isXwayland || _cursorHidden;
|
|
+ rclolLastPos = NSMakePoint(_relativeMouse.physicalX, _relativeMouse.physicalY);
|
|
+ X11RelativeMouseMotion relative = X11RelativeMouseHandleMotion(
|
|
+ &_relativeMouse, ev->xmotion.x_root, ev->xmotion.y_root, warpsHonoured);
|
|
+ rclolEventPos = NSMakePoint(_relativeMouse.virtualX, _relativeMouse.virtualY);
|
|
+ if (relative.rewarp) {
|
|
+ if (rclolTraceOn())
|
|
+ rclolTraceRated(&rclolWarpRewarpChannel, "target=%d,%d from=%d,%d honoured=%d",
|
|
+ relative.warpX, relative.warpY, ev->xmotion.x_root, ev->xmotion.y_root,
|
|
+ warpsHonoured);
|
|
+ unsigned queued = [self warpPointerToRootX: relative.warpX y: relative.warpY];
|
|
+ X11RelativeMouseExpectEcho(&_relativeMouse, relative.warpX, relative.warpY, queued);
|
|
+ }
|
|
+ if (!relative.post) {
|
|
+ if (rclolTraceOn())
|
|
+ rclolTraceMotion(ev, YES, rclolEventPos, rclolLastPos, relative.deltaX, relative.deltaY,
|
|
+ relative.echo ? "echo" : "relative-still", -1);
|
|
break;
|
|
}
|
|
+ deltaX = relative.deltaX;
|
|
+ deltaY = relative.deltaY;
|
|
+ pos = [self locationInWindow: window x: ev->xmotion.x y: ev->xmotion.y
|
|
+ rootX: ev->xmotion.x_root rootY: ev->xmotion.y_root];
|
|
+ rclolDecision = relative.rewarp ? "relative+rewarp" : "relative";
|
|
} else {
|
|
- [window setLastKnownCursorPosition: pos];
|
|
+ X11AbsoluteMouseDelta(lastMotionPos.x, lastMotionPos.y, pos.x, pos.y, &deltaX, &deltaY);
|
|
}
|
|
+ [window setLastKnownCursorPosition: pos];
|
|
|
|
type = NSMouseMoved;
|
|
|
|
+ [self pointerMoved];
|
|
if (ev->xmotion.state & Button1Mask) {
|
|
type = NSLeftMouseDragged;
|
|
- } else if (ev->xmotion.state & Button2Mask) {
|
|
+ } else if (ev->xmotion.state & Button3Mask) {
|
|
type = NSRightMouseDragged;
|
|
+ } else if ((ev->xmotion.state & Button2Mask) || _otherButtonsDown) {
|
|
+ type = NSOtherMouseDragged;
|
|
}
|
|
|
|
- if (type == NSMouseMoved && ![delegate acceptsMouseMovedEvents])
|
|
- break;
|
|
+ if (rclolTraceOn())
|
|
+ rclolTraceMotion(ev, _relativeMouse.active, rclolEventPos, rclolLastPos, deltaX, deltaY, rclolDecision,
|
|
+ type != NSMouseMoved || [delegate acceptsMouseMovedEvents] ||
|
|
+ getenv("RCLOL_FORWARD_MOUSE_MOVED") != NULL);
|
|
+
|
|
+ if (inputTrace) {
|
|
+ static NSTimeInterval lastMotionTrace;
|
|
+ NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
|
|
+ if (now - lastMotionTrace >= 1.0) {
|
|
+ lastMotionTrace = now;
|
|
+ NSLog(@"LOLINPUT motion window=%lu mapped=%@ pos=%.0f,%.0f delta=%.0f,%.0f state=%u grabbed=%d forward=%d",
|
|
+ (unsigned long)ev->xany.window, [delegate class],
|
|
+ pos.x, pos.y, deltaX, deltaY, ev->xmotion.state,
|
|
+ _relativeMouse.active, type != NSMouseMoved || [delegate acceptsMouseMovedEvents] ||
|
|
+ getenv("RCLOL_FORWARD_MOUSE_MOVED") != NULL);
|
|
+ }
|
|
+ }
|
|
|
|
- event = [NSEvent
|
|
+ if (type != NSMouseMoved || [delegate acceptsMouseMovedEvents] ||
|
|
+ getenv("RCLOL_FORWARD_MOUSE_MOVED")) {
|
|
+ /* Relative motion is all in the deltas: keep those of the queued events coalesced below. */
|
|
+ if (_relativeMouse.active)
|
|
+ for (NSEvent *queued in _eventQueue)
|
|
+ if ([queued type] == type) {
|
|
+ deltaX += [queued deltaX];
|
|
+ deltaY += [queued deltaY];
|
|
+ }
|
|
+ event = [NSEvent
|
|
mouseEventWithType: type
|
|
location: pos
|
|
modifierFlags:
|
|
@@ -1211,28 +1911,49 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
clickCount: 1
|
|
deltaX: deltaX
|
|
deltaY: deltaY];
|
|
+ if (type == NSOtherMouseDragged) {
|
|
+ unsigned buttons = _otherButtonsDown | ((ev->xmotion.state & Button2Mask) ? 4u : 0u);
|
|
+ [event _setButtonNumber: __builtin_ctz(buttons)];
|
|
+ }
|
|
[self postEvent: event atStart: NO];
|
|
- [self discardEventsMatchingMask: NSLeftMouseDraggedMask
|
|
+ [self discardEventsMatchingMask: NSEventMaskFromType(type)
|
|
beforeEvent: event];
|
|
+ }
|
|
|
|
+ /* Tracking areas need motion even when the window doesn't ask
|
|
+ * for general mouseMoved events (Chromium uses this mode). */
|
|
[delegate platformWindowSetCursorEvent: window];
|
|
break;
|
|
}
|
|
|
|
case EnterNotify:
|
|
+ [self updateMouseConfinementForWindow:window];
|
|
NSLog(@"EnterNotify");
|
|
- if (!_cursorGrabbed)
|
|
+ if (!_relativeMouse.active)
|
|
[window setLastKnownCursorPosition:
|
|
[window transformPoint: NSMakePoint(
|
|
ev->xcrossing.x,
|
|
ev->xcrossing.y)]];
|
|
+ if (!_relativeMouse.active && ev->xcrossing.detail != NotifyInferior)
|
|
+ [delegate platformWindowSetCursorEvent: window];
|
|
break;
|
|
|
|
case LeaveNotify:
|
|
NSLog(@"LeaveNotify");
|
|
+ if (!_relativeMouse.active && ev->xcrossing.detail != NotifyInferior) {
|
|
+ [window setLastKnownCursorPosition:
|
|
+ [window transformPoint: NSMakePoint(ev->xcrossing.x, ev->xcrossing.y)]];
|
|
+ [delegate platformWindowSetCursorEvent: window];
|
|
+ }
|
|
break;
|
|
|
|
case FocusIn:
|
|
+ if (rclolTraceOn())
|
|
+ rclolTraceRated(&rclolFocusChannel, "FocusIn window=%lu known=%d mode=%s detail=%s relative=%d/%d confined=%lu appActive=%d sheet=%d",
|
|
+ (unsigned long) ev->xany.window, window != nil, rclolFocusModeName(ev->xfocus.mode),
|
|
+ rclolFocusDetailName(ev->xfocus.detail), _relativeRequested, _relativeMouse.active,
|
|
+ (unsigned long) _confinedWindow,
|
|
+ [NSApp isActive], [delegate attachedSheet] != nil);
|
|
NSLog(@"FocusIn");
|
|
if ([delegate attachedSheet]) {
|
|
[[delegate attachedSheet] makeKeyAndOrderFront: delegate];
|
|
@@ -1243,28 +1964,55 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
checkForAppDeactivation: NO];
|
|
lastFocusedWindow = nil;
|
|
}
|
|
+ char modifierKeys[32];
|
|
+ XQueryKeymap(_display, modifierKeys);
|
|
+ [self reconcileModifierKeys:modifierKeys window:delegate];
|
|
[delegate platformWindowActivated: window displayIfNeeded: YES];
|
|
lastFocusedWindow = delegate;
|
|
if (window != nil)
|
|
XSetICFocus(window->_xic);
|
|
+ /* Relative mode released its grab on focus loss; take it again. */
|
|
+ if (_relativeRequested && !_relativeMouse.active)
|
|
+ [self beginRelativeMouseWithWindow: NULL pick: NULL];
|
|
+ [self updateMouseConfinementForWindow:window];
|
|
+ if (rclolTraceOn())
|
|
+ rclolTraceRated(&rclolFocusChannel, "FocusIn done window=%lu relative=%d/%d confined=%lu appActive=%d",
|
|
+ (unsigned long) ev->xany.window, _relativeRequested, _relativeMouse.active,
|
|
+ (unsigned long) _confinedWindow, [NSApp isActive]);
|
|
break;
|
|
|
|
case FocusOut:
|
|
+ if (rclolTraceOn())
|
|
+ rclolTraceRated(&rclolFocusChannel, "FocusOut window=%lu known=%d mode=%s detail=%s relative=%d/%d confined=%lu appActive=%d",
|
|
+ (unsigned long) ev->xany.window, window != nil, rclolFocusModeName(ev->xfocus.mode),
|
|
+ rclolFocusDetailName(ev->xfocus.detail), _relativeRequested, _relativeMouse.active,
|
|
+ (unsigned long) _confinedWindow,
|
|
+ [NSApp isActive]);
|
|
NSLog(@"FocusOut");
|
|
+ [self reconcileModifierKeys:NULL window:delegate];
|
|
+ _lastKeySym = 0;
|
|
[delegate platformWindowDeactivated: window
|
|
checkForAppDeactivation: NO];
|
|
lastFocusedWindow = nil;
|
|
- if (_cursorGrabbed)
|
|
- [self grabMouse: NO];
|
|
+ /* Give the pointer back to other clients; relative mode stays requested and resumes on FocusIn. */
|
|
+ [self suspendRelativeMouse];
|
|
+ if (_confinedWindow) {
|
|
+ XUngrabPointer(_display, CurrentTime);
|
|
+ _confinedWindow = None;
|
|
+ }
|
|
+ _otherButtonsDown = 0;
|
|
if (window != nil)
|
|
XUnsetICFocus(window->_xic);
|
|
break;
|
|
|
|
case KeymapNotify:
|
|
NSLog(@"KeymapNotify");
|
|
+ [self reconcileModifierKeys:ev->xkeymap.key_vector window:lastFocusedWindow];
|
|
break;
|
|
|
|
case Expose:;
|
|
+ if ([window handleRemoteFrameExpose: ev->xany.window])
|
|
+ break;
|
|
O2Rect rect = NSMakeRect(ev->xexpose.x, ev->xexpose.y,
|
|
ev->xexpose.width, ev->xexpose.height);
|
|
|
|
@@ -1301,11 +2049,23 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
break;
|
|
|
|
case UnmapNotify:
|
|
- NSLog(@"UnmapNotify");
|
|
- break;
|
|
-
|
|
case MapNotify:
|
|
- NSLog(@"MapNotify");
|
|
+ /* Map state belongs to the top-level native window. Remote frame and
|
|
+ * GL children must not change its Cocoa visibility or occlusion state. */
|
|
+ if(window && ev->xany.window == [window windowHandle] &&
|
|
+ [delegate respondsToSelector: @selector(_setVisible:)]) {
|
|
+ BOOL visible = ev->type == MapNotify;
|
|
+ /* The server released a grab whose window became unviewable. */
|
|
+ if (!visible && ev->xany.window == _relativeWindow)
|
|
+ [self suspendRelativeMouse];
|
|
+ [self updateMouseConfinementForWindow:window];
|
|
+ if([delegate isVisible] != visible) {
|
|
+ [delegate _setVisible: visible];
|
|
+ [[NSNotificationCenter defaultCenter]
|
|
+ postNotificationName: NSWindowDidChangeOcclusionStateNotification
|
|
+ object: delegate];
|
|
+ }
|
|
+ }
|
|
break;
|
|
|
|
case MapRequest:
|
|
@@ -1321,6 +2081,7 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
[delegate platformWindow: window
|
|
frameChanged: [window frame]
|
|
didSize: YES];
|
|
+ [self updateMouseConfinementForWindow:window];
|
|
break;
|
|
|
|
case ConfigureRequest:
|
|
@@ -1344,6 +2105,8 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
break;
|
|
|
|
case PropertyNotify:
|
|
+ if (ev->xproperty.atom == XInternAtom(_display, "_NET_WM_STATE", False))
|
|
+ [self updateMouseConfinementForWindow:window];
|
|
if ([window respondsToSelector: @selector(propertyNotify:)]) {
|
|
[window propertyNotify: &ev->xproperty];
|
|
}
|
|
@@ -1399,7 +2162,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 +2198,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);
|
|
+ if (rclolTraceOn())
|
|
+ rclolTraceRated(&rclolErrorChannel, "request=%d:%d error=%d resource=%lu serial=%lu",
|
|
+ errorEvent->request_code, errorEvent->minor_code, errorEvent->error_code,
|
|
+ (unsigned long) errorEvent->resourceid, (unsigned long) errorEvent->serial);
|
|
return 0;
|
|
}
|
|
|
|
@@ -1476,54 +2245,218 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
|
|
return frame;
|
|
}
|
|
|
|
+/* Moves the X pointer and waits for the server, so the warp's MotionNotify (if any) is already queued on return.
|
|
+ * Returns the number of queued events, the window in which that echo must arrive. */
|
|
+- (unsigned) warpPointerToRootX: (int) x y: (int) y {
|
|
+ XWarpPointer(_display, None, DefaultRootWindow(_display), 0, 0, 0, 0, x, y);
|
|
+ XSync(_display, False);
|
|
+ rclolPointerStamp = 0;
|
|
+ return (unsigned) XEventsQueued(_display, QueuedAlready);
|
|
+}
|
|
+
|
|
+/* Window-local Cocoa location of a pointer event at (x, y) in window, root (rootX, rootY). While relative this is
|
|
+ * the virtual cursor: macOS reports the frozen cursor position, not where the physical pointer went. */
|
|
+- (NSPoint) locationInWindow: (X11Window *) window x: (int) x y: (int) y rootX: (int) rootX rootY: (int) rootY {
|
|
+ if (_relativeMouse.active)
|
|
+ return [window transformPoint: NSMakePoint(_relativeMouse.virtualX - (rootX - x),
|
|
+ _relativeMouse.virtualY - (rootY - y))];
|
|
+ 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",
|
|
+ position.x, position.y, _relativeRequested, _relativeMouse.active, _relativeMouse.virtualX,
|
|
+ _relativeMouse.virtualY, _relativeMouse.physicalX, _relativeMouse.physicalY,
|
|
+ !_isXwayland || _cursorHidden, (unsigned long) _confinedWindow, rclolRootX, rclolRootY,
|
|
+ [NSApp isActive]);
|
|
+ int x = X11RelativeMouseWarpCoordinate(position.x), y = X11RelativeMouseWarpCoordinate(position.y);
|
|
+ if (_relativeMouse.active) {
|
|
+ /* CGWarpMouseCursorPosition moves the disassociated cursor without producing a delta: the virtual
|
|
+ * cursor follows and the warp's echo is dropped. Not honoured on Xwayland with a visible cursor. */
|
|
+ X11RelativeMouseWarp(&_relativeMouse, position.x, position.y, [self warpPointerToRootX: x y: y]);
|
|
+ return;
|
|
+ }
|
|
NSLog(@"Warp to: x=%f, y=%f\n", position.x, position.y);
|
|
- XWarpPointer(_display, None, DefaultRootWindow(_display), 0, 0, 0, 0,
|
|
- 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 {
|
|
+ if (getenv("RCLOL_INPUT_TRACE")) fprintf(stderr,"CONFINEMENT check window=%p delegate=%p focused=%p relative=%d\n",window,[window delegate],lastFocusedWindow,_relativeRequested);
|
|
+ if (!window || _relativeRequested) {
|
|
+ if (rclolTraceOn())
|
|
+ rclolTraceRated(&rclolConfineChannel, "skip: %s confined=%lu", !window ? "window=nil" : "relative mode requested",
|
|
+ (unsigned long) _confinedWindow);
|
|
+ return;
|
|
+ }
|
|
+ Window win = [window windowHandle];
|
|
+ Window focus; int revert;
|
|
+ XGetInputFocus(_display, &focus, &revert);
|
|
+ Window ancestor = focus;
|
|
+ while (ancestor != win && ancestor != None && ancestor != PointerRoot && ancestor != DefaultRootWindow(_display)) {
|
|
+ Window root, parent, *children=NULL; unsigned nchildren;
|
|
+ if (!XQueryTree(_display, ancestor, &root, &parent, &children, &nchildren)) break;
|
|
+ if (children) XFree(children);
|
|
+ ancestor=parent;
|
|
+ }
|
|
+ if (getenv("RCLOL_INPUT_TRACE")) fprintf(stderr,"CONFINEMENT focus=%lu target=%lu ancestor=%lu\n",focus,win,ancestor);
|
|
+ if (ancestor != win) {
|
|
+ if (rclolTraceOn())
|
|
+ rclolTraceRated(&rclolConfineChannel, "skip: not focused window=%lu focus=%lu ancestor=%lu confined=%lu",
|
|
+ (unsigned long) win, (unsigned long) focus, (unsigned long) ancestor,
|
|
+ (unsigned long) _confinedWindow);
|
|
+ return;
|
|
+ }
|
|
+ XWindowAttributes attrs;
|
|
+ if (!XGetWindowAttributes(_display, win, &attrs)) {
|
|
+ if (rclolTraceOn())
|
|
+ rclolTraceRated(&rclolConfineChannel, "skip: XGetWindowAttributes failed window=%lu", (unsigned long) win);
|
|
+ return;
|
|
+ }
|
|
+ BOOL fullscreen = NO;
|
|
+ Atom actual; int format; unsigned long count, remaining; unsigned char *data = NULL;
|
|
+ Atom state = XInternAtom(_display, "_NET_WM_STATE", False);
|
|
+ Atom fs = XInternAtom(_display, "_NET_WM_STATE_FULLSCREEN", False);
|
|
+ if (XGetWindowProperty(_display, win, state, 0, 64, False, XA_ATOM,
|
|
+ &actual, &format, &count, &remaining, &data) == Success && format == 32) {
|
|
+ for (unsigned long i=0; i<count; ++i) if (((Atom *)data)[i] == fs) fullscreen = YES;
|
|
+ }
|
|
+ if (data) XFree(data);
|
|
+ BOOL rclolEwmhFullscreen = fullscreen;
|
|
+ /* League's borderless fullscreen does not set the EWMH fullscreen bit.
|
|
+ * Recognize a borderless window covering one monitor, including monitor 2. */
|
|
+ if (!fullscreen && !([[window delegate] styleMask] & NSTitledWindowMask)) {
|
|
+ Window child; int x, y;
|
|
+ XTranslateCoordinates(_display, win, DefaultRootWindow(_display), 0, 0, &x, &y, &child);
|
|
+ XRRScreenResources *resources = XRRGetScreenResourcesCurrent(_display, DefaultRootWindow(_display));
|
|
+ if (resources) {
|
|
+ for (int i=0; i<resources->ncrtc; ++i) {
|
|
+ XRRCrtcInfo *crtc = XRRGetCrtcInfo(_display, resources, resources->crtcs[i]);
|
|
+ if (crtc) {
|
|
+ if (crtc->mode && abs(x-crtc->x)<=2 && abs(y-crtc->y)<=2 &&
|
|
+ abs(attrs.width-(int)crtc->width)<=2 && abs(attrs.height-(int)crtc->height)<=2) fullscreen=YES;
|
|
+ XRRFreeCrtcInfo(crtc);
|
|
+ }
|
|
+ }
|
|
+ XRRFreeScreenResources(resources);
|
|
+ }
|
|
+ }
|
|
+ if (!fullscreen || attrs.map_state != IsViewable) {
|
|
+ if (rclolTraceOn())
|
|
+ rclolTraceRated(&rclolConfineChannel, "no confinement window=%lu fullscreen=%d ewmh=%d titled=%d size=%dx%d map_state=%d %s",
|
|
+ (unsigned long) win, fullscreen, rclolEwmhFullscreen,
|
|
+ ([[window delegate] styleMask] & NSTitledWindowMask) != 0, attrs.width, attrs.height,
|
|
+ attrs.map_state, _confinedWindow == win ? "releasing confinement" : "nothing to release");
|
|
+ if (_confinedWindow == win) { XUngrabPointer(_display, CurrentTime); _confinedWindow=None; }
|
|
+ return;
|
|
+ }
|
|
+ if (_confinedWindow == win) {
|
|
+ if (rclolTraceOn())
|
|
+ rclolTraceRated(&rclolConfineChannel, "already confined window=%lu", (unsigned long) win);
|
|
+ return;
|
|
+ }
|
|
+ unsigned mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask;
|
|
+ int status=XGrabPointer(_display, win, True, 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 {
|
|
+ NSWindow *nswin = [NSApp keyWindow];
|
|
+ const char *pick = "keyWindow";
|
|
+ if (!nswin) {
|
|
+ nswin = [NSApp mainWindow];
|
|
+ pick = nswin ? "mainWindow" : "nil";
|
|
+ }
|
|
+ if (outWindow) *outWindow = nswin;
|
|
+ if (outPick) *outPick = pick;
|
|
+ X11Window *xwin = (X11Window *) [nswin platformWindow];
|
|
+ Window win = xwin ? [xwin windowHandle] : None;
|
|
+ if (win == None) {
|
|
+ NSLog(@"Relative mouse mode: no window to grab the pointer for\n");
|
|
+ return -1;
|
|
+ }
|
|
+ /* FocusChangeMask is not valid in a pointer grab. This grab replaces any confinement grab. */
|
|
+ const unsigned int mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask;
|
|
+ int result = XGrabPointer(_display, win, False, mask, GrabModeAsync,
|
|
+ GrabModeAsync, win, None, CurrentTime);
|
|
+ if (result != GrabSuccess) {
|
|
+ NSLog(@"XGrabPointer() failed with error %d for window %lu\n", result, win);
|
|
+ return result;
|
|
+ }
|
|
+ NSLog(@"XGrabPointer() succeeded for window %lu\n", win);
|
|
+ _confinedWindow = None;
|
|
+ Window root, child;
|
|
+ int rootX = rclolRootX, rootY = rclolRootY, windowX, windowY;
|
|
+ unsigned int state;
|
|
+ if (!XQueryPointer(_display, win, &root, &child, &rootX, &rootY, &windowX, &windowY, &state)) {
|
|
+ rootX = rclolRootX;
|
|
+ rootY = rclolRootY;
|
|
+ }
|
|
+ X11RelativeMouseBegin(&_relativeMouse, rootX, rootY);
|
|
+ _relativeWindow = win;
|
|
+ rclolPointerStamp = 0;
|
|
+ return result;
|
|
+}
|
|
+
|
|
+/* Releases the relative-mode grab without cancelling the request (focus loss, window gone). */
|
|
+- (void) suspendRelativeMouse {
|
|
+ if (!_relativeMouse.active) return;
|
|
+ XUngrabPointer(_display, CurrentTime);
|
|
+ X11RelativeMouseEnd(&_relativeMouse);
|
|
+ _relativeWindow = None;
|
|
+ rclolPointerStamp = 0;
|
|
+}
|
|
+
|
|
+/* CGAssociateMouseAndMouseCursorPosition(!doGrab). */
|
|
- (void) grabMouse: (BOOL) doGrab {
|
|
+ BOOL rclolRelativeBefore = _relativeMouse.active;
|
|
+ Window rclolConfinedBefore = _confinedWindow;
|
|
+ NSWindow *rclolWindow = nil;
|
|
+ const char *rclolPick = "keyWindow";
|
|
+ int rclolResult = -1;
|
|
+ if (rclolTraceOn()) rclolTraceGrabChanged();
|
|
if (doGrab) {
|
|
- NSWindow *nswin = [NSApp keyWindow];
|
|
- if (!nswin)
|
|
- nswin = [NSApp mainWindow];
|
|
-
|
|
- X11Window *xwin = (X11Window *) [nswin platformWindow];
|
|
- Window win = [xwin windowHandle];
|
|
- // Window win = DefaultRootWindow(_display);
|
|
- const unsigned int mask = ButtonPressMask | ButtonReleaseMask |
|
|
- PointerMotionMask | FocusChangeMask;
|
|
- int result = XGrabPointer(_display, win, False, mask, GrabModeAsync,
|
|
- GrabModeAsync, None, None, CurrentTime);
|
|
- if (result == GrabSuccess) {
|
|
- _cursorGrabbed = YES;
|
|
- NSLog(@"XGrabPointer() succeeded for window %lu\n", win);
|
|
-
|
|
- NSRect frame = [xwin transformFrame: nswin.frame];
|
|
- // NSLog(@"Window's frame is at %f,%f, size %fx%f\n",
|
|
- // frame.origin.x, frame.origin.y, frame.size.width,
|
|
- // frame.size.height);
|
|
- CGPoint ptGlobal =
|
|
- NSMakePoint(frame.size.width / 2.0 + frame.origin.x,
|
|
- frame.size.height / 2.0 + frame.origin.y);
|
|
- CGPoint ptLocal = NSMakePoint(frame.size.width / 2.0,
|
|
- frame.size.height / 2.0);
|
|
-
|
|
- // NSLog(@"setting last known pos in window to x=%f, y=%f\n",
|
|
- // ptLocal.x, ptLocal.y);
|
|
-
|
|
- [xwin setLastKnownCursorPosition: [xwin transformPoint: ptLocal]];
|
|
- [self warpMouse: ptGlobal];
|
|
- } else {
|
|
- NSLog(@"XGrabPointer() failed with error %d for window %lu\n",
|
|
- result, win);
|
|
- }
|
|
+ _relativeRequested = YES;
|
|
+ rclolResult = [self beginRelativeMouseWithWindow: &rclolWindow pick: &rclolPick];
|
|
} else {
|
|
XUngrabPointer(_display, CurrentTime);
|
|
- _cursorGrabbed = NO;
|
|
+ X11RelativeMouseEnd(&_relativeMouse);
|
|
+ _relativeRequested = NO;
|
|
+ _relativeWindow = None;
|
|
+ _confinedWindow = None;
|
|
+ rclolPointerStamp = 0;
|
|
+ NSWindow *nswin = [NSApp keyWindow];
|
|
+ if (nswin) [self updateMouseConfinementForWindow:(X11Window *)[nswin platformWindow]];
|
|
+ rclolWindow = nswin;
|
|
+ rclolPick = nswin ? "keyWindow" : "nil";
|
|
}
|
|
XSync(_display, False);
|
|
+ if (rclolTraceOn())
|
|
+ rclolTraceLine("grabMouse gen=%u: doGrab=%d window=%s nswindow=%p class=%s xwindow=%lu XGrabPointer=%d(%s) "
|
|
+ "relative %d->%d requested=%d confined %lu->%lu appActive=%d virtual=%.1f,%.1f xwayland=%d "
|
|
+ "hidden=%d cachedRoot=%d,%d",
|
|
+ rclolGrabGeneration, doGrab, rclolPick, rclolWindow,
|
|
+ rclolWindow ? object_getClassName(rclolWindow) : "-",
|
|
+ (unsigned long) (rclolWindow ? [(X11Window *) [rclolWindow platformWindow] windowHandle] : None),
|
|
+ rclolResult, doGrab ? rclolGrabResultName(rclolResult) : "ungrab", rclolRelativeBefore,
|
|
+ _relativeMouse.active, _relativeRequested, (unsigned long) rclolConfinedBefore,
|
|
+ (unsigned long) _confinedWindow, [NSApp isActive], _relativeMouse.virtualX,
|
|
+ _relativeMouse.virtualY, _isXwayland, _cursorHidden, rclolRootX, rclolRootY);
|
|
}
|
|
|
|
@end
|
|
diff --git a/AppKit/X11.backend/X11Pasteboard.h b/AppKit/X11.backend/X11Pasteboard.h
|
|
index 411903cc..4b196afd 100644
|
|
--- a/AppKit/X11.backend/X11Pasteboard.h
|
|
+++ b/AppKit/X11.backend/X11Pasteboard.h
|
|
@@ -26,6 +26,7 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
Display *_display;
|
|
Window _window;
|
|
Atom _selectionName;
|
|
+ Window _observedSelectionOwner;
|
|
|
|
NSMutableDictionary<NSPasteboardType, NSData *> *_typeToData;
|
|
NSMutableDictionary<NSPasteboardType, id<NSPasteboardTypeOwner>>
|
|
diff --git a/AppKit/X11.backend/X11Pasteboard.m b/AppKit/X11.backend/X11Pasteboard.m
|
|
index 0e91a5d1..1e4cec1b 100644
|
|
--- a/AppKit/X11.backend/X11Pasteboard.m
|
|
+++ b/AppKit/X11.backend/X11Pasteboard.m
|
|
@@ -18,6 +18,8 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#import "X11Pasteboard.h"
|
|
+#include <poll.h>
|
|
+#include <time.h>
|
|
|
|
@implementation X11Pasteboard
|
|
|
|
@@ -53,9 +55,7 @@ static NSMutableDictionary<NSPasteboardName, X11Pasteboard *> *nameToPboard;
|
|
int screen = DefaultScreen(_display);
|
|
_window = XCreateSimpleWindow(_display, RootWindow(_display, screen), -10,
|
|
-10, 1, 1, 0, 0, 0);
|
|
- XSelectInput(_display, _window,
|
|
- SelectionClear | SelectionRequest | SelectionNotify |
|
|
- PropertyNotify);
|
|
+ XSelectInput(_display, _window, PropertyChangeMask);
|
|
|
|
[x11Display setWindow: self forID: _window];
|
|
|
|
@@ -63,6 +63,7 @@ static NSMutableDictionary<NSPasteboardName, X11Pasteboard *> *nameToPboard;
|
|
}
|
|
|
|
- (void) ensureSelectionOwner {
|
|
+ [self syncSelectionOwner];
|
|
if (_typeToData != nil) {
|
|
// Already an owner.
|
|
return;
|
|
@@ -93,7 +94,8 @@ static NSMutableDictionary<NSPasteboardName, X11Pasteboard *> *nameToPboard;
|
|
- (void) giveUpSelectionOwner {
|
|
_changeCount++;
|
|
|
|
- [self clearContents];
|
|
+ for (id owner in [_typeToOwner allValues])
|
|
+ [owner pasteboardChangedOwner: self];
|
|
|
|
[_typeToOwner release];
|
|
_typeToOwner = nil;
|
|
@@ -115,12 +117,47 @@ static NSMutableDictionary<NSPasteboardName, X11Pasteboard *> *nameToPboard;
|
|
[super dealloc];
|
|
}
|
|
|
|
+- (void) syncSelectionOwner {
|
|
+ Window owner = XGetSelectionOwner(_display, _selectionName);
|
|
+ if (_typeToData != nil && owner != _window)
|
|
+ [self giveUpSelectionOwner];
|
|
+ if (owner != _observedSelectionOwner) {
|
|
+ _observedSelectionOwner = owner;
|
|
+ _changeCount++;
|
|
+ }
|
|
+}
|
|
+
|
|
- (NSInteger) changeCount {
|
|
+ [self syncSelectionOwner];
|
|
return _changeCount;
|
|
}
|
|
|
|
+- (BOOL) requestTarget: (Atom) target {
|
|
+ XDeleteProperty(_display, _window, _receivingProperty);
|
|
+ XConvertSelection(_display, _selectionName, target, _receivingProperty,
|
|
+ _window, CurrentTime);
|
|
+ XFlush(_display);
|
|
+ struct timespec start, now;
|
|
+ clock_gettime(CLOCK_MONOTONIC, &start);
|
|
+ for (;;) {
|
|
+ XEvent event;
|
|
+ if (XCheckTypedWindowEvent(_display, _window, SelectionNotify, &event)) {
|
|
+ if (event.xselection.selection == _selectionName &&
|
|
+ event.xselection.target == target)
|
|
+ return event.xselection.property == _receivingProperty;
|
|
+ }
|
|
+ clock_gettime(CLOCK_MONOTONIC, &now);
|
|
+ if ((now.tv_sec-start.tv_sec)*1000 +
|
|
+ (now.tv_nsec-start.tv_nsec)/1000000 >= 2000)
|
|
+ return NO;
|
|
+ struct pollfd fd = { ConnectionNumber(_display), POLLIN, 0 };
|
|
+ poll(&fd, 1, 10);
|
|
+ }
|
|
+}
|
|
+
|
|
- (void) selectionClear: (XSelectionClearEvent *) event {
|
|
- [self giveUpSelectionOwner];
|
|
+ if (XGetSelectionOwner(_display, _selectionName) != _window)
|
|
+ [self giveUpSelectionOwner];
|
|
}
|
|
|
|
+ (NSArray<NSString *> *) targetsForType: (NSPasteboardType) type {
|
|
@@ -177,7 +214,7 @@ static NSMutableDictionary<NSPasteboardName, X11Pasteboard *> *nameToPboard;
|
|
}
|
|
|
|
- (NSData *) dataForType: (NSPasteboardType) type {
|
|
-
|
|
+ [self syncSelectionOwner];
|
|
if (_typeToOwner != nil) {
|
|
// We're the owner; just fetch the data directly.
|
|
id<NSPasteboardTypeOwner> owner = _typeToOwner[type];
|
|
@@ -185,25 +222,26 @@ static NSMutableDictionary<NSPasteboardName, X11Pasteboard *> *nameToPboard;
|
|
return _typeToData[type];
|
|
}
|
|
|
|
- NSString *target = [X11Pasteboard targetsForType: type][0];
|
|
- Atom targetAtom = XInternAtom(_display, [target UTF8String], False);
|
|
- XConvertSelection(_display, _selectionName, targetAtom, _receivingProperty,
|
|
- _window, CurrentTime); // FIXME: don't use CurrentTime
|
|
-
|
|
- _selectionNotifyResult = WAITING;
|
|
- while (_selectionNotifyResult == WAITING) {
|
|
- [[NSDisplay currentDisplay]
|
|
- nextEventMatchingMask: NSAnyEventMask
|
|
- untilDate: [NSDate distantFuture]
|
|
- inMode: NSDefaultRunLoopMode
|
|
- dequeue: NO];
|
|
+ for (NSString *target in [X11Pasteboard targetsForType: type]) {
|
|
+ Atom atom = XInternAtom(_display, [target UTF8String], False);
|
|
+ if (![self requestTarget: atom])
|
|
+ continue;
|
|
+ NSData *data = [self readDataFromReceivingProperty];
|
|
+ if (data != nil) {
|
|
+ if ([target isEqual: @"STRING"] && [type isEqual: NSStringPboardType]) {
|
|
+ NSString *text = [[[NSString alloc] initWithData: data
|
|
+ encoding: NSISOLatin1StringEncoding] autorelease];
|
|
+ return [text dataUsingEncoding: NSUTF8StringEncoding];
|
|
+ }
|
|
+ return data;
|
|
+ }
|
|
}
|
|
-
|
|
- return [self readDataFromReceivingProperty];
|
|
+ return nil;
|
|
}
|
|
|
|
- (NSString *) stringForType: (NSPasteboardType) type {
|
|
NSData *data = [self dataForType: type];
|
|
+ if (data == nil) return nil;
|
|
|
|
NSStringEncoding encoding = NSUnicodeStringEncoding;
|
|
if ([type isEqual: NSStringPboardType]) {
|
|
@@ -252,8 +290,37 @@ static NSMutableDictionary<NSPasteboardName, X11Pasteboard *> *nameToPboard;
|
|
}
|
|
|
|
- (NSArray<NSPasteboardType> *) types {
|
|
- return [[_typeToData allKeys]
|
|
- arrayByAddingObjectsFromArray: [_typeToOwner allKeys]];
|
|
+ [self syncSelectionOwner];
|
|
+ if (_typeToData != nil)
|
|
+ return [[_typeToData allKeys]
|
|
+ arrayByAddingObjectsFromArray: [_typeToOwner allKeys]];
|
|
+ if (![self requestTarget: XInternAtom(_display, "TARGETS", False)])
|
|
+ return @[];
|
|
+ Atom actualType;
|
|
+ int format;
|
|
+ unsigned long count, remaining;
|
|
+ unsigned char *bytes = NULL;
|
|
+ XGetWindowProperty(_display, _window, _receivingProperty, 0, 4096, True,
|
|
+ AnyPropertyType, &actualType, &format, &count, &remaining,
|
|
+ &bytes);
|
|
+ NSMutableArray *types = [NSMutableArray array];
|
|
+ if (format == 32 && actualType != None) {
|
|
+ Atom *atoms = (Atom *) bytes;
|
|
+ for (unsigned long i = 0; i < count; i++) {
|
|
+ char *name = XGetAtomName(_display, atoms[i]);
|
|
+ if (name != NULL) {
|
|
+ NSString *target = [NSString stringWithUTF8String: name];
|
|
+ if (![target isEqual: @"TARGETS"] && ![target isEqual: @"TIMESTAMP"]) {
|
|
+ NSString *type = [X11Pasteboard typeForTarget: target];
|
|
+ if (![types containsObject: type])
|
|
+ [types addObject: type];
|
|
+ }
|
|
+ XFree(name);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ if (bytes != NULL) XFree(bytes);
|
|
+ return types;
|
|
}
|
|
|
|
- (void) selectionNotify: (XSelectionEvent *) event {
|
|
diff --git a/AppKit/X11.backend/X11Window.h b/AppKit/X11.backend/X11Window.h
|
|
index bfe3839a..dbc974a0 100644
|
|
--- a/AppKit/X11.backend/X11Window.h
|
|
+++ b/AppKit/X11.backend/X11Window.h
|
|
@@ -26,7 +26,7 @@
|
|
#import <X11/Xresource.h>
|
|
#import <X11/Xlocale.h>
|
|
|
|
-@class X11Display, CAWindowOpenGLContext, NSWindow, O2Context;
|
|
+@class X11Display, CAWindowOpenGLContext, NSWindow, O2Context, NSTimer;
|
|
|
|
@interface X11Window : CGWindow {
|
|
int _level; // TODO: care about this value
|
|
@@ -49,6 +49,26 @@
|
|
BOOL _isModal;
|
|
BOOL _isOpaque;
|
|
BOOL _hasShadow;
|
|
+ NSTimer *_remoteFrameTimer;
|
|
+ Window _remoteFrameWindow;
|
|
+ GC _remoteFrameGC;
|
|
+ unsigned int _remoteFrameSequence, _remoteFrameWidth, _remoteFrameHeight;
|
|
+ BOOL _remoteFrameValid;
|
|
+ char _remoteFramePath[128];
|
|
+ XWindowAttributes _remoteFrameTargets[17];
|
|
+ unsigned _remoteFrameTargetCount;
|
|
+ double _remoteFrameTargetRefresh;
|
|
+ void *_remoteFrameMapping;
|
|
+ size_t _remoteFrameMappingSize;
|
|
+ char _remoteFrameMappingPath[128];
|
|
+ double _remoteFrameSelectionRefresh;
|
|
+ void *_remoteFrameShm;
|
|
+ void (*_remoteFrameShmDestroy)(void *);
|
|
+ int (*_remoteFrameShmPut)(void *, Drawable, GC, const void *, size_t);
|
|
+ int (*_remoteFrameShmStart)(void *, Drawable, GC, const char *);
|
|
+ BOOL _remoteFrameShmPumping;
|
|
+ BOOL _remoteFrameSoftwarePublisher;
|
|
+ BOOL _remoteFrameTimerSlow;
|
|
|
|
@public
|
|
XIC _xic;
|
|
@@ -60,9 +80,9 @@
|
|
- (Visual *) visual;
|
|
- (Drawable) drawable;
|
|
- (NSPoint) transformPoint: (NSPoint) pos;
|
|
-- (O2Rect) transformFrame: (O2Rect) frame;
|
|
|
|
- (Window) windowHandle;
|
|
+- (BOOL) handleRemoteFrameExpose: (Window) window;
|
|
|
|
- (void) frameChanged;
|
|
- (void) setLastKnownCursorPosition: (CGPoint) point;
|
|
diff --git a/AppKit/X11.backend/X11Window.m b/AppKit/X11.backend/X11Window.m
|
|
index f4925b66..6f653f6d 100644
|
|
--- a/AppKit/X11.backend/X11Window.m
|
|
+++ b/AppKit/X11.backend/X11Window.m
|
|
@@ -38,6 +38,34 @@
|
|
#import "X11Window.h"
|
|
#import <X11/Xatom.h>
|
|
#import <X11/Xutil.h>
|
|
+#import <X11/extensions/shape.h>
|
|
+#include <fcntl.h>
|
|
+#include <stdint.h>
|
|
+#include <stdlib.h>
|
|
+#include <unistd.h>
|
|
+#include <dirent.h>
|
|
+#include <string.h>
|
|
+#include <sys/stat.h>
|
|
+#include <sys/mman.h>
|
|
+#include <dlfcn.h>
|
|
+#include <sys/signal.h>
|
|
+#include <errno.h>
|
|
+
|
|
+#define DARLING_CA_FRAME_MAGIC 0x43414652u
|
|
+#define DARLING_CA_FRAME_BUFFERED_MAGIC 0x43414632u
|
|
+static size_t framePayloadOffset(uint32_t magic, uint32_t format) {
|
|
+ return magic == DARLING_CA_FRAME_BUFFERED_MAGIC && (format & 0x40000000u)
|
|
+ ? 32u * 1024u * 1024u : 0;
|
|
+}
|
|
+typedef struct {
|
|
+ uint32_t magic;
|
|
+ volatile uint32_t sequence;
|
|
+ uint32_t width;
|
|
+ uint32_t height;
|
|
+ uint32_t bytesPerRow;
|
|
+ uint32_t pixelFormat;
|
|
+ uint64_t dataSize;
|
|
+} DarlingCARemoteFrame;
|
|
|
|
@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,13 @@ static NSData *makeWindowIcon() {
|
|
|
|
XSetWindowAttributes xattr;
|
|
unsigned long xattr_mask;
|
|
- xattr.override_redirect =
|
|
- _styleMask == NSBorderlessWindowMask ? True : False;
|
|
+ // A borderless game is still a focusable application window. Leaving it
|
|
+ // override-redirect makes XWayland bypass compositor keyboard activation.
|
|
+ // Menus and nonactivating popups keep their unmanaged behavior.
|
|
+ xattr.override_redirect = _styleMask == NSBorderlessWindowMask &&
|
|
+ ([delegate isKindOfClass:[NSMenuWindow class]] ||
|
|
+ [delegate isKindOfClass:[NSPopUpWindow class]] ||
|
|
+ ![delegate canBecomeKeyWindow]) ? True : False;
|
|
xattr_mask = CWOverrideRedirect | CWColormap;
|
|
xattr.colormap = cmap;
|
|
|
|
@@ -206,6 +239,12 @@ static NSData *makeWindowIcon() {
|
|
(_visualInfo == NULL) ? CopyFromParent : _visualInfo->visual,
|
|
xattr_mask, &xattr);
|
|
|
|
+ /* Keep Cocoa's process identifier available to cross-process activation. */
|
|
+ unsigned long windowPID = (unsigned long)getpid();
|
|
+ XChangeProperty(_display, _window, XInternAtom(_display, "_NET_WM_PID", False),
|
|
+ XA_CARDINAL, 32, PropModeReplace,
|
|
+ (unsigned char *)&windowPID, 1);
|
|
+
|
|
[self syncDelegateProperties];
|
|
|
|
Atom atm = XInternAtom(_display, "WM_DELETE_WINDOW", False);
|
|
@@ -266,6 +305,11 @@ static NSData *makeWindowIcon() {
|
|
|
|
[self setWindowIcon];
|
|
|
|
+ /* Poll the host-visible remote CA frame on the AppKit/main runloop. */
|
|
+ _remoteFrameTimer = [[NSTimer scheduledTimerWithTimeInterval: 1.0 / 60.0
|
|
+ target: self selector: @selector(presentRemoteCAFrame:)
|
|
+ userInfo: nil repeats: YES] retain];
|
|
+
|
|
return self;
|
|
}
|
|
|
|
@@ -338,7 +382,6 @@ static NSData *makeWindowIcon() {
|
|
}
|
|
|
|
+ (void) removeDecorationForWindow: (Window) w onDisplay: (Display *) dpy {
|
|
- return;
|
|
struct {
|
|
unsigned long flags;
|
|
unsigned long functions;
|
|
@@ -358,6 +401,7 @@ static NSData *makeWindowIcon() {
|
|
if (!_mapped) {
|
|
XMapWindow(_display, _window);
|
|
_mapped = YES;
|
|
+ _remoteFrameTargetRefresh = 0;
|
|
}
|
|
}
|
|
|
|
@@ -377,15 +421,27 @@ static NSData *makeWindowIcon() {
|
|
- (void) syncDelegateProperties {
|
|
long mask = KeyPressMask | KeyReleaseMask | ExposureMask |
|
|
StructureNotifyMask | EnterWindowMask | LeaveWindowMask |
|
|
- ButtonPressMask | ButtonReleaseMask | ButtonMotionMask |
|
|
- VisibilityChangeMask | FocusChangeMask |
|
|
+ ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | PointerMotionMask |
|
|
+ VisibilityChangeMask | FocusChangeMask | PropertyChangeMask |
|
|
SubstructureRedirectMask;
|
|
|
|
- if ([_delegate acceptsMouseMovedEvents]) {
|
|
- mask |= PointerMotionMask;
|
|
- }
|
|
+ /* Motion also restores a cursor hidden while typing. App-level motion
|
|
+ * delivery is still filtered by acceptsMouseMovedEvents in X11Display. */
|
|
XSelectInput(_display, _window, mask);
|
|
|
|
+ /* Mouse-transparent windows must not steal crossing events from the
|
|
+ * view below them. Filtering dispatched events leaves X11 hit testing
|
|
+ * unchanged and can repeatedly hide/recreate tooltip windows. */
|
|
+ if ([_delegate respondsToSelector: @selector(ignoresMouseEvents)]) {
|
|
+ if ([_delegate ignoresMouseEvents])
|
|
+ XShapeCombineRectangles(_display, _window, ShapeInput,
|
|
+ 0, 0, NULL, 0, ShapeSet, Unsorted);
|
|
+ else
|
|
+ XShapeCombineMask(_display, _window, ShapeInput,
|
|
+ 0, 0, None, ShapeSet);
|
|
+ XFlush(_display);
|
|
+ }
|
|
+
|
|
// TODO: background color
|
|
}
|
|
|
|
@@ -393,6 +449,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.
|
|
+ [_remoteFrameTimer invalidate];
|
|
+ [_remoteFrameTimer release];
|
|
+ _remoteFrameTimer = nil;
|
|
+ if (_remoteFrameShm) {
|
|
+ _remoteFrameShmDestroy(_remoteFrameShm);
|
|
+ _remoteFrameShm = NULL;
|
|
+ _remoteFrameShmPumping = NO;
|
|
+ }
|
|
+ if (_remoteFrameMapping) {
|
|
+ munmap(_remoteFrameMapping, _remoteFrameMappingSize);
|
|
+ _remoteFrameMapping = NULL;
|
|
+ _remoteFrameMappingSize = 0;
|
|
+ }
|
|
+
|
|
+ if (_remoteFrameWindow) {
|
|
+ [(X11Display *) [NSDisplay currentDisplay] setWindow: nil forID: _remoteFrameWindow];
|
|
+ if (_remoteFrameGC) XFreeGC(_display, _remoteFrameGC);
|
|
+ XDestroyWindow(_display, _remoteFrameWindow);
|
|
+ _remoteFrameWindow = 0;
|
|
+ _remoteFrameGC = NULL;
|
|
+ _remoteFrameValid = NO;
|
|
+ }
|
|
[_context release];
|
|
_context = nil;
|
|
|
|
@@ -421,6 +499,388 @@ static NSData *makeWindowIcon() {
|
|
}
|
|
}
|
|
|
|
+- (void) setRemoteFrameTimerSlow: (BOOL) slow {
|
|
+ if (_remoteFrameTimerSlow == slow) return;
|
|
+ _remoteFrameTimerSlow = slow;
|
|
+ [_remoteFrameTimer invalidate];
|
|
+ [_remoteFrameTimer release];
|
|
+ _remoteFrameTimer = [[NSTimer scheduledTimerWithTimeInterval: slow ? 0.25 : 1.0 / 60.0
|
|
+ target: self selector: @selector(presentRemoteCAFrame:)
|
|
+ userInfo: nil repeats: YES] retain];
|
|
+}
|
|
+
|
|
+- (BOOL) presentRemoteCAFrame: (NSTimer *) timer {
|
|
+ static unsigned char *pixels;
|
|
+ static size_t pixelCapacity;
|
|
+ if (!_mapped) return NO;
|
|
+ double now = CFAbsoluteTimeGetCurrent();
|
|
+ /* X geometry queries are synchronous round trips. Cache stable targets
|
|
+ * across animation ticks, invalidating on native resize/mapping changes. */
|
|
+ if (!_remoteFrameTargetCount || now >= _remoteFrameTargetRefresh) {
|
|
+ XWindowAttributes attributes;
|
|
+ if (!XGetWindowAttributes(_display, _window, &attributes) ||
|
|
+ attributes.map_state != IsViewable)
|
|
+ return NO;
|
|
+ _remoteFrameTargetCount = 1;
|
|
+ _remoteFrameTargets[0] = attributes;
|
|
+ Window root, parent, *children = NULL;
|
|
+ unsigned childCount = 0;
|
|
+ if (XQueryTree(_display, _window, &root, &parent, &children, &childCount)) {
|
|
+ for (unsigned i = 0; i < childCount && _remoteFrameTargetCount < 17; ++i)
|
|
+ if (children[i] != _remoteFrameWindow &&
|
|
+ XGetWindowAttributes(_display, children[i],
|
|
+ &_remoteFrameTargets[_remoteFrameTargetCount]))
|
|
+ ++_remoteFrameTargetCount;
|
|
+ }
|
|
+ if (children) XFree(children);
|
|
+ _remoteFrameTargetRefresh = now + 1.0;
|
|
+ }
|
|
+ XWindowAttributes *frameTargets = _remoteFrameTargets;
|
|
+ unsigned frameTargetCount = _remoteFrameTargetCount;
|
|
+ DarlingCARemoteFrame before, after;
|
|
+ if (_remoteFrameValid && _remoteFrameMapping &&
|
|
+ (((DarlingCARemoteFrame *)_remoteFrameMapping)->sequence & 1)) {
|
|
+ XRaiseWindow(_display, _remoteFrameWindow);
|
|
+ XFlush(_display);
|
|
+ return YES;
|
|
+ }
|
|
+ /* darling-lol M58c diagnostic (opt-in via M58_PRES_TRACE): one character
|
|
+ * per event appended to /dev/shm/m58c-present-<pid> so the consumer's
|
|
+ * behavior can be observed from the host without guest logging noise.
|
|
+ * t=timer fired, s=no candidate slot, S=slot chosen, s=same sequence,
|
|
+ * b=blit performed, v=header validation failed. */
|
|
+ int (*_tputc)(int) = NULL;
|
|
+ (void) _tputc;
|
|
+ static FILE *tlog;
|
|
+ if (getenv("M58_PRES_TRACE")) {
|
|
+ if (!tlog) {
|
|
+ char tpath[128];
|
|
+ snprintf(tpath, sizeof(tpath),
|
|
+ "/dev/shm/m58c-present-%d", (int) getpid());
|
|
+ tlog = fopen(tpath, "a");
|
|
+ }
|
|
+ if (tlog) {
|
|
+ fputc('t', tlog);
|
|
+ fflush(tlog);
|
|
+ }
|
|
+ }
|
|
+ /* darling-lol M58c: the publisher side now writes one slot file per
|
|
+ * process (/dev/shm/darling-ca-frame-<uid>-<pid>) because concurrent
|
|
+ * publishers corrupt a shared slot's seqlock. Several processes publish
|
|
+ * same-size surfaces (measured: the browser's own surface is all black
|
|
+ * while the GPU's compositor output carries the real frame), so scan
|
|
+ * every publisher's slot and display the most recently updated one that
|
|
+ * actually contains pixels; an all-black frame is only used when nothing
|
|
+ * else is available. */
|
|
+ char path[128], bestPath[128], bestBlackPath[128];
|
|
+ /* A window no publisher has a slot for (a Metal game window never gets one) used to rescan /dev/shm on every
|
|
+ * 60 Hz tick: opendir, readdir and stat/open/pread/close per slot file on the main thread. */
|
|
+ if (!_remoteFrameMapping && now < _remoteFrameSelectionRefresh)
|
|
+ return _remoteFrameValid;
|
|
+ if (_remoteFrameMapping && now < _remoteFrameSelectionRefresh) {
|
|
+ snprintf(path, sizeof(path), "%s", _remoteFrameMappingPath);
|
|
+ goto selectedFrame;
|
|
+ }
|
|
+ _remoteFrameSelectionRefresh = now + 0.25;
|
|
+ bestPath[0] = bestBlackPath[0] = '\0';
|
|
+ uint64_t bestMtime = 0, bestBlackMtime = 0;
|
|
+ BOOL bestSoftware = NO;
|
|
+
|
|
+ char prefix[32];
|
|
+ snprintf(prefix, sizeof(prefix), "darling-ca-frame-%u-", getuid());
|
|
+ size_t prefixLen = strlen(prefix);
|
|
+ DIR *dir = opendir("/dev/shm");
|
|
+ if (dir) {
|
|
+ struct dirent *ent;
|
|
+ while ((ent = readdir(dir)) != NULL) {
|
|
+ if (strncmp(ent->d_name, prefix, prefixLen) != 0)
|
|
+ continue;
|
|
+ char candidate[128];
|
|
+ snprintf(candidate, sizeof(candidate), "/dev/shm/%s",
|
|
+ ent->d_name);
|
|
+ struct stat st;
|
|
+ if (stat(candidate, &st) != 0)
|
|
+ continue;
|
|
+ uint64_t updateTime = (uint64_t)st.st_mtimespec.tv_sec * 1000000000u +
|
|
+ st.st_mtimespec.tv_nsec;
|
|
+ DarlingCARemoteFrame head = {0};
|
|
+ int cfd = open(candidate, O_RDONLY);
|
|
+ if (cfd < 0)
|
|
+ continue;
|
|
+ BOOL ok = (pread(cfd, &head, sizeof(head), 0) == sizeof(head) &&
|
|
+ (head.magic == DARLING_CA_FRAME_MAGIC || head.magic == DARLING_CA_FRAME_BUFFERED_MAGIC) &&
|
|
+ !(head.sequence & 1) &&
|
|
+ head.width && head.height &&
|
|
+ head.bytesPerRow >= head.width * 4 &&
|
|
+ head.dataSize == (uint64_t)head.bytesPerRow * head.height &&
|
|
+ head.dataSize <= 64u * 1024u * 1024u);
|
|
+ close(cfd);
|
|
+ BOOL matchesWindow = NO;
|
|
+ for (unsigned i = 0; i < frameTargetCount; ++i)
|
|
+ if (head.width == (unsigned)frameTargets[i].width &&
|
|
+ head.height == (unsigned)frameTargets[i].height)
|
|
+ matchesWindow = YES;
|
|
+ if (!ok || !matchesWindow)
|
|
+ continue;
|
|
+
|
|
+ /* Peek the top rows to distinguish a rendered frame from an
|
|
+ * empty (all-zero) surface of the same size. */
|
|
+ BOOL hasPixels = NO;
|
|
+ size_t peekRows = head.height < 40 ? (size_t) head.height : 40;
|
|
+ size_t peekLen = peekRows * head.bytesPerRow;
|
|
+ unsigned char peek[40 * 4096];
|
|
+ if (peekLen > sizeof(peek))
|
|
+ peekLen = sizeof(peek);
|
|
+ int pfd = open(candidate, O_RDONLY);
|
|
+ if (pfd >= 0) {
|
|
+ if (pread(pfd, peek, peekLen, sizeof(head) + framePayloadOffset(head.magic, head.pixelFormat)) ==
|
|
+ (ssize_t) peekLen) {
|
|
+ for (size_t i = 0; i + 3 < peekLen; i += 4) {
|
|
+ if (peek[i] | peek[i + 1] | peek[i + 2]) {
|
|
+ hasPixels = YES;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ close(pfd);
|
|
+ }
|
|
+
|
|
+ if (hasPixels) {
|
|
+ BOOL software = (head.pixelFormat & 0x80000000u) != 0;
|
|
+ if ((software && !bestSoftware) ||
|
|
+ (software == bestSoftware && updateTime > bestMtime)) {
|
|
+ bestSoftware = software;
|
|
+ bestMtime = updateTime;
|
|
+ snprintf(bestPath, sizeof(bestPath), "%s", candidate);
|
|
+ }
|
|
+ } else if (updateTime > bestBlackMtime) {
|
|
+ bestBlackMtime = updateTime;
|
|
+ snprintf(bestBlackPath, sizeof(bestBlackPath), "%s", candidate);
|
|
+ }
|
|
+ }
|
|
+ closedir(dir);
|
|
+ }
|
|
+
|
|
+ /* Keep a software owner across popup updates while the presented size
|
|
+ * still belongs to this window. After a real resize, retaining that owner
|
|
+ * can strand us on its popup slot even though a matching readback frame
|
|
+ * is ready from another publisher. Let normal candidate selection make
|
|
+ * that handoff when the old presentation no longer matches a target. */
|
|
+ BOOL presentationMatchesTarget = NO;
|
|
+ for (unsigned i = 0; i < frameTargetCount; ++i)
|
|
+ if (_remoteFrameWidth == (unsigned)frameTargets[i].width &&
|
|
+ _remoteFrameHeight == (unsigned)frameTargets[i].height)
|
|
+ presentationMatchesTarget = YES;
|
|
+ if (_remoteFrameSoftwarePublisher && _remoteFrameMapping && !bestSoftware &&
|
|
+ presentationMatchesTarget) {
|
|
+ const char *pidText = strrchr(_remoteFrameMappingPath, '-');
|
|
+ int publisher = pidText ? atoi(pidText + 1) : 0;
|
|
+ if (publisher > 0 && (kill(publisher, 0) == 0 || errno != ESRCH)) {
|
|
+ snprintf(path, sizeof(path), "%s", _remoteFrameMappingPath);
|
|
+ goto selectedFrame;
|
|
+ }
|
|
+ }
|
|
+ if (bestPath[0] != '\0')
|
|
+ snprintf(path, sizeof(path), "%s", bestPath);
|
|
+ else if (bestBlackPath[0] != '\0')
|
|
+ snprintf(path, sizeof(path), "%s", bestBlackPath);
|
|
+ else {
|
|
+ /* A writer's odd sequence is a temporary gap, not a hidden window.
|
|
+ * Keep the last complete frame instead of exposing local GL pixels. */
|
|
+ if (tlog) { fputc('S', tlog); }
|
|
+ if (!_remoteFrameMapping) _remoteFrameSelectionRefresh = now + 0.5;
|
|
+ return _remoteFrameValid;
|
|
+ }
|
|
+
|
|
+ if (tlog) { fputc('S', tlog); }
|
|
+
|
|
+ /* Only the presentation child owns remote pixels. GL swaps in sibling
|
|
+ * views cannot clear it, so unchanged frames need no repeated pixel copy. */
|
|
+selectedFrame:
|
|
+ if (!_remoteFrameMapping || strcmp(_remoteFrameMappingPath, path)) {
|
|
+ int fd = open(path, O_RDONLY);
|
|
+ if (fd < 0) return _remoteFrameValid;
|
|
+ struct stat st;
|
|
+ if (fstat(fd, &st) || st.st_size < sizeof(before) ||
|
|
+ st.st_size > sizeof(before) + 64u * 1024u * 1024u) {
|
|
+ close(fd); return _remoteFrameValid;
|
|
+ }
|
|
+ void *mapping = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
|
|
+ close(fd);
|
|
+ if (mapping == MAP_FAILED) return _remoteFrameValid;
|
|
+ if (_remoteFrameMapping) munmap(_remoteFrameMapping, _remoteFrameMappingSize);
|
|
+ _remoteFrameMapping = mapping;
|
|
+ _remoteFrameSoftwarePublisher = NO;
|
|
+ _remoteFrameMappingSize = st.st_size;
|
|
+ snprintf(_remoteFrameMappingPath, sizeof(_remoteFrameMappingPath), "%s", path);
|
|
+ }
|
|
+ memcpy(&before, _remoteFrameMapping, sizeof(before));
|
|
+ __sync_synchronize();
|
|
+ if ((before.magic != DARLING_CA_FRAME_MAGIC && before.magic != DARLING_CA_FRAME_BUFFERED_MAGIC) || (before.sequence & 1) ||
|
|
+ !before.width || !before.height || before.bytesPerRow < before.width * 4 ||
|
|
+ before.dataSize != (uint64_t)before.bytesPerRow * before.height ||
|
|
+ framePayloadOffset(before.magic, before.pixelFormat) > _remoteFrameMappingSize - sizeof(before) ||
|
|
+ before.dataSize > _remoteFrameMappingSize - sizeof(before) - framePayloadOffset(before.magic, before.pixelFormat))
|
|
+ return _remoteFrameValid;
|
|
+ if (before.pixelFormat & 0x80000000u) _remoteFrameSoftwarePublisher = YES;
|
|
+ BOOL matchesTarget = NO;
|
|
+ for (unsigned i = 0; i < frameTargetCount; ++i)
|
|
+ if (before.width == (unsigned)frameTargets[i].width &&
|
|
+ before.height == (unsigned)frameTargets[i].height) matchesTarget = YES;
|
|
+ /* A GPU process may publish several windows into its slot. Never resize
|
|
+ * our presentation child to another window's intervening surface. */
|
|
+ if (!matchesTarget) return _remoteFrameValid;
|
|
+ int frameX = 0, frameY = 0;
|
|
+ for (unsigned i = 0; i < frameTargetCount; ++i) {
|
|
+ if (before.width == (unsigned)frameTargets[i].width &&
|
|
+ before.height == (unsigned)frameTargets[i].height) {
|
|
+ if (i) { frameX = frameTargets[i].x; frameY = frameTargets[i].y; }
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (_remoteFrameWindow)
|
|
+ XMoveWindow(_display, _remoteFrameWindow, frameX, frameY);
|
|
+ if (_remoteFrameShmPumping && _remoteFrameWidth == before.width &&
|
|
+ _remoteFrameHeight == before.height) {
|
|
+ _remoteFrameShmStart(_remoteFrameShm, _remoteFrameWindow, _remoteFrameGC, path);
|
|
+ if (!_remoteFrameValid)
|
|
+ _remoteFrameShmPut(_remoteFrameShm, _remoteFrameWindow, _remoteFrameGC, NULL, 0);
|
|
+ XRaiseWindow(_display, _remoteFrameWindow);
|
|
+ XFlush(_display);
|
|
+ _remoteFrameSequence = before.sequence;
|
|
+ snprintf(_remoteFramePath, sizeof(_remoteFramePath), "%s", path);
|
|
+ _remoteFrameValid = YES;
|
|
+ return YES;
|
|
+ }
|
|
+ if (_remoteFrameValid && _remoteFrameWindow &&
|
|
+ _remoteFrameSequence == before.sequence &&
|
|
+ _remoteFrameWidth == before.width && _remoteFrameHeight == before.height &&
|
|
+ strcmp(_remoteFramePath, path) == 0) {
|
|
+ XRaiseWindow(_display, _remoteFrameWindow);
|
|
+ XFlush(_display);
|
|
+ return YES;
|
|
+ }
|
|
+ if (pixelCapacity < before.dataSize) {
|
|
+ unsigned char *newPixels = realloc(pixels, before.dataSize);
|
|
+ if (!newPixels) {
|
|
+ return _remoteFrameValid;
|
|
+ }
|
|
+ pixels = newPixels;
|
|
+ pixelCapacity = before.dataSize;
|
|
+ }
|
|
+ memcpy(pixels, (char *)_remoteFrameMapping + sizeof(before) + framePayloadOffset(before.magic, before.pixelFormat), before.dataSize);
|
|
+ __sync_synchronize();
|
|
+ memcpy(&after, _remoteFrameMapping, sizeof(after));
|
|
+ if (after.sequence != before.sequence || (after.sequence & 1)) {
|
|
+ if (tlog) { fputc('T', tlog); }
|
|
+ return _remoteFrameValid;
|
|
+ }
|
|
+
|
|
+ XImage *image = XCreateImage(_display, _visualInfo->visual,
|
|
+ _visualInfo->depth, ZPixmap, 0, (char *)pixels, before.width,
|
|
+ before.height, 32, before.bytesPerRow);
|
|
+ if (!image)
|
|
+ return _remoteFrameValid;
|
|
+
|
|
+ if (!_remoteFrameWindow) {
|
|
+ int shapeMajor, shapeMinor;
|
|
+ if (!XShapeQueryVersion(_display, &shapeMajor, &shapeMinor) ||
|
|
+ shapeMajor < 1 || (shapeMajor == 1 && shapeMinor < 1)) {
|
|
+ image->data = NULL;
|
|
+ XDestroyImage(image);
|
|
+ return _remoteFrameValid;
|
|
+ }
|
|
+ XSetWindowAttributes attrs = {0};
|
|
+ attrs.background_pixmap = None;
|
|
+ attrs.event_mask = ExposureMask;
|
|
+ _remoteFrameWindow = XCreateWindow(_display, _window, frameX, frameY,
|
|
+ before.width, before.height, 0, CopyFromParent, InputOutput,
|
|
+ CopyFromParent, CWBackPixmap | CWEventMask, &attrs);
|
|
+ /* An empty input shape lets clicks and motion reach native views. */
|
|
+ XShapeCombineRectangles(_display, _remoteFrameWindow, ShapeInput,
|
|
+ 0, 0, NULL, 0, ShapeSet, Unsorted);
|
|
+ [(X11Display *) [NSDisplay currentDisplay] setWindow: self forID: _remoteFrameWindow];
|
|
+ _remoteFrameGC = XCreateGC(_display, _remoteFrameWindow, 0, NULL);
|
|
+ }
|
|
+ if (_remoteFrameWidth != before.width || _remoteFrameHeight != before.height) {
|
|
+ if (_remoteFrameShm) {
|
|
+ _remoteFrameShmDestroy(_remoteFrameShm);
|
|
+ _remoteFrameShm = NULL;
|
|
+ _remoteFrameShmPumping = NO;
|
|
+ [self setRemoteFrameTimerSlow: NO];
|
|
+ }
|
|
+ XResizeWindow(_display, _remoteFrameWindow, before.width, before.height);
|
|
+ _remoteFrameWidth = before.width;
|
|
+ _remoteFrameHeight = before.height;
|
|
+ }
|
|
+ XMapRaised(_display, _remoteFrameWindow);
|
|
+ typedef void *(*CreateShm)(Display *, Visual *, unsigned, unsigned, unsigned);
|
|
+ typedef int (*PutShm)(void *, Drawable, GC, const void *, size_t);
|
|
+ static CreateShm createShm;
|
|
+ static PutShm putShm;
|
|
+ static void (*destroyShm)(void *);
|
|
+ static int (*startShm)(void *, Drawable, GC, const char *);
|
|
+ static BOOL checkedShm;
|
|
+ if (!checkedShm) {
|
|
+ checkedShm = YES;
|
|
+ const char *transportPath = getenv("DARLING_XSHM_LIBRARY");
|
|
+ const char *shmOption = getenv("RCLOL_XSHM");
|
|
+ void *transport = (shmOption && shmOption[0] == '0') ? NULL :
|
|
+ dlopen(transportPath ? transportPath : "/usr/lib/native/libDarlingXShm.dylib",
|
|
+ RTLD_NOW | RTLD_LOCAL);
|
|
+ void *(*getCreate)(void) = transport ? dlsym(transport, "DarlingXShmGetCreate") : NULL;
|
|
+ void *(*getPut)(void) = transport ? dlsym(transport, "DarlingXShmGetPut") : NULL;
|
|
+ void *(*getDestroy)(void) = transport ? dlsym(transport, "DarlingXShmGetDestroy") : NULL;
|
|
+ void *(*getStart)(void) = transport ? dlsym(transport, "DarlingXShmGetStart") : NULL;
|
|
+ if (getCreate && getPut && getDestroy) {
|
|
+ createShm = (CreateShm)getCreate();
|
|
+ putShm = (PutShm)getPut();
|
|
+ destroyShm = (void (*)(void *))getDestroy();
|
|
+ if (getStart) startShm = (int (*)(void *, Drawable, GC, const char *))getStart();
|
|
+ }
|
|
+ }
|
|
+ if (!_remoteFrameShm && createShm && putShm && destroyShm) {
|
|
+ /* The independent connection must see this client's child creation
|
|
+ * before issuing its first upload or creating its private GC. */
|
|
+ XSync(_display, False);
|
|
+ _remoteFrameShm = createShm(_display, _visualInfo->visual,
|
|
+ _visualInfo->depth, before.width, before.height);
|
|
+ _remoteFrameShmDestroy = destroyShm;
|
|
+ _remoteFrameShmPut = putShm;
|
|
+ _remoteFrameShmStart = startShm;
|
|
+ if (getenv("RCLOL_XSHM_TRACE")) {
|
|
+ extern int __simple_printf(const char *, ...);
|
|
+ __simple_printf("REMOTE_XSHM width=%u height=%u active=%d\n",
|
|
+ before.width, before.height, _remoteFrameShm != NULL);
|
|
+ }
|
|
+ }
|
|
+ if (!_remoteFrameShm || !putShm(_remoteFrameShm, _remoteFrameWindow,
|
|
+ _remoteFrameGC, pixels, before.bytesPerRow)) {
|
|
+ XPutImage(_display, _remoteFrameWindow, _remoteFrameGC, image, 0, 0, 0, 0,
|
|
+ before.width, before.height);
|
|
+ XFlush(_display);
|
|
+ }
|
|
+ if (_remoteFrameShm && startShm && !_remoteFrameShmPumping) {
|
|
+ _remoteFrameShmPumping = startShm(_remoteFrameShm, _remoteFrameWindow, _remoteFrameGC, path);
|
|
+ if (_remoteFrameShmPumping) [self setRemoteFrameTimerSlow: YES];
|
|
+ }
|
|
+ _remoteFrameSequence = before.sequence;
|
|
+ snprintf(_remoteFramePath, sizeof(_remoteFramePath), "%s", path);
|
|
+ _remoteFrameValid = YES;
|
|
+ image->data = NULL; /* pixels is reused by the next frame */
|
|
+ XDestroyImage(image);
|
|
+ if (tlog) { fputc('b', tlog); fflush(tlog); }
|
|
+ return YES;
|
|
+}
|
|
+
|
|
+- (BOOL) handleRemoteFrameExpose: (Window) window {
|
|
+ if (!window || window != _remoteFrameWindow)
|
|
+ return NO;
|
|
+ _remoteFrameValid = NO;
|
|
+ [self presentRemoteCAFrame: nil];
|
|
+ return YES;
|
|
+}
|
|
+
|
|
- (Window) windowHandle {
|
|
return _window;
|
|
}
|
|
@@ -473,11 +933,20 @@ static NSData *makeWindowIcon() {
|
|
}
|
|
|
|
- (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;
|
|
+ // Nonresizable windows can be created at 1x1 and enlarged before display.
|
|
+ // Refresh the constraints so a managing compositor sees the current size.
|
|
+ if (!(_styleMask & NSWindowStyleMaskResizable)) {
|
|
+ XSizeHints hints = {0};
|
|
+ hints.flags = PMinSize | PMaxSize;
|
|
+ hints.min_width = hints.max_width = frame.size.width;
|
|
+ hints.min_height = hints.max_height = frame.size.height;
|
|
+ XSetWMSizeHints(_display, _window, &hints, XA_WM_NORMAL_HINTS);
|
|
+ }
|
|
}
|
|
|
|
- (void) setHasShadow: (BOOL) value {
|
|
@@ -488,6 +957,25 @@ static NSData *makeWindowIcon() {
|
|
_level = value;
|
|
}
|
|
|
|
+/* NSApplication's attention timer calls this for requestUserAttention:. The
|
|
+ * CGWindow base class raises an abstract-invocation exception, which aborted
|
|
+ * the whole LeagueClientUx process. Ask the window manager to mark the window
|
|
+ * as demanding attention instead. */
|
|
+- (void) flashWindow {
|
|
+ XClientMessageEvent event = {0};
|
|
+ event.type = ClientMessage;
|
|
+ event.window = _window;
|
|
+ event.message_type = XInternAtom(_display, "_NET_WM_STATE", False);
|
|
+ event.format = 32;
|
|
+ event.data.l[0] = 1; /* _NET_WM_STATE_ADD */
|
|
+ event.data.l[1] = (long) XInternAtom(_display, "_NET_WM_STATE_DEMANDS_ATTENTION", False);
|
|
+ event.data.l[3] = 1;
|
|
+ XSendEvent(_display, DefaultRootWindow(_display), False,
|
|
+ SubstructureNotifyMask | SubstructureRedirectMask,
|
|
+ (XEvent *) &event);
|
|
+ XFlush(_display);
|
|
+}
|
|
+
|
|
- (void) setOpaque: (BOOL) value {
|
|
_isOpaque = value;
|
|
}
|
|
@@ -559,7 +1047,29 @@ static NSData *makeWindowIcon() {
|
|
XRaiseWindow(_display, _window);
|
|
}
|
|
|
|
+/* darling-lol: AppKit asks every window whether it is miniaturized on every event poll (NSWindow
|
|
+ * _makeSureIsOnAScreen), and each answer was a synchronous X round trip (XGetWindowProperty): about 13 blocking waits
|
|
+ * per game frame. Reuse the answer for 100 ms per window. */
|
|
+#include <mach/mach_time.h>
|
|
+#define RCLOL_MINI_SLOTS 32
|
|
+static struct { Window window; uint64_t stamp; BOOL value; } rclolMini[RCLOL_MINI_SLOTS];
|
|
+
|
|
- (BOOL) isMiniaturized {
|
|
+ static mach_timebase_info_data_t tb;
|
|
+ if (tb.denom == 0) mach_timebase_info(&tb);
|
|
+ uint64_t now = mach_absolute_time();
|
|
+ unsigned slot = (unsigned) (_window % RCLOL_MINI_SLOTS);
|
|
+ if (rclolMini[slot].window == _window && rclolMini[slot].stamp != 0 &&
|
|
+ (now - rclolMini[slot].stamp) * tb.numer / tb.denom < 100000000ULL)
|
|
+ return rclolMini[slot].value;
|
|
+ BOOL value = [self rclolQueryMiniaturized];
|
|
+ rclolMini[slot].window = _window;
|
|
+ rclolMini[slot].value = value;
|
|
+ rclolMini[slot].stamp = now;
|
|
+ return value;
|
|
+}
|
|
+
|
|
+- (BOOL) rclolQueryMiniaturized {
|
|
Atom wmState = XInternAtom(_display, "_NET_WM_STATE", FALSE);
|
|
Atom hiddenState = XInternAtom(_display, "_NET_WM_STATE_HIDDEN", FALSE);
|
|
|
|
@@ -615,6 +1125,12 @@ static NSData *makeWindowIcon() {
|
|
}
|
|
|
|
- (void) openGLFlushBuffer {
|
|
+ /* A remote compositor surface owns this drawable while it is present.
|
|
+ * Swapping the local Onyx surface would erase it until the next timer
|
|
+ * tick. Keep local presentation for windows without a usable remote frame. */
|
|
+ if ([self presentRemoteCAFrame: nil])
|
|
+ return;
|
|
+
|
|
CGLError error;
|
|
|
|
CGLContextObj prevContext = CGLGetCurrentContext();
|
|
@@ -652,7 +1168,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 +1182,7 @@ static int ignoreBadWindow(Display *display, XErrorEvent *errorEvent) {
|
|
}
|
|
|
|
- (void) frameChanged {
|
|
+ _remoteFrameTargetRefresh = 0;
|
|
XErrorHandler previousHandler = XSetErrorHandler(ignoreBadWindow);
|
|
@try {
|
|
Window root, parent;
|
|
@@ -710,14 +1227,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
|
|
+++ b/AppKit/include/AppKit/NSAccessibilityConstants.h
|
|
@@ -9,6 +9,7 @@ APPKIT_EXPORT NSString *const NSAccessibilityContentsAttribute;
|
|
APPKIT_EXPORT NSString *const NSAccessibilityDescriptionAttribute;
|
|
APPKIT_EXPORT NSString *const NSAccessibilityEnabledAttribute;
|
|
APPKIT_EXPORT NSString *const NSAccessibilityFocusedAttribute;
|
|
+APPKIT_EXPORT NSString *const NSAccessibilityRequiredAttribute;
|
|
APPKIT_EXPORT NSString *const NSAccessibilityHelpAttribute;
|
|
APPKIT_EXPORT NSString *const NSAccessibilityMaxValueAttribute;
|
|
APPKIT_EXPORT NSString *const NSAccessibilityMinValueAttribute;
|
|
diff --git a/AppKit/include/AppKit/NSApplication.h b/AppKit/include/AppKit/NSApplication.h
|
|
index a13138ae..32b97211 100644
|
|
--- a/AppKit/include/AppKit/NSApplication.h
|
|
+++ b/AppKit/include/AppKit/NSApplication.h
|
|
@@ -140,6 +140,7 @@ typedef NS_OPTIONS(NSUInteger, NSApplicationPresentationOptions) {
|
|
NSMutableArray *_orderedWindows; // get rid of
|
|
NSTimer *_attentionTimer;
|
|
NSApplicationPresentationOptions _presentationOptions;
|
|
+ BOOL _finishedLaunching;
|
|
}
|
|
|
|
@property(strong) NSMenu *helpMenu;
|
|
@@ -168,6 +169,7 @@ typedef NS_OPTIONS(NSUInteger, NSApplicationPresentationOptions) {
|
|
- (BOOL) isActive;
|
|
- (BOOL) isHidden;
|
|
- (BOOL) isRunning;
|
|
+- (BOOL) isFullKeyboardAccessEnabled;
|
|
|
|
- (NSWindow *) makeWindowsPerform: (SEL) selector inOrder: (BOOL) inOrder;
|
|
- (void) miniaturizeAll: sender;
|
|
diff --git a/AppKit/include/AppKit/NSColor.h b/AppKit/include/AppKit/NSColor.h
|
|
index b5ae3a65..6d555f60 100644
|
|
--- a/AppKit/include/AppKit/NSColor.h
|
|
+++ b/AppKit/include/AppKit/NSColor.h
|
|
@@ -29,6 +29,14 @@ typedef NSString *NSColorSpaceName;
|
|
|
|
@class NSImage;
|
|
@class NSPasteboard;
|
|
+@class NSColorSpace;
|
|
+
|
|
+enum {
|
|
+ NSDefaultControlTint = 0,
|
|
+ NSBlueControlTint = 1,
|
|
+ NSGraphiteControlTint = 6,
|
|
+ NSClearControlTint = 7,
|
|
+};
|
|
|
|
@interface NSColor : NSObject <NSCopying, NSCoding> {
|
|
NSColorListName _catalogName;
|
|
@@ -131,6 +139,7 @@ typedef NSString *NSColorSpaceName;
|
|
+ (NSColor *) alternateSelectedControlColor;
|
|
+ (NSColor *) alternateSelectedControlTextColor;
|
|
+ (NSColor *) controlColor;
|
|
++ (NSUInteger) currentControlTint;
|
|
+ (NSColor *) secondarySelectedControlColor;
|
|
+ (NSColor *) selectedControlColor;
|
|
+ (NSColor *) controlTextColor;
|
|
@@ -271,6 +280,7 @@ typedef NSString *NSColorSpaceName;
|
|
- (NSColor *) colorUsingColorSpaceName: (NSString *) colorSpace;
|
|
- (NSColor *) colorUsingColorSpaceName: (NSString *) colorSpace
|
|
device: (NSDictionary *) device;
|
|
+- (NSColor *) colorUsingColorSpace: (NSColorSpace *) space;
|
|
|
|
- (NSColor *) blendedColorWithFraction: (CGFloat) fraction
|
|
ofColor: (NSColor *) color;
|
|
diff --git a/AppKit/include/AppKit/NSDisplay.h b/AppKit/include/AppKit/NSDisplay.h
|
|
index 8860a087..aec7116d 100644
|
|
--- a/AppKit/include/AppKit/NSDisplay.h
|
|
+++ b/AppKit/include/AppKit/NSDisplay.h
|
|
@@ -22,6 +22,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
#import <AppKit/NSPasteboard.h>
|
|
#import <CarbonCore/UnicodeUtilities.h>
|
|
#import <Foundation/Foundation.h>
|
|
+#include <sys/types.h>
|
|
|
|
@class NSColor, NSDraggingManager, NSPrintInfo, NSView, NSSavePanel,
|
|
NSOpenPanel, NSFontTypeface, CGWindow;
|
|
@@ -29,8 +30,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
@interface NSDisplay : NSObject {
|
|
NSMutableArray *_eventQueue;
|
|
+ BOOL _cursorHiddenUntilMouseMoves;
|
|
}
|
|
|
|
+ (NSDisplay *) currentDisplay;
|
|
+- (BOOL) activateProcessIdentifier: (pid_t) processIdentifier;
|
|
|
|
- (NSArray *) screens;
|
|
|
|
@@ -50,7 +53,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
- (void) hideCursor;
|
|
- (void) unhideCursor;
|
|
+- (void) setCursorHiddenUntilMouseMoves: (BOOL) flag;
|
|
+- (void) pointerMoved;
|
|
|
|
- (id) cursorWithImage: (NSImage *) image hotSpot: (NSPoint) hotSpot;
|
|
- (id) cursorWithName: (NSString *) name;
|
|
- (void) setCursor: (id) cursor;
|
|
|
|
@@ -95,6 +100,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
|
|
- (NSPoint) mouseLocation;
|
|
- (NSUInteger) currentModifierFlags;
|
|
+- (NSUInteger) pressedMouseButtons;
|
|
|
|
- (NSArray *) orderedWindowNumbers;
|
|
|
|
diff --git a/AppKit/include/AppKit/NSEvent.h b/AppKit/include/AppKit/NSEvent.h
|
|
index 0677f2e7..d16080f4 100644
|
|
--- a/AppKit/include/AppKit/NSEvent.h
|
|
+++ b/AppKit/include/AppKit/NSEvent.h
|
|
@@ -83,6 +83,8 @@ typedef NS_ENUM(NSUInteger, NSEventType) {
|
|
|
|
NSEventTypeOtherMouseUp = 26,
|
|
NSOtherMouseUp = 26,
|
|
+ NSEventTypeOtherMouseDragged = 27,
|
|
+ NSOtherMouseDragged = 27,
|
|
|
|
NSAppKitSystem = 100,
|
|
|
|
@@ -212,6 +214,7 @@ enum { NSApplicationActivated = 0, NSApplicationDeactivated = 1 };
|
|
|
|
+ (NSPoint) mouseLocation;
|
|
+ (NSEventModifierFlags) modifierFlags;
|
|
++ (NSUInteger) pressedMouseButtons;
|
|
|
|
- (instancetype) initWithType: (NSEventType) type
|
|
location: (NSPoint) location
|
|
diff --git a/AppKit/include/AppKit/NSFont.h b/AppKit/include/AppKit/NSFont.h
|
|
index 3215013d..9f8bceb6 100644
|
|
--- a/AppKit/include/AppKit/NSFont.h
|
|
+++ b/AppKit/include/AppKit/NSFont.h
|
|
@@ -79,6 +79,7 @@ typedef enum {
|
|
+ (NSFont *) messageFontOfSize: (CGFloat) size;
|
|
+ (NSFont *) paletteFontOfSize: (CGFloat) size;
|
|
+ (NSFont *) systemFontOfSize: (CGFloat) size;
|
|
++ (NSFont *) systemFontOfSize: (CGFloat) size weight: (CGFloat) weight;
|
|
+ (NSFont *) titleBarFontOfSize: (CGFloat) size;
|
|
+ (NSFont *) toolTipsFontOfSize: (CGFloat) size;
|
|
+ (NSFont *) userFontOfSize: (CGFloat) size;
|
|
@@ -98,6 +99,7 @@ typedef enum {
|
|
+ (NSArray *) preferredFontNames;
|
|
+ (void) setPreferredFontNames: (NSArray *) fontNames;
|
|
|
|
+- (CTFontRef) _coreTextFont;
|
|
- (CGFloat) pointSize;
|
|
- (NSString *) fontName;
|
|
- (const CGFloat *) matrix;
|
|
@@ -113,6 +115,9 @@ typedef enum {
|
|
- (NSFont *) screenFont;
|
|
- (NSFont *) screenFontWithRenderingMode: (NSFontRenderingMode) mode;
|
|
|
|
+/* CoreText bridge used when an NSFont is accepted as a CTFontRef. */
|
|
+- (CGFontRef) copyGraphicsFont;
|
|
+
|
|
- (NSRect) boundingRectForFont;
|
|
- (NSRect) boundingRectForGlyph: (NSGlyph) glyph;
|
|
|
|
diff --git a/AppKit/include/AppKit/NSMenu.h b/AppKit/include/AppKit/NSMenu.h
|
|
index aa776e1a..82873d60 100644
|
|
--- a/AppKit/include/AppKit/NSMenu.h
|
|
+++ b/AppKit/include/AppKit/NSMenu.h
|
|
@@ -99,6 +99,11 @@ APPKIT_EXPORT const NSNotificationName NSMenuDidEndTrackingNotification;
|
|
- (void) setDelegate: (id<NSMenuDelegate>) object;
|
|
- (id<NSMenuDelegate>) delegate;
|
|
|
|
+/* Private AppKit API. Chromium/Electron calls this when building menu
|
|
+ * padding metadata; Cocotron has no such layout, so it is a no-op setter
|
|
+ * (without it the call raises NSException and the process terminates). */
|
|
+- (void) _setHasPadding: (BOOL) padding onEdge: (NSInteger) edge;
|
|
+
|
|
@end
|
|
|
|
@interface NSObject (NSMenu_validateItem)
|
|
diff --git a/AppKit/include/AppKit/NSMenuItem.h b/AppKit/include/AppKit/NSMenuItem.h
|
|
index 5231496d..6c5eac59 100644
|
|
--- a/AppKit/include/AppKit/NSMenuItem.h
|
|
+++ b/AppKit/include/AppKit/NSMenuItem.h
|
|
@@ -45,6 +45,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
NSImage *_onStateImage;
|
|
NSImage *_offStateImage;
|
|
NSImage *_mixedStateImage;
|
|
+ NSView *_view;
|
|
+ NSString *_toolTip; /* darling-lol M42b: -setToolTip: (Electron menus) */
|
|
BOOL _alternate;
|
|
BOOL _allowsKeyEquivalentWhenHidden;
|
|
NSUserInterfaceItemIdentifier _identifier;
|
|
@@ -72,6 +74,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
- (NSString *) keyEquivalent;
|
|
- (unsigned) keyEquivalentModifierMask;
|
|
- (NSImage *) image;
|
|
+- (NSView *) view;
|
|
- (NSImage *) onStateImage;
|
|
- (NSImage *) offStateImage;
|
|
- (NSImage *) mixedStateImage;
|
|
@@ -86,6 +89,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
- (BOOL) isHidden;
|
|
|
|
- (void) setTitle: (NSString *) title;
|
|
+- (NSString *) toolTip;
|
|
+- (void) setToolTip: (NSString *) toolTip;
|
|
- (void) setAttributedTitle: (NSAttributedString *) title;
|
|
- (void) setTitleWithMnemonic: (NSString *) mnemonic;
|
|
- (void) setMnemonicLocation: (unsigned) location;
|
|
@@ -97,6 +102,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
- (void) setKeyEquivalent: (NSString *) keyEquivalent;
|
|
- (void) setKeyEquivalentModifierMask: (unsigned) mask;
|
|
- (void) setImage: (NSImage *) image;
|
|
+- (void) setView: (NSView *) view;
|
|
- (void) setOnStateImage: (NSImage *) image;
|
|
- (void) setOffStateImage: (NSImage *) image;
|
|
- (void) setMixedStateImage: (NSImage *) image;
|
|
diff --git a/AppKit/include/AppKit/NSNib.h b/AppKit/include/AppKit/NSNib.h
|
|
index ff3f2d6f..40729e09 100644
|
|
--- a/AppKit/include/AppKit/NSNib.h
|
|
+++ b/AppKit/include/AppKit/NSNib.h
|
|
@@ -42,5 +42,6 @@ APPKIT_EXPORT NSString *const NSNibTopLevelObjects;
|
|
|
|
- (BOOL) instantiateNibWithExternalNameTable: (NSDictionary *) nameTable;
|
|
- (BOOL) instantiateNibWithOwner: owner topLevelObjects: (NSArray **) objects;
|
|
+- (BOOL) instantiateWithOwner: (id) owner topLevelObjects: (NSArray **) objects;
|
|
|
|
@end
|
|
diff --git a/AppKit/include/AppKit/NSResponder.h b/AppKit/include/AppKit/NSResponder.h
|
|
index 11dfac87..b595625e 100644
|
|
--- a/AppKit/include/AppKit/NSResponder.h
|
|
+++ b/AppKit/include/AppKit/NSResponder.h
|
|
@@ -24,9 +24,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
|
|
@interface NSResponder : NSObject <NSCoding> {
|
|
id _nextResponder;
|
|
+ id _touchBar;
|
|
}
|
|
|
|
- (NSResponder *) nextResponder;
|
|
+- (id) touchBar;
|
|
+- (void) setTouchBar: (id) touchBar;
|
|
|
|
- (NSMenu *) menu;
|
|
- (NSUndoManager *) undoManager;
|
|
@@ -69,6 +72,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
- (void) mouseExited: (NSEvent *) event;
|
|
- (void) mouseDragged: (NSEvent *) event;
|
|
|
|
+- (void) otherMouseDown: (NSEvent *) event;
|
|
+- (void) otherMouseUp: (NSEvent *) event;
|
|
+- (void) otherMouseDragged: (NSEvent *) event;
|
|
- (void) rightMouseUp: (NSEvent *) event;
|
|
- (void) rightMouseDown: (NSEvent *) event;
|
|
- (void) rightMouseDragged: (NSEvent *) event;
|
|
diff --git a/AppKit/include/AppKit/NSRunningApplication.h b/AppKit/include/AppKit/NSRunningApplication.h
|
|
index c577764b..d8bb21cc 100644
|
|
--- a/AppKit/include/AppKit/NSRunningApplication.h
|
|
+++ b/AppKit/include/AppKit/NSRunningApplication.h
|
|
@@ -18,6 +18,7 @@
|
|
*/
|
|
|
|
#import <Foundation/NSObject.h>
|
|
+#include <sys/types.h>
|
|
|
|
typedef enum {
|
|
NSApplicationActivationPolicyRegular,
|
|
@@ -26,7 +27,14 @@ typedef enum {
|
|
} NSApplicationActivationPolicy;
|
|
|
|
@interface NSRunningApplication : NSObject
|
|
+{
|
|
+ pid_t _processIdentifier;
|
|
+}
|
|
|
|
+ (NSArray<NSRunningApplication *> *) runningApplicationsWithBundleIdentifier: (NSString *) bundleIdentifier;
|
|
++ (NSRunningApplication *) runningApplicationWithProcessIdentifier: (pid_t) processIdentifier;
|
|
+- (pid_t) processIdentifier;
|
|
+- (BOOL) isTerminated;
|
|
+- (BOOL) activateWithOptions: (NSUInteger) options;
|
|
|
|
@end
|
|
diff --git a/AppKit/include/AppKit/NSScrubberItemView.h b/AppKit/include/AppKit/NSScrubberItemView.h
|
|
index 40042c89..37c1f4b6 100644
|
|
--- a/AppKit/include/AppKit/NSScrubberItemView.h
|
|
+++ b/AppKit/include/AppKit/NSScrubberItemView.h
|
|
@@ -27,3 +27,6 @@
|
|
|
|
@interface NSScrubberTextItemView : NSScrubberItemView
|
|
@end
|
|
+
|
|
+@interface NSScrubberImageItemView : NSScrubberItemView
|
|
+@end
|
|
diff --git a/AppKit/include/AppKit/NSSpeechSynthesizer.h b/AppKit/include/AppKit/NSSpeechSynthesizer.h
|
|
index d8a6f6f1..a1c37d46 100644
|
|
--- a/AppKit/include/AppKit/NSSpeechSynthesizer.h
|
|
+++ b/AppKit/include/AppKit/NSSpeechSynthesizer.h
|
|
@@ -38,7 +38,12 @@ APPKIT_EXPORT NSSpeechPropertyKey const NSSpeechRateProperty;
|
|
APPKIT_EXPORT NSSpeechPropertyKey const NSSpeechPitchBaseProperty;
|
|
APPKIT_EXPORT NSSpeechPropertyKey const NSSpeechVolumeProperty;
|
|
|
|
+@class NSArray, NSDictionary;
|
|
+
|
|
@interface NSSpeechSynthesizer : NSObject
|
|
++ (NSArray *)availableVoices;
|
|
++ (NSString *)defaultVoice;
|
|
++ (NSDictionary *)attributesForVoice:(NSString *)voice;
|
|
@end
|
|
|
|
@protocol NSSpeechSynthesizerDelegate <NSObject>
|
|
diff --git a/AppKit/include/AppKit/NSSpellChecker.h b/AppKit/include/AppKit/NSSpellChecker.h
|
|
index aaf2083b..41386be3 100644
|
|
--- a/AppKit/include/AppKit/NSSpellChecker.h
|
|
+++ b/AppKit/include/AppKit/NSSpellChecker.h
|
|
@@ -124,7 +124,7 @@ typedef NSInteger NSCorrectionResponse;
|
|
orthography: (NSOrthography **) orthography
|
|
wordCount: (NSInteger *) wordCount;
|
|
|
|
-#ifdef NS_BLOCKS
|
|
+#if defined(NS_BLOCKS) || defined(__BLOCKS__)
|
|
- (NSInteger) requestCheckingOfString: (NSString *) stringToCheck
|
|
range: (NSRange) range
|
|
types: (NSTextCheckingTypes) checkingTypes
|
|
diff --git a/AppKit/include/AppKit/NSTextInput.h b/AppKit/include/AppKit/NSTextInput.h
|
|
index c9dfd7c7..413f5ac1 100644
|
|
--- a/AppKit/include/AppKit/NSTextInput.h
|
|
+++ b/AppKit/include/AppKit/NSTextInput.h
|
|
@@ -23,7 +23,17 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
|
|
APPKIT_EXPORT NSString *const NSTextInputContextKeyboardSelectionDidChangeNotification;
|
|
|
|
-@interface NSTextInputContext : NSObject
|
|
+@class NSEvent;
|
|
+@interface NSTextInputContext : NSObject {
|
|
+ id _client;
|
|
+}
|
|
+- (instancetype)initWithClient:(id)client;
|
|
+- (id)client;
|
|
+- (BOOL)handleEvent:(NSEvent *)event;
|
|
+- (void)activate;
|
|
+- (void)deactivate;
|
|
+- (void)discardMarkedText;
|
|
+- (void)invalidateCharacterCoordinates;
|
|
|
|
@end
|
|
|
|
diff --git a/AppKit/include/AppKit/NSTouchBarItem.h b/AppKit/include/AppKit/NSTouchBarItem.h
|
|
index 956712e1..528e7418 100644
|
|
--- a/AppKit/include/AppKit/NSTouchBarItem.h
|
|
+++ b/AppKit/include/AppKit/NSTouchBarItem.h
|
|
@@ -30,6 +30,8 @@ APPKIT_EXPORT NSTouchBarItemIdentifier const
|
|
NSTouchBarItemIdentifierFlexibleSpace;
|
|
APPKIT_EXPORT NSTouchBarItemIdentifier const
|
|
NSTouchBarItemIdentifierOtherItemsProxy;
|
|
+APPKIT_EXPORT NSTouchBarItemIdentifier const
|
|
+ NSTouchBarItemIdentifierCandidateList;
|
|
|
|
@interface NSTouchBarItem : NSObject <NSCoding>
|
|
|
|
diff --git a/AppKit/include/AppKit/NSView.h b/AppKit/include/AppKit/NSView.h
|
|
index edb45627..581991d1 100644
|
|
--- a/AppKit/include/AppKit/NSView.h
|
|
+++ b/AppKit/include/AppKit/NSView.h
|
|
@@ -139,6 +139,12 @@ APPKIT_EXPORT const NSViewFullScreenModeOptionKey NSFullScreenModeApplicationPre
|
|
NSLayoutPriority _verticalContentCompressionResistancePriority;
|
|
|
|
NSAppearance *_appearance;
|
|
+
|
|
+ /* darling-lol M57: modern NSView properties Chromium reads. */
|
|
+ NSUInteger _allowedTouchTypes;
|
|
+ BOOL _wantsUpdateLayer;
|
|
+ BOOL _canDrawSubviewsIntoLayer;
|
|
+ BOOL _wantsBestResolutionOpenGLSurface;
|
|
}
|
|
|
|
@property(class, readonly) BOOL requiresConstraintBasedLayout;
|
|
@@ -213,6 +219,18 @@ APPKIT_EXPORT const NSViewFullScreenModeOptionKey NSFullScreenModeApplicationPre
|
|
|
|
- (NSPoint) convertPoint: (NSPoint) point fromView: (NSView *) viewOrNil;
|
|
- (NSPoint) convertPoint: (NSPoint) point toView: (NSView *) viewOrNil;
|
|
+- (NSUInteger) allowedTouchTypes;
|
|
+- (void) setAllowedTouchTypes: (NSUInteger) value;
|
|
+- (BOOL) wantsUpdateLayer;
|
|
+- (void) setWantsUpdateLayer: (BOOL) value;
|
|
+- (BOOL) canDrawSubviewsIntoLayer;
|
|
+- (void) setCanDrawSubviewsIntoLayer: (BOOL) value;
|
|
+- (BOOL) wantsBestResolutionOpenGLSurface;
|
|
+- (void) setWantsBestResolutionOpenGLSurface: (BOOL) value;
|
|
+- (NSPoint) convertPointFromBacking: (NSPoint) point;
|
|
+- (NSPoint) convertPointToBacking: (NSPoint) point;
|
|
+- (NSRect) convertRectFromBacking: (NSRect) rect;
|
|
+- (NSRect) convertRectToBacking: (NSRect) rect;
|
|
- (NSSize) convertSize: (NSSize) size fromView: (NSView *) viewOrNil;
|
|
- (NSSize) convertSize: (NSSize) size toView: (NSView *) viewOrNil;
|
|
- (NSRect) convertRect: (NSRect) rect fromView: (NSView *) viewOrNil;
|
|
diff --git a/AppKit/include/AppKit/NSWindow.h b/AppKit/include/AppKit/NSWindow.h
|
|
index a83c4d75..575c3581 100644
|
|
--- a/AppKit/include/AppKit/NSWindow.h
|
|
+++ b/AppKit/include/AppKit/NSWindow.h
|
|
@@ -188,6 +188,16 @@ APPKIT_EXPORT const NSNotificationName NSWindowDidExposeNotification;
|
|
int _cursorRectsDisabled;
|
|
int _flushDisabled;
|
|
|
|
+ /* darling-lol M57: modern AppKit window properties Chromium reads. */
|
|
+ BOOL _titlebarAppearsTransparent;
|
|
+ NSInteger _titleVisibility;
|
|
+ BOOL _restorable;
|
|
+ NSString *_subtitle;
|
|
+ NSInteger _tabbingMode;
|
|
+ NSString *_tabbingIdentifier;
|
|
+ NSInteger _titlebarSeparatorStyle;
|
|
+ NSInteger _toolbarStyle;
|
|
+
|
|
BOOL _isOpaque;
|
|
BOOL _isVisible;
|
|
BOOL _isDocumentEdited;
|
|
@@ -217,6 +227,7 @@ APPKIT_EXPORT const NSNotificationName NSWindowDidExposeNotification;
|
|
BOOL _showsToolbarButton;
|
|
BOOL _ignoresMouseEvents;
|
|
BOOL _isMovableByWindowBackground;
|
|
+ BOOL _isMovable;
|
|
BOOL _allowsToolTipsWhenApplicationIsInactive;
|
|
BOOL _defaultButtonCellKeyEquivalentDisabled;
|
|
BOOL _autorecalculatesKeyViewLoop;
|
|
@@ -240,6 +251,7 @@ APPKIT_EXPORT const NSNotificationName NSWindowDidExposeNotification;
|
|
NSMutableArray *_drawers;
|
|
NSToolbar *_toolbar;
|
|
NSWindowAnimationContext *_animationContext;
|
|
+ NSButton *_standardWindowButtons[3];
|
|
|
|
NSRect _savedFrame;
|
|
NSPoint _mouseDownLocationInWindow;
|
|
@@ -425,6 +437,7 @@ APPKIT_EXPORT const NSNotificationName NSWindowDidExposeNotification;
|
|
- (NSButton *) standardWindowButton: (NSWindowButton) value;
|
|
- (NSButtonCell *) defaultButtonCell;
|
|
- (NSWindow *) attachedSheet;
|
|
+- (NSWindow *) sheetParent;
|
|
|
|
- (id) windowController;
|
|
- (NSArray *) drawers;
|
|
@@ -450,6 +463,7 @@ APPKIT_EXPORT const NSNotificationName NSWindowDidExposeNotification;
|
|
- (BOOL) isMainWindow;
|
|
- (BOOL) isMiniaturized;
|
|
- (BOOL) isMovable;
|
|
+- (void) performWindowDragWithEvent:(NSEvent *)event;
|
|
- (BOOL) inLiveResize;
|
|
- (BOOL) canBecomeKeyWindow;
|
|
- (BOOL) canBecomeMainWindow;
|
|
@@ -458,6 +472,30 @@ APPKIT_EXPORT const NSNotificationName NSWindowDidExposeNotification;
|
|
|
|
- (NSPoint) convertBaseToScreen: (NSPoint) point;
|
|
- (NSPoint) convertScreenToBase: (NSPoint) point;
|
|
+- (NSPoint) convertPointToScreen: (NSPoint) point;
|
|
+- (NSPoint) convertPointFromScreen: (NSPoint) point;
|
|
+- (NSRect) convertRectToScreen: (NSRect) rect;
|
|
+- (NSRect) convertRectFromScreen: (NSRect) rect;
|
|
+
|
|
+- (BOOL) titlebarAppearsTransparent;
|
|
+- (void) setTitlebarAppearsTransparent: (BOOL) value;
|
|
+- (NSInteger) titleVisibility;
|
|
+- (void) setTitleVisibility: (NSInteger) value;
|
|
+- (BOOL) movableByWindowBackground;
|
|
+- (NSUInteger) occlusionState;
|
|
+- (BOOL) isRestorable;
|
|
+- (BOOL) restorable;
|
|
+- (void) setRestorable: (BOOL) value;
|
|
+- (NSString *) subtitle;
|
|
+- (void) setSubtitle: (NSString *) value;
|
|
+- (NSInteger) tabbingMode;
|
|
+- (void) setTabbingMode: (NSInteger) value;
|
|
+- (NSString *) tabbingIdentifier;
|
|
+- (void) setTabbingIdentifier: (NSString *) value;
|
|
+- (NSInteger) titlebarSeparatorStyle;
|
|
+- (void) setTitlebarSeparatorStyle: (NSInteger) value;
|
|
+- (NSInteger) toolbarStyle;
|
|
+- (void) setToolbarStyle: (NSInteger) value;
|
|
|
|
- (NSRect) frameRectForContentRect: (NSRect) rect;
|
|
- (NSRect) contentRectForFrameRect: (NSRect) rect;
|
|
diff --git a/AppKit/nib.subproj/NSNib.m b/AppKit/nib.subproj/NSNib.m
|
|
index 01dc29ad..63654d66 100644
|
|
--- a/AppKit/nib.subproj/NSNib.m
|
|
+++ b/AppKit/nib.subproj/NSNib.m
|
|
@@ -284,10 +284,8 @@ NSString *const NSNibTopLevelObjects = @"NSNibTopLevelObjects";
|
|
return result;
|
|
}
|
|
|
|
-#warning -[NSNib instantiateWithOwner:topLevelObjects:] method makes darling be a zombie process and need to restart device
|
|
-
|
|
-/* - (BOOL) instantiateWithOwner: (id) owner topLevelObjects: (NSArray **) objects {
|
|
+- (BOOL) instantiateWithOwner: (id) owner topLevelObjects: (NSArray **) objects {
|
|
return [self instantiateNibWithOwner: owner topLevelObjects: objects];
|
|
-} */
|
|
+}
|
|
|
|
@end
|
|
diff --git a/AppKit/private-include/AppKit/NSAccessibilityRemoteUIElement.h b/AppKit/private-include/AppKit/NSAccessibilityRemoteUIElement.h
|
|
index 756d6078..51cbe463 100644
|
|
--- a/AppKit/private-include/AppKit/NSAccessibilityRemoteUIElement.h
|
|
+++ b/AppKit/private-include/AppKit/NSAccessibilityRemoteUIElement.h
|
|
@@ -20,4 +20,14 @@
|
|
#import <Foundation/Foundation.h>
|
|
|
|
@interface NSAccessibilityRemoteUIElement : NSObject
|
|
+
|
|
+/* darling-lol M57: Chromium/Electron asks for a remote token for a local
|
|
+ * accessibility element; Cocotron lacked the class method entirely, so the
|
|
+ * UX aborted with an unrecognized-selector NSException during createWindow. */
|
|
++ (id) remoteTokenForLocalUIElement: (id) element;
|
|
++ (void) setRemoteUIApp: (id) app;
|
|
++ (void) registerRemoteUIProcessIdentifier: (int) pid;
|
|
+- (id) init;
|
|
+- (id) initWithRemoteToken: (id) token;
|
|
+
|
|
@end
|
|
diff --git a/CoreGraphics/CGAffineTransform.m b/CoreGraphics/CGAffineTransform.m
|
|
index aaeca762..0c04444e 100644
|
|
--- a/CoreGraphics/CGAffineTransform.m
|
|
+++ b/CoreGraphics/CGAffineTransform.m
|
|
@@ -100,6 +100,18 @@ CGAffineTransform CGAffineTransformTranslate(CGAffineTransform xform,
|
|
}
|
|
|
|
CGRect CGRectApplyAffineTransform(CGRect rect, CGAffineTransform t) {
|
|
- printf("CGRectApplyAffineTransform STUB\n");
|
|
- return rect;
|
|
+ if (CGRectIsNull(rect))
|
|
+ return rect;
|
|
+ CGFloat minX = 0, minY = 0, maxX = 0, maxY = 0;
|
|
+ for (int i = 0; i < 4; ++i) {
|
|
+ CGFloat x = rect.origin.x + ((i & 1) ? rect.size.width : 0);
|
|
+ CGFloat y = rect.origin.y + ((i & 2) ? rect.size.height : 0);
|
|
+ CGFloat tx = t.a * x + t.c * y + t.tx;
|
|
+ CGFloat ty = t.b * x + t.d * y + t.ty;
|
|
+ if (i == 0 || tx < minX) minX = tx;
|
|
+ if (i == 0 || ty < minY) minY = ty;
|
|
+ if (i == 0 || tx > maxX) maxX = tx;
|
|
+ if (i == 0 || ty > maxY) maxY = ty;
|
|
+ }
|
|
+ return CGRectMake(minX, minY, maxX - minX, maxY - minY);
|
|
}
|
|
diff --git a/CoreGraphics/CGColorSpace.m b/CoreGraphics/CGColorSpace.m
|
|
index c84a2f75..e0ca9572 100644
|
|
--- a/CoreGraphics/CGColorSpace.m
|
|
+++ b/CoreGraphics/CGColorSpace.m
|
|
@@ -37,6 +37,8 @@ const CFStringRef kCGColorSpaceACESCGLinear =
|
|
CFSTR("kCGColorSpaceACESCGLinear");
|
|
const CFStringRef kCGColorSpaceITUR_709 = CFSTR("kCGColorSpaceITUR_709");
|
|
const CFStringRef kCGColorSpaceITUR_2020 = CFSTR("kCGColorSpaceITUR_2020");
|
|
+const CFStringRef kCGColorSpaceITUR_2100_HLG = CFSTR("kCGColorSpaceITUR_2100_HLG");
|
|
+const CFStringRef kCGColorSpaceITUR_2100_PQ = CFSTR("kCGColorSpaceITUR_2100_PQ");
|
|
const CFStringRef kCGColorSpaceROMMRGB = CFSTR("kCGColorSpaceROMMRGB");
|
|
const CFStringRef kCGColorSpaceDCIP3 = CFSTR("kCGColorSpaceDCIP3");
|
|
const CFStringRef kCGColorSpaceExtendedSRGB =
|
|
@@ -85,3 +87,11 @@ size_t CGColorSpaceGetNumberOfComponents(CGColorSpaceRef self) {
|
|
CGColorSpaceRef CGColorSpaceCreateWithName(CFStringRef name) {
|
|
return (CGColorSpaceRef) O2ColorSpaceCreateWithName(name);
|
|
}
|
|
+
|
|
+CFDataRef CGColorSpaceCopyICCData(CGColorSpaceRef self) {
|
|
+ /* No ICC profile store in Darling yet; callers (e.g. Chromium display
|
|
+ * setup) treat NULL as "use default sRGB", consistent with what
|
|
+ * -[NSScreen colorSpace] reports. */
|
|
+ (void)self;
|
|
+ return NULL;
|
|
+}
|
|
diff --git a/CoreGraphics/CGContext.m b/CoreGraphics/CGContext.m
|
|
index db8869c8..6fea5107 100644
|
|
--- a/CoreGraphics/CGContext.m
|
|
+++ b/CoreGraphics/CGContext.m
|
|
@@ -614,8 +614,13 @@ void CGContextDrawTiledImage(CGContextRef c, CGRect rect, CGImageRef image)
|
|
}
|
|
|
|
void CGContextShowGlyphsAtPositions(CGContextRef c,
|
|
- const CGGlyph * glyphs, const CGPoint * Lpositions,
|
|
+ const CGGlyph *glyphs, const CGPoint *positions,
|
|
size_t count)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
+ if (!c || !glyphs || !positions)
|
|
+ return;
|
|
+ CGPoint savedPosition = CGContextGetTextPosition(c);
|
|
+ for (size_t i = 0; i < count; ++i)
|
|
+ CGContextShowGlyphsAtPoint(c, positions[i].x, positions[i].y, glyphs + i, 1);
|
|
+ CGContextSetTextPosition(c, savedPosition.x, savedPosition.y);
|
|
}
|
|
diff --git a/CoreGraphics/CGDirectDisplay.m b/CoreGraphics/CGDirectDisplay.m
|
|
index e213b066..569d77f2 100644
|
|
--- a/CoreGraphics/CGDirectDisplay.m
|
|
+++ b/CoreGraphics/CGDirectDisplay.m
|
|
@@ -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;
|
|
|
|
- [display grabMouse: connected];
|
|
+ // Association enables ordinary absolute pointer motion. A disconnected
|
|
+ // cursor requests relative motion, implemented by the backend's grab.
|
|
+ [display grabMouse: !connected];
|
|
return kCGErrorSuccess;
|
|
}
|
|
|
|
@@ -500,3 +526,38 @@ CFDictionaryRef CGDisplayCurrentMode(CGDirectDisplayID display) {
|
|
size_t CGDisplayModeGetPixelWidth(CGDisplayModeRef mode) {
|
|
return 0;
|
|
}
|
|
+
|
|
+bool CGDisplayUsesForceToGray(void) {
|
|
+ /* Accessibility grayscale is never forced on X11. */
|
|
+ return false;
|
|
+}
|
|
+
|
|
+bool CGDisplayIsBuiltin(CGDirectDisplayID display) {
|
|
+ /* Single X11 screen; treat it as the built-in display. */
|
|
+ (void)display;
|
|
+ return true;
|
|
+}
|
|
+
|
|
+double CGDisplayRotation(CGDirectDisplayID display) {
|
|
+ (void)display;
|
|
+ return 0.0;
|
|
+}
|
|
+
|
|
+CGColorSpaceRef CGDisplayCopyColorSpace(CGDirectDisplayID display) {
|
|
+ (void)display;
|
|
+ return CGColorSpaceCreateDeviceRGB();
|
|
+}
|
|
+
|
|
+CGImageRef CGDisplayCreateImage(CGDirectDisplayID displayID) {
|
|
+ /* Screen capture not implemented; callers must handle NULL. */
|
|
+ (void)displayID;
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+bool CGPreflightScreenCaptureAccess(void) {
|
|
+ return false;
|
|
+}
|
|
+
|
|
+bool CGRequestScreenCaptureAccess(void) {
|
|
+ return false;
|
|
+}
|
|
diff --git a/CoreGraphics/CGDisplayStream.m b/CoreGraphics/CGDisplayStream.m
|
|
index a9457e73..f9adca0d 100644
|
|
--- a/CoreGraphics/CGDisplayStream.m
|
|
+++ b/CoreGraphics/CGDisplayStream.m
|
|
@@ -20,3 +20,7 @@
|
|
#import <CoreGraphics/CGDisplayStream.h>
|
|
|
|
const CFStringRef kCGDisplayStreamShowCursor = CFSTR("kCGDisplayStreamShowCursor");
|
|
+const CFStringRef kCGDisplayStreamColorSpace = CFSTR("kCGDisplayStreamColorSpace");
|
|
+const CFStringRef kCGDisplayStreamDestinationRect = CFSTR("kCGDisplayStreamDestinationRect");
|
|
+const CFStringRef kCGDisplayStreamMinimumFrameTime = CFSTR("kCGDisplayStreamMinimumFrameTime");
|
|
+const CFStringRef kCGDisplayStreamPreserveAspectRatio = CFSTR("kCGDisplayStreamPreserveAspectRatio");
|
|
diff --git a/CoreGraphics/CGEventSource.m b/CoreGraphics/CGEventSource.m
|
|
index 537c67ae..0bffcd36 100644
|
|
--- a/CoreGraphics/CGEventSource.m
|
|
+++ b/CoreGraphics/CGEventSource.m
|
|
@@ -76,3 +76,22 @@ CGEventFlags CGEventSourceFlagsState(CGEventSourceStateID stateID) {
|
|
void CGEventSourceSetLocalEventsSuppressionInterval(CGEventSourceRef source, CFTimeInterval seconds) {
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
}
|
|
+
|
|
+bool CGEventSourceKeyState(CGEventSourceStateID stateID, CGKeyCode key) {
|
|
+ /* No access to the host keymap from here; report no keys down.
|
|
+ * Used by push-to-talk style polling (e.g. Discord); returning
|
|
+ * false is safe and keeps callers (dlopen-time binding included)
|
|
+ * alive. A future XQueryKeymap-backed version can upgrade this. */
|
|
+ (void)stateID; (void)key;
|
|
+ return false;
|
|
+}
|
|
+
|
|
+CFTimeInterval CGEventSourceSecondsSinceLastEventType(CGEventSourceStateID stateID, CGEventType eventType) {
|
|
+ (void)stateID; (void)eventType;
|
|
+ return 0.0;
|
|
+}
|
|
+
|
|
+uint32_t CGEventSourceCounterForEventType(CGEventSourceStateID stateID, CGEventType eventType) {
|
|
+ (void)stateID; (void)eventType;
|
|
+ return 0;
|
|
+}
|
|
diff --git a/CoreGraphics/CGFont.m b/CoreGraphics/CGFont.m
|
|
index bcd822fd..51e89ae2 100644
|
|
--- a/CoreGraphics/CGFont.m
|
|
+++ b/CoreGraphics/CGFont.m
|
|
@@ -96,11 +96,27 @@ CFDataRef CGFontCopyTableForTag(CGFontRef self, uint32_t tag) {
|
|
|
|
CGFontRef _Nullable CGFontCreateWithDataProvider(CGDataProviderRef _Nullable provider)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ if (!provider)
|
|
+ return nil;
|
|
+ return (CGFontRef)O2FontCreateWithDataProvider((O2DataProviderRef)provider);
|
|
}
|
|
|
|
CFStringRef const kCGFontVariationAxisName = CFSTR("kCGFontVariationAxisName");
|
|
CFStringRef const kCGFontVariationAxisMinValue = CFSTR("kCGFontVariationAxisMinValue");
|
|
CFStringRef const kCGFontVariationAxisDefaultValue = CFSTR("kCGFontVariationAxisDefaultValue");
|
|
CFStringRef const kCGFontVariationAxisMaxValue = CFSTR("kCGFontVariationAxisMaxValue");
|
|
+
|
|
+
|
|
+/* darling-lol M58b: Chromium's renderer lazy-binds CGFontCopyPostScriptName
|
|
+ * (the shim lacked the symbol and the GPU-composited renderer died on a
|
|
+ * lazy-bind abort after the compositor engaged). Return the font name —
|
|
+ * a PostScript name approximation is sufficient for this path. */
|
|
+CFStringRef CGFontCopyPostScriptName(CGFontRef self) {
|
|
+ /* Chromium can probe the fallback font before CoreText has produced a
|
|
+ * concrete CGFont. Apple's implementation yields a usable family name
|
|
+ * along this path; do not pass NULL into O2FontCopyFullName. The Copy
|
|
+ * contract requires a retained result. */
|
|
+ if (self == NULL)
|
|
+ return (CFStringRef) CFRetain(CFSTR("DarlingSans"));
|
|
+ return (CFStringRef)[(O2Font *)self copyPostScriptName];
|
|
+}
|
|
diff --git a/CoreGraphics/CGSConnection.m b/CoreGraphics/CGSConnection.m
|
|
index 99826d19..0b75047b 100644
|
|
--- a/CoreGraphics/CGSConnection.m
|
|
+++ b/CoreGraphics/CGSConnection.m
|
|
@@ -94,6 +94,11 @@
|
|
NSInvalidAbstractInvocation();
|
|
}
|
|
|
|
+-(NSArray<NSDictionary*>*) copyWindowDescriptions
|
|
+{
|
|
+ return [[NSArray alloc] init];
|
|
+}
|
|
+
|
|
-(CGError) destroyWindow:(CGSWindowID)winId
|
|
{
|
|
CGSWindow* win = [self windowForId: winId];
|
|
diff --git a/CoreGraphics/CGSWindow.m b/CoreGraphics/CGSWindow.m
|
|
index e1da261d..f050b6e8 100644
|
|
--- a/CoreGraphics/CGSWindow.m
|
|
+++ b/CoreGraphics/CGSWindow.m
|
|
@@ -101,6 +101,47 @@ CGError CGSSetWindowTitle(CGSConnectionID cid, CGSWindowID wid,
|
|
return CGSSetWindowProperty(cid, wid, kCGSWindowTitle, title);
|
|
}
|
|
|
|
+// Windows of every client of the display server, front to back. The option and the reference
|
|
+// window select the same subsets as on macOS.
|
|
+CFArrayRef __nullable CGWindowListCopyWindowInfo(CGWindowListOption option, CGWindowID relativeToWindow)
|
|
+{
|
|
+ CGSConnection* connection = _CGSConnectionForID(_CGSDefaultConnection());
|
|
+ NSArray<NSDictionary*>* all = connection ? [connection copyWindowDescriptions] : [[NSArray alloc] init];
|
|
+ NSMutableArray<NSDictionary*>* selected = [[NSMutableArray alloc] init];
|
|
+
|
|
+ NSInteger referenceIndex = NSNotFound;
|
|
+ for (NSUInteger i = 0; i < [all count]; i++)
|
|
+ {
|
|
+ if ([all[i][(NSString*) kCGWindowNumber] unsignedIntValue] == relativeToWindow)
|
|
+ referenceIndex = i;
|
|
+ }
|
|
+
|
|
+ for (NSUInteger i = 0; i < [all count]; i++)
|
|
+ {
|
|
+ NSDictionary* window = all[i];
|
|
+ BOOL isReference = (NSInteger) i == referenceIndex;
|
|
+ BOOL haveReference = referenceIndex != NSNotFound;
|
|
+ BOOL include = YES;
|
|
+
|
|
+ if ((option & kCGWindowListOptionOnScreenOnly) && ![window[(NSString*) kCGWindowIsOnscreen] boolValue])
|
|
+ continue;
|
|
+
|
|
+ // The list is front to back: earlier entries are above the reference window
|
|
+ if (option & kCGWindowListOptionOnScreenAboveWindow)
|
|
+ include = (haveReference && (NSInteger) i < referenceIndex) || ((option & kCGWindowListOptionIncludingWindow) && isReference);
|
|
+ else if (option & kCGWindowListOptionOnScreenBelowWindow)
|
|
+ include = (haveReference && (NSInteger) i > referenceIndex) || ((option & kCGWindowListOptionIncludingWindow) && isReference);
|
|
+ else if (option & kCGWindowListOptionIncludingWindow)
|
|
+ include = isReference;
|
|
+
|
|
+ if (include)
|
|
+ [selected addObject: window];
|
|
+ }
|
|
+
|
|
+ [all release];
|
|
+ return (CFArrayRef) selected;
|
|
+}
|
|
+
|
|
CFArrayRef __nullable CGWindowListCreate(CGWindowListOption option, CGWindowID relativeToWindow)
|
|
{
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
diff --git a/CoreGraphics/X11.backend/CGSConnectionX11.h b/CoreGraphics/X11.backend/CGSConnectionX11.h
|
|
index 5437acb5..07f7eb4f 100644
|
|
--- a/CoreGraphics/X11.backend/CGSConnectionX11.h
|
|
+++ b/CoreGraphics/X11.backend/CGSConnectionX11.h
|
|
@@ -48,6 +48,7 @@
|
|
-(CGSWindow*) newWindow:(CGSRegionRef)region;
|
|
|
|
-(void) processPendingEvents;
|
|
+-(NSArray<NSDictionary*>*) copyWindowDescriptions;
|
|
|
|
+(BOOL) isAvailable;
|
|
|
|
diff --git a/CoreGraphics/X11.backend/CGSConnectionX11.m b/CoreGraphics/X11.backend/CGSConnectionX11.m
|
|
index 7af8bd93..90420b6f 100644
|
|
--- a/CoreGraphics/X11.backend/CGSConnectionX11.m
|
|
+++ b/CoreGraphics/X11.backend/CGSConnectionX11.m
|
|
@@ -18,6 +18,7 @@
|
|
*/
|
|
#import "CGSConnectionX11.h"
|
|
#import <CoreGraphics/CGSKeyboardLayout.h>
|
|
+#import <CoreGraphics/CGWindow.h>
|
|
#include "CarbonKeys.h"
|
|
#import "X11KeySymToUCS.h"
|
|
#import <Foundation/NSDebug.h>
|
|
@@ -25,6 +26,7 @@
|
|
#include <stdlib.h>
|
|
#include <stdatomic.h>
|
|
#import <X11/Xutil.h>
|
|
+#import <X11/Xatom.h>
|
|
#import <X11/extensions/Xrandr.h>
|
|
#import <X11/XKBlib.h>
|
|
#import <X11/extensions/XKBrules.h>
|
|
@@ -52,6 +54,13 @@ static void socketCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef a
|
|
|
|
-(instancetype) initWithConnectionID:(CGSConnectionID)connId
|
|
{
|
|
+ // Native EGL and the run loop may use Xlib concurrently. This connection
|
|
+ // can be opened before AppKit initializes its own display.
|
|
+ if (!XInitThreads()) {
|
|
+ NSLog(@"CGSConnectionX11: Xlib thread initialization failed");
|
|
+ [self release];
|
|
+ return nil;
|
|
+ }
|
|
_display = XOpenDisplay(NULL);
|
|
if (!_display)
|
|
_display = XOpenDisplay(":0");
|
|
@@ -122,6 +131,106 @@ static void socketCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef a
|
|
[super dealloc];
|
|
}
|
|
|
|
+// Reads a property as raw bytes; returns NULL if it is not set. Caller XFree()s the result.
|
|
+static unsigned char* windowProperty(Display* display, Window window, Atom property, Atom type, unsigned long* count)
|
|
+{
|
|
+ Atom actualType;
|
|
+ int actualFormat;
|
|
+ unsigned long bytesAfter;
|
|
+ unsigned char* data = NULL;
|
|
+
|
|
+ *count = 0;
|
|
+ if (XGetWindowProperty(display, window, property, 0, 1024, False, type, &actualType, &actualFormat,
|
|
+ count, &bytesAfter, &data) != Success || actualType == None || *count == 0)
|
|
+ {
|
|
+ if (data)
|
|
+ XFree(data);
|
|
+ *count = 0;
|
|
+ return NULL;
|
|
+ }
|
|
+ return data;
|
|
+}
|
|
+
|
|
+-(NSArray<NSDictionary*>*) copyWindowDescriptions
|
|
+{
|
|
+ NSMutableArray<NSDictionary*>* result = [[NSMutableArray alloc] init];
|
|
+
|
|
+ Window root = DefaultRootWindow(_display), rootReturn, parentReturn, *children = NULL;
|
|
+ unsigned int count = 0;
|
|
+
|
|
+ if (!XQueryTree(_display, root, &rootReturn, &parentReturn, &children, &count))
|
|
+ return result;
|
|
+
|
|
+ Atom netWmPid = XInternAtom(_display, "_NET_WM_PID", True);
|
|
+ Atom netWmName = XInternAtom(_display, "_NET_WM_NAME", True);
|
|
+ Atom utf8String = XInternAtom(_display, "UTF8_STRING", True);
|
|
+
|
|
+ // XQueryTree lists children bottom to top, CGWindowList is front to back
|
|
+ for (int i = (int) count - 1; i >= 0; i--)
|
|
+ {
|
|
+ Window window = children[i];
|
|
+ XWindowAttributes attributes;
|
|
+ unsigned long length;
|
|
+
|
|
+ if (!XGetWindowAttributes(_display, window, &attributes))
|
|
+ continue;
|
|
+
|
|
+ // Windows of clients that do not name their process (window manager helpers) are skipped
|
|
+ unsigned char* pidData = netWmPid != None ? windowProperty(_display, window, netWmPid, XA_CARDINAL, &length) : NULL;
|
|
+ if (!pidData)
|
|
+ continue;
|
|
+ long pid = *(long*) pidData;
|
|
+ XFree(pidData);
|
|
+
|
|
+ NSMutableDictionary* description = [NSMutableDictionary dictionary];
|
|
+ description[(NSString*) kCGWindowNumber] = @((long) window);
|
|
+ description[(NSString*) kCGWindowOwnerPID] = @(pid);
|
|
+ description[(NSString*) kCGWindowLayer] = @0;
|
|
+ description[(NSString*) kCGWindowAlpha] = @1.0;
|
|
+ description[(NSString*) kCGWindowIsOnscreen] = @(attributes.map_state == IsViewable);
|
|
+ description[(NSString*) kCGWindowBounds] = @{
|
|
+ @"X": @(attributes.x), @"Y": @(attributes.y),
|
|
+ @"Width": @(attributes.width), @"Height": @(attributes.height),
|
|
+ };
|
|
+
|
|
+ XClassHint classHint = {NULL, NULL};
|
|
+ if (XGetClassHint(_display, window, &classHint))
|
|
+ {
|
|
+ if (classHint.res_class)
|
|
+ description[(NSString*) kCGWindowOwnerName] = [NSString stringWithUTF8String: classHint.res_class];
|
|
+ if (classHint.res_name)
|
|
+ XFree(classHint.res_name);
|
|
+ if (classHint.res_class)
|
|
+ XFree(classHint.res_class);
|
|
+ }
|
|
+
|
|
+ unsigned char* name = netWmName != None ? windowProperty(_display, window, netWmName, utf8String, &length) : NULL;
|
|
+ if (name)
|
|
+ {
|
|
+ NSString* title = [[NSString alloc] initWithBytes: name length: length encoding: NSUTF8StringEncoding];
|
|
+ if (title)
|
|
+ description[(NSString*) kCGWindowName] = title;
|
|
+ [title release];
|
|
+ XFree(name);
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ char* legacyName = NULL;
|
|
+ if (XFetchName(_display, window, &legacyName) && legacyName)
|
|
+ {
|
|
+ description[(NSString*) kCGWindowName] = [NSString stringWithUTF8String: legacyName];
|
|
+ XFree(legacyName);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ [result addObject: description];
|
|
+ }
|
|
+
|
|
+ if (children)
|
|
+ XFree(children);
|
|
+ return result;
|
|
+}
|
|
+
|
|
-(CGSKeyboardLayout*) createKeyboardLayout
|
|
{
|
|
@synchronized (self)
|
|
diff --git a/CoreGraphics/include/CoreGraphics/CGColorSpace.h b/CoreGraphics/include/CoreGraphics/CGColorSpace.h
|
|
index 2f5674ff..0eec27b9 100644
|
|
--- a/CoreGraphics/include/CoreGraphics/CGColorSpace.h
|
|
+++ b/CoreGraphics/include/CoreGraphics/CGColorSpace.h
|
|
@@ -27,6 +27,8 @@ COREGRAPHICS_EXPORT const CFStringRef kCGColorSpaceGenericLab;
|
|
COREGRAPHICS_EXPORT const CFStringRef kCGColorSpaceACESCGLinear;
|
|
COREGRAPHICS_EXPORT const CFStringRef kCGColorSpaceITUR_709;
|
|
COREGRAPHICS_EXPORT const CFStringRef kCGColorSpaceITUR_2020;
|
|
+COREGRAPHICS_EXPORT const CFStringRef kCGColorSpaceITUR_2100_HLG;
|
|
+COREGRAPHICS_EXPORT const CFStringRef kCGColorSpaceITUR_2100_PQ;
|
|
COREGRAPHICS_EXPORT const CFStringRef kCGColorSpaceROMMRGB;
|
|
COREGRAPHICS_EXPORT const CFStringRef kCGColorSpaceDCIP3;
|
|
COREGRAPHICS_EXPORT const CFStringRef kCGColorSpaceExtendedSRGB;
|
|
diff --git a/CoreGraphics/include/CoreGraphics/CGDirectDisplay.h b/CoreGraphics/include/CoreGraphics/CGDirectDisplay.h
|
|
index dd3f5479..f17543d7 100644
|
|
--- a/CoreGraphics/include/CoreGraphics/CGDirectDisplay.h
|
|
+++ b/CoreGraphics/include/CoreGraphics/CGDirectDisplay.h
|
|
@@ -2,6 +2,8 @@
|
|
#import <CoreGraphics/CGError.h>
|
|
#import <CoreGraphics/CGGeometry.h>
|
|
#import <CoreGraphics/CoreGraphicsExport.h>
|
|
+#import <CoreGraphics/CGColorSpace.h>
|
|
+#import <CoreGraphics/CGImage.h>
|
|
#import <mach/boolean.h>
|
|
|
|
typedef uint32_t CGDirectDisplayID;
|
|
@@ -74,3 +76,10 @@ COREGRAPHICS_EXPORT CGError CGDisplaySwitchToMode(CGDirectDisplayID display,
|
|
CFDictionaryRef mode);
|
|
COREGRAPHICS_EXPORT size_t CGDisplayModeGetPixelWidth(CGDisplayModeRef mode);
|
|
COREGRAPHICS_EXPORT CGSize CGDisplayScreenSize(CGDirectDisplayID display);
|
|
+COREGRAPHICS_EXPORT bool CGDisplayIsBuiltin(CGDirectDisplayID display);
|
|
+COREGRAPHICS_EXPORT double CGDisplayRotation(CGDirectDisplayID display);
|
|
+COREGRAPHICS_EXPORT CGColorSpaceRef CGDisplayCopyColorSpace(CGDirectDisplayID display);
|
|
+COREGRAPHICS_EXPORT CGImageRef CGDisplayCreateImage(CGDirectDisplayID displayID);
|
|
+COREGRAPHICS_EXPORT bool CGDisplayUsesForceToGray(void);
|
|
+COREGRAPHICS_EXPORT bool CGPreflightScreenCaptureAccess(void);
|
|
+COREGRAPHICS_EXPORT bool CGRequestScreenCaptureAccess(void);
|
|
diff --git a/CoreGraphics/include/CoreGraphics/CGDisplayStream.h b/CoreGraphics/include/CoreGraphics/CGDisplayStream.h
|
|
index ad289284..856d5009 100644
|
|
--- a/CoreGraphics/include/CoreGraphics/CGDisplayStream.h
|
|
+++ b/CoreGraphics/include/CoreGraphics/CGDisplayStream.h
|
|
@@ -20,3 +20,7 @@
|
|
#import <CoreGraphics/CoreGraphicsExport.h>
|
|
|
|
COREGRAPHICS_EXPORT const CFStringRef kCGDisplayStreamShowCursor;
|
|
+COREGRAPHICS_EXPORT const CFStringRef kCGDisplayStreamColorSpace;
|
|
+COREGRAPHICS_EXPORT const CFStringRef kCGDisplayStreamDestinationRect;
|
|
+COREGRAPHICS_EXPORT const CFStringRef kCGDisplayStreamMinimumFrameTime;
|
|
+COREGRAPHICS_EXPORT const CFStringRef kCGDisplayStreamPreserveAspectRatio;
|
|
diff --git a/CoreGraphics/include/CoreGraphics/CGFont.h b/CoreGraphics/include/CoreGraphics/CGFont.h
|
|
index 50a92d54..cb0add22 100644
|
|
--- a/CoreGraphics/include/CoreGraphics/CGFont.h
|
|
+++ b/CoreGraphics/include/CoreGraphics/CGFont.h
|
|
@@ -36,6 +36,7 @@ COREGRAPHICS_EXPORT CGFontRef CGFontRetain(CGFontRef self);
|
|
COREGRAPHICS_EXPORT void CGFontRelease(CGFontRef self);
|
|
|
|
COREGRAPHICS_EXPORT CFStringRef CGFontCopyFullName(CGFontRef self);
|
|
+COREGRAPHICS_EXPORT CFStringRef CGFontCopyPostScriptName(CGFontRef self);
|
|
COREGRAPHICS_EXPORT int CGFontGetUnitsPerEm(CGFontRef self);
|
|
COREGRAPHICS_EXPORT int CGFontGetAscent(CGFontRef self);
|
|
COREGRAPHICS_EXPORT int CGFontGetDescent(CGFontRef self);
|
|
diff --git a/CoreGraphics/include/CoreGraphics/CGSConnection.h b/CoreGraphics/include/CoreGraphics/CGSConnection.h
|
|
index f3991816..7862bdbb 100644
|
|
--- a/CoreGraphics/include/CoreGraphics/CGSConnection.h
|
|
+++ b/CoreGraphics/include/CoreGraphics/CGSConnection.h
|
|
@@ -38,6 +38,10 @@
|
|
-(CGSWindow*) newWindow:(CGSRegionRef)region;
|
|
-(CGError) destroyWindow:(CGSWindowID)winId;
|
|
|
|
+// Top-level windows of all clients of the display server, front to back, described with the
|
|
+// kCGWindow* keys. Backends that cannot enumerate windows return an empty array.
|
|
+-(NSArray<NSDictionary*>*) copyWindowDescriptions;
|
|
+
|
|
// Mouse location in CG coordinates, i.e. relative to the upper left corner
|
|
-(CGPoint) mouseLocation;
|
|
|
|
diff --git a/CoreGraphics/include/CoreGraphics/CGWindow.h b/CoreGraphics/include/CoreGraphics/CGWindow.h
|
|
index e1112f40..3a4fa2d6 100644
|
|
--- a/CoreGraphics/include/CoreGraphics/CGWindow.h
|
|
+++ b/CoreGraphics/include/CoreGraphics/CGWindow.h
|
|
@@ -30,6 +30,7 @@ extern const CFStringRef kCGWindowAlpha;
|
|
extern const CFStringRef kCGWindowBounds;
|
|
extern const CFStringRef kCGWindowLayer;
|
|
extern const CFStringRef kCGWindowName;
|
|
+extern const CFStringRef kCGWindowNumber;
|
|
extern const CFStringRef kCGWindowIsOnscreen;
|
|
extern const CFStringRef kCGWindowOwnerName;
|
|
extern const CFStringRef kCGWindowOwnerPID;
|
|
@@ -172,6 +173,7 @@ typedef uint32_t CGWindowID;
|
|
CF_IMPLICIT_BRIDGING_ENABLED
|
|
|
|
COREGRAPHICS_EXPORT CFArrayRef CGWindowListCreate(CGWindowListOption option, CGWindowID relativeToWindow);
|
|
+COREGRAPHICS_EXPORT CFArrayRef CGWindowListCopyWindowInfo(CGWindowListOption option, CGWindowID relativeToWindow);
|
|
COREGRAPHICS_EXPORT CFArrayRef CGWindowListCreateDescriptionFromArray(CFArrayRef windowArray);
|
|
COREGRAPHICS_EXPORT CGImageRef CGWindowListCreateImageFromArray(CGRect screenBounds, CFArrayRef windowArray, CGWindowImageOption imageOption);
|
|
|
|
diff --git a/CoreText/CMakeLists.txt b/CoreText/CMakeLists.txt
|
|
index 1ec11620..af21f5b3 100644
|
|
--- a/CoreText/CMakeLists.txt
|
|
+++ b/CoreText/CMakeLists.txt
|
|
@@ -28,7 +28,6 @@ set(CoreText_sources
|
|
KTFont.m
|
|
KTFont_FT.m
|
|
constants.c
|
|
- stubs.c
|
|
)
|
|
|
|
set_source_files_properties(${CoreText_sources} LANGUAGE C)
|
|
@@ -58,4 +57,5 @@ add_framework(CoreText
|
|
Onyx2D
|
|
# native
|
|
FreeType
|
|
+ fontconfig
|
|
)
|
|
diff --git a/CoreText/CTFont.m b/CoreText/CTFont.m
|
|
index 94ddabf9..fa018d3d 100644
|
|
--- a/CoreText/CTFont.m
|
|
+++ b/CoreText/CTFont.m
|
|
@@ -20,6 +20,18 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
#import <CoreText/CTFont.h>
|
|
#import <CoreText/CoreText.h>
|
|
#import <CoreText/KTFont.h>
|
|
+#import <CoreText/CTFontDescriptor.h>
|
|
+#import <CoreGraphics/CGDataProvider.h>
|
|
+#import <CoreGraphics/CGFont.h>
|
|
+#import <Onyx2D/O2Font.h>
|
|
+
|
|
+/* NSFont uses 32-bit NSGlyph buffers; CTFont uses 16-bit CGGlyph buffers.
|
|
+ * Bridge to the underlying KTFont before calling font methods. */
|
|
+static CTFontRef normalizedFont(CTFontRef font) {
|
|
+ if (font && [(id)font respondsToSelector:@selector(_coreTextFont)])
|
|
+ return (CTFontRef)[(id)font performSelector:@selector(_coreTextFont)];
|
|
+ return font;
|
|
+}
|
|
|
|
const CFStringRef kCTFontCopyrightNameKey = CFSTR("CTFontCopyrightName");
|
|
const CFStringRef kCTFontFamilyNameKey = CFSTR("CTFontFamilyName");
|
|
@@ -60,31 +72,75 @@ const CFStringRef kCTFontFeatureTooltipTextKey = CFSTR("CTFeatureTooltipText");
|
|
|
|
CTFontRef CTFontCreateWithName(CFStringRef name, CGFloat size, const CGAffineTransform *matrix)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ if (!name)
|
|
+ return nil;
|
|
+ CGFontRef cgFont = CGFontCreateWithFontName(name);
|
|
+ if (!cgFont)
|
|
+ return nil;
|
|
+ if (size == 0)
|
|
+ size = 12;
|
|
+ KTFont *font = [[KTFont alloc] initWithFont:cgFont size:size];
|
|
+ CGFontRelease(cgFont);
|
|
+ (void)matrix;
|
|
+ return (CTFontRef)font;
|
|
}
|
|
|
|
CTFontRef CTFontCreateWithNameAndOptions(CFStringRef name, CGFloat size,
|
|
const CGAffineTransform *matrix,
|
|
CTFontOptions options)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ (void)options;
|
|
+ return CTFontCreateWithName(name, size, matrix);
|
|
}
|
|
|
|
CTFontRef CTFontCreateWithFontDescriptor(CTFontDescriptorRef descriptor, CGFloat size,
|
|
const CGAffineTransform *matrix)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ if (!descriptor)
|
|
+ return nil;
|
|
+ if (size == 0) {
|
|
+ CFTypeRef descriptorSize = CTFontDescriptorCopyAttribute(descriptor, kCTFontSizeAttribute);
|
|
+ if (descriptorSize) {
|
|
+ if (CFGetTypeID(descriptorSize) == CFNumberGetTypeID())
|
|
+ CFNumberGetValue((CFNumberRef)descriptorSize, kCFNumberCGFloatType, &size);
|
|
+ CFRelease(descriptorSize);
|
|
+ }
|
|
+ if (size == 0)
|
|
+ size = 12;
|
|
+ }
|
|
+ /* Darling path: descriptors built by CTFontManagerCreateFontDescriptorFromData
|
|
+ * carry the raw font bytes; feed them to FreeType via O2Font. */
|
|
+ CFTypeRef dataObj = CTFontDescriptorCopyAttribute(descriptor, CFSTR("DarlingFontData"));
|
|
+ if (dataObj) {
|
|
+ CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)dataObj);
|
|
+ CFRelease(dataObj);
|
|
+ if (provider) {
|
|
+ CGFontRef cgFont = CGFontCreateWithDataProvider(provider);
|
|
+ CGDataProviderRelease(provider);
|
|
+ if (cgFont) {
|
|
+ KTFont *font = [[KTFont alloc] initWithFont:cgFont size:size];
|
|
+ CGFontRelease(cgFont);
|
|
+ (void)matrix;
|
|
+ return (CTFontRef)font;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ CFStringRef name = (CFStringRef)CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute);
|
|
+ if (!name)
|
|
+ name = (CFStringRef)CTFontDescriptorCopyAttribute(descriptor, kCTFontFamilyNameAttribute);
|
|
+ if (!name)
|
|
+ return nil;
|
|
+ CTFontRef result = CTFontCreateWithName(name, size, matrix);
|
|
+ CFRelease(name);
|
|
+ return result;
|
|
}
|
|
|
|
CTFontRef CTFontCreateWithFontDescriptorAndOptions(CTFontDescriptorRef descriptor, CGFloat size,
|
|
const CGAffineTransform *matrix,
|
|
CTFontOptions options)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ (void)options;
|
|
+ return CTFontCreateWithFontDescriptor(descriptor, size, matrix);
|
|
}
|
|
|
|
CTFontRef CTFontCreateUIFontForLanguage(CTFontUIFontType uiFontType,
|
|
@@ -99,8 +155,27 @@ CTFontRef CTFontCreateCopyWithAttributes(CTFontRef font, CGFloat size,
|
|
const CGAffineTransform *matrix,
|
|
CTFontDescriptorRef attributes)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ font = normalizedFont(font);
|
|
+ if (!font)
|
|
+ return nil;
|
|
+ if (attributes) {
|
|
+ CFTypeRef name = CTFontDescriptorCopyAttribute(attributes, kCTFontNameAttribute);
|
|
+ if (name) {
|
|
+ CTFontRef result = CTFontCreateWithName((CFStringRef)name, size, matrix);
|
|
+ CFRelease(name);
|
|
+ if (result)
|
|
+ return result;
|
|
+ }
|
|
+ }
|
|
+ CGFontRef cgFont = [(KTFont *)font copyGraphicsFont];
|
|
+ if (!cgFont)
|
|
+ return nil;
|
|
+ if (size == 0)
|
|
+ size = [(KTFont *)font pointSize];
|
|
+ CTFontRef result = (CTFontRef)[[KTFont alloc] initWithFont:cgFont size:size];
|
|
+ CGFontRelease(cgFont);
|
|
+ (void)matrix;
|
|
+ return result;
|
|
}
|
|
|
|
CTFontRef CTFontCreateCopyWithSymbolicTraits(CTFontRef font, CGFloat size,
|
|
@@ -108,6 +183,7 @@ CTFontRef CTFontCreateCopyWithSymbolicTraits(CTFontRef font, CGFloat size,
|
|
CTFontSymbolicTraits symTraitValue,
|
|
CTFontSymbolicTraits symTraitMask)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return nil;
|
|
}
|
|
@@ -115,12 +191,14 @@ CTFontRef CTFontCreateCopyWithSymbolicTraits(CTFontRef font, CGFloat size,
|
|
CTFontRef CTFontCreateCopyWithFamily(CTFontRef font, CGFloat size,
|
|
const CGAffineTransform *matrix, CFStringRef family)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return nil;
|
|
}
|
|
|
|
CTFontRef CTFontCreateForString(CTFontRef currentFont, CFStringRef string, CFRange range)
|
|
{
|
|
+ currentFont = normalizedFont(currentFont);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return nil;
|
|
}
|
|
@@ -128,157 +206,275 @@ CTFontRef CTFontCreateForString(CTFontRef currentFont, CFStringRef string, CFRan
|
|
CTFontRef CTFontCreateForStringWithLanguage(CTFontRef currentFont, CFStringRef string,
|
|
CFRange range, CFStringRef language)
|
|
{
|
|
+ currentFont = normalizedFont(currentFont);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return nil;
|
|
}
|
|
|
|
CTFontDescriptorRef CTFontCopyFontDescriptor(CTFontRef font)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ font = normalizedFont(font);
|
|
+ if (!font)
|
|
+ return nil;
|
|
+ CFStringRef name = CTFontCopyPostScriptName(font);
|
|
+ CFDictionaryRef traits = CTFontCopyTraits(font);
|
|
+ CGFloat size = CTFontGetSize(font);
|
|
+ CFNumberRef sizeValue = CFNumberCreate(NULL, kCFNumberCGFloatType, &size);
|
|
+ if (!name || !traits || !sizeValue) {
|
|
+ if (name) CFRelease(name);
|
|
+ if (traits) CFRelease(traits);
|
|
+ if (sizeValue) CFRelease(sizeValue);
|
|
+ return nil;
|
|
+ }
|
|
+ const void *keys[] = {kCTFontNameAttribute, kCTFontSizeAttribute, kCTFontTraitsAttribute};
|
|
+ const void *values[] = {name, sizeValue, traits};
|
|
+ CFDictionaryRef attributes = CFDictionaryCreate(NULL, keys, values, 3,
|
|
+ &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
|
+ CTFontDescriptorRef result = CTFontDescriptorCreateWithAttributes(attributes);
|
|
+ CFRelease(attributes);
|
|
+ CFRelease(name);
|
|
+ CFRelease(sizeValue);
|
|
+ CFRelease(traits);
|
|
+ return result;
|
|
}
|
|
|
|
CFTypeRef CTFontCopyAttribute(CTFontRef font, CFStringRef attribute)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ font = normalizedFont(font);
|
|
+ CTFontDescriptorRef descriptor = CTFontCopyFontDescriptor(font);
|
|
+ if (!descriptor)
|
|
+ return nil;
|
|
+ CFTypeRef value = CTFontDescriptorCopyAttribute(descriptor, attribute);
|
|
+ CFRelease(descriptor);
|
|
+ return value;
|
|
}
|
|
|
|
CGFloat CTFontGetSize(CTFontRef self) {
|
|
+ self = normalizedFont(self);
|
|
return [self pointSize];
|
|
}
|
|
|
|
CGAffineTransform CTFontGetMatrix(CTFontRef font)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return CGAffineTransformIdentity;
|
|
}
|
|
|
|
+static CTFontSymbolicTraits fontTraits(CTFontRef font, CGFloat *weight, CGFloat *width)
|
|
+{
|
|
+ static const CGFloat weights[] = {-0.8, -0.6, -0.4, 0, 0.23, 0.3, 0.4, 0.56, 0.62};
|
|
+ unsigned int weightClass = 400, widthClass = 5;
|
|
+ CTFontSymbolicTraits traits = 0;
|
|
+ CGFontRef graphicsFont = CTFontCopyGraphicsFont(font, NULL);
|
|
+ if (graphicsFont) {
|
|
+ CFDataRef table = CGFontCopyTableForTag(graphicsFont, 0x4f532f32 /* OS/2 */);
|
|
+ if (table && CFDataGetLength(table) >= 8) {
|
|
+ const UInt8 *bytes = CFDataGetBytePtr(table);
|
|
+ weightClass = ((unsigned int)bytes[4] << 8) | bytes[5];
|
|
+ widthClass = ((unsigned int)bytes[6] << 8) | bytes[7];
|
|
+ }
|
|
+ if (table) CFRelease(table);
|
|
+ table = CGFontCopyTableForTag(graphicsFont, 0x706f7374 /* post */);
|
|
+ if (table && CFDataGetLength(table) >= 16) {
|
|
+ const UInt8 *bytes = CFDataGetBytePtr(table);
|
|
+ if (bytes[12] || bytes[13] || bytes[14] || bytes[15])
|
|
+ traits |= kCTFontTraitMonoSpace;
|
|
+ }
|
|
+ if (table) CFRelease(table);
|
|
+ if (CGFontGetItalicAngle(graphicsFont) != 0)
|
|
+ traits |= kCTFontTraitItalic;
|
|
+ CGFontRelease(graphicsFont);
|
|
+ }
|
|
+ if (weightClass < 100) weightClass = 100;
|
|
+ if (weightClass > 900) weightClass = 900;
|
|
+ unsigned int index = weightClass / 100 - 1;
|
|
+ *weight = weights[index];
|
|
+ if (index < 8)
|
|
+ *weight += (weights[index + 1] - weights[index]) * (weightClass % 100) / 100.0;
|
|
+ if (widthClass < 1 || widthClass > 9) widthClass = 5;
|
|
+ *width = ((CGFloat)widthClass - 5) / 4;
|
|
+ if (weightClass >= 700) traits |= kCTFontTraitBold;
|
|
+ if (widthClass < 5) traits |= kCTFontTraitCondensed;
|
|
+ if (widthClass > 5) traits |= kCTFontTraitExpanded;
|
|
+ return traits;
|
|
+}
|
|
+
|
|
CTFontSymbolicTraits CTFontGetSymbolicTraits(CTFontRef font)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return kCTFontTraitItalic;
|
|
+ font = normalizedFont(font);
|
|
+ CGFloat weight, width;
|
|
+ return font ? fontTraits(font, &weight, &width) : 0;
|
|
}
|
|
|
|
CFDictionaryRef CTFontCopyTraits(CTFontRef font)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ font = normalizedFont(font);
|
|
+ if (!font) return nil;
|
|
+ CGFloat weight, width, slant = 0;
|
|
+ SInt32 symbolic = fontTraits(font, &weight, &width);
|
|
+ if (symbolic & kCTFontTraitItalic) slant = 0.2;
|
|
+ const void *keys[] = {kCTFontSymbolicTrait, kCTFontWeightTrait, kCTFontWidthTrait, kCTFontSlantTrait};
|
|
+ CFNumberRef values[] = {
|
|
+ CFNumberCreate(NULL, kCFNumberSInt32Type, &symbolic),
|
|
+ CFNumberCreate(NULL, kCFNumberCGFloatType, &weight),
|
|
+ CFNumberCreate(NULL, kCFNumberCGFloatType, &width),
|
|
+ CFNumberCreate(NULL, kCFNumberCGFloatType, &slant)
|
|
+ };
|
|
+ CFDictionaryRef result = CFDictionaryCreate(NULL, keys, (const void **)values, 4,
|
|
+ &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
|
+ for (int i = 0; i < 4; ++i) CFRelease(values[i]);
|
|
+ return result;
|
|
}
|
|
|
|
CFArrayRef CTFontCopyDefaultCascadeListForLanguages(CTFontRef font, CFArrayRef languagePrefList)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return nil;
|
|
}
|
|
|
|
CFStringRef CTFontCopyPostScriptName(CTFontRef font)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ font = normalizedFont(font);
|
|
+ CGFontRef graphicsFont = CTFontCopyGraphicsFont(font, NULL);
|
|
+ if (!graphicsFont) return nil;
|
|
+ CFStringRef name = CGFontCopyPostScriptName(graphicsFont);
|
|
+ CGFontRelease(graphicsFont);
|
|
+ return name;
|
|
}
|
|
|
|
CFStringRef CTFontCopyFamilyName(CTFontRef font)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ font = normalizedFont(font);
|
|
+ CGFontRef graphicsFont = CTFontCopyGraphicsFont(font, NULL);
|
|
+ if (!graphicsFont) return nil;
|
|
+ CFStringRef name = (CFStringRef)[(O2Font *)graphicsFont copyFamilyName];
|
|
+ CGFontRelease(graphicsFont);
|
|
+ return name;
|
|
}
|
|
|
|
CFStringRef CTFontCopyFullName(CTFontRef self) {
|
|
+ self = normalizedFont(self);
|
|
return [self copyName];
|
|
}
|
|
|
|
CFStringRef CTFontCopyDisplayName(CTFontRef font)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ font = normalizedFont(font);
|
|
+ return CTFontCopyFullName(font);
|
|
}
|
|
|
|
CFStringRef _Nullable CTFontCopyName(CTFontRef font, CFStringRef nameKey)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
+ font = normalizedFont(font);
|
|
+ if (!font || !nameKey) return nil;
|
|
+ if (CFEqual(nameKey, kCTFontPostScriptNameKey)) return CTFontCopyPostScriptName(font);
|
|
+ if (CFEqual(nameKey, kCTFontFamilyNameKey)) return CTFontCopyFamilyName(font);
|
|
+ if (CFEqual(nameKey, kCTFontFullNameKey)) return CTFontCopyFullName(font);
|
|
return nil;
|
|
}
|
|
|
|
CFStringRef CTFontCopyLocalizedName(CTFontRef font, CFStringRef nameKey,
|
|
CFStringRef _Nullable *actualLanguage)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return nil;
|
|
}
|
|
|
|
CFCharacterSetRef CTFontCopyCharacterSet(CTFontRef font)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return nil;
|
|
}
|
|
|
|
CFStringEncoding CTFontGetStringEncoding(CTFontRef font)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return CFStringGetSystemEncoding();
|
|
}
|
|
|
|
CFArrayRef CTFontCopySupportedLanguages(CTFontRef font)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return nil;
|
|
}
|
|
|
|
CGFloat CTFontGetAscent(CTFontRef self) {
|
|
+ self = normalizedFont(self);
|
|
return [self ascender];
|
|
}
|
|
|
|
CGFloat CTFontGetDescent(CTFontRef self) {
|
|
+ self = normalizedFont(self);
|
|
return [self descender];
|
|
}
|
|
|
|
CGFloat CTFontGetLeading(CTFontRef self) {
|
|
+ self = normalizedFont(self);
|
|
return [self leading];
|
|
}
|
|
|
|
unsigned int CTFontGetUnitsPerEm(CTFontRef font)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return 0;
|
|
+ font = normalizedFont(font);
|
|
+ CGFontRef graphicsFont = CTFontCopyGraphicsFont(font, NULL);
|
|
+ if (!graphicsFont)
|
|
+ return 0;
|
|
+ unsigned int units = CGFontGetUnitsPerEm(graphicsFont);
|
|
+ CGFontRelease(graphicsFont);
|
|
+ return units;
|
|
}
|
|
|
|
CFIndex CTFontGetGlyphCount(CTFontRef font) {
|
|
+ font = normalizedFont(font);
|
|
return [font numberOfGlyphs];
|
|
}
|
|
|
|
CGRect CTFontGetBoundingBox(CTFontRef self) {
|
|
+ self = normalizedFont(self);
|
|
return [self boundingRect];
|
|
}
|
|
|
|
CGFloat CTFontGetUnderlinePosition(CTFontRef self) {
|
|
+ self = normalizedFont(self);
|
|
return [self underlinePosition];
|
|
}
|
|
|
|
CGFloat CTFontGetUnderlineThickness(CTFontRef self) {
|
|
+ self = normalizedFont(self);
|
|
return [self underlineThickness];
|
|
}
|
|
|
|
CGFloat CTFontGetSlantAngle(CTFontRef self) {
|
|
+ self = normalizedFont(self);
|
|
return [self italicAngle];
|
|
}
|
|
|
|
CGFloat CTFontGetCapHeight(CTFontRef self) {
|
|
+ self = normalizedFont(self);
|
|
return [self capHeight];
|
|
}
|
|
|
|
CGFloat CTFontGetXHeight(CTFontRef self) {
|
|
+ self = normalizedFont(self);
|
|
return [self xHeight];
|
|
}
|
|
|
|
CGPathRef CTFontCreatePathForGlyph(CTFontRef self, CGGlyph glyph,
|
|
CGAffineTransform *xform)
|
|
{
|
|
+ self = normalizedFont(self);
|
|
return (CGPathRef) [self createPathForGlyph: glyph transform: xform];
|
|
}
|
|
|
|
CGGlyph CTFontGetGlyphWithName(CTFontRef font, CFStringRef glyphName)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return CGNullGlyph;
|
|
}
|
|
@@ -287,20 +483,44 @@ CGRect CTFontGetBoundingRectsForGlyphs(CTFontRef font, CTFontOrientation orienta
|
|
const CGGlyph *glyphs, CGRect *boundingRects,
|
|
CFIndex count)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return CGRectMake(0, 0, 0, 0);
|
|
+ font = normalizedFont(font);
|
|
+ if (!font || !glyphs || count <= 0)
|
|
+ return CGRectZero;
|
|
+ CGFontRef graphicsFont = CTFontCopyGraphicsFont(font, NULL);
|
|
+ if (!graphicsFont)
|
|
+ return CGRectZero;
|
|
+ int units = CGFontGetUnitsPerEm(graphicsFont);
|
|
+ CGFloat scale = units > 0 ? CTFontGetSize(font) / units : 0;
|
|
+ CGRect total = CGRectNull;
|
|
+ for (CFIndex i = 0; i < count; ++i) {
|
|
+ O2Rect bounds = [(O2Font *)graphicsFont boundingRectForGlyph:glyphs[i]];
|
|
+ CGRect rect = CGRectMake(bounds.origin.x * scale, bounds.origin.y * scale,
|
|
+ bounds.size.width * scale, bounds.size.height * scale);
|
|
+ if (boundingRects) boundingRects[i] = rect;
|
|
+ total = CGRectUnion(total, rect);
|
|
+ }
|
|
+ CGFontRelease(graphicsFont);
|
|
+ return total;
|
|
}
|
|
|
|
double CTFontGetAdvancesForGlyphs(CTFontRef font, CTFontOrientation orientation,
|
|
const CGGlyph *glyphs, CGSize *advances,
|
|
CFIndex count)
|
|
{
|
|
- [font getAdvancements: advances forGlyphs: glyphs count: count];
|
|
-
|
|
- double sum;
|
|
-
|
|
- for (int i = 0; i < count; i++) {
|
|
- sum += advances[i].width;
|
|
+ font = normalizedFont(font);
|
|
+ if (!font || !glyphs || count <= 0)
|
|
+ return 0;
|
|
+ if (advances)
|
|
+ [(KTFont *)font getAdvancements: advances forGlyphs: glyphs count: count];
|
|
+ double sum = 0;
|
|
+
|
|
+ for (CFIndex i = 0; i < count; i++) {
|
|
+ CGSize advance;
|
|
+ if (advances)
|
|
+ advance = advances[i];
|
|
+ else
|
|
+ [(KTFont *)font getAdvancements: &advance forGlyphs: &glyphs[i] count: 1];
|
|
+ sum += advance.width;
|
|
}
|
|
|
|
return sum;
|
|
@@ -310,6 +530,7 @@ CGRect CTFontGetOpticalBoundsForGlyphs(CTFontRef font, const CGGlyph *glyphs,
|
|
CGRect *boundingRects, CFIndex count,
|
|
CFOptionFlags options)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return CGRectMake(0, 0, 0, 0);
|
|
}
|
|
@@ -317,29 +538,34 @@ CGRect CTFontGetOpticalBoundsForGlyphs(CTFontRef font, const CGGlyph *glyphs,
|
|
void CTFontGetVerticalTranslationsForGlyphs(CTFontRef font, const CGGlyph *glyphs,
|
|
CGSize *translations, CFIndex count)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
}
|
|
|
|
CFArrayRef CTFontCopyVariationAxes(CTFontRef font)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return nil;
|
|
}
|
|
|
|
CFDictionaryRef CTFontCopyVariation(CTFontRef font)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return nil;
|
|
}
|
|
|
|
CFArrayRef CTFontCopyFeatures(CTFontRef font)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return nil;
|
|
}
|
|
|
|
CFArrayRef CTFontCopyFeatureSettings(CTFontRef font)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return nil;
|
|
}
|
|
@@ -347,6 +573,7 @@ CFArrayRef CTFontCopyFeatureSettings(CTFontRef font)
|
|
bool CTFontGetGlyphsForCharacters(CTFontRef font, const UniChar *characters,
|
|
CGGlyph *glyphs, CFIndex count)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
[font getGlyphs: glyphs forCharacters: characters length: count];
|
|
// FIXME: change getGlyphs: to return a BOOL
|
|
return YES;
|
|
@@ -355,20 +582,36 @@ bool CTFontGetGlyphsForCharacters(CTFontRef font, const UniChar *characters,
|
|
void CTFontDrawGlyphs(CTFontRef font, const CGGlyph *glyphs, const CGPoint *positions,
|
|
size_t count, CGContextRef context)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
+ font = normalizedFont(font);
|
|
+ if (!font || !context || !glyphs || !positions || !count)
|
|
+ return;
|
|
+ CGFontRef graphicsFont = CTFontCopyGraphicsFont(font, NULL);
|
|
+ if (!graphicsFont)
|
|
+ return;
|
|
+ CGContextSaveGState(context);
|
|
+ CGContextSetFont(context, graphicsFont);
|
|
+ CGContextSetFontSize(context, CTFontGetSize(font));
|
|
+ CGContextShowGlyphsAtPositions(context, glyphs, positions, count);
|
|
+ CGContextRestoreGState(context);
|
|
+ CGFontRelease(graphicsFont);
|
|
}
|
|
|
|
CFIndex CTFontGetLigatureCaretPositions(CTFontRef font, CGGlyph glyph, CGFloat *positions,
|
|
CFIndex maxPositions)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return -1;
|
|
}
|
|
|
|
CGFontRef CTFontCopyGraphicsFont(CTFontRef font, CTFontDescriptorRef _Nullable *attributes)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ font = normalizedFont(font);
|
|
+ if (attributes)
|
|
+ *attributes = nil;
|
|
+ if (!font)
|
|
+ return nil;
|
|
+ return [(KTFont *)font copyGraphicsFont];
|
|
}
|
|
|
|
CTFontRef
|
|
@@ -381,6 +624,7 @@ CTFontCreateWithGraphicsFont(CGFontRef cgFont, CGFloat size,
|
|
|
|
ATSFontRef CTFontGetPlatformFont(CTFontRef font, CTFontDescriptorRef _Nullable *attributes)
|
|
{
|
|
+ font = normalizedFont(font);
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return 0;
|
|
}
|
|
@@ -402,14 +646,27 @@ CTFontRef CTFontCreateWithQuickdrawInstance(ConstStr255Param name, int16_t ident
|
|
|
|
CFArrayRef CTFontCopyAvailableTables(CTFontRef font, CTFontTableOptions options)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ font = normalizedFont(font);
|
|
+ CGFontRef graphicsFont = CTFontCopyGraphicsFont(font, NULL);
|
|
+ if (!graphicsFont)
|
|
+ return nil;
|
|
+ CFArrayRef result = (CFArrayRef)[(O2Font *)graphicsFont copyTableTags];
|
|
+ CGFontRelease(graphicsFont);
|
|
+ (void)options;
|
|
+ return result;
|
|
}
|
|
|
|
CFDataRef CTFontCopyTable(CTFontRef font, CTFontTableTag table, CTFontTableOptions options)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ font = normalizedFont(font);
|
|
+ if (!font)
|
|
+ return nil;
|
|
+ CGFontRef graphicsFont = CTFontCopyGraphicsFont(font, NULL);
|
|
+ if (!graphicsFont)
|
|
+ return nil;
|
|
+ CFDataRef data = CGFontCopyTableForTag(graphicsFont, table);
|
|
+ CGFontRelease(graphicsFont);
|
|
+ return data;
|
|
}
|
|
|
|
CFTypeID CTFontGetTypeID(void)
|
|
diff --git a/CoreText/CTFontDescriptor.m b/CoreText/CTFontDescriptor.m
|
|
index 8866ce59..62dcf226 100644
|
|
--- a/CoreText/CTFontDescriptor.m
|
|
+++ b/CoreText/CTFontDescriptor.m
|
|
@@ -1,6 +1,7 @@
|
|
#import <CoreText/CTFontDescriptor.h>
|
|
|
|
-#include <stdio.h>
|
|
+#import <Foundation/Foundation.h>
|
|
+#import <Onyx2D/O2Font_freetype.h>
|
|
|
|
const CFStringRef kCTFontURLAttribute = CFSTR("NSCTFontFileURLAttribute");
|
|
const CFStringRef kCTFontNameAttribute = CFSTR("NSFontNameAttribute");
|
|
@@ -24,9 +25,103 @@ const CFStringRef kCTFontEnabledAttribute = CFSTR("NSCTFontEnabledAttribute");
|
|
const CFStringRef kCTFontFormatAttribute = CFSTR("NSCTFontFormatAttribute");
|
|
const CFStringRef kCTFontRegistrationScopeAttribute = CFSTR("NSCTFontRegistrationScopeAttribute");
|
|
const CFStringRef kCTFontPriorityAttribute = CFSTR("NSCTFontPriorityAttribute");
|
|
+const CFStringRef kCTFontCollectionRemoveDuplicatesOption = CFSTR("kCTFontCollectionRemoveDuplicatesOption");
|
|
+
|
|
+CTFontDescriptorRef CTFontDescriptorCreateWithAttributes(CFDictionaryRef attributes)
|
|
+{
|
|
+ if (!attributes)
|
|
+ return NULL;
|
|
+ /* Back descriptors directly by an immutable dictionary copy. */
|
|
+ return (CTFontDescriptorRef)CFDictionaryCreateCopy(NULL, attributes);
|
|
+}
|
|
|
|
CFTypeRef CTFontDescriptorCopyAttribute(CTFontDescriptorRef descriptor, CFStringRef attribute)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ CFTypeRef value;
|
|
+ if (!descriptor || !attribute)
|
|
+ return nil;
|
|
+ value = CFDictionaryGetValue((CFDictionaryRef)descriptor, attribute);
|
|
+ if (value)
|
|
+ CFRetain(value);
|
|
+ return value;
|
|
+}
|
|
+
|
|
+/* Enumerate actual installed faces, rather than returning the query itself.
|
|
+ * FcFontList does not substitute an unrelated font for a missing family. */
|
|
+CFArrayRef CTFontDescriptorCreateMatchingFontDescriptors(CTFontDescriptorRef descriptor,
|
|
+ CFSetRef mandatoryAttributes)
|
|
+{
|
|
+ if (!descriptor)
|
|
+ return NULL;
|
|
+ NSDictionary *query = (NSDictionary *)descriptor;
|
|
+ FcPattern *pattern = FcPatternCreate();
|
|
+ const CFStringRef keys[] = { kCTFontFamilyNameAttribute, kCTFontNameAttribute,
|
|
+ kCTFontStyleNameAttribute, kCTFontDisplayNameAttribute };
|
|
+ const char *properties[] = { FC_FAMILY, FC_POSTSCRIPT_NAME, FC_STYLE, FC_FULLNAME };
|
|
+ for (unsigned i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) {
|
|
+ id value = [query objectForKey:(id)keys[i]];
|
|
+ if ([value isKindOfClass:[NSString class]])
|
|
+ FcPatternAddString(pattern, properties[i], (const FcChar8 *)[value UTF8String]);
|
|
+ }
|
|
+ FcObjectSet *objects = FcObjectSetBuild(FC_FAMILY, FC_POSTSCRIPT_NAME,
|
|
+ FC_STYLE, FC_FULLNAME, FC_FILE, NULL);
|
|
+ FcFontSet *fonts = FcFontList(O2FontSharedFontConfig(), pattern, objects);
|
|
+ CFMutableArrayRef matchesArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
|
|
+ for (int i = 0; fonts && i < fonts->nfont; ++i) {
|
|
+ NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
|
|
+ for (unsigned j = 0; j < sizeof(keys) / sizeof(keys[0]); ++j) {
|
|
+ FcChar8 *text;
|
|
+ if (FcPatternGetString(fonts->fonts[i], properties[j], 0, &text) == FcResultMatch) {
|
|
+ NSString *value = [NSString stringWithUTF8String:(const char *)text];
|
|
+ if (value) [attributes setObject:value forKey:(id)keys[j]];
|
|
+ }
|
|
+ }
|
|
+ FcChar8 *file;
|
|
+ if (FcPatternGetString(fonts->fonts[i], FC_FILE, 0, &file) == FcResultMatch) {
|
|
+ NSURL *url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:(const char *)file]];
|
|
+ if (url) [attributes setObject:url forKey:(id)kCTFontURLAttribute];
|
|
+ }
|
|
+ /* Point size is a request, not an installed-face property. */
|
|
+ id size = [query objectForKey:(id)kCTFontSizeAttribute];
|
|
+ if (size) [attributes setObject:size forKey:(id)kCTFontSizeAttribute];
|
|
+ BOOL matches = YES;
|
|
+ for (id key in (NSSet *)mandatoryAttributes) {
|
|
+ id expected = [query objectForKey:key];
|
|
+ if (!expected || ![expected isEqual:[attributes objectForKey:key]]) {
|
|
+ matches = NO;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (matches) {
|
|
+ CTFontDescriptorRef match = CTFontDescriptorCreateWithAttributes((CFDictionaryRef)attributes);
|
|
+ CFArrayAppendValue(matchesArray, match);
|
|
+ CFRelease(match);
|
|
+ }
|
|
+ }
|
|
+ if (fonts) FcFontSetDestroy(fonts);
|
|
+ FcObjectSetDestroy(objects);
|
|
+ FcPatternDestroy(pattern);
|
|
+ return matchesArray;
|
|
+}
|
|
+
|
|
+CTFontDescriptorRef CTFontDescriptorCreateMatchingFontDescriptor(CTFontDescriptorRef descriptor,
|
|
+ CFSetRef mandatoryAttributes)
|
|
+{
|
|
+ CFArrayRef matches = CTFontDescriptorCreateMatchingFontDescriptors(descriptor, mandatoryAttributes);
|
|
+ CTFontDescriptorRef result = NULL;
|
|
+ if (matches) {
|
|
+ if (CFArrayGetCount(matches))
|
|
+ result = (CTFontDescriptorRef)CFRetain(CFArrayGetValueAtIndex(matches, 0));
|
|
+ CFRelease(matches);
|
|
+ }
|
|
+ return result;
|
|
+}
|
|
+
|
|
+CFTypeRef CTFontDescriptorCopyLocalizedAttribute(CTFontDescriptorRef descriptor,
|
|
+ CFStringRef attribute, CFStringRef *language)
|
|
+{
|
|
+ /* Fall back to the canonical name when localized font metadata is absent.
|
|
+ * A NULL language output is valid (Chromium uses it for family names). */
|
|
+ if (language) *language = NULL;
|
|
+ return CTFontDescriptorCopyAttribute(descriptor, attribute);
|
|
}
|
|
diff --git a/CoreText/CTFontManager.m b/CoreText/CTFontManager.m
|
|
index 943e1b78..049665fb 100644
|
|
--- a/CoreText/CTFontManager.m
|
|
+++ b/CoreText/CTFontManager.m
|
|
@@ -1,23 +1,43 @@
|
|
#import <CoreText/CTFontManager.h>
|
|
+#import <CoreText/CTFontDescriptor.h>
|
|
+#import <Foundation/NSString.h>
|
|
+#import <Foundation/NSDictionary.h>
|
|
+#import <Foundation/NSData.h>
|
|
+#import <Foundation/NSSet.h>
|
|
+#import <Foundation/NSArray.h>
|
|
|
|
const CFStringRef kCTFontManagerRegisteredFontsChangedNotification = CFSTR("CTFontManagerFontChangedNotification");
|
|
|
|
bool CTFontManagerRegisterGraphicsFont(CGFontRef font, CFErrorRef* error)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ /* The CGFont (typically from CGFontCreateWithDataProvider, backed by
|
|
+ * FreeType) is already fully usable; registration is bookkeeping.
|
|
+ * Report success so clients proceed to CTFont creation. */
|
|
+ (void)error;
|
|
+ return (font != NULL);
|
|
}
|
|
|
|
bool CTFontManagerUnregisterGraphicsFont(CGFontRef font, CFErrorRef *error)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ (void)error;
|
|
+ return (font != NULL);
|
|
}
|
|
|
|
CFArrayRef CTFontManagerCopyAvailableFontFamilyNames(void)
|
|
{
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return nil;
|
|
+ CFDictionaryRef empty = CFDictionaryCreate(NULL, NULL, NULL, 0,
|
|
+ &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
|
+ CTFontDescriptorRef query = CTFontDescriptorCreateWithAttributes(empty);
|
|
+ CFArrayRef descriptors = CTFontDescriptorCreateMatchingFontDescriptors(query, NULL);
|
|
+ NSMutableSet *families = [NSMutableSet set];
|
|
+ for (NSDictionary *descriptor in (NSArray *)descriptors) {
|
|
+ id family = [descriptor objectForKey:(id)kCTFontFamilyNameAttribute];
|
|
+ if (family) [families addObject:family];
|
|
+ }
|
|
+ CFRelease(query);
|
|
+ CFRelease(empty);
|
|
+ if (descriptors) CFRelease(descriptors);
|
|
+ return (CFArrayRef)[[[families allObjects] sortedArrayUsingSelector:@selector(compare:)] copy];
|
|
}
|
|
|
|
bool CTFontManagerRegisterFontsForURL(CFURLRef fontURL, CTFontManagerScope scope, CFErrorRef * error)
|
|
@@ -25,3 +45,118 @@ bool CTFontManagerRegisterFontsForURL(CFURLRef fontURL, CTFontManagerScope scope
|
|
printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
return false;
|
|
}
|
|
+
|
|
+/* Private key under which CreateFontDescriptorFromData stashes the raw
|
|
+ * font bytes for CTFontCreateWithFontDescriptor. */
|
|
+static CFStringRef DarlingFontDataKey(void) {
|
|
+ static CFStringRef key = NULL;
|
|
+ if (!key)
|
|
+ key = CFSTR("DarlingFontData");
|
|
+ return key;
|
|
+}
|
|
+
|
|
+static uint16_t sfnt_u16(const uint8_t *p) { return (uint16_t)((p[0] << 8) | p[1]); }
|
|
+static uint32_t sfnt_u32(const uint8_t *p) { return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3]; }
|
|
+
|
|
+/* Extract one name-table string (UTF-16BE for Windows platform, MacRoman
|
|
+ * for Mac platform). Returns a retained CFString or NULL. */
|
|
+static CFStringRef sfnt_copyName(const uint8_t *data, size_t len,
|
|
+ const uint8_t *storage, size_t storageLen,
|
|
+ uint16_t platform, uint16_t encoding,
|
|
+ uint16_t length, uint16_t offset) {
|
|
+ const uint8_t *s;
|
|
+ if (offset >= storageLen || length > storageLen - offset)
|
|
+ return NULL;
|
|
+ (void)len;
|
|
+ s = storage + offset;
|
|
+ if (platform == 3) {
|
|
+ /* Windows Unicode BMP: UTF-16BE */
|
|
+ return CFStringCreateWithBytes(NULL, s, length, kCFStringEncodingUTF16BE, false);
|
|
+ } else if (platform == 1 && encoding == 0) {
|
|
+ return CFStringCreateWithBytes(NULL, s, length, kCFStringEncodingMacRoman, false);
|
|
+ }
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+CTFontDescriptorRef CTFontManagerCreateFontDescriptorFromData(CFDataRef data) {
|
|
+ const uint8_t *bytes;
|
|
+ size_t len, i, j;
|
|
+ uint16_t numTables, count, stringOffset;
|
|
+ const uint8_t *nameTab = NULL;
|
|
+ size_t nameLen = 0;
|
|
+ CFStringRef psName = NULL, family = NULL, style = NULL;
|
|
+ NSMutableDictionary *dict;
|
|
+ CTFontDescriptorRef desc;
|
|
+
|
|
+ if (!data)
|
|
+ return NULL;
|
|
+ len = CFDataGetLength(data);
|
|
+ bytes = CFDataGetBytePtr(data);
|
|
+ numTables = sfnt_u16(bytes + 4);
|
|
+ if (12 + (size_t)numTables * 16 > len)
|
|
+ return NULL;
|
|
+ for (i = 0; i < numTables; i++) {
|
|
+ const uint8_t *rec = bytes + 12 + i * 16;
|
|
+ if (rec[0] == 'n' && rec[1] == 'a' && rec[2] == 'm' && rec[3] == 'e') {
|
|
+ uint32_t off = sfnt_u32(rec + 8), tlen = sfnt_u32(rec + 12);
|
|
+ if (off < len && tlen <= len - off) {
|
|
+ nameTab = bytes + off;
|
|
+ nameLen = tlen;
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (!nameTab || nameLen < 6)
|
|
+ return NULL;
|
|
+ if (sfnt_u16(nameTab) != 0)
|
|
+ return NULL;
|
|
+ count = sfnt_u16(nameTab + 2);
|
|
+ stringOffset = sfnt_u16(nameTab + 4);
|
|
+ if (6 + (size_t)count * 12 > nameLen || stringOffset > nameLen)
|
|
+ return NULL;
|
|
+ /* Pass 1: Windows Unicode (3,1,0x409); pass 2: Mac Roman (1,0,0). */
|
|
+ for (int pass = 0; pass < 2 && (!psName || !family || !style); pass++) {
|
|
+ for (j = 0; j < count; j++) {
|
|
+ const uint8_t *r = nameTab + 6 + j * 12;
|
|
+ uint16_t plat = sfnt_u16(r), enc = sfnt_u16(r + 2), lang = sfnt_u16(r + 4);
|
|
+ uint16_t nid = sfnt_u16(r + 6), slen = sfnt_u16(r + 8), soff = sfnt_u16(r + 10);
|
|
+ CFStringRef *slot = NULL;
|
|
+ if (pass == 0 && !(plat == 3 && enc == 1 && lang == 0x409))
|
|
+ continue;
|
|
+ if (pass == 1 && !(plat == 1 && enc == 0 && lang == 0))
|
|
+ continue;
|
|
+ if (nid == 6 && !psName) slot = &psName;
|
|
+ else if (nid == 1 && !family) slot = &family;
|
|
+ else if (nid == 2 && !style) slot = &style;
|
|
+ else if (nid == 4 && !psName) slot = &psName;
|
|
+ else if (nid == 16 && !family) slot = &family;
|
|
+ else if (nid == 17 && !style) slot = &style;
|
|
+ if (slot)
|
|
+ *slot = sfnt_copyName(bytes, len, nameTab + stringOffset,
|
|
+ nameLen - stringOffset, plat, enc, slen, soff);
|
|
+ }
|
|
+ }
|
|
+ if (!family && psName)
|
|
+ family = (CFStringRef)[(NSString *)psName retain];
|
|
+ if (!family) {
|
|
+ if (psName) CFRelease(psName);
|
|
+ if (style) CFRelease(style);
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ dict = [NSMutableDictionary dictionary];
|
|
+ [dict setObject:(NSString *)family forKey:(NSString *)kCTFontFamilyNameAttribute];
|
|
+ if (style)
|
|
+ [dict setObject:(NSString *)style forKey:(NSString *)kCTFontStyleNameAttribute];
|
|
+ if (psName)
|
|
+ [dict setObject:(NSString *)psName forKey:(NSString *)kCTFontNameAttribute];
|
|
+ else
|
|
+ [dict setObject:(NSString *)family forKey:(NSString *)kCTFontNameAttribute];
|
|
+ /* Stash raw bytes for CTFontCreateWithFontDescriptor. */
|
|
+ [dict setObject:(NSData *)data forKey:(NSString *)DarlingFontDataKey()];
|
|
+ desc = CTFontDescriptorCreateWithAttributes((CFDictionaryRef)dict);
|
|
+ if (psName) CFRelease(psName);
|
|
+ if (family) CFRelease(family);
|
|
+ if (style) CFRelease(style);
|
|
+ return desc;
|
|
+}
|
|
diff --git a/CoreText/KTFont.m b/CoreText/KTFont.m
|
|
index bcf32460..00f64ed8 100644
|
|
--- a/CoreText/KTFont.m
|
|
+++ b/CoreText/KTFont.m
|
|
@@ -46,6 +46,27 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
return CGFontCopyFullName(_font);
|
|
}
|
|
|
|
+- (CGFontRef) copyGraphicsFont {
|
|
+ return CGFontRetain(_font);
|
|
+}
|
|
+
|
|
+/* CTFont and NSFont share these identity operations on Quartz fonts. */
|
|
+- (id) printerFont {
|
|
+ return self;
|
|
+}
|
|
+
|
|
+- (id) screenFont {
|
|
+ return self;
|
|
+}
|
|
+
|
|
+- (NSString *) fontName {
|
|
+ return [(id)CTFontCopyPostScriptName((CTFontRef)self) autorelease];
|
|
+}
|
|
+
|
|
+- (NSString *) familyName {
|
|
+ return [(id)CTFontCopyFamilyName((CTFontRef)self) autorelease];
|
|
+}
|
|
+
|
|
- (CGFloat) pointSize {
|
|
return _size;
|
|
}
|
|
@@ -55,8 +76,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
}
|
|
|
|
- (CGRect) boundingRect {
|
|
- O2InvalidAbstractInvocation();
|
|
- return CGRectZero;
|
|
+ CGRect box = CGFontGetFontBBox(_font);
|
|
+ if (_unitsPerEm == 0)
|
|
+ return CGRectZero;
|
|
+ CGFloat scale = _size / _unitsPerEm;
|
|
+ return CGRectMake(box.origin.x * scale, box.origin.y * scale,
|
|
+ box.size.width * scale, box.size.height * scale);
|
|
}
|
|
|
|
- (CGFloat) ascender {
|
|
@@ -78,13 +103,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
}
|
|
|
|
- (CGFloat) underlineThickness {
|
|
- O2InvalidAbstractInvocation();
|
|
- return 0;
|
|
+ /* FreeType's generic CGFont bridge does not expose underline metrics.
|
|
+ * Use the conventional em-relative values expected by CoreText clients. */
|
|
+ return _size / 14.0;
|
|
}
|
|
|
|
- (CGFloat) underlinePosition {
|
|
- O2InvalidAbstractInvocation();
|
|
- return 0;
|
|
+ return -_size / 10.0;
|
|
}
|
|
|
|
- (CGFloat) italicAngle {
|
|
@@ -157,8 +182,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
- (CGPathRef) createPathForGlyph: (CGGlyph) glyph
|
|
transform: (CGAffineTransform *) xform
|
|
{
|
|
- O2InvalidAbstractInvocation();
|
|
- return nil;
|
|
+ /* Keep the CoreText Copy contract even where the generic backend cannot
|
|
+ * yet expose a native outline. An empty retained path is a valid glyph
|
|
+ * path (e.g. for whitespace) and lets clients fall back to table-based
|
|
+ * outline extraction instead of throwing an Objective-C exception. */
|
|
+ (void)glyph;
|
|
+ (void)xform;
|
|
+ return CGPathCreateMutable();
|
|
}
|
|
|
|
@end
|
|
diff --git a/CoreText/KTFont_FT.m b/CoreText/KTFont_FT.m
|
|
index 33dc18c6..78887dd8 100644
|
|
--- a/CoreText/KTFont_FT.m
|
|
+++ b/CoreText/KTFont_FT.m
|
|
@@ -60,11 +60,13 @@
|
|
length: (NSUInteger) length
|
|
{
|
|
O2Font_freetype *o2Font = (O2Font_freetype *) _font;
|
|
- FT_Face face = [o2Font face];
|
|
+ @synchronized(o2Font) {
|
|
+ FT_Face face = [o2Font face];
|
|
|
|
- int i;
|
|
- for (i = 0; i < length; i++) {
|
|
- glyphs[i] = FT_Get_Char_Index(face, characters[i]);
|
|
+ int i;
|
|
+ for (i = 0; i < length; i++) {
|
|
+ glyphs[i] = FT_Get_Char_Index(face, characters[i]);
|
|
+ }
|
|
}
|
|
}
|
|
|
|
@@ -73,15 +75,17 @@
|
|
count: (NSUInteger) count
|
|
{
|
|
O2Font_freetype *o2Font = (O2Font_freetype *) _font;
|
|
- FT_Face face = [o2Font face];
|
|
+ @synchronized(o2Font) {
|
|
+ FT_Face face = [o2Font face];
|
|
|
|
- int i;
|
|
- FT_Set_Pixel_Sizes(face, _size, _size);
|
|
+ int i;
|
|
+ FT_Set_Pixel_Sizes(face, _size, _size);
|
|
|
|
- for (i = 0; i < count; i++) {
|
|
- FT_Load_Glyph(face, glyphs[i], FT_LOAD_DEFAULT);
|
|
- advancements[i] = CGSizeMake(face->glyph->advance.x / (O2Float)(2 << 5),
|
|
- face->glyph->advance.y / (O2Float)(2 << 5));
|
|
+ for (i = 0; i < count; i++) {
|
|
+ FT_Load_Glyph(face, glyphs[i], FT_LOAD_DEFAULT);
|
|
+ advancements[i] = CGSizeMake(face->glyph->advance.x / (O2Float)(2 << 5),
|
|
+ face->glyph->advance.y / (O2Float)(2 << 5));
|
|
+ }
|
|
}
|
|
}
|
|
|
|
@@ -90,18 +94,20 @@
|
|
isNominal: (BOOL *) isNominalp
|
|
{
|
|
O2Font_freetype *o2Font = (O2Font_freetype *) _font;
|
|
- FT_Face face = [o2Font face];
|
|
+ @synchronized(o2Font) {
|
|
+ FT_Face face = [o2Font face];
|
|
|
|
- *isNominalp = YES;
|
|
+ *isNominalp = YES;
|
|
|
|
- if (!current)
|
|
- return NSZeroPoint;
|
|
+ if (!current)
|
|
+ return NSZeroPoint;
|
|
|
|
- FT_Set_Pixel_Sizes(face, _size, _size);
|
|
+ FT_Set_Pixel_Sizes(face, _size, _size);
|
|
|
|
- FT_Load_Glyph(face, current, FT_LOAD_DEFAULT);
|
|
- return NSMakePoint(face->glyph->advance.x / (O2Float)(2 << 5),
|
|
- face->glyph->advance.y / (O2Float)(2 << 5));
|
|
+ FT_Load_Glyph(face, current, FT_LOAD_DEFAULT);
|
|
+ return NSMakePoint(face->glyph->advance.x / (O2Float)(2 << 5),
|
|
+ face->glyph->advance.y / (O2Float)(2 << 5));
|
|
+ }
|
|
}
|
|
|
|
@end
|
|
diff --git a/CoreText/include/CoreText/CTFontDescriptor.h b/CoreText/include/CoreText/CTFontDescriptor.h
|
|
index b2688960..fee68509 100644
|
|
--- a/CoreText/include/CoreText/CTFontDescriptor.h
|
|
+++ b/CoreText/include/CoreText/CTFontDescriptor.h
|
|
@@ -1,4 +1,4 @@
|
|
-#import <CoreFoundation/CFString.h>
|
|
+#import <CoreFoundation/CoreFoundation.h>
|
|
#import <CoreText/CoreTextExport.h>
|
|
#import <CoreText/CTFontTraits.h>
|
|
|
|
@@ -24,6 +24,19 @@ CORETEXT_EXPORT const CFStringRef kCTFontEnabledAttribute;
|
|
CORETEXT_EXPORT const CFStringRef kCTFontFormatAttribute;
|
|
CORETEXT_EXPORT const CFStringRef kCTFontRegistrationScopeAttribute;
|
|
CORETEXT_EXPORT const CFStringRef kCTFontPriorityAttribute;
|
|
+CORETEXT_EXPORT const CFStringRef kCTFontSlantTrait;
|
|
+CORETEXT_EXPORT const CFStringRef kCTFontWeightTrait;
|
|
+CORETEXT_EXPORT const CFStringRef kCTFontWidthTrait;
|
|
+CORETEXT_EXPORT const CFStringRef kCTFontSymbolicTrait;
|
|
+CORETEXT_EXPORT const CFStringRef kCTFontVariationAxisIdentifierKey;
|
|
+CORETEXT_EXPORT const CFStringRef kCTFontVariationAxisMinimumValueKey;
|
|
+CORETEXT_EXPORT const CFStringRef kCTFontVariationAxisMaximumValueKey;
|
|
+CORETEXT_EXPORT const CFStringRef kCTFontVariationAxisDefaultValueKey;
|
|
+CORETEXT_EXPORT const CFStringRef kCTFontFamilyNameKey;
|
|
+CORETEXT_EXPORT const CFStringRef kCTFontFullNameKey;
|
|
+CORETEXT_EXPORT const CFStringRef kCTFontPostScriptNameKey;
|
|
+CORETEXT_EXPORT const CFStringRef kCTFontCollectionRemoveDuplicatesOption;
|
|
+CORETEXT_EXPORT const CFStringRef kCTFontManagerRegisteredFontsChangedNotification;
|
|
|
|
CF_IMPLICIT_BRIDGING_ENABLED
|
|
|
|
@@ -39,5 +52,10 @@ typedef enum CTFontOrientation : uint32_t {
|
|
} CTFontOrientation;
|
|
|
|
CORETEXT_EXPORT CFTypeRef CTFontDescriptorCopyAttribute(CTFontDescriptorRef descriptor, CFStringRef attribute);
|
|
+CORETEXT_EXPORT CTFontDescriptorRef CTFontDescriptorCreateWithAttributes(CFDictionaryRef attributes);
|
|
+
|
|
+CORETEXT_EXPORT CFArrayRef CTFontDescriptorCreateMatchingFontDescriptors(CTFontDescriptorRef descriptor, CFSetRef mandatoryAttributes);
|
|
+CORETEXT_EXPORT CTFontDescriptorRef CTFontDescriptorCreateMatchingFontDescriptor(CTFontDescriptorRef descriptor, CFSetRef mandatoryAttributes);
|
|
+CORETEXT_EXPORT CFTypeRef CTFontDescriptorCopyLocalizedAttribute(CTFontDescriptorRef descriptor, CFStringRef attribute, CFStringRef *language);
|
|
|
|
CF_IMPLICIT_BRIDGING_DISABLED
|
|
diff --git a/CoreText/include/CoreText/KTFont.h b/CoreText/include/CoreText/KTFont.h
|
|
index 46a5bab0..e2170bc5 100644
|
|
--- a/CoreText/include/CoreText/KTFont.h
|
|
+++ b/CoreText/include/CoreText/KTFont.h
|
|
@@ -35,6 +35,7 @@ enum { CGNullGlyph = 0x0 };
|
|
language: (NSString *) language;
|
|
|
|
- (CFStringRef) copyName;
|
|
+- (CGFontRef) copyGraphicsFont;
|
|
- (CGFloat) pointSize;
|
|
- (CGFloat) fontSize;
|
|
|
|
diff --git a/CoreText/stubs.c b/CoreText/stubs.c
|
|
deleted file mode 100644
|
|
index 1287daa5..00000000
|
|
--- a/CoreText/stubs.c
|
|
+++ /dev/null
|
|
@@ -1,6 +0,0 @@
|
|
-#include <stdio.h>
|
|
-
|
|
-void *CTFontDescriptorCreateWithAttributes(void) {
|
|
- printf("STUB %s\n", __PRETTY_FUNCTION__);
|
|
- return NULL;
|
|
-}
|
|
diff --git a/Onyx2D/O2Context_builtin_FT.m b/Onyx2D/O2Context_builtin_FT.m
|
|
index a8bf3fa6..59f3c45b 100644
|
|
--- a/Onyx2D/O2Context_builtin_FT.m
|
|
+++ b/Onyx2D/O2Context_builtin_FT.m
|
|
@@ -92,6 +92,12 @@ static void renderFreeTypeBitmap(O2Context_builtin_FT *self, O2Surface *surface,
|
|
FT_Bitmap *bitmap, NSInteger x, NSInteger y,
|
|
O2Paint *paint)
|
|
{
|
|
+ if (!surface->_read_argb8u) {
|
|
+ NSLog(@"Glyph surface unsupported: bpc=%zu bpp=%zu info=0x%x reader=%p",
|
|
+ O2ImageGetBitsPerComponent(surface), O2ImageGetBitsPerPixel(surface),
|
|
+ O2ImageGetBitmapInfo(surface), surface->_read_argb8u);
|
|
+ return;
|
|
+ }
|
|
// Size of the bitmap.
|
|
NSInteger fullWidth = bitmap->width;
|
|
NSInteger fullHeight = bitmap->rows;
|
|
@@ -123,7 +129,7 @@ static void renderFreeTypeBitmap(O2Context_builtin_FT *self, O2Surface *surface,
|
|
|
|
// We're going to advance these pointers as we move along this row.
|
|
unsigned char *coverage =
|
|
- bitmap->buffer + (curY - y) * fullWidth + (curX - x);
|
|
+ bitmap->buffer + (curY - y) * bitmap->pitch + (curX - x);
|
|
O2argb8u *src = srcBuffer;
|
|
O2argb8u *dst = dstBuffer;
|
|
|
|
@@ -190,57 +196,61 @@ static void renderFreeTypeBitmap(O2Context_builtin_FT *self, O2Surface *surface,
|
|
[self establishFontStateInDeviceIfDirty];
|
|
|
|
O2Font_freetype *font = (O2Font_freetype *) gState->_font;
|
|
- FT_Face face = [font face];
|
|
-
|
|
- int i;
|
|
- FT_Error ftError;
|
|
-
|
|
- if (face == NULL) {
|
|
- NSLog(@"face is NULL");
|
|
- O2SurfaceUnlock(_surface);
|
|
- return;
|
|
- }
|
|
+ /* FT_Load_Glyph replaces the shared face glyph slot and bitmap.
|
|
+ * Hold the same font lock used by metric queries through rasterization. */
|
|
+ @synchronized(font) {
|
|
+ FT_Face face = [font face];
|
|
+
|
|
+ int i;
|
|
+ FT_Error ftError;
|
|
+
|
|
+ if (face == NULL) {
|
|
+ NSLog(@"face is NULL");
|
|
+ O2SurfaceUnlock(_surface);
|
|
+ return;
|
|
+ }
|
|
|
|
- FT_GlyphSlot slot = face->glyph;
|
|
+ FT_GlyphSlot slot = face->glyph;
|
|
|
|
- if ((ftError =
|
|
- FT_Set_Char_Size(face, 0, fontSize.height * 64, 72.0, 72.0))) {
|
|
- NSLog(@"FT_Set_Char_Size returned %d", ftError);
|
|
- O2SurfaceUnlock(_surface);
|
|
- return;
|
|
- }
|
|
+ if ((ftError =
|
|
+ FT_Set_Char_Size(face, 0, fontSize.height * 64, 72.0, 72.0))) {
|
|
+ NSLog(@"FT_Set_Char_Size returned %d", ftError);
|
|
+ O2SurfaceUnlock(_surface);
|
|
+ return;
|
|
+ }
|
|
|
|
- for (i = 0; i < count; i++) {
|
|
+ for (i = 0; i < count; i++) {
|
|
|
|
- ftError = FT_Load_Glyph(face, glyphs[i], FT_LOAD_DEFAULT);
|
|
- if (ftError)
|
|
- continue;
|
|
+ ftError = FT_Load_Glyph(face, glyphs[i], FT_LOAD_DEFAULT);
|
|
+ if (ftError)
|
|
+ continue;
|
|
|
|
- ftError = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
|
|
- if (ftError)
|
|
- continue;
|
|
+ ftError = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
|
|
+ if (ftError)
|
|
+ continue;
|
|
|
|
- renderFreeTypeBitmap(self, _surface, &slot->bitmap,
|
|
- point.x + slot->bitmap_left,
|
|
- point.y - slot->bitmap_top, paint);
|
|
+ renderFreeTypeBitmap(self, _surface, &slot->bitmap,
|
|
+ point.x + slot->bitmap_left,
|
|
+ point.y - slot->bitmap_top, paint);
|
|
|
|
- point.x += slot->advance.x >> 6;
|
|
- }
|
|
+ point.x += slot->advance.x >> 6;
|
|
+ }
|
|
|
|
- O2PaintRelease(paint);
|
|
+ O2PaintRelease(paint);
|
|
|
|
- int glyphAdvances[count];
|
|
- O2Float unitsPerEm = O2FontGetUnitsPerEm(font);
|
|
+ int glyphAdvances[count];
|
|
+ O2Float unitsPerEm = O2FontGetUnitsPerEm(font);
|
|
|
|
- O2FontGetGlyphAdvances(font, glyphs, count, glyphAdvances);
|
|
+ O2FontGetGlyphAdvances(font, glyphs, count, glyphAdvances);
|
|
|
|
- O2Float total = 0;
|
|
+ O2Float total = 0;
|
|
|
|
- for (i = 0; i < count; i++)
|
|
- total += glyphAdvances[i];
|
|
+ for (i = 0; i < count; i++)
|
|
+ total += glyphAdvances[i];
|
|
|
|
- total = (total / O2FontGetUnitsPerEm(font)) * gState->_pointSize;
|
|
+ total = (total / O2FontGetUnitsPerEm(font)) * gState->_pointSize;
|
|
|
|
+ } /* font lock */
|
|
O2SurfaceUnlock(_surface);
|
|
}
|
|
|
|
diff --git a/Onyx2D/O2DataProvider.m b/Onyx2D/O2DataProvider.m
|
|
index 8e80cddb..386fee81 100644
|
|
--- a/Onyx2D/O2DataProvider.m
|
|
+++ b/Onyx2D/O2DataProvider.m
|
|
@@ -97,6 +97,10 @@ void O2DataProviderRelease(O2DataProviderRef self) {
|
|
}
|
|
|
|
CFDataRef O2DataProviderCopyData(O2DataProviderRef self) {
|
|
+ /* darling-lol M57: callers (CoreGraphics/QuartzCore) can pass a NULL
|
|
+ * provider; dereferencing it crashed with addr=0x10. */
|
|
+ if (self == NULL)
|
|
+ return NULL;
|
|
if (self->_data != nil)
|
|
return (CFDataRef)[self->_data copy];
|
|
else
|
|
diff --git a/Onyx2D/O2Font.m b/Onyx2D/O2Font.m
|
|
index 1f72ccef..8891602a 100644
|
|
--- a/Onyx2D/O2Font.m
|
|
+++ b/Onyx2D/O2Font.m
|
|
@@ -21,8 +21,25 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
#import <Onyx2D/O2Font.h>
|
|
|
|
@implementation O2Font
|
|
+
|
|
+- (NSString *) copyPostScriptName {
|
|
+ return [_name copy];
|
|
+}
|
|
+
|
|
+- (NSString *) copyFamilyName {
|
|
+ return [_name copy];
|
|
+}
|
|
+
|
|
+- (NSArray *) copyTableTags {
|
|
+ return [[NSArray alloc] init];
|
|
+}
|
|
+
|
|
static NSArray *_preferredFontNames = nil;
|
|
|
|
+- (O2Rect) boundingRectForGlyph: (O2Glyph) glyph {
|
|
+ return _bbox;
|
|
+}
|
|
+
|
|
+ (void) initialize {
|
|
if (self == [O2Font class]) {
|
|
// Set some decent defaut substitution fonts
|
|
diff --git a/Onyx2D/O2Font_freetype.m b/Onyx2D/O2Font_freetype.m
|
|
index e21cd7d5..1c7c737d 100644
|
|
--- a/Onyx2D/O2Font_freetype.m
|
|
+++ b/Onyx2D/O2Font_freetype.m
|
|
@@ -4,6 +4,55 @@
|
|
|
|
@implementation O2Font_freetype
|
|
|
|
+- (NSString *) copyPostScriptName {
|
|
+ const char *name = FT_Get_Postscript_Name(_face);
|
|
+ return name ? [[NSString alloc] initWithUTF8String:name] : [super copyPostScriptName];
|
|
+}
|
|
+
|
|
+- (NSString *) copyFamilyName {
|
|
+ return _face->family_name ? [[NSString alloc] initWithUTF8String:_face->family_name] : [super copyFamilyName];
|
|
+}
|
|
+
|
|
+- (NSArray *) copyTableTags {
|
|
+ NSMutableArray *tags = [[NSMutableArray alloc] init];
|
|
+ @synchronized(self) {
|
|
+ for (FT_UInt index = 0; ; ++index) {
|
|
+ FT_ULong tag, length;
|
|
+ if (FT_Sfnt_Table_Info(_face, index, &tag, &length) != 0)
|
|
+ break;
|
|
+ [tags addObject:[NSNumber numberWithUnsignedLong:tag]];
|
|
+ }
|
|
+ }
|
|
+ return tags;
|
|
+}
|
|
+
|
|
+
|
|
+- (O2Rect) boundingRectForGlyph: (O2Glyph) glyph {
|
|
+ @synchronized(self) {
|
|
+ if (FT_Load_Glyph(_face, glyph, FT_LOAD_NO_SCALE | FT_LOAD_IGNORE_TRANSFORM) != 0)
|
|
+ return O2RectMake(0, 0, 0, 0);
|
|
+ FT_Glyph_Metrics metrics = _face->glyph->metrics;
|
|
+ return O2RectMake(metrics.horiBearingX, metrics.horiBearingY - metrics.height,
|
|
+ metrics.width, metrics.height);
|
|
+ }
|
|
+}
|
|
+
|
|
+- (NSData *) copyTableForTag: (uint32_t) tag {
|
|
+ FT_ULong length = 0;
|
|
+ if (FT_Load_Sfnt_Table(_face, tag, 0, NULL, &length) != 0 || length == 0)
|
|
+ return nil;
|
|
+ unsigned char *bytes = malloc(length);
|
|
+ if (!bytes)
|
|
+ return nil;
|
|
+ if (FT_Load_Sfnt_Table(_face, tag, 0, bytes, &length) != 0) {
|
|
+ free(bytes);
|
|
+ return nil;
|
|
+ }
|
|
+ NSData *result = [[NSData alloc] initWithBytes:bytes length:length];
|
|
+ free(bytes);
|
|
+ return result;
|
|
+}
|
|
+
|
|
#ifdef DARLING
|
|
|
|
O2FontRef O2FontCreateWithFontName_platform(NSString *name) {
|
|
@@ -208,6 +257,7 @@ FT_Face O2FontFreeTypeFace(O2Font_freetype *self) {
|
|
}
|
|
|
|
- (void) fetchAdvances {
|
|
+ @synchronized(self) {
|
|
FT_Set_Char_Size(_face, 0, _unitsPerEm * 64, 72, 72);
|
|
|
|
_advances = NSZoneMalloc(NULL, sizeof(NSInteger) * _numberOfGlyphs);
|
|
@@ -217,6 +267,7 @@ FT_Face O2FontFreeTypeFace(O2Font_freetype *self) {
|
|
|
|
_advances[glyph] = _face->glyph->advance.x / (O2Float)(2 << 5);
|
|
}
|
|
+ }
|
|
}
|
|
|
|
- (O2Glyph) glyphWithGlyphName: (NSString *) name {
|
|
diff --git a/Onyx2D/O2Surface.m b/Onyx2D/O2Surface.m
|
|
index e96a03a3..d1f0e087 100644
|
|
--- a/Onyx2D/O2Surface.m
|
|
+++ b/Onyx2D/O2Surface.m
|
|
@@ -294,6 +294,18 @@ static void O2SurfaceWrite_argb8u_to_BGRA8888(O2Surface *self, int x, int y,
|
|
}
|
|
}
|
|
|
|
+static void O2SurfaceWrite_argb8u_to_XRGB8888(O2Surface *self, int x, int y,
|
|
+ O2argb8u *span, int length)
|
|
+{
|
|
+ uint8_t *scanline = self->_pixelBytes + y * self->_bytesPerRow + x * 4;
|
|
+ for (int i = 0; i < length; ++i) {
|
|
+ *scanline++ = 255;
|
|
+ *scanline++ = span[i].r;
|
|
+ *scanline++ = span[i].g;
|
|
+ *scanline++ = span[i].b;
|
|
+ }
|
|
+}
|
|
+
|
|
static void O2SurfaceWrite_argb32f_to_RGBA4444(O2Surface *self, int x, int y,
|
|
O2argb32f *span, int length)
|
|
{
|
|
@@ -424,6 +436,7 @@ static BOOL initFunctionsForParameters(O2Surface *self, size_t bitsPerComponent,
|
|
case kO2ImageAlphaNone:
|
|
break;
|
|
|
|
+ case kO2ImageAlphaNoneSkipLast:
|
|
case kO2ImageAlphaLast:
|
|
case kO2ImageAlphaPremultipliedLast:
|
|
switch (bitmapInfo & kO2BitmapByteOrderMask) {
|
|
@@ -457,10 +470,18 @@ static BOOL initFunctionsForParameters(O2Surface *self, size_t bitsPerComponent,
|
|
case kO2ImageAlphaFirst:
|
|
break;
|
|
|
|
- case kO2ImageAlphaNoneSkipLast:
|
|
- break;
|
|
-
|
|
case kO2ImageAlphaNoneSkipFirst:
|
|
+ switch (bitmapInfo & kO2BitmapByteOrderMask) {
|
|
+ case kO2BitmapByteOrderDefault:
|
|
+ case kO2BitmapByteOrder16Little:
|
|
+ case kO2BitmapByteOrder32Little:
|
|
+ self->_writeargb8u = O2SurfaceWrite_argb8u_to_BGRA8888;
|
|
+ return YES;
|
|
+ case kO2BitmapByteOrder16Big:
|
|
+ case kO2BitmapByteOrder32Big:
|
|
+ self->_writeargb8u = O2SurfaceWrite_argb8u_to_XRGB8888;
|
|
+ return YES;
|
|
+ }
|
|
break;
|
|
}
|
|
} else if ([colorSpace type] == kO2ColorSpaceModelCMYK) {
|
|
diff --git a/Onyx2D/include/Onyx2D/O2Font.h b/Onyx2D/include/Onyx2D/O2Font.h
|
|
index c2e06958..197aa09c 100644
|
|
--- a/Onyx2D/include/Onyx2D/O2Font.h
|
|
+++ b/Onyx2D/include/Onyx2D/O2Font.h
|
|
@@ -81,7 +81,11 @@ typedef enum {
|
|
- initWithFontName: (NSString *) name;
|
|
- initWithDataProvider: (O2DataProviderRef) provider;
|
|
|
|
+- (NSArray *) copyTableTags;
|
|
+- (NSString *) copyPostScriptName;
|
|
+- (NSString *) copyFamilyName;
|
|
- (NSData *) copyTableForTag: (uint32_t) tag;
|
|
+- (O2Rect) boundingRectForGlyph: (O2Glyph) glyph;
|
|
|
|
- (O2Glyph) glyphWithGlyphName: (NSString *) name;
|
|
- (NSString *) copyGlyphNameForGlyph: (O2Glyph) glyph;
|
|
diff --git a/Onyx2D/include/Onyx2D/O2Font_freetype.h b/Onyx2D/include/Onyx2D/O2Font_freetype.h
|
|
index 039d8c94..b518c961 100644
|
|
--- a/Onyx2D/include/Onyx2D/O2Font_freetype.h
|
|
+++ b/Onyx2D/include/Onyx2D/O2Font_freetype.h
|
|
@@ -10,6 +10,7 @@
|
|
#import <ft2build.h>
|
|
#import FT_FREETYPE_H
|
|
#import FT_RENDER_H
|
|
+#import FT_TRUETYPE_TABLES_H
|
|
|
|
#import <fontconfig/fontconfig.h>
|
|
|
|
diff --git a/QuartzCore/CALayer.m b/QuartzCore/CALayer.m
|
|
index 5dcac115..5de4f03f 100644
|
|
--- a/QuartzCore/CALayer.m
|
|
+++ b/QuartzCore/CALayer.m
|
|
@@ -1,8 +1,243 @@
|
|
#import <Foundation/NSDictionary.h>
|
|
+#import <Foundation/NSInvocation.h>
|
|
+#import <Foundation/NSMethodSignature.h>
|
|
#import <QuartzCore/CAAnimation.h>
|
|
#import <QuartzCore/CALayer.h>
|
|
#import <QuartzCore/CALayerContext.h>
|
|
#import <QuartzCore/CATransaction.h>
|
|
+#import <IOSurface/IOSurface.h>
|
|
+#import <OpenGL/OpenGL.h>
|
|
+#include <fcntl.h>
|
|
+#include <sys/mman.h>
|
|
+#include <sys/stat.h>
|
|
+#include <sys/time.h>
|
|
+#include <sys/signal.h>
|
|
+#include <errno.h>
|
|
+#include <dirent.h>
|
|
+#include <unistd.h>
|
|
+#include <stdlib.h>
|
|
+#include <pthread.h>
|
|
+#include <dlfcn.h>
|
|
+
|
|
+/* Darling has no WindowServer/CAContext service to carry Chromium's remote
|
|
+ * layer tree from its GPU process to AppKit. Keep the latest IOSurface in a
|
|
+ * host-visible seqlocked slot; the X11 AppKit backend consumes this slot.
|
|
+ * This is intentionally pixel transport, not guest object transport: guest
|
|
+ * objects and globals are not coherent between Darling processes. */
|
|
+#define DARLING_CA_FRAME_MAGIC 0x43414652u /* "CAFR" */
|
|
+#define DARLING_CA_FRAME_BUFFERED_MAGIC 0x43414632u /* "CAF2" */
|
|
+#define DARLING_CA_FRAME_BACK_BUFFER 0x40000000u
|
|
+#define DARLING_CA_FRAME_CAPACITY (64u * 1024u * 1024u)
|
|
+typedef struct {
|
|
+ uint32_t magic;
|
|
+ volatile uint32_t sequence;
|
|
+ uint32_t width;
|
|
+ uint32_t height;
|
|
+ uint32_t bytesPerRow;
|
|
+ uint32_t pixelFormat;
|
|
+ uint64_t dataSize;
|
|
+} DarlingCARemoteFrame;
|
|
+
|
|
+static pthread_key_t committedSurfaceKey;
|
|
+static pthread_key_t softwareSurfaceKey;
|
|
+static pthread_once_t committedSurfaceOnce = PTHREAD_ONCE_INIT;
|
|
+static void releaseCommittedSurface(void *surface) { CFRelease(surface); }
|
|
+static void createCommittedSurfaceKey(void) {
|
|
+ pthread_key_create(&committedSurfaceKey, releaseCommittedSurface);
|
|
+ pthread_key_create(&softwareSurfaceKey, releaseCommittedSurface);
|
|
+}
|
|
+static void rememberCommittedSurface(IOSurfaceRef surface) {
|
|
+ pthread_once(&committedSurfaceOnce, createCommittedSurfaceKey);
|
|
+ IOSurfaceRef previous = pthread_getspecific(committedSurfaceKey);
|
|
+ if (previous != surface) {
|
|
+ CFRetain(surface);
|
|
+ pthread_setspecific(committedSurfaceKey, surface);
|
|
+ if (previous) CFRelease(previous);
|
|
+ }
|
|
+}
|
|
+static pthread_mutex_t remoteFrameLock = PTHREAD_MUTEX_INITIALIZER;
|
|
+static void publishRemoteIOSurfaceImpl(IOSurfaceRef surface, BOOL readback) {
|
|
+ static DarlingCARemoteFrame *slot;
|
|
+ static int fd = -1;
|
|
+ /* The X11 frame protocol carries packed32-bit pixels. Planar decoder
|
|
+ * buffers belong to the GPU video upload path, never whole-window frames. */
|
|
+ if(IOSurfaceGetPlaneCount(surface)>1 || IOSurfaceGetBytesPerElement(surface)!=4){
|
|
+ static unsigned ignored;
|
|
+ if(getenv("DARLING_CA_FRAME_TRACE") && ignored++<8)
|
|
+ fprintf(stderr,"CA_FRAME_UNSUPPORTED planes=%zu bpe=%zu size=%zux%zu\n",IOSurfaceGetPlaneCount(surface),IOSurfaceGetBytesPerElement(surface),IOSurfaceGetWidth(surface),IOSurfaceGetHeight(surface));
|
|
+ return;
|
|
+ }
|
|
+ size_t width = IOSurfaceGetWidth(surface);
|
|
+ size_t height = IOSurfaceGetHeight(surface);
|
|
+ if (getenv("DARLING_UI_GEOMETRY_TRACE")) {
|
|
+ static __thread size_t previousWidth, previousHeight;
|
|
+ if (width != previousWidth || height != previousHeight) {
|
|
+ fprintf(stderr, "UI_GEOMETRY surface pid=%d pixels=%zux%zu route=%s\n",
|
|
+ getpid(), width, height, readback ? "gpu-readback" : "software");
|
|
+ previousWidth = width;
|
|
+ previousHeight = height;
|
|
+ }
|
|
+ }
|
|
+ size_t rowBytes = IOSurfaceGetBytesPerRow(surface);
|
|
+ size_t dataSize = rowBytes * height;
|
|
+ void *base = IOSurfaceGetBaseAddress(surface);
|
|
+ if (!base || !width || !height || !rowBytes ||
|
|
+ dataSize > DARLING_CA_FRAME_CAPACITY)
|
|
+ return;
|
|
+
|
|
+ /* Readback may itself finish a software write and enter this publisher.
|
|
+ * Do it before taking the frame lock; serialize only file initialization
|
|
+ * and snapshot publication across threads in this process. */
|
|
+ pthread_once(&committedSurfaceOnce, createCommittedSurfaceKey);
|
|
+ IOSurfaceRef softwareSurface = pthread_getspecific(softwareSurfaceKey);
|
|
+ if (!readback && softwareSurface != surface) {
|
|
+ CFRetain(surface);
|
|
+ pthread_setspecific(softwareSurfaceKey, surface);
|
|
+ if (softwareSurface) CFRelease(softwareSurface);
|
|
+ softwareSurface = surface;
|
|
+ }
|
|
+ BOOL softwarePixels = softwareSurface == surface;
|
|
+ /* Only the process/context that completed GPU readback owns fresh pixels.
|
|
+ * A browser snapshot of shared backing may race or trail that publisher. */
|
|
+ if(readback && !softwarePixels && getenv("RCLOL_GPU_DIRECT_PRESENT")){
|
|
+ typedef int (*PresentFn)(IOSurfaceRef);
|
|
+ PresentFn present=(PresentFn)dlsym(RTLD_DEFAULT,"CGLPresentIOSurface");
|
|
+ if(present && present(surface)){rememberCommittedSurface(surface);return;}
|
|
+ }
|
|
+ if (readback && !softwarePixels && CGLReadbackIOSurface(surface) != kCGLNoError)
|
|
+ return;
|
|
+ softwarePixels = pthread_getspecific(softwareSurfaceKey) == surface;
|
|
+ pthread_mutex_lock(&remoteFrameLock);
|
|
+ if (!slot) {
|
|
+ char path[128];
|
|
+ /* darling-lol M58c: one slot file per PUBLISHER process. A single
|
|
+ * per-uid slot was raced by every publishing process (GPU layer tree
|
|
+ * + browser DisplayCALayerTree), corrupting the odd/even seqlock for
|
|
+ * the consumer: 655/663 probes read a torn/unstable frame. Per-pid
|
|
+ * slots keep each file single-writer, so the seqlock holds. */
|
|
+ snprintf(path, sizeof(path), "/dev/shm/darling-ca-frame-%u-%d",
|
|
+ getuid(), (int) getpid());
|
|
+ fd = open(path, O_CREAT | O_RDWR, 0600);
|
|
+ if (fd < 0 || ftruncate(fd, sizeof(*slot) + DARLING_CA_FRAME_CAPACITY)) {
|
|
+ pthread_mutex_unlock(&remoteFrameLock);
|
|
+ return;
|
|
+ }
|
|
+ slot = mmap(NULL, sizeof(*slot) + DARLING_CA_FRAME_CAPACITY,
|
|
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
+ if (slot == MAP_FAILED) {
|
|
+ slot = NULL;
|
|
+ pthread_mutex_unlock(&remoteFrameLock);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* darling-lol M58c: reap slot files left by dead publishers
|
|
+ * (64 MB each). Slot names end in the publishing guest pid. */
|
|
+ {
|
|
+ DIR *dir = opendir("/dev/shm");
|
|
+ char prefix[32];
|
|
+ snprintf(prefix, sizeof(prefix), "darling-ca-frame-%u-", getuid());
|
|
+ size_t prefixLen = strlen(prefix);
|
|
+ if (dir) {
|
|
+ struct dirent *ent;
|
|
+ while ((ent = readdir(dir)) != NULL) {
|
|
+ if (strncmp(ent->d_name, prefix, prefixLen) != 0)
|
|
+ continue;
|
|
+ int stalePid = atoi(ent->d_name + prefixLen);
|
|
+ if (stalePid > 0 && stalePid != (int) getpid() &&
|
|
+ kill(stalePid, 0) != 0 && errno == ESRCH) {
|
|
+ char stale[128];
|
|
+ snprintf(stale, sizeof(stale), "/dev/shm/%s",
|
|
+ ent->d_name);
|
|
+ unlink(stale);
|
|
+ }
|
|
+ }
|
|
+ closedir(dir);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ rememberCommittedSurface(surface);
|
|
+
|
|
+ /* CGLTexImageIOSurface2D's Mesa fallback cannot provide true shared GL
|
|
+ * storage. Materialize the completed compositor texture first. */
|
|
+ IOSurfaceLock(surface, kIOSurfaceLockReadOnly, NULL);
|
|
+ uint32_t sequence = slot->sequence;
|
|
+ BOOL buffered = dataSize <= DARLING_CA_FRAME_CAPACITY / 2;
|
|
+ size_t offset = buffered && !(slot->magic == DARLING_CA_FRAME_BUFFERED_MAGIC &&
|
|
+ (slot->pixelFormat & DARLING_CA_FRAME_BACK_BUFFER))
|
|
+ ? DARLING_CA_FRAME_CAPACITY / 2 : 0;
|
|
+ /* Keep the completed front buffer readable during the bulk copy. Only
|
|
+ * publishing its new header needs the short seqlock critical section.
|
|
+ * Large legacy frames still use one buffer and must invalidate first. */
|
|
+ if (!buffered || (slot->magic == DARLING_CA_FRAME_MAGIC &&
|
|
+ slot->dataSize > DARLING_CA_FRAME_CAPACITY / 2)) {
|
|
+ slot->sequence = (sequence + 1) | 1;
|
|
+ __sync_synchronize();
|
|
+ }
|
|
+ memcpy((char *)(slot + 1) + offset, base, dataSize);
|
|
+ __sync_synchronize();
|
|
+ slot->sequence = (sequence + 1) | 1;
|
|
+ __sync_synchronize();
|
|
+ slot->magic = buffered ? DARLING_CA_FRAME_BUFFERED_MAGIC : DARLING_CA_FRAME_MAGIC;
|
|
+ slot->width = (uint32_t)width;
|
|
+ slot->height = (uint32_t)height;
|
|
+ slot->bytesPerRow = (uint32_t)rowBytes;
|
|
+ /* This is an internal transport header; two upper bits carry provenance
|
|
+ * and buffer location rather than the original IOSurface fourcc. */
|
|
+ slot->pixelFormat = ((uint32_t)IOSurfaceGetPixelFormat(surface) & 0x3fffffffu) |
|
|
+ (softwarePixels ? 0x80000000u : 0) |
|
|
+ (offset ? DARLING_CA_FRAME_BACK_BUFFER : 0);
|
|
+ slot->dataSize = dataSize;
|
|
+ __sync_synchronize();
|
|
+ slot->sequence = (sequence + 2) & ~1u;
|
|
+ IOSurfaceUnlock(surface, kIOSurfaceLockReadOnly, NULL);
|
|
+ futimes(fd, NULL); /* mmap writes alone do not refresh publisher ordering */
|
|
+
|
|
+ /* darling-lol M58c diagnostic (opt-in via M58_PRES_TRACE): publisher
|
|
+ * cadence. p=publish, P=publish whose first 8 rows are all-zero,
|
|
+ * f=first publish in this process (carries the size). */
|
|
+ if (getenv("M58_PRES_TRACE")) {
|
|
+ static FILE *plog;
|
|
+ if (!plog) {
|
|
+ char ppath[128];
|
|
+ snprintf(ppath, sizeof(ppath),
|
|
+ "/dev/shm/m58c-publish-%d", (int) getpid());
|
|
+ plog = fopen(ppath, "w");
|
|
+ if (plog)
|
|
+ fprintf(plog,
|
|
+ "pid=%d %zux%zu first\n", (int) getpid(), width, height);
|
|
+ }
|
|
+ if (plog) {
|
|
+ BOOL blank = YES;
|
|
+ size_t scan = 8 * rowBytes;
|
|
+ const unsigned char *p8 = (const unsigned char *) base;
|
|
+ for (size_t i = 0; i + 3 < scan && blank; i += 4)
|
|
+ if (p8[i] | p8[i + 1] | p8[i + 2])
|
|
+ blank = NO;
|
|
+ fputc(blank ? 'P' : 'p', plog);
|
|
+ fflush(plog);
|
|
+ }
|
|
+ }
|
|
+ pthread_mutex_unlock(&remoteFrameLock);
|
|
+}
|
|
+
|
|
+static void publishRemoteIOSurface(IOSurfaceRef surface) {
|
|
+ publishRemoteIOSurfaceImpl(surface, YES);
|
|
+}
|
|
+
|
|
+/* Software paint already wrote the backing pixels: do not replace them with
|
|
+ * a GL texture readback. IOSurface calls this only after a write unlock. */
|
|
+void DarlingCAPublishIOSurfacePixels(IOSurfaceRef surface) {
|
|
+ publishRemoteIOSurfaceImpl(surface, NO);
|
|
+}
|
|
+
|
|
+/* Remote CA reuses a layer and its IOSurface across frames. GPU writes do
|
|
+ * not necessarily call setContents again; refresh pixels at outer commit. */
|
|
+void DarlingCAPublishCommittedIOSurface(void) {
|
|
+ pthread_once(&committedSurfaceOnce, createCommittedSurfaceKey);
|
|
+ IOSurfaceRef surface = pthread_getspecific(committedSurfaceKey);
|
|
+ if (surface) publishRemoteIOSurface(surface);
|
|
+}
|
|
|
|
NSString *const kCAFilterLinear = @"linear";
|
|
NSString *const kCAFilterNearest = @"nearest";
|
|
@@ -61,13 +296,22 @@ NSString *const kCAContentsFormatGray8Uint = @"Gray8";
|
|
}
|
|
|
|
- (void) setSublayers: (NSArray *) sublayers {
|
|
+ NSArray *oldSublayers = _sublayers;
|
|
sublayers = [sublayers copy];
|
|
- [_sublayers release];
|
|
+ /* A child only retains its parent through the parent's array. Clear
|
|
+ * its non-owning back pointers before dropping the old array. */
|
|
+ for (CALayer *child in oldSublayers) {
|
|
+ if ([sublayers indexOfObjectIdenticalTo: child] == NSNotFound) {
|
|
+ [child _setSuperLayer: nil];
|
|
+ [child _setContext: nil];
|
|
+ }
|
|
+ }
|
|
_sublayers = sublayers;
|
|
[_sublayers makeObjectsPerformSelector: @selector(_setSuperLayer:)
|
|
withObject: self];
|
|
[_sublayers makeObjectsPerformSelector: @selector(_setContext:)
|
|
withObject: _context];
|
|
+ [oldSublayers release];
|
|
}
|
|
|
|
- (id<CALayerDelegate>) delegate {
|
|
@@ -117,6 +361,11 @@ NSString *const kCAContentsFormatGray8Uint = @"Gray8";
|
|
[self addAnimation: action forKey: @"bounds"];
|
|
}
|
|
|
|
+ if (!CGRectEqualToRect(_bounds, value) && getenv("DARLING_UI_GEOMETRY_TRACE") &&
|
|
+ (value.size.width >= 256 || value.size.height >= 256))
|
|
+ fprintf(stderr, "UI_GEOMETRY layer pid=%d class=%s bounds=%.0fx%.0f transform=%.3fx%.3f\n",
|
|
+ getpid(), class_getName([self class]), value.size.width, value.size.height,
|
|
+ _transform.m11, _transform.m22);
|
|
_bounds = value;
|
|
}
|
|
|
|
@@ -171,14 +420,73 @@ NSString *const kCAContentsFormatGray8Uint = @"Gray8";
|
|
_opaque = value;
|
|
}
|
|
|
|
+/* darling-lol M57: Chromium sets a layer's geometryFlipped flag when hosting
|
|
+ * web content. */
|
|
+- (BOOL) geometryFlipped {
|
|
+ return _geometryFlipped;
|
|
+}
|
|
+
|
|
+- (void) setGeometryFlipped: (BOOL) value {
|
|
+ _geometryFlipped = value;
|
|
+}
|
|
+
|
|
- (id) contents {
|
|
return _contents;
|
|
}
|
|
|
|
- (void) setContents: (id) value {
|
|
+ /* darling-lol M57: a live compositor (e.g. Chromium) replaces a layer's
|
|
+ * contents every frame. Drop the cached GL texture so CARenderer uploads
|
|
+ * the new image instead of reusing the first (possibly empty) one, then
|
|
+ * present the updated tree (Darling's NSTimer-driven frame loop does not
|
|
+ * fire for this context). */
|
|
+ BOOL needsPresent = NO;
|
|
+ if (_context != nil && _contents != value) {
|
|
+ [_context deleteTextureId: _textureId];
|
|
+ [_textureId release];
|
|
+ _textureId = nil;
|
|
+ needsPresent = YES;
|
|
+ }
|
|
value = [value retain];
|
|
[_contents release];
|
|
_contents = value;
|
|
+
|
|
+ /* Remote CA trees can have a context without a local drawable. Their
|
|
+ * IOSurface still needs a pixel handoff to the X11 consumer. */
|
|
+ if (value != nil &&
|
|
+ CFGetTypeID((CFTypeRef)value) == IOSurfaceGetTypeID())
|
|
+ if(getenv("DARLING_CA_PRESENT_AT_COMMIT"))rememberCommittedSurface((IOSurfaceRef)value);
|
|
+ else publishRemoteIOSurface((IOSurfaceRef)value);
|
|
+
|
|
+ if (needsPresent) {
|
|
+ /* darling-lol M57 diagnostic (opt-in): report what kind of contents
|
|
+ * Chromium delivers (IOSurface == a real compositor frame). */
|
|
+ if (getenv("M57_QC_TRACE") != 0) {
|
|
+ BOOL isSurf = (value != nil &&
|
|
+ CFGetTypeID((CFTypeRef)value) == IOSurfaceGetTypeID());
|
|
+ NSLog(@"M57SETCONTENTS class=%s iosurface=%d ctx=%p",
|
|
+ value ? object_getClassName(value) : "nil", (int)isSurf, _context);
|
|
+ }
|
|
+ [_context render];
|
|
+ [_context flush];
|
|
+ }
|
|
+}
|
|
+
|
|
+- (void) setContentsChanged {
|
|
+ /* Chromium can reuse an IOSurface and update its pixels without replacing
|
|
+ * the contents object. Invalidate the cached texture and publish the new
|
|
+ * pixels just as for a contents replacement. */
|
|
+ [_context deleteTextureId: _textureId];
|
|
+ [_textureId release];
|
|
+ _textureId = nil;
|
|
+ if (_contents != nil &&
|
|
+ CFGetTypeID((CFTypeRef)_contents) == IOSurfaceGetTypeID())
|
|
+ if(getenv("DARLING_CA_PRESENT_AT_COMMIT"))rememberCommittedSurface((IOSurfaceRef)_contents);
|
|
+ else publishRemoteIOSurface((IOSurfaceRef)_contents);
|
|
+ if (_context != nil) {
|
|
+ [_context render];
|
|
+ [_context flush];
|
|
+ }
|
|
}
|
|
|
|
- (CATransform3D) transform {
|
|
@@ -186,6 +494,11 @@ NSString *const kCAContentsFormatGray8Uint = @"Gray8";
|
|
}
|
|
|
|
- (void) setTransform: (CATransform3D) value {
|
|
+ if (getenv("DARLING_UI_GEOMETRY_TRACE") &&
|
|
+ (_bounds.size.width >= 256 || _bounds.size.height >= 256))
|
|
+ fprintf(stderr, "UI_GEOMETRY transform pid=%d class=%s bounds=%.0fx%.0f scale=%.3fx%.3f\n",
|
|
+ getpid(), class_getName([self class]), _bounds.size.width, _bounds.size.height,
|
|
+ value.m11, value.m22);
|
|
_transform = value;
|
|
}
|
|
|
|
@@ -236,6 +549,10 @@ NSString *const kCAContentsFormatGray8Uint = @"Gray8";
|
|
}
|
|
|
|
- (void) dealloc {
|
|
+ for (CALayer *child in _sublayers) {
|
|
+ [child _setSuperLayer: nil];
|
|
+ [child _setContext: nil];
|
|
+ }
|
|
[_sublayers release];
|
|
[_animations release];
|
|
[_minificationFilter release];
|
|
@@ -258,6 +575,33 @@ NSString *const kCAContentsFormatGray8Uint = @"Gray8";
|
|
[self setSublayers: [_sublayers arrayByAddingObject: layer]];
|
|
}
|
|
|
|
+- (void) insertSublayer: (CALayer *) layer atIndex: (unsigned) index {
|
|
+ if (!layer)
|
|
+ return;
|
|
+ [layer removeFromSuperlayer];
|
|
+ NSMutableArray *layers = [_sublayers mutableCopy];
|
|
+ NSUInteger boundedIndex = index > [layers count] ? [layers count] : index;
|
|
+ [layers insertObject: layer atIndex: boundedIndex];
|
|
+ [self setSublayers: layers];
|
|
+ [layers release];
|
|
+}
|
|
+
|
|
+- (void) insertSublayer: (CALayer *) layer below: (CALayer *) sibling {
|
|
+ NSUInteger index = [_sublayers indexOfObjectIdenticalTo: sibling];
|
|
+ if (index == NSNotFound)
|
|
+ index = 0;
|
|
+ [self insertSublayer: layer atIndex: (unsigned)index];
|
|
+}
|
|
+
|
|
+- (void) insertSublayer: (CALayer *) layer above: (CALayer *) sibling {
|
|
+ NSUInteger index = [_sublayers indexOfObjectIdenticalTo: sibling];
|
|
+ if (index == NSNotFound)
|
|
+ index = [_sublayers count];
|
|
+ else
|
|
+ index++;
|
|
+ [self insertSublayer: layer atIndex: (unsigned)index];
|
|
+}
|
|
+
|
|
- (void) replaceSublayer: (CALayer *) layer with: (CALayer *) other {
|
|
NSMutableArray *layers = [_sublayers mutableCopy];
|
|
NSUInteger index = [_sublayers indexOfObjectIdenticalTo: layer];
|
|
@@ -298,9 +642,14 @@ NSString *const kCAContentsFormatGray8Uint = @"Gray8";
|
|
}
|
|
|
|
- (void) removeFromSuperlayer {
|
|
- [_superlayer _removeSublayer: self];
|
|
+ /* Removing the child from its parent's array may release the last
|
|
+ * external reference to this layer. Keep it alive until cleanup ends. */
|
|
+ [self retain];
|
|
+ CALayer *parent = _superlayer;
|
|
_superlayer = nil;
|
|
+ [parent _removeSublayer: self];
|
|
[self _setContext: nil];
|
|
+ [self release];
|
|
}
|
|
|
|
- (void) setNeedsDisplay {
|
|
@@ -319,6 +668,19 @@ NSString *const kCAContentsFormatGray8Uint = @"Gray8";
|
|
[_context startTimerIfNeeded];
|
|
}
|
|
|
|
+/* darling-lol M57: used by CALayerContext to decide whether the on-demand frame
|
|
+ * timer should keep re-arming. With no animations there is no reason to render
|
|
+ * continuously; contents changes drive presentation directly. */
|
|
+- (BOOL) _treeHasAnimations {
|
|
+ if ([_animations count] > 0)
|
|
+ return YES;
|
|
+ for (CALayer *child in _sublayers) {
|
|
+ if ([child _treeHasAnimations])
|
|
+ return YES;
|
|
+ }
|
|
+ return NO;
|
|
+}
|
|
+
|
|
- (CAAnimation *) animationForKey: (NSString *) key {
|
|
return [_animations objectForKey: key];
|
|
}
|
|
@@ -364,4 +726,119 @@ NSString *const kCAContentsFormatGray8Uint = @"Gray8";
|
|
_textureId = value;
|
|
}
|
|
|
|
+/* darling-lol M57: property accessors Chromium's layer hosting needs. */
|
|
+
|
|
+- (NSUInteger) autoresizingMask { return _autoresizingMask; }
|
|
+- (void) setAutoresizingMask: (NSUInteger) v { _autoresizingMask = v; }
|
|
+
|
|
+- (BOOL) masksToBounds { return _masksToBounds; }
|
|
+- (void) setMasksToBounds: (BOOL) v { _masksToBounds = v; }
|
|
+
|
|
+- (CGFloat) cornerRadius { return _cornerRadius; }
|
|
+- (void) setCornerRadius: (CGFloat) v { _cornerRadius = v; }
|
|
+
|
|
+- (CGColorRef) backgroundColor { return _backgroundColor; }
|
|
+- (void) setBackgroundColor: (CGColorRef) v { _backgroundColor = v; }
|
|
+
|
|
+- (CGFloat) borderWidth { return _borderWidth; }
|
|
+- (void) setBorderWidth: (CGFloat) v { _borderWidth = v; }
|
|
+
|
|
+- (CGColorRef) borderColor { return _borderColor; }
|
|
+- (void) setBorderColor: (CGColorRef) v { _borderColor = v; }
|
|
+
|
|
+- (CGFloat) zPosition { return _zPosition; }
|
|
+- (void) setZPosition: (CGFloat) v { _zPosition = v; }
|
|
+
|
|
+- (BOOL) isHidden { return _hidden; }
|
|
+- (BOOL) hidden { return _hidden; }
|
|
+- (void) setHidden: (BOOL) v { _hidden = v; }
|
|
+
|
|
+- (BOOL) isDoubleSided { return _doubleSided; }
|
|
+- (BOOL) doubleSided { return _doubleSided; }
|
|
+- (void) setDoubleSided: (BOOL) v { _doubleSided = v; }
|
|
+
|
|
+- (BOOL) needsDisplayOnBoundsChange { return _needsDisplayOnBoundsChange; }
|
|
+- (void) setNeedsDisplayOnBoundsChange: (BOOL) v { _needsDisplayOnBoundsChange = v; }
|
|
+
|
|
+- (BOOL) shouldRasterize { return _shouldRasterize; }
|
|
+- (void) setShouldRasterize: (BOOL) v { _shouldRasterize = v; }
|
|
+
|
|
+- (CGFloat) rasterizationScale { return _rasterizationScale; }
|
|
+- (void) setRasterizationScale: (CGFloat) v { _rasterizationScale = v; }
|
|
+
|
|
+- (BOOL) allowsEdgeAntialiasing { return _allowsEdgeAntialiasing; }
|
|
+- (void) setAllowsEdgeAntialiasing: (BOOL) v { _allowsEdgeAntialiasing = v; }
|
|
+
|
|
+- (BOOL) allowsGroupOpacity { return _allowsGroupOpacity; }
|
|
+- (void) setAllowsGroupOpacity: (BOOL) v { _allowsGroupOpacity = v; }
|
|
+
|
|
+- (BOOL) allowsGroupBlending { return _allowsGroupBlending; }
|
|
+- (void) setAllowsGroupBlending: (BOOL) v { _allowsGroupBlending = v; }
|
|
+
|
|
+- (BOOL) allowsHitTesting { return _allowsHitTesting; }
|
|
+- (void) setAllowsHitTesting: (BOOL) v { _allowsHitTesting = v; }
|
|
+
|
|
+- (BOOL) drawsAsynchronously { return _drawsAsynchronously; }
|
|
+- (void) setDrawsAsynchronously: (BOOL) v { _drawsAsynchronously = v; }
|
|
+
|
|
+- (CGFloat) contentsScale { return _contentsScale; }
|
|
+- (void) setContentsScale: (CGFloat) v { _contentsScale = v; }
|
|
+
|
|
+- (NSString *) contentsGravity { return _contentsGravity; }
|
|
+- (void) setContentsGravity: (NSString *) v {
|
|
+ v = [v copy]; [_contentsGravity release]; _contentsGravity = v;
|
|
+}
|
|
+
|
|
+- (CGRect) contentsRect { return _contentsRect; }
|
|
+- (void) setContentsRect: (CGRect) v { _contentsRect = v; }
|
|
+
|
|
+- (CGRect) contentsCenter { return _contentsCenter; }
|
|
+- (void) setContentsCenter: (CGRect) v { _contentsCenter = v; }
|
|
+
|
|
+- (CGFloat) anchorPointZ { return _anchorPointZ; }
|
|
+- (void) setAnchorPointZ: (CGFloat) v { _anchorPointZ = v; }
|
|
+
|
|
+- (CGFloat) shadowOpacity { return _shadowOpacity; }
|
|
+- (void) setShadowOpacity: (CGFloat) v { _shadowOpacity = v; }
|
|
+
|
|
+- (CGSize) shadowOffset { return _shadowOffset; }
|
|
+- (void) setShadowOffset: (CGSize) v { _shadowOffset = v; }
|
|
+
|
|
+- (CGFloat) shadowRadius { return _shadowRadius; }
|
|
+- (void) setShadowRadius: (CGFloat) v { _shadowRadius = v; }
|
|
+
|
|
+- (CGColorRef) shadowColor { return _shadowColor; }
|
|
+- (void) setShadowColor: (CGColorRef) v { _shadowColor = v; }
|
|
+
|
|
+- (id) shadowPath { return _shadowPath; }
|
|
+- (void) setShadowPath: (id) v { v = [v retain]; [_shadowPath release]; _shadowPath = v; }
|
|
+
|
|
+- (float) minificationFilterBias { return _minificationFilterBias; }
|
|
+- (void) setMinificationFilterBias: (float) v { _minificationFilterBias = v; }
|
|
+
|
|
+- (unsigned int) edgeAntialiasingMask { return _edgeAntialiasingMask; }
|
|
+- (void) setEdgeAntialiasingMask: (unsigned int) v { _edgeAntialiasingMask = v; }
|
|
+
|
|
+- (NSArray *) filters { return _filters; }
|
|
+- (void) setFilters: (NSArray *) v { v = [v copy]; [_filters release]; _filters = v; }
|
|
+
|
|
+- (NSArray *) backgroundFilters { return _backgroundFilters; }
|
|
+- (void) setBackgroundFilters: (NSArray *) v { v = [v copy]; [_backgroundFilters release]; _backgroundFilters = v; }
|
|
+
|
|
+- (id) compositingFilter { return _compositingFilter; }
|
|
+- (void) setCompositingFilter: (id) v { v = [v retain]; [_compositingFilter release]; _compositingFilter = v; }
|
|
+
|
|
+- (NSDictionary *) style { return _style; }
|
|
+- (void) setStyle: (NSDictionary *) v { v = [v copy]; [_style release]; _style = v; }
|
|
+
|
|
+- (NSArray *) actions { return _actions; }
|
|
+- (void) setActions: (NSArray *) v { v = [v copy]; [_actions release]; _actions = v; }
|
|
+
|
|
+/* Layout hooks: Cocotron does not run a layout pass, so these are no-ops
|
|
+ * (except layoutSublayers, if present, which delegates to the delegate). */
|
|
+- (void) setNeedsLayout { }
|
|
+- (void) layoutIfNeeded { }
|
|
+- (BOOL) needsLayout { return NO; }
|
|
+- (void) layoutSublayers { }
|
|
+
|
|
@end
|
|
diff --git a/QuartzCore/CALayerContext.m b/QuartzCore/CALayerContext.m
|
|
index 5dce8c56..dee2b80c 100644
|
|
--- a/QuartzCore/CALayerContext.m
|
|
+++ b/QuartzCore/CALayerContext.m
|
|
@@ -1,47 +1,58 @@
|
|
#import <Foundation/NSString.h>
|
|
+#import <Foundation/NSMutableArray.h>
|
|
#import <QuartzCore/CALayer.h>
|
|
#import <QuartzCore/CALayerContext.h>
|
|
#import <QuartzCore/CARenderer.h>
|
|
#import "CALayerInternal.h"
|
|
+#import <IOSurface/IOSurface.h>
|
|
+#include <stdlib.h>
|
|
+#include <stdio.h>
|
|
#import <OpenGL/CGLInternal.h>
|
|
|
|
@class CAMetalLayerInternal;
|
|
|
|
+/* The X11 transport presents Chromium's flattened full-window surface.
|
|
+ * Avoid uploading and compositing that same window again into a covered GL
|
|
+ * child. Restrict this to the explicitly configured native presentation path. */
|
|
+static BOOL hasFullWindowSurface(CALayer *layer,CGSize size) {
|
|
+ id contents=layer.contents;
|
|
+ static unsigned traces;
|
|
+ if(getenv("DARLING_CA_LOCAL_RENDER_TRACE") && contents &&
|
|
+ CFGetTypeID((CFTypeRef)contents)==IOSurfaceGetTypeID() && traces++<24)
|
|
+ fprintf(stderr,"CA_LOCAL_SURFACE frame=%.0fx%.0f surface=%zux%zu\n",size.width,size.height,IOSurfaceGetWidth((IOSurfaceRef)contents),IOSurfaceGetHeight((IOSurfaceRef)contents));
|
|
+ if(contents && CFGetTypeID((CFTypeRef)contents)==IOSurfaceGetTypeID() &&
|
|
+ IOSurfaceGetWidth((IOSurfaceRef)contents)==(size_t)size.width &&
|
|
+ IOSurfaceGetHeight((IOSurfaceRef)contents)==(size_t)size.height)return YES;
|
|
+ for(CALayer *child in layer.sublayers)
|
|
+ if(hasFullWindowSurface(child,size))return YES;
|
|
+ return NO;
|
|
+}
|
|
@implementation CALayerContext
|
|
|
|
@synthesize glContext = _glContext;
|
|
+@synthesize subwindow = _subwindow;
|
|
|
|
- initWithFrame: (CGRect) rect {
|
|
- CGLError error;
|
|
-
|
|
- CGLPixelFormatAttribute attributes[1] = {
|
|
- 0,
|
|
- };
|
|
- GLint numberOfVirtualScreens;
|
|
-
|
|
- CGLChoosePixelFormat(attributes, &_pixelFormat, &numberOfVirtualScreens);
|
|
-
|
|
- if ((error = CGLCreateContext(_pixelFormat, NULL, &_glContext)) !=
|
|
- kCGLNoError)
|
|
- NSLog(@"CGLCreateContext failed with %d in %s %d", error, __FILE__,
|
|
- __LINE__);
|
|
+ self = [super init];
|
|
+ if (self == nil)
|
|
+ return nil;
|
|
|
|
+ _deleteTextureIds = [[NSMutableArray alloc] init];
|
|
_frame = rect;
|
|
-
|
|
- _renderer = [[CARenderer rendererWithCGLContext: _glContext
|
|
- options: nil] retain];
|
|
+ /* Views can acquire a backing layer before joining a window. Defer
|
|
+ * expensive native GL resources until there is a drawable. */
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void) dealloc {
|
|
- [_timer invalidate];
|
|
- [_timer release];
|
|
+ [self invalidate];
|
|
+ [_deleteTextureIds release];
|
|
[_renderer release];
|
|
CGLReleaseContext(_glContext);
|
|
+ CGLReleasePixelFormat(_pixelFormat);
|
|
CGLDestroyWindow(_cglWindow);
|
|
[_subwindow release];
|
|
- [_layer release];
|
|
[super dealloc];
|
|
}
|
|
|
|
@@ -51,12 +62,22 @@
|
|
}
|
|
|
|
- (void) setLayer: (CALayer *) layer {
|
|
+ if (_layer == layer)
|
|
+ return;
|
|
+
|
|
layer = [layer retain];
|
|
+ /* CALayer keeps a non-owning context pointer. Clear it before the old
|
|
+ * context or layer can be released. */
|
|
+ [_layer _setContext: nil];
|
|
[_layer release];
|
|
_layer = layer;
|
|
|
|
[_layer _setContext: self];
|
|
[_renderer setLayer: layer];
|
|
+
|
|
+ /* darling-lol M57: the layer compositor must be driven even when no
|
|
+ * CAAnimation is ever added (e.g. Chromium's compositor output). */
|
|
+ [self startTimerIfNeeded];
|
|
}
|
|
|
|
- (void) setSubwindow: (CGSubWindow*) subwindow
|
|
@@ -68,16 +89,50 @@
|
|
}
|
|
|
|
_subwindow = [subwindow retain];
|
|
- _cglWindow = CGLGetWindow([_subwindow nativeWindow]);
|
|
+ _cglWindow = NULL;
|
|
+ if (_subwindow != nil) {
|
|
+ if (_glContext == NULL) {
|
|
+ CGLPixelFormatAttribute attributes[] = { 0 };
|
|
+ GLint screens;
|
|
+ CGLError error = CGLChoosePixelFormat(attributes, &_pixelFormat, &screens);
|
|
+ if (error == kCGLNoError)
|
|
+ error = CGLCreateContext(_pixelFormat, NULL, &_glContext);
|
|
+ if (error != kCGLNoError) {
|
|
+ NSLog(@"CGL context creation failed with %d", error);
|
|
+ CGLReleasePixelFormat(_pixelFormat);
|
|
+ _pixelFormat = NULL;
|
|
+ } else {
|
|
+ _renderer = [[CARenderer rendererWithCGLContext: _glContext
|
|
+ options: nil] retain];
|
|
+ [_renderer setLayer: _layer];
|
|
+ }
|
|
+ }
|
|
+ if (_glContext != NULL)
|
|
+ _cglWindow = CGLGetWindow([_subwindow nativeWindow]);
|
|
+ }
|
|
|
|
[_subwindow show];
|
|
|
|
[oldSubwindow release];
|
|
|
|
[_subwindow setFrame: _frame];
|
|
+
|
|
+ // A view can attach its layer before the subwindow creates the GL context.
|
|
+ // Notify the whole layer tree now that drawable resources can be created.
|
|
+ [_layer _setContext: self];
|
|
+ /* darling-lol M57: begin compositing once a drawable exists. */
|
|
+ [self startTimerIfNeeded];
|
|
}
|
|
|
|
- (void) invalidate {
|
|
+ [_timer invalidate];
|
|
+ [_timer release];
|
|
+ _timer = nil;
|
|
+
|
|
+ [_layer _setContext: nil];
|
|
+ [_renderer setLayer: nil];
|
|
+ [_layer release];
|
|
+ _layer = nil;
|
|
}
|
|
|
|
- (void) assignTextureIdsToLayerTree: (CALayer *) layer {
|
|
@@ -93,7 +148,22 @@
|
|
[self assignTextureIdsToLayerTree: child];
|
|
}
|
|
|
|
+extern void darlingQCTiming(const char* what, double seconds);
|
|
+
|
|
+- (void) prepareMetalLayers: (CALayer *) layer {
|
|
+ // Metal layers may be nested beneath ordinary backing layers. Every queued
|
|
+ // drawable must be consumed before CARenderer samples the layer tree.
|
|
+ if ([[layer class] isSubclassOfClass: [CAMetalLayerInternal class]]) {
|
|
+ CFTimeInterval prepStart = CACurrentMediaTime();
|
|
+ [(CAMetalLayerInternal*)layer prepareRender];
|
|
+ darlingQCTiming("prepareRenderTotal", CACurrentMediaTime() - prepStart);
|
|
+ }
|
|
+ for (CALayer *child in layer.sublayers)
|
|
+ [self prepareMetalLayers: child];
|
|
+}
|
|
+
|
|
- (void) renderLayer: (CALayer *) layer {
|
|
+ CFTimeInterval renderLayerStart = CACurrentMediaTime();
|
|
CGLContextMakeCurrentAndAttachToWindow(_glContext, _cglWindow);
|
|
|
|
glEnable(GL_DEPTH_TEST);
|
|
@@ -118,27 +188,63 @@
|
|
|
|
[_deleteTextureIds removeAllObjects];
|
|
|
|
+ CFTimeInterval assignStart = CACurrentMediaTime();
|
|
+ darlingQCTiming("renderLayerSetup", assignStart - renderLayerStart);
|
|
[self assignTextureIdsToLayerTree: layer];
|
|
+ darlingQCTiming("assignTextureIds", CACurrentMediaTime() - assignStart);
|
|
|
|
- // this is where the Metal layer renders to an internal texture for us to use
|
|
- if ([[layer class] isSubclassOfClass: [CAMetalLayerInternal class]]) {
|
|
- CAMetalLayerInternal* mtl = (CAMetalLayerInternal*)layer;
|
|
- [mtl prepareRender];
|
|
- }
|
|
+ [self prepareMetalLayers: layer];
|
|
|
|
+ CFTimeInterval rendererStart = CACurrentMediaTime();
|
|
[_renderer render];
|
|
+ darlingQCTiming("rendererRender", CACurrentMediaTime() - rendererStart);
|
|
}
|
|
|
|
- (void) render {
|
|
+ if (_cglWindow == NULL || _layer == nil)
|
|
+ return;
|
|
+ if(getenv("DARLING_CA_SKIP_LOCAL_IOSURFACE_RENDER") &&
|
|
+ hasFullWindowSurface(_layer,_frame.size))return;
|
|
[self renderLayer: _layer];
|
|
}
|
|
|
|
+extern void darlingQCTiming(const char* what, double seconds);
|
|
+
|
|
- (void) timer: (NSTimer *) timer {
|
|
+ /* darling-lol M57: this is now a one-shot frame request, not an
|
|
+ * unconditional 60 Hz loop. The old repeating timer ran a full software
|
|
+ * GL composite of the layer tree every 16 ms even when nothing had
|
|
+ * changed; under Darling that monopolised the main thread and starved
|
|
+ * Chromium's task queue (JS timers, IPC), which presented as an "endless
|
|
+ * synchronous IPC wait". Frames are now requested on demand (contents
|
|
+ * changes / animations) and the timer stops when there is no work. */
|
|
+ [_timer release];
|
|
+ _timer = nil;
|
|
+
|
|
+ if (_layer == nil || _cglWindow == NULL)
|
|
+ return;
|
|
+
|
|
+ static CFTimeInterval lastTimer = 0;
|
|
+ CFTimeInterval timerStart = CACurrentMediaTime();
|
|
+ if (lastTimer != 0)
|
|
+ darlingQCTiming("compositorTimerInterval", timerStart - lastTimer);
|
|
+ lastTimer = timerStart;
|
|
+
|
|
[_renderer beginFrameAtTime: CACurrentMediaTime() timeStamp: NULL];
|
|
|
|
[self render];
|
|
+ CFTimeInterval renderDone = CACurrentMediaTime();
|
|
+ darlingQCTiming("compositorRender", renderDone - timerStart);
|
|
|
|
[_renderer endFrame];
|
|
+
|
|
+ /* darling-lol M57: present the composited frame to the drawable. */
|
|
+ [self flush];
|
|
+ darlingQCTiming("compositorFlush", CACurrentMediaTime() - renderDone);
|
|
+
|
|
+ /* Keep animating only while the layer tree actually has animations. */
|
|
+ if ([_layer _treeHasAnimations])
|
|
+ [self startTimerIfNeeded];
|
|
}
|
|
|
|
- (void) startTimerIfNeeded {
|
|
@@ -147,7 +253,7 @@
|
|
target: self
|
|
selector: @selector(timer:)
|
|
userInfo: nil
|
|
- repeats: YES] retain];
|
|
+ repeats: NO] retain];
|
|
}
|
|
|
|
- (void) deleteTextureId: (NSNumber *) textureId {
|
|
@@ -155,7 +261,10 @@
|
|
}
|
|
|
|
- (void) flush {
|
|
- CGLFlushDrawable(_glContext);
|
|
+ if(getenv("DARLING_CA_SKIP_LOCAL_IOSURFACE_RENDER") && _layer &&
|
|
+ hasFullWindowSurface(_layer,_frame.size))return;
|
|
+ if (_glContext != NULL && _cglWindow != NULL)
|
|
+ CGLFlushDrawable(_glContext);
|
|
}
|
|
|
|
@end
|
|
diff --git a/QuartzCore/CALayerInternal.h b/QuartzCore/CALayerInternal.h
|
|
index 5ad375f8..c8e83814 100644
|
|
--- a/QuartzCore/CALayerInternal.h
|
|
+++ b/QuartzCore/CALayerInternal.h
|
|
@@ -23,4 +23,5 @@
|
|
- (void) _setContext: (CALayerContext *) context;
|
|
- (NSNumber *) _textureId;
|
|
- (void) _setTextureId: (NSNumber *) value;
|
|
+- (BOOL) _treeHasAnimations;
|
|
@end
|
|
diff --git a/QuartzCore/CAMetalDrawable.mm b/QuartzCore/CAMetalDrawable.mm
|
|
index d6f86b31..687f05d5 100644
|
|
--- a/QuartzCore/CAMetalDrawable.mm
|
|
+++ b/QuartzCore/CAMetalDrawable.mm
|
|
@@ -31,6 +31,13 @@
|
|
#include <OpenGL/glext.h>
|
|
#include <indium/indium.private.hpp>
|
|
|
|
+#include <atomic>
|
|
+#include <cstdio>
|
|
+#include <cstring>
|
|
+#include <mutex>
|
|
+#include <string>
|
|
+#include <unistd.h>
|
|
+
|
|
namespace DynamicVK = Indium::DynamicVK;
|
|
|
|
static void reportGLErrors(void) {
|
|
@@ -50,8 +57,111 @@ static void reportGLErrors(void) {
|
|
namespace Indium::DynamicVK {
|
|
static DynamicFunction<PFN_vkGetSemaphoreFdKHR> vkGetSemaphoreFdKHR("vkGetSemaphoreFdKHR");
|
|
static DynamicFunction<PFN_vkGetMemoryFdKHR> vkGetMemoryFdKHR("vkGetMemoryFdKHR");
|
|
+ static DynamicFunction<PFN_vkGetPhysicalDeviceProperties2> vkGetPhysicalDeviceProperties2Local("vkGetPhysicalDeviceProperties2");
|
|
};
|
|
|
|
+//
|
|
+// GPU selection diagnostics
|
|
+//
|
|
+
|
|
+// The drawable's memory and semaphores are exported as OPAQUE_FD and imported into
|
|
+// this layer's OpenGL context. That only works when Vulkan and OpenGL run on the
|
|
+// same physical device/driver, and until now nothing checked it: a mismatch would
|
|
+// silently break. Record which GL renderer actually presents and whether its
|
|
+// device UUID matches the Vulkan device's, once per process, in
|
|
+// /tmp/metal-device.log (alongside the device list Indium logs at startup).
|
|
+// Clears pending GL errors (bounded: without a current context glGetError may never
|
|
+// report GL_NO_ERROR).
|
|
+static void drainGLErrors() {
|
|
+ for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {
|
|
+ }
|
|
+}
|
|
+
|
|
+// Same line format as Indium's device log.
|
|
+static void appendDeviceLog(const std::string& line) {
|
|
+ if (FILE* log = fopen("/tmp/metal-device.log", "a")) {
|
|
+ fprintf(log, "[MetalDevice] pid=%d %s\n", getpid(), line.c_str());
|
|
+ fclose(log);
|
|
+ }
|
|
+}
|
|
+
|
|
+static std::string darlingUuidString(const GLubyte* uuid) {
|
|
+ static const char* digits = "0123456789abcdef";
|
|
+ std::string out;
|
|
+ out.reserve(32);
|
|
+ for (int i = 0; i < GL_UUID_SIZE_EXT; ++i) {
|
|
+ out += digits[(uuid[i] >> 4) & 0xf];
|
|
+ out += digits[uuid[i] & 0xf];
|
|
+ }
|
|
+ return out;
|
|
+}
|
|
+
|
|
+static void reportGpuDeviceMatch(std::shared_ptr<Indium::PrivateDevice> device) {
|
|
+ if (!device) {
|
|
+ return;
|
|
+ }
|
|
+ static std::once_flag once;
|
|
+ std::call_once(once, [device]() {
|
|
+ const GLubyte* renderer = glGetString(GL_RENDERER);
|
|
+ const GLubyte* vendor = glGetString(GL_VENDOR);
|
|
+
|
|
+ GLubyte glDeviceUUID[GL_UUID_SIZE_EXT] = {0};
|
|
+ GLubyte glDriverUUID[GL_UUID_SIZE_EXT] = {0};
|
|
+ GLint uuidCount = 0;
|
|
+ glGetIntegerv(GL_NUM_DEVICE_UUIDS_EXT, &uuidCount);
|
|
+ if (uuidCount > 0) {
|
|
+ glGetUnsignedBytei_vEXT(GL_DEVICE_UUID_EXT, 0, glDeviceUUID);
|
|
+ glGetUnsignedBytevEXT(GL_DRIVER_UUID_EXT, glDriverUUID);
|
|
+ }
|
|
+
|
|
+ // Queried here rather than through a new libindium export, so this QuartzCore
|
|
+ // still loads against a libindium built before the device log.
|
|
+ VkPhysicalDeviceIDProperties idProperties {};
|
|
+ idProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES;
|
|
+ VkPhysicalDeviceProperties2 properties2 {};
|
|
+ properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
|
|
+ properties2.pNext = &idProperties;
|
|
+ bool haveVkUUID = false;
|
|
+ if (DynamicVK::vkGetPhysicalDeviceProperties2Local) {
|
|
+ DynamicVK::vkGetPhysicalDeviceProperties2Local(device->physicalDevice(), &properties2);
|
|
+ haveVkUUID = true;
|
|
+ }
|
|
+ const uint8_t* vkDeviceUUID = idProperties.deviceUUID;
|
|
+ const uint8_t* vkDriverUUID = idProperties.driverUUID;
|
|
+
|
|
+ std::string line = "presenting GL renderer=\"" + std::string(renderer ? reinterpret_cast<const char*>(renderer) : "(null)") + "\""
|
|
+ + " vendor=\"" + std::string(vendor ? reinterpret_cast<const char*>(vendor) : "(null)") + "\""
|
|
+ + " glDeviceUUID=" + (uuidCount > 0 ? darlingUuidString(glDeviceUUID) : std::string("none"))
|
|
+ + " glDriverUUID=" + (uuidCount > 0 ? darlingUuidString(glDriverUUID) : std::string("none"))
|
|
+ + " vulkanDeviceUUID=" + (haveVkUUID ? darlingUuidString(vkDeviceUUID) : std::string("none"))
|
|
+ + " vulkanDriverUUID=" + (haveVkUUID ? darlingUuidString(vkDriverUUID) : std::string("none"));
|
|
+
|
|
+ if (uuidCount > 0 && haveVkUUID && memcmp(glDeviceUUID, vkDeviceUUID, GL_UUID_SIZE_EXT) != 0) {
|
|
+ line += " MISMATCH: OpenGL and Vulkan are on different GPUs; the opaque-FD import cannot work."
|
|
+ " Run the launcher under prime-run so both use the same GPU.";
|
|
+ } else if (uuidCount > 0 && haveVkUUID) {
|
|
+ line += " match=yes";
|
|
+ } else {
|
|
+ line += " match=unknown";
|
|
+ }
|
|
+
|
|
+ appendDeviceLog(line);
|
|
+ });
|
|
+}
|
|
+
|
|
+// The internal image is shared with OpenGL as linear memory because of a Mesa bug with
|
|
+// optimal tiling on AMD. NVIDIA's proprietary GL rejects GL_LINEAR_TILING_EXT
|
|
+// (GL_INVALID_VALUE), leaving the texture optimal and its storage import failing, which
|
|
+// shows as a black window. Only NVIDIA's own driver takes the optimal path; the check
|
|
+// runs once, with the presenting GL context current.
|
|
+static bool interopUsesLinearTiling() {
|
|
+ static const bool linear = []() {
|
|
+ const GLubyte* vendor = glGetString(GL_VENDOR);
|
|
+ return !(vendor && strstr(reinterpret_cast<const char*>(vendor), "NVIDIA"));
|
|
+ }();
|
|
+ return linear;
|
|
+}
|
|
+
|
|
//
|
|
// helper classes
|
|
//
|
|
@@ -129,27 +239,109 @@ public:
|
|
// texture
|
|
//
|
|
|
|
-static size_t findSharedMemory(const VkMemoryRequirements& reqs, std::shared_ptr<Indium::PrivateDevice> device, bool framebufferOnly) {
|
|
+static std::string memoryFlagsString(VkMemoryPropertyFlags flags) {
|
|
+ char buffer[16];
|
|
+ snprintf(buffer, sizeof(buffer), "0x%x(", (unsigned) flags);
|
|
+ std::string out = buffer;
|
|
+ if (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) out += "DEVICE_LOCAL|";
|
|
+ if (flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) out += "HOST_VISIBLE|";
|
|
+ if (flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) out += "HOST_COHERENT|";
|
|
+ if (flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) out += "HOST_CACHED|";
|
|
+ if (flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) out += "LAZILY_ALLOCATED|";
|
|
+ if (flags & VK_MEMORY_PROPERTY_PROTECTED_BIT) out += "PROTECTED|";
|
|
+ if (flags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD) out += "DEVICE_COHERENT_AMD|";
|
|
+ if (flags & VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD) out += "DEVICE_UNCACHED_AMD|";
|
|
+ if (out.back() == '|') out.back() = ')';
|
|
+ else out += "none)";
|
|
+ return out;
|
|
+}
|
|
+
|
|
+// Vulkan orders memory types so a flag-less type (system RAM, which NVIDIA
|
|
+// exposes) precedes every DEVICE_LOCAL type, and first fit put framebufferOnly
|
|
+// drawables there. Both images are only touched by the GPU then -- the internal
|
|
+// one is imported by the presenting OpenGL context, which runs on the same GPU
|
|
+// (see reportGpuDeviceMatch) -- so they take the first allowed DEVICE_LOCAL type,
|
|
+// falling back to first fit. Without framebufferOnly they still need host-visible,
|
|
+// coherent memory and keep first fit. On RADV type 0 is DEVICE_LOCAL: unchanged.
|
|
+//
|
|
+// League sets framebufferOnly=NO, so its drawables lived in system RAM and crossed
|
|
+// PCIe every frame. framebufferOnly=NO lets Metal sample or blit the texture; it
|
|
+// does not make it CPU-mapped, and nothing here maps either image (replaceRegion
|
|
+// aborts, getBytes is a stub), so those drawables are placed like framebufferOnly
|
|
+// ones too. On the RX 6600 that gave about 20 FPS more in a practice-tool fight and
|
|
+// the same FPS when idle. The guest flag file /tmp/metal-no-drawable-vram restores
|
|
+// host-visible memory. Read once per process, when the first drawable is created.
|
|
+static size_t findSharedMemory(const VkMemoryRequirements& reqs, std::shared_ptr<Indium::PrivateDevice> device, bool framebufferOnly, bool internal) {
|
|
+ static const bool drawableVram = access("/tmp/metal-no-drawable-vram", F_OK) != 0;
|
|
+ static std::once_flag logSwitch;
|
|
+ std::call_once(logSwitch, []() {
|
|
+ appendDeviceLog(std::string("drawable-vram=") + (drawableVram ? "on" : "off (file:/tmp/metal-no-drawable-vram)"));
|
|
+ });
|
|
+
|
|
+ const bool vramDrawable = !framebufferOnly && drawableVram;
|
|
+ const bool gpuOnly = framebufferOnly || vramDrawable;
|
|
+ const auto& memory = device->memoryProperties();
|
|
+ size_t firstFit = SIZE_MAX;
|
|
+ size_t legacy = SIZE_MAX; // the choice before 2026-10-16 (host-visible drawables), for the log
|
|
size_t targetIndex = SIZE_MAX;
|
|
|
|
- for (size_t i = 0; i < device->memoryProperties().memoryTypeCount; ++i) {
|
|
- const auto& type = device->memoryProperties().memoryTypes[i];
|
|
+ for (size_t i = 0; i < memory.memoryTypeCount; ++i) {
|
|
+ const auto& type = memory.memoryTypes[i];
|
|
|
|
- if ((reqs.memoryTypeBits & (1 << i)) == 0) {
|
|
+ if ((reqs.memoryTypeBits & (1u << i)) == 0) {
|
|
continue;
|
|
}
|
|
|
|
- if (!framebufferOnly && (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) {
|
|
- continue;
|
|
+ const bool hostVisible = (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0 && (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
|
|
+ if (legacy == SIZE_MAX && (framebufferOnly || hostVisible)) {
|
|
+ legacy = i;
|
|
}
|
|
|
|
- if (!framebufferOnly && (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0) {
|
|
+ if (!gpuOnly && !hostVisible) {
|
|
continue;
|
|
}
|
|
|
|
- // okay, this is good enough
|
|
- targetIndex = i;
|
|
- break;
|
|
+ if (firstFit == SIZE_MAX) {
|
|
+ firstFit = i;
|
|
+ }
|
|
+
|
|
+ // no early exit: the scan continues to find the legacy choice for the log
|
|
+ if (targetIndex == SIZE_MAX && (!gpuOnly || (type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0)) {
|
|
+ targetIndex = i;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (targetIndex == SIZE_MAX) {
|
|
+ targetIndex = firstFit;
|
|
+ }
|
|
+ if (targetIndex == SIZE_MAX) {
|
|
+ return targetIndex;
|
|
+ }
|
|
+
|
|
+ // Once per (image, storage, type) in the process, in Indium's device log.
|
|
+ static std::atomic<uint64_t> logged[3] {};
|
|
+ const size_t storage = vramDrawable ? 2 : (framebufferOnly ? 1 : 0);
|
|
+ const size_t bit = ((internal ? 3u : 0u) + storage) * 32 + targetIndex;
|
|
+ const uint64_t mask = uint64_t(1) << (bit & 63);
|
|
+ if ((logged[bit >> 6].load(std::memory_order_relaxed) & mask) == 0 && (logged[bit >> 6].fetch_or(mask) & mask) == 0) {
|
|
+ auto describe = [&](size_t index) {
|
|
+ if (index == SIZE_MAX) {
|
|
+ return std::string("none");
|
|
+ }
|
|
+ const auto& type = memory.memoryTypes[index];
|
|
+ return std::to_string(index) + " flags=" + memoryFlagsString(type.propertyFlags)
|
|
+ + " heap=" + std::to_string(type.heapIndex)
|
|
+ + " heapDeviceLocal=" + ((memory.memoryHeaps[type.heapIndex].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) ? "yes" : "no");
|
|
+ };
|
|
+ static const char* storageNames[] = { "hostVisible", "framebufferOnly", "vram" };
|
|
+ char typeBits[16];
|
|
+ snprintf(typeBits, sizeof(typeBits), "0x%08x", (unsigned) reqs.memoryTypeBits);
|
|
+ appendDeviceLog(std::string("memory alloc kind=") + (internal ? "drawable-internal" : "drawable")
|
|
+ + " storage=" + storageNames[storage]
|
|
+ + " typeBits=" + typeBits
|
|
+ + " type=" + describe(targetIndex)
|
|
+ + " firstFit=" + describe(legacy)
|
|
+ + (targetIndex != legacy ? " changed=yes" : " changed=no"));
|
|
}
|
|
|
|
return targetIndex;
|
|
@@ -161,6 +353,11 @@ CAMetalDrawableTexture::CAMetalDrawableTexture(CGSize size, Indium::PixelFormat
|
|
_pixelFormat(pixelFormat),
|
|
_framebufferOnly(framebufferOnly)
|
|
{
|
|
+ // The layer's GL context is current here (recreateDrawables / _setContext set
|
|
+ // it), so this is the first point where the presenting GL device can be
|
|
+ // compared with the Vulkan device. Logged once per process.
|
|
+ reportGpuDeviceMatch(privateDevice);
|
|
+
|
|
//
|
|
// create the images
|
|
//
|
|
@@ -195,7 +392,7 @@ CAMetalDrawableTexture::CAMetalDrawableTexture(CGSize size, Indium::PixelFormat
|
|
// some drivers (e.g. AMD's drivers) don't play nice when sharing
|
|
// Vulkan images and OpenGL textures with optimal tiling
|
|
// (https://gitlab.freedesktop.org/mesa/mesa/-/issues/7657)
|
|
- imgInfo.tiling = VK_IMAGE_TILING_LINEAR;
|
|
+ imgInfo.tiling = interopUsesLinearTiling() ? VK_IMAGE_TILING_LINEAR : VK_IMAGE_TILING_OPTIMAL;
|
|
|
|
VkExternalMemoryImageCreateInfo extMemInfo {};
|
|
|
|
@@ -219,7 +416,7 @@ CAMetalDrawableTexture::CAMetalDrawableTexture(CGSize size, Indium::PixelFormat
|
|
|
|
DynamicVK::vkGetImageMemoryRequirements(_device->device(), _image, &reqs);
|
|
|
|
- size_t targetIndex = findSharedMemory(reqs, _device, _framebufferOnly);
|
|
+ size_t targetIndex = findSharedMemory(reqs, _device, _framebufferOnly, false);
|
|
|
|
if (targetIndex == SIZE_MAX) {
|
|
throw std::runtime_error("No suitable memory region found for image");
|
|
@@ -242,7 +439,7 @@ CAMetalDrawableTexture::CAMetalDrawableTexture(CGSize size, Indium::PixelFormat
|
|
|
|
DynamicVK::vkGetImageMemoryRequirements(_device->device(), _internalImage, &reqs);
|
|
|
|
- targetIndex = findSharedMemory(reqs, _device, _framebufferOnly);
|
|
+ targetIndex = findSharedMemory(reqs, _device, _framebufferOnly, true);
|
|
|
|
if (targetIndex == SIZE_MAX) {
|
|
throw std::runtime_error("No suitable memory region found for internal image");
|
|
@@ -380,31 +577,70 @@ CAMetalDrawableTexture::CAMetalDrawableTexture(CGSize size, Indium::PixelFormat
|
|
abort();
|
|
}
|
|
|
|
- reportGLErrors();
|
|
+ // reportGLErrors() is compiled out, so a failed import used to be silent (a black
|
|
+ // window). Clear errors left by earlier calls; the import is checked below.
|
|
+ drainGLErrors();
|
|
|
|
// import the memory into OpenGL
|
|
glCreateMemoryObjectsEXT(1, &_memoryObject);
|
|
reportGLErrors();
|
|
|
|
+ // _internalMemory is a dedicated allocation (VkMemoryDedicatedAllocateInfo above),
|
|
+ // and EXT_external_objects requires DEDICATED_MEMORY_OBJECT_EXT to be TRUE when the
|
|
+ // memory was created as one. Mesa ignores the flag; NVIDIA enforces it. It has to be
|
|
+ // set before the import, after which the memory object is immutable.
|
|
+ GLint dedicated = GL_TRUE;
|
|
+ glMemoryObjectParameterivEXT(_memoryObject, GL_DEDICATED_MEMORY_OBJECT_EXT, &dedicated);
|
|
+ const GLenum dedicatedError = glGetError();
|
|
+
|
|
// this transfers ownership of the FD to OpenGL
|
|
glImportMemoryFdEXT(_memoryObject, reqs.size, GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd);
|
|
fd = -1;
|
|
- reportGLErrors();
|
|
+ const GLenum importError = glGetError();
|
|
|
|
GLint prevTex = 0;
|
|
glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevTex);
|
|
|
|
glCreateTextures(GL_TEXTURE_2D, 1, &_textureID);
|
|
reportGLErrors();
|
|
- glTextureParameteri(_textureID, GL_TEXTURE_TILING_EXT, GL_LINEAR_TILING_EXT /*GL_OPTIMAL_TILING_EXT*/);
|
|
+ if (interopUsesLinearTiling()) {
|
|
+ glTextureParameteri(_textureID, GL_TEXTURE_TILING_EXT, GL_LINEAR_TILING_EXT);
|
|
+ }
|
|
glTextureParameteri(_textureID, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTextureParameteri(_textureID, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTextureParameteri(_textureID, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTextureParameteri(_textureID, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
- reportGLErrors();
|
|
+ // A Metal layer is opaque: macOS ignores the drawable's alpha channel. The game's blended effects lower destination
|
|
+ // alpha as a side effect, and the layer compositor (CARenderer: blending plus an alpha test against 0, over a
|
|
+ // cleared-to-black window) then showed those regions as black quads. Always present alpha = 1.
|
|
+ glTextureParameteri(_textureID, GL_TEXTURE_SWIZZLE_A, GL_ONE);
|
|
+ const GLenum setupError = glGetError();
|
|
|
|
glTextureStorageMem2DEXT(_textureID, 1, GL_RGBA8, _size.width, _size.height, _memoryObject, 0);
|
|
- reportGLErrors();
|
|
+ const GLenum storageError = glGetError();
|
|
+
|
|
+ // The first import in the process is always logged, so a working run on a new
|
|
+ // driver shows up too; after that only failed imports, at most five.
|
|
+ static std::atomic<unsigned> importCount {0};
|
|
+ static std::atomic<unsigned> importFailures {0};
|
|
+ const bool failed = dedicatedError != GL_NO_ERROR || importError != GL_NO_ERROR || setupError != GL_NO_ERROR || storageError != GL_NO_ERROR;
|
|
+ const bool first = importCount.fetch_add(1, std::memory_order_relaxed) == 0;
|
|
+ if (first || (failed && importFailures.fetch_add(1, std::memory_order_relaxed) < 5)) {
|
|
+ GLint dedicatedReadBack = -1;
|
|
+ GLint tiling = 0;
|
|
+ glGetMemoryObjectParameterivEXT(_memoryObject, GL_DEDICATED_MEMORY_OBJECT_EXT, &dedicatedReadBack);
|
|
+ glGetTextureParameteriv(_textureID, GL_TEXTURE_TILING_EXT, &tiling);
|
|
+ const GLenum queryError = glGetError();
|
|
+ char line[512];
|
|
+ snprintf(line, sizeof(line), "drawable GL import%s dedicated=%s size=%llu %dx%d memoryType=%zu vkTiling=%s glTiling=%s"
|
|
+ " dedicated_error=0x%x import_error=0x%x setup_error=0x%x storage_error=0x%x query_error=0x%x%s",
|
|
+ first ? " (first)" : "", dedicatedReadBack == GL_TRUE ? "yes" : (dedicatedReadBack == -1 ? "unknown" : "no"),
|
|
+ (unsigned long long) reqs.size, (int) _size.width, (int) _size.height, targetIndex,
|
|
+ interopUsesLinearTiling() ? "linear" : "optimal",
|
|
+ tiling == GL_LINEAR_TILING_EXT ? "linear" : (tiling == GL_OPTIMAL_TILING_EXT ? "optimal" : "unknown"),
|
|
+ dedicatedError, importError, setupError, storageError, queryError, failed ? " FAILED" : " ok");
|
|
+ appendDeviceLog(line);
|
|
+ }
|
|
|
|
glBindTexture(GL_TEXTURE_2D, prevTex);
|
|
};
|
|
@@ -605,35 +841,16 @@ void CAMetalDrawableActual::present() {
|
|
return;
|
|
}
|
|
|
|
- [layer queuePresent: _drawableID];
|
|
|
|
_semaphore = _texture->synchronizePresentation();
|
|
|
|
- if (_semaphore) {
|
|
- int fd = -1;
|
|
- VkSemaphoreGetFdInfoKHR info {};
|
|
+ // present() may run on the deferred encoding worker. The window's
|
|
+ // EGL context remains current on the main thread, so attempting to
|
|
+ // import a semaphore here fails and leaks the exported syncobj FD.
|
|
+ // Import it in synchronizeRender(), with the compositor context
|
|
+ // current, immediately before the GPU-side wait.
|
|
|
|
- info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
|
|
- info.semaphore = _semaphore->semaphore;
|
|
- info.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
|
|
-
|
|
- if (DynamicVK::vkGetSemaphoreFdKHR(_semaphore->device->device(), &info, &fd) != VK_SUCCESS) {
|
|
- // TODO
|
|
- abort();
|
|
- }
|
|
-
|
|
- CGLContextObj prev = CGLGetCurrentContext();
|
|
- CGLSetCurrentContext(_glContext);
|
|
-
|
|
- reportGLErrors();
|
|
- glGenSemaphoresEXT(1, &_glSemaphore);
|
|
- reportGLErrors();
|
|
- glImportSemaphoreFdEXT(_glSemaphore, GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd); // this consumes the FD
|
|
- fd = -1;
|
|
- reportGLErrors();
|
|
-
|
|
- CGLSetCurrentContext(prev);
|
|
- }
|
|
+ [layer queuePresent: _drawableID];
|
|
}
|
|
}
|
|
|
|
@@ -702,9 +919,46 @@ void CAMetalDrawableActual::reset() {
|
|
};
|
|
|
|
void CAMetalDrawableActual::synchronizeRender() {
|
|
- GLuint tex = _texture->glTexture();
|
|
- GLenum layout = GL_LAYOUT_GENERAL_EXT;
|
|
- glWaitSemaphoreEXT(_glSemaphore, 0, NULL, 1, &tex, &layout);
|
|
+ if (!_semaphore) return;
|
|
+ // GL errors of the import and wait are checked for the first imports of the
|
|
+ // process only: glGetError can stall a threaded GL driver, and this runs every frame.
|
|
+ static std::atomic<unsigned> checkedImports {0};
|
|
+ bool check = false;
|
|
+ GLenum importError = GL_NO_ERROR;
|
|
+ if (_glSemaphore == 0) {
|
|
+ // prepareRender() has made the layer's GL context current on this
|
|
+ // thread. Never try to steal it from the submission worker.
|
|
+ glGenSemaphoresEXT(1, &_glSemaphore);
|
|
+ if (_glSemaphore == 0) abort();
|
|
+ int fd = -1;
|
|
+ VkSemaphoreGetFdInfoKHR info {};
|
|
+ info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
|
|
+ info.semaphore = _semaphore->semaphore;
|
|
+ info.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
|
|
+ if (DynamicVK::vkGetSemaphoreFdKHR(_semaphore->device->device(), &info, &fd) != VK_SUCCESS) abort();
|
|
+ check = checkedImports.fetch_add(1, std::memory_order_relaxed) < 600;
|
|
+ if (check) drainGLErrors();
|
|
+ glImportSemaphoreFdEXT(_glSemaphore, GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd);
|
|
+ // A successful import transfers the native FD to the GL driver.
|
|
+ if (check) importError = glGetError();
|
|
+ }
|
|
+ GLuint tex = _texture->glTexture();
|
|
+ GLenum layout = GL_LAYOUT_GENERAL_EXT;
|
|
+ glWaitSemaphoreEXT(_glSemaphore, 0, NULL, 1, &tex, &layout);
|
|
+ if (check) {
|
|
+ // One line on first use, then only failures (at most five).
|
|
+ const GLenum waitError = glGetError();
|
|
+ const bool failed = importError != GL_NO_ERROR || waitError != GL_NO_ERROR;
|
|
+ static std::atomic<bool> loggedFirst {false};
|
|
+ static std::atomic<unsigned> failures {0};
|
|
+ const bool first = !loggedFirst.exchange(true, std::memory_order_relaxed);
|
|
+ if (first || (failed && failures.fetch_add(1, std::memory_order_relaxed) < 5)) {
|
|
+ char line[192];
|
|
+ snprintf(line, sizeof(line), "drawable GL semaphore%s import_error=0x%x wait_error=0x%x%s (checked for the first 600 imports)",
|
|
+ first ? " (first)" : "", importError, waitError, failed ? " FAILED" : " ok");
|
|
+ appendDeviceLog(line);
|
|
+ }
|
|
+ }
|
|
};
|
|
|
|
static void glDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* context) {
|
|
diff --git a/QuartzCore/CAMetalLayer.mm b/QuartzCore/CAMetalLayer.mm
|
|
index b81e2e14..63533444 100644
|
|
--- a/QuartzCore/CAMetalLayer.mm
|
|
+++ b/QuartzCore/CAMetalLayer.mm
|
|
@@ -25,7 +25,79 @@
|
|
#import <QuartzCore/CALayerContext.h>
|
|
#import <Metal/stubs.h>
|
|
|
|
+// Wait-site counters (flag file /tmp/metal-debug-waits, checked once at process start): the same summary the Indium
|
|
+// hand-off waits write, so the drawable hand-out and the command-buffer hand-offs appear in one histogram.
|
|
+#include <indium/waits.private.hpp>
|
|
+
|
|
#include <algorithm>
|
|
+#include <cstdio>
|
|
+#include <unistd.h>
|
|
+#include <map>
|
|
+#include <mutex>
|
|
+#include <string>
|
|
+#include <vector>
|
|
+#import <dispatch/dispatch.h>
|
|
+
|
|
+// Diagnostics are off unless /tmp/metal-debug-trace exists when the process starts.
|
|
+static bool darlingMetalDebug()
|
|
+{
|
|
+ static const bool enabled = access("/tmp/metal-debug-trace", F_OK) == 0;
|
|
+ return enabled;
|
|
+}
|
|
+
|
|
+// Diagnostic trace of the drawable/presentation flow, written to the guest's /tmp/metal-trace.log.
|
|
+// Rate limited: the first events and then every hundredth.
|
|
+static void darlingMetalTrace(const char* what, long a, long b)
|
|
+{
|
|
+ if (!darlingMetalDebug()) {
|
|
+ return;
|
|
+ }
|
|
+ static int count = 0;
|
|
+ int n = __sync_add_and_fetch(&count, 1);
|
|
+ if (n > 80 && n % 100 != 0) {
|
|
+ return;
|
|
+ }
|
|
+ if (FILE* log = fopen("/tmp/metal-trace.log", "a")) {
|
|
+ fprintf(log, "%.3f #%d %s main=%d a=%ld b=%ld\n", CACurrentMediaTime(), n, what, (int) [NSThread isMainThread], a, b);
|
|
+ fclose(log);
|
|
+ }
|
|
+}
|
|
+
|
|
+// Cheap aggregated timings (flag file /tmp/metal-debug-timing in the guest when the process starts), printed every 5 s.
|
|
+static void darlingMetalTiming(const char* what, double seconds)
|
|
+{
|
|
+ static const bool enabled = access("/tmp/metal-debug-timing", F_OK) == 0;
|
|
+ if (!enabled) {
|
|
+ return;
|
|
+ }
|
|
+ struct Stat { double sum = 0, max = 0; unsigned long count = 0; };
|
|
+ static std::mutex lock;
|
|
+ static std::map<std::string, Stat> stats;
|
|
+ static double last = 0;
|
|
+ std::lock_guard<std::mutex> guard(lock);
|
|
+ Stat& stat = stats[what];
|
|
+ stat.sum += seconds;
|
|
+ stat.count++;
|
|
+ if (seconds > stat.max) stat.max = seconds;
|
|
+ double now = CACurrentMediaTime();
|
|
+ if (now - last >= 5) {
|
|
+ last = now;
|
|
+ if (FILE* log = fopen("/tmp/metal-trace.log", "a")) {
|
|
+ fprintf(log, "timing@%.0f:", now);
|
|
+ for (auto& entry: stats) {
|
|
+ fprintf(log, " %s[n=%lu avg=%.0fms max=%.0fms]", entry.first.c_str(), entry.second.count, entry.second.sum / entry.second.count * 1000, entry.second.max * 1000);
|
|
+ }
|
|
+ fprintf(log, "\n");
|
|
+ fclose(log);
|
|
+ }
|
|
+ stats.clear();
|
|
+ }
|
|
+}
|
|
+
|
|
+extern "C" void darlingQCTiming(const char* what, double seconds)
|
|
+{
|
|
+ darlingMetalTiming(what, seconds);
|
|
+}
|
|
|
|
#import "CALayerInternal.h"
|
|
|
|
@@ -205,6 +277,65 @@ static void reportGLErrors(void) {
|
|
|
|
@end
|
|
|
|
+#include <array>
|
|
+#include <atomic>
|
|
+#include <chrono>
|
|
+#include <mutex>
|
|
+#include <unistd.h>
|
|
+namespace {
|
|
+struct PresentIntervalSample { std::atomic<int64_t> queued{0}, renderStart{0}, renderDone{0}, flushDone{0}; };
|
|
+static int64_t presentIntervalNow() { return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count(); }
|
|
+static PresentIntervalSample* beginPresentIntervalSample() {
|
|
+ static const bool enabled = [] { FILE* flag = fopen("/tmp/metal-frame-interval-capture", "r"); if (!flag) return false; fclose(flag); return true; }();
|
|
+ if (!enabled) return nullptr;
|
|
+ static std::atomic<bool> finished{false};
|
|
+ static std::mutex captureMutex;
|
|
+ // Never reuse a callback's timestamp storage. A delayed main-queue callback
|
|
+ // must not stall the recorder or write into a subsequent capture. All
|
|
+ // storage stays alive until process exit; 64 blocks cap its size at 4 MiB.
|
|
+ static std::array<std::array<PresentIntervalSample,2048>,64> blocks;
|
|
+ static size_t count = 0;
|
|
+ static unsigned block = 0, seriesBlocks = 0;
|
|
+ static const bool testing = getenv("RIOT_FRAME_CAPTURE_TEST") != nullptr;
|
|
+ if (finished.load(std::memory_order_relaxed)) {
|
|
+ static std::atomic<unsigned> checks{0};
|
|
+ if (checks.fetch_add(1, std::memory_order_relaxed) % (testing ? 8 : 128)) return nullptr;
|
|
+ // Explicitly rearm a bounded eight-block capture in the same match. Keep
|
|
+ // filenames increasing so an earlier workload's measurements survive.
|
|
+ if (access("/tmp/metal-frame-interval-repeat", F_OK) != 0) return nullptr;
|
|
+ std::lock_guard<std::mutex> guard(captureMutex);
|
|
+ if (finished.load(std::memory_order_relaxed) && block < blocks.size() &&
|
|
+ unlink("/tmp/metal-frame-interval-repeat") == 0) {
|
|
+ count = 0; seriesBlocks = 0;
|
|
+ finished.store(false, std::memory_order_relaxed);
|
|
+ }
|
|
+ }
|
|
+ std::lock_guard<std::mutex> guard(captureMutex);
|
|
+ if (finished.load(std::memory_order_relaxed)) return nullptr;
|
|
+ static const int64_t start = presentIntervalNow();
|
|
+ const int64_t now = presentIntervalNow();
|
|
+ if (!testing && now - start < 90000000000LL) return nullptr;
|
|
+ auto& samples = blocks[block];
|
|
+ static const size_t limit = testing ? 8 : samples.size();
|
|
+ if (count < limit) { samples[count].queued.store(now); return &samples[count++]; }
|
|
+ // Completion columns may still be zero; the analyzer labels incomplete rows.
|
|
+ // Queued timestamps already cover every request in the bounded block.
|
|
+ char path[96];
|
|
+ if (block == 0) snprintf(path,sizeof(path),"/tmp/metal-present-intervals.csv");
|
|
+ else snprintf(path,sizeof(path),"/tmp/metal-present-intervals-%u.csv",block);
|
|
+ if (FILE* log=fopen(path,"w")) {
|
|
+ fprintf(log,"queued_ns,render_start_ns,render_done_ns,flush_done_ns\n");
|
|
+ for (size_t i=0;i<limit;++i) { const auto& sample=samples[i]; fprintf(log,"%lld,%lld,%lld,%lld\n",(long long)sample.queued.load(),(long long)sample.renderStart.load(),(long long)sample.renderDone.load(),(long long)sample.flushDone.load()); }
|
|
+ fclose(log);
|
|
+ }
|
|
+ ++block;
|
|
+ if (++seriesBlocks >= 8 || block >= blocks.size()) { finished.store(true, std::memory_order_relaxed); return nullptr; }
|
|
+ count=0;
|
|
+ // This request covers file I/O and is deliberately outside both sample blocks.
|
|
+ return nullptr;
|
|
+}
|
|
+}
|
|
+
|
|
@implementation CAMetalLayerInternal
|
|
|
|
#if DARLING_METAL_ENABLED
|
|
@@ -363,12 +494,15 @@ static void reportGLErrors(void) {
|
|
- (id<CAMetalDrawable>)nextDrawable
|
|
{
|
|
std::shared_ptr<CAMetalDrawableActual> drawable = nullptr;
|
|
+ CFTimeInterval waitStart = CACurrentMediaTime();
|
|
|
|
[_drawableCondition lock];
|
|
|
|
NSDate* endTime = [NSDate dateWithTimeIntervalSinceNow: 1];
|
|
|
|
while (_usableDrawablesBitmap == 0) {
|
|
+ // No drawable is free: this thread really blocks here (the wait is what the pool starvation costs the game).
|
|
+ Indium::Waits::Block block("nextDrawable");
|
|
if (_allowsNextDrawableTimeout) {
|
|
if (![_drawableCondition waitUntilDate: endTime]) {
|
|
break;
|
|
@@ -380,6 +514,8 @@ static void reportGLErrors(void) {
|
|
|
|
if (_usableDrawablesBitmap == 0) {
|
|
// timeout reached
|
|
+ darlingMetalTiming("nextDrawableTimeout", CACurrentMediaTime() - waitStart);
|
|
+ darlingMetalTrace("nextDrawable-timeout", (long) _queuedDrawableCount, 0);
|
|
[_drawableCondition unlock];
|
|
return nil;
|
|
}
|
|
@@ -400,6 +536,8 @@ static void reportGLErrors(void) {
|
|
}
|
|
|
|
_usableDrawablesBitmap &= ~(1u << drawableID);
|
|
+ darlingMetalTiming("nextDrawableWait", CACurrentMediaTime() - waitStart);
|
|
+ darlingMetalTrace("nextDrawable-ok", (long) drawableID, (long) _usableDrawablesBitmap);
|
|
|
|
drawable = _drawables[drawableID];
|
|
|
|
@@ -410,20 +548,36 @@ static void reportGLErrors(void) {
|
|
|
|
- (void)queuePresent: (NSUInteger)drawableID
|
|
{
|
|
+ auto* intervalSample = beginPresentIntervalSample();
|
|
[_drawableCondition lock];
|
|
_queuedDrawables[_queuedDrawableCount] = drawableID;
|
|
++_queuedDrawableCount;
|
|
+ darlingMetalTrace("queuePresent", (long) drawableID, (long) _queuedDrawableCount);
|
|
[_drawableCondition unlock];
|
|
|
|
// we now need to schedule a render
|
|
//
|
|
// FIXME: once we fix up CALayer and make it more featureful, this needs to change to `self.needsDisplay = YES` or equivalently `[self setNeedsDisplay: YES]`
|
|
// right now, CALayer is missing all the needs-display logic (which is currently in NSView)
|
|
- [self display];
|
|
+ // Present only after the Vulkan/GL synchronization has been prepared, on
|
|
+ // the window thread. display alone does not drive the layer compositor.
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
+ if (intervalSample) intervalSample->renderStart.store(presentIntervalNow());
|
|
+ darlingMetalTrace("present-block-runs", 0, 0);
|
|
+ CFTimeInterval blockStart = CACurrentMediaTime();
|
|
+ [self->_context render];
|
|
+ if (intervalSample) intervalSample->renderDone.store(presentIntervalNow());
|
|
+ CFTimeInterval renderDone = CACurrentMediaTime();
|
|
+ darlingMetalTiming("mainBlockRender", renderDone - blockStart);
|
|
+ [self->_context flush];
|
|
+ if (intervalSample) intervalSample->flushDone.store(presentIntervalNow(), std::memory_order_release);
|
|
+ darlingMetalTiming("mainBlockFlush", CACurrentMediaTime() - renderDone);
|
|
+ });
|
|
}
|
|
|
|
- (void)releaseDrawable: (NSUInteger)drawableID
|
|
{
|
|
+ darlingMetalTrace("releaseDrawable", (long) drawableID, 0);
|
|
[_drawableCondition lock];
|
|
_usableDrawablesBitmap |= 1u << drawableID;
|
|
[_drawableCondition signal];
|
|
@@ -455,7 +609,7 @@ static void reportGLErrors(void) {
|
|
}
|
|
_usableDrawablesBitmap = 0;
|
|
|
|
- if (!_device || !_context || _drawableSize.width == 0 || _drawableSize.height == 0) {
|
|
+ if (!_device || !_context || !_context.glContext || _drawableSize.width == 0 || _drawableSize.height == 0) {
|
|
// can't create new drawables
|
|
[_drawableCondition unlock];
|
|
return;
|
|
@@ -506,6 +660,10 @@ static void reportGLErrors(void) {
|
|
glTextureParameteri(_tex, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTextureParameteri(_tex, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTextureParameteri(_tex, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
+ // The layer is opaque, as on macOS: never let the drawable's alpha reach the compositor (its blending and
|
|
+ // alpha test turned the game's low-alpha effect regions into black quads). The drawable is copied in with
|
|
+ // glCopyImageSubData, so the swizzle has to be on this texture, the one that is actually sampled.
|
|
+ glTextureParameteri(_tex, GL_TEXTURE_SWIZZLE_A, GL_ONE);
|
|
reportGLErrors();
|
|
}
|
|
}
|
|
@@ -516,6 +674,10 @@ static void reportGLErrors(void) {
|
|
- (void)prepareRender
|
|
{
|
|
std::shared_ptr<CAMetalDrawableActual> drawable = nullptr;
|
|
+ static CFTimeInterval lastPrepare = 0;
|
|
+ CFTimeInterval prepareStart = CACurrentMediaTime();
|
|
+ if (lastPrepare != 0) darlingMetalTiming("prepareRenderInterval", prepareStart - lastPrepare);
|
|
+ lastPrepare = prepareStart;
|
|
|
|
[_drawableCondition lock];
|
|
if (_queuedDrawableCount > 0) {
|
|
@@ -528,11 +690,62 @@ static void reportGLErrors(void) {
|
|
if (drawable) {
|
|
// wait for the drawable to be fully rendered before copying it to the render texture
|
|
// (this is a GPU-side wait)
|
|
+ CFTimeInterval syncStart = CACurrentMediaTime();
|
|
drawable->synchronizeRender();
|
|
+ darlingMetalTiming("synchronizeRender", CACurrentMediaTime() - syncStart);
|
|
|
|
+ reportGLErrors();
|
|
+ {
|
|
+ // Diagnostic: statistics of the imported Vulkan image itself, before the copy
|
|
+ static unsigned long sourceCounter = 0;
|
|
+ if (darlingMetalDebug() && access("/tmp/metal-debug-frames", F_OK) == 0 && (++sourceCounter <= 30 || sourceCounter % 150 == 0)) {
|
|
+ size_t width = _drawableSize.width, height = _drawableSize.height;
|
|
+ std::vector<uint8_t> source(width * height * 4);
|
|
+ glGetTextureImage(drawable->texture()->glTexture(), 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLsizei) source.size(), source.data());
|
|
+ unsigned long lit = 0, total = 0;
|
|
+ for (size_t i = 0; i < source.size(); i += 4) {
|
|
+ unsigned v = source[i] + source[i + 1] + source[i + 2];
|
|
+ total += v;
|
|
+ if (v > 6) ++lit;
|
|
+ }
|
|
+ darlingMetalTrace("source-stats", (long) lit, (long) (total / (width * height)));
|
|
+ }
|
|
+ }
|
|
reportGLErrors();
|
|
glCopyImageSubData(drawable->texture()->glTexture(), GL_TEXTURE_2D, 0, 0, 0, 0, _tex, GL_TEXTURE_2D, 0, 0, 0, 0, _drawableSize.width, _drawableSize.height, 1);
|
|
reportGLErrors();
|
|
+
|
|
+ // Diagnostic: every 150th presented frame, log pixel statistics of what is about to be shown
|
|
+ // and save a few of them as /tmp/metal-frame-<n>.ppm.
|
|
+ static unsigned long frameCounter = 0;
|
|
+ ++frameCounter;
|
|
+ if (darlingMetalDebug() && access("/tmp/metal-debug-frames", F_OK) == 0 && (frameCounter <= 30 || frameCounter % 150 == 0) && frameCounter <= 1800) {
|
|
+ size_t width = _drawableSize.width, height = _drawableSize.height;
|
|
+ std::vector<uint8_t> pixels(width * height * 4);
|
|
+ glGetTextureImage(_tex, 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLsizei) pixels.size(), pixels.data());
|
|
+ unsigned long nonBlack = 0, sum = 0;
|
|
+ for (size_t i = 0; i < pixels.size(); i += 4) {
|
|
+ unsigned v = pixels[i] + pixels[i + 1] + pixels[i + 2];
|
|
+ sum += v;
|
|
+ if (v > 6) ++nonBlack;
|
|
+ }
|
|
+ darlingMetalTrace("frame-stats", (long) nonBlack, (long) (sum / (width * height)));
|
|
+ if (frameCounter == 5 || frameCounter == 30 || frameCounter == 300 || frameCounter == 900) {
|
|
+ char path[64];
|
|
+ snprintf(path, sizeof(path), "/tmp/metal-frame-%lu.ppm", frameCounter);
|
|
+ if (FILE* out = fopen(path, "wb")) {
|
|
+ fprintf(out, "P6\n%zu %zu\n255\n", width, height);
|
|
+ for (size_t row = 0; row < height; ++row) {
|
|
+ // GL rows run bottom to top
|
|
+ const uint8_t* line = pixels.data() + (height - 1 - row) * width * 4;
|
|
+ for (size_t x = 0; x < width; ++x) {
|
|
+ fwrite(line + x * 4, 1, 3, out);
|
|
+ }
|
|
+ }
|
|
+ fclose(out);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
}
|
|
|
|
@synchronized(self) {
|
|
@@ -575,6 +788,11 @@ static void reportGLErrors(void) {
|
|
{
|
|
[super _setContext: context];
|
|
|
|
+ if (!context || !context.glContext) {
|
|
+ [self recreateDrawables];
|
|
+ return;
|
|
+ }
|
|
+
|
|
if (_tex == 0) {
|
|
// create our textures now using the context's CGLContext
|
|
CGLContextObj prev = CGLGetCurrentContext();
|
|
@@ -589,10 +807,15 @@ static void reportGLErrors(void) {
|
|
glTextureParameteri(_tex, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTextureParameteri(_tex, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTextureParameteri(_tex, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
+ // The layer is opaque, as on macOS: never let the drawable's alpha reach the compositor (its blending and
|
|
+ // alpha test turned the game's low-alpha effect regions into black quads). The drawable is copied in with
|
|
+ // glCopyImageSubData, so the swizzle has to be on this texture, the one that is actually sampled.
|
|
+ glTextureParameteri(_tex, GL_TEXTURE_SWIZZLE_A, GL_ONE);
|
|
reportGLErrors();
|
|
|
|
CGLSetCurrentContext(prev);
|
|
}
|
|
+ [self recreateDrawables];
|
|
}
|
|
|
|
#else
|
|
diff --git a/QuartzCore/CARenderer.m b/QuartzCore/CARenderer.m
|
|
index dd0a1270..b6916db3 100644
|
|
--- a/QuartzCore/CARenderer.m
|
|
+++ b/QuartzCore/CARenderer.m
|
|
@@ -1,10 +1,12 @@
|
|
#import <CoreVideo/CoreVideo.h>
|
|
+#import <IOSurface/IOSurface.h>
|
|
#import <Onyx2D/O2Surface.h>
|
|
#import <OpenGL/OpenGL.h>
|
|
#import <QuartzCore/CAAnimation.h>
|
|
#import <QuartzCore/CAMediaTimingFunction.h>
|
|
#import <QuartzCore/CARenderer.h>
|
|
#import "CALayerInternal.h"
|
|
+#include <stdlib.h>
|
|
|
|
NSString *const kCARendererColorSpace = @"kCARendererColorSpace";
|
|
|
|
@@ -221,13 +223,40 @@ static GLint interpolationFromName(NSString *name) {
|
|
}
|
|
|
|
void CATexImage2DCGImage(CGImageRef image) {
|
|
- size_t imageWidth = CGImageGetWidth(image);
|
|
- size_t imageHeight = CGImageGetHeight(image);
|
|
- CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image);
|
|
+ static const uint8_t kTransparent[4] = { 0, 0, 0, 0 };
|
|
+
|
|
+ size_t imageWidth = 0;
|
|
+ size_t imageHeight = 0;
|
|
+ CGBitmapInfo bitmapInfo = 0;
|
|
+ CGDataProviderRef provider = NULL;
|
|
+ CFDataRef data = NULL;
|
|
+ const uint8_t *pixelBytes = NULL;
|
|
+
|
|
+ /* darling-lol M57: layers can have nil contents, or a CGImage whose data
|
|
+ * provider is unavailable under Darling; the old code dereferenced those
|
|
+ * (O2DataProviderCopyData(NULL) / CFDataGetBytePtr(NULL)). */
|
|
+ if (image != NULL) {
|
|
+ provider = CGImageGetDataProvider(image);
|
|
+ if (provider != NULL)
|
|
+ data = CGDataProviderCopyData(provider);
|
|
+ }
|
|
+
|
|
+ if (data != NULL) {
|
|
+ imageWidth = CGImageGetWidth(image);
|
|
+ imageHeight = CGImageGetHeight(image);
|
|
+ bitmapInfo = CGImageGetBitmapInfo(image);
|
|
+ pixelBytes = CFDataGetBytePtr(data);
|
|
+ }
|
|
|
|
- CGDataProviderRef provider = CGImageGetDataProvider(image);
|
|
- CFDataRef data = CGDataProviderCopyData(provider);
|
|
- const uint8_t *pixelBytes = CFDataGetBytePtr(data);
|
|
+ if (pixelBytes == NULL) {
|
|
+ NSLog(@"LOLTEX no pixel data: image=%p provider=%p data=%p", image,
|
|
+ provider, data);
|
|
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA,
|
|
+ GL_UNSIGNED_BYTE, kTransparent);
|
|
+ if (data != NULL)
|
|
+ CFRelease(data);
|
|
+ return;
|
|
+ }
|
|
|
|
GLenum glFormat = GL_BGRA;
|
|
GLenum glType = GL_UNSIGNED_INT_8_8_8_8_REV;
|
|
@@ -272,6 +301,68 @@ void CATexImage2DCGImage(CGImageRef image) {
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, imageWidth, imageHeight, 0,
|
|
glFormat, glType, pixelBytes);
|
|
+
|
|
+ CFRelease(data);
|
|
+}
|
|
+
|
|
+/* darling-lol M57: Chromium's macOS compositor presents frames through
|
|
+ * DisplayCALayerTree, which sets CALayer.contents to an IOSurfaceRef (not a
|
|
+ * CGImage). Upload such a surface directly as a texture. */
|
|
+void CATexImage2DIOSurface(IOSurfaceRef surface) {
|
|
+ static const uint8_t kTransparent[4] = { 0, 0, 0, 0 };
|
|
+
|
|
+ size_t width = IOSurfaceGetWidth(surface);
|
|
+ size_t height = IOSurfaceGetHeight(surface);
|
|
+ size_t bytesPerRow = IOSurfaceGetBytesPerRow(surface);
|
|
+ OSType pixelFormat = IOSurfaceGetPixelFormat(surface);
|
|
+ void *base = IOSurfaceGetBaseAddress(surface);
|
|
+
|
|
+ /* darling-lol M57 diagnostic (opt-in): is the delivered compositor frame
|
|
+ * real? A zero base / all-zero pixels here means frame data never reached
|
|
+ * the browser process. */
|
|
+ if (getenv("M57_QC_TRACE") != 0) {
|
|
+ unsigned int z = 0;
|
|
+ unsigned long nonzero = 0, i;
|
|
+ if (base != NULL && width != 0) {
|
|
+ const unsigned char *p = (const unsigned char *)base;
|
|
+ for (i = 0; i < width * 4 && i < 4096; i++)
|
|
+ if (p[i]) nonzero++;
|
|
+ z = (unsigned int)nonzero;
|
|
+ }
|
|
+ NSLog(@"M57TEXIOS w=%zu h=%zu bpr=%zu fmt=%08x base=%p first4k_nonzero=%u",
|
|
+ width, height, bytesPerRow, (unsigned)pixelFormat, base, z);
|
|
+ }
|
|
+
|
|
+ if (base == NULL || width == 0 || height == 0) {
|
|
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA,
|
|
+ GL_UNSIGNED_BYTE, kTransparent);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ GLenum glFormat = GL_BGRA;
|
|
+ GLenum glType = GL_UNSIGNED_INT_8_8_8_8_REV;
|
|
+
|
|
+ switch (pixelFormat) {
|
|
+ case 'RGBA':
|
|
+ glFormat = GL_RGBA;
|
|
+ glType = GL_UNSIGNED_INT_8_8_8_8_REV;
|
|
+ break;
|
|
+ case 'BGRA':
|
|
+ default:
|
|
+ glFormat = GL_BGRA;
|
|
+ glType = GL_UNSIGNED_INT_8_8_8_8_REV;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
+ if (bytesPerRow != 0 && bytesPerRow >= width * 4)
|
|
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)(bytesPerRow / 4));
|
|
+
|
|
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei) width, (GLsizei) height,
|
|
+ 0, glFormat, glType, base);
|
|
+
|
|
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
|
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
}
|
|
|
|
- (void) _renderLayer: (CALayer *) layer
|
|
@@ -293,9 +384,14 @@ void CATexImage2DCGImage(CGImageRef image) {
|
|
}
|
|
|
|
if (loadPixelData) {
|
|
- CGImageRef image = (CGImageRef)layer.contents;
|
|
+ id contents = (id) layer.contents;
|
|
|
|
- CATexImage2DCGImage(image);
|
|
+ if (contents != nil &&
|
|
+ CFGetTypeID((CFTypeRef) contents) == IOSurfaceGetTypeID()) {
|
|
+ CATexImage2DIOSurface((IOSurfaceRef) contents);
|
|
+ } else {
|
|
+ CATexImage2DCGImage((CGImageRef) contents);
|
|
+ }
|
|
|
|
GLint minFilter = interpolationFromName(layer.minificationFilter);
|
|
GLint magFilter = interpolationFromName(layer.magnificationFilter);
|
|
diff --git a/QuartzCore/CATransaction.m b/QuartzCore/CATransaction.m
|
|
index 5f7998b9..b8968893 100644
|
|
--- a/QuartzCore/CATransaction.m
|
|
+++ b/QuartzCore/CATransaction.m
|
|
@@ -4,6 +4,10 @@
|
|
#import <Foundation/NSString.h>
|
|
#import <QuartzCore/CALayerContext.h>
|
|
#import <QuartzCore/CATransaction.h>
|
|
+#import <Block.h>
|
|
+#import <CoreFoundation/CFRunLoop.h>
|
|
+#include <stdlib.h>
|
|
+extern void DarlingCAPublishCommittedIOSurface(void);
|
|
|
|
NSString *const kCATransactionAnimationDuration =
|
|
@"kCATransactionAnimationDuration";
|
|
@@ -15,6 +19,63 @@ NSString *const kCATransactionCompletionBlock =
|
|
|
|
@implementation CATransaction
|
|
|
|
+/* Commit callbacks belong to the registering thread, and are consumed once. */
|
|
+typedef struct CommitHandler {
|
|
+ void (^block)(void);
|
|
+ CATransactionPhase phase;
|
|
+ struct CommitHandler *next;
|
|
+} CommitHandler;
|
|
+static __thread CommitHandler *pendingCommitHandlers;
|
|
+static __thread BOOL commitHandlersScheduled;
|
|
+static __thread BOOL drainingCommitHandlers;
|
|
+static __thread CATransactionGroup *implicitTransactionGroup;
|
|
+static CATransactionGroup *createImplicitTransactionGroupIfNeeded(void);
|
|
+
|
|
++ (void) addCommitHandler: (void (^)(void)) handler
|
|
+ forPhase: (CATransactionPhase) phase {
|
|
+ if (!handler || phase < 0 || phase > 2)
|
|
+ return;
|
|
+ createImplicitTransactionGroupIfNeeded();
|
|
+ CommitHandler *entry = calloc(1, sizeof(*entry));
|
|
+ if (!entry)
|
|
+ return;
|
|
+ entry->block = Block_copy(handler);
|
|
+ entry->phase = phase;
|
|
+ CommitHandler **tail = &pendingCommitHandlers;
|
|
+ while (*tail)
|
|
+ tail = &(*tail)->next;
|
|
+ *tail = entry;
|
|
+}
|
|
+
|
|
++ (void) _drainCommitHandlers {
|
|
+ CommitHandler *batch = pendingCommitHandlers;
|
|
+ pendingCommitHandlers = NULL;
|
|
+ commitHandlersScheduled = NO;
|
|
+ drainingCommitHandlers = YES;
|
|
+ for (int phase = 0; phase < 3; ++phase)
|
|
+ for (CommitHandler *entry = batch; entry; entry = entry->next)
|
|
+ if (entry->phase == phase)
|
|
+ entry->block();
|
|
+ while (batch) {
|
|
+ CommitHandler *next = batch->next;
|
|
+ Block_release(batch->block);
|
|
+ free(batch);
|
|
+ batch = next;
|
|
+ }
|
|
+ drainingCommitHandlers = NO;
|
|
+ [self _scheduleCommitHandlers];
|
|
+}
|
|
+
|
|
++ (void) setCompletionBlock: (void (^)(void)) block {
|
|
+ void (^copy)(void) = block ? Block_copy(block) : nil;
|
|
+ [self setValue: (id)copy forKey: kCATransactionCompletionBlock];
|
|
+ if (copy) Block_release(copy);
|
|
+}
|
|
+
|
|
++ (void (^)(void)) completionBlock {
|
|
+ return [self valueForKey: kCATransactionCompletionBlock];
|
|
+}
|
|
+
|
|
static NSMutableArray *transactionStack() {
|
|
NSMutableDictionary *shared = [NSCurrentThread() sharedDictionary];
|
|
id stack = [shared objectForKey: @"CATransactionStack"];
|
|
@@ -42,8 +103,10 @@ static CATransactionGroup *createImplicitTransactionGroupIfNeeded() {
|
|
check = [[CATransactionGroup alloc] init];
|
|
|
|
[transactionStack() addObject: check];
|
|
+ implicitTransactionGroup = check;
|
|
+ [check release];
|
|
[[NSRunLoop currentRunLoop]
|
|
- performSelector: @selector(commit)
|
|
+ performSelector: @selector(_commitImplicitTransaction)
|
|
target: [CATransaction class]
|
|
argument: nil
|
|
order: 0
|
|
@@ -99,13 +162,59 @@ static CATransactionGroup *createImplicitTransactionGroupIfNeeded() {
|
|
CATransactionGroup *group = [[CATransactionGroup alloc] init];
|
|
|
|
[transactionStack() addObject: group];
|
|
+ [group release];
|
|
+}
|
|
+
|
|
++ (void) _scheduleCommitHandlers {
|
|
+ if ([transactionStack() count] || !pendingCommitHandlers ||
|
|
+ commitHandlersScheduled || drainingCommitHandlers)
|
|
+ return;
|
|
+ /* Chromium's handlers may pump a nested runloop. Invoke them after the
|
|
+ * transaction has unwound, on its original thread. Drain new batches
|
|
+ * only after the current batch has completed all of its phases. */
|
|
+ commitHandlersScheduled = YES;
|
|
+ CFRunLoopRef loop = CFRunLoopGetCurrent();
|
|
+ CFRunLoopPerformBlock(loop, kCFRunLoopCommonModes, ^{
|
|
+ [CATransaction _drainCommitHandlers];
|
|
+ });
|
|
+ CFRunLoopWakeUp(loop);
|
|
+}
|
|
+
|
|
++ (void) _commitImplicitTransaction {
|
|
+ NSMutableArray *stack = transactionStack();
|
|
+ if ([stack count] == 1 && [stack lastObject] == implicitTransactionGroup)
|
|
+ [self commit];
|
|
}
|
|
|
|
+ (void) commit {
|
|
- [transactionStack() removeLastObject];
|
|
+ NSMutableArray *stack = transactionStack();
|
|
+ if ([stack count]) {
|
|
+ void (^completion)(void) = [[stack lastObject] valueForKey: kCATransactionCompletionBlock];
|
|
+ if (completion)
|
|
+ [self addCommitHandler: completion forPhase: kCATransactionPhasePostCommit];
|
|
+ if ([stack lastObject] == implicitTransactionGroup)
|
|
+ implicitTransactionGroup = nil;
|
|
+ [stack removeLastObject];
|
|
+ }
|
|
+ if ([stack count] == 1 && [stack lastObject] == implicitTransactionGroup) {
|
|
+ CFRunLoopRef loop = CFRunLoopGetCurrent();
|
|
+ CFRunLoopPerformBlock(loop, kCFRunLoopCommonModes, ^{
|
|
+ [CATransaction _commitImplicitTransaction];
|
|
+ });
|
|
+ CFRunLoopWakeUp(loop);
|
|
+ }
|
|
+ if (![stack count])
|
|
+ DarlingCAPublishCommittedIOSurface();
|
|
+ [self _scheduleCommitHandlers];
|
|
}
|
|
|
|
+ (void) flush {
|
|
+ NSMutableArray *stack = transactionStack();
|
|
+ BOOL commitsImplicit = [stack count] == 1 && [stack lastObject] == implicitTransactionGroup;
|
|
+ [self _commitImplicitTransaction];
|
|
+ /* An implicit commit already publishes its completed frame. */
|
|
+ if(!getenv("DARLING_CA_PRESENT_AT_COMMIT") || !commitsImplicit)
|
|
+ DarlingCAPublishCommittedIOSurface();
|
|
}
|
|
|
|
+ (void) lock {
|
|
diff --git a/QuartzCore/CATransactionGroup.m b/QuartzCore/CATransactionGroup.m
|
|
index 5a060cc3..572ddf65 100644
|
|
--- a/QuartzCore/CATransactionGroup.m
|
|
+++ b/QuartzCore/CATransactionGroup.m
|
|
@@ -21,7 +21,13 @@
|
|
}
|
|
|
|
- (void) setValue: value forKey: (NSString *) key {
|
|
- [_values setObject: value forKey: key];
|
|
+ if (value) [_values setObject: value forKey: key];
|
|
+ else [_values removeObjectForKey: key];
|
|
+}
|
|
+
|
|
+- (void) dealloc {
|
|
+ [_values release];
|
|
+ [super dealloc];
|
|
}
|
|
|
|
@end
|
|
diff --git a/QuartzCore/CATransformLayer.m b/QuartzCore/CATransformLayer.m
|
|
index 444122cc..81b01703 100644
|
|
--- a/QuartzCore/CATransformLayer.m
|
|
+++ b/QuartzCore/CATransformLayer.m
|
|
@@ -1,18 +1,42 @@
|
|
-#import <Foundation/NSObject.h>
|
|
+/*
|
|
+ * This file is part of Darling.
|
|
+ *
|
|
+ * Copyright (C) 2022 Darling developers
|
|
+ *
|
|
+ * Darling is free software: you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation, either version 3 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * Darling is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
+ */
|
|
|
|
-@interface CATransformLayer : NSObject
|
|
-@end
|
|
-
|
|
-@implementation CATransformLayer
|
|
+/*
|
|
+ * darling-lol M58F35:
|
|
+ *
|
|
+ * CATransformLayer was a stub NSObject whose forwardInvocation: merely logged
|
|
+ * "Stub called: …". Chromium (and AppKit) use CATransformLayer as a real layer
|
|
+ * inside the layer tree, so CALayer's recursive -_setContext:/-_setSuperLayer:
|
|
+ * and CALayer's/Chromium's -insertSublayer:atIndex: were dispatched to the stub
|
|
+ * and did nothing; the runtime's forward-signature check also emitted ~49k
|
|
+ * "NSForwardSignatureError: invoked with 3 args, but 2 expected" lines per run.
|
|
+ * The result was a broken layer tree (context never propagated to the
|
|
+ * transform-layer subtree — the compositor's frame never reached the tree).
|
|
+ *
|
|
+ * A CATransformLayer IS a CALayer (it only adds an is-3D flag and renders its
|
|
+ * sublayers in 3D); on macOS it responds to the whole CALayer surface. Deriving
|
|
+ * it from CALayer therefore restores the correct behaviour for every selector
|
|
+ * that was previously swallowed by the stub.
|
|
+ */
|
|
|
|
-- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
|
|
-{
|
|
- return [NSMethodSignature signatureWithObjCTypes: "v@:"];
|
|
-}
|
|
-
|
|
-- (void)forwardInvocation:(NSInvocation *)anInvocation
|
|
-{
|
|
- NSLog(@"Stub called: %@ in %@", NSStringFromSelector([anInvocation selector]), [self class]);
|
|
-}
|
|
+#import <QuartzCore/CALayer.h>
|
|
+#import <QuartzCore/CATransformLayer.h>
|
|
|
|
+@implementation CATransformLayer
|
|
@end
|
|
diff --git a/QuartzCore/CMakeLists.txt b/QuartzCore/CMakeLists.txt
|
|
index bc12fb7f..3ab43fd7 100644
|
|
--- a/QuartzCore/CMakeLists.txt
|
|
+++ b/QuartzCore/CMakeLists.txt
|
|
@@ -100,6 +100,7 @@ add_framework(QuartzCore
|
|
Onyx2D
|
|
OpenGL
|
|
CoreGraphics
|
|
+ IOSurface
|
|
Metal_private
|
|
${QUARTZCORE_LINK_INDIUM_PRIVATE}
|
|
cxx
|
|
@@ -116,7 +117,7 @@ set_target_properties(QuartzCore
|
|
CXX_STANDARD_REQUIRED ON
|
|
)
|
|
|
|
-add_dependencies(QuartzCore CoreImage CoreVideo)
|
|
+add_dependencies(QuartzCore CoreImage CoreVideo IOSurface)
|
|
|
|
target_include_directories(QuartzCore PUBLIC
|
|
include
|
|
diff --git a/QuartzCore/include/QuartzCore/CALayer.h b/QuartzCore/include/QuartzCore/CALayer.h
|
|
index 49b03483..5ebbf535 100644
|
|
--- a/QuartzCore/include/QuartzCore/CALayer.h
|
|
+++ b/QuartzCore/include/QuartzCore/CALayer.h
|
|
@@ -70,6 +70,7 @@ CA_EXPORT NSString *const kCAContentsFormatGray8Uint;
|
|
CGRect _bounds;
|
|
CGFloat _opacity;
|
|
BOOL _opaque;
|
|
+ BOOL _geometryFlipped;
|
|
id _contents;
|
|
CATransform3D _transform;
|
|
CATransform3D _sublayerTransform;
|
|
@@ -78,6 +79,42 @@ CA_EXPORT NSString *const kCAContentsFormatGray8Uint;
|
|
BOOL _needsDisplay;
|
|
NSMutableDictionary *_animations;
|
|
NSNumber *_textureId;
|
|
+
|
|
+ /* darling-lol M57: additional layer properties Chromium relies on. */
|
|
+ NSUInteger _autoresizingMask;
|
|
+ BOOL _masksToBounds;
|
|
+ CGFloat _cornerRadius;
|
|
+ CGColorRef _backgroundColor;
|
|
+ CGFloat _borderWidth;
|
|
+ CGColorRef _borderColor;
|
|
+ CGFloat _zPosition;
|
|
+ BOOL _hidden;
|
|
+ BOOL _doubleSided;
|
|
+ BOOL _needsDisplayOnBoundsChange;
|
|
+ BOOL _shouldRasterize;
|
|
+ CGFloat _rasterizationScale;
|
|
+ BOOL _allowsEdgeAntialiasing;
|
|
+ BOOL _allowsGroupOpacity;
|
|
+ BOOL _allowsGroupBlending;
|
|
+ BOOL _allowsHitTesting;
|
|
+ BOOL _drawsAsynchronously;
|
|
+ CGFloat _contentsScale;
|
|
+ NSString *_contentsGravity;
|
|
+ CGRect _contentsRect;
|
|
+ CGRect _contentsCenter;
|
|
+ CGFloat _anchorPointZ;
|
|
+ CGFloat _shadowOpacity;
|
|
+ CGSize _shadowOffset;
|
|
+ CGFloat _shadowRadius;
|
|
+ CGColorRef _shadowColor;
|
|
+ id _shadowPath;
|
|
+ float _minificationFilterBias;
|
|
+ unsigned int _edgeAntialiasingMask;
|
|
+ NSArray *_filters;
|
|
+ NSArray *_backgroundFilters;
|
|
+ id _compositingFilter;
|
|
+ NSDictionary *_style;
|
|
+ NSArray *_actions;
|
|
}
|
|
|
|
+ layer;
|
|
@@ -91,6 +128,41 @@ CA_EXPORT NSString *const kCAContentsFormatGray8Uint;
|
|
@property CGRect frame;
|
|
@property CGFloat opacity;
|
|
@property BOOL opaque;
|
|
+@property BOOL geometryFlipped;
|
|
+@property NSUInteger autoresizingMask;
|
|
+@property BOOL masksToBounds;
|
|
+@property CGFloat cornerRadius;
|
|
+@property CGColorRef backgroundColor;
|
|
+@property CGFloat borderWidth;
|
|
+@property CGColorRef borderColor;
|
|
+@property CGFloat zPosition;
|
|
+@property BOOL hidden;
|
|
+@property BOOL doubleSided;
|
|
+@property BOOL needsDisplayOnBoundsChange;
|
|
+@property BOOL shouldRasterize;
|
|
+@property CGFloat rasterizationScale;
|
|
+@property BOOL allowsEdgeAntialiasing;
|
|
+@property BOOL allowsGroupOpacity;
|
|
+@property BOOL allowsGroupBlending;
|
|
+@property BOOL allowsHitTesting;
|
|
+@property BOOL drawsAsynchronously;
|
|
+@property CGFloat contentsScale;
|
|
+@property(copy) NSString *contentsGravity;
|
|
+@property CGRect contentsRect;
|
|
+@property CGRect contentsCenter;
|
|
+@property CGFloat anchorPointZ;
|
|
+@property CGFloat shadowOpacity;
|
|
+@property CGSize shadowOffset;
|
|
+@property CGFloat shadowRadius;
|
|
+@property CGColorRef shadowColor;
|
|
+@property(retain) id shadowPath;
|
|
+@property float minificationFilterBias;
|
|
+@property unsigned int edgeAntialiasingMask;
|
|
+@property(copy) NSArray *filters;
|
|
+@property(copy) NSArray *backgroundFilters;
|
|
+@property(retain) id compositingFilter;
|
|
+@property(copy) NSDictionary *style;
|
|
+@property(copy) NSArray *actions;
|
|
@property(retain) id contents;
|
|
//@property CATransform3D transform;
|
|
@property CATransform3D sublayerTransform;
|
|
@@ -101,6 +173,9 @@ CA_EXPORT NSString *const kCAContentsFormatGray8Uint;
|
|
- init;
|
|
|
|
- (void) addSublayer: (CALayer *) layer;
|
|
+- (void) insertSublayer: (CALayer *) layer atIndex: (unsigned) index;
|
|
+- (void) insertSublayer: (CALayer *) layer below: (CALayer *) sibling;
|
|
+- (void) insertSublayer: (CALayer *) layer above: (CALayer *) sibling;
|
|
- (void) replaceSublayer: (CALayer *) layer with: (CALayer *) other;
|
|
- (void) display;
|
|
- (void) displayIfNeeded;
|
|
diff --git a/QuartzCore/include/QuartzCore/CALayerContext.h b/QuartzCore/include/QuartzCore/CALayerContext.h
|
|
index e4b0c342..d61c37ba 100644
|
|
--- a/QuartzCore/include/QuartzCore/CALayerContext.h
|
|
+++ b/QuartzCore/include/QuartzCore/CALayerContext.h
|
|
@@ -20,6 +20,7 @@
|
|
}
|
|
|
|
@property(readonly) CGLContextObj glContext;
|
|
+@property(readonly) CGSubWindow *subwindow;
|
|
|
|
- initWithFrame: (CGRect) rect;
|
|
|
|
diff --git a/QuartzCore/include/QuartzCore/CATransaction.h b/QuartzCore/include/QuartzCore/CATransaction.h
|
|
index 985f77f3..db64f55d 100644
|
|
--- a/QuartzCore/include/QuartzCore/CATransaction.h
|
|
+++ b/QuartzCore/include/QuartzCore/CATransaction.h
|
|
@@ -10,6 +10,23 @@ CA_EXPORT NSString *const kCATransactionCompletionBlock;
|
|
|
|
@interface CATransaction : NSObject
|
|
|
|
+/* darling-lol M57: Chromium schedules post-commit work with
|
|
+ * +[CATransaction addCommitHandler:forPhase:]; Cocotron did not implement it,
|
|
+ * so the UX aborted with an unrecognized-selector NSException right after
|
|
+ * createWindow(). The phase constants match macOS. */
|
|
+typedef NSInteger CATransactionPhase;
|
|
+enum {
|
|
+ kCATransactionPhasePreLayout = 0,
|
|
+ kCATransactionPhasePreCommit = 1,
|
|
+ kCATransactionPhasePostCommit = 2,
|
|
+};
|
|
+
|
|
++ (void) addCommitHandler: (void (^)(void)) handler
|
|
+ forPhase: (CATransactionPhase) phase;
|
|
+
|
|
++ (void) setCompletionBlock: (void (^)(void)) block;
|
|
++ (void (^)(void)) completionBlock;
|
|
+
|
|
+ (BOOL) disableActions;
|
|
+ (CFTimeInterval) animationDuration;
|
|
+ (CAMediaTimingFunction *) animationTimingFunction;
|
|
diff --git a/QuartzCore/include/QuartzCore/CATransformLayer.h b/QuartzCore/include/QuartzCore/CATransformLayer.h
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/QuartzCore/include/QuartzCore/CATransformLayer.h
|
|
@@ -0,0 +1,28 @@
|
|
+/*
|
|
+ * This file is part of Darling.
|
|
+ *
|
|
+ * Copyright (C) 2022 Darling developers
|
|
+ *
|
|
+ * Darling is free software: you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation, either version 3 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * Darling is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
+ */
|
|
+
|
|
+#import <QuartzCore/CALayer.h>
|
|
+
|
|
+/* darling-lol M58F35: CATransformLayer is a CALayer that renders its sublayers
|
|
+ * in 3D (it adds no surface API of its own). */
|
|
+@interface CATransformLayer : CALayer
|
|
+
|
|
+- (instancetype) init;
|
|
+
|
|
+@end
|
|
diff --git a/AppKit/NSWorkspace.m b/AppKit/NSWorkspace.m
|
|
index ad5e4f9d..dbd997aa 100644
|
|
--- a/AppKit/NSWorkspace.m
|
|
+++ b/AppKit/NSWorkspace.m
|
|
@@ -20,9 +20,58 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
#import <AppKit/NSRaise.h>
|
|
#import <AppKit/NSWorkspace.h>
|
|
#import <Foundation/Foundation.h>
|
|
+#include <dispatch/dispatch.h>
|
|
|
|
NSString *const NSWorkspaceApplicationKey = @"NSWorkspaceApplicationKey";
|
|
|
|
+/* Electron's shell.openExternal() uses -openURL:configuration:completionHandler: (macOS 10.15)
|
|
+ * with a configured NSWorkspaceOpenConfiguration, so the configuration needs real properties.
|
|
+ * The URL itself is opened by -openURL: (LaunchServices in MacWorkspace). */
|
|
+@interface NSWorkspaceOpenConfiguration () {
|
|
+ BOOL _activates, _hides, _hidesOthers, _addsToRecentItems, _createsNewApplicationInstance;
|
|
+ BOOL _allowsRunningApplicationSubstitution, _forPrinting, _promptsUserIfNeeded, _requiresUniversalLinks;
|
|
+ NSAppleEventDescriptor *_appleEvent;
|
|
+ NSArray *_arguments;
|
|
+ NSDictionary *_environment;
|
|
+ cpu_type_t _architecture;
|
|
+}
|
|
+@end
|
|
+
|
|
+@implementation NSWorkspaceOpenConfiguration
|
|
+@synthesize activates = _activates, hides = _hides, hidesOthers = _hidesOthers;
|
|
+@synthesize addsToRecentItems = _addsToRecentItems, createsNewApplicationInstance = _createsNewApplicationInstance;
|
|
+@synthesize allowsRunningApplicationSubstitution = _allowsRunningApplicationSubstitution;
|
|
+@synthesize forPrinting = _forPrinting, promptsUserIfNeeded = _promptsUserIfNeeded;
|
|
+@synthesize requiresUniversalLinks = _requiresUniversalLinks;
|
|
+@synthesize appleEvent = _appleEvent, arguments = _arguments, environment = _environment;
|
|
+@synthesize architecture = _architecture;
|
|
++ (instancetype) configuration {
|
|
+ NSWorkspaceOpenConfiguration *c = [[[self alloc] init] autorelease];
|
|
+ c->_activates = YES;
|
|
+ c->_addsToRecentItems = YES;
|
|
+ c->_allowsRunningApplicationSubstitution = YES;
|
|
+ return c;
|
|
+}
|
|
+- (id) copyWithZone: (NSZone *) zone {
|
|
+ NSWorkspaceOpenConfiguration *c = [[[self class] allocWithZone: zone] init];
|
|
+ c->_activates = _activates; c->_hides = _hides; c->_hidesOthers = _hidesOthers;
|
|
+ c->_addsToRecentItems = _addsToRecentItems;
|
|
+ c->_createsNewApplicationInstance = _createsNewApplicationInstance;
|
|
+ c->_allowsRunningApplicationSubstitution = _allowsRunningApplicationSubstitution;
|
|
+ c->_forPrinting = _forPrinting; c->_promptsUserIfNeeded = _promptsUserIfNeeded;
|
|
+ c->_requiresUniversalLinks = _requiresUniversalLinks; c->_architecture = _architecture;
|
|
+ c->_appleEvent = [_appleEvent retain];
|
|
+ c->_arguments = [_arguments copy]; c->_environment = [_environment copy];
|
|
+ return c;
|
|
+}
|
|
+- (void) dealloc {
|
|
+ [_appleEvent release];
|
|
+ [_arguments release];
|
|
+ [_environment release];
|
|
+ [super dealloc];
|
|
+}
|
|
+@end
|
|
+
|
|
NSString *const NSWorkspaceWillPowerOffNotification =
|
|
@"NSWorkspaceWillPowerOffNotification";
|
|
|
|
@@ -161,6 +210,24 @@ NSNotificationName NSWorkspaceWillUnmountNotification = @"NSWorkspaceWillUnmount
|
|
return 0;
|
|
}
|
|
|
|
+- (void) openURL: (NSURL *) url
|
|
+ configuration: (NSWorkspaceOpenConfiguration *) configuration
|
|
+ completionHandler: (void (^)(id application, NSError *error)) completionHandler
|
|
+{
|
|
+ // Opening may wait on another process; keep it off the caller's thread.
|
|
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
+ BOOL opened = [self openURL: url];
|
|
+ if (completionHandler != NULL) {
|
|
+ NSError *error = nil;
|
|
+ if (!opened)
|
|
+ error = [NSError errorWithDomain: NSCocoaErrorDomain code: 256 userInfo: nil];
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
+ completionHandler(nil, error);
|
|
+ });
|
|
+ }
|
|
+ });
|
|
+}
|
|
+
|
|
- (BOOL) selectFile: (NSString *) path
|
|
inFileViewerRootedAtPath: (NSString *) rootedAtPath
|
|
{
|
|
@@ -317,16 +384,3 @@ NSNotificationName NSWorkspaceWillUnmountNotification = @"NSWorkspaceWillUnmount
|
|
}
|
|
|
|
@end
|
|
-
|
|
-static dispatch_once_t _initOnceNsWorkspaceOpenConfig;
|
|
-static NSWorkspaceOpenConfiguration* _singletonNsWorkspaceOpenConfig;
|
|
-
|
|
-@implementation NSWorkspaceOpenConfiguration
|
|
-+ (instancetype)configuration {
|
|
- dispatch_once(&_initOnceNsWorkspaceOpenConfig, ^{
|
|
- _singletonNsWorkspaceOpenConfig = [[NSWorkspaceOpenConfiguration alloc] init];
|
|
- });
|
|
- return _singletonNsWorkspaceOpenConfig;
|
|
-}
|
|
-
|
|
-@end
|
|
\ No newline at end of file
|
|
diff --git a/AppKit/NSGeometryTrace.h b/AppKit/NSGeometryTrace.h
|
|
new file mode 100644
|
|
index 00000000..a51a9241
|
|
--- /dev/null
|
|
+++ b/AppKit/NSGeometryTrace.h
|
|
@@ -0,0 +1,25 @@
|
|
+/* Opt-in runtime geometry diagnostics; never log view text or client data. */
|
|
+#ifndef DARLING_NS_GEOMETRY_TRACE_H
|
|
+#define DARLING_NS_GEOMETRY_TRACE_H
|
|
+#include <stdarg.h>
|
|
+#include <stdio.h>
|
|
+#include <stdlib.h>
|
|
+
|
|
+static inline void DarlingGeometryTrace(const char *format, ...) {
|
|
+ if (!getenv("DARLING_UI_GEOMETRY_TRACE"))
|
|
+ return;
|
|
+ va_list args, copy;
|
|
+ va_start(args, format);
|
|
+ va_copy(copy, args);
|
|
+ vfprintf(stderr, format, args);
|
|
+ va_end(args);
|
|
+ /* Clients may redirect stderr to a non-log descriptor. Keep a copy in
|
|
+ * the guest's temporary directory so their resize events remain visible. */
|
|
+ FILE *file = fopen("/tmp/darling-ui-geometry.log", "a");
|
|
+ if (file) {
|
|
+ vfprintf(file, format, copy);
|
|
+ fclose(file);
|
|
+ }
|
|
+ va_end(copy);
|
|
+}
|
|
+#endif
|
|
diff --git a/AppKit/X11.backend/X11RelativeMouse.h b/AppKit/X11.backend/X11RelativeMouse.h
|
|
new file mode 100644
|
|
index 00000000..7fc39cc4
|
|
--- /dev/null
|
|
+++ b/AppKit/X11.backend/X11RelativeMouse.h
|
|
@@ -0,0 +1,147 @@
|
|
+/* darling-lol: decision logic for macOS-style relative mouse mode in the X11 backend.
|
|
+ *
|
|
+ * While CGAssociateMouseAndMouseCursorPosition(false) is in effect, macOS freezes the cursor: physical motion
|
|
+ * only produces deltas, events report the frozen cursor position, and CGWarpMouseCursorPosition moves the cursor
|
|
+ * without producing a delta. Here the frozen cursor is the "virtual" position (X root coordinates). Core X motion
|
|
+ * still moves the real pointer, so deltas are measured against the last physical position, and where the server
|
|
+ * honours warps the pointer is warped back to the virtual position after each motion. Every warp (ours or the
|
|
+ * application's) is followed by a MotionNotify at its target; those echoes are recognised by position and
|
|
+ * dropped. Warps are synchronous (XSync), so an echo can only be among the events already queued at that moment;
|
|
+ * an expected echo expires once that many events have been handled.
|
|
+ *
|
|
+ * On Xwayland a warp only reaches the compositor while the X cursor is hidden. With a visible cursor the echo
|
|
+ * still arrives, but the next real motion reports the unmoved physical pointer, so honoured=false keeps the last
|
|
+ * physical position across echoes and never warps back.
|
|
+ *
|
|
+ * Plain C without X11 or Objective-C dependencies, so tools/test-x11-relative-mouse.c can run it on the host. */
|
|
+#ifndef X11_RELATIVE_MOUSE_H
|
|
+#define X11_RELATIVE_MOUSE_H
|
|
+
|
|
+#include <stdbool.h>
|
|
+
|
|
+#define X11_RELATIVE_MOUSE_MAX_ECHOES 8
|
|
+
|
|
+typedef struct {
|
|
+ int x, y;
|
|
+ unsigned remaining; /* X events (of any type) still to be handled before this echo is given up */
|
|
+} X11RelativeMouseEcho;
|
|
+
|
|
+typedef struct {
|
|
+ bool active; /* relative motion handling is on (the pointer grab is held) */
|
|
+ double virtualX, virtualY; /* the frozen macOS cursor, X root coordinates */
|
|
+ int physicalX, physicalY; /* last physical pointer position, X root coordinates */
|
|
+ unsigned echoCount;
|
|
+ X11RelativeMouseEcho echoes[X11_RELATIVE_MOUSE_MAX_ECHOES]; /* oldest first */
|
|
+} X11RelativeMouse;
|
|
+
|
|
+typedef struct {
|
|
+ bool post; /* deliver a mouse moved/dragged event */
|
|
+ bool echo; /* the event was the echo of a warp */
|
|
+ double deltaX, deltaY; /* NSEvent convention: positive deltaY is downwards */
|
|
+ bool rewarp; /* warp the pointer back to (warpX, warpY) */
|
|
+ int warpX, warpY;
|
|
+} X11RelativeMouseMotion;
|
|
+
|
|
+/* X11 warps take integer coordinates; conversions truncate like the original implicit double-to-int argument. */
|
|
+static inline int X11RelativeMouseWarpCoordinate(double value) {
|
|
+ return (int) value;
|
|
+}
|
|
+
|
|
+static inline void X11RelativeMouseBegin(X11RelativeMouse *mouse, int rootX, int rootY) {
|
|
+ mouse->active = true;
|
|
+ mouse->virtualX = rootX;
|
|
+ mouse->virtualY = rootY;
|
|
+ mouse->physicalX = rootX;
|
|
+ mouse->physicalY = rootY;
|
|
+ mouse->echoCount = 0;
|
|
+}
|
|
+
|
|
+static inline void X11RelativeMouseEnd(X11RelativeMouse *mouse) {
|
|
+ mouse->active = false;
|
|
+ mouse->echoCount = 0;
|
|
+}
|
|
+
|
|
+/* Record a warp to (x, y) that was just sent and synced. queuedEvents is the number of X events queued after the
|
|
+ * sync; zero means the warp produced no event (the pointer was already there). */
|
|
+static inline void X11RelativeMouseExpectEcho(X11RelativeMouse *mouse, int x, int y, unsigned queuedEvents) {
|
|
+ if (!queuedEvents) return;
|
|
+ if (mouse->echoCount) {
|
|
+ X11RelativeMouseEcho *last = &mouse->echoes[mouse->echoCount - 1];
|
|
+ if (last->x == x && last->y == y) {
|
|
+ if (last->remaining < queuedEvents) last->remaining = queuedEvents;
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ if (mouse->echoCount == X11_RELATIVE_MOUSE_MAX_ECHOES) {
|
|
+ for (unsigned i = 1; i < mouse->echoCount; ++i)
|
|
+ mouse->echoes[i - 1] = mouse->echoes[i];
|
|
+ mouse->echoCount--;
|
|
+ }
|
|
+ mouse->echoes[mouse->echoCount++] = (X11RelativeMouseEcho) {x, y, queuedEvents};
|
|
+}
|
|
+
|
|
+/* CGWarpMouseCursorPosition while relative: the virtual cursor moves to the target; the physical position is
|
|
+ * only updated when the echo arrives (and only if the warp was honoured). */
|
|
+static inline void X11RelativeMouseWarp(X11RelativeMouse *mouse, double x, double y, unsigned queuedEvents) {
|
|
+ mouse->virtualX = x;
|
|
+ mouse->virtualY = y;
|
|
+ X11RelativeMouseExpectEcho(mouse, X11RelativeMouseWarpCoordinate(x), X11RelativeMouseWarpCoordinate(y),
|
|
+ queuedEvents);
|
|
+}
|
|
+
|
|
+/* One X event has been handled: age the expected echoes. */
|
|
+static inline void X11RelativeMouseTick(X11RelativeMouse *mouse) {
|
|
+ unsigned kept = 0;
|
|
+ for (unsigned i = 0; i < mouse->echoCount; ++i)
|
|
+ if (mouse->echoes[i].remaining > 1) {
|
|
+ mouse->echoes[kept] = mouse->echoes[i];
|
|
+ mouse->echoes[kept++].remaining--;
|
|
+ }
|
|
+ mouse->echoCount = kept;
|
|
+}
|
|
+
|
|
+/* A MotionNotify at root position (rootX, rootY) while relative. Ages the expected echoes. */
|
|
+static inline X11RelativeMouseMotion X11RelativeMouseHandleMotion(X11RelativeMouse *mouse, int rootX, int rootY,
|
|
+ bool warpsHonoured)
|
|
+{
|
|
+ X11RelativeMouseMotion result = {false, false, 0.0, 0.0, false, 0, 0};
|
|
+ for (unsigned i = 0; i < mouse->echoCount; ++i)
|
|
+ if (mouse->echoes[i].x == rootX && mouse->echoes[i].y == rootY) {
|
|
+ /* Warps are echoed in order; earlier expected echoes that did not arrive never will. */
|
|
+ for (unsigned j = i + 1; j < mouse->echoCount; ++j)
|
|
+ mouse->echoes[j - i - 1] = mouse->echoes[j];
|
|
+ mouse->echoCount -= i + 1;
|
|
+ if (warpsHonoured) {
|
|
+ mouse->physicalX = rootX;
|
|
+ mouse->physicalY = rootY;
|
|
+ }
|
|
+ result.echo = true;
|
|
+ X11RelativeMouseTick(mouse);
|
|
+ return result;
|
|
+ }
|
|
+ result.deltaX = rootX - mouse->physicalX;
|
|
+ result.deltaY = rootY - mouse->physicalY;
|
|
+ mouse->physicalX = rootX;
|
|
+ mouse->physicalY = rootY;
|
|
+ result.post = result.deltaX != 0.0 || result.deltaY != 0.0;
|
|
+ int warpX = X11RelativeMouseWarpCoordinate(mouse->virtualX);
|
|
+ int warpY = X11RelativeMouseWarpCoordinate(mouse->virtualY);
|
|
+ if (warpsHonoured && (rootX != warpX || rootY != warpY)) {
|
|
+ result.rewarp = true;
|
|
+ result.warpX = warpX;
|
|
+ result.warpY = warpY;
|
|
+ }
|
|
+ X11RelativeMouseTick(mouse);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+/* NSEvent deltas for absolute (non-relative) motion between two positions in Cocoa window coordinates, which
|
|
+ * are bottom-up while NSEvent's deltaY is positive downwards. */
|
|
+static inline void X11AbsoluteMouseDelta(double lastX, double lastY, double x, double y, double *deltaX,
|
|
+ double *deltaY)
|
|
+{
|
|
+ *deltaX = x - lastX;
|
|
+ *deltaY = lastY - y;
|
|
+}
|
|
+
|
|
+#endif
|