I've developed a new Quicklook data-based preview extension for a custom file type that generates an image preview of the file. I previously used a Quick Look generator plug-in but support for it was deprecated and now removed in macOS Sequoia.
My app opens files using a
open(url.path, O_RDWR | O_NONBLOCK | O_EXLOCK)
call. The locking flags are used to prevent other clients from writing the file if it's already open.
I discovered that when Finder is showing the “large” file previews (such as when in column or gallery modes) from a SMB share, the open call fails with EWOULDBLOCK as if the file is locked.
It does work just fine on local files. Opening with O_SHLOCK also has the issue. Surprisingly it does work just fine for previews that return Plain Text data instead of Image data.
Using the lsof command, it seems like the Quicklook process has some kind of lock on the file.
This is the output of the lsof command:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE
QuickLook 48487 XXXX txt REG 1,15 125000611 3161369
Attached is a test project that tries a few different opening and locking functions. It also includes a test file and a sample image preview extension that displays a red square.
When everything is working, regular console messages show the progress of the tests. When the file is on a SMB share and selected in Finder Gallery mode, the open test will fail with a fault message in the console.
Notably, locking with flock works, which is weird because it should have similar semantics according to the man page for open(2).
Filed this as FB15051186
QuickLook Thumbnailing
RSS for tagGenerate thumbnails for common file types and add a Thumbnail Extension to your app using QuickLook Thumbnailing.
Posts under QuickLook Thumbnailing tag
9 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
I'm trying to look at what the best way to do thumbnails for images that are saved in core data, which are being synced across multiple devices.
I know I can save a lower quality version into core data, but I'm wondering if there's a better way of doing it.
I've come across quick look thumbnailing which looks like what I want, but I'm not sure if it can be adapted for core data as its using file paths, whereas the images are stored in a data type property in core data.
From what I can tell, I'd have to save the image locally, produce the thumbnail, then delete the local image
QLPreview keeps blinking and stops previewing the file when opening some high-resolution PDF files with heavy content. This can only be simulated in a real device built in xcode simulators cant simulate the issue.
Tested in Xcode Version 15.4 (15F31d) and iPadOS Version 17.5.1.
Following is My Sample code :
import UIKit
import QuickLook
class ViewController: UIViewController {
var currentViewFilePath = ""
let preview = QLPreviewController()
override func viewDidLoad() {
super.viewDidLoad()
self.setupQLPreview()
loaddata()
// Do any additional setup after loading the view.
}
func loaddata(){
if let fileURL = Bundle.main.url(forResource: "CD28048D", withExtension: "pdf") {
self.currentViewFilePath = fileURL.relativePath
self.preview.reloadData()
// Use the fileURL here
print("File URL: \(fileURL)")
} else {
print("File not found")
}
}
}
extension ViewController:QLPreviewControllerDelegate,QLPreviewControllerDataSource{
func setupQLPreview(){
preview.delegate = self
preview.dataSource = self
self.view.addSubview(preview.view)
let previewhight = (UIScreen.main.bounds.height - ((self.navigationController?.navigationBar.frame.height ?? 0)))
preview.view.translatesAutoresizingMaskIntoConstraints = false
preview.view.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
preview.view.leftAnchor.constraint(equalTo: self.view.leftAnchor,constant: 4).isActive = true
preview.view.rightAnchor.constraint(equalTo: self.view.rightAnchor,constant: -4).isActive = true
preview.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor,constant: 4).isActive = true
preview.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor,constant: -4).isActive = true
preview.view.widthAnchor.constraint(equalToConstant: self.view.frame.width-8).isActive = true
preview.view.heightAnchor.constraint(equalToConstant: previewhight-4).isActive = true
preview.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
preview.navigationController?.isNavigationBarHidden = true
self.addChild(preview)
preview.didMove(toParent: self)
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
if self.currentViewFilePath.isEmpty || self.currentViewFilePath == ""{
return 0
}else{
return 1
}
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
let fileUrl = URL(fileURLWithPath: self.currentViewFilePath)
return fileUrl as QLPreviewItem
}
}
while running the above code Xcode debugger prints following errors:
View service did terminate with error:
Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "(null)" UserInfo={Message=Service Connection Interrupted} #Remote
Preview collection viewServiceDidTerminateWithError: Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "(null)" UserInfo={Message=Service Connection Interrupted} #Remote
[u 0AA9D4C5-BBA1-56F6-8FFF-F5F56B99399A:m (null)] [com.apple.quicklook.extension.previewUI(1.0)] Connection to plugin invalidated while in use.
Default behaviour of the macOS when interacting via Finder with a network share is to create thumbnails to provide a nice user experience. This behaviour is implemented by QuickLook framework.
This triggers the full file download and quickly lead to cache using large disk space and even cache trashing.
We can disable QuickLook selectively by injecting specially prepared .DS_Store file with instruction/setup to disable thumbnails in that directory but still degrades user's experience only in the network share ecosystem.
Is there any way we can provide our own thumbnails or ask Finder to not download full file to generate the thumbnail?
I want to provide my own thumbnails for the file types which System already provides thumbnails for. I tried using QLThumbnailProvider however it works only when the file type is custom. For system supported file types it doesn't allow me to override my own thumbnails. Is there any alternative to this?
I am developing Thumbnail Extension. To prepare a graphic (in .dylib) for file specified in URL.path I need to access second file, which has the same filename, but different extension. For example, I have "data" in file.*** and "colors" in file.yyy. Is it possible to read data from this second file while processing the first file in my extension? In my current implementation it sometimes works, but most of the time it doesn't work - it is not possible to read data from file.yyy when preparing thumbnail for file.***, causing exception in my .dylib routine. Is there some setting which would allow me to do this?
We have QuickLook thumbnailing framework which is used to provide thumbnails for custom file types. However, how can we provide thumbnails for system defined file types for eg. image file types?
Is there any way to achieve this?
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:
create NSTask or Process and run the third-party binary, using the requested file path as input
run the task, pipe output, and read output
convert the piped output to NSImage
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.
Hi folks,
My app is reading proprietary files with the file name extension .JPX - which is out of my control. In addition I’m providing QuickLook and Thumbnail extensions, used system-wide and in my app.
Unfortunately iOS is assigning the JPEG-2000 file type (UTI „public.jpeg-2000“) to this file extension, and therefore - to work with associated files - my app is importing this UTI and both extensions are listing „public.jpeg-2000“ in their info.plist as QLSupportedContentTypes. This works to some extent in simulators and when debugging from Xcode on a device: Files with the file extension „.JPX“ are listed with thumbnails provided by my extension, although the preview seems to invoke the system-provided viewer and fails. Not perfect, but good enough as my app requires an icon preview (aka thumbnail) in its UIDocumentBrowserViewController.
But when I try to submit my app incl. extensions to the Apple App Store / TestFlight asset validation is reporting an error:
„Asset validation failed. Invalid Info.plist value. The value for the key ‚QLSupportedContentTypes‘ in bundle … is invalid. [public.jpeg-2000] are system-supported types.“
How to assign QuickLook / Thumbnail extensions to 3rd party files types whose extension is conflicting with a system-supported UTI?
I just spent one of my TSIs for this question - as my Apple developer membership is renewed shortly - but maybe this community as some smart tip to share...
Appreciate any help, Mattes