Difference between revisions of "macOS File and Custom URL Scheme Associations"

From Lazarus wiki
Jump to navigationJump to search
(→‎Simple Editor example: Work in progress ...)
(→‎See also: Added entry)
Line 282: Line 282:
 
== See also ==
 
== See also ==
  
* t/c
+
* [[macOS property list files]]
  
 
== External links ==
 
== External links ==

Revision as of 07:15, 24 May 2020

English (en)

macOSlogo.png

This article applies to macOS only.

See also: Multiplatform Programming Guide


Warning-icon.png

Warning: Work in progress...

Overview

macOS does not have a central registry like Windows to which applications explicitly write information. Instead, every application with a graphical interface has a property list file named Info.plist, roughly equivalent to a manifest in the Windows world, stored in its application bundle. The Info.plist file may optionally include file type association and custom URL scheme information.

macOS Launch Services is an API that enables a running application to open other applications or their document files in a way similar to the Finder or the Dock. Using Launch Services, an application can perform such tasks as:

  • Open (launch or activate) another application
  • Open a document or a URL (uniform resource locator) in another application
  • Identify the preferred application for opening a given document or URL
  • Register information about the kinds of document files and URLs an application is capable of opening
  • Obtain appropriate information for displaying a file or URL on the screen, such as its icon, display name, and kind string
  • Maintain and update the contents of the Recent Items menu

Launch Services automatically registers an application with the database the first time the application becomes known to the system. This can occur when:

  • The Finder reports an application has been added to the Applications folder.
  • An application installer is run.
  • When a document is opened that has no preferred application, the user is asked to select an application to use, and that application is registered with Launch Services.
  • When the built-in Launch Services tool is run whenever you boot your Mac or login as a user. This tool scans the Applications folder looking for any new applications that have been placed there.

When you open a document or URL, Launch Services is used to determine which application should open the item. Launch Services uses the following specific order:

  • If the user has manually set a file association, then Launch Services will use that application to open the document or URL.
  • If the document has a file name extension, Launch Services will find all applications that list the extension as compatible.
  • If the document has a four-character file type, Launch Services finds all applications that accept that file type.
  • If more than one application is found, then:
    • If the document has a four-character creator type that matches an application:
      • Give preference to applications on the boot volume;
      • Give preference to applications on local volumes rather than remote volumes.
  • If two or more applications still meet the criteria, give preference to the newest version.

Identifying the type of content in a file

There are two primary techniques for identifying the type of content in a file:

  • Uniform Type Identifiers (UTIs)
  • Filename extensions

A UTI is a string that uniquely identifies a class of entities considered to have a "type". UTIs provide consistent identifiers for data on which all applications and services can recognise and rely. They are also more flexible than most other techniques because you can use them to represent any type of data, not just files and directories. Examples of UTIs include:

  • public.text — A public type that identifies text data.
  • public.jpeg — A public type that identifies JPEG image data.
  • com.apple.bundle — An Apple type that identifies a bundle directory.
  • com.apple.application-bundle — An Apple type that identifies a bundled app.

Whenever a UTI-based interface is available for specifying file types, you should prefer that interface over any others. Many macOS interfaces allow you to specify UTIs corresponding to the files or directories you want to work with.

One way the system determines the UTI for a given file is by looking at its filename extension. A filename extension is a string of characters appended to the end of a file and separated from the main filename with a period. Each unique string of characters identifies a file of a specific type. For example, the .png extension identifies a file with image data in the portable network graphics format.

Note: Because period characters are valid characters in macOS filenames, only the characters after the last period in a filename are considered part of the filename extension. Everything to the left of the last period is considered part of the filename itself.

If your application defines custom file formats, you should register those formats and any associated filename extensions in your application's Info.plist file. The CFBundleDocumentTypes key specifies the file formats that your application recognises and is able to open. Entries for any custom file formats should include both a filename extension and UTI corresponding to the file contents. The system uses that information to direct files with the appropriate type to your application.

Simple MyEditor example application

We shall start by creating a very simple editor application to which we will add our own custom file type extension. After that is working we will then add a custom URL scheme so that other applications (eg Safari) when faced with our custom URL scheme will offer to open files in our MyEditor application.

  • Start a new Lazarus application
  • Drop a memo on the form
  • Drop three buttons on the form
    • name one button btnOpen
    • name one button btnSave
    • name one button btnQuit
  • Drop an OpenDialog on the form
  • Drop a SaveDialog on the form
  • Copy the button click handlers from the code below into your unit1.pas file
procedure TForm1.btnOpenClick(Sender: TObject);
begin
  LoadMemo;
end;

procedure TForm1.btnSaveClick(Sender: TObject);
begin
  SaveMemo;
end;

procedure TForm1.btnQuitClick(Sender: TObject);
begin
  close;
end;
  • Go to the Object Inspector, select each button in turn, select the Events tab, select the OnClick event and double click the empty field. The correct event handler for each button should be chosen automatically.
  • In the private section, before the public section, of TForm1 in the Interface section at the top of the unit add:
    sTmp : string;
    procedure LoadMemo;
    procedure SaveMemo;
  • Add these two procedures after the button click handlers:
procedure TForm1.LoadMemo;
begin
if OpenDialog1.Execute then
  sTmp:=OpenDialog1.Filename;

if FileExists(sTmp) then
  memo1.Lines.LoadFromFile(sTmp)
 else
   showmessage('File not found: ' + sTmp);
end;

procedure TForm1.SaveMemo;
begin

if SaveDialog1.Execute then
  begin
    try
      memo1.Lines.SaveToFile(SaveDialog1.FileName);
    except on EStreamError do
        showmessage('Could not save file: ' + SaveDialog1.FileName);
    end;
  end
end;
  • Compile and run the application. You should be able to Open a file in the editor, Save a file and Quit the application.
  • If you encounter any errors, compare your unit1.pas against the full unit1.pas below and make any necessary corrections:
unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;

type

  { TForm1 }

  TForm1 = class(TForm)
    btnOpen: TButton;
    btnSave: TButton;
    btnQuit: TButton;
    Memo1: TMemo;
    OpenDialog1: TOpenDialog;
    SaveDialog1: TSaveDialog;
    procedure btnOpenClick(Sender: TObject);
    procedure btnQuitClick(Sender: TObject);
    procedure btnSaveClick(Sender: TObject);
  private
    sTmp : string;
    procedure LoadMemo;
    procedure SaveMemo;
  public

  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.btnOpenClick(Sender: TObject);
begin
  Form1.LoadMemo;
end;

procedure TForm1.btnSaveClick(Sender: TObject);
begin
  Form1.SaveMemo;
end;

procedure TForm1.btnQuitClick(Sender: TObject);
begin
  Form1.Close;
end;

procedure TForm1.LoadMemo;
begin
if OpenDialog1.Execute then
  sTmp:=OpenDialog1.Filename;

if FileExists(sTmp) then
  memo1.Lines.LoadFromFile(sTmp)
 else
   showmessage('File not found: ' + sTmp);
end;

procedure TForm1.SaveMemo;
begin
if SaveDialog1.Execute then
  begin
    try
      memo1.Lines.SaveToFile(SaveDialog1.FileName);
    except on EStreamError do
        showmessage('Could not save file: ' + SaveDialog1.FileName);
    end;
  end
end;

end.

Adding your own file type extension

Lazarus will have created an application bundle for our MyEditor application. Use Finder to navigate to the project directory, right-click on the bundle directory MyEditor.app and select "Show package contents". Open the Contents subdirectory and you should see: a MacOS subdirectory, a Resources subdirectory, a Pkginfo file and the application's Info.plist property list file. Open the file with TextEditor.app and the content should look very similar to this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleDevelopmentRegion</key>
  <string>English</string>
  <key>CFBundleExecutable</key>
  <string>MyEditor</string>
  <key>CFBundleName</key>
  <string>MyEditor</string>
  <key>CFBundleIdentifier</key>
  <string>com.company.MyEditor</string>
  <key>CFBundleInfoDictionaryVersion</key>
  <string>6.0</string>
  <key>CFBundlePackageType</key>
  <string>APPL</string>
  <key>CFBundleSignature</key>
  <string>MyEd</string>
  <key>CFBundleShortVersionString</key>
  <string>0.1</string>
  <key>CFBundleVersion</key>
  <string>1</string>
  <key>CSResourcesFileMapped</key>
  <true/>
  <key>CFBundleDocumentTypes</key>
  <array>
    <dict>
      <key>CFBundleTypeRole</key>
      <string>Viewer</string>
      <key>CFBundleTypeExtensions</key>
      <array>
        <string>*</string>
      </array>
      <key>CFBundleTypeOSTypes</key>
      <array>
        <string>fold</string>
        <string>disk</string>
        <string>****</string>
      </array>
    </dict>
  </array>
  <key>NSHighResolutionCapable</key>
  <true/>
</dict>
</plist>

We will now add our custom filename extension to the property list so that MyEditor will be the default application used to open such files. Delete the highlighted lines and insert these replacement lines:

      <string>Editor</string>
	<key>LSTypeIsPackage</key>
	<false/>
        <key>CFBundleTypeIconFile</key>
        <string>txt.icns</string>
	<key>CFBundleTypeExtensions</key>
	<array>
	  <string>xyz</string>
	</array>

Save the file. You will notice that we have changed the "role" from Viewer to Editor and we have added our custom file extension.


... TO BE CONTINUED ...

See also

External links