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
| Old | New | Line | |
|---|---|---|---|
| 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 |
