mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
Traverse the layer tree before compositing so nested Metal layers consume queued drawables and recycle their pool. Add a nested presentation probe covering pool reuse and Vulkan-to-OpenGL pixel readback.
8674 lines
338 KiB
Diff
8674 lines
338 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..dd6215c9 100644
|
|
--- a/AppKit/X11.backend/X11Display.h
|
|
+++ b/AppKit/X11.backend/X11Display.h
|
|
@@ -16,7 +16,7 @@
|
|
#import <CoreFoundation/CFSocket.h>
|
|
#endif
|
|
|
|
-@class X11Cursor;
|
|
+@class X11Cursor, X11Window;
|
|
|
|
@interface X11Display : NSDisplay {
|
|
Display *_display;
|
|
@@ -37,7 +37,10 @@
|
|
int clickCount;
|
|
X11Cursor *_blankCursor, *_defaultCursor;
|
|
BOOL _cursorGrabbed;
|
|
+ Window _confinedWindow;
|
|
+ unsigned int _otherButtonsDown;
|
|
KeySym _lastKeySym;
|
|
+ unsigned char _modifierKeysDown[256];
|
|
int _rrEventBase;
|
|
NSArray* _lastScreens;
|
|
|
|
@@ -45,7 +48,9 @@
|
|
XIM _xim;
|
|
}
|
|
|
|
+- (void) updateMouseConfinementForWindow:(X11Window *)window;
|
|
- (Display *) display;
|
|
+- (BOOL) activateProcessIdentifier: (pid_t) processIdentifier;
|
|
|
|
- (void) setWindow: (id) window forID: (XID) i;
|
|
|
|
diff --git a/AppKit/X11.backend/X11Display.m b/AppKit/X11.backend/X11Display.m
|
|
index 5ae29bf4..358e606b 100644
|
|
--- a/AppKit/X11.backend/X11Display.m
|
|
+++ b/AppKit/X11.backend/X11Display.m
|
|
@@ -48,15 +48,17 @@
|
|
#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>
|
|
|
|
void NSColorSetCatalogColor(NSColorListName catalogName, NSColorName colorName, NSColor *color);
|
|
|
|
@implementation X11Display
|
|
|
|
static int errorHandler(Display *display, XErrorEvent *errorEvent) {
|
|
@@ -75,6 +81,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) {
|
|
@@ -148,14 +163,16 @@ static void socketCallback(CFSocketRef s, CFSocketCallBackType type,
|
|
[_blankCursor release];
|
|
[_defaultCursor 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 +189,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;
|
|
@@ -536,12 +603,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 +782,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];
|
|
}
|
|
@@ -754,4 +870,7 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
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);
|
|
}
|
|
}
|
|
@@ -921,15 +1040,9 @@ 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);
|
|
+ rclolQueryPointer(_display);
|
|
int height = DisplayHeight(_display, DefaultScreen(_display));
|
|
- return NSMakePoint(root_x, height - root_y);
|
|
+ return NSMakePoint(rclolRootX, height - rclolRootY);
|
|
}
|
|
|
|
- (void) setWindow: (id) window forID: (XID) i {
|
|
@@ -962,20 +1075,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 +1185,93 @@ 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;
|
|
+ }
|
|
|
|
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 +1289,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 +1340,29 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
break;
|
|
|
|
case ButtonPress:;
|
|
+ if (ev->xbutton.button >= Button4 && ev->xbutton.button <= 7) {
|
|
+ pos = [window transformPoint:NSMakePoint(ev->xbutton.x, ev->xbutton.y)];
|
|
+ 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]) {
|
|
@@ -1096,10 +1382,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 +1395,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:
|
|
+ // One scroll event per notch, sent on press; releases are not clicks.
|
|
+ if (ev->xbutton.button >= Button4 && ev->xbutton.button <= 7) break;
|
|
pos = [window
|
|
transformPoint: NSMakePoint(ev->xbutton.x, ev->xbutton.y)];
|
|
|
|
+ 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 +1420,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,11 +1433,13 @@ 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;
|
|
|
|
case MotionNotify:; {
|
|
// NSLog(@"MotionNotify, x=%d, y=%d, xroot=%d, yroot=%d\n",
|
|
// ev->xmotion.x, ev->xmotion.y, ev->xmotion.x_root,
|
|
// ev->xmotion.y_root);
|
|
@@ -1195,14 +1479,29 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
|
|
|
+ [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 (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,
|
|
+ _cursorGrabbed, type != NSMouseMoved || [delegate acceptsMouseMovedEvents] ||
|
|
+ getenv("RCLOL_FORWARD_MOUSE_MOVED") != NULL);
|
|
+ }
|
|
+ }
|
|
|
|
- event = [NSEvent
|
|
+ if (type != NSMouseMoved || [delegate acceptsMouseMovedEvents] ||
|
|
+ getenv("RCLOL_FORWARD_MOUSE_MOVED")) {
|
|
+ event = [NSEvent
|
|
mouseEventWithType: type
|
|
location: pos
|
|
modifierFlags:
|
|
@@ -1211,25 +1510,40 @@ 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)
|
|
[window setLastKnownCursorPosition:
|
|
[window transformPoint: NSMakePoint(
|
|
ev->xcrossing.x,
|
|
ev->xcrossing.y)]];
|
|
+ if (!_cursorGrabbed && ev->xcrossing.detail != NotifyInferior)
|
|
+ [delegate platformWindowSetCursorEvent: window];
|
|
break;
|
|
|
|
case LeaveNotify:
|
|
NSLog(@"LeaveNotify");
|
|
+ if (!_cursorGrabbed && ev->xcrossing.detail != NotifyInferior) {
|
|
+ [window setLastKnownCursorPosition:
|
|
+ [window transformPoint: NSMakePoint(ev->xcrossing.x, ev->xcrossing.y)]];
|
|
+ [delegate platformWindowSetCursorEvent: window];
|
|
+ }
|
|
break;
|
|
|
|
case FocusIn:
|
|
@@ -1243,28 +1557,42 @@ 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);
|
|
+ [self updateMouseConfinementForWindow:window];
|
|
break;
|
|
|
|
case FocusOut:
|
|
NSLog(@"FocusOut");
|
|
+ [self reconcileModifierKeys:NULL window:delegate];
|
|
+ _lastKeySym = 0;
|
|
[delegate platformWindowDeactivated: window
|
|
checkForAppDeactivation: NO];
|
|
lastFocusedWindow = nil;
|
|
if (_cursorGrabbed)
|
|
[self grabMouse: NO];
|
|
+ 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 +1629,20 @@ 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;
|
|
+ [self updateMouseConfinementForWindow:window];
|
|
+ if([delegate isVisible] != visible) {
|
|
+ [delegate _setVisible: visible];
|
|
+ [[NSNotificationCenter defaultCenter]
|
|
+ postNotificationName: NSWindowDidChangeOcclusionStateNotification
|
|
+ object: delegate];
|
|
+ }
|
|
+ }
|
|
break;
|
|
|
|
case MapRequest:
|
|
@@ -1321,6 +1658,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 +1682,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];
|
|
}
|
|
@@ -1483,6 +1823,64 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
|
|
XSync(_display, False);
|
|
}
|
|
|
|
+/* Confinement preserves absolute motion and edge panning. Relative-mode
|
|
+ * capture remains separate: it deliberately warps to the center. */
|
|
+- (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,_cursorGrabbed);
|
|
+ if (!window || _cursorGrabbed) 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) return;
|
|
+ XWindowAttributes attrs;
|
|
+ if (!XGetWindowAttributes(_display, win, &attrs)) 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);
|
|
+ /* 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 (_confinedWindow == win) { XUngrabPointer(_display, CurrentTime); _confinedWindow=None; }
|
|
+ return;
|
|
+ }
|
|
+ if (_confinedWindow == 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 (status == GrabSuccess) _confinedWindow=win;
|
|
+}
|
|
+
|
|
- (void) grabMouse: (BOOL) doGrab {
|
|
if (doGrab) {
|
|
NSWindow *nswin = [NSApp keyWindow];
|
|
@@ -1495,9 +1893,10 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
|
|
const unsigned int mask = ButtonPressMask | ButtonReleaseMask |
|
|
PointerMotionMask | FocusChangeMask;
|
|
int result = XGrabPointer(_display, win, False, mask, GrabModeAsync,
|
|
- GrabModeAsync, None, None, CurrentTime);
|
|
+ GrabModeAsync, win, None, CurrentTime);
|
|
if (result == GrabSuccess) {
|
|
_cursorGrabbed = YES;
|
|
+ _confinedWindow = None;
|
|
NSLog(@"XGrabPointer() succeeded for window %lu\n", win);
|
|
|
|
NSRect frame = [xwin transformFrame: nswin.frame];
|
|
@@ -1522,6 +1921,9 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
|
|
} else {
|
|
XUngrabPointer(_display, CurrentTime);
|
|
_cursorGrabbed = NO;
|
|
+ _confinedWindow = None;
|
|
+ NSWindow *nswin = [NSApp keyWindow];
|
|
+ if (nswin) [self updateMouseConfinementForWindow:(X11Window *)[nswin platformWindow]];
|
|
}
|
|
XSync(_display, False);
|
|
}
|
|
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..2521ffd5 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;
|
|
@@ -63,6 +83,7 @@
|
|
- (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..5ad2d1f7 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
|
|
|
|
@@ -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;
|
|
}
|
|
@@ -478,6 +938,15 @@ static NSData *makeWindowIcon() {
|
|
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();
|
|
@@ -666,6 +1182,7 @@ static int ignoreBadWindow(Display *display, XErrorEvent *errorEvent) {
|
|
}
|
|
|
|
- (void) frameChanged {
|
|
+ _remoteFrameTargetRefresh = 0;
|
|
XErrorHandler previousHandler = XSetErrorHandler(ignoreBadWindow);
|
|
@try {
|
|
Window root, parent;
|
|
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..94d5c7bb 100644
|
|
--- a/CoreGraphics/CGDirectDisplay.m
|
|
+++ b/CoreGraphics/CGDirectDisplay.m
|
|
@@ -474,7 +474,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 +502,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
|