darlinghq / darling

Darwin/macOS emulation layer for Linux
http://www.darlinghq.org
GNU General Public License v3.0
11.44k stars 444 forks source link

SketchTool crashes #494

Open tommedema opened 5 years ago

tommedema commented 5 years ago

E.g. would this be able to run Sketchtool (https://developer.sketchapp.com/guides/sketchtool/)?

bugaevc commented 5 years ago

I've no idea what (frameworks, APIs) Sketchtool uses. Try it and see ;)

ahyattdev commented 5 years ago

I tried downloading it and running it and it looks like it relies on AppKit. Our AppKit is currently missing a symbol it needs.

dyld: Symbol not found: _NSImageInterlaced
  Referenced from: /Applications/Sketch.app/Contents/Resources/sketchtool/bin/../../../Frameworks/Chocolat.framework/Versions/A/Chocolat (which was built for Mac OS X 10.13)
  Expected in: /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit
 in /Applications/Sketch.app/Contents/Resources/sketchtool/bin/../../../Frameworks/Chocolat.framework/Versions/A/Chocolat
Abort trap: 6 (core dumped)
tommedema commented 5 years ago

That's useful info, thanks! What would it take to get these symbols in your AppKit?

bugaevc commented 5 years ago

Well, adding the symbol is easy, NSImageInterlaced is just a string. (If you'd like to submit a fix, take a look at the docs and these files in Cocotron: NSBitmapImageRep.h NSBitmapImageRep.m CGImageProperties.h CGImageProperties.m). It's harder to make it actually work as expected, but perhaps Sketchtool doesn't really need it, at least not for most of its functionality.

tommedema commented 5 years ago

OK - I don't actually know C++, but I suppose I could learn? I'll have to get darling to build first though, made a separate issue for that.

bugaevc commented 5 years ago

This is Objective-C, not C++. I guess you don't know that either, but it won't help if you start learning the wrong language :wink:

tommedema commented 5 years ago

That's right, thanks :')

ahyattdev commented 5 years ago

This is an example of a commit that adds missing constants to a framework: https://github.com/darlinghq/darling-cocotron/commit/89ca94c60f6a58e3e22bf23890aaefeb3aa57962

tommedema commented 5 years ago

@bugaevc you mentioned that NSImageInterlaced is a string but https://developer.apple.com/documentation/appkit/nsimageinterlaced?language=objc seems to imply it's not? Or did I misunderstand?

ahyattdev commented 5 years ago

@tommedema that's a good observation. In the macOS SDK's NSImage.h we have this:

typedef NSString * NSImageName

Apple has typedef'd a lot of NSString * constants to their own types in order to make it easier to use them in Swift.

At runtime they are just NSString pointers and our codebase doesn't have any Swift, so we don't bother with that and just do NSString * in our implementations of the frameworks.

bugaevc commented 5 years ago

@tommedema to clarify what @ahyattdev says, NSBitmapImageRepPropertyKey is typedef'ed to NSString *, so saying NSBitmapImageRepPropertyKey is just a fancy way of saying "string".

We actually prefer to use the same typedefs as Apple (to make it possible to compile more code against Darling as an SDK), but it's not strictly required for anything.

ahyattdev commented 5 years ago
Darling [~]$ /Applications/Sketch.app/Contents/Resources/sketchtool/bin/sketchtool 
STUB CGPathRef CGPathCreateWithEllipseInRect(CGRect, const CGAffineTransform *)
STUB CGPathRef CGPathCreateWithEllipseInRect(CGRect, const CGAffineTransform *)
STUB CGPathRef CGPathCreateWithRect(CGRect, const CGAffineTransform *)
STUB CGPathRef CGPathCreateWithRect(CGRect, const CGAffineTransform *)
STUB CGRect CGPathGetPathBoundingBox(CGPathRef)
STUB CGPathRef CGPathCreateWithEllipseInRect(CGRect, const CGAffineTransform *)
STUB CGPathRef CGPathCreateWithEllipseInRect(CGRect, const CGAffineTransform *)
STUB CGPathRef CGPathCreateWithRect(CGRect, const CGAffineTransform *)
STUB CGPathRef CGPathCreateWithRect(CGRect, const CGAffineTransform *)
STUB CGRect CGPathGetPathBoundingBox(CGPathRef)
STUB CGPathRef CGPathCreateWithEllipseInRect(CGRect, const CGAffineTransform *)
STUB CGPathRef CGPathCreateWithEllipseInRect(CGRect, const CGAffineTransform *)
STUB CGPathRef CGPathCreateWithRect(CGRect, const CGAffineTransform *)
STUB CGPathRef CGPathCreateWithRect(CGRect, const CGAffineTransform *)
STUB CGRect CGPathGetPathBoundingBox(CGPathRef)
STUB CGRect CGPathGetPathBoundingBox(CGPathRef)
STUB CGPathRef CGPathCreateCopyByTransformingPath(CGPathRef, CGAffineTransform *)
STUB CGRect CGPathGetPathBoundingBox(CGPathRef)
STUB CGRect CGPathGetPathBoundingBox(CGPathRef)
STUB CGPathRef CGPathCreateCopyByTransformingPath(CGPathRef, CGAffineTransform *)
STUB CGPathRef CGPathCreateWithRect(CGRect, const CGAffineTransform *)
Segmentation fault: 11 (core dumped)

The STUB lines each represent a function call being made to CoreGraphics. I assume it crashes because our CGPathRef CGPathCreateWithRect stub returns a NULL pointer which sketchtool dereferences. The next order of business is implementing these CoreGraphics stubs.

bugaevc commented 5 years ago

CGPathGetPathBoundingBox(), for example, should just wrap O2PathGetBoundingBox(). Other ones also sound easily implementable on top of Onyx2D; and it'd better to put the actual implementation into O2 and make the CG functions wrap them, as always.

bugaevc commented 5 years ago

Hmm, there is already a CGPathGetBoundingBox() that wraps O2PathGetBoundingBox(). How's it different from CGPathGetPathBoundingBox()?

ahyattdev commented 5 years ago

https://developer.apple.com/documentation/coregraphics/1411165-cgpathgetboundingbox?language=occ

The bounding box is the smallest rectangle completely enclosing all points in the path, including control points for Bézier and quadratic curves.

https://developer.apple.com/documentation/coregraphics/1411200-cgpathgetpathboundingbox?language=occ

The path bounding box is the smallest rectangle completely enclosing all points in the path but not including control points for Bézier and quadratic curves.

Apparently this.

sztomi commented 5 years ago

I would be interested in getting this to work as well. Any way I can help?

bugaevc commented 5 years ago

Any way I can help?

Yes; see the above comment(s) about implementing the (previously missing, now stubbed out) CG* functions. (And feel free to ask questions!)

sztomi commented 5 years ago

What's the best order to go about implementing them? Do they all belong in the cocotron submodule?

sztomi commented 5 years ago

Also, in the case of CGPathCreateWithXXX functions, it is mentioned in the docs that

Using this convenience function is more efficient than creating a mutable path and adding an ellipse to it

would it be acceptable to implement them in terms of creating and adding XXX to the path anyway (given that the AddXXX functions are already there). I don't know how I would implement them more efficiently.

bugaevc commented 5 years ago

There only are four of them (so far), so a particular order doesn't matter much.

Yes, they all belong to Cocoton; that's where Darling's implementation of Core Graphics (and Onyx2D, and few other frameworks) is. Here are the stubs: https://github.com/darlinghq/darling-cocotron/commit/cc89db5830d5d4203c06a1dc5fbf95ad08e8c9d9#diff-27b41bcf06f5bb8c042e82b2133d96c4 Generally, all Core Graphics functions should be direct wrappers over the corresponding Onyx2D functions/methods, so the first thing to do is writing (or finding) the appropriate Onyx2D functions. CGPathCreateWithRect(), for example, should wrap O2PathCreateWithRect() or [O2Path initWithRect:transform:].

would it be acceptable to implement them in terms of creating and adding XXX to the path anyway (given that the AddXXX functions are already there). I don't know how I would implement them more efficiently.

The way to do it more efficiently would be to directly pre-allocate the necessary number of operators/points and set them up directly without going through the wrapper functions (see O2MutablePath.m), but it's okay to just make them wrap Add* functions too.

sztomi commented 5 years ago

Thanks, I'll go in the direction of least resistance (at least for now). I added two implementations:

CGPathRef CGPathCreateWithEllipseInRect(CGRect rect, const CGAffineTransform *transform)
{
   CGPathRef path = (CGPathRef)O2PathCreateMutable();
   CGPathAddEllipseInRect(path, transform, rect);
   return path;
}

CGPathRef CGPathCreateWithRect(CGRect rect, const CGAffineTransform *transform)
{
   CGPathRef path = (CGPathRef)O2PathCreateMutable();
   CGPathAddRect(path, transform, rect);
   return path;
}

I'm running the build now, and I want to see if these work (let me know if there is something obviously wrong about them). Excuse my ignorance, but I didn't really know how to create an immutable path, since there was no designated function for that. I assume that the ref being mutable or immutable will decide that?

Moving forward, I see some of the functions will have to be implemented from the ground up. What is the process of verifying the correctness of the implementation?

bugaevc commented 5 years ago

let me know if there is something obviously wrong about them

It's not technically wrong, but what I meant is that the implementations should go into Onyx2D too, and the CG* functions should be thin wrappers over the O2* functions, like this:

// file: O2Path.m
O2PathRef O2PathCreateWithEllipseInRect(O2Rect rect, const O2AffineTransform *transform)
{
   O2MutablePathRef path = O2PathCreateMutable();
   O2PathAddEllipseInRect(path, transform, rect);
   return path;
}

// file: CGPath.c
CGPathRef CGPathCreateWithEllipseInRect(CGRect rect, const CGAffineTransform *transform)
{
   return (CGPathRef) O2PathCreateWithEllipseInRect(rect, transform);
}

Excuse my ignorance, but I didn't really know how to create an immutable path, since there was no designated function for that. I assume that the ref being mutable or immutable will decide that?

You can create an instance of O2Path (that won't also be an instance of O2MutablePath) by calling [O2Path new] (aka [[O2Path alloc] init]); that will get you an empty immutable path, which is not very useful. You can also get a (non-empty) truly immutable path as a result of calling [path copy]/O2PathCreateCopy() on some existing path.

But indeed, most paths are "mutable inside", and are only immutable because some piece of code has a O2PathRef rather than a O2MutablePathRef to it, and so is not supposed to mutate it.

Moving forward, I see some of the functions will have to be implemented from the ground up. What is the process of verifying the correctness of the implementation?

There's not really any process. You can, for example, write a simple test program which uses the functions to create a bunch of paths and then renders them to a context (either an NSView context or a bitmap context which you can save to an image file), and then just check whether the result looks as expected.

bugaevc commented 5 years ago

You can, for example, write a simple test program which uses the functions to create a bunch of paths and then renders them to a context (either an NSView context or a bitmap context which you can save to an image file)

For the reference, here's an example of how you can create a bitmap context, draw some paths to it and save it to a PNG image using Onyx2D API (it's mostly the same with Core Graphics API):

#import <Onyx2D/O2BitmapContext.h>
#import <Onyx2D/O2MutablePath.h>
#import <Onyx2D/O2ColorSpace.h>
#import <Onyx2D/O2ImageDestination.h>
#import <Foundation/Foundation.h>

int main() {
    NSAutoreleasePool *pool = [NSAutoreleasePool new];

    // Create a bitmap context.
    O2ColorSpace *colorSpace = O2ColorSpaceCreateDeviceRGB();
    size_t width = 200, height = 100;
    O2Context *context = O2BitmapContextCreate(
        NULL, width, height,
        8, width * 4,
        colorSpace,
        kO2ImageAlphaPremultipliedFirst | kO2BitmapByteOrder32Host
    );

    O2ColorSpaceRelease(colorSpace);

    // Create a path.
    O2MutablePath *path = O2PathCreateMutable();
    O2PathAddRect(path, NULL, O2RectMake(35, 42, 10, 50));

    // Draw the path on the context.
    O2ContextAddPath(context, path);
    O2ContextDrawPath(context, kO2PathStroke);

    O2PathRelease(path);

    // Create an image.
    O2Image *image = O2BitmapContextCreateImage(context);

    O2ContextRelease(context);

    // Save the image to a file.
    O2ImageDestination *destination = O2ImageDestinationCreateWithURL(
        (CFURLRef) [NSURL fileURLWithPath: @"/tmp/res.png"],
        (CFStringRef) @"public.png",
        1,
        NULL
    );
    O2ImageDestinationAddImage(destination, image, NULL);
    O2ImageDestinationFinalize(destination);

    O2ImageRelease(image);
    // There's no O2ImageDestinationRelease(), so let's instead
    // call CFRelease(); we can also use [destination release].
    CFRelease(destination);

    [pool release];
}

And here's the image this program generates: image

tommedema commented 5 years ago

@sztomi have you ever been able to get it to run?

ahyattdev commented 5 years ago

@tommedema good news, once those CoreGraphics stubs were implemented sketchtool got this far:

Darling [~]$ darling/build/Sketch.app/Contents/Resources/sketchtool/bin/sketchtool
Usage: sketchtool <command> [<args>]
                  [--formats=<string> | -f <string>]
                  [--use-id-for-name{=YES|NO} | --no-use-id-for-name | -n {<YES|NO>}]
                  [--export-page-as-fallback{=YES|NO} | --no-export-page-as-fallback | -B {<YES|NO>}]
                  [--serial{=YES|NO} | --no-serial | -R {<YES|NO>}]
                  [--context=<string> | -C <string>]
                  [--application=<path> | -A <path>]
                  [--without-activating{=YES|NO} | --no-without-activating | -Q {<YES|NO>}]
                  [--item=<string> | -i <string>]
                  [--items=<string> | -a <string>]
                  [--safemode{=YES|NO} | --no-safemode | -S {<YES|NO>}]
                  [--max-size=<float> | -m <float>]
                  [--background=<string> | -g <string>]
                  [--compression=<float> | -c <float>]
                  [--new-instance{=YES|NO} | --no-new-instance | -N {<YES|NO>}]
                  [--reveal{=YES|NO} | --no-reveal | -r {<YES|NO>}]
                  [--timeout=<float> | -T <float>]
                  [--include-symbols{=YES|NO} | --no-include-symbols | -I {<YES|NO>}]
                  [--bounds=<rectangle> | -b <rectangle>]
                  [--outputJSON=<path> | -J <path>]
                  [--filename=<string> | -F <string>]
                  [--wait-for-exit{=YES|NO} | --no-wait-for-exit | -W {<YES|NO>}]
                  [--scales=<path> | -s <path>] [--version | -v]
                  [--overwriting{=YES|NO} | --no-overwriting | -V {<YES|NO>}]
                  [--group-contents-only{=YES|NO} | --no-group-contents-only | -G {<YES|NO>}]
                  [--trimmed{=YES|NO} | --no-trimmed | -t {<YES|NO>}]
                  [--help | -h]
                  [--progressive{=YES|NO} | --no-progressive | -p {<YES|NO>}]
                  [--save-for-web{=YES|NO} | --no-save-for-web | -w {<YES|NO>}]
                  [--output=<path> | -o <path>]

Commands:
    dump                 Dump out the structure of a document as JSON.

    export artboards     Export one or more artboards
    export layers        Export one or more layers
    export pages         Export an area from one or more pages
    export preview       Export a preview image for a document
    export slices        Export one or more slices

    help                 Show this help message.

    list artboards       List information on the document's artboards.
    list formats         List the supported export formats.
    list layers          List information on all of the document's layers.
    list pages           List information on the document's pages.
    list slices          List information on the document's slices.

    metadata             List the metadata for a document.
    run                  Run a command from a plugin, inside Sketch.
    show                 Show the location of the various sketch folders.

See ‘sketchtool help <command>’ for more information on a specific command.

Could you provide some example invocations and test files so we can run Sketch in the way you want to use it and fix whatever that shows as broken?

tommedema commented 5 years ago

@ahyattdev amazing! I've prepared a file and command for testing:

/Applications/Sketch.app/Contents/Resources/sketchtool/bin/sketchtool export layers "./rect-vector.sketch" --items=814EBF9A-77C7-4EAF-88AE-046463A7EB4E,FE223445-E635-4790-ADE8-B973BD4AA999 --formats=png,svg --use-id-for-name --scales=2 --save-for-web --output="./outputDir"

You'll have to unzip the file rect-vector.sketch.zip

ahyattdev commented 5 years ago

@tommedema thanks for providing that file.

I implemented a few things in AppKit and CoreGraphics and this is the state that it's left off at:

Darling [~]$ sh sketchcmd.sh 
CALLED: O2ColorSpaceCreateWithName
CALLED: O2ColorSpaceCreateWithName
CALLED: O2ColorSpaceCreateWithName
CALLED: O2ColorSpaceCreateWithName
CALLED: O2ColorSpaceCreateWithName
CALLED: O2ColorSpaceCreateWithName
CALLED: O2ColorSpaceCreateWithName
CALLED: O2ColorSpaceCreateWithName
CALLED: O2ColorSpaceCreateWithName
CALLED: O2ColorSpaceCreateWithName
2019-08-13 21:52:02.714 sketchtool[24:3e8] -[O2GState clipBoundingBox] unimplemented in /home/andrewhyatt/darling/src/external/cocotron/Onyx2D/O2GraphicsState.m at 116
2019-08-13 21:52:02.715 sketchtool[24:3e8] -[O2GState clipBoundingBox] unimplemented in /home/andrewhyatt/darling/src/external/cocotron/Onyx2D/O2GraphicsState.m at 116
2019-08-13 21:52:02.716 sketchtool[24:3e8] -[O2GState clipBoundingBox] unimplemented in /home/andrewhyatt/darling/src/external/cocotron/Onyx2D/O2GraphicsState.m at 116
2019-08-13 21:52:02.755 sketchtool[24:3e8] -[O2GState clipBoundingBox] unimplemented in /home/andrewhyatt/darling/src/external/cocotron/Onyx2D/O2GraphicsState.m at 116
CGRectApplyAffineTransform STUB
2019-08-13 21:52:02.776 sketchtool[24:3e8] Terminating app due to uncaught exception 'NSException', reason: '-[NSBitmapImageRep drawInRect:fromRect:operation:fraction:respectFlipped:hints:]: unrecognized selector sent to instance 0x7fe77cc4b6a0'
libc++abi.dylib: terminating with uncaught exception of type NSException
sketchcmd.sh: line 1:    24 Abort trap: 6           (core dumped) /Applications/Sketch.app/Contents/Resources/sketchtool/bin/sketchtool export layers "./rect-vector.sketch" --items=814EBF9A-77C7-4EAF-88AE-046463A7EB4E,FE223445-E635-4790-ADE8-B973BD4AA999 --formats=png,svg --use-id-for-name --scales=2 --save-for-web --output="./outputDir"

Looks like SketchTool is pretty AppKit/CoreGraphics heavy. This will help us improve the APIs there!

tommedema commented 5 years ago

Hmm cool, but is it really realistic to implement all those APIs? Seems like a lot of effort and somewhat error prone?

On Tue, Aug 13, 2019 at 18:55 Andrew Hyatt notifications@github.com wrote:

@tommedema https://github.com/tommedema thanks for providing that file.

I implemented a few things in AppKit and CoreGraphics and this is the state that it's left off at:

Darling [~]$ sh sketchcmd.sh CALLED: O2ColorSpaceCreateWithName CALLED: O2ColorSpaceCreateWithName CALLED: O2ColorSpaceCreateWithName CALLED: O2ColorSpaceCreateWithName CALLED: O2ColorSpaceCreateWithName CALLED: O2ColorSpaceCreateWithName CALLED: O2ColorSpaceCreateWithName CALLED: O2ColorSpaceCreateWithName CALLED: O2ColorSpaceCreateWithName CALLED: O2ColorSpaceCreateWithName 2019-08-13 21:52:02.714 sketchtool[24:3e8] -[O2GState clipBoundingBox] unimplemented in /home/andrewhyatt/darling/src/external/cocotron/Onyx2D/O2GraphicsState.m at 116 2019-08-13 21:52:02.715 sketchtool[24:3e8] -[O2GState clipBoundingBox] unimplemented in /home/andrewhyatt/darling/src/external/cocotron/Onyx2D/O2GraphicsState.m at 116 2019-08-13 21:52:02.716 sketchtool[24:3e8] -[O2GState clipBoundingBox] unimplemented in /home/andrewhyatt/darling/src/external/cocotron/Onyx2D/O2GraphicsState.m at 116 2019-08-13 21:52:02.755 sketchtool[24:3e8] -[O2GState clipBoundingBox] unimplemented in /home/andrewhyatt/darling/src/external/cocotron/Onyx2D/O2GraphicsState.m at 116 CGRectApplyAffineTransform STUB 2019-08-13 21:52:02.776 sketchtool[24:3e8] Terminating app due to uncaught exception 'NSException', reason: '-[NSBitmapImageRep drawInRect:fromRect:operation:fraction:respectFlipped:hints:]: unrecognized selector sent to instance 0x7fe77cc4b6a0' libc++abi.dylib: terminating with uncaught exception of type NSException sketchcmd.sh: line 1: 24 Abort trap: 6 (core dumped) /Applications/Sketch.app/Contents/Resources/sketchtool/bin/sketchtool export layers "./rect-vector.sketch" --items=814EBF9A-77C7-4EAF-88AE-046463A7EB4E,FE223445-E635-4790-ADE8-B973BD4AA999 --formats=png,svg --use-id-for-name --scales=2 --save-for-web --output="./outputDir"

Looks like SketchTool is pretty AppKit/CoreGraphics heavy. This will help us improve the APIs there!

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/darlinghq/darling/issues/494?email_source=notifications&email_token=AACRAOOPSLGJSYHTBIZZXS3QENQXJA5CNFSM4HBJBFQKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD4HO7WY#issuecomment-521072603, or mute the thread https://github.com/notifications/unsubscribe-auth/AACRAOODAK3QF6OOXCUBDLDQENQXJANCNFSM4HBJBFQA .

CuriousTommy commented 5 years ago

@tommedema Unless Apple had released the source code for these API's, our only choice is to reimplement them from scratch.

Seems like a lot of effort

Yeah, it is a lot of effort (we can always use more people!).

somewhat error prone?

It can be, but as long as we create some test cases and compare the results to a real mac, we should be fine.

jpt commented 4 years ago

@ahyattdev

Looks like SketchTool is pretty AppKit/CoreGraphics heavy

This is essentially Sketch's stated reason for only supporting macOS. Following this issue with great enthusiasm; Sketch-on-Darling would probably blow up in the design systems world if support is ever close to 100% - if only I had the system-level programming chops to contribute 🙂

ahyattdev commented 4 years ago

@jpt good to know that could be a popular usecase. As our support for GUI steadily improves, we will indirectly implement a decent amount of stuff they use.

There may be more opportunity to contribute than you think. Lots to do if you just know AppKit!