Sign in Join
Wiki copytemporary

Locating macOS significant directories

(macOS only)

Overview

To locate various significant directories in macOS you can use the native macOS Foundation function

pascal
NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde);

which creates a list of path strings for the specified directories in the specified domains. The list is in the order in which you should search the directories.

Note: The directory returned by this method may not exist. This method simply gives you the appropriate location for the requested directory. Depending on the application’s needs, it may be up to the developer to create the appropriate directory and any in between.

Determining where to store files

Get significant directories function

pascal
{$mode objfpc}{H+}
{$modeswitch objectivec1} 

interface

Uses
  ...
  CocoaUtils,   // for NStringToString
  CocoaAll;     // for NSArray      
...

function GetSignificantDir(DirLocation: qword; DomainMask: qword; count: byte): string;
var
  paths : NSArray;
begin
  paths := NSSearchPathForDirectoriesInDomains(DirLocation, DomainMask, True);
  if(count < paths.count) then
    Result := NSString(paths.objectAtIndex(0)).UTF8String
  else
    Result := '';  
end;

Table of Directory Locations

Directory name Directory Description
NSApplicationDirectory Supported applications (/Applications)
NSDemoApplicationDirectory Unsupported applications and demonstration versions
NSDeveloperApplicationDirectory Developer applications (/Developer/Applications)
NSAdminApplicationDirectory System and network administration applications
NSLibraryDirectory Various user-visible documentation, support, and configuration files (/Library)
NSDeveloperDirectory Developer resources (/Developer)
NSUserDirectory User home directories (/Users)
NSDocumentationDirectory Documentation
NSDocumentDirectory Document directory
NSCoreServiceDirectory Core services (System/Library/CoreServices)
NSAutosavedInformationDirectory The user’s autosaved documents (Library/Autosave Information)
NSDesktopDirectory The user’s desktop directory
NSCachesDirectory Discardable cache files (Library/Caches)
NSApplicationSupportDirectory Application support files (Library/Application Support)
NSDownloadsDirectory The user’s downloads directory
NSInputMethodsDirectory Input Methods (Library/Input Methods)
NSMoviesDirectory The user’s Movies directory (~/Movies)
NSMusicDirectory The user’s Music directory (~/Music)
NSPicturesDirectory The user’s Pictures directory (~/Pictures)
NSPrinterDescriptionDirectory The system’s PPDs directory (Library/Printers/PPDs)
NSSharedPublicDirectory The user’s Public sharing directory (~/Public)
NSPreferencePanesDirectory The PreferencePanes directory for use with System Preferences (Library/PreferencePanes)
NSApplicationScriptsDirectory The user scripts folder for the calling application (~/Library/Application Scripts/<code-signing-id>
NSItemReplacementDirectory The constant used to create a temporary directory
NSAllApplicationsDirectory All directories where applications can be stored
NSAllLibrariesDirectory All directories where resources can be stored
NSTrashDirectory The user's trash directory

Directory Locations

Table of Path Domains

Domain name Domain Description
NSUserDomainMask The user’s home directory—the place to install user’s personal items (~).
NSLocalDomainMask The place to install items available to everyone on this machine
NSNetworkDomainMask The place to install items available on the network (/Network)
NSSystemDomainMask A directory for system files provided by Apple (/System)
NSAllDomainsMask All domains

Path Domains

Code Examples

You can now locate various significant directories and display them as demonstrated by the code examples below.

User caches directory

pascal
procedure TForm1.MenuItem5Click(Sender: TObject);
begin
  ShowMessage('User caches dir: ' + GetSignificantDir(NSCachesDirectory,NSUserDomainMask,0));
end;

User trash directory

pascal
procedure TForm1.MenuItem16Click(Sender: TObject);
begin
  ShowMessage('User trash dir: '
          + GetSignificantDir(NSTrashDirectory,NSUserDomainMask,0));
end;

Users directory

Maybe not what you expect... this is the /Users directory :-)

pascal
procedure TForm1.MenuItem10Click(Sender: TObject);
begin
  ShowMessage('Users dir: ' +  GetSignificantDir(NSUserDirectory,NSLocalDomainMask,0));
end;

To retrieve the current user's home directory, you need to use the NSHomeDirectory() function instead:

pascal
procedure TForm1.MenuItem11Click(Sender: TObject);
begin
  ShowMessage('User''s dir: ' + NSStringToString(NSHomeDirectory));
end;

BUT note that if the application is in a sandbox, this will return the application’s sandbox directory and not the current user’s home directory.

An alternative way to retrieve the current user's hime directory:

pascal
function NSUserName: CFStringRef external name '_NSUserName';
...

procedure TForm1.MenuItem22Click(Sender: TObject);
var
  usernameStr: ShortString;
  status: Boolean = false;
begin
   status := CFStringGetPascalString(CFStringRef(NSusername),@usernameStr,255,CFStringGetSystemEncoding);
  if(status = true) then
     ShowMessage('Current user''s dir: ' + NSStringToString(NShomeDirectoryForUser(NSStr(usernameStr))))
  else
     ShowMessage('Error retrieving username'); 
end;

All application directories

pascal
procedure TForm1.MenuItem7Click(Sender: TObject);
var
  count: byte;
begin
  for count := 0 to 12 do
   if GetSignificantDir(NSAllApplicationsDirectory, NSAllDomainsMask, count) <> '' then
      ShowMessage('Application dir ' + IntToStr(count) + ': '
           + GetSignificantDir(NSAllApplicationsDirectory,NSAllDomainsMask,count))
   else
      exit; 
end;

All user application directories

pascal
procedure TForm1.MenuItem8Click(Sender: TObject);
var
  count: byte;
begin
  for count := 0 to 12 do
   if GetSignificantDir(NSAllApplicationsDirectory, NSUserDomainMask, count) <> '' then
      ShowMessage('User application dir ' + IntToStr(count) + ': '
           + GetSignificantDir(NSAllApplicationsDirectory,NSUserDomainMask,count))
   else
      exit; 
end;

All system application directories

pascal
procedure TForm1.MenuItem9Click(Sender: TObject);
var
  count: byte;
begin
  for count := 0 to 12 do
   if GetSignificantDir(NSAllApplicationsDirectory, NSSystemDomainMask, count) <> '' then
      ShowMessage('System application dir ' + IntToStr(count) + ': '
           + GetSignificantDir(NSAllApplicationsDirectory,NSSystemDomainMask,count))
   else
      exit; 
end;

Replacements for FPC's GetAppConfigDir and GetAppConfigFile

The existing FPC GetAppConfigDir and GetAppConfigFile functions are not macOS friendly because they use the UNIX convention and not the Apple macOS convention. For example, the output from these functions:

pascal
writeLn(GetAppConfigDir(true));
writeLn(GetAppConfigDir(false));

writeLn(GetAppConfigFile(true));
writeLn(GetAppConfigFile(false));

writeLn(GetAppConfigFile(true,true));
writeLn(GetAppConfigFile(true,false));

writeLn(GetAppConfigFile(false,false));
writeLn(GetAppConfigFile(false,true));

is this:

text
/etc/
/Users/<username>/.config/

/etc/project1.cfg
/Users/<username>/.config/project1.cfg

/etc/project1/project1.cfg
/etc/project1.cfg

/Users/<username>/.config/project1.cfg
/Users/<username>/.config/project1/project1.cfg

whereas the correct output for a macOS command line application (ie without an Application Bundle) is:

text
/Library/Application Support/project1/
/Users/<username>/Library/Application Support/project1/

/Library/Application Support/project1/project1.plist
/Users/<username>/Library/Application Support/project1/project1.plist

/Library/Application Support/project1/project1.plist
/Library/Application Support/project1/project1.plist

/Users/<username>/Library/Application Support/project1/project1.plist
/Users/<username>/Library/Application Support/project1/project1.plist

and the correct output for a macOS GUI application (ie with an Application Bundle):

text
/Library/Application Support/com.company.project1/
/Users/<username>/Library/Application Support/com.company.project1/

/Library/Application Support/com.company.project1/project1.plist
/Users/<username>/Library/Application Support/com.company.project1/project1.plist

/Library/Application Support/com.company.project1/project1.plist
/Library/Application Support/com.company.project1/project1.plist

/Users/<username>/Library/Application Support/com.company.project1/project1.plist
/Users/<username>/Library/Application Support/com.company.project1/project1.plist

Here are the replacement functions -- named with the addition of a 2 to the existing function names -- and with ${IFDEF}s so that they can be used in multi-platform code:

pascal
{$mode objfpc}{H+}
{$modeswitch objectivec1}

...

Uses
  ...
  SysUtils,
  CocoaAll,
  Cocoautils;
...

//
// GetAppConfigDir Replacement
//

function GetAppConfigDir2(Global: Boolean): String;

{$IFDEF DARWIN}
var
  bundleIdStr: String;
  pathsArr : NSArray;
{$ENDIF}
begin
  {$IFDEF DARWIN}
  bundleIdStr := NSStringToString(NSBundle.mainBundle.bundleIdentifier);
  if(bundleIdStr = '') then
     bundleIdStr := ExtractFileName(paramstr(0)); // Handle non-bundled apps eg command line

  if(Global) then
    pathsArr := NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSLocalDomainMask, True)
  else
    pathsArr := NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True);

  Result := NSString(pathsArr.objectAtIndex(0)).Utf8String + '/' + bundleIdStr + '/';
  {$ELSE}
    GetAppConfigDir(global);
  {$ENDIF}
end;  

//    
// GetAppConfigFile replacement
//

Function GetAppConfigFile2(Global : Boolean; SubDir : Boolean) : String;

{$IFDEF DARWIN}
var
  bundleIdStr: ShortString;
  appNameStr: ShortString;
  pathBufferStr: String;
  isAppBundle : Boolean = True;
{$ENDIF}
begin
  {$IFDEF DARWIN}
  bundleIdStr := NSStringToString(NSBundle.mainBundle.bundleIdentifier);
  if(bundleIdStr = '') then
    begin
     bundleIdStr := ExtractFileName(paramstr(0)); // Handle non-bundled apps eg command line
     isAppBundle := False;
    end;

  pathBufferStr := GetAppConfigDir2(Global);

  if(isAppBundle) then
    appNameStr := StringReplace(ExtractFileExt(bundleIdStr), '.', '', [rfReplaceAll])
  else
    appNameStr := extractFileName(ExcludeTrailingPathDelimiter(pathBufferStr));

  if(SubDir) then
    // Ignore because Apple dictates the subdir already as part of GetAppConfigDir()
    ;

  Result := pathBufferStr + appNameStr + '.plist';

  {$ELSE}
  Result := GetAppConfigFile(Global, SubDir);
  {$ENDIF}
end;

See also


From the Free Pascal wiki, page "Locating macOS significant directories", as it stood on 2021-03-26 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.

Revision 1, last changed by WikiArchive .

Talk about this page

Nobody has opened a bench for this page yet. Sign in to open one.