diff --git a/src/sago/SagoMisc.hpp b/src/sago/SagoMisc.hpp index 55bc097..4c3db91 100644 --- a/src/sago/SagoMisc.hpp +++ b/src/sago/SagoMisc.hpp @@ -29,7 +29,7 @@ SOFTWARE. #include namespace sago { - + /** * Returns a vector with all filenames in a given directory. * PHYSFS must be setup before hand. The directory is relative to the PHYSFS base @@ -37,7 +37,7 @@ namespace sago { * @return A vector with the filenames in the given directory. If empty the directory was empty or did not exist */ std::vector GetFileList(const char* dir); - + /** * Reads an entire file into memory. * PHYSFS must be setup before hand @@ -45,7 +45,7 @@ namespace sago { * @return The content of the file. If empty either the file was empty, did not exist or could not be opened */ std::string GetFileContent(const char* filename); - + /** * Reads an entire file into memory. * PHYSFS must be setup before hand @@ -53,19 +53,19 @@ namespace sago { * @return The content of the file. If empty either the file was empty, did not exist or could not be opened */ inline std::string GetFileContent(const std::string& filename) { return GetFileContent(filename.c_str()); }; - + bool FileExists(const char* filename); - + void WriteFileContent(const char* filename, const std::string& content); - + /** - * This functions convers a string on a best effort basis + * This functions converts a string on a best effort basis * Unlike atol this does NOT cause undefined behavior if out of range * @param c_string A string that may contain a number * @return A number between LONG_MIN and LONG_MAX (both inclusive) */ long int StrToLong(const char* c_string); - + } //namespace sago #endif /* SAGOMISC_HPP */ diff --git a/src/sago/SagoTextBox.cpp b/src/sago/SagoTextBox.cpp index 8a531bf..2f98a20 100644 --- a/src/sago/SagoTextBox.cpp +++ b/src/sago/SagoTextBox.cpp @@ -178,14 +178,14 @@ void SagoTextBox::UpdateCache() { data->renderedText = data->text; } -void SagoTextBox::Draw(SDL_Renderer* target, int x, int y) { +void SagoTextBox::Draw(SDL_Renderer* target, int x, int y, SagoTextField::Alignment alignment ) { if (data->text != data->renderedText) { UpdateCache(); } TTF_Font *font = data->tex->getFontPtr(data->fontName, data->fontSize); int lineSkip = TTF_FontLineSkip(font); for (size_t i = 0; i < data->lines.size(); ++i) { - data->lines[i].Draw(target, x, y+i*lineSkip); + data->lines[i].Draw(target, x, y+i*lineSkip, alignment); } } diff --git a/src/sago/SagoTextBox.hpp b/src/sago/SagoTextBox.hpp index 906fae2..e0b7096 100644 --- a/src/sago/SagoTextBox.hpp +++ b/src/sago/SagoTextBox.hpp @@ -56,7 +56,7 @@ public: */ void SetMaxWidth(int width); const std::string& GetText() const; - void Draw(SDL_Renderer* target, int x, int y); + void Draw(SDL_Renderer* target, int x, int y, SagoTextField::Alignment alignment = SagoTextField::Alignment::left); void UpdateCache(); private: void AppendLineToCache(const std::string& text); diff --git a/src/sago/platform_folders.cpp b/src/sago/platform_folders.cpp index 34e6bae..c5204cd 100644 --- a/src/sago/platform_folders.cpp +++ b/src/sago/platform_folders.cpp @@ -29,22 +29,68 @@ 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) { +namespace sago { +namespace internal { + +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 +101,49 @@ 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 ) ) ) { +} // namesapce internal +} // namespace sago + +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 sago::internal::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 +154,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 +168,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 +181,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; }; @@ -236,14 +255,20 @@ static void PlatformFoldersAddFromFile(const std::string& filename, 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); @@ -282,81 +305,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 @@ -370,14 +400,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(); } @@ -390,5 +428,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/src/sago/platform_folders.h b/src/sago/platform_folders.h index bd6c0d5..646b794 100644 --- a/src/sago/platform_folders.h +++ b/src/sago/platform_folders.h @@ -37,39 +37,51 @@ 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 + #ifdef _WIN32 + std::string win32_utf16_to_utf8(const wchar_t* wstr); + #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 +136,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 +172,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 +221,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 +246,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 +254,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