Sign in Join
Wiki copytemporary

Locating macOS significant directories: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: Locating macOS significant directories

OldNewLine
1+*(macOS only)*
2+
3+## Overview
4+
5+To locate various significant directories in macOS you can use the native macOS Foundation function
6+
7+``` pascal
8+NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde);
9+```
10+
11+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.
12+
13+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.
14+
15+## Determining where to store files
16+
17+## Get significant directories function
18+
19+``` pascal
20+{$mode objfpc}{H+}
21+{$modeswitch objectivec1}
22+
23+interface
24+
25+Uses
26+ ...
27+ CocoaUtils, // for NStringToString
28+ CocoaAll; // for NSArray
29+...
30+
31+function GetSignificantDir(DirLocation: qword; DomainMask: qword; count: byte): string;
32+var
33+ paths : NSArray;
34+begin
35+ paths := NSSearchPathForDirectoriesInDomains(DirLocation, DomainMask, True);
36+ if(count < paths.count) then
37+ Result := NSString(paths.objectAtIndex(0)).UTF8String
38+ else
39+ Result := '';
40+end;
41+```
42+
43+## Table of Directory Locations
44+
45+| Directory name | Directory Description |
46+|----|----|
47+| NSApplicationDirectory | Supported applications (/Applications) |
48+| NSDemoApplicationDirectory | Unsupported applications and demonstration versions |
49+| NSDeveloperApplicationDirectory | Developer applications (/Developer/Applications) |
50+| NSAdminApplicationDirectory | System and network administration applications |
51+| NSLibraryDirectory | Various user-visible documentation, support, and configuration files (/Library) |
52+| NSDeveloperDirectory | Developer resources (/Developer) |
53+| NSUserDirectory | User home directories (/Users) |
54+| NSDocumentationDirectory | Documentation |
55+| NSDocumentDirectory | Document directory |
56+| NSCoreServiceDirectory | Core services (System/Library/CoreServices) |
57+| NSAutosavedInformationDirectory | The user’s autosaved documents (Library/Autosave Information) |
58+| NSDesktopDirectory | The user’s desktop directory |
59+| NSCachesDirectory | Discardable cache files (Library/Caches) |
60+| NSApplicationSupportDirectory | Application support files (Library/Application Support) |
61+| NSDownloadsDirectory | The user’s downloads directory |
62+| NSInputMethodsDirectory | Input Methods (Library/Input Methods) |
63+| NSMoviesDirectory | The user’s Movies directory (~/Movies) |
64+| NSMusicDirectory | The user’s Music directory (~/Music) |
65+| NSPicturesDirectory | The user’s Pictures directory (~/Pictures) |
66+| NSPrinterDescriptionDirectory | The system’s PPDs directory (Library/Printers/PPDs) |
67+| NSSharedPublicDirectory | The user’s Public sharing directory (~/Public) |
68+| NSPreferencePanesDirectory | The PreferencePanes directory for use with System Preferences (Library/PreferencePanes) |
69+| NSApplicationScriptsDirectory | The user scripts folder for the calling application (~/Library/Application Scripts/\<code-signing-id\> |
70+| NSItemReplacementDirectory | The constant used to create a temporary directory |
71+| NSAllApplicationsDirectory | All directories where applications can be stored |
72+| NSAllLibrariesDirectory | All directories where resources can be stored |
73+| NSTrashDirectory | The user's trash directory |
74+
75+Directory Locations
76+
77+## Table of Path Domains
78+
79+| Domain name | Domain Description |
80+|----|----|
81+| NSUserDomainMask | The user’s home directory—the place to install user’s personal items (~). |
82+| NSLocalDomainMask | The place to install items available to everyone on this machine |
83+| NSNetworkDomainMask | The place to install items available on the network (/Network) |
84+| NSSystemDomainMask | A directory for system files provided by Apple (/System) |
85+| NSAllDomainsMask | All domains |
86+
87+Path Domains
88+
89+## Code Examples
90+
91+You can now locate various significant directories and display them as demonstrated by the code examples below.
92+
93+### User caches directory
94+
95+``` pascal
96+procedure TForm1.MenuItem5Click(Sender: TObject);
97+begin
98+ ShowMessage('User caches dir: ' + GetSignificantDir(NSCachesDirectory,NSUserDomainMask,0));
99+end;
100+```
101+
102+### User trash directory
103+
104+``` pascal
105+procedure TForm1.MenuItem16Click(Sender: TObject);
106+begin
107+ ShowMessage('User trash dir: '
108+ + GetSignificantDir(NSTrashDirectory,NSUserDomainMask,0));
109+end;
110+```
111+
112+### Users directory
113+
114+Maybe not what you expect... this is the */Users* directory :-)
115+
116+``` pascal
117+procedure TForm1.MenuItem10Click(Sender: TObject);
118+begin
119+ ShowMessage('Users dir: ' + GetSignificantDir(NSUserDirectory,NSLocalDomainMask,0));
120+end;
121+```
122+
123+To retrieve the current user's home directory, you need to use the *NSHomeDirectory()* function instead:
124+
125+``` pascal
126+procedure TForm1.MenuItem11Click(Sender: TObject);
127+begin
128+ ShowMessage('User''s dir: ' + NSStringToString(NSHomeDirectory));
129+end;
130+```
131+
132+**BUT** note that if the application is in a sandbox, this will return the application’s [[Sandboxing for macOS|sandbox]] directory and not the current user’s home directory.
133+
134+An alternative way to retrieve the current user's hime directory:
135+
136+``` pascal
137+function NSUserName: CFStringRef external name '_NSUserName';
138+...
139+
140+procedure TForm1.MenuItem22Click(Sender: TObject);
141+var
142+ usernameStr: ShortString;
143+ status: Boolean = false;
144+begin
145+ status := CFStringGetPascalString(CFStringRef(NSusername),@usernameStr,255,CFStringGetSystemEncoding);
146+ if(status = true) then
147+ ShowMessage('Current user''s dir: ' + NSStringToString(NShomeDirectoryForUser(NSStr(usernameStr))))
148+ else
149+ ShowMessage('Error retrieving username');
150+end;
151+```
152+
153+### All application directories
154+
155+``` pascal
156+procedure TForm1.MenuItem7Click(Sender: TObject);
157+var
158+ count: byte;
159+begin
160+ for count := 0 to 12 do
161+ if GetSignificantDir(NSAllApplicationsDirectory, NSAllDomainsMask, count) <> '' then
162+ ShowMessage('Application dir ' + IntToStr(count) + ': '
163+ + GetSignificantDir(NSAllApplicationsDirectory,NSAllDomainsMask,count))
164+ else
165+ exit;
166+end;
167+```
168+
169+### All user application directories
170+
171+``` pascal
172+procedure TForm1.MenuItem8Click(Sender: TObject);
173+var
174+ count: byte;
175+begin
176+ for count := 0 to 12 do
177+ if GetSignificantDir(NSAllApplicationsDirectory, NSUserDomainMask, count) <> '' then
178+ ShowMessage('User application dir ' + IntToStr(count) + ': '
179+ + GetSignificantDir(NSAllApplicationsDirectory,NSUserDomainMask,count))
180+ else
181+ exit;
182+end;
183+```
184+
185+### All system application directories
186+
187+``` pascal
188+procedure TForm1.MenuItem9Click(Sender: TObject);
189+var
190+ count: byte;
191+begin
192+ for count := 0 to 12 do
193+ if GetSignificantDir(NSAllApplicationsDirectory, NSSystemDomainMask, count) <> '' then
194+ ShowMessage('System application dir ' + IntToStr(count) + ': '
195+ + GetSignificantDir(NSAllApplicationsDirectory,NSSystemDomainMask,count))
196+ else
197+ exit;
198+end;
199+```
200+
201+## Replacements for FPC's GetAppConfigDir and GetAppConfigFile
202+
203+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:
204+
205+``` pascal
206+writeLn(GetAppConfigDir(true));
207+writeLn(GetAppConfigDir(false));
208+
209+writeLn(GetAppConfigFile(true));
210+writeLn(GetAppConfigFile(false));
211+
212+writeLn(GetAppConfigFile(true,true));
213+writeLn(GetAppConfigFile(true,false));
214+
215+writeLn(GetAppConfigFile(false,false));
216+writeLn(GetAppConfigFile(false,true));
217+```
218+
219+is this:
220+
221+```text
222+/etc/
223+/Users/<username>/.config/
224+
225+/etc/project1.cfg
226+/Users/<username>/.config/project1.cfg
227+
228+/etc/project1/project1.cfg
229+/etc/project1.cfg
230+
231+/Users/<username>/.config/project1.cfg
232+/Users/<username>/.config/project1/project1.cfg
233+```
234+
235+whereas the correct output for a macOS command line application (ie without an [[Application Bundle]]) is:
236+
237+```text
238+/Library/Application Support/project1/
239+/Users/<username>/Library/Application Support/project1/
240+
241+/Library/Application Support/project1/project1.plist
242+/Users/<username>/Library/Application Support/project1/project1.plist
243+
244+/Library/Application Support/project1/project1.plist
245+/Library/Application Support/project1/project1.plist
246+
247+/Users/<username>/Library/Application Support/project1/project1.plist
248+/Users/<username>/Library/Application Support/project1/project1.plist
249+```
250+
251+and the correct output for a macOS GUI application (ie with an [[Application Bundle]]):
252+
253+```text
254+/Library/Application Support/com.company.project1/
255+/Users/<username>/Library/Application Support/com.company.project1/
256+
257+/Library/Application Support/com.company.project1/project1.plist
258+/Users/<username>/Library/Application Support/com.company.project1/project1.plist
259+
260+/Library/Application Support/com.company.project1/project1.plist
261+/Library/Application Support/com.company.project1/project1.plist
262+
263+/Users/<username>/Library/Application Support/com.company.project1/project1.plist
264+/Users/<username>/Library/Application Support/com.company.project1/project1.plist
265+```
266+
267+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:
268+
269+``` pascal
270+{$mode objfpc}{H+}
271+{$modeswitch objectivec1}
272+
273+...
274+
275+Uses
276+ ...
277+ SysUtils,
278+ CocoaAll,
279+ Cocoautils;
280+...
281+
282+//
283+// GetAppConfigDir Replacement
284+//
285+
286+function GetAppConfigDir2(Global: Boolean): String;
287+
288+{$IFDEF DARWIN}
289+var
290+ bundleIdStr: String;
291+ pathsArr : NSArray;
292+{$ENDIF}
293+begin
294+ {$IFDEF DARWIN}
295+ bundleIdStr := NSStringToString(NSBundle.mainBundle.bundleIdentifier);
296+ if(bundleIdStr = '') then
297+ bundleIdStr := ExtractFileName(paramstr(0)); // Handle non-bundled apps eg command line
298+
299+ if(Global) then
300+ pathsArr := NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSLocalDomainMask, True)
301+ else
302+ pathsArr := NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True);
303+
304+ Result := NSString(pathsArr.objectAtIndex(0)).Utf8String + '/' + bundleIdStr + '/';
305+ {$ELSE}
306+ GetAppConfigDir(global);
307+ {$ENDIF}
308+end;
309+
310+//
311+// GetAppConfigFile replacement
312+//
313+
314+Function GetAppConfigFile2(Global : Boolean; SubDir : Boolean) : String;
315+
316+{$IFDEF DARWIN}
317+var
318+ bundleIdStr: ShortString;
319+ appNameStr: ShortString;
320+ pathBufferStr: String;
321+ isAppBundle : Boolean = True;
322+{$ENDIF}
323+begin
324+ {$IFDEF DARWIN}
325+ bundleIdStr := NSStringToString(NSBundle.mainBundle.bundleIdentifier);
326+ if(bundleIdStr = '') then
327+ begin
328+ bundleIdStr := ExtractFileName(paramstr(0)); // Handle non-bundled apps eg command line
329+ isAppBundle := False;
330+ end;
331+
332+ pathBufferStr := GetAppConfigDir2(Global);
333+
334+ if(isAppBundle) then
335+ appNameStr := StringReplace(ExtractFileExt(bundleIdStr), '.', '', [rfReplaceAll])
336+ else
337+ appNameStr := extractFileName(ExcludeTrailingPathDelimiter(pathBufferStr));
338+
339+ if(SubDir) then
340+ // Ignore because Apple dictates the subdir already as part of GetAppConfigDir()
341+ ;
342+
343+ Result := pathBufferStr + appNameStr + '.plist';
344+
345+ {$ELSE}
346+ Result := GetAppConfigFile(Global, SubDir);
347+ {$ENDIF}
348+end;
349+```
350+
351+## See also
352+
353+- [[Locating macOS app support, preferences folders]]
354+- [[Locating the macOS application resources directory]]
355+- [[Locating the macOS tmp directory]]
356+
357+---
358+*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](/wiki-contact).*
359+Category: Code Snippets
360+Category: Platform-sensitive development
361+Category: macOS

Back to history