diff --git a/CMakeLists.txt b/CMakeLists.txt index ad8213c..48ae458 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,8 +31,11 @@ if(MINGW) endif(MINGW) if (WIN32) -SET(GUI_TYPE WIN32) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=i686") + SET(GUI_TYPE WIN32) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=i686") + if(NOT _WIN32_WINNT AND NOT WINVER) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_WIN32_WINNT=0x0601 -DWINVER=0x0601") + endif() endif() if (NOT WIN32 AND NOT STANDALONE) diff --git a/source/code/sago/platform_folders.cpp b/source/code/sago/platform_folders.cpp index f55e4ad..1c9b99c 100644 --- a/source/code/sago/platform_folders.cpp +++ b/source/code/sago/platform_folders.cpp @@ -29,22 +29,65 @@ SOFTWARE. #include "platform_folders.h" #include #include -#include #include #include -#if defined(_WIN32) +#ifndef _WIN32 + +#include +#include + +/** + * Retrives the effective user's home dir. + * If the user is running as root we ignore the HOME environment. It works badly with sudo. + * Writing to $HOME as root implies security concerns that a multiplatform program cannot be assumed to handle. + * @return The home directory. HOME environment is respected for non-root users if it exists. + */ +static std::string getHome() { + std::string res; + int uid = getuid(); + const char* homeEnv = std::getenv("HOME"); + if ( uid != 0 && homeEnv) { + //We only acknowlegde HOME if not root. + res = homeEnv; + return res; + } + struct passwd* pw = getpwuid(uid); + if (!pw) { + throw std::runtime_error("Unable to get passwd struct."); + } + const char* tempRes = pw->pw_dir; + if (!tempRes) { + throw std::runtime_error("User has no home directory"); + } + res = tempRes; + return res; +} + +#endif + +#ifdef _WIN32 +// Make sure we don't bring in all the extra junk with windows.h +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +// stringapiset.h depends on this #include +// For SUCCEEDED macro +#include +// For WideCharToMultiByte +#include +// For SHGetFolderPathW and various CSIDL "magic numbers" #include static std::string win32_utf16_to_utf8(const wchar_t* wstr) { std::string res; // If the 6th parameter is 0 then WideCharToMultiByte returns the number of bytes needed to store the result. - int actualSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); + int actualSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr); if (actualSize > 0) { //If the converted UTF-8 string could not be in the initial buffer. Allocate one that can hold it. std::vector buffer(actualSize); - actualSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &buffer[0], buffer.size(), NULL, NULL); + actualSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &buffer[0], buffer.size(), nullptr, nullptr); res = buffer.data(); } if (actualSize == 0) { @@ -55,50 +98,46 @@ static std::string win32_utf16_to_utf8(const wchar_t* wstr) { return res; } -static std::string GetWindowsFolder(int folderId, const char* errorMsg) { - wchar_t szPath[MAX_PATH]; - szPath[0] = 0; - if ( !SUCCEEDED( SHGetFolderPathW( NULL, folderId, NULL, 0, szPath ) ) ) { +class FreeCoTaskMemory { + LPWSTR pointer = NULL; +public: + explicit FreeCoTaskMemory(LPWSTR pointer) : pointer(pointer) {}; + ~FreeCoTaskMemory() { + CoTaskMemFree(pointer); + } +}; + +static std::string GetKnownWindowsFolder(REFKNOWNFOLDERID folderId, const char* errorMsg) { + LPWSTR wszPath = NULL; + HRESULT hr; + hr = SHGetKnownFolderPath(folderId, KF_FLAG_CREATE, NULL, &wszPath); + FreeCoTaskMemory scopeBoundMemory(wszPath); + + if (!SUCCEEDED(hr)) { throw std::runtime_error(errorMsg); } - return win32_utf16_to_utf8(szPath); + return win32_utf16_to_utf8(wszPath); } static std::string GetAppData() { - return GetWindowsFolder(CSIDL_APPDATA, "RoamingAppData could not be found"); + return GetKnownWindowsFolder(FOLDERID_RoamingAppData, "RoamingAppData could not be found"); } static std::string GetAppDataCommon() { - return GetWindowsFolder(CSIDL_COMMON_APPDATA, "Common appdata could not be found"); + return GetKnownWindowsFolder(FOLDERID_ProgramData, "ProgramData could not be found"); } static std::string GetAppDataLocal() { - return GetWindowsFolder(CSIDL_LOCAL_APPDATA, "LocalAppData could not be found"); + return GetKnownWindowsFolder(FOLDERID_LocalAppData, "LocalAppData could not be found"); } #elif defined(__APPLE__) -#include - -static std::string GetMacFolder(OSType folderType, const char* errorMsg) { - std::string ret; - FSRef ref; - char path[PATH_MAX]; - OSStatus err = FSFindFolder( kUserDomain, folderType, kCreateFolder, &ref ); - if (err != noErr) { - throw std::runtime_error(errorMsg); - } - FSRefMakePath( &ref, (UInt8*)&path, PATH_MAX ); - ret = path; - return ret; -} - #else #include #include -#include -#include #include // For strlen and strtok #include +#include //Typically Linux. For easy reading the comments will just say Linux but should work with most *nixes static void throwOnRelative(const char* envName, const char* envValue) { @@ -109,32 +148,7 @@ static void throwOnRelative(const char* envName, const char* envValue) { } } -/** - * Retrives the effective user's home dir. - * If the user is running as root we ignore the HOME environment. It works badly with sudo. - * Writing to $HOME as root implies security concerns that a multiplatform program cannot be assumed to handle. - * @return The home directory. HOME environment is respected for non-root users if it exists. - */ -static std::string getHome() { - std::string res; - int uid = getuid(); - const char* homeEnv = std::getenv("HOME"); - if ( uid != 0 && homeEnv) { - //We only acknowlegde HOME if not root. - res = homeEnv; - return res; - } - struct passwd* pw = getpwuid(uid); - if (!pw) { - throw std::runtime_error("Unable to get passwd struct."); - } - const char* tempRes = pw->pw_dir; - if (!tempRes) { - throw std::runtime_error("User has no home directory"); - } - res = tempRes; - return res; -} + static std::string getLinuxFolderDefault(const char* envName, const char* defaultRelativePath) { std::string res; @@ -148,28 +162,12 @@ static std::string getLinuxFolderDefault(const char* envName, const char* defaul return res; } -static void appendExtraFoldersTokenizer(const char* envName, const char* envValue, std::vector& folders) { - std::vector buffer(envValue, envValue + std::strlen(envValue) + 1); - char* p = std::strtok ( &buffer[0], ":"); - while (p != NULL) { - if (p[0] == '/') { - folders.push_back(p); - } - else { - //Unless the system is wrongly configured this should never happen... But of course some systems will be incorectly configured. - //The XDG documentation indicates that the folder should be ignored but that the program should continue. - std::cerr << "Skipping path \"" << p << "\" in \"" << envName << "\" because it does not start with a \"/\"\n"; - } - p = std::strtok (NULL, ":"); - } -} - static void appendExtraFolders(const char* envName, const char* defaultValue, std::vector& folders) { const char* envValue = std::getenv(envName); if (!envValue) { envValue = defaultValue; } - appendExtraFoldersTokenizer(envName, envValue, folders); + sago::internal::appendExtraFoldersTokenizer(envName, envValue, folders); } #endif @@ -177,57 +175,72 @@ static void appendExtraFolders(const char* envName, const char* defaultValue, st namespace sago { +#if !defined(_WIN32) && !defined(__APPLE__) +namespace internal { + void appendExtraFoldersTokenizer(const char* envName, const char* envValue, std::vector& folders) { + std::stringstream ss(envValue); + std::string value; + while (std::getline(ss, value, ':')) { + if (value[0] == '/') { + folders.push_back(value); + } + else { + //Unless the system is wrongly configured this should never happen... But of course some systems will be incorectly configured. + //The XDG documentation indicates that the folder should be ignored but that the program should continue. + std::cerr << "Skipping path \"" << value << "\" in \"" << envName << "\" because it does not start with a \"/\"\n"; + } + } + } +} +#endif + std::string getDataHome() { -#if defined(_WIN32) +#ifdef _WIN32 return GetAppData(); #elif defined(__APPLE__) - return GetMacFolder(kApplicationSupportFolderType, "Failed to find the Application Support Folder"); + return getHome()+"/Library/Application Support"; #else return getLinuxFolderDefault("XDG_DATA_HOME", ".local/share"); #endif } std::string getConfigHome() { -#if defined(_WIN32) +#ifdef _WIN32 return GetAppData(); #elif defined(__APPLE__) - return GetMacFolder(kApplicationSupportFolderType, "Failed to find the Application Support Folder"); + return getHome()+"/Library/Application Support"; #else return getLinuxFolderDefault("XDG_CONFIG_HOME", ".config"); #endif } std::string getCacheDir() { -#if defined(_WIN32) +#ifdef _WIN32 return GetAppDataLocal(); #elif defined(__APPLE__) - return GetMacFolder(kCachedDataFolderType, "Failed to find the Application Support Folder"); + return getHome()+"/Library/Caches"; #else return getLinuxFolderDefault("XDG_CACHE_HOME", ".cache"); #endif } void appendAdditionalDataDirectories(std::vector& homes) { -#if defined(_WIN32) +#ifdef _WIN32 homes.push_back(GetAppDataCommon()); -#elif defined(__APPLE__) -#else +#elif !defined(__APPLE__) appendExtraFolders("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/", homes); #endif } void appendAdditionalConfigDirectories(std::vector& homes) { -#if defined(_WIN32) +#ifdef _WIN32 homes.push_back(GetAppDataCommon()); -#elif defined(__APPLE__) -#else +#elif !defined(__APPLE__) appendExtraFolders("XDG_CONFIG_DIRS", "/etc/xdg", homes); #endif } -#if defined(_WIN32) -#elif defined(__APPLE__) -#else +#if !defined(_WIN32) && !defined(__APPLE__) struct PlatformFolders::PlatformFoldersData { std::map folders; }; @@ -273,9 +286,7 @@ static void PlatformFoldersFillData(std::map& folders) #endif PlatformFolders::PlatformFolders() { -#if defined(_WIN32) -#elif defined(__APPLE__) -#else +#if !defined(_WIN32) && !defined(__APPLE__) this->data = new PlatformFolders::PlatformFoldersData(); try { PlatformFoldersFillData(data->folders); @@ -288,81 +299,88 @@ PlatformFolders::PlatformFolders() { } PlatformFolders::~PlatformFolders() { -#if defined(_WIN32) -#elif defined(__APPLE__) -#else +#if !defined(_WIN32) && !defined(__APPLE__) delete this->data; #endif } std::string PlatformFolders::getDocumentsFolder() const { -#if defined(_WIN32) - return GetWindowsFolder(CSIDL_PERSONAL, "Failed to find My Documents folder"); +#ifdef _WIN32 + return GetKnownWindowsFolder(FOLDERID_Documents, "Failed to find My Documents folder"); #elif defined(__APPLE__) - return GetMacFolder(kDocumentsFolderType, "Failed to find Documents Folder"); + return getHome()+"/Documents"; #else return data->folders["XDG_DOCUMENTS_DIR"]; #endif } std::string PlatformFolders::getDesktopFolder() const { -#if defined(_WIN32) - return GetWindowsFolder(CSIDL_DESKTOP, "Failed to find Desktop folder"); +#ifdef _WIN32 + return GetKnownWindowsFolder(FOLDERID_Desktop, "Failed to find Desktop folder"); #elif defined(__APPLE__) - return GetMacFolder(kDesktopFolderType, "Failed to find Desktop folder"); + return getHome()+"/Desktop"; #else return data->folders["XDG_DESKTOP_DIR"]; #endif } std::string PlatformFolders::getPicturesFolder() const { -#if defined(_WIN32) - return GetWindowsFolder(CSIDL_MYPICTURES, "Failed to find My Pictures folder"); +#ifdef _WIN32 + return GetKnownWindowsFolder(FOLDERID_Pictures, "Failed to find My Pictures folder"); #elif defined(__APPLE__) - return GetMacFolder(kPictureDocumentsFolderType, "Failed to find Picture folder"); + return getHome()+"/Pictures"; #else return data->folders["XDG_PICTURES_DIR"]; #endif } +std::string PlatformFolders::getPublicFolder() const { +#ifdef _WIN32 + return GetKnownWindowsFolder(FOLDERID_Public, "Failed to find the Public folder"); +#elif defined(__APPLE__) + return getHome()+"/Public"; +#else + return data->folders["XDG_PUBLICSHARE_DIR"]; +#endif +} + std::string PlatformFolders::getDownloadFolder1() const { -#if defined(_WIN32) - //Pre Vista. Files was downloaded to the desktop - return GetWindowsFolder(CSIDL_DESKTOP, "Failed to find My Downloads (Desktop) folder"); +#ifdef _WIN32 + return GetKnownWindowsFolder(FOLDERID_Downloads, "Failed to find My Downloads folder"); #elif defined(__APPLE__) - return GetMacFolder(kDownloadsFolderType, "Failed to find Download folder"); + return getHome()+"/Downloads"; #else return data->folders["XDG_DOWNLOAD_DIR"]; #endif } std::string PlatformFolders::getMusicFolder() const { -#if defined(_WIN32) - return GetWindowsFolder(CSIDL_MYMUSIC, "Failed to find My Music folder"); +#ifdef _WIN32 + return GetKnownWindowsFolder(FOLDERID_Music, "Failed to find My Music folder"); #elif defined(__APPLE__) - return GetMacFolder(kMusicDocumentsFolderType, "Failed to find Music folder"); + return getHome()+"/Music"; #else return data->folders["XDG_MUSIC_DIR"]; #endif } std::string PlatformFolders::getVideoFolder() const { -#if defined(_WIN32) - return GetWindowsFolder(CSIDL_MYVIDEO, "Failed to find My Video folder"); +#ifdef _WIN32 + return GetKnownWindowsFolder(FOLDERID_Videos, "Failed to find My Video folder"); #elif defined(__APPLE__) - return GetMacFolder(kMovieDocumentsFolderType, "Failed to find Movie folder"); + return getHome()+"/Movies"; #else return data->folders["XDG_VIDEOS_DIR"]; #endif } std::string PlatformFolders::getSaveGamesFolder1() const { -#if defined(_WIN32) +#ifdef _WIN32 //A dedicated Save Games folder was not introduced until Vista. For XP and older save games are most often saved in a normal folder named "My Games". //Data that should not be user accessible should be placed under GetDataHome() instead - return GetWindowsFolder(CSIDL_PERSONAL, "Failed to find My Documents folder")+"\\My Games"; + return GetKnownWindowsFolder(FOLDERID_Documents, "Failed to find My Documents folder")+"\\My Games"; #elif defined(__APPLE__) - return GetMacFolder(kApplicationSupportFolderType, "Failed to find Application Support Folder"); + return getHome()+"/Library/Application Support"; #else return getDataHome(); #endif @@ -376,14 +394,22 @@ std::string getDocumentsFolder() { return PlatformFolders().getDocumentsFolder(); } -std::string getDownloadFolder1() { +std::string getDownloadFolder() { return PlatformFolders().getDownloadFolder1(); } +std::string getDownloadFolder1() { + return getDownloadFolder(); +} + std::string getPicturesFolder() { return PlatformFolders().getPicturesFolder(); } +std::string getPublicFolder() { + return PlatformFolders().getPublicFolder(); +} + std::string getMusicFolder() { return PlatformFolders().getMusicFolder(); } @@ -396,5 +422,12 @@ std::string getSaveGamesFolder1() { return PlatformFolders().getSaveGamesFolder1(); } +std::string getSaveGamesFolder2() { +#ifdef _WIN32 + return GetKnownWindowsFolder(FOLDERID_SavedGames, "Failed to find Saved Games folder"); +#else + return PlatformFolders().getSaveGamesFolder1(); +#endif +} } //namespace sago diff --git a/source/code/sago/platform_folders.h b/source/code/sago/platform_folders.h index bd6c0d5..fc9dd3b 100644 --- a/source/code/sago/platform_folders.h +++ b/source/code/sago/platform_folders.h @@ -37,39 +37,47 @@ SOFTWARE. */ namespace sago { +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace internal { + #if !defined(_WIN32) && !defined(__APPLE__) + void appendExtraFoldersTokenizer(const char* envName, const char* envValue, std::vector& folders); + #endif +} +#endif //DOXYGEN_SHOULD_SKIP_THIS + /** - * Retrives the base folder for storring data files. + * Retrives the base folder for storing data files. * You must add the program name yourself like this: * @code{.cpp} * string data_home = getDataHome()+"/My Program Name/"; * @endcode * On Windows this defaults to %APPDATA% (Roaming profile) * On Linux this defaults to ~/.local/share but can be configured - * @return The base folder for storring program data. + * @return The base folder for storing program data. */ std::string getDataHome(); /** - * Retrives the base folder for storring config files. + * Retrives the base folder for storing config files. * You must add the program name yourself like this: * @code{.cpp} * string data_home = getConfigHome()+"/My Program Name/"; * @endcode * On Windows this defaults to %APPDATA% (Roaming profile) * On Linux this defaults to ~/.config but can be configured - * @return The base folder for storring config data. + * @return The base folder for storing config data. */ std::string getConfigHome(); /** - * Retrives the base folder for storring cache files. + * Retrives the base folder for storing cache files. * You must add the program name yourself like this: * @code{.cpp} * string data_home = getCacheDir()+"/My Program Name/"; * @endcode * On Windows this defaults to %APPDATALOCAL% * On Linux this defaults to ~/.cache but can be configured - * @return The base folder for storring data that do not need to be backed up. + * @return The base folder for storing data that do not need to be backed up. */ std::string getCacheDir(); @@ -124,18 +132,30 @@ std::string getDocumentsFolder(); /** * The folder where files are downloaded. - * @note Windows: This version is XP compatible and returns the Desktop. Vista and later has a dedicated folder. + * @return Absolute path to the folder where files are downloaded to. + */ +std::string getDownloadFolder(); + +/** + * The folder where files are downloaded. + * @note This is provided for backward compatibility. Use getDownloadFolder instead. * @return Absolute path to the folder where files are downloaded to. */ std::string getDownloadFolder1(); /** - * The folder for storring the user's pictures. + * The folder for storing the user's pictures. * @return Absolute path to the "Picture" folder */ std::string getPicturesFolder(); /** + * This returns the folder that can be used for sharing files with other users on the same system. + * @return Absolute path to the "Public" folder + */ +std::string getPublicFolder(); + +/** * The folder where music is stored * @return Absolute path to the music folder */ @@ -148,17 +168,31 @@ std::string getMusicFolder(); std::string getVideoFolder(); /** - * The base folder for storring saved games. + * A base folder for storing saved games. * You must add the program name to it like this: * @code{.cpp} * string saved_games_folder = sago::getSaveGamesFolder1()+"/My Program Name/"; * @endcode * @note Windows: This is an XP compatible version and returns the path to "My Games" in Documents. Vista and later has an official folder. * @note Linux: XDF does not define a folder for saved games. This will just return the same as GetDataHome() - * @return The folder base folder for storring save games. + * @return The folder base folder for storing save games. */ std::string getSaveGamesFolder1(); +/** + * A base folder for storing saved games. + * You must add the program name to it like this: + * @code{.cpp} + * string saved_games_folder = sago::getSaveGamesFolder2()+"/My Program Name/"; + * @endcode + * @note PlatformFolders provide different folders to for saved games as not all operating systems has support for Saved Games yet. + * It is recommended to pick the highest number (currently getSaveGamesFolder2) at the time your product enters production and stick with it + * @note Windows: This returns the "Saved Games" folder. This folder exist in Vista and later + * @note Linux: XDF does not define a folder for saved games. This will just return the same as GetDataHome() + * @return The folder base folder for storing save games. + */ +std::string getSaveGamesFolder2(); + #ifndef DOXYGEN_SHOULD_SKIP_THIS /** @@ -183,11 +217,15 @@ public: */ std::string getDocumentsFolder() const; /** - * The folder for storring the user's pictures. + * The folder for storing the user's pictures. * @return Absolute path to the "Picture" folder */ std::string getPicturesFolder() const; /** + * Use sago::getPublicFolder() instead! + */ + std::string getPublicFolder() const; + /** * The folder where files are downloaded. * @note Windows: This version is XP compatible and returns the Desktop. Vista and later has a dedicated folder. * @return Absolute path to the folder where files are downloaded to. @@ -204,7 +242,7 @@ public: */ std::string getVideoFolder() const; /** - * The base folder for storring saved games. + * The base folder for storing saved games. * You must add the program name to it like this: * @code{.cpp} * PlatformFolders pf; @@ -212,15 +250,13 @@ public: * @endcode * @note Windows: This is an XP compatible version and returns the path to "My Games" in Documents. Vista and later has an official folder. * @note Linux: XDF does not define a folder for saved games. This will just return the same as GetDataHome() - * @return The folder base folder for storring save games. + * @return The folder base folder for storing save games. */ std::string getSaveGamesFolder1() const; private: PlatformFolders(const PlatformFolders&); PlatformFolders& operator=(const PlatformFolders&); -#if defined(_WIN32) -#elif defined(__APPLE__) -#else +#if !defined(_WIN32) && !defined(__APPLE__) struct PlatformFoldersData; PlatformFoldersData* data; #endif