FLTK logo

[master] 7308bcd - Preferences XDG path (#377)

FLTK matrix user chat room
(using Element browser app)   FLTK gitter user chat room   GitHub FLTK Project   FLTK News RSS Feed  
  FLTK Apps      FLTK Library      Forums      Links     Login 
 All Forums  |  Back to fltk.commit  ]
 
Previous Message ]Next Message ]

[master] 7308bcd - Preferences XDG path (#377) "Matthias Melcher" Jan 23, 2022  
 
commit 7308bcdb74e34626c6459699cb57371afd7b343b
Author:     Matthias Melcher <github@matthiasm.com>
AuthorDate: Sun Jan 23 22:35:21 2022 +0100
Commit:     GitHub <noreply@github.com>
CommitDate: Sun Jan 23 22:35:21 2022 +0100

    Preferences XDG path (#377)
    
    * Preferences: remove CamelCase from public interface.
    
    * Prefs: documentation.
    
    * Prefs: updating Doxygen comments
    
    * XDG conforming preferences path

 FL/Fl_Preferences.H                      | 65 +++++++++++++++++++-------------
 src/Fl_Preferences.cxx                   | 58 +++++++++++++++++-----------
 src/drivers/X11/Fl_X11_System_Driver.cxx | 55 ++++++++++++++++++++++-----
 3 files changed, 121 insertions(+), 57 deletions(-)

diff --git FL/Fl_Preferences.H FL/Fl_Preferences.H
index 21e331c..82f3ef4 100644
--- FL/Fl_Preferences.H
+++ FL/Fl_Preferences.H
@@ -32,19 +32,19 @@
  used documents.
 
  Preferences are organized in a hierarchy of groups. Every group can contain
- more groups and any number of kay/value pairs. Keys can be text strings
+ more groups and any number of key/value pairs. Keys can be text strings
  containing ASCII letters, digits, periods, and underscores. Forward slashes
  in a key name are treated as subgroups, i.e the key 'window/width' would
- actually refere to the key 'width' inside the group 'window'.
+ actually refer to the key 'width' inside the group 'window'.
 
- Keys have usually a unique name within their group. Duplicate kays are
- possible though and can beaccessed using the index based functions.
+ Keys usually have a unique name within their group. Duplicate keys are
+ possible though and can be accessed using the index based functions.
 
- A value should be an ASCII string. Control characters and utf8 sequences are
- stores as octal values. Long strings will wrap at the line ending and will be
+ A value can be an UTF-8 string. Control characters and UTF-8 sequences are
+ stores as octal values. Long strings are wrap at the line ending and will be
  reassembled when reading the file back.
 
- Many shortcuts exist to set and get numerical values and binary data.
+ Several methods allow setting and getting numerical values and binary data.
 
  Preferences are stored in text files that can be edited manually if needed.
  The file format is easy to read and relatively forgiving. Preferences files
@@ -60,16 +60,17 @@
  Preferences should no be used to store document data. The .prefs file should
  be kept small for performance reasons. One application can have multiple
  preferences files. Extensive binary data however should be stored in separate
- files: see \a Fl_Preferences::getUserdataPath() .
+ files: see \a Fl_Preferences::get_userdata_path() .
 
  Fl_Preferences are not thread-safe. They can temprorarily change the locale
- on some platforms during read an write access, which is alse observable in
- other threads of the same app.
+ on some platforms during read an write access, which is also changes it
+ temporarily in other threads of the same app.
 
- Typically a preferences database is read at startup and close, and then writte
- again at app shutdown:
- ```.cpp
+ Typically a preferences database is read at startup, and then reopended and
+ written at app shutdown:
+ ```
  int appWindowWidth, appWindowHeight;
+
  void launch() {
    Fl_Preferences app(Fl_Preferences::USER_L, "matthiasm.com", "hello");
    // 'app' constructor will be called, reading data from .prefs file
@@ -78,6 +79,7 @@
    window.get("height", appWindowHeight, 600);
    // 'app' destructor will be called, writing data to .prefs file
  }
+
  void quit() {
    Fl_Preferences app(Fl_Preferences::USER_L, "matthiasm.com", "hello");
    Fl_Preferences window(app, "window");
@@ -88,10 +90,11 @@
 
  \see Fl_Preferences::Fl_Preferences( Root root, const char *vendor, const char *application )
 
- As a special case, Fl_Preferences can be memeory mapped and not be associated
+ As a special case, Fl_Preferences can be memory mapped and not be associated
  with a file on disk.
 
  \see Fl_Preferences::Fl_Preferences( Fl_Preferences *parent, const char *group )
+ for more details on memory mapped preferences.
 
    \note Starting with FLTK 1.3, preference databases are expected to
      be in UTF-8 encoding. Previous databases were stored in the
@@ -121,14 +124,14 @@ public:
     UNKNOWN_ROOT_TYPE = -1, ///< Returned if storage could not be determined.
     SYSTEM = 0,        ///< Preferences are used system-wide, deprecated, see SYSTEM_L
     USER,              ///< Preferences apply only to the current user, deprecated, see USER_L
-    MEMORY,            ///< Returned if querying runtime prefs
+    MEMORY,            ///< Returned if querying memory mapped preferences
     ROOT_MASK = 0xFF,  ///< masks for the values above
     CORE = 0x100,      ///< OR'd by FLTK to read and write core library preferences and options
     CORE_SYSTEM = CORE|SYSTEM,
     CORE_USER = CORE|USER,
     C_LOCALE = 0x1000, ///< this flag should always be set, it makes sure that floating point values wre writte correctly independently of the current locale
-    SYSTEM_L = SYSTEM|C_LOCALE, ///< Preferences are used system-wide
-    USER_L = USER|C_LOCALE,     ///< Preferences apply only to the current user
+    SYSTEM_L = SYSTEM|C_LOCALE, ///< Preferences are used system-wide, locale independent
+    USER_L = USER|C_LOCALE,     ///< Preferences apply only to the current user, locale independent
   };
 
   /**
@@ -140,7 +143,7 @@ public:
    */
   typedef void *ID;
 
-  static const char *newUUID();
+  static const char *new_UUID();
 
   /** Set this, if no call to Fl_Preferences shall access the file sytem
    @see Fl_Preferences::file_access(unsigned int)
@@ -208,15 +211,15 @@ public:
 
   int groups();
   const char *group( int num_group );
-  char groupExists( const char *key );
-  char deleteGroup( const char *group );
-  char deleteAllGroups();
+  char group_exists( const char *key );
+  char delete_group( const char *group );
+  char delete_all_groups();
 
   int entries();
   const char *entry( int index );
-  char entryExists( const char *key );
-  char deleteEntry( const char *entry );
-  char deleteAllEntries();
+  char entry_exists( const char *key );
+  char delete_entry( const char *entry );
+  char delete_all_entries();
 
   char clear();
 
@@ -239,14 +242,22 @@ public:
 
   int size( const char *entry );
 
-  char getUserdataPath( char *path, int pathlen );
+  char get_userdata_path( char *path, int pathlen );
 
   int flush();
 
   int dirty();
 
-  // char export( const char *filename, Type fileFormat );
-  // char import( const char *filename );
+  /** \cond PRIVATE */
+  static const char *newUUID() { return new_UUID(); }
+  char groupExists( const char *key ) { return group_exists(key); }
+  char deleteGroup( const char *group ) { return delete_group(group); }
+  char deleteAllGroups() { return delete_all_groups(); }
+  char entryExists( const char *key ) { return entry_exists(key); }
+  char deleteEntry( const char *entry ) { return delete_entry(entry); }
+  char deleteAllEntries() { return delete_all_entries(); }
+  char getUserdataPath( char *path, int pathlen ) { return get_userdata_path(path, pathlen); }
+  /** \endcond */
 
   /**
      'Name' provides a simple method to create numerical or more complex
diff --git src/Fl_Preferences.cxx src/Fl_Preferences.cxx
index 83fa3da..b3c3c9c 100644
--- src/Fl_Preferences.cxx
+++ src/Fl_Preferences.cxx
@@ -63,7 +63,7 @@ static int clocale_sscanf(const char *input, const char *format, ...)
  \return a pointer to a static buffer containing the new UUID in ASCII format.
          The buffer is overwritten during every call to this function!
  */
-const char *Fl_Preferences::newUUID() {
+const char *Fl_Preferences::new_UUID() {
   Fl::system_driver()->newUUID(uuidBuffer);
   return uuidBuffer;
 }
@@ -126,7 +126,7 @@ unsigned int Fl_Preferences::file_access()
  of the pathname componennts. This can be used to check if a preferneces file
  already exists.
 
- \param[out] buffer write the reulting path into this buffer
+ \param[out] buffer write the resulting path into this buffer
  \param[in] buffer_size size of the `buffer` in bytes
  \param[in] root can be \c USER_L or \c SYSTEM_L for user specific or system
     wide preferences
@@ -157,10 +157,16 @@ Fl_Preferences::Root Fl_Preferences::filename( char *buffer, size_t buffer_size,
 
 
 /**
- The constructor creates a group that manages name/value pairs and
- child groups. Groups are ready for reading and writing at any time.
- The root argument is either `Fl_Preferences::USER_L`
- or `Fl_Preferences::SYSTEM_L`.
+ The constructor creates a group that manages key/value pairs and
+ child groups.
+
+ Preferences can be stored per user using the root type
+ `Fl_Preferences::USER_L`, or stored system-wide using
+ `Fl_Preferences::SYSTEM_L`.
+
+ Groups and key/value pairs can be read and written randomly. Reading undefined
+ values will return the default value. Writing undefined values will create
+ all required groups and key/vlaue pairs.
 
  This constructor creates the <i>base</i> instance for all following entries
  and reads the database from disk into memory if it exists.
@@ -192,12 +198,22 @@ Fl_Preferences::Root Fl_Preferences::filename( char *buffer, size_t buffer_size,
  check the path member of the passwd struct returned by \c getpwuid(getuid()) .
  If all attempts fail, data will be stored in RAM only and be lost when the
  app exits.
- The filename and path is then constructed as
- <tt>\$(directory)/.fltk/\$(vendor)/\$(application).prefs</tt> .
- The \c SYSTEM directory is hardcoded as
+
+ The \c SYSTEM preferences filename is hardcoded as
  <tt>/etc/fltk/\$(vendor)/\$(application).prefs</tt> .
 
- \par In FLTK versions before 1.4.0, if \c $HOME was not set, the \c USER path
+ For backward compatibility, the old \c USER `.prefs` file naming scheme
+ <tt>\$(directory)/.fltk/\$(vendor)/\$(application).prefs</tt> is checked first.
+ If that file does not exist, the environment variable `$XDG_CONFIG_HOME` is
+ read as a base directory. If `$XDG_CONFIG_HOME` not set, the base directory
+ defaults to `$HOME/.config/`.
+
+ The user preferences will be stored in
+ <tt>\$(directory)/\$(vendor)/\$(application).prefs</tt>, The user data path
+ will be
+ <tt>\$(directory)/\$(vendor)/\$(application)/</tt>
+
+ In FLTK versions before 1.4.0, if \c $HOME was not set, the \c USER path
  would be empty, generating <tt>\$(vendor)/\$(application).prefs</tt>, which
  was used relative to the current working directory.
 
@@ -270,11 +286,11 @@ Fl_Preferences::Fl_Preferences( Fl_Preferences &parent, const char *group ) {
  only in local memory and is not associated with a file on disk. The root type
  of this databse is set to `Fl_Preferences::MEMORY`.
 
- * the memory database is \em not shared among multiple instances of the same app
- * memory databses are \em not thread safe
- * all data will be lost when the app quits
+ - the memory database is \em not shared among multiple instances of the same app
+ - memory databses are \em not thread safe
+ - all data will be lost when the app quits
 
- ```{.cpp}
+ ```
  void some_function() {
    Fl_Preferences guide( NULL, "Guide" );
    guide.set("answer", 42);
@@ -457,7 +473,7 @@ const char *Fl_Preferences::group( int num_group ) {
    \param[in] key name of group that is searched for
    \return 0 if no group by that name was found
  */
-char Fl_Preferences::groupExists( const char *key ) {
+char Fl_Preferences::group_exists( const char *key ) {
   return node->search( key ) ? 1 : 0 ;
 }
 
@@ -470,7 +486,7 @@ char Fl_Preferences::groupExists( const char *key ) {
    \param[in] group name of the group to delete
    \return 0 if call failed
  */
-char Fl_Preferences::deleteGroup( const char *group ) {
+char Fl_Preferences::delete_group( const char *group ) {
   Node *nd = node->search( group );
   if ( nd ) return nd->remove();
   return 0;
@@ -479,7 +495,7 @@ char Fl_Preferences::deleteGroup( const char *group ) {
 /**
  Delete all groups.
  */
-char Fl_Preferences::deleteAllGroups() {
+char Fl_Preferences::delete_all_groups() {
   node->deleteAllChildren();
   return 1;
 }
@@ -511,7 +527,7 @@ const char *Fl_Preferences::entry( int index ) {
    \param[in] key name of entry that is searched for
    \return 0 if entry was not found
  */
-char Fl_Preferences::entryExists( const char *key ) {
+char Fl_Preferences::entry_exists( const char *key ) {
   return node->getEntry( key )>=0 ? 1 : 0 ;
 }
 
@@ -523,14 +539,14 @@ char Fl_Preferences::entryExists( const char *key ) {
    \param[in] key name of entry to delete
    \return 0 if deleting the entry failed
  */
-char Fl_Preferences::deleteEntry( const char *key ) {
+char Fl_Preferences::delete_entry( const char *key ) {
   return node->deleteEntry( key );
 }
 
 /**
  Delete all entries.
  */
-char Fl_Preferences::deleteAllEntries() {
+char Fl_Preferences::delete_all_entries() {
   node->deleteAllEntries();
   return 1;
 }
@@ -1012,7 +1028,7 @@ int Fl_Preferences::size( const char *key ) {
 
  \see Fl_Preferences::Fl_Preferences(Root, const char*, const char*)
  */
-char Fl_Preferences::getUserdataPath( char *path, int pathlen ) {
+char Fl_Preferences::get_userdata_path( char *path, int pathlen ) {
   if ( rootNode )
     return rootNode->getPath( path, pathlen );
   return 0;
diff --git src/drivers/X11/Fl_X11_System_Driver.cxx src/drivers/X11/Fl_X11_System_Driver.cxx
index 9ffae3d..71becad 100644
--- src/drivers/X11/Fl_X11_System_Driver.cxx
+++ src/drivers/X11/Fl_X11_System_Driver.cxx
@@ -459,19 +459,19 @@ char *Fl_X11_System_Driver::preference_rootnode(Fl_Preferences * /*prefs*/, Fl_P
 {
   static char *filename = 0L;
   if (!filename) filename = (char*)::calloc(1, FL_PATH_MAX);
-  const char *e;
+  const char *home;
   switch (root&Fl_Preferences::ROOT_MASK) {
     case Fl_Preferences::USER:
-      e = getenv("HOME");
+      home = getenv("HOME");
       // make sure that $HOME is set to an existing directory
-      if ( (e==0L) || (e[0]==0) || (::access(e, F_OK)==-1) ) {
+      if ( (home==NULL) || (home[0]==0) || (::access(home, F_OK)==-1) ) {
         struct passwd *pw = getpwuid(getuid());
-        e = pw->pw_dir;
+        home = pw->pw_dir;
       }
-      if ( (e==0L) || (e[0]==0) || (::access(e, F_OK)==-1) ) {
-        return 0L;
+      if ( (home==0L) || (home[0]==0) || (::access(home, F_OK)==-1) ) {
+        return NULL;
       } else {
-        strlcpy(filename, e, FL_PATH_MAX);
+        strlcpy(filename, home, FL_PATH_MAX);
         if (filename[strlen(filename)-1] != '/')
           strlcat(filename, "/", FL_PATH_MAX);
         strlcat(filename, ".fltk/", FL_PATH_MAX);
@@ -483,13 +483,50 @@ char *Fl_X11_System_Driver::preference_rootnode(Fl_Preferences * /*prefs*/, Fl_P
   }
 
   // Make sure that the parameters are not NULL
-  if ( (vendor==0L) || (vendor[0]==0) )
+  if ( (vendor==NULL) || (vendor[0]==0) )
     vendor = "unknown";
-  if ( (application==0L) || (application[0]==0) )
+  if ( (application==NULL) || (application[0]==0) )
     application = "unknown";
 
   snprintf(filename + strlen(filename), FL_PATH_MAX - strlen(filename),
            "%s/%s.prefs", vendor, application);
+
+  // If this is the SYSTEM path, we are done
+  if ((root&Fl_Preferences::ROOT_MASK)!=Fl_Preferences::USER)
+    return filename;
+
+  // If the legacy file exists, we are also done
+  if (::access(filename, F_OK)==0)
+    return filename;
+
+  // This is USER mode, and there is no legacy file. Create an XDG conforming path.
+  // Check $XDG_CONFIG_HOME, and if it isn't set, default to $HOME/.config
+  const char *xdg = getenv("XDG_CONFIG_HOME");
+  if (xdg==NULL) {
+    xdg = "~/.config";
+  }
+  filename[0] = 0;
+  if (strncmp(xdg, "~/", 2)==0) {
+    strlcpy(filename, home, FL_PATH_MAX);
+    strlcat(filename, "/", FL_PATH_MAX);
+    strlcat(filename, xdg+2, FL_PATH_MAX);
+  } else if (strncmp(xdg, "$HOME/", 6)==0) {
+    strlcpy(filename, home, FL_PATH_MAX);
+    strlcat(filename, "/", FL_PATH_MAX);
+    strlcat(filename, xdg+6, FL_PATH_MAX);
+  } else if (strncmp(xdg, "${HOME}/", 8)==0) {
+    strlcpy(filename, home, FL_PATH_MAX);
+    strlcat(filename, "/", FL_PATH_MAX);
+    strlcat(filename, xdg+8, FL_PATH_MAX);
+  } else {
+    strlcpy(filename, xdg, FL_PATH_MAX);
+  }
+  strlcat(filename, "/", FL_PATH_MAX);
+  strlcat(filename, vendor, FL_PATH_MAX);
+  strlcat(filename, "/", FL_PATH_MAX);
+  strlcat(filename, application, FL_PATH_MAX);
+  strlcat(filename, ".prefs", FL_PATH_MAX);
+
   return filename;
 }
 
Direct Link to Message ]
 
     
Previous Message ]Next Message ]
 
 

Comments are owned by the poster. All other content is copyright 1998-2024 by Bill Spitzak and others. This project is hosted by The FLTK Team. Please report site problems to 'erco@seriss.com'.