diff --git a/README.md b/README.md index 2add710..a0ca37f 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,13 @@ Basic use: #include "cfgpath.h" - char cfgdir[MAX_PATH]; - get_user_config_file(cfgdir, sizeof(cfgdir), "myapp"); + cfgpathchar_t cfgdir[MAX_PATH]; + get_user_config_file(cfgdir, sizeof(cfgdir)/sizeof(cfgdir[0]), CFGPATHTEXT("myapp")); if (cfgdir[0] == 0) { printf("Unable to find home directory.\n"); return 1; } - printf("Saving configuration file to %s\n", cfgdir); + cfgpath__printf(CFGPATHTEXT("Saving configuration file to %s\n"), cfgdir); To integrate it into your own project, just copy cfgpath.h. All the other files are for testing to make sure it works correctly, so you don't need them diff --git a/cfgpath.h b/cfgpath.h index b457235..4043dcc 100644 --- a/cfgpath.h +++ b/cfgpath.h @@ -9,13 +9,20 @@ * * Example use: * - * char cfgdir[256]; - * get_user_config_file(cfgdir, sizeof(cfgdir), "myapp"); + * cfgpathchar_t cfgdir[256]; + * get_user_config_file(cfgdir, sizeof(cfgdir)/sizeof(cfgdir[0]), CFGPATHTEXT("myapp")); * if (cfgdir[0] == 0) { * printf("Unable to find home directory.\n"); * return 1; * } - * printf("Saving configuration file to %s\n", cfgdir); + * cfgpath__printf(CFGPATHTEXT("Saving configuration file to %s\n"), cfgdir); + * + * The CFGPATHTEXT macro will prefix strings with L if building for Win32 with + * UNICODE #defined. Similarly, cfgpath__printf(), cfgpath__strcat(), etc. will + * use the Win32 wchar_t functions, and cfgpathchar_t will resolve to the wchar_t + * data type. Otherwise, UTF-8 is assumed and the standard C library functions are + * used along with the char data type. For maximum portability between platforms + * and encodings, use these macros. * * A number of constants are also defined: * @@ -33,6 +40,18 @@ #ifndef CFGPATH_H_ #define CFGPATH_H_ +#if defined(WIN32) && defined(UNICODE) +#define CFGPATHTEXT(q) L##q +#else +#define CFGPATHTEXT(q) q +#endif + +#define cfgpath__strlen strlen +#define cfgpath__strcat strcat +#define cfgpath__strcpy strcpy +#define cfgpath__strcmp strcmp +#define cfgpath__printf printf + #ifdef _MSC_VER #define inline __inline #include @@ -50,7 +69,22 @@ #include /* MAX_PATH is defined by the Windows API */ #define PATH_SEPARATOR_CHAR '\\' -#define PATH_SEPARATOR_STRING "\\" +#define PATH_SEPARATOR_STRING CFGPATHTEXT("\\") +#ifdef UNICODE // will use wchar_t types in this instance +#define CFGPATH_WIN32_UNICODE +#define HAVE_CFGPATHCHAR_T +typedef wchar_t cfgpathchar_t; +#undef cfgpath__strlen +#undef cfgpath__strcat +#undef cfgpath__strcpy +#undef cfgpath__strcmp +#undef cfgpath__printf +#define cfgpath__strlen wcslen +#define cfgpath__strcat wcscat +#define cfgpath__strcpy wcscpy +#define cfgpath__strcmp wcscmp +#define cfgpath__printf wprintf +#endif // UNICODE #elif defined(__APPLE__) #include #include @@ -61,6 +95,10 @@ #error cfgpath.h functions have not been implemented for your platform! Please send patches. #endif +#ifndef HAVE_CFGPATHCHAR_T +typedef char cfgpathchar_t; +#endif + /** Get an absolute path to a single configuration file, specific to this user. * * This function is useful for programs that need only a single configuration @@ -86,7 +124,7 @@ * @post The file may or may not exist. * @post The folder holding the file is created if needed. */ -static inline void get_user_config_file(char *out, unsigned int maxlen, const char *appname) +static inline void get_user_config_file(cfgpathchar_t *out, unsigned int maxlen, const cfgpathchar_t *appname) { #ifdef __linux__ const char *out_orig = out; @@ -137,14 +175,14 @@ static inline void get_user_config_file(char *out, unsigned int maxlen, const ch return; } /* We don't try to create the AppData folder as it always exists already */ - unsigned int appname_len = strlen(appname); - if (strlen(out) + 1 + appname_len + strlen(".ini") + 1 > maxlen) { + unsigned int appname_len = cfgpath__strlen(appname); + if (cfgpath__strlen(out) + 1 + appname_len + cfgpath__strlen(CFGPATHTEXT(".ini")) + 1 > maxlen) { out[0] = 0; return; } - strcat(out, "\\"); - strcat(out, appname); - strcat(out, ".ini"); + cfgpath__strcat(out, PATH_SEPARATOR_STRING); + cfgpath__strcat(out, appname); + cfgpath__strcat(out, CFGPATHTEXT(".ini")); #elif defined(__APPLE__) FSRef ref; FSFindFolder(kUserDomain, kApplicationSupportFolderType, kCreateFolder, &ref); @@ -192,7 +230,7 @@ static inline void get_user_config_file(char *out, unsigned int maxlen, const ch * * @post The folder is created if needed. */ -static inline void get_user_config_folder(char *out, unsigned int maxlen, const char *appname) +static inline void get_user_config_folder(cfgpathchar_t *out, unsigned int maxlen, const cfgpathchar_t *appname) { #ifdef __linux__ const char *out_orig = out; @@ -246,16 +284,16 @@ static inline void get_user_config_folder(char *out, unsigned int maxlen, const return; } /* We don't try to create the AppData folder as it always exists already */ - unsigned int appname_len = strlen(appname); - if (strlen(out) + 1 + appname_len + 1 + 1 > maxlen) { + unsigned int appname_len = cfgpath__strlen(appname); + if (cfgpath__strlen(out) + 1 + appname_len + 1 + 1 > maxlen) { out[0] = 0; return; } - strcat(out, "\\"); - strcat(out, appname); + cfgpath__strcat(out, PATH_SEPARATOR_STRING); + cfgpath__strcat(out, appname); /* Make the AppData\appname folder if it doesn't already exist */ mkdir(out); - strcat(out, "\\"); + cfgpath__strcat(out, PATH_SEPARATOR_STRING); #elif defined(__APPLE__) FSRef ref; FSFindFolder(kUserDomain, kApplicationSupportFolderType, kCreateFolder, &ref); @@ -309,7 +347,7 @@ static inline void get_user_config_folder(char *out, unsigned int maxlen, const * * @post The folder is created if needed. */ -static inline void get_user_data_folder(char *out, unsigned int maxlen, const char *appname) +static inline void get_user_data_folder(cfgpathchar_t *out, unsigned int maxlen, const cfgpathchar_t *appname) { #ifdef __linux__ const char *out_orig = out; @@ -394,7 +432,7 @@ static inline void get_user_data_folder(char *out, unsigned int maxlen, const ch * * @post The folder is created if needed. */ -static inline void get_user_cache_folder(char *out, unsigned int maxlen, const char *appname) +static inline void get_user_cache_folder(cfgpathchar_t *out, unsigned int maxlen, const cfgpathchar_t *appname) { #ifdef __linux__ const char *out_orig = out; @@ -448,16 +486,16 @@ static inline void get_user_cache_folder(char *out, unsigned int maxlen, const c return; } /* We don't try to create the AppData folder as it always exists already */ - unsigned int appname_len = strlen(appname); - if (strlen(out) + 1 + appname_len + 1 + 1 > maxlen) { + unsigned int appname_len = cfgpath__strlen(appname); + if (cfgpath__strlen(out) + 1 + appname_len + 1 + 1 > maxlen) { out[0] = 0; return; } - strcat(out, "\\"); - strcat(out, appname); + cfgpath__strcat(out, PATH_SEPARATOR_STRING); + cfgpath__strcat(out, appname); /* Make the AppData\appname folder if it doesn't already exist */ mkdir(out); - strcat(out, "\\"); + cfgpath__strcat(out, PATH_SEPARATOR_STRING); #elif defined(__APPLE__) /* No distinction under OS X */ get_user_config_folder(out, maxlen, appname); diff --git a/test-linux.c b/test-linux.c index 8bda730..6ed269a 100644 --- a/test-linux.c +++ b/test-linux.c @@ -40,12 +40,15 @@ char *test_getenv(const char *var) int test_mkdir(const char *path, mode_t mode) { return 0; + // silence unused variable warnings + (void)path; + (void)mode; } #define TOSTRING_X(x) #x #define TOSTRING(x) TOSTRING_X(x) #define RUN_TEST(result, msg) \ - TEST_FUNC(buffer, sizeof(buffer), "test-linux"); \ + TEST_FUNC(buffer, sizeof(buffer)/sizeof(buffer[0]), "test-linux"); \ CHECK_RESULT(result, msg) #define CHECK_RESULT(result, msg) \ @@ -57,9 +60,9 @@ int test_mkdir(const char *path, mode_t mode) printf("PASS: " TOSTRING(TEST_FUNC) "() " msg "\n"); \ } -int main(int argc, char *argv[]) +int main(void) { - char buffer[256]; + cfgpathchar_t buffer[MAX_PATH]; /* * get_user_config_file() diff --git a/test-win-unicode.c b/test-win-unicode.c new file mode 100644 index 0000000..54d1463 --- /dev/null +++ b/test-win-unicode.c @@ -0,0 +1,105 @@ +/** + * @file test-win-unicode.c + * @brief cfgpath.h test code for the Windows, with Unicode support. + * + * Copyright (C) 2013 Adam Nielsen + * + * This code is placed in the public domain. You are free to use it for any + * purpose. If you add new platform support, please contribute a patch! + */ + +#include +#include + +#define SHGetFolderPath test_SHGetFolderPath +#define mkdir test_mkdir +int test_mkdir(void *path); +int test_SHGetFolderPath(void *hwndOwner, int nFolder, void *hToken, int dwFlags, void *_pszPath); +#undef __linux__ +#ifndef WIN32 +#define WIN32 +#endif +#ifndef UNICODE // enable unicode before #include-ing cfgpath.h +#define UNICODE +#endif +#include "cfgpath.h" + +const cfgpathchar_t *set_appdata; +const cfgpathchar_t *set_appdata_local; +int set_retval; + +/* Fake implementation of SHGetFolderPath() that fails when and how we want */ +int test_SHGetFolderPath(void *hwndOwner, int nFolder, void *hToken, + int dwFlags, void *_pszPath) +{ + // silence unused variable warnings + (void)hwndOwner; + (void)hToken; + (void)dwFlags; + + /* Hopefully trigger an error if the buffer is too small */ + cfgpathchar_t *pszPath = (cfgpathchar_t*) _pszPath; + pszPath[MAX_PATH - 1] = 0; + + switch (nFolder) { + case CSIDL_APPDATA: + if (set_appdata) { + cfgpath__strcpy(pszPath, set_appdata); + return set_retval; + } + break; + case CSIDL_LOCAL_APPDATA: + if (set_appdata_local) { + cfgpath__strcpy(pszPath, set_appdata_local); + return set_retval; + } + break; + } + return E_FAIL; +} + +int test_mkdir(void *path) +{ + return 0; + (void)path; // silence unused variable warning +} + +int main(void) +{ + cfgpathchar_t buffer[MAX_PATH]; + +// get_user_config_file + get_user_config_file(buffer, sizeof(buffer)/sizeof(buffer[0]), CFGPATHTEXT("myapp")); + if (buffer[0] == 0) { + printf("get_user_config_file() fail\n"); + return 1; + } + cfgpath__printf(CFGPATHTEXT("get_user_config_file() returned '%s'!\n"), buffer); + +// get_user_config_folder + get_user_config_folder(buffer, sizeof(buffer)/sizeof(buffer[0]), CFGPATHTEXT("myapp")); + if (buffer[0] == 0) { + printf("get_user_config_folder() fail\n"); + return 1; + } + cfgpath__printf(CFGPATHTEXT("get_user_config_folder() returned '%s'!\n"), buffer); + +// get_user_data_folder + get_user_data_folder(buffer, sizeof(buffer)/sizeof(buffer[0]), CFGPATHTEXT("myapp")); + if (buffer[0] == 0) { + printf("get_user_data_folder() fail\n"); + return 1; + } + cfgpath__printf(CFGPATHTEXT("get_user_data_folder() returned '%s'!\n"), buffer); + +// get_user_cache_folder + get_user_cache_folder(buffer, sizeof(buffer)/sizeof(buffer[0]), CFGPATHTEXT("myapp")); + if (buffer[0] == 0) { + printf("get_user_cache_folder() fail\n"); + return 1; + } + cfgpath__printf(CFGPATHTEXT("get_user_cache_folder() returned '%s'!\n"), buffer); + + printf("All tests passed for platform: Windows (Unicode).\n"); + return 0; +} diff --git a/test-win.c b/test-win.c index 15a6d77..7a1cfa5 100644 --- a/test-win.c +++ b/test-win.c @@ -1,6 +1,6 @@ /** - * @file test-linux.c - * @brief cfgpath.h test code for the Linux platform. + * @file test-win.c + * @brief cfgpath.h test code for the Windows platform. * * Copyright (C) 2013 Adam Nielsen * @@ -13,10 +13,15 @@ #define SHGetFolderPath test_SHGetFolderPath #define mkdir test_mkdir +int test_mkdir(const char *path); +int test_SHGetFolderPath(void *hwndOwner, int nFolder, void *hToken, int dwFlags, char *pszPath); #undef __linux__ #ifndef WIN32 #define WIN32 #endif +#ifdef UNICODE +#undef UNICODE // try test-win-unicode.c if you want a unicode example +#endif #include "cfgpath.h" const char *set_appdata; @@ -27,6 +32,11 @@ int set_retval; int test_SHGetFolderPath(void *hwndOwner, int nFolder, void *hToken, int dwFlags, char *pszPath) { + // silence unused variable warnings + (void)hwndOwner; + (void)hToken; + (void)dwFlags; + /* Hopefully trigger an error if the buffer is too small */ pszPath[MAX_PATH - 1] = 0; @@ -50,16 +60,17 @@ int test_SHGetFolderPath(void *hwndOwner, int nFolder, void *hToken, int test_mkdir(const char *path) { return 0; + (void)path; // silence unused variable warning } #define TOSTRING_X(x) #x #define TOSTRING(x) TOSTRING_X(x) #define RUN_TEST(result, msg) \ - TEST_FUNC(buffer, sizeof(buffer), "test-win"); \ + TEST_FUNC(buffer, sizeof(buffer)/sizeof(buffer[0]), "test-win"); \ CHECK_RESULT(result, msg) #define CHECK_RESULT(result, msg) \ - if (strcmp(buffer, result) != 0) { \ + if (cfgpath__strcmp(buffer, result) != 0) { \ printf("FAIL: %s:%d " TOSTRING(TEST_FUNC) "() " msg " Test failed, " \ "returning the wrong value.\n" \ "Expected: %s\nGot: %s\n", __FILE__, __LINE__, result, buffer); \ @@ -68,9 +79,9 @@ int test_mkdir(const char *path) printf("PASS: " TOSTRING(TEST_FUNC) "() " msg "\n"); \ } -int main(int argc, char *argv[]) +int main(void) { - char buffer[256]; + cfgpathchar_t buffer[MAX_PATH]; /* * get_user_config_file()