diff --git a/docs/changes.txt b/docs/changes.txt index 4d4cea7339..e53e0cd8df 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -195,6 +195,7 @@ wxOSX: Unix: - Support new gstreamer API in 1.7.2+ in wxMediaCtrl (Sebastian Dröge). +- Add wxStandardPaths::SetFileLayout(FileLayout_XDG) (Martin Koegler). 3.1.0: (released 2016-02-29) diff --git a/include/wx/msw/stdpaths.h b/include/wx/msw/stdpaths.h index e9e1e8b231..3f733bc67f 100644 --- a/include/wx/msw/stdpaths.h +++ b/include/wx/msw/stdpaths.h @@ -29,6 +29,8 @@ public: virtual wxString GetUserLocalDataDir() const wxOVERRIDE; virtual wxString GetPluginsDir() const wxOVERRIDE; virtual wxString GetUserDir(Dir userDir) const wxOVERRIDE; + virtual wxString MakeConfigFileName(const wxString& basename, + ConfigFileConv conv) const wxOVERRIDE; // MSW-specific methods diff --git a/include/wx/osx/cocoa/stdpaths.h b/include/wx/osx/cocoa/stdpaths.h index 59c33f2019..c77713da12 100644 --- a/include/wx/osx/cocoa/stdpaths.h +++ b/include/wx/osx/cocoa/stdpaths.h @@ -32,6 +32,8 @@ public: GetLocalizedResourcesDir(const wxString& lang, ResourceCat category = ResourceCat_None) const wxOVERRIDE; virtual wxString GetUserDir(Dir userDir) const wxOVERRIDE; + virtual wxString MakeConfigFileName(const wxString& basename, + ConfigFileConv conv) const wxOVERRIDE; protected: // Ctor is protected, use wxStandardPaths::Get() instead of instantiating diff --git a/include/wx/stdpaths.h b/include/wx/stdpaths.h index 8f7c2d43bb..a7aa61c892 100644 --- a/include/wx/stdpaths.h +++ b/include/wx/stdpaths.h @@ -60,6 +60,21 @@ public: Dir_Videos }; + // Layout to use for user config/data files under Unix. + enum FileLayout + { + FileLayout_Classic, // Default: use home directory. + FileLayout_XDG // Recommended: use XDG specification. + }; + + // Naming convention for the config files under Unix. + enum ConfigFileConv + { + ConfigFileConv_Dot, // Classic Unix dot-file convention. + ConfigFileConv_Ext // Use .conf extension. + }; + + // return the global standard paths object static wxStandardPaths& Get(); @@ -155,6 +170,10 @@ public: virtual wxString GetUserDir(Dir userDir) const; + virtual wxString + MakeConfigFileName(const wxString& basename, + ConfigFileConv conv = ConfigFileConv_Ext) const = 0; + // virtual dtor for the base class virtual ~wxStandardPathsBase(); @@ -166,6 +185,15 @@ public: bool UsesAppInfo(int info) const { return (m_usedAppInfo & info) != 0; } + void SetFileLayout(FileLayout layout) + { + m_fileLayout = layout; + } + + FileLayout GetFileLayout() const + { + return m_fileLayout; + } protected: // Ctor is protected as this is a base class which should never be created @@ -182,6 +210,9 @@ protected: // combination of AppInfo_XXX flags used by AppendAppInfo() int m_usedAppInfo; + + // The file layout to use, currently only used under Unix. + FileLayout m_fileLayout; }; #if wxUSE_STDPATHS diff --git a/include/wx/unix/stdpaths.h b/include/wx/unix/stdpaths.h index 74f915398c..e67d9c6ea2 100644 --- a/include/wx/unix/stdpaths.h +++ b/include/wx/unix/stdpaths.h @@ -49,6 +49,8 @@ public: #ifndef __VMS virtual wxString GetUserDir(Dir userDir) const wxOVERRIDE; #endif + virtual wxString MakeConfigFileName(const wxString& basename, + ConfigFileConv conv) const wxOVERRIDE; protected: // Ctor is protected, use wxStandardPaths::Get() instead of instantiating diff --git a/interface/wx/stdpaths.h b/interface/wx/stdpaths.h index 9a1f3cbf9f..9540770cb4 100644 --- a/interface/wx/stdpaths.h +++ b/interface/wx/stdpaths.h @@ -135,6 +135,66 @@ public: Dir_Videos }; + /** + Possible values for SetFileLayout() argument. + + The elements of this enum correspond to the different file layout + standards under Unix systems. + + @since 3.1.1 + */ + enum FileLayout + { + /** + Use the classic file layout. + + User configuration and data files are located directly in the home + directory. + + This is the default behaviour for compatibility reasons. + */ + FileLayout_Classic, + + /** + Use a XDG styled file layout. + + File layout follows the XDG Base Directory Specification (see + https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html). + + This is the recommended layout for new applications. + */ + FileLayout_XDG + }; + + /** + Possible values for MakeConfigFileName() naming convention argument. + + The values in this enum are only used under Unix and only when using + the classic Unix convention for file layout, in XDG mode, XDG naming + convention is used unconditionally. + + @since 3.1.1 + */ + enum ConfigFileConv + { + /** + Use the class Unix dot-file convention. + + Prepend the dot to the file base name. + + This value is ignored when in XDG mode, where MakeConfigFileName() + always behaves as if ConfigFileConv_Ext was specified. + */ + ConfigFileConv_Dot, + + /** + Use @c .conf extension for the file names. + + This convention is always used in XDG mode. + */ + ConfigFileConv_Ext + }; + /** MSW-specific function undoing the effect of IgnoreAppSubDir() calls. @@ -300,8 +360,11 @@ public: virtual wxString GetTempDir() const; /** - Return the directory for the user config files: - - Unix: @c ~ (the home directory) + Return the directory for the user config files. + + This directory is: + - Unix: @c ~ (the home directory) or @c XDG_CONFIG_HOME depending on + GetFileLayout() return value - Windows: @c "C:\Users\username\AppData\Roaming" or @c "C:\Documents and Settings\username\Application Data" - Mac: @c ~/Library/Preferences @@ -326,7 +389,8 @@ public: If the value could not be determined the users home directory is returned. - @note On Unix this supports the xdg user dirs specification. + @note On Unix this method respects the XDG base directory specification + only if SetFileLayout() with @c FileLayout_XDG had been called. @since 3.1.0 */ @@ -444,6 +508,44 @@ public: */ void UseAppInfo(int info); + /** + Returns the current file layout. + + The default layout is @c FileLayout_Classic for compatibility, however + newer applications are encouraged to set it to @c FileLayout_XDG on + program startup. + + @since 3.1.1 + */ + void SetFileLayout(FileLayout layout); + + /** + Returns the current file layout. + + @see SetFileLayout() + + @since 3.1.1 + */ + FileLayout GetFileLayout() const; + + /** + Return the file name which would be used by wxFileConfig if it were + constructed with @a basename. + + @a conv is used to construct the name of the file under Unix and only + matters when using the class file layout, i.e. if SetFileLayout() had + @e not been called with @c FileLayout_XDG argument. In this case, this + argument is used to determine whether to use an extension or a leading + dot. When following XDG specification, the function always appends the + extension, regardless of @a conv value. Finally, this argument is not + used at all under non-Unix platforms. + + @since 3.1.1 + */ + virtual wxString + MakeConfigFileName(const wxString& basename, + ConfigFileConv conv = ConfigFileConv_Ext) const; + protected: /** Protected default constructor. diff --git a/src/common/fileconf.cpp b/src/common/fileconf.cpp index c87bcf82f0..77fa9a8103 100644 --- a/src/common/fileconf.cpp +++ b/src/common/fileconf.cpp @@ -247,26 +247,6 @@ public: // static functions // ---------------------------------------------------------------------------- -// this function modifies in place the given wxFileName object if it doesn't -// already have an extension -// -// note that it's slightly misnamed under Mac as there it doesn't add an -// extension but modifies the file name instead, so you shouldn't suppose that -// fn.HasExt() is true after it returns -static void AddConfFileExtIfNeeded(wxFileName& fn) -{ - if ( !fn.HasExt() ) - { -#if defined( __WXMAC__ ) - fn.SetName(fn.GetName() + wxT(" Preferences")); -#elif defined( __UNIX__ ) - fn.SetExt(wxT("conf")); -#else // Windows - fn.SetExt(wxT("ini")); -#endif // UNIX/Win - } -} - wxString wxFileConfig::GetGlobalDir() { return wxStandardPaths::Get().GetConfigDir(); @@ -286,31 +266,28 @@ wxString wxFileConfig::GetLocalDir(int style) wxFileName wxFileConfig::GetGlobalFile(const wxString& szFile) { - wxFileName fn(GetGlobalDir(), szFile); + wxStandardPathsBase& stdp = wxStandardPaths::Get(); - AddConfFileExtIfNeeded(fn); - - return fn; + return wxFileName(GetGlobalDir(), stdp.MakeConfigFileName(szFile)); } wxFileName wxFileConfig::GetLocalFile(const wxString& szFile, int style) { - wxFileName fn(GetLocalDir(style), szFile); + wxStandardPathsBase& stdp = wxStandardPaths::Get(); -#if defined( __UNIX__ ) && !defined( __WXMAC__ ) - if ( !(style & wxCONFIG_USE_SUBDIR) ) - { - // dot-files under Unix start with, well, a dot (but OTOH they usually - // don't have any specific extension) - fn.SetName(wxT('.') + fn.GetName()); - } - else // we do append ".conf" extension to config files in subdirectories -#endif // defined( __UNIX__ ) && !defined( __WXMAC__ ) - { - AddConfFileExtIfNeeded(fn); - } + // If the config file is located in a subdirectory, we always use an + // extension for it, but we use just the leading dot if it is located + // directly in the home directory. Note that if wxStandardPaths is + // configured to follow XDG specification, all config files go to a + // subdirectory of XDG_CONFIG_HOME anyhow, so in this case we'll still end + // up using the extension even if wxCONFIG_USE_SUBDIR is not set, but this + // is the correct and expected (if a little confusing) behaviour. + const wxStandardPaths::ConfigFileConv + conv = style & wxCONFIG_USE_SUBDIR + ? wxStandardPaths::ConfigFileConv_Ext + : wxStandardPaths::ConfigFileConv_Dot; - return fn; + return wxFileName(GetLocalDir(style), stdp.MakeConfigFileName(szFile, conv)); } // ---------------------------------------------------------------------------- diff --git a/src/common/stdpbase.cpp b/src/common/stdpbase.cpp index 143c4f117c..6f776c30d4 100644 --- a/src/common/stdpbase.cpp +++ b/src/common/stdpbase.cpp @@ -97,6 +97,9 @@ wxStandardPathsBase::wxStandardPathsBase() // Derived classes can call this in their constructors // to set the platform-specific settings UseAppInfo(AppInfo_AppName); + + // Default for compatibility with the existing config files. + SetFileLayout(FileLayout_Classic); } wxStandardPathsBase::~wxStandardPathsBase() diff --git a/src/msw/stdpaths.cpp b/src/msw/stdpaths.cpp index edc1517e66..cab68719f9 100644 --- a/src/msw/stdpaths.cpp +++ b/src/msw/stdpaths.cpp @@ -345,6 +345,16 @@ wxString wxStandardPaths::GetPluginsDir() const return GetAppDir(); } + +wxString +wxStandardPaths::MakeConfigFileName(const wxString& basename, + ConfigFileConv WXUNUSED(conv)) const +{ + wxFileName fn(wxEmptyString, basename); + fn.SetExt(wxT("ini")); + return fn.GetFullName(); +} + // ============================================================================ // wxStandardPathsWin16 implementation // ============================================================================ diff --git a/src/osx/cocoa/stdpaths.mm b/src/osx/cocoa/stdpaths.mm index a8b8323d2a..85418fb5af 100644 --- a/src/osx/cocoa/stdpaths.mm +++ b/src/osx/cocoa/stdpaths.mm @@ -19,6 +19,7 @@ #if wxUSE_STDPATHS +#include "wx/filename.h" #include "wx/stdpaths.h" #include "wx/osx/private.h" #include "wx/osx/core/cfstring.h" @@ -129,4 +130,13 @@ wxString wxStandardPaths::GetUserDir(Dir userDir) const return GetFMDirectory(dirType, NSUserDomainMask); } +wxString +wxStandardPaths::MakeConfigFileName(const wxString& basename, + ConfigFileConv WXUNUSED(conv)) const +{ + wxFileName fn(wxEmptyString, basename); + fn.SetName(fn.GetName() + wxT(" Preferences")); + return fn.GetFullName(); +} + #endif // wxUSE_STDPATHS diff --git a/src/unix/stdpaths.cpp b/src/unix/stdpaths.cpp index 5f61f19ecb..4b6140e2f6 100644 --- a/src/unix/stdpaths.cpp +++ b/src/unix/stdpaths.cpp @@ -50,9 +50,34 @@ void wxStandardPaths::SetInstallPrefix(const wxString& prefix) m_prefix = prefix; } +// Helper function returning the value of XDG_CONFIG_HOME environment variable +// or its default value if it is not defined. +static wxString GetXDGConfigHome() +{ + wxString dir; + if ( !wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) + dir = wxFileName::GetHomeDir() + wxS("/.config"); + return dir; +} + wxString wxStandardPaths::GetUserConfigDir() const { - return wxFileName::GetHomeDir(); + wxString dir; + + switch ( GetFileLayout() ) + { + case FileLayout_Classic: + dir = wxFileName::GetHomeDir(); + break; + + case FileLayout_XDG: + dir = GetXDGConfigHome(); + break; + } + + wxASSERT_MSG( !dir.empty(), wxS("unsupported file layout") ); + + return dir; } @@ -235,69 +260,70 @@ wxStandardPaths::GetLocalizedResourcesDir(const wxString& lang, wxString wxStandardPaths::GetUserDir(Dir userDir) const { + // Note that we do not use the file layout here because there is no reason + // not to respect the XDG convention even if SetFileLayout(FileLayout_XDG) + // hadn't been called: we're not bound by any backwards compatibility + // considerations as there can't be any pre-existing config or data files + // in the home directory that wouldn't be found any longer after updating + // the version of wxWidgets used by the application. + + wxLogNull logNull; + const wxString homeDir = wxFileName::GetHomeDir(); + if (userDir == Dir_Cache) { - wxLogNull logNull; - wxString homeDir = wxFileName::GetHomeDir(); - if (userDir == Dir_Cache) + wxString cacheDir; + if ( !wxGetEnv(wxS("XDG_CACHE_HOME"), &cacheDir) ) + cacheDir = homeDir + wxS("/.cache"); + + return cacheDir; + } + + const wxFileName dirsFile(GetXDGConfigHome(), wxS("user-dirs.dirs")); + if ( dirsFile.FileExists() ) + { + wxString userDirId; + switch (userDir) { - if (wxGetenv(wxT("XDG_CACHE_HOME"))) - return wxGetenv(wxT("XDG_CACHE_HOME")); - else - return homeDir + wxT("/.cache"); + case Dir_Desktop: + userDirId = "XDG_DESKTOP_DIR"; + break; + case Dir_Downloads: + userDirId = "XDG_DOWNLOAD_DIR"; + break; + case Dir_Music: + userDirId = "XDG_MUSIC_DIR"; + break; + case Dir_Pictures: + userDirId = "XDG_PICTURES_DIR"; + break; + case Dir_Videos: + userDirId = "XDG_VIDEOS_DIR"; + break; + default: + userDirId = "XDG_DOCUMENTS_DIR"; + break; } - wxString configPath; - if (wxGetenv(wxT("XDG_CONFIG_HOME"))) - configPath = wxGetenv(wxT("XDG_CONFIG_HOME")); - else - configPath = homeDir + wxT("/.config"); - wxString dirsFile = configPath + wxT("/user-dirs.dirs"); - if (wxFileExists(dirsFile)) + wxTextFile textFile; + if ( textFile.Open(dirsFile.GetFullPath()) ) { - wxString userDirId; - switch (userDir) + for ( wxString line = textFile.GetFirstLine(); + !textFile.Eof(); + line = textFile.GetNextLine() ) { - case Dir_Desktop: - userDirId = "XDG_DESKTOP_DIR"; - break; - case Dir_Downloads: - userDirId = "XDG_DOWNLOAD_DIR"; - break; - case Dir_Music: - userDirId = "XDG_MUSIC_DIR"; - break; - case Dir_Pictures: - userDirId = "XDG_PICTURES_DIR"; - break; - case Dir_Videos: - userDirId = "XDG_VIDEOS_DIR"; - break; - default: - userDirId = "XDG_DOCUMENTS_DIR"; - break; - } - - wxTextFile textFile; - if (textFile.Open(dirsFile)) - { - size_t i; - for (i = 0; i < textFile.GetLineCount(); i++) + int pos = line.Find(userDirId); + if (pos != wxNOT_FOUND) { - wxString line(textFile[i]); - int pos = line.Find(userDirId); - if (pos != wxNOT_FOUND) - { - wxString value = line.AfterFirst(wxT('=')); - value.Replace(wxT("$HOME"), homeDir); - value.Trim(true); - value.Trim(false); - // Remove quotes - value.Replace("\"", "", true /* replace all */); - if (!value.IsEmpty() && wxDirExists(value)) - return value; - else - break; - } + wxString value = line.AfterFirst(wxT('=')); + value.Replace(wxT("$HOME"), homeDir); + value.Trim(true); + value.Trim(false); + // Remove quotes + value.Replace("\"", "", true /* replace all */); + if (!value.IsEmpty() && wxDirExists(value)) + return value; + else + break; } } } @@ -308,4 +334,39 @@ wxString wxStandardPaths::GetUserDir(Dir userDir) const #endif // __VMS/!__VMS +wxString +wxStandardPaths::MakeConfigFileName(const wxString& basename, + ConfigFileConv conv) const +{ + wxFileName fn(wxEmptyString, basename); + + bool addExt = false; + + switch ( GetFileLayout() ) + { + case FileLayout_Classic: + switch ( conv ) + { + case ConfigFileConv_Dot: + fn.SetName(wxT('.') + fn.GetName()); + break; + + case ConfigFileConv_Ext: + addExt = true; + break; + } + break; + + case FileLayout_XDG: + // Dot files are never used in XDG mode. + addExt = true; + break; + } + + if ( addExt ) + fn.SetExt(wxS("conf")); + + return fn.GetFullName(); +} + #endif // wxUSE_STDPATHS