Implementing "Open with…" on MacOS with QtI just released
PhotoTeleport 0.12, which includes the feature mentioned in the title of this blog post. Given that it took me some time to understand how this could work with Qt, I think it might be worth spending a couple of lines about how to implement it.
In the target application
The first step (and the easiest one) is about adding the proper information to your
.plist
file: this is needed to tell MacOS what file types are supported by your application. The official documentation is
here, but given that an example is better than a thousand words, here's what I had to add to
PhotoTeleport.plist
in order to have it registered as a handler for
TIFF
files:
CFBundleDocumentTypes
CFBundleTypeExtensions
tiff
TIFF
tif
TIF
CFBundleTypeMIMETypes
image/tiff
CFBundleTypeName
NSTIFFPboardType
CFBundleTypeOSTypes
TIFF
****
CFBundleTypeRole
Viewer
LSHandlerRank
Default
LSItemContentTypes
public.tiff
NSDocumentClass
PVDocument
…more dict entries for other supported file formats…
This is enough to have your application appear in Finder's "Open with…" menu and be started when the user selects it from the context menu, but it's only half of the story: to my big surprise, the selected files are not passed to your application as command line parameters, but via some MacOS-specific event which needs to be handled.
By grepping into the Qt source code, I've found out that Qt already handles the event, which is then transformed into a
QFileOpenEvent
. The documentation here is quite helpful, so I won't waste your time to repeat it here; what has hard for me was to actually
find that this functionality exists and is supported by Qt.
In the source application
The above is only half of the story: what if you are writing an application which wants to send some files to some other application? Because of the sandboxing, you cannot just start the desired application in a
QProcess
and pass the files as parameters: again, we need to use the Apple
Launch Servicesso that the target application would receive the files through the mechanism described above.
Unfortunately, as far as I could find this is not something that Qt supports; sure, with
QDesktopServices::openUrlExternally()
you can start the default handler for the given url, but what if you need to open more than one file at once? And what if you want to open the files in a specific application, and not just in the default one? Well, you need to get your hands dirty and use some MacOS APIs:
#import
#import
void MacOS::runApp(const QString &app, const QList &files)
{
CFURLRef appUrl = QUrl::fromLocalFile(app).toCFURL();
CFMutableArrayRef cfaFiles =
CFArrayCreateMutable(kCFAllocatorDefault,
files.count(),
&kCFTypeArrayCallBacks);
for (const QUrl &url: files) {
CFURLRef u = url.toCFURL();
CFArrayAppendValue(cfaFiles, u);
CFRelease(u);
}
LSLaunchURLSpec inspec;
inspec.appURL = appUrl;
inspec.itemURLs = cfaFiles;
inspec.asyncRefCon = NULL;
inspec.launchFlags = kLSLaunchDefaults + kLSLaunchAndDisplayErrors;
inspec.passThruParams = NULL;
OSStatus ret;
ret = LSOpenFromURLSpec(&inspec, NULL);
CFRelease(appUrl);
}
In
Imaginario I've saved this into a
macos.mm
file, added it to the source files, and also added the native MacOS libraries to the build (qmake):
LIBS += -framework CoreServices
You can see the
commit implementing all this, it really doesn't get more complex than this. The first parameter to the
MacOS::runApp()
function is the name of the application; I've verified that the form
/Applications/YourAppName.app
works, but it may be that more human-friendly variants work as well.