View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0020706 | FPC | RTL | public | 2011-11-18 21:44 | 2021-02-16 09:11 |
Reporter | Johannes W. Dietrich | Assigned To | Jonas Maebe | ||
Priority | normal | Severity | major | Reproducibility | always |
Status | assigned | Resolution | open | ||
Platform | Macintosh | OS | Mac OS X | ||
Product Version | 2.4.4 | ||||
Summary | 0020706: GetAppConfigDir delivers uncorrect path on Mac OS | ||||
Description | On Mac OS X (Carbon widgetset, but probably not restricted to that) GetAppConfigDir delivers a wrong path. The function returns the directory ~/.config/ApplicationName that is hard to find for users on Mac OS X 10.4 as it is hidden by default. Moreover it is not compatible with Apple's user interface guidelines. Even more importantly, on modern versions of Mac OS X (at least 10.6 and up) this directory is write protected, therefore preferences can not be stored. The correct path would be ~/Library/Preferences/. | ||||
Steps To Reproduce | This problem is reproducible permanently. | ||||
Tags | 0.9.30, Carbon, cocoa, generic patch, Mac OS X, rtl, sysutils | ||||
Fixed in Revision | |||||
FPCOldBugId | |||||
FPCTarget | |||||
Attached Files |
|
has duplicate | 0025746 | closed | Jonas Maebe | GetAppConfigDir function does not return appropriate path on Mac |
related to | 0022624 | closed | Michael Van Canneyt | [Windows] GetAppConfigFile's SubDir parameter does not behave as documented |
|
~/.config is not write-protected unless you make it write protected yourself (Mac OS X 10.6 or earlier doesn't matter). It's a plain directory in the user's home directory (that is created by the program if it does not yet exist). Both ~/.config and ~/Library/Preferences are proper places to store config files, especially since both Unix-style command line programs and high level GUI applications can be created with FPC. It is however wrong to hardcode ~/Library/Preferences, because that directory may not be the correct place in case of network home directories. Maybe the Lazarus Carbon/Cocoa widget sets should set "IsConsole" to false (or the -WC/-WF command line switches of the compiler may be usable for that), and then the FCL could be modified to return a different directory in that case on Mac OS X based on what FSFindFolder/NSSearchPathForDirectoriesInDomains returns. |
|
Sorry, but on a fresh installation of Mac OS X 10.6 the application is not able to write the preferences file in ~/.config/. Therefore I assumed that the directory is write protected, but there may be an other reason for the fault. |
|
> Sorry, but on a fresh installation of Mac OS X 10.6 the > application is not able to write the preferences file in ~/.config/. The directory may not yet exist. You do have to create it in that case (this is not specific to Mac OS X, the same can happen on Linux or any other Unix platform). |
|
Thanks for the hint, I will give it a try. However, I still have the opinion that it would be better to store preferences in ~/Library/Preferences, because most users will not be able to manage the issue with the missing ~/.config/ directory. |
|
I meant that the program should create that directory if it doesn't exist yet. Maybe even the GetAppConfigDir routine itself should do that. |
|
For the meanwhile, i have created a workaround with the following function: function GetPreferencesFolder: String; { platform-independend method to search for the location of preferences folder} const kMaxPath = 1024; var {$IFDEF LCLCarbon} theError: OSErr; theRef: FSRef; {$ENDIF} pathBuffer: PChar; begin {$IFDEF LCLCarbon} try pathBuffer := Allocmem(kMaxPath); except on exception do exit; end; try Fillchar(pathBuffer^, kMaxPath, #0); Fillchar(theRef, Sizeof(theRef), #0); theError := FSFindFolder(kOnAppropriateDisk, kPreferencesFolderType, kDontCreateFolder, theRef); if (pathBuffer <> nil) and (theError = noErr) then begin theError := FSRefMakePath(theRef, pathBuffer, kMaxPath); if theError = noErr then GetPreferencesFolder := UTF8ToAnsi(StrPas(pathBuffer)) + '/'; end; finally Freemem(pathBuffer); end {$ELSE} GetPreferencesFolder := GetAppConfigDir(false); {$ENDIF} end; It returns the correct path to the preferences folder on Mac OS X and the standard paths with GetAppConfigDir on Windows and Linux. |
|
This still appears to be a problem under MacOS X. I can confirm that ~/.config/AppName appears to cause problems with several MacOS X versions (I can confirm 10.7, 10.8 and 10.9). The directory can not be created for some reason (when I distribute an application) ... I use the following code for that: if not DirectoryExists( GetAppConfigDir(false) ) then mkdir( GetAppConfigDir(false) ); GateKeeper set to "Allow Apps fro anywhere", and none of these users made their home directory read only. Lazarus 1.0.12, FPC 2.6.2. |
|
mkdir fails because it cannot create a directory hierarchy (i.e., it will fail to create ~/.config/AppName if ~/.config doesn't exist yet). Use sysutils.forcedirectories instead: http://www.freepascal.org/docs-html/rtl/sysutils/forcedirectories.html |
|
A modified version of Joahnnes W. Dietrich's code: function GetPreferencesFolder(Global:boolean): String; { platform-independend method to search for the location of preferences folder} const kMaxPath = 1024; var {$IFDEF LCLCarbon} theError: OSErr; theRef: FSRef; {$ENDIF} pathBuffer: PChar; begin {$IFDEF LCLCarbon} try pathBuffer := Allocmem(kMaxPath); except on exception do exit; end; try Fillchar(pathBuffer^, kMaxPath, #0); Fillchar(theRef, Sizeof(theRef), #0); if Global then // kLocalDomain theError := FSFindFolder(kLocalDomain, kApplicationSupportFolderType, kDontCreateFolder, theRef) else theError := FSFindFolder(kUserDomain , kApplicationSupportFolderType, kDontCreateFolder, theRef); if (pathBuffer <> nil) and (theError = noErr) then begin theError := FSRefMakePath(theRef, pathBuffer, kMaxPath); if theError = noErr then GetPreferencesFolder := UTF8ToAnsi(StrPas(pathBuffer)) + '/'; end; finally Freemem(pathBuffer); end {$ELSE} GetPreferencesFolder := GetAppConfigDir(false); {$ENDIF} { User preferences live in the location returned by FSFindFolder(kUserDomain,kPreferencesFolderType). User application support items live in the location returned by FSFindFolder(kUserDomain, kApplicationSupportFolderType). Global preferences live in the location returned by FSFindFolder(kLocalDomain,kPreferencesFolderType). Global application support items live in the location returned by FSFindFolder(kLocalDomain, kApplicationSupportFolderType). } end; This uses the Application Data folder, since GetAppConfigDir does not distinguish between Preferences and Application Data. The Application Data folder would also not require a proper XML file for settings/preferences. Just an idea ... I did do an attempt to modify GetAppConfigDir and GetAppConfigFile, but the files are read-only, probably since it's part of FPC ...? See also: http://forum.lazarus.freepascal.org/index.php/topic,32599.0.html |
|
Geez Louise, it's been nearly 8 years and this is still not resolved :( Delphi will have a 64 bit macOS compiler any day <hint>. Thanks to Johannes and Hans, on whose shoulders I stand, I've modified their efforts to bring it up to date (replace Carbon defines, rename function, expand to cover macOS application support folder and preferences folder): { platform-independent method to retrieve application/preferences support directory } { FolderType values: kApplicationSupportFolderType _or_ kPreferencesFolderType } function GetSupportDir(Global:boolean; FolderType:LongWord): String; {$IFDEF DARWIN} const kMaxPath = 1024; var theError: OSErr; theRef: FSRef; pathBuffer: PChar; {$ENDIF} begin {$IFDEF DARWIN} try pathBuffer := Allocmem(kMaxPath); except on exception do exit; end; try Fillchar(pathBuffer^, kMaxPath, #0); Fillchar(theRef, Sizeof(theRef), #0); if Global then // kLocalDomain theError := FSFindFolder(kLocalDomain, FolderType, kDontCreateFolder, theRef) else // kUserDomain theError := FSFindFolder(kUserDomain , FolderType, kDontCreateFolder, theRef); if (pathBuffer <> nil) and (theError = noErr) then begin theError := FSRefMakePath(theRef, pathBuffer, kMaxPath); if theError = noErr then GetSupportDir := UTF8ToAnsi(StrPas(pathBuffer)) + '/'; end; finally Freemem(pathBuffer); end {$ELSE} GetSupportDir := GetAppConfigDir(false); {$ENDIF} { User preferences live in the location returned by FSFindFolder(kUserDomain,kPreferencesFolderType). User application support items live in the location returned by FSFindFolder(kUserDomain, kApplicationSupportFolderType). Global preferences live in the location returned by FSFindFolder(kLocalDomain,kPreferencesFolderType). Global application support items live in the location returned by FSFindFolder(kLocalDomain, kApplicationSupportFolderType). } end; |
|
1. Fixed an un-initialised variable (theRef) compiler warning. 2. Fixed so macOS returning the app support directory but other OS returning the directory in the app support directory. {platform-independent method to retrieve application support/preferences directories} {FolderTypes: kApplicationSupportFolderType _or_ kPreferencesFolderType} function GetSupportDir(Global:boolean; FolderType:LongWord): String; {$IFDEF DARWIN} const kMaxPath = 1024; var theError: OSErr; theRef: FSRef; pathBuffer: PChar; {$ENDIF} begin {$IFDEF DARWIN} theRef := Default(FSRef); // init try pathBuffer := Allocmem(kMaxPath); except on exception do exit; end; try Fillchar(pathBuffer^, kMaxPath, #0); Fillchar(theRef, Sizeof(theRef), #0); if Global then // kLocalDomain theError := FSFindFolder(kLocalDomain, FolderType, kDontCreateFolder, theRef) else // kUserDomain theError := FSFindFolder(kUserDomain , FolderType, kDontCreateFolder, theRef); if (pathBuffer <> nil) and (theError = noErr) then begin theError := FSRefMakePath(theRef, pathBuffer, kMaxPath); if theError = noErr then GetSupportDir := UTF8ToAnsi(StrPas(pathBuffer)) + '/' + ApplicationName + '/'; end; finally Freemem(pathBuffer); end {$ELSE} GetSupportDir := GetAppConfigDirUTF8(false); {$ENDIF} { User preferences live in the location returned by FSFindFolder(kUserDomain,kPreferencesFolderType). User application support items live in the location returned by FSFindFolder(kUserDomain, kApplicationSupportFolderType). Global preferences live in the location returned by FSFindFolder(kLocalDomain,kPreferencesFolderType). Global application support items live in the location returned by FSFindFolder(kLocalDomain, kApplicationSupportFolderType). } end; |
|
1. Fixed non-macOS config to accept the global boolean (previously hardcoded false - oops) {platform-independent method to retrieve application support/preferences directories} {FolderTypes: kApplicationSupportFolderType _or_ kPreferencesFolderType} function GetSupportDir(Global:boolean; FolderType:LongWord): String; {$IFDEF DARWIN} const kMaxPath = 1024; var theError: OSErr; theRef: FSRef; pathBuffer: PChar; {$ENDIF} begin {$IFDEF DARWIN} theRef := Default(FSRef); // init try pathBuffer := Allocmem(kMaxPath); except on exception do exit; end; try Fillchar(pathBuffer^, kMaxPath, #0); Fillchar(theRef, Sizeof(theRef), #0); if Global then // kLocalDomain theError := FSFindFolder(kLocalDomain, FolderType, kDontCreateFolder, theRef) else // kUserDomain theError := FSFindFolder(kUserDomain , FolderType, kDontCreateFolder, theRef); if (pathBuffer <> nil) and (theError = noErr) then begin theError := FSRefMakePath(theRef, pathBuffer, kMaxPath); if theError = noErr then GetSupportDir := UTF8ToAnsi(StrPas(pathBuffer)) + '/' + ApplicationName + '/'; end; finally Freemem(pathBuffer); end {$ELSE} GetSupportDir := GetAppConfigDirUTF8(global); {$ENDIF} { User preferences live in the location returned by FSFindFolder(kUserDomain,kPreferencesFolderType). User application support items live in the location returned by FSFindFolder(kUserDomain, kApplicationSupportFolderType). Global preferences live in the location returned by FSFindFolder(kLocalDomain,kPreferencesFolderType). Global application support items live in the location returned by FSFindFolder(kLocalDomain, kApplicationSupportFolderType). } end; |
|
@Trevor Roydhouse : Please, create proper patch : https://wiki.freepascal.org/Creating_A_Patch |
|
The previous solution above was deprecated in macOS 10.8. This non-deprecated solution applies to macOS 10.0+. The new solution also copes with applications which are not stored in application bundles and works for sandboxed applications trapped in containers. It does not deal with preferences, which should use CFPreferences to automatically read/write preferences in the correct location in the correct property list file. Instead, it deals with the directory (Application Support) in which are stored resource and data files that an application creates and manages for the user (eg to store application state information, computed or downloaded data, user created data that the application manages on behalf of the user, and autosave files). This choice was made as it seemed a reasonable compromise between macOS usage and the rationale behind the existing FPC functions. Alas, my attempt to incorporate this solution into unix/sysutils.pp and generate a proper patch has failed because of the need to access functions in CocoaAll and MacOSAll. Someone with the relevant knowledge of FPC and its RTL would no doubt be able to overcome this. Function GetAppConfigDir(Global : Boolean) : String; var bundleIdStr : ShortString; // Bundle Ids are limited to 155 characters pathBuffer : PChar; pathsArr : NSArray; status : Boolean; begin try pathBuffer := AllocMem(MAX_PATH); except on exception do exit; end; try try CFStringGetPascalString(CFStringRef(CFBundleGetIdentifier(CFBundleGetMainBundle)),@bundleIdStr,255,CFStringGetSystemEncoding); except on exception do // Handle non-bundled apps eg command line bundleIdStr := ExtractFileName(paramstr(0)); end; if(Global) then // NSLocalDomain pathsArr := NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSLocalDomainMask, True) else // NSUserDomain pathsArr := NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True); status := CFStringGetCString(CFStringRef(pathsArr.objectAtIndex(0)),pathBuffer,MAX_PATH,CFStringGetSystemEncoding); if(status) then Result := pathBuffer + '/' + bundleIdStr + '/' else Result := ''; finally FreeMem(pathBuffer); end; Function GetAppConfigFile(Global : Boolean; SubDir : Boolean) : String; var bundleIdStr : ShortString; appNameStr : ShortString; pathBufferStr : String; isAppBundle : Boolean = True; begin try CFStringGetPascalString(CFStringRef(CFBundleGetIdentifier(CFBundleGetMainBundle)),@bundleIdStr,255,CFStringGetSystemEncoding); except on exception do begin // Handle non-bundled apps eg command line bundleIdStr := ExtractFileName(paramstr(0)); isAppBundle := False; end; end; pathBufferStr := GetAppConfigDir(Global); if(isAppBundle) then appNameStr := StringReplace(ExtractFileExt(bundleIdStr), '.', '', [rfReplaceAll]) else appNameStr := extractFileName(ExcludeTrailingPathDelimiter(pathBufferStr)); if(SubDir) then // Ignore because macOS dictates the subdir inclusion already as part of GetAppConfigDir() ; Result := pathBufferStr + appNameStr + '.plist'; end; Function GetAppConfigFile(Global : Boolean) : String; var bundleIdStr : ShortString; appNameStr : ShortString; pathBufferStr : String; isAppBundle : Boolean = True; begin try CFStringGetPascalString(CFStringRef(CFBundleGetIdentifier(CFBundleGetMainBundle)),@bundleIdStr,255,CFStringGetSystemEncoding); except on exception do begin // Handle non-bundled apps eg command line bundleIdStr := ExtractFileName(paramstr(0)); isAppBundle := False; end; end; pathBufferStr := GetAppConfigDir(Global); if(isAppBundle) then appNameStr := StringReplace(ExtractFileExt(bundleIdStr), '.', '', [rfReplaceAll]) else appNameStr := extractFileName(ExcludeTrailingPathDelimiter(pathBufferStr)); Result := GetAppConfigDir(Global) + appNameStr + '.plist'; end; |
|
On latest MacOS version BigSur 11.2.1 I am getting reports " Unable to create file "/Users/*username*/.config/MyApp/reg.xml": Permission denied. Press OK to ignore and risk data corruption. Press Abort to kill the program. " because the TRegistry wrapper uses GetAppConfigDir |
|
That suggests the TRegistry unit has a bug and does not create this directory if it is absent. |
Date Modified | Username | Field | Change |
---|---|---|---|
2011-11-18 21:44 | Johannes W. Dietrich | New Issue | |
2011-11-18 21:55 | Johannes W. Dietrich | Tag Attached: Mac OS X | |
2011-11-18 21:55 | Johannes W. Dietrich | Tag Attached: rtl | |
2011-11-18 21:55 | Johannes W. Dietrich | Tag Attached: 0.9.30 | |
2011-11-18 21:55 | Johannes W. Dietrich | Tag Attached: Carbon | |
2011-11-18 21:57 | Johannes W. Dietrich | Tag Attached: sysutils | |
2011-11-18 22:01 | Jonas Maebe | Note Added: 0054224 | |
2011-11-29 20:38 | Johannes W. Dietrich | Note Added: 0054554 | |
2011-11-29 23:10 | Jonas Maebe | Note Added: 0054556 | |
2011-12-04 19:47 | Johannes W. Dietrich | Note Added: 0054703 | |
2011-12-05 13:35 | Jonas Maebe | Note Added: 0054722 | |
2012-04-25 19:58 | Johannes W. Dietrich | Note Added: 0058981 | |
2012-04-30 11:58 | Jonas Maebe | Status | new => assigned |
2012-04-30 11:58 | Jonas Maebe | Assigned To | => Jonas Maebe |
2012-08-12 20:38 | Jonas Maebe | Relationship added | has duplicate 0022624 |
2012-08-12 20:40 | Jonas Maebe | Relationship replaced | related to 0022624 |
2013-11-10 23:43 | Hans Luijten | Note Added: 0071253 | |
2013-11-11 00:15 | Jonas Maebe | Note Added: 0071255 | |
2013-11-11 00:15 | Jonas Maebe | Note Edited: 0071255 | View Revisions |
2014-02-07 12:02 | Vincent Snijders | Relationship added | has duplicate 0025687 |
2014-02-19 13:22 | Bart Broersma | Relationship deleted | has duplicate 0025687 |
2014-02-20 18:58 | Jonas Maebe | Relationship added | has duplicate 0025746 |
2014-02-20 19:03 | Dmitry Boyarintsev | Relationship added | has duplicate 0025687 |
2014-02-26 16:34 | Dmitry Boyarintsev | Relationship deleted | has duplicate 0025687 |
2016-05-12 13:55 | Hans Luijten | Note Added: 0092536 | |
2019-07-12 09:06 | Trevor Roydhouse | Note Added: 0117208 | |
2019-11-30 02:30 | Trevor Roydhouse | Note Added: 0119553 | |
2019-11-30 02:31 | Trevor Roydhouse | Tag Attached: cocoa | |
2019-12-24 01:39 | Trevor Roydhouse | Note Added: 0120048 | |
2019-12-24 01:41 | Trevor Roydhouse | Tag Attached: generic patch | |
2019-12-24 01:43 | Trevor Roydhouse | Tag Attached: MacOS | |
2019-12-24 01:43 | Trevor Roydhouse | Tag Detached: MacOS | |
2019-12-24 04:59 | Cyrax | Note Added: 0120049 | |
2019-12-24 06:11 | Cyrax | Note Edited: 0120049 | View Revisions |
2020-05-31 07:47 | Trevor Roydhouse | Note Added: 0123152 | |
2021-02-16 08:13 | MIS5 | Note Added: 0128946 | |
2021-02-16 09:11 | Jonas Maebe | Note Added: 0128947 |