My FMX app (MyApp, say) uses its own custom file type to store data files. Let's say these files have extension *.myext.
I have managed to set up the info.plist so that OSX registers MyApp as the owner of file type *.myext.
If MyApp is not yet open, it opens when double-clicking a file with that extension. Of course, the file does not open because I haven't written any code to handle the event because I don't know how to detect in OSX that the event has occurred.
If MyApp is already open, I get, I get a message 'The document “xxxx.myext” could not be opened. [MyApp] cannot open files in the “[MyApp file]” format.'
So my question is how MyApp can know that a file has been double-clicked so that it can launch a file open procedure?
I answered my own question eventually. Three authors had each given fairly similar units containing most of the necessary code.
Chris Rolliston: https://delphihaven.wordpress.com/2012/08/14/associating-a-file-type-on-osx-part3/
Victor Fedorenkov: https://pastebin.com/r4y6KmWz
Remy Lebeau: https://forums.embarcadero.com/thread.jspa?messageID=934522
None of the three units could be used without modification in Delphi Tokyo 10.2, no doubt due to changes that have taken place in Firemonkey since the posts were written.
I took parts of each of the three versions and made various edits to arrive at the unit below, which works in Delphi 10.2 Update 3:
unit NSApplicationOpenFileDelegateUnit.Mac;
interface
type
TOpenFileEvent = reference to procedure(const AFileName: string);
procedure InstallApplicationDelegate2(const AOnOpenFile: TOpenFileEvent);
implementation
uses
System.SysUtils, System.RTLConsts, System.Messaging, System.Classes,
Macapi.ObjectiveC, Macapi.CoreFoundation, Macapi.CocoaTypes, Macapi.AppKit,
Macapi.Foundation, FMX.Forms,
Macapi.ObjCRuntime,
FMX.Platform, FMX.Platform.Mac, FMX.Helpers.Mac;
type
NSApplicationDelegate2 = interface(NSApplicationDelegate)
['{BE9AEDB7-80AC-49B1-8921-F226CC9310F4}']
function application(theApplication: Pointer; openFile: CFStringRef)
: Boolean; cdecl;
end;
TNSApplicationDelegate2 = class(TOCLocal, NSApplicationDelegate2)
private
FOnOpenFile: TOpenFileEvent;
public
constructor Create(const AOnOpenFile: TOpenFileEvent);
procedure applicationDidFinishLaunching(Notification
: NSNotification); cdecl;
procedure applicationDidHide(Notification: NSNotification); cdecl;
procedure applicationDidUnhide(Notification: NSNotification); cdecl;
function applicationShouldTerminate(Notification: NSNotification)
: NSInteger; cdecl;
function applicationDockMenu(sender: NSApplication): NSMenu; cdecl;
procedure applicationWillTerminate(Notification: NSNotification); cdecl;
function application(theApplication: Pointer; openFile: CFStringRef)
: Boolean; cdecl;
end;
constructor TNSApplicationDelegate2.Create(const AOnOpenFile: TOpenFileEvent);
begin
inherited Create;
FOnOpenFile := AOnOpenFile;
end;
procedure TNSApplicationDelegate2.applicationDidFinishLaunching
(Notification: NSNotification); cdecl;
begin
// Seems we have to have this method even though it is empty.
end;
procedure TNSApplicationDelegate2.applicationDidHide(Notification
: NSNotification); cdecl;
begin
// Seems we have to have this method even though it is empty.
end;
procedure TNSApplicationDelegate2.applicationDidUnhide
(Notification: NSNotification); cdecl;
begin
// Seems we have to have this method even though it is empty.
end;
function TNSApplicationDelegate2.applicationShouldTerminate
(Notification: NSNotification): NSInteger; cdecl;
begin
Result := NSTerminateNow;
end;
function TNSApplicationDelegate2.applicationDockMenu(sender: NSApplication)
: NSMenu; cdecl;
begin
Result := nil;
end;
procedure TNSApplicationDelegate2.applicationWillTerminate
(Notification: NSNotification); cdecl;
begin
FreeAndNil(FMX.Forms.application);
end;
function TNSApplicationDelegate2.application(theApplication: Pointer;
openFile: CFStringRef): Boolean; cdecl;
var
Range: CFRange;
S: String;
begin
Result := Assigned(FOnOpenFile);
if not Result then
Exit;
Range.location := 0;
Range.length := CFStringGetLength(openFile);
SetLength(S, Range.length);
CFStringGetCharacters(openFile, Range, PChar(S));
try
FOnOpenFile(S);
except
on E: Exception do
begin
FMX.Forms.application.HandleException(E);
Result := False;
end;
end;
end;
var
Delegate: NSApplicationDelegate2;
procedure InstallApplicationDelegate2(const AOnOpenFile: TOpenFileEvent);
var
NSApp: NSApplication;
begin
NSApp := TNSApplication.Wrap(TNSApplication.OCClass.sharedApplication);
Delegate := TNSApplicationDelegate2.Create(AOnOpenFile);
NSApp.setDelegate(Delegate);
end;
end.
To use the unit, include this line in the main form's FormCreate:
InstallApplicationDelegate2(OpenFile);
and also include a file open procedure
procedure TMyMainForm.OpenFile(const AFileName: String);
begin
// Application-specific code to open the file
end;
Double-clicking a file in Finder now opens the file up in the application.
The above presupposes that your app and your proprietary file extension have already been associated via the info.plist file. The following is an info.plist to associate file extension ".myext" with an application MyApp.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>My App Full Name</string>
<key>CFBundleDisplayName</key>
<string>My App Full Name</string>
<key>CFBundleIdentifier</key>
<string>com.mycompany.MyAppName</string>
<key>CFBundleVersion</key>
<string>1.0.6662</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleAllowMixedLocalizations</key>
<string>YES</string>
<key>CFBundleExecutable</key>
<string>MyAppName</string>
<key>NSHighResolutionCapable</key>
<string>true</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>The reason for accessing the location information of the
user</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>The reason for accessing the location information of the
user</string>
<key>NSContactsUsageDescription</key>
<string>The reason for accessing the contacts</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleIconFile</key>
<string>MyAppName.icns</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSItemContentTypes</key>
<array>
<string>com.mycompany.MyAppName.myext</string>
</array>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>CFBundleTypeIconFile</key>
<string>MyAppName.icns</string>
</dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>com.mycompany.MyAppName.myext</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>myext</string>
</array>
</dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>My App Full Name document</string>
<key>UTTypeIconFile</key>
<string>MyAppName.icns</string>
</dict>
</array>
</dict>
</plist>