View Issue Details

IDProjectCategoryView StatusLast Update
0020706FPCRTLpublic2019-11-30 02:31
ReporterJohannes W. DietrichAssigned ToJonas Maebe 
PrioritynormalSeveritymajorReproducibilityalways
Status assignedResolutionopen 
PlatformMacintoshOSMac OS XOS Version10.4.11
Product Version2.4.4Product Build 
Target VersionFixed in Version 
Summary0020706: GetAppConfigDir delivers uncorrect path on Mac OS
DescriptionOn 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 ReproduceThis problem is reproducible permanently.
Tags0.9.30, Carbon, cocoa, Mac OS X, rtl, sysutils
Fixed in Revision
FPCOldBugId
FPCTarget
Attached Files

Relationships

has duplicate 0025746 closedJonas Maebe GetAppConfigDir function does not return appropriate path on Mac 
related to 0022624 closedMichael Van Canneyt [Windows] GetAppConfigFile's SubDir parameter does not behave as documented 

Activities

Jonas Maebe

2011-11-18 22:01

manager   ~0054224

~/.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.

Johannes W. Dietrich

2011-11-29 20:38

reporter   ~0054554

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.

Jonas Maebe

2011-11-29 23:10

manager   ~0054556

> 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).

Johannes W. Dietrich

2011-12-04 19:47

reporter   ~0054703

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.

Jonas Maebe

2011-12-05 13:35

manager   ~0054722

I meant that the program should create that directory if it doesn't exist yet. Maybe even the GetAppConfigDir routine itself should do that.

Johannes W. Dietrich

2012-04-25 19:58

reporter   ~0058981

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.

Hans Luijten

2013-11-10 23:43

reporter   ~0071253

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.

Jonas Maebe

2013-11-11 00:15

manager   ~0071255

Last edited: 2013-11-11 00:15

View 2 revisions

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

Hans Luijten

2016-05-12 13:55

reporter   ~0092536

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

Trevor Roydhouse

2019-07-12 09:06

reporter   ~0117208

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;

Trevor Roydhouse

2019-11-30 02:30

reporter   ~0119553

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;

Issue History

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