Re: [AD] standard path updates

[ Thread Index | Date Index | More lists.liballeg.org/allegro-developers Archives ]


OK here is the patch (against rc4) It's mostly just the 2 combined
except a little tweak to the docs, using resourcePath instead of
bundlePath + /Contents/Resources, and using
NSSearchPathForDirectoriesInDomains with NSDocumentDirectory instead
of NSHomeDirectory + Documents.

The output of ex_get_path is below and a5steroids app works OK.

Pete

Output from ex_get_path (non-bundle):
RESOURCES_PATH: /Users/peterhull/Projects/allegro/patches/build/examples/./
TEMP_PATH: /var/folders/XF/XFm6zogdHkCnnwpxDDHMOE+++TI/-Tmp-/
USER_DATA_PATH: /Users/peterhull/Library/liballeg.org/ex_get_path/
USER_SETTINGS_PATH: /Users/peterhull/Library/Application
Support/liballeg.org/ex_get_path/
USER_HOME_PATH: /Users/peterhull/
USER_DOCUMENTS_PATH: /Users/peterhull/Documents/
EXENAME_PATH: /Users/peterhull/Projects/allegro/patches/build/examples/./ex_get_path

(bundle)
RESOURCES_PATH:
/Users/peterhull/Projects/allegro/patches/build/examples/GetPath.app/Contents/Resources/
TEMP_PATH: /var/folders/XF/XFm6zogdHkCnnwpxDDHMOE+++TI/-Tmp-/
USER_DATA_PATH: /Users/peterhull/Library/liballeg.org/ex_get_path/
USER_SETTINGS_PATH: /Users/peterhull/Library/Application
Support/liballeg.org/ex_get_path/
USER_HOME_PATH: /Users/peterhull/
USER_DOCUMENTS_PATH: /Users/peterhull/Documents/
EXENAME_PATH: /Users/peterhull/Projects/allegro/patches/build/examples/GetPath.app/Contents/MacOS/ex_get_path
diff --minimal -u -r -x '.*' allegro-5.0.0rc4/demos/a5teroids/src/Game.cpp 5.0all/demos/a5teroids/src/Game.cpp
--- allegro-5.0.0rc4/demos/a5teroids/src/Game.cpp	2010-10-31 01:09:23.000000000 +0000
+++ 5.0all/demos/a5teroids/src/Game.cpp	2011-01-05 21:02:13.000000000 +0000
@@ -27,7 +27,7 @@
    snprintf(res, 511, fmt, ap);
 
    if (!dir) {
-      dir = al_get_standard_path(ALLEGRO_PROGRAM_PATH);
+      dir = al_get_standard_path(ALLEGRO_RESOURCES_PATH);
 #ifdef ALLEGRO_MSVC
       {
          /* Hack to cope automatically with MSVC workspaces. */
diff --minimal -u -r -x '.*' allegro-5.0.0rc4/docs/src/refman/system.txt 5.0all/docs/src/refman/system.txt
--- allegro-5.0.0rc4/docs/src/refman/system.txt	2010-12-05 10:01:50.000000000 +0000
+++ 5.0all/docs/src/refman/system.txt	2011-01-05 23:08:16.000000000 +0000
@@ -60,22 +60,58 @@
 
 ## API: al_get_standard_path
 
-Gets a system path, depending on the `id` parameter:
+Gets a system path, depending on the `id` parameter. Some of these paths
+may be affected by the organization and application name, so be sure to
+set those before calling this function.
 
-ALLEGRO_PROGRAM_PATH
-:   Directory with the executed program.
+The paths are not guaranteed to be unique (e.g., SETTINGS and DATA may be
+the same on some platforms), so you should be sure your filenames are unique
+if you need to avoid naming collisions. Also, a returned path may not actually
+exist on the file system.
+
+ALLEGRO_RESOURCES_PATH
+:   If you bundle data in a location relative to your executable, then you
+    should use this path to locate that data. On most platforms, this is the
+    directory that contains the executable file. 
+    
+    If ran from an OS X app bundle, then this will point to the internal
+    resource directory (<bundle.app>/Contents/Resources.) To maintain 
+    consistency, if you put your resources
+    into a directory called "data" beneath the executable on some other platform
+    (like Windows), then you should also create a directory called "data" under
+    the OS X app bundle's resource folder.
+    
+    You should not try to write to this path, as it is very likely read-only.
+    
+    If you install your resources in some other system directory (e.g., in
+    /usr/share or C:\ProgramData), then you are responsible for keeping track
+    of that yourself.    
+    
 ALLEGRO_TEMP_PATH
 :   Path to the directory for temporary files.
-ALLEGRO_SYSTEM_DATA_PATH
-:   Data path for system-wide installation.
-ALLEGRO_USER_DATA_PATH
-:   Data path for per-user installation.
+
 ALLEGRO_USER_HOME_PATH
-:   Path to the user's home directory.
+:   This is the user's home directory. You should not write into this directory,
+    or create any sub folders in it. One practical application of this path
+    would be to use it as the starting place of a file selector in a GUI.
+    
+ALLEGRO_USER_DOCUMENTS_PATH
+:   This location is easily accessible by the user, and is the place
+    to store documents and files that the user might want to later open with
+    an external program or transfer to another place. 
+    
+    You should not save files here unless the user expects it, usually by
+    explicit permission.
+    
+ALLEGRO_USER_DATA_PATH
+:   If your program saves any data that the user doesn't need to access 
+    externally, then you should place it here. This is generally the least
+    intrusive place to store data.
+    
 ALLEGRO_USER_SETTINGS_PATH
-:   Path to per-user settings directory.
-ALLEGRO_SYSTEM_SETTINGS_PATH
-:   Path to system-wide settings directory.
+:   If you are saving configuration files (especially if the user may want to
+    edit them outside of your program), then you should place them here.
+    
 ALLEGRO_EXENAME_PATH
 :   The full path to the executable.
 
diff --minimal -u -r -x '.*' allegro-5.0.0rc4/examples/ex_get_path.c 5.0all/examples/ex_get_path.c
--- allegro-5.0.0rc4/examples/ex_get_path.c	2010-12-05 09:50:40.000000000 +0000
+++ 5.0all/examples/ex_get_path.c	2011-01-05 21:02:13.000000000 +0000
@@ -18,22 +18,14 @@
    }
    open_log();
 
-   path = al_get_standard_path(ALLEGRO_PROGRAM_PATH);
-   log_printf("PROGRAM_PATH: %s\n", al_path_cstr(path, '/'));
+   path = al_get_standard_path(ALLEGRO_RESOURCES_PATH);
+   log_printf("RESOURCES_PATH: %s\n", al_path_cstr(path, '/'));
    al_destroy_path(path);
 
    path = al_get_standard_path(ALLEGRO_TEMP_PATH);
    log_printf("TEMP_PATH: %s\n", al_path_cstr(path, '/'));
    al_destroy_path(path);
 
-   path = al_get_standard_path(ALLEGRO_SYSTEM_DATA_PATH);
-   log_printf("SYSTEM_DATA_PATH: %s\n", al_path_cstr(path, '/'));
-   al_destroy_path(path);
-
-   path = al_get_standard_path(ALLEGRO_SYSTEM_SETTINGS_PATH);
-   log_printf("SYSTEM_SETTINGS_PATH: %s\n", al_path_cstr(path, '/'));
-   al_destroy_path(path);
-
    path = al_get_standard_path(ALLEGRO_USER_DATA_PATH);
    log_printf("USER_DATA_PATH: %s\n", al_path_cstr(path, '/'));
    al_destroy_path(path);
@@ -45,6 +37,10 @@
    log_printf("USER_HOME_PATH: %s\n", al_path_cstr(path, '/'));
    al_destroy_path(path);
 
+   path = al_get_standard_path(ALLEGRO_USER_DOCUMENTS_PATH);
+   log_printf("USER_DOCUMENTS_PATH: %s\n", al_path_cstr(path, '/'));
+   al_destroy_path(path);
+
    path = al_get_standard_path(ALLEGRO_EXENAME_PATH);
    log_printf("EXENAME_PATH: %s\n", al_path_cstr(path, '/'));
    al_destroy_path(path);
diff --minimal -u -r -x '.*' allegro-5.0.0rc4/include/allegro5/platform/alosx.h 5.0all/include/allegro5/platform/alosx.h
--- allegro-5.0.0rc4/include/allegro5/platform/alosx.h	2010-10-10 13:23:46.000000000 +0100
+++ 5.0all/include/allegro5/platform/alosx.h	2011-01-05 22:05:40.000000000 +0000
@@ -49,11 +49,6 @@
 #endif
 
 
-/* The following code comes from alunix.h */
-/* Magic to capture name of executable file */
-extern int    __crt0_argc;
-extern char **__crt0_argv;
-
 #ifndef ALLEGRO_NO_MAGIC_MAIN
    #define ALLEGRO_MAGIC_MAIN
    #define main _al_mangled_main
diff --minimal -u -r -x '.*' allegro-5.0.0rc4/include/allegro5/system.h 5.0all/include/allegro5/system.h
--- allegro-5.0.0rc4/include/allegro5/system.h	2010-12-05 10:01:50.000000000 +0000
+++ 5.0all/include/allegro5/system.h	2011-01-05 22:06:06.000000000 +0000
@@ -21,13 +21,12 @@
 AL_FUNC(ALLEGRO_CONFIG *, al_get_system_config, (void));
 
 enum {
-   ALLEGRO_PROGRAM_PATH = 0,
+   ALLEGRO_RESOURCES_PATH = 0,
    ALLEGRO_TEMP_PATH,
-   ALLEGRO_SYSTEM_DATA_PATH,
    ALLEGRO_USER_DATA_PATH,
    ALLEGRO_USER_HOME_PATH,
    ALLEGRO_USER_SETTINGS_PATH,
-   ALLEGRO_SYSTEM_SETTINGS_PATH,
+   ALLEGRO_USER_DOCUMENTS_PATH,
    ALLEGRO_EXENAME_PATH,
    ALLEGRO_LAST_PATH // must be last
 };
diff --minimal -u -r -x '.*' allegro-5.0.0rc4/src/iphone/iphone_path.m 5.0all/src/iphone/iphone_path.m
--- allegro-5.0.0rc4/src/iphone/iphone_path.m	2009-08-27 12:36:55.000000000 +0100
+++ 5.0all/src/iphone/iphone_path.m	2011-01-05 22:10:40.000000000 +0000
@@ -8,22 +8,21 @@
     NSBundle *mainBundle;
     switch (id) {
         case ALLEGRO_EXENAME_PATH:
-        case ALLEGRO_PROGRAM_PATH:
         case ALLEGRO_USER_HOME_PATH:
+        case ALLEGRO_USER_DOCUMENTS_PATH:
             strcpy(str, getenv("HOME"));
             return al_create_path_for_directory(str);
         case ALLEGRO_TEMP_PATH:
             string = NSTemporaryDirectory();
             sprintf(str, "%s", [string UTF8String]);
             return al_create_path_for_directory(str);
-        case ALLEGRO_SYSTEM_DATA_PATH:
         case ALLEGRO_USER_DATA_PATH:
+        case ALLEGRO_RESOURCES_PATH:
             mainBundle = [NSBundle mainBundle];
             string = [mainBundle resourcePath];
             sprintf(str, "%s", [string UTF8String]);
             return al_create_path_for_directory(str);
         case ALLEGRO_USER_SETTINGS_PATH:
-        case ALLEGRO_SYSTEM_SETTINGS_PATH:
             sprintf(str, "%s/Library/Preferences", getenv("HOME"));
             return al_create_path_for_directory(str);
     }
diff --minimal -u -r -x '.*' allegro-5.0.0rc4/src/macosx/osx_app_delegate.m 5.0all/src/macosx/osx_app_delegate.m
--- allegro-5.0.0rc4/src/macosx/osx_app_delegate.m	2010-11-06 15:12:15.000000000 +0000
+++ 5.0all/src/macosx/osx_app_delegate.m	2011-01-05 22:07:36.000000000 +0000
@@ -30,9 +30,11 @@
 
 #endif
 
+extern NSBundle *_al_osx_bundle;
+
 /* For compatibility with the unix code */
-extern int    __crt0_argc;
-extern char **__crt0_argv;
+static int    __crt0_argc;
+static char **__crt0_argv;
 static int (*user_main)(int, char **);
 
 static char *arg0, *arg1 = NULL;
@@ -156,9 +158,9 @@
          * or to the 'magic' resource directory if it exists.
          * (see the readme.osx file for more info)
          */
-        NSBundle* osx_bundle = [NSBundle mainBundle];
-        exename = [[osx_bundle executablePath] lastPathComponent];
-        resdir = [[osx_bundle resourcePath] stringByAppendingPathComponent: exename];
+        _al_osx_bundle = [NSBundle mainBundle];
+        exename = [[_al_osx_bundle executablePath] lastPathComponent];
+        resdir = [[_al_osx_bundle resourcePath] stringByAppendingPathComponent: exename];
         fm = [NSFileManager defaultManager];
         if ([fm fileExistsAtPath: resdir isDirectory: &isDir] && isDir) {
             /* Yes, it exists inside the bundle */
@@ -166,13 +168,13 @@
         }
         else {
             /* No, change to the 'standard' OSX resource directory if it exists*/
-            if ([fm fileExistsAtPath: [osx_bundle resourcePath] isDirectory: &isDir] && isDir)
+            if ([fm fileExistsAtPath: [_al_osx_bundle resourcePath] isDirectory: &isDir] && isDir)
             {
-                [fm changeCurrentDirectoryPath: [osx_bundle resourcePath]];
+                [fm changeCurrentDirectoryPath: [_al_osx_bundle resourcePath]];
             }
             /* It doesn't exist - this is unusual for a bundle. Don't chdir */
         }
-        arg0 = strdup([[osx_bundle bundlePath] fileSystemRepresentation]);
+        arg0 = strdup([[_al_osx_bundle bundlePath] fileSystemRepresentation]);
         if (arg1) {
             static char *args[2];
             args[0] = arg0;
diff --minimal -u -r -x '.*' allegro-5.0.0rc4/src/macosx/system.m 5.0all/src/macosx/system.m
--- allegro-5.0.0rc4/src/macosx/system.m	2010-12-26 13:13:06.000000000 +0000
+++ 5.0all/src/macosx/system.m	2011-01-05 22:27:30.000000000 +0000
@@ -54,15 +54,7 @@
 
 
 /* Global variables */
-int    __crt0_argc;
-char **__crt0_argv;
-NSBundle *osx_bundle = NULL;
-struct _AL_MUTEX osx_event_mutex;
-//AllegroWindow *osx_window = NULL;
-//char osx_window_title[ALLEGRO_MESSAGE_SIZE];
-void (*osx_window_close_hook)(void) = NULL;
-//int osx_emulate_mouse_buttons = false;
-//int osx_window_first_expose = false;
+NSBundle *_al_osx_bundle = NULL;
 static _AL_VECTOR osx_display_modes;
 static ALLEGRO_SYSTEM osx_system;
 _AL_VECTOR _osx_threads = _AL_VECTOR_INITIALIZER(THREAD_AND_POOL *);
@@ -130,7 +122,7 @@
    osx_system.vt = _al_system_osx_driver();
    _al_vector_init(&osx_system.displays, sizeof(ALLEGRO_DISPLAY*));
   
-   if (osx_bundle == NULL) {
+   if (_al_osx_bundle == NULL) {
        /* If in a bundle, the dock will recognise us automatically */
        osx_tell_dock();
    }
@@ -549,23 +541,20 @@
    ALLEGRO_PATH *path = NULL;
 
    switch (id) {
-      case ALLEGRO_PROGRAM_PATH:
-         ans = [[NSBundle mainBundle] bundlePath];
-         path = al_create_path_for_directory([ans UTF8String]);
+      case ALLEGRO_RESOURCES_PATH:
+         if (_al_osx_bundle) {
+            ans = [_al_osx_bundle resourcePath];
+            path = al_create_path_for_directory([ans UTF8String]);
+         } else {
+            /* Otherwise, return the executable pathname */
+            path = osx_get_path(ALLEGRO_EXENAME_PATH);
+            al_set_path_filename(path, NULL);
+         }
          break;
       case ALLEGRO_TEMP_PATH:
          ans = NSTemporaryDirectory();
          path = al_create_path_for_directory([ans UTF8String]);
          break;
-      case ALLEGRO_SYSTEM_DATA_PATH:
-         ans = [[NSBundle mainBundle] resourcePath];
-         if (ans != nil) {
-            /* Append program name */
-            ans = [[ans stringByAppendingPathComponent: org_name]
-                                     stringByAppendingPathComponent: app_name];
-         }
-         path = al_create_path_for_directory([ans UTF8String]);
-         break;
       case ALLEGRO_USER_DATA_PATH:
          paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
             NSUserDomainMask,
@@ -583,39 +572,27 @@
          ans = NSHomeDirectory();
          path = al_create_path_for_directory([ans UTF8String]);
          break;
-      case ALLEGRO_EXENAME_PATH:
-         /* If the application lives in a bundle, return the bundle path as
-          * the executable path, since this is probably what is expected.
-          */
-         if (osx_bundle) {
-            ans = [[NSBundle mainBundle] bundlePath];
-         } else {
-            /* Otherwise, return the executable pathname */
-            char path[PATH_MAX];
-            uint32_t size = sizeof(path);
-            if (_NSGetExecutablePath(path, &size) == 0)
-               ans = [NSString stringWithUTF8String: path];
-         }
-         path = al_create_path([ans UTF8String]);
-         break;
-      case ALLEGRO_USER_SETTINGS_PATH:
-         paths =
-         NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory,
+      case ALLEGRO_USER_DOCUMENTS_PATH:
+         paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
             NSUserDomainMask,
             YES);
          if ([paths count] > 0)
             ans = [paths objectAtIndex: 0];
-         if (ans != nil) {
-            /* Append program name */
-            ans = [[ans stringByAppendingPathComponent: org_name]
-                                     stringByAppendingPathComponent: app_name];
-         }
          path = al_create_path_for_directory([ans UTF8String]);
          break;
-      case ALLEGRO_SYSTEM_SETTINGS_PATH:
+      case ALLEGRO_EXENAME_PATH: {
+         char exepath[PATH_MAX];
+         uint32_t size = sizeof(exepath);
+         if (_NSGetExecutablePath(exepath, &size) == 0)
+            ans = [NSString stringWithUTF8String: exepath];
+
+         path = al_create_path([ans UTF8String]);
+         break;
+      }
+      case ALLEGRO_USER_SETTINGS_PATH:
          paths =
          NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory,
-            NSLocalDomainMask,
+            NSUserDomainMask,
             YES);
          if ([paths count] > 0)
             ans = [paths objectAtIndex: 0];
diff --minimal -u -r -x '.*' allegro-5.0.0rc4/src/unix/upath.c 5.0all/src/unix/upath.c
--- allegro-5.0.0rc4/src/unix/upath.c	2010-10-30 08:46:26.000000000 +0100
+++ 5.0all/src/unix/upath.c	2011-01-05 21:02:13.000000000 +0000
@@ -299,25 +299,14 @@
          return NULL;
       } break;
 
-      case ALLEGRO_PROGRAM_PATH: {
-
+      case ALLEGRO_RESOURCES_PATH: {
          ALLEGRO_PATH *exe = get_executable_name();
+         /* TODO: follow symlinks */
          al_set_path_filename(exe, NULL);
          return exe;
 
       } break;
 
-      case ALLEGRO_SYSTEM_DATA_PATH: {
-         ALLEGRO_PATH *sys_data_path = NULL;
-
-         /* FIXME: make this a compile time define, or a allegro cfg option? or both */
-         sys_data_path = al_create_path("/usr/share/");
-         al_append_path_component(sys_data_path, al_get_org_name());
-         al_append_path_component(sys_data_path, al_get_app_name());
-
-         return sys_data_path;
-      } break;
-
 #if 0
       case ALLEGRO_USER_DATA_PATH: {
          int32_t ret = 0;
@@ -361,13 +350,21 @@
       case ALLEGRO_USER_SETTINGS_PATH:
       case ALLEGRO_USER_DATA_PATH: {
          ALLEGRO_PATH *local_path = NULL;
+         const char *org_name = al_get_org_name();
+         const char *app_name = al_get_app_name();
+         
+         if (!app_name)
+            return NULL;
 
          local_path = _unix_find_home();
          if (!local_path)
             return NULL;
 
-         al_append_path_component(local_path, ".config");
-         al_append_path_component(local_path, al_get_org_name());
+         al_append_path_component(local_path, ".config");         
+         if (org_name && org_name[0]) {            
+         	  /* only add org name if not blank */
+            al_append_path_component(local_path, al_get_org_name());         
+         }
          al_append_path_component(local_path, al_get_app_name());
 
         return local_path;
@@ -375,16 +372,36 @@
 
       case ALLEGRO_USER_HOME_PATH:
          return _unix_find_home();
-
-      case ALLEGRO_SYSTEM_SETTINGS_PATH: {
-         ALLEGRO_PATH *sys_path;
-
-         /* FIXME: make this a compile time define, or something */
-         sys_path = al_create_path("/etc/");
-         al_append_path_component(sys_path, al_get_org_name());
-         al_append_path_component(sys_path, al_get_app_name());
-
-         return sys_path;
+         
+      case ALLEGRO_USER_DOCUMENTS_PATH: {
+         const char *doc_paths[] = {"Documents", "documents", NULL};
+         const char **doc_path = doc_paths;
+         ALLEGRO_PATH *path = _unix_find_home();
+         const ALLEGRO_FS_INTERFACE *old_fs;
+         ALLEGRO_FS_ENTRY *file;
+         
+         if (!path) return NULL;
+         
+         while (doc_path) {         
+            al_append_path_component(path, *doc_path++);
+         
+            old_fs = al_get_fs_interface();
+            al_set_standard_fs_interface();
+            file = al_create_fs_entry(al_path_cstr(path, '/'));
+            al_set_fs_interface(old_fs);
+         
+            if (file) {
+               uint32_t mode = al_get_fs_entry_mode(file);
+               al_destroy_fs_entry(file);
+            
+               if (mode & ALLEGRO_FILEMODE_ISDIR) return path;
+            }
+         
+            al_drop_path_tail(path);
+         }
+         
+         /* couldn't find a Documents folder, so return the home folder */         
+         return path;
       } break;
 
       case ALLEGRO_EXENAME_PATH:
diff --minimal -u -r -x '.*' allegro-5.0.0rc4/src/win/wsystem.c 5.0all/src/win/wsystem.c
--- allegro-5.0.0rc4/src/win/wsystem.c	2010-12-17 10:59:54.000000000 +0000
+++ 5.0all/src/win/wsystem.c	2011-01-05 21:02:13.000000000 +0000
@@ -408,7 +408,7 @@
 
       } break;
 
-      case ALLEGRO_PROGRAM_PATH: { /* where the program is in */
+      case ALLEGRO_RESOURCES_PATH: { /* where the program is in */
          HANDLE process = GetCurrentProcess();
          char *ptr;
          GetModuleFileNameEx(process, NULL, path, MAX_PATH);
@@ -424,30 +424,17 @@
          return al_create_path_for_directory(path);
       } break;
 
-      case ALLEGRO_SYSTEM_DATA_PATH: /* CSIDL_COMMON_APPDATA */
-         csidl = CSIDL_COMMON_APPDATA;
-         break;
-
       case ALLEGRO_USER_DATA_PATH: /* CSIDL_APPDATA */
-         csidl = CSIDL_APPDATA;
-         break;
-
-      case ALLEGRO_USER_HOME_PATH: /* CSIDL_PERSONAL */
-         csidl = CSIDL_PERSONAL;
-         break;
-
       case ALLEGRO_USER_SETTINGS_PATH:
-         /* CHECKME: is this correct? Windows doesn't seem to have a "path"
-          * for settings; I guess these should go in the registry instead?
-          */
          csidl = CSIDL_APPDATA;
          break;
 
-      case ALLEGRO_SYSTEM_SETTINGS_PATH:
-         /* CHECKME: is this correct? Windows doesn't seem to have a "path"
-          * for settings; I guess these should go in the registry instead?
-          */
-         csidl = CSIDL_COMMON_APPDATA;
+      case ALLEGRO_USER_HOME_PATH: /* CSIDL_PROFILE */
+         csidl = CSIDL_PROFILE;
+         break;
+         
+      case ALLEGRO_USER_DOCUMENTS_PATH: /* CSIDL_PERSONAL */
+         csidl = CSIDL_PERSONAL;
          break;
 
       case ALLEGRO_EXENAME_PATH: { /* full path to the exe including its name */


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/