From 36d6f1d67e45d1a6cef3ae50b58d1db0379dcda8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20B=C3=A1lint=20Misius?= Date: Thu, 19 Jan 2023 18:31:47 +0100 Subject: [PATCH] Preprocessor purge round 18: difficult parts of WIN, LIN, MACOSX, AND --- meson.build | 13 +- src/Config.template.h | 7 +- src/PowderToyFontEditor.cpp | 7 +- src/PowderToySDL.cpp | 7 +- src/common/Platform.cpp | 632 +-------------------------------- src/common/Platform.h | 2 + src/common/PlatformAndroid.cpp | 32 ++ src/common/PlatformDarwin.cpp | 56 +++ src/common/PlatformLinux.cpp | 111 ++++++ src/common/PlatformNull.cpp | 19 + src/common/PlatformPosix.cpp | 198 +++++++++++ src/common/PlatformWindows.cpp | 385 ++++++++++++++++++++ src/common/meson.build | 16 + 13 files changed, 838 insertions(+), 647 deletions(-) create mode 100644 src/common/PlatformAndroid.cpp create mode 100644 src/common/PlatformDarwin.cpp create mode 100644 src/common/PlatformLinux.cpp create mode 100644 src/common/PlatformNull.cpp create mode 100644 src/common/PlatformPosix.cpp create mode 100644 src/common/PlatformWindows.cpp diff --git a/meson.build b/meson.build index 344b52082..61b004e87 100644 --- a/meson.build +++ b/meson.build @@ -37,6 +37,9 @@ host_arch = host_machine.cpu_family() host_platform = host_machine.system() # educated guesses follow, PRs welcome if c_compiler.get_id() in [ 'msvc' ] + if host_platform != 'windows' + error('this seems fishy') + endif host_libc = 'msvc' elif c_compiler.get_id() in [ 'gcc' ] and host_platform == 'windows' host_libc = 'mingw' @@ -49,6 +52,11 @@ elif host_platform in [ 'android' ] host_platform = 'android' host_libc = 'bionic' else + if host_platform != 'linux' + # TODO: maybe use 'default' in place of 'linux', or use something other than host_platform where details such as desktop integration are concerned + warning('host platform is not linux but we will pretend that it is') + host_platform = 'linux' + endif host_libc = 'gnu' endif @@ -326,10 +334,7 @@ is_beta = get_option('beta') is_mod = mod_id > 0 update_server = get_option('update_server') -conf_data.set('LIN', host_platform == 'linux') -conf_data.set('AND', host_platform == 'android') -conf_data.set('WIN', host_platform == 'windows') -conf_data.set('MACOSX', host_platform == 'darwin') +conf_data.set('SET_WINDOW_ICON', host_platform == 'linux' ? 'true' : 'false') conf_data.set('X86', is_x86 ? 'true' : 'false') conf_data.set('BETA', is_beta ? 'true' : 'false') conf_data.set('INSTALL_CHECK', install_check ? 'true' : 'false') diff --git a/src/Config.template.h b/src/Config.template.h index c29a832ce..5d941aad4 100644 --- a/src/Config.template.h +++ b/src/Config.template.h @@ -1,12 +1,7 @@ #pragma once #include -// Boolean macros (defined / not defined), would be great to get rid of them all. -#mesondefine LIN -#mesondefine AND -#mesondefine WIN -#mesondefine MACOSX - +constexpr bool SET_WINDOW_ICON = @SET_WINDOW_ICON@; constexpr bool DEBUG = @DEBUG@; constexpr bool X86 = @X86@; constexpr bool BETA = @BETA@; diff --git a/src/PowderToyFontEditor.cpp b/src/PowderToyFontEditor.cpp index f813e21df..cb4c1e01e 100644 --- a/src/PowderToyFontEditor.cpp +++ b/src/PowderToyFontEditor.cpp @@ -117,9 +117,10 @@ int SDLOpen() } } -#ifdef LIN - WindowIcon(sdl_window); -#endif + if constexpr (SET_WINDOW_ICON) + { + WindowIcon(sdl_window); + } return 0; } diff --git a/src/PowderToySDL.cpp b/src/PowderToySDL.cpp index 15351c434..b2635104a 100644 --- a/src/PowderToySDL.cpp +++ b/src/PowderToySDL.cpp @@ -177,9 +177,10 @@ void SDLOpen() } } -#ifdef LIN - WindowIcon(sdl_window); -#endif + if constexpr (SET_WINDOW_ICON) + { + WindowIcon(sdl_window); + } } void SDLSetScreen(int scale_, bool resizable_, bool fullscreen_, bool altFullscreen_, bool forceIntegerScaling_) diff --git a/src/common/Platform.cpp b/src/common/Platform.cpp index 78d84bebf..d34ce9a6a 100644 --- a/src/common/Platform.cpp +++ b/src/common/Platform.cpp @@ -6,34 +6,6 @@ #include #include #include -#ifdef WIN -# ifndef NOMINMAX -# define NOMINMAX -# endif -# include -# include -# include -# include -# include -# include -# include -#else -# include -# include -# include -# include -#endif -#ifdef MACOSX -# include -# include -# include -#endif -#ifdef LIN -# include "icon_cps.png.h" -# include "icon_exe.png.h" -# include "save.xml.h" -# include "powder.desktop.h" -#endif namespace Platform { @@ -41,194 +13,6 @@ namespace Platform std::string originalCwd; std::string sharedCwd; -ByteString GetCwd() -{ - ByteString cwd; -#if defined(WIN) - wchar_t *cwdPtr = _wgetcwd(NULL, 0); - if (cwdPtr) - { - cwd = WinNarrow(cwdPtr); - } - free(cwdPtr); -#else - char *cwdPtr = getcwd(NULL, 0); - if (cwdPtr) - { - cwd = cwdPtr; - } - free(cwdPtr); -#endif - return cwd; -} - -void OpenURI(ByteString uri) -{ -#if defined(WIN) - if (int(INT_PTR(ShellExecuteW(NULL, NULL, WinWiden(uri).c_str(), NULL, NULL, SW_SHOWNORMAL))) <= 32) - { - fprintf(stderr, "cannot open URI: ShellExecute(...) failed\n"); - } -#elif defined(MACOSX) - if (system(("open \"" + uri + "\"").c_str())) - { - fprintf(stderr, "cannot open URI: system(...) failed\n"); - } -#elif defined(LIN) - if (system(("xdg-open \"" + uri + "\"").c_str())) - { - fprintf(stderr, "cannot open URI: system(...) failed\n"); - } -#else - fprintf(stderr, "cannot open URI: not implemented\n"); -#endif -} - -void Millisleep(long int t) -{ -#ifdef WIN - Sleep(t); -#else - struct timespec s; - s.tv_sec = t / 1000; - s.tv_nsec = (t % 1000) * 10000000; - nanosleep(&s, NULL); -#endif -} - -long unsigned int GetTime() -{ -#ifdef WIN - return GetTickCount(); -#elif defined(MACOSX) - struct timeval s; - gettimeofday(&s, NULL); - return (unsigned int)(s.tv_sec * 1000 + s.tv_usec / 1000); -#else - struct timespec s; - clock_gettime(CLOCK_MONOTONIC, &s); - return s.tv_sec * 1000 + s.tv_nsec / 1000000; -#endif -} - -bool Stat(ByteString filename) -{ -#ifdef WIN - struct _stat s; - if (_stat(filename.c_str(), &s) == 0) -#else - struct stat s; - if (stat(filename.c_str(), &s) == 0) -#endif - { - return true; // Something exists, be it a file, directory, link, etc. - } - else - { - return false; // Doesn't exist - } -} - -bool FileExists(ByteString filename) -{ -#ifdef WIN - struct _stat s; - if (_stat(filename.c_str(), &s) == 0) -#else - struct stat s; - if (stat(filename.c_str(), &s) == 0) -#endif - { - if(s.st_mode & S_IFREG) - { - return true; // Is file - } - else - { - return false; // Is directory or something else - } - } - else - { - return false; // Doesn't exist - } -} - -bool DirectoryExists(ByteString directory) -{ -#ifdef WIN - struct _stat s; - if (_stat(directory.c_str(), &s) == 0) -#else - struct stat s; - if (stat(directory.c_str(), &s) == 0) -#endif - { - if(s.st_mode & S_IFDIR) - { - return true; // Is directory - } - else - { - return false; // Is file or something else - } - } - else - { - return false; // Doesn't exist - } -} - -bool RemoveFile(ByteString filename) -{ -#ifdef WIN - return _wremove(WinWiden(filename).c_str()) == 0; -#else - return remove(filename.c_str()) == 0; -#endif -} - -bool RenameFile(ByteString filename, ByteString newFilename, bool replace) -{ -#ifdef WIN - if (replace) - { - // TODO: we rely on errno but errors from this are available through GetLastError(); fix - return MoveFileExW(WinWiden(filename).c_str(), WinWiden(newFilename).c_str(), MOVEFILE_REPLACE_EXISTING); - } - return _wrename(WinWiden(filename).c_str(), WinWiden(newFilename).c_str()) == 0; -#else - return rename(filename.c_str(), newFilename.c_str()) == 0; -#endif -} - -bool DeleteDirectory(ByteString folder) -{ -#ifdef WIN - return _wrmdir(WinWiden(folder).c_str()) == 0; -#else - return rmdir(folder.c_str()) == 0; -#endif -} - -bool MakeDirectory(ByteString dir) -{ -#ifdef WIN - return _wmkdir(WinWiden(dir).c_str()) == 0; -#else - return mkdir(dir.c_str(), 0755) == 0; -#endif -} - -bool ChangeDir(ByteString toDir) -{ -#ifdef WIN - return _wchdir(WinWiden(toDir).c_str()) == 0; -#else - return chdir(toDir.c_str()) == 0; -#endif -} - // Returns a list of all files in a directory matching a search // search - list of search terms. extensions - list of extensions to also match std::vector DirectorySearch(ByteString directory, ByteString search, std::vector extensions) @@ -237,35 +21,7 @@ std::vector DirectorySearch(ByteString directory, ByteString search, //Normalise directory string, ensure / or \ is present if (!directory.size() || (directory.back() != '/' && directory.back() != '\\')) directory += PATH_SEP; - std::vector directoryList; -#ifdef WIN - struct _wfinddata_t currentFile; - intptr_t findFileHandle; - ByteString fileMatch = directory + "*.*"; - findFileHandle = _wfindfirst(Platform::WinWiden(fileMatch).c_str(), ¤tFile); - if (findFileHandle == -1L) - { - return std::vector(); - } - do - { - directoryList.push_back(Platform::WinNarrow(currentFile.name)); - } - while (_wfindnext(findFileHandle, ¤tFile) == 0); - _findclose(findFileHandle); -#else - struct dirent * directoryEntry; - DIR *directoryHandle = opendir(directory.c_str()); - if (!directoryHandle) - { - return std::vector(); - } - while ((directoryEntry = readdir(directoryHandle))) - { - directoryList.push_back(ByteString(directoryEntry->d_name)); - } - closedir(directoryHandle); -#endif + auto directoryList = DirectoryList(directory); search = search.ToLower(); @@ -295,38 +51,6 @@ std::vector DirectorySearch(ByteString directory, ByteString search, return searchResults; } -#ifdef WIN -ByteString WinNarrow(const std::wstring &source) -{ - int buffer_size = WideCharToMultiByte(CP_UTF8, 0, source.c_str(), source.size(), nullptr, 0, NULL, NULL); - if (!buffer_size) - { - return ""; - } - std::string output(buffer_size, 0); - if (!WideCharToMultiByte(CP_UTF8, 0, source.c_str(), source.size(), &output[0], buffer_size, NULL, NULL)) - { - return ""; - } - return output; -} - -std::wstring WinWiden(const ByteString &source) -{ - int buffer_size = MultiByteToWideChar(CP_UTF8, 0, source.c_str(), source.size(), nullptr, 0); - if (!buffer_size) - { - return L""; - } - std::wstring output(buffer_size, 0); - if (!MultiByteToWideChar(CP_UTF8, 0, source.c_str(), source.size(), &output[0], buffer_size)) - { - return L""; - } - return output; -} -#endif - bool ReadFile(std::vector &fileData, ByteString filename) { std::ifstream f(filename, std::ios::binary); @@ -382,362 +106,8 @@ bool WriteFile(const std::vector &fileData, ByteString filename) return true; } -ByteString ExecutableName() -{ -#ifdef WIN - std::wstring buf(L"?"); - while (true) - { - SetLastError(ERROR_SUCCESS); - if (!GetModuleFileNameW(NULL, &buf[0], DWORD(buf.size()))) - { - std::cerr << "GetModuleFileNameW: " << GetLastError() << std::endl; - return ""; - } - if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) - { - break; - } - buf.resize(buf.size() * 2); - } - return WinNarrow(&buf[0]); // Pass pointer to copy only up to the zero terminator. -#else -# ifdef MACOSX - ByteString firstApproximation("?"); - { - auto bufSize = uint32_t(firstApproximation.size()); - auto ret = _NSGetExecutablePath(&firstApproximation[0], &bufSize); - if (ret == -1) - { - // Buffer not large enough; likely to happen since it's initially a single byte. - firstApproximation.resize(bufSize); - ret = _NSGetExecutablePath(&firstApproximation[0], &bufSize); - } - if (ret != 0) - { - // Can't even get a first approximation. - std::cerr << "_NSGetExecutablePath: " << ret << std::endl; - return ""; - } - } -# else - ByteString firstApproximation("/proc/self/exe"); -# endif - auto rp = std::unique_ptr(realpath(&firstApproximation[0], NULL), std::free); - if (!rp) - { - std::cerr << "realpath: " << errno << std::endl; - return ""; - } - return rp.get(); -#endif -} - -void DoRestart() -{ - ByteString exename = ExecutableName(); - if (exename.length()) - { -#ifdef WIN - int ret = int(INT_PTR(ShellExecuteW(NULL, NULL, WinWiden(exename).c_str(), NULL, NULL, SW_SHOWNORMAL))); - if (ret <= 32) - { - fprintf(stderr, "cannot restart: ShellExecute(...) failed: code %i\n", ret); - } - else - { - exit(0); - } -#elif defined(LIN) || defined(MACOSX) - execl(exename.c_str(), exename.c_str(), NULL); - int ret = errno; - fprintf(stderr, "cannot restart: execl(...) failed: code %i\n", ret); -#endif - } - else - { - fprintf(stderr, "cannot restart: no executable name???\n"); - } - exit(-1); -} - -bool CanUpdate() -{ -#ifdef MACOSX - return false; -#else - return true; -#endif -} - bool CanInstall() { return INSTALL_CHECK; } - -bool Install() -{ - bool ok = true; -#if defined(WIN) - auto deleteKey = [](ByteString path) { - RegDeleteKeyW(HKEY_CURRENT_USER, Platform::WinWiden(path).c_str()); - }; - auto createKey = [](ByteString path, ByteString value, ByteString extraKey = {}, ByteString extraValue = {}) { - auto ok = true; - auto wPath = Platform::WinWiden(path); - auto wValue = Platform::WinWiden(value); - auto wExtraKey = Platform::WinWiden(extraKey); - auto wExtraValue = Platform::WinWiden(extraValue); - HKEY k; - ok = ok && RegCreateKeyExW(HKEY_CURRENT_USER, wPath.c_str(), 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &k, NULL) == ERROR_SUCCESS; - ok = ok && RegSetValueExW(k, NULL, 0, REG_SZ, reinterpret_cast(wValue.c_str()), (wValue.size() + 1) * 2) == ERROR_SUCCESS; - if (wExtraKey.size()) - { - ok = ok && RegSetValueExW(k, wExtraKey.c_str(), 0, REG_SZ, reinterpret_cast(wExtraValue.c_str()), (wExtraValue.size() + 1) * 2) == ERROR_SUCCESS; - } - RegCloseKey(k); - return ok; - }; - - CoInitializeEx(NULL, COINIT_MULTITHREADED); - auto exe = Platform::ExecutableName(); -#ifndef IDI_DOC_ICON - // make this fail so I don't remove #include "resource.h" again and get away with it -# error where muh IDI_DOC_ICON D: -#endif - auto icon = ByteString::Build(exe, ",-", IDI_DOC_ICON); - auto path = Platform::GetCwd(); - auto open = ByteString::Build("\"", exe, "\" ddir \"", path, "\" \"file://%1\""); - auto ptsave = ByteString::Build("\"", exe, "\" ddir \"", path, "\" \"%1\""); - deleteKey("Software\\Classes\\ptsave"); - deleteKey("Software\\Classes\\.cps"); - deleteKey("Software\\Classes\\.stm"); - deleteKey("Software\\Classes\\PowderToySave"); - ok = ok && createKey("Software\\Classes\\ptsave", "Powder Toy Save", "URL Protocol", ""); - ok = ok && createKey("Software\\Classes\\ptsave\\DefaultIcon", icon); - ok = ok && createKey("Software\\Classes\\ptsave\\shell\\open\\command", ptsave); - ok = ok && createKey("Software\\Classes\\.cps", "PowderToySave"); - ok = ok && createKey("Software\\Classes\\.stm", "PowderToySave"); - ok = ok && createKey("Software\\Classes\\PowderToySave", "Powder Toy Save"); - ok = ok && createKey("Software\\Classes\\PowderToySave\\DefaultIcon", icon); - ok = ok && createKey("Software\\Classes\\PowderToySave\\shell\\open\\command", open); - IShellLinkW *shellLink = NULL; - IPersistFile *shellLinkPersist = NULL; - wchar_t programsPath[MAX_PATH]; - ok = ok && SHGetFolderPathW(NULL, CSIDL_PROGRAMS, NULL, SHGFP_TYPE_CURRENT, programsPath) == S_OK; - ok = ok && CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (LPVOID *)&shellLink) == S_OK; - ok = ok && shellLink->SetPath(Platform::WinWiden(exe).c_str()) == S_OK; - ok = ok && shellLink->SetWorkingDirectory(Platform::WinWiden(path).c_str()) == S_OK; - ok = ok && shellLink->SetDescription(Platform::WinWiden(APPNAME).c_str()) == S_OK; - ok = ok && shellLink->QueryInterface(IID_IPersistFile, (LPVOID *)&shellLinkPersist) == S_OK; - ok = ok && shellLinkPersist->Save(Platform::WinWiden(ByteString::Build(Platform::WinNarrow(programsPath), "\\", APPNAME, ".lnk")).c_str(), TRUE) == S_OK; - if (shellLinkPersist) - { - shellLinkPersist->Release(); - } - if (shellLink) - { - shellLink->Release(); - } - CoUninitialize(); -#elif defined(LIN) - auto desktopEscapeString = [](ByteString str) { - ByteString escaped; - for (auto ch : str) - { - auto from = " " "\n" "\t" "\r" "\\"; - auto to = "s" "n" "t" "r" "\\"; - if (auto off = strchr(from, ch)) - { - escaped.append(1, '\\'); - escaped.append(1, to[off - from]); - } - else - { - escaped.append(1, ch); - } - } - return escaped; - }; - auto desktopEscapeExec = [](ByteString str) { - ByteString escaped; - for (auto ch : str) - { - if (strchr(" \t\n\"\'\\><~|&;$*?#()`", ch)) - { - escaped.append(1, '\\'); - } - escaped.append(1, ch); - } - return escaped; - }; - - if (ok) - { - ByteString desktopData(powder_desktop, powder_desktop + powder_desktop_size); - auto exe = Platform::ExecutableName(); - auto path = exe.SplitFromEndBy('/').Before(); - desktopData = desktopData.Substitute("Exec=" + ByteString(APPEXE), "Exec=" + desktopEscapeString(desktopEscapeExec(exe))); - desktopData += ByteString::Build("Path=", desktopEscapeString(path), "\n"); - ByteString file = ByteString::Build(APPVENDOR, "-", APPID, ".desktop"); - ok = ok && Platform::WriteFile(std::vector(desktopData.begin(), desktopData.end()), file); - ok = ok && !system(ByteString::Build("xdg-desktop-menu install ", file).c_str()); - ok = ok && !system(ByteString::Build("xdg-mime default ", file, " application/vnd.powdertoy.save").c_str()); - ok = ok && !system(ByteString::Build("xdg-mime default ", file, " x-scheme-handler/ptsave").c_str()); - Platform::RemoveFile(file); - } - if (ok) - { - ByteString file = ByteString(APPVENDOR) + "-save.xml"; - ok = ok && Platform::WriteFile(std::vector(save_xml, save_xml + save_xml_size), file); - ok = ok && !system(ByteString::Build("xdg-mime install ", file).c_str()); - Platform::RemoveFile(file); - } - if (ok) - { - ByteString file = ByteString(APPVENDOR) + "-cps.png"; - ok = ok && Platform::WriteFile(std::vector(icon_cps_png, icon_cps_png + icon_cps_png_size), file); - ok = ok && !system(ByteString::Build("xdg-icon-resource install --noupdate --context mimetypes --size 64 ", file, " application-vnd.powdertoy.save").c_str()); - Platform::RemoveFile(file); - } - if (ok) - { - ByteString file = ByteString(APPVENDOR) + "-exe.png"; - ok = ok && Platform::WriteFile(std::vector(icon_exe_png, icon_exe_png + icon_exe_png_size), file); - ok = ok && !system(ByteString::Build("xdg-icon-resource install --noupdate --size 64 ", file, " ", APPVENDOR, "-", APPEXE).c_str()); - Platform::RemoveFile(file); - } - if (ok) - { - ok = ok && !system("xdg-icon-resource forceupdate"); - } -#else - ok = false; -#endif - return ok; -} - -bool UpdateStart(const std::vector &data) -{ - ByteString exeName = Platform::ExecutableName(), updName; - - if (!exeName.length()) - return false; - -#ifdef WIN - updName = exeName; - ByteString extension = exeName.substr(exeName.length() - 4); - if (extension == ".exe") - updName = exeName.substr(0, exeName.length() - 4); - updName = updName + "_upd.exe"; - - if (!MoveFile(Platform::WinWiden(exeName).c_str(), Platform::WinWiden(updName).c_str())) - return false; - - if (!WriteFile(data, exeName)) - { - Platform::RemoveFile(exeName); - return false; - } - - if ((uintptr_t)ShellExecute(NULL, L"open", Platform::WinWiden(exeName).c_str(), NULL, NULL, SW_SHOWNORMAL) <= 32) - { - Platform::RemoveFile(exeName); - return false; - } - - return true; -#else - updName = exeName + "-update"; - - if (!WriteFile(data, updName)) - { - RemoveFile(updName); - return false; - } - - if (chmod(updName.c_str(), 0755)) - { - RemoveFile(updName); - return false; - } - - if (!RenameFile(updName, exeName)) - { - RemoveFile(updName); - return false; - } - - execl(exeName.c_str(), "powder-update", NULL); - return false; // execl returned, we failed -#endif -} - -bool UpdateFinish() -{ -#ifdef WIN - ByteString exeName = Platform::ExecutableName(), updName; - int timeout = 5, err; - if constexpr (DEBUG) - { - printf("Update: Current EXE name: %s\n", exeName.c_str()); - } - updName = exeName; - ByteString extension = exeName.substr(exeName.length() - 4); - if (extension == ".exe") - updName = exeName.substr(0, exeName.length() - 4); - updName = updName + "_upd.exe"; - if constexpr (DEBUG) - { - printf("Update: Temp EXE name: %s\n", updName.c_str()); - } - while (!Platform::RemoveFile(updName)) - { - err = GetLastError(); - if (err == ERROR_FILE_NOT_FOUND) - { - if constexpr (DEBUG) - { - printf("Update: Temp file not deleted\n"); - } - // Old versions of powder toy name their update files with _update.exe, delete that upgrade file here - updName = exeName; - ByteString extension = exeName.substr(exeName.length() - 4); - if (extension == ".exe") - updName = exeName.substr(0, exeName.length() - 4); - updName = updName + "_update.exe"; - Platform::RemoveFile(updName); - return true; - } - Sleep(500); - timeout--; - if (timeout <= 0) - { - if constexpr (DEBUG) - { - printf("Update: Delete timeout\n"); - } - return false; - } - } -#endif - return true; -} - -void UpdateCleanup() -{ -#ifdef WIN - UpdateFinish(); -#endif -} - -void SetupCrt() -{ -#ifdef WIN - if constexpr (DEBUG) - { - _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG); - } -#endif -} } diff --git a/src/common/Platform.h b/src/common/Platform.h index 9dd717805..fa1e9be6d 100644 --- a/src/common/Platform.h +++ b/src/common/Platform.h @@ -6,6 +6,7 @@ namespace Platform { ByteString GetCwd(); + ByteString ExecutableNameFirstApprox(); ByteString ExecutableName(); void DoRestart(); @@ -32,6 +33,7 @@ namespace Platform * @return true on success */ bool MakeDirectory(ByteString dir); + std::vector DirectoryList(ByteString directory); std::vector DirectorySearch(ByteString directory, ByteString search, std::vector extensions); bool ReadFile(std::vector &fileData, ByteString filename); diff --git a/src/common/PlatformAndroid.cpp b/src/common/PlatformAndroid.cpp new file mode 100644 index 000000000..034b0ff04 --- /dev/null +++ b/src/common/PlatformAndroid.cpp @@ -0,0 +1,32 @@ +#include "Platform.h" +#include + +namespace Platform +{ +void OpenURI(ByteString uri) +{ + fprintf(stderr, "cannot open URI: not implemented\n"); +} + +long unsigned int GetTime() +{ + struct timespec s; + clock_gettime(CLOCK_MONOTONIC, &s); + return s.tv_sec * 1000 + s.tv_nsec / 1000000; +} + +ByteString ExecutableNameFirstApprox() +{ + return "/proc/self/exe"; +} + +bool CanUpdate() +{ + return false; +} + +bool Install() +{ + return false; +} +} diff --git a/src/common/PlatformDarwin.cpp b/src/common/PlatformDarwin.cpp new file mode 100644 index 000000000..a75be3c38 --- /dev/null +++ b/src/common/PlatformDarwin.cpp @@ -0,0 +1,56 @@ +#include "Platform.h" +#include +#include +#include +#include + +namespace Platform +{ + +void OpenURI(ByteString uri) +{ + if (system(("open \"" + uri + "\"").c_str())) + { + fprintf(stderr, "cannot open URI: system(...) failed\n"); + } +} + +long unsigned int GetTime() +{ + struct timeval s; + gettimeofday(&s, NULL); + return (unsigned int)(s.tv_sec * 1000 + s.tv_usec / 1000); +} + +ByteString ExecutableNameFirstApprox() +{ + ByteString firstApproximation("?"); + { + auto bufSize = uint32_t(firstApproximation.size()); + auto ret = _NSGetExecutablePath(&firstApproximation[0], &bufSize); + if (ret == -1) + { + // Buffer not large enough; likely to happen since it's initially a single byte. + firstApproximation.resize(bufSize); + ret = _NSGetExecutablePath(&firstApproximation[0], &bufSize); + } + if (ret != 0) + { + // Can't even get a first approximation. + std::cerr << "_NSGetExecutablePath: " << ret << std::endl; + return ""; + } + } + return firstApproximation; +} + +bool CanUpdate() +{ + return false; +} + +bool Install() +{ + return false; +} +} diff --git a/src/common/PlatformLinux.cpp b/src/common/PlatformLinux.cpp new file mode 100644 index 000000000..0e6cbf64f --- /dev/null +++ b/src/common/PlatformLinux.cpp @@ -0,0 +1,111 @@ +#include "Platform.h" +#include +#include +#include "icon_cps.png.h" +#include "icon_exe.png.h" +#include "save.xml.h" +#include "powder.desktop.h" + +namespace Platform +{ +void OpenURI(ByteString uri) +{ + if (system(("xdg-open \"" + uri + "\"").c_str())) + { + fprintf(stderr, "cannot open URI: system(...) failed\n"); + } +} + +long unsigned int GetTime() +{ + struct timespec s; + clock_gettime(CLOCK_MONOTONIC, &s); + return s.tv_sec * 1000 + s.tv_nsec / 1000000; +} + +ByteString ExecutableNameFirstApprox() +{ + return "/proc/self/exe"; +} + +bool CanUpdate() +{ + return true; +} + +bool Install() +{ + bool ok = true; + auto desktopEscapeString = [](ByteString str) { + ByteString escaped; + for (auto ch : str) + { + auto from = " " "\n" "\t" "\r" "\\"; + auto to = "s" "n" "t" "r" "\\"; + if (auto off = strchr(from, ch)) + { + escaped.append(1, '\\'); + escaped.append(1, to[off - from]); + } + else + { + escaped.append(1, ch); + } + } + return escaped; + }; + auto desktopEscapeExec = [](ByteString str) { + ByteString escaped; + for (auto ch : str) + { + if (strchr(" \t\n\"\'\\><~|&;$*?#()`", ch)) + { + escaped.append(1, '\\'); + } + escaped.append(1, ch); + } + return escaped; + }; + + if (ok) + { + ByteString desktopData(powder_desktop, powder_desktop + powder_desktop_size); + auto exe = Platform::ExecutableName(); + auto path = exe.SplitFromEndBy('/').Before(); + desktopData = desktopData.Substitute("Exec=" + ByteString(APPEXE), "Exec=" + desktopEscapeString(desktopEscapeExec(exe))); + desktopData += ByteString::Build("Path=", desktopEscapeString(path), "\n"); + ByteString file = ByteString::Build(APPVENDOR, "-", APPID, ".desktop"); + ok = ok && Platform::WriteFile(std::vector(desktopData.begin(), desktopData.end()), file); + ok = ok && !system(ByteString::Build("xdg-desktop-menu install ", file).c_str()); + ok = ok && !system(ByteString::Build("xdg-mime default ", file, " application/vnd.powdertoy.save").c_str()); + ok = ok && !system(ByteString::Build("xdg-mime default ", file, " x-scheme-handler/ptsave").c_str()); + Platform::RemoveFile(file); + } + if (ok) + { + ByteString file = ByteString(APPVENDOR) + "-save.xml"; + ok = ok && Platform::WriteFile(std::vector(save_xml, save_xml + save_xml_size), file); + ok = ok && !system(ByteString::Build("xdg-mime install ", file).c_str()); + Platform::RemoveFile(file); + } + if (ok) + { + ByteString file = ByteString(APPVENDOR) + "-cps.png"; + ok = ok && Platform::WriteFile(std::vector(icon_cps_png, icon_cps_png + icon_cps_png_size), file); + ok = ok && !system(ByteString::Build("xdg-icon-resource install --noupdate --context mimetypes --size 64 ", file, " application-vnd.powdertoy.save").c_str()); + Platform::RemoveFile(file); + } + if (ok) + { + ByteString file = ByteString(APPVENDOR) + "-exe.png"; + ok = ok && Platform::WriteFile(std::vector(icon_exe_png, icon_exe_png + icon_exe_png_size), file); + ok = ok && !system(ByteString::Build("xdg-icon-resource install --noupdate --size 64 ", file, " ", APPVENDOR, "-", APPEXE).c_str()); + Platform::RemoveFile(file); + } + if (ok) + { + ok = ok && !system("xdg-icon-resource forceupdate"); + } + return ok; +} +} diff --git a/src/common/PlatformNull.cpp b/src/common/PlatformNull.cpp new file mode 100644 index 000000000..1f082f369 --- /dev/null +++ b/src/common/PlatformNull.cpp @@ -0,0 +1,19 @@ +#include "Platform.h" + +namespace Platform +{ +void OpenURI(ByteString uri) +{ + fprintf(stderr, "cannot open URI: not implemented\n"); +} + +bool CanUpdate() +{ + return false; +} + +bool Install() +{ + return false; +} +} diff --git a/src/common/PlatformPosix.cpp b/src/common/PlatformPosix.cpp new file mode 100644 index 000000000..23fdbc5ab --- /dev/null +++ b/src/common/PlatformPosix.cpp @@ -0,0 +1,198 @@ +#include "Platform.h" +#include +#include +#include +#include +#include +#include +#include + +namespace Platform +{ +ByteString GetCwd() +{ + ByteString cwd; + char *cwdPtr = getcwd(NULL, 0); + if (cwdPtr) + { + cwd = cwdPtr; + } + free(cwdPtr); + return cwd; +} + +void Millisleep(long int t) +{ + struct timespec s; + s.tv_sec = t / 1000; + s.tv_nsec = (t % 1000) * 10000000; + nanosleep(&s, NULL); +} + +bool Stat(ByteString filename) +{ + struct stat s; + if (stat(filename.c_str(), &s) == 0) + { + return true; // Something exists, be it a file, directory, link, etc. + } + else + { + return false; // Doesn't exist + } +} + +bool FileExists(ByteString filename) +{ + struct stat s; + if (stat(filename.c_str(), &s) == 0) + { + if(s.st_mode & S_IFREG) + { + return true; // Is file + } + else + { + return false; // Is directory or something else + } + } + else + { + return false; // Doesn't exist + } +} + +bool DirectoryExists(ByteString directory) +{ + struct stat s; + if (stat(directory.c_str(), &s) == 0) + { + if(s.st_mode & S_IFDIR) + { + return true; // Is directory + } + else + { + return false; // Is file or something else + } + } + else + { + return false; // Doesn't exist + } +} + +bool RemoveFile(ByteString filename) +{ + return remove(filename.c_str()) == 0; +} + +bool RenameFile(ByteString filename, ByteString newFilename, bool replace) +{ + return rename(filename.c_str(), newFilename.c_str()) == 0; +} + +bool DeleteDirectory(ByteString folder) +{ + return rmdir(folder.c_str()) == 0; +} + +bool MakeDirectory(ByteString dir) +{ + return mkdir(dir.c_str(), 0755) == 0; +} + +bool ChangeDir(ByteString toDir) +{ + return chdir(toDir.c_str()) == 0; +} + +std::vector DirectoryList(ByteString directory) +{ + std::vector directoryList; + struct dirent * directoryEntry; + DIR *directoryHandle = opendir(directory.c_str()); + if (!directoryHandle) + { + return std::vector(); + } + while ((directoryEntry = readdir(directoryHandle))) + { + directoryList.push_back(ByteString(directoryEntry->d_name)); + } + closedir(directoryHandle); + return directoryList; +} + +ByteString ExecutableName() +{ + auto firstApproximation = ExecutableNameFirstApprox(); + auto rp = std::unique_ptr(realpath(&firstApproximation[0], NULL), std::free); + if (!rp) + { + std::cerr << "realpath: " << errno << std::endl; + return ""; + } + return rp.get(); +} + +void DoRestart() +{ + ByteString exename = ExecutableName(); + if (exename.length()) + { + execl(exename.c_str(), exename.c_str(), NULL); + int ret = errno; + fprintf(stderr, "cannot restart: execl(...) failed: code %i\n", ret); + } + else + { + fprintf(stderr, "cannot restart: no executable name???\n"); + } + exit(-1); +} + +bool UpdateStart(const std::vector &data) +{ + ByteString exeName = Platform::ExecutableName(), updName; + + if (!exeName.length()) + return false; + + updName = exeName + "-update"; + + if (!WriteFile(data, updName)) + { + RemoveFile(updName); + return false; + } + + if (chmod(updName.c_str(), 0755)) + { + RemoveFile(updName); + return false; + } + + if (!RenameFile(updName, exeName)) + { + RemoveFile(updName); + return false; + } + + execl(exeName.c_str(), "powder-update", NULL); + return false; // execl returned, we failed +} + +bool UpdateFinish() +{ + return true; +} + +void UpdateCleanup() +{ +} + +void SetupCrt() +{ +} +} diff --git a/src/common/PlatformWindows.cpp b/src/common/PlatformWindows.cpp new file mode 100644 index 000000000..6564768b2 --- /dev/null +++ b/src/common/PlatformWindows.cpp @@ -0,0 +1,385 @@ +#include "Platform.h" +#include "resource.h" +#include +#include +#ifndef NOMINMAX +# define NOMINMAX +#endif +#include +#include +#include +#include +#include + +namespace Platform +{ +ByteString GetCwd() +{ + ByteString cwd; + wchar_t *cwdPtr = _wgetcwd(NULL, 0); + if (cwdPtr) + { + cwd = WinNarrow(cwdPtr); + } + free(cwdPtr); + return cwd; +} + +void OpenURI(ByteString uri) +{ + if (int(INT_PTR(ShellExecuteW(NULL, NULL, WinWiden(uri).c_str(), NULL, NULL, SW_SHOWNORMAL))) <= 32) + { + fprintf(stderr, "cannot open URI: ShellExecute(...) failed\n"); + } +} + +void Millisleep(long int t) +{ + Sleep(t); +} + +long unsigned int GetTime() +{ + return GetTickCount(); +} + +bool Stat(ByteString filename) +{ + struct _stat s; + if (_stat(filename.c_str(), &s) == 0) + { + return true; // Something exists, be it a file, directory, link, etc. + } + else + { + return false; // Doesn't exist + } +} + +bool FileExists(ByteString filename) +{ + struct _stat s; + if (_stat(filename.c_str(), &s) == 0) + { + if(s.st_mode & S_IFREG) + { + return true; // Is file + } + else + { + return false; // Is directory or something else + } + } + else + { + return false; // Doesn't exist + } +} + +bool DirectoryExists(ByteString directory) +{ + struct _stat s; + if (_stat(directory.c_str(), &s) == 0) + { + if(s.st_mode & S_IFDIR) + { + return true; // Is directory + } + else + { + return false; // Is file or something else + } + } + else + { + return false; // Doesn't exist + } +} + +bool RemoveFile(ByteString filename) +{ + return _wremove(WinWiden(filename).c_str()) == 0; +} + +bool RenameFile(ByteString filename, ByteString newFilename, bool replace) +{ + if (replace) + { + // TODO: we rely on errno but errors from this are available through GetLastError(); fix + return MoveFileExW(WinWiden(filename).c_str(), WinWiden(newFilename).c_str(), MOVEFILE_REPLACE_EXISTING); + } + return _wrename(WinWiden(filename).c_str(), WinWiden(newFilename).c_str()) == 0; +} + +bool DeleteDirectory(ByteString folder) +{ + return _wrmdir(WinWiden(folder).c_str()) == 0; +} + +bool MakeDirectory(ByteString dir) +{ + return _wmkdir(WinWiden(dir).c_str()) == 0; +} + +bool ChangeDir(ByteString toDir) +{ + return _wchdir(WinWiden(toDir).c_str()) == 0; +} + +std::vector DirectoryList(ByteString directory) +{ + std::vector directoryList; + struct _wfinddata_t currentFile; + intptr_t findFileHandle; + ByteString fileMatch = directory + "*.*"; + findFileHandle = _wfindfirst(Platform::WinWiden(fileMatch).c_str(), ¤tFile); + if (findFileHandle == -1L) + { + return std::vector(); + } + do + { + directoryList.push_back(Platform::WinNarrow(currentFile.name)); + } + while (_wfindnext(findFileHandle, ¤tFile) == 0); + _findclose(findFileHandle); + return directoryList; +} + +ByteString WinNarrow(const std::wstring &source) +{ + int buffer_size = WideCharToMultiByte(CP_UTF8, 0, source.c_str(), source.size(), nullptr, 0, NULL, NULL); + if (!buffer_size) + { + return ""; + } + std::string output(buffer_size, 0); + if (!WideCharToMultiByte(CP_UTF8, 0, source.c_str(), source.size(), &output[0], buffer_size, NULL, NULL)) + { + return ""; + } + return output; +} + +std::wstring WinWiden(const ByteString &source) +{ + int buffer_size = MultiByteToWideChar(CP_UTF8, 0, source.c_str(), source.size(), nullptr, 0); + if (!buffer_size) + { + return L""; + } + std::wstring output(buffer_size, 0); + if (!MultiByteToWideChar(CP_UTF8, 0, source.c_str(), source.size(), &output[0], buffer_size)) + { + return L""; + } + return output; +} + +ByteString ExecutableName() +{ + std::wstring buf(L"?"); + while (true) + { + SetLastError(ERROR_SUCCESS); + if (!GetModuleFileNameW(NULL, &buf[0], DWORD(buf.size()))) + { + std::cerr << "GetModuleFileNameW: " << GetLastError() << std::endl; + return ""; + } + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + break; + } + buf.resize(buf.size() * 2); + } + return WinNarrow(&buf[0]); // Pass pointer to copy only up to the zero terminator. +} + +void DoRestart() +{ + ByteString exename = ExecutableName(); + if (exename.length()) + { + int ret = int(INT_PTR(ShellExecuteW(NULL, NULL, WinWiden(exename).c_str(), NULL, NULL, SW_SHOWNORMAL))); + if (ret <= 32) + { + fprintf(stderr, "cannot restart: ShellExecute(...) failed: code %i\n", ret); + } + else + { + exit(0); + } + } + else + { + fprintf(stderr, "cannot restart: no executable name???\n"); + } + exit(-1); +} + +bool CanUpdate() +{ + return true; +} + +bool Install() +{ + bool ok = true; + auto deleteKey = [](ByteString path) { + RegDeleteKeyW(HKEY_CURRENT_USER, Platform::WinWiden(path).c_str()); + }; + auto createKey = [](ByteString path, ByteString value, ByteString extraKey = {}, ByteString extraValue = {}) { + auto ok = true; + auto wPath = Platform::WinWiden(path); + auto wValue = Platform::WinWiden(value); + auto wExtraKey = Platform::WinWiden(extraKey); + auto wExtraValue = Platform::WinWiden(extraValue); + HKEY k; + ok = ok && RegCreateKeyExW(HKEY_CURRENT_USER, wPath.c_str(), 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &k, NULL) == ERROR_SUCCESS; + ok = ok && RegSetValueExW(k, NULL, 0, REG_SZ, reinterpret_cast(wValue.c_str()), (wValue.size() + 1) * 2) == ERROR_SUCCESS; + if (wExtraKey.size()) + { + ok = ok && RegSetValueExW(k, wExtraKey.c_str(), 0, REG_SZ, reinterpret_cast(wExtraValue.c_str()), (wExtraValue.size() + 1) * 2) == ERROR_SUCCESS; + } + RegCloseKey(k); + return ok; + }; + + CoInitializeEx(NULL, COINIT_MULTITHREADED); + auto exe = Platform::ExecutableName(); +#ifndef IDI_DOC_ICON + // make this fail so I don't remove #include "resource.h" again and get away with it +# error where muh IDI_DOC_ICON D: +#endif + auto icon = ByteString::Build(exe, ",-", IDI_DOC_ICON); + auto path = Platform::GetCwd(); + auto open = ByteString::Build("\"", exe, "\" ddir \"", path, "\" \"file://%1\""); + auto ptsave = ByteString::Build("\"", exe, "\" ddir \"", path, "\" \"%1\""); + deleteKey("Software\\Classes\\ptsave"); + deleteKey("Software\\Classes\\.cps"); + deleteKey("Software\\Classes\\.stm"); + deleteKey("Software\\Classes\\PowderToySave"); + ok = ok && createKey("Software\\Classes\\ptsave", "Powder Toy Save", "URL Protocol", ""); + ok = ok && createKey("Software\\Classes\\ptsave\\DefaultIcon", icon); + ok = ok && createKey("Software\\Classes\\ptsave\\shell\\open\\command", ptsave); + ok = ok && createKey("Software\\Classes\\.cps", "PowderToySave"); + ok = ok && createKey("Software\\Classes\\.stm", "PowderToySave"); + ok = ok && createKey("Software\\Classes\\PowderToySave", "Powder Toy Save"); + ok = ok && createKey("Software\\Classes\\PowderToySave\\DefaultIcon", icon); + ok = ok && createKey("Software\\Classes\\PowderToySave\\shell\\open\\command", open); + IShellLinkW *shellLink = NULL; + IPersistFile *shellLinkPersist = NULL; + wchar_t programsPath[MAX_PATH]; + ok = ok && SHGetFolderPathW(NULL, CSIDL_PROGRAMS, NULL, SHGFP_TYPE_CURRENT, programsPath) == S_OK; + ok = ok && CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (LPVOID *)&shellLink) == S_OK; + ok = ok && shellLink->SetPath(Platform::WinWiden(exe).c_str()) == S_OK; + ok = ok && shellLink->SetWorkingDirectory(Platform::WinWiden(path).c_str()) == S_OK; + ok = ok && shellLink->SetDescription(Platform::WinWiden(APPNAME).c_str()) == S_OK; + ok = ok && shellLink->QueryInterface(IID_IPersistFile, (LPVOID *)&shellLinkPersist) == S_OK; + ok = ok && shellLinkPersist->Save(Platform::WinWiden(ByteString::Build(Platform::WinNarrow(programsPath), "\\", APPNAME, ".lnk")).c_str(), TRUE) == S_OK; + if (shellLinkPersist) + { + shellLinkPersist->Release(); + } + if (shellLink) + { + shellLink->Release(); + } + CoUninitialize(); + return ok; +} + +bool UpdateStart(const std::vector &data) +{ + ByteString exeName = Platform::ExecutableName(), updName; + + if (!exeName.length()) + return false; + + updName = exeName; + ByteString extension = exeName.substr(exeName.length() - 4); + if (extension == ".exe") + updName = exeName.substr(0, exeName.length() - 4); + updName = updName + "_upd.exe"; + + if (!MoveFile(Platform::WinWiden(exeName).c_str(), Platform::WinWiden(updName).c_str())) + return false; + + if (!WriteFile(data, exeName)) + { + Platform::RemoveFile(exeName); + return false; + } + + if ((uintptr_t)ShellExecute(NULL, L"open", Platform::WinWiden(exeName).c_str(), NULL, NULL, SW_SHOWNORMAL) <= 32) + { + Platform::RemoveFile(exeName); + return false; + } + + return true; +} + +bool UpdateFinish() +{ + ByteString exeName = Platform::ExecutableName(), updName; + int timeout = 5, err; + if constexpr (DEBUG) + { + printf("Update: Current EXE name: %s\n", exeName.c_str()); + } + updName = exeName; + ByteString extension = exeName.substr(exeName.length() - 4); + if (extension == ".exe") + updName = exeName.substr(0, exeName.length() - 4); + updName = updName + "_upd.exe"; + if constexpr (DEBUG) + { + printf("Update: Temp EXE name: %s\n", updName.c_str()); + } + while (!Platform::RemoveFile(updName)) + { + err = GetLastError(); + if (err == ERROR_FILE_NOT_FOUND) + { + if constexpr (DEBUG) + { + printf("Update: Temp file not deleted\n"); + } + // Old versions of powder toy name their update files with _update.exe, delete that upgrade file here + updName = exeName; + ByteString extension = exeName.substr(exeName.length() - 4); + if (extension == ".exe") + updName = exeName.substr(0, exeName.length() - 4); + updName = updName + "_update.exe"; + Platform::RemoveFile(updName); + return true; + } + Sleep(500); + timeout--; + if (timeout <= 0) + { + if constexpr (DEBUG) + { + printf("Update: Delete timeout\n"); + } + return false; + } + } + return true; +} + +void UpdateCleanup() +{ + UpdateFinish(); +} + +void SetupCrt() +{ + if constexpr (DEBUG) + { + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG); + } +} +} diff --git a/src/common/meson.build b/src/common/meson.build index 1228599aa..677ef088c 100644 --- a/src/common/meson.build +++ b/src/common/meson.build @@ -4,3 +4,19 @@ common_files += files( 'tpt-rand.cpp', 'tpt-thread-local.cpp', ) +if host_platform == 'windows' + common_files += files('PlatformWindows.cpp') +elif host_platform == 'darwin' + common_files += files('PlatformDarwin.cpp') + common_files += files('PlatformPosix.cpp') +elif host_platform == 'android' + common_files += files('PlatformAndroid.cpp') + common_files += files('PlatformPosix.cpp') +elif host_platform == 'linux' + # TODO: again, this is more like "posix" than "linux" + common_files += files('PlatformLinux.cpp') + common_files += files('PlatformPosix.cpp') +else + common_files += files('PlatformNull.cpp') + common_files += files('PlatformPosix.cpp') +endif