Implementing "Open with…" on MacOS with Qt
I 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:
<key>CFBundleDocumentTypes</key> <array> <dict> <key>CFBundleTypeExtensions</key> <array> <string>tiff</string> <string>TIFF</string> <string>tif</string> <string>TIF</string> </array> <key>CFBundleTypeMIMETypes</key> <array> <string>image/tiff</string> </array> <key>CFBundleTypeName</key> <string>NSTIFFPboardType</string> <key>CFBundleTypeOSTypes</key> <array> <string>TIFF</string> <string>****</string> </array> <key>CFBundleTypeRole</key> <string>Viewer</string> <key>LSHandlerRank</key> <string>Default</string> <key>LSItemContentTypes</key> <array> <string>public.tiff</string> </array> <key>NSDocumentClass</key> <string>PVDocument</string> </dict> …more dict entries for other supported file formats… </array>
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
Services
so 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 <CoreFoundation/CoreFoundation.h> #import <ApplicationServices/ApplicationServices.h> void MacOS::runApp(const QString &app, const QList<QUrl> &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.
Commentos
There's also webmention support.