Add SDL_GetPath() for default OS folders (#7665)

This commit is contained in:
Semphriss 2023-05-04 14:38:11 -04:00 committed by GitHub
parent 72e5a52c51
commit c1dab7745a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 672 additions and 0 deletions

View file

@ -138,6 +138,83 @@ extern DECLSPEC char *SDLCALL SDL_GetBasePath(void);
*/
extern DECLSPEC char *SDLCALL SDL_GetPrefPath(const char *org, const char *app);
/**
* The type of the OS-provided default folder for a specific purpose.
*
* Note that the Trash folder isn't included here, because trashing files usually
* involves extra OS-specific functionality to remember the file's original
* location.
*
* \sa SDL_GetPath
*/
typedef enum
{
/** The folder which contains all of the current user's data, preferences,
and documents. It usually contains most of the other folders. If a
requested folder does not exist, the home folder can be considered a safe
fallback to store a user's documents. Supported on Windows, macOS and
Unix with XDG. */
SDL_FOLDER_HOME,
/** The folder of files that are displayed on the desktop. Note that the
existence of a desktop folder does not guarantee that the system does
show icons on its desktop; certain GNU/Linux distros with a graphical
environment may not have desktop icons. Supported on Windows, macOS and
Unix with XDG. */
SDL_FOLDER_DESKTOP,
/** General document files, possibly application-specific. This is a good
place to save a user's projects. Supported on Windows, macOS and Unix
with XDG. */
SDL_FOLDER_DOCUMENTS,
/** Generic landing folder for files downloaded from the internet. Supported
on Windows Vista and later, macOS and Unix with XDG. */
SDL_FOLDER_DOWNLOADS,
/** Music files that can be played using a standard music player (mp3,
ogg...). Supported on Windows, macOS and Unix with XDG. */
SDL_FOLDER_MUSIC,
/** Image files that can be displayed using a standard viewer (png,
jpg...). Supported on Windows, macOS and Unix with XDG. */
SDL_FOLDER_PICTURES,
/** Files that are meant to be shared with other users on the same
computer. Supported on macOS and Unix with XDG. */
SDL_FOLDER_PUBLICSHARE,
/** Save files for games. Supported on Windows Vista and later. */
SDL_FOLDER_SAVEDGAMES,
/** Application screenshots. Supported on Windows Vista and later. */
SDL_FOLDER_SCREENSHOTS,
/** Template files to be used when the user requests the desktop environment
to create a new file in a certain folder, such as "New Text File.txt".
Any file in the Templates folder can be used as a starting point for a
new file. Supported on Windows, macOS and Unix with XDG. */
SDL_FOLDER_TEMPLATES,
/** Video files that can be played using a standard video player (mp4,
webm...). On macOS, this is the "Movies" folder. Supported on Windows,
macOS and Unix with XDG. */
SDL_FOLDER_VIDEOS,
} SDL_Folder;
/**
* Finds the most suitable OS-provided folder for @p folder, and returns its
* path in OS-specific notation.
*
* Many OSes provide certain standard folders for certain purposes, such as
* storing pictures, music or videos for a certain user. This function gives
* the path for many of those special locations.
*
* Note that the function is expensive, and should be called once at the
* beginning of the execution and kept for as long as needed.
*
* The returned value is owned by the caller and should be freed with
* SDL_free().
*
* If NULL is returned, the error may be obtained with SDL_GetError().
*
* \returns Either a null-terminated C string containing the full path to the
* folder, or NULL if an error happened.
*
* \sa SDL_Folder
*/
extern DECLSPEC char *SDLCALL SDL_GetPath(SDL_Folder folder);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}

View file

@ -851,6 +851,7 @@ SDL3_0.0.0 {
SDL_TryLockRWLockForWriting;
SDL_UnlockRWLock;
SDL_DestroyRWLock;
SDL_GetPath;
# extra symbols go here (don't modify this line)
local: *;
};

View file

@ -877,3 +877,4 @@
#define SDL_TryLockRWLockForWriting SDL_TryLockRWLockForWriting_REAL
#define SDL_UnlockRWLock SDL_UnlockRWLock_REAL
#define SDL_DestroyRWLock SDL_DestroyRWLock_REAL
#define SDL_GetPath SDL_GetPath_REAL

View file

@ -922,3 +922,4 @@ SDL_DYNAPI_PROC(int,SDL_TryLockRWLockForReading,(SDL_RWLock *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_TryLockRWLockForWriting,(SDL_RWLock *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_UnlockRWLock,(SDL_RWLock *a),(a),return)
SDL_DYNAPI_PROC(void,SDL_DestroyRWLock,(SDL_RWLock *a),(a),)
SDL_DYNAPI_PROC(char*,SDL_GetPath,(SDL_Folder a),(a),return)

View file

@ -132,4 +132,118 @@ SDL_GetPrefPath(const char *org, const char *app)
}
}
char *
SDL_GetPath(SDL_Folder folder)
{
@autoreleasepool {
#if TARGET_OS_TV
SDL_SetError("tvOS does not have persistent storage");
return NULL;
#else
char *retval = NULL;
const char* base;
NSArray *array;
NSSearchPathDirectory dir;
NSString *str;
char *ptr;
switch (folder)
{
case SDL_FOLDER_HOME:
base = SDL_getenv("HOME");
if (!base)
{
SDL_SetError("No $HOME environment variable available");
}
retval = SDL_strdup(base);
if (!retval)
SDL_OutOfMemory();
return retval;
case SDL_FOLDER_DESKTOP:
dir = NSDesktopDirectory;
break;
case SDL_FOLDER_DOCUMENTS:
dir = NSDocumentDirectory;
break;
case SDL_FOLDER_DOWNLOADS:
dir = NSDownloadsDirectory;
break;
case SDL_FOLDER_MUSIC:
dir = NSMusicDirectory;
break;
case SDL_FOLDER_PICTURES:
dir = NSPicturesDirectory;
break;
case SDL_FOLDER_PUBLICSHARE:
dir = NSSharedPublicDirectory;
break;
case SDL_FOLDER_SAVEDGAMES:
SDL_SetError("Saved games folder not supported on Cocoa");
return NULL;
case SDL_FOLDER_SCREENSHOTS:
SDL_SetError("Screenshots folder not supported on Cocoa");
return NULL;
case SDL_FOLDER_TEMPLATES:
SDL_SetError("Templates folder not supported on Cocoa");
return NULL;
case SDL_FOLDER_VIDEOS:
dir = NSMoviesDirectory;
break;
default:
SDL_SetError("Invalid SDL_Folder: %d", (int) folder);
return NULL;
};
array = NSSearchPathForDirectoriesInDomains(dir, NSUserDomainMask, YES);
if ([array count] <= 0)
{
SDL_SetError("Directory not found");
return NULL;
}
str = [array objectAtIndex:0];
base = [str fileSystemRepresentation];
if (!base)
{
SDL_SetError("Couldn't get folder path");
return NULL;
}
retval = SDL_strdup(base);
if (retval == NULL)
{
SDL_OutOfMemory();
return NULL;
}
for (ptr = retval + 1; *ptr; ptr++) {
if (*ptr == '/') {
*ptr = '\0';
mkdir(retval, 0700);
*ptr = '/';
}
}
mkdir(retval, 0700);
return retval;
#endif /* TARGET_OS_TV */
}
}
#endif /* SDL_FILESYSTEM_COCOA */

View file

@ -39,4 +39,11 @@ SDL_GetPrefPath(const char *org, const char *app)
return NULL;
}
char *
SDL_GetPath(SDL_Folder folder)
{
SDL_Unsupported();
return NULL;
}
#endif /* SDL_FILESYSTEM_DUMMY || SDL_FILESYSTEM_DISABLED */

View file

@ -27,6 +27,7 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
@ -332,4 +333,287 @@ SDL_GetPrefPath(const char *org, const char *app)
return retval;
}
/*
The two functions below (prefixed with `xdg_`) have been copied from:
https://gitlab.freedesktop.org/xdg/xdg-user-dirs/-/blob/master/xdg-user-dir-lookup.c
and have been adapted to work with SDL. They are licensed under the following
terms:
Copyright (c) 2007 Red Hat, Inc.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
static char *
xdg_user_dir_lookup_with_fallback (const char *type, const char *fallback)
{
FILE *file;
char *home_dir, *config_home, *config_file;
char buffer[512];
char *user_dir;
char *p, *d;
int len;
int relative;
size_t l;
home_dir = SDL_getenv ("HOME");
if (home_dir == NULL)
goto error;
config_home = SDL_getenv ("XDG_CONFIG_HOME");
if (config_home == NULL || config_home[0] == 0)
{
l = SDL_strlen (home_dir) + SDL_strlen ("/.config/user-dirs.dirs") + 1;
config_file = (char*) SDL_malloc (l);
if (config_file == NULL)
goto error;
SDL_strlcpy (config_file, home_dir, l);
SDL_strlcat (config_file, "/.config/user-dirs.dirs", l);
}
else
{
l = SDL_strlen (config_home) + SDL_strlen ("/user-dirs.dirs") + 1;
config_file = (char*) SDL_malloc (l);
if (config_file == NULL)
goto error;
SDL_strlcpy (config_file, config_home, l);
SDL_strlcat (config_file, "/user-dirs.dirs", l);
}
file = fopen (config_file, "r");
SDL_free (config_file);
if (file == NULL)
goto error;
user_dir = NULL;
while (fgets (buffer, sizeof (buffer), file))
{
/* Remove newline at end */
len = SDL_strlen (buffer);
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = 0;
p = buffer;
while (*p == ' ' || *p == '\t')
p++;
if (SDL_strncmp (p, "XDG_", 4) != 0)
continue;
p += 4;
if (SDL_strncmp (p, type, SDL_strlen (type)) != 0)
continue;
p += strlen (type);
if (SDL_strncmp (p, "_DIR", 4) != 0)
continue;
p += 4;
while (*p == ' ' || *p == '\t')
p++;
if (*p != '=')
continue;
p++;
while (*p == ' ' || *p == '\t')
p++;
if (*p != '"')
continue;
p++;
relative = 0;
if (SDL_strncmp (p, "$HOME/", 6) == 0)
{
p += 6;
relative = 1;
}
else if (*p != '/')
continue;
SDL_free (user_dir);
if (relative)
{
l = SDL_strlen (home_dir) + 1 + SDL_strlen (p) + 1;
user_dir = (char*) SDL_malloc (l);
if (user_dir == NULL)
goto error2;
SDL_strlcpy (user_dir, home_dir, l);
SDL_strlcat (user_dir, "/", l);
}
else
{
user_dir = (char*) SDL_malloc (SDL_strlen (p) + 1);
if (user_dir == NULL)
goto error2;
*user_dir = 0;
}
d = user_dir + SDL_strlen (user_dir);
while (*p && *p != '"')
{
if ((*p == '\\') && (*(p+1) != 0))
p++;
*d++ = *p++;
}
*d = 0;
}
error2:
fclose (file);
if (user_dir)
return user_dir;
error:
if (fallback)
return SDL_strdup (fallback);
return NULL;
}
static char *
xdg_user_dir_lookup (const char *type)
{
char *dir, *home_dir, *user_dir;
dir = xdg_user_dir_lookup_with_fallback(type, NULL);
if (dir != NULL)
return dir;
home_dir = SDL_getenv("HOME");
if (home_dir == NULL)
return NULL;
/* Special case desktop for historical compatibility */
if (SDL_strcmp(type, "DESKTOP") == 0)
{
user_dir = (char*) SDL_malloc(SDL_strlen(home_dir) +
SDL_strlen("/Desktop") + 1);
if (user_dir == NULL)
return NULL;
strcpy(user_dir, home_dir);
strcat(user_dir, "/Desktop");
return user_dir;
}
return NULL;
}
char *
SDL_GetPath(SDL_Folder folder)
{
const char *param = NULL;
char *retval;
/* According to `man xdg-user-dir`, the possible values are:
DESKTOP
DOWNLOAD
TEMPLATES
PUBLICSHARE
DOCUMENTS
MUSIC
PICTURES
VIDEOS
*/
switch(folder)
{
case SDL_FOLDER_HOME:
param = SDL_getenv("HOME");
if (!param)
{
SDL_SetError("No $HOME environment variable available");
}
retval = SDL_strdup(param);
if (!retval)
SDL_OutOfMemory();
return retval;
case SDL_FOLDER_DESKTOP:
param = "DESKTOP";
break;
case SDL_FOLDER_DOCUMENTS:
param = "DOCUMENTS";
break;
case SDL_FOLDER_DOWNLOADS:
param = "DOWNLOAD";
break;
case SDL_FOLDER_MUSIC:
param = "MUSIC";
break;
case SDL_FOLDER_PICTURES:
param = "PICTURES";
break;
case SDL_FOLDER_PUBLICSHARE:
param = "PUBLICSHARE";
break;
case SDL_FOLDER_SAVEDGAMES:
SDL_SetError("Saved Games folder unavailable on XDG");
return NULL;
case SDL_FOLDER_SCREENSHOTS:
SDL_SetError("Screenshots folder unavailable on XDG");
return NULL;
case SDL_FOLDER_TEMPLATES:
param = "TEMPLATES";
break;
case SDL_FOLDER_VIDEOS:
param = "VIDEOS";
break;
default:
SDL_SetError("Invalid SDL_Folder: %d", (int) folder);
return NULL;
}
/* param *should* to be set to something at this point, but just in case */
if (!param)
{
SDL_SetError("No corresponding XDG user directory");
return NULL;
}
retval = xdg_user_dir_lookup(param);
if (!retval)
{
SDL_SetError("XDG directory not available");
}
return retval;
}
#endif /* SDL_FILESYSTEM_UNIX */

View file

@ -26,7 +26,14 @@
/* System dependent filesystem routines */
#include "../../core/windows/SDL_windows.h"
#include <errhandlingapi.h>
#include <fileapi.h>
#include <shlobj.h>
#include <libloaderapi.h>
/* Lowercase is necessary for Wine */
#include <knownfolders.h>
#include <initguid.h>
#include <windows.h>
char *
SDL_GetBasePath(void)
@ -166,6 +173,179 @@ SDL_GetPrefPath(const char *org, const char *app)
return retval;
}
char *
SDL_GetPath(SDL_Folder folder)
{
typedef HRESULT (*SHGKFP)(REFKNOWNFOLDERID, DWORD, HANDLE, PWSTR *);
char *retval;
HMODULE lib = LoadLibrary(L"Shell32.dll");
SHGKFP SHGetKnownFolderPath_ = (SHGKFP) GetProcAddress(lib,
"SHGetKnownFolderPath");
if (!SHGetKnownFolderPath_)
{
int type;
HRESULT result;
wchar_t path[MAX_PATH];
switch (folder)
{
case SDL_FOLDER_HOME:
type = CSIDL_PROFILE;
break;
case SDL_FOLDER_DESKTOP:
type = CSIDL_DESKTOP;
break;
case SDL_FOLDER_DOCUMENTS:
type = CSIDL_MYDOCUMENTS;
break;
case SDL_FOLDER_DOWNLOADS:
SDL_SetError("Downloads folder unavailable before Vista");
return NULL;
case SDL_FOLDER_MUSIC:
type = CSIDL_MYMUSIC;
break;
case SDL_FOLDER_PICTURES:
type = CSIDL_MYPICTURES;
break;
case SDL_FOLDER_PUBLICSHARE:
SDL_SetError("Public share unavailable on Windows");
return NULL;
case SDL_FOLDER_SAVEDGAMES:
SDL_SetError("Saved games unavailable before Vista");
return NULL;
case SDL_FOLDER_SCREENSHOTS:
SDL_SetError("Screenshots folder unavailable before Vista");
return NULL;
case SDL_FOLDER_TEMPLATES:
type = CSIDL_TEMPLATES;
break;
case SDL_FOLDER_VIDEOS:
type = CSIDL_MYVIDEO;
break;
default:
SDL_SetError("Unsupported SDL_Folder on Windows before Vista: %d",
(int) folder);
return NULL;
};
/* Create the OS-specific folder if it doesn't already exist */
type |= CSIDL_FLAG_CREATE;
#if 0
/* Apparently the oldest, but not supported in modern Windows */
HRESULT result = SHGetSpecialFolderPath(NULL, path, type, TRUE);
#endif
/* Windows 2000/XP and later, deprecated as of Windows 10 (still
available), available in Wine (tested 6.0.3) */
result = SHGetFolderPathW(NULL, type, NULL, SHGFP_TYPE_CURRENT, path);
/* use `!= TRUE` for SHGetSpecialFolderPath */
if (result != S_OK)
{
SDL_SetError("Couldn't get folder, windows-specific error: %ld",
result);
return NULL;
}
retval = (char *) SDL_malloc((SDL_wcslen(path) + 1) * 2);
if (retval == NULL)
{
SDL_OutOfMemory();
return NULL;
}
retval = WIN_StringToUTF8W(path);
return retval;
}
else
{
KNOWNFOLDERID type;
HRESULT result;
wchar_t *path;
switch (folder)
{
case SDL_FOLDER_HOME:
type = FOLDERID_Profile;
break;
case SDL_FOLDER_DESKTOP:
type = FOLDERID_Desktop;
break;
case SDL_FOLDER_DOCUMENTS:
type = FOLDERID_Documents;
break;
case SDL_FOLDER_DOWNLOADS:
type = FOLDERID_Downloads;
break;
case SDL_FOLDER_MUSIC:
type = FOLDERID_Music;
break;
case SDL_FOLDER_PICTURES:
type = FOLDERID_Pictures;
break;
case SDL_FOLDER_PUBLICSHARE:
SDL_SetError("Public share unavailable on Windows");
return NULL;
case SDL_FOLDER_SAVEDGAMES:
type = FOLDERID_SavedGames;
break;
case SDL_FOLDER_SCREENSHOTS:
type = FOLDERID_Screenshots;
break;
case SDL_FOLDER_TEMPLATES:
type = FOLDERID_Templates;
break;
case SDL_FOLDER_VIDEOS:
type = FOLDERID_Videos;
break;
default:
SDL_SetError("Invalid SDL_Folder: %d", (int) folder);
return NULL;
};
result = SHGetKnownFolderPath_(&type, KF_FLAG_CREATE, NULL, &path);
if (result != S_OK)
{
SDL_SetError("Couldn't get folder, windows-specific error: %ld",
result);
return NULL;
}
retval = (char *) SDL_malloc((SDL_wcslen(path) + 1) * 2);
if (retval == NULL)
{
SDL_OutOfMemory();
return NULL;
}
retval = WIN_StringToUTF8W(path);
return retval;
}
}
#endif /* SDL_FILESYSTEM_WINDOWS */
#ifdef SDL_FILESYSTEM_XBOX
@ -182,4 +362,11 @@ SDL_GetPrefPath(const char *org, const char *app)
SDL_Unsupported();
return NULL;
}
char *
SDL_GetPath(SDL_Folder folder)
{
SDL_Unsupported();
return NULL;
}
#endif /* SDL_FILESYSTEM_XBOX */