Embed external binary in QuickLook Preview application

Hello,

I am trying to write a simple QuickLook generator, which takes a file, runs an external binary to produce the data, and then creates and shows an image in a preview. The input files are uncommon data formats, and the binary in this case converts these to PNG.

I have tried multiple methods and followed many discussions on these forums and StackOverflow, but unfortunately I am yet to get it working. That said, the program I have builds and runs using the qlmanage tool, however when running in Finder or on the server qlmanage -x the program fails.

As I understand, it when using QuickLook in Finder it calls the QuickLook daemon quicklookd which is also called when using the qlmanage -x CLI command, but please correct me if I am wrong about this.

The basic idea for all generator functions is:

  1. create NSTask or Process and run the third-party binary, using the requested file path as input
  2. run the task, pipe output, and read output
  3. convert the piped output to NSImage
  4. show image

Attempt 1 (old-style qlgenerator plugin in Obj-C):

Initially I tried adding the binary as an asset to the QuickLook Preview target and the generator would work as mentioned using qlmanage, however it would not work using qlmanage -x or from Finder.

Here is the preview function:

OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options)
{
    NSLog(@"QuickLook plugin started");
    
    NSInteger width = 512;
    NSURL *fileURL = (__bridge NSURL *)url;
    
    NSString *escapedFilePath = [fileURL.path stringByReplacingOccurrencesOfString:@" " withString:@"\\ "];
    NSLog(@"Original Path: %@", fileURL.path);
    NSLog(@"Escaped Path: %@", escapedFilePath);
    
    NSBundle *bundle = [NSBundle bundleWithIdentifier:@"com.pjharrison.qlgwyddion"];
    
    NSURL *nsURLExecutable = [bundle URLForAuxiliaryExecutable:@"gwyddion-thumbnailer"];
    
    if (nsURLExecutable) {
        // The resource was found, and nsURLData is not nil.
        // Proceed with using nsURLData.
        NSLog(@"Resource 'gwyddion-thumbnailer' found in the bundle.");
    } else {
        // The resource was not found. Handle the situation accordingly.
        NSLog(@"Resource 'gwyddion-thumbnailer' not found in the bundle.");
    }
    
    NSTask *task = [[NSTask alloc] init];
    NSLog(@"Pathhhh %@", nsURLExecutable.path);
    [task setExecutableURL:nsURLExecutable];
    [task setArguments:@[@"kde4", [@(width) stringValue], escapedFilePath]];
    NSLog(@"Task set");
    NSString *argumentString = [task.arguments componentsJoinedByString:@" "];
    NSLog(argumentString);
    

    NSPipe *pipe = [NSPipe pipe];
    NSFileHandle *file = pipe.fileHandleForReading;
    [task setStandardOutput:pipe];
    NSLog(@"Pipe set");

    [task launch];
    NSLog(@"Task launched");
    
    NSData *data = [file readDataToEndOfFile];
    NSLog(@"Got data");
    
    [task waitUntilExit];
    NSLog(@"Task completed");
    
    NSInteger taskStatusCode = [task terminationStatus];
    NSLog(@"Task finished with status code %ld", taskStatusCode);
    
    NSString *dataLength = [NSString stringWithFormat:@"%lu", data.length];
    NSLog(dataLength);
    
    CGDataProviderRef imageDataProvider = CGDataProviderCreateWithCFData((CFDataRef)data);
    CGImageRef cgImage = CGImageCreateWithPNGDataProvider(imageDataProvider, NULL, true, kCGRenderingIntentDefault);

    if (cgImage) {
        CGFloat height = CGImageGetHeight(cgImage);
        CGFloat width = CGImageGetWidth(cgImage);
        CGSize size = CGSizeMake(width, height);

        // Preview will be drawn in a vectorized context
        CGRect rect = CGRectMake(0, 0, width, height);
        CGContextRef cgContext = QLPreviewRequestCreateContext(preview, size, true, NULL);
        
        CGContextDrawImage(cgContext, rect, cgImage);
        NSLog(@"Image drawn");
        QLPreviewRequestFlushContext(preview, cgContext);
        NSLog(@"Flushed");
        // release resources
        if (cgContext) {
            CFRelease(cgContext);
            NSLog(@"Context released");
        }
        CGImageRelease(cgImage);
        NSLog(@"Image released");
    }
    else {
        NSLog(@"cgImage is NULL");
    }

    return noErr;
}

Inspection console logs would give various Operation not permitted errors when run from the quicklookd server. I suspect that possibly this is due to App Sandboxing issues.

Attempt 2

After coming across various articles online by @eskimo and others, including the official docs and various other posts, I have tried to sign the binary executable as described in the documentation and embed it in the project. Unfortunately after signing the app I have not been able to get the executable to run. When called from the QuickLook Preview function, the process returns with an error code. Perhaps the issue is here and the executable has not been signed properly?

Attempt 3

I have also tried to create a Swift-based app and add a QuickLook Extension to the app (creating an app with a .appex) using a similar flow as tried in Attempt 1. Also no luck here so far.

At this point I am stuck and would appreciate any help or pointers! Many thanks in advance.