Unicode. Rename UTF8toUTF16 to U8toU16 and switch back to std::wstring instead of std::u16string.tags/v2.0.0
| @@ -61,10 +61,11 @@ ifdef ARCH_WIN | |||||
| TARGET := libRack.dll | TARGET := libRack.dll | ||||
| SOURCES += dep/osdialog/osdialog_win.c | SOURCES += dep/osdialog/osdialog_win.c | ||||
| LDFLAGS += -municode | |||||
| LDFLAGS += -Wl,--export-all-symbols | LDFLAGS += -Wl,--export-all-symbols | ||||
| LDFLAGS += -Wl,--out-implib,$(TARGET).a | LDFLAGS += -Wl,--out-implib,$(TARGET).a | ||||
| LDFLAGS += -Wl,-Bstatic -Wl,--whole-archive | LDFLAGS += -Wl,-Bstatic -Wl,--whole-archive | ||||
| LDFLAGS += dep/lib/libglew32.a dep/lib/libglfw3.a dep/lib/libjansson.a dep/lib/libspeexdsp.a dep/lib/libsamplerate.a dep/lib/libarchive.a dep/lib/libzstd.a dep/lib/libzip.a dep/lib/libz.a dep/lib/libcurl.a dep/lib/libssl.a dep/lib/libcrypto.a dep/lib/librtaudio.a dep/lib/librtmidi.a | |||||
| LDFLAGS += dep/lib/libglew32.a dep/lib/libglfw3.a dep/lib/libjansson.a dep/lib/libspeexdsp.a dep/lib/libsamplerate.a dep/lib/libarchive.a dep/lib/libzstd.a dep/lib/libzip.a dep/lib/libz.a dep/lib/libcurl.a dep/lib/libssl.a dep/lib/libcrypto.a dep/lib/librtaudio.a dep/lib/librtmidi.a -lstdc++fs | |||||
| LDFLAGS += -Wl,-Bdynamic -Wl,--no-whole-archive | LDFLAGS += -Wl,-Bdynamic -Wl,--no-whole-archive | ||||
| LDFLAGS += -lpthread -lopengl32 -lgdi32 -lws2_32 -lcomdlg32 -lole32 -ldsound -lwinmm -lksuser -lshlwapi -lmfplat -lmfuuid -lwmcodecdspuuid -ldbghelp | LDFLAGS += -lpthread -lopengl32 -lgdi32 -lws2_32 -lcomdlg32 -lole32 -ldsound -lwinmm -lksuser -lshlwapi -lmfplat -lmfuuid -lwmcodecdspuuid -ldbghelp | ||||
| @@ -201,22 +201,22 @@ extern const std::string API_VERSION; | |||||
| #if defined ARCH_WIN | #if defined ARCH_WIN | ||||
| // wchar_t on Windows should be 2 bytes | // wchar_t on Windows should be 2 bytes | ||||
| static_assert(sizeof(char16_t) == sizeof(wchar_t)); | |||||
| static_assert(sizeof(wchar_t) == 2); | |||||
| // Windows C standard functions are ASCII-8 instead of UTF-8, so redirect these functions to wrappers which convert to UTF-8 | // Windows C standard functions are ASCII-8 instead of UTF-8, so redirect these functions to wrappers which convert to UTF-8 | ||||
| #define fopen fopen_utf8 | |||||
| #define remove remove_utf8 | |||||
| #define rename rename_utf8 | |||||
| #define fopen fopen_u8 | |||||
| #define remove remove_u8 | |||||
| #define rename rename_u8 | |||||
| extern "C" { | extern "C" { | ||||
| FILE* fopen_utf8(const char* filename, const char* mode); | |||||
| int remove_utf8(const char* path); | |||||
| int rename_utf8(const char* oldname, const char* newname); | |||||
| FILE* fopen_u8(const char* filename, const char* mode); | |||||
| int remove_u8(const char* path); | |||||
| int rename_u8(const char* oldname, const char* newname); | |||||
| } | } | ||||
| namespace std { | namespace std { | ||||
| using ::fopen_utf8; | |||||
| using ::remove_utf8; | |||||
| using ::rename_utf8; | |||||
| using ::fopen_u8; | |||||
| using ::remove_u8; | |||||
| using ::rename_u8; | |||||
| } | } | ||||
| #endif | #endif | ||||
| @@ -29,7 +29,7 @@ struct PatchManager { | |||||
| /** Loads a patch and nothing else. | /** Loads a patch and nothing else. | ||||
| Returns whether the patch was loaded successfully. | Returns whether the patch was loaded successfully. | ||||
| */ | */ | ||||
| bool load(std::string path); | |||||
| void load(std::string path); | |||||
| /** Loads the template patch. */ | /** Loads the template patch. */ | ||||
| void loadTemplate(); | void loadTemplate(); | ||||
| void loadTemplateDialog(); | void loadTemplateDialog(); | ||||
| @@ -89,9 +89,12 @@ struct CaseInsensitiveCompare { | |||||
| #if defined ARCH_WIN | #if defined ARCH_WIN | ||||
| /** Performs a Unicode string conversion from UTF-16 to UTF-8. | /** Performs a Unicode string conversion from UTF-16 to UTF-8. | ||||
| These are only defined on Windows because the implementation uses Windows' API, and conversion is not needed on other OS's (since everything on Mac and Linux is UTF-8). | These are only defined on Windows because the implementation uses Windows' API, and conversion is not needed on other OS's (since everything on Mac and Linux is UTF-8). | ||||
| std::string and char* variables are considered UTF-8, anywhere in the program. | |||||
| See https://utf8everywhere.org/ for more information about VCV Rack's philosophy on string encoding, especially section 10 for rules VCV follows for handling text on Windows. | |||||
| */ | */ | ||||
| std::string UTF16toUTF8(const std::u16string& s); | |||||
| std::u16string UTF8toUTF16(const std::string& s); | |||||
| std::string U16toU8(const std::wstring& w); | |||||
| std::wstring U8toU16(const std::string& s); | |||||
| #endif | #endif | ||||
| } // namespace string | } // namespace string | ||||
| @@ -69,11 +69,19 @@ The launched process will continue running if the current process is closed. | |||||
| */ | */ | ||||
| void runProcessDetached(const std::string& path); | void runProcessDetached(const std::string& path); | ||||
| std::string getOperatingSystemInfo(); | std::string getOperatingSystemInfo(); | ||||
| /** Extracts an archive into a folder. | |||||
| /** Compresses the contents of a folder (recursively) to an archive. | |||||
| Currently supports the "ustar zstd" format (.tar.zstd) | Currently supports the "ustar zstd" format (.tar.zstd) | ||||
| An equivalent shell command is | |||||
| tar -cf archivePath --zstd -C folderPath . | |||||
| */ | */ | ||||
| void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::path& folderPath); | |||||
| void archiveFolder(const filesystem::path& archivePath, const filesystem::path& folderPath); | void archiveFolder(const filesystem::path& archivePath, const filesystem::path& folderPath); | ||||
| /** Extracts an archive into a folder. | |||||
| An equivalent shell command is | |||||
| tar -xf archivePath --zstd -C folderPath | |||||
| */ | |||||
| void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::path& folderPath); | |||||
| } // namespace system | } // namespace system | ||||
| @@ -61,12 +61,12 @@ static void initSystemDir() { | |||||
| #endif | #endif | ||||
| #if defined ARCH_WIN | #if defined ARCH_WIN | ||||
| // Get path to executable | // Get path to executable | ||||
| char16_t moduleBufU16[MAX_PATH] = u""; | |||||
| DWORD length = GetModuleFileNameW(NULL, (wchar_t*) moduleBufU16, LENGTHOF(moduleBufU16)); | |||||
| wchar_t moduleBufW[MAX_PATH] = L""; | |||||
| DWORD length = GetModuleFileNameW(NULL, moduleBufW, LENGTHOF(moduleBufW)); | |||||
| assert(length > 0); | assert(length > 0); | ||||
| // Get folder of executable | // Get folder of executable | ||||
| PathRemoveFileSpecW((wchar_t*) moduleBufU16); | |||||
| systemDir = string::UTF16toUTF8(moduleBufU16); | |||||
| PathRemoveFileSpecW(moduleBufW); | |||||
| systemDir = string::U16toU8(moduleBufW); | |||||
| #endif | #endif | ||||
| #if defined ARCH_LIN | #if defined ARCH_LIN | ||||
| // Use the current working directory as the default path on Linux. | // Use the current working directory as the default path on Linux. | ||||
| @@ -86,10 +86,10 @@ static void initUserDir() { | |||||
| #if defined ARCH_WIN | #if defined ARCH_WIN | ||||
| // Get "My Documents" folder | // Get "My Documents" folder | ||||
| char16_t documentsBufU16[MAX_PATH] = u"."; | |||||
| HRESULT result = SHGetFolderPathW(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, (wchar_t*) documentsBufU16); | |||||
| wchar_t documentsBufW[MAX_PATH] = L"."; | |||||
| HRESULT result = SHGetFolderPathW(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, documentsBufW); | |||||
| assert(result == S_OK); | assert(result == S_OK); | ||||
| userDir = string::UTF16toUTF8(documentsBufU16); | |||||
| userDir = string::U16toU8(documentsBufW); | |||||
| userDir += "/Rack"; | userDir += "/Rack"; | ||||
| #endif | #endif | ||||
| #if defined ARCH_MAC | #if defined ARCH_MAC | ||||
| @@ -27,16 +27,16 @@ const std::string API_VERSION = "2"; | |||||
| #if defined ARCH_WIN | #if defined ARCH_WIN | ||||
| #include <windows.h> | #include <windows.h> | ||||
| FILE* fopen_utf8(const char* filename, const char* mode) { | |||||
| return _wfopen((wchar_t*) rack::string::UTF8toUTF16(filename).c_str(), (wchar_t*) rack::string::UTF8toUTF16(mode).c_str()); | |||||
| FILE* fopen_u8(const char* filename, const char* mode) { | |||||
| return _wfopen(rack::string::U8toU16(filename).c_str(), rack::string::U8toU16(mode).c_str()); | |||||
| } | } | ||||
| int remove_utf8(const char* path) { | |||||
| return _wremove((wchar_t*) rack::string::UTF8toUTF16(path).c_str()); | |||||
| int remove_u8(const char* path) { | |||||
| return _wremove(rack::string::U8toU16(path).c_str()); | |||||
| } | } | ||||
| int rename_utf8(const char* oldname, const char* newname) { | |||||
| return _wrename((wchar_t*) rack::string::UTF8toUTF16(oldname).c_str(), (wchar_t*) rack::string::UTF8toUTF16(newname).c_str()); | |||||
| int rename_u8(const char* oldname, const char* newname) { | |||||
| return _wrename(rack::string::U8toU16(oldname).c_str(), rack::string::U8toU16(newname).c_str()); | |||||
| } | } | ||||
| #endif | #endif | ||||
| @@ -55,17 +55,24 @@ void PatchManager::save(std::string path) { | |||||
| INFO("Saving patch %s", path.c_str()); | INFO("Saving patch %s", path.c_str()); | ||||
| saveAutosave(); | saveAutosave(); | ||||
| system::archiveFolder(path, asset::autosavePath); | |||||
| system::archiveFolder(filesystem::u8path(path), asset::autosavePath); | |||||
| } | } | ||||
| void PatchManager::saveDialog() { | void PatchManager::saveDialog() { | ||||
| if (path == "") { | if (path == "") { | ||||
| saveAsDialog(); | saveAsDialog(); | ||||
| return; | |||||
| } | } | ||||
| else { | |||||
| try { | |||||
| save(path); | save(path); | ||||
| APP->history->setSaved(); | |||||
| } | } | ||||
| catch (Exception& e) { | |||||
| WARN("Could not save patch: %s", e.what()); | |||||
| return; | |||||
| } | |||||
| APP->history->setSaved(); | |||||
| } | } | ||||
| void PatchManager::saveAsDialog() { | void PatchManager::saveAsDialog() { | ||||
| @@ -100,7 +107,14 @@ void PatchManager::saveAsDialog() { | |||||
| path += ".vcv"; | path += ".vcv"; | ||||
| } | } | ||||
| save(path); | |||||
| try { | |||||
| save(path); | |||||
| } | |||||
| catch (Exception& e) { | |||||
| WARN("Could not save patch: %s", e.what()); | |||||
| return; | |||||
| } | |||||
| this->path = path; | this->path = path; | ||||
| APP->history->setSaved(); | APP->history->setSaved(); | ||||
| pushRecentPath(path); | pushRecentPath(path); | ||||
| @@ -111,7 +125,13 @@ void PatchManager::saveTemplateDialog() { | |||||
| if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Overwrite template patch?")) | if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Overwrite template patch?")) | ||||
| return; | return; | ||||
| save(asset::templatePath); | |||||
| try { | |||||
| save(asset::templatePath); | |||||
| } | |||||
| catch (Exception& e) { | |||||
| WARN("Could not save template patch: %s", e.what()); | |||||
| return; | |||||
| } | |||||
| } | } | ||||
| void PatchManager::saveAutosave() { | void PatchManager::saveAutosave() { | ||||
| @@ -138,28 +158,35 @@ void PatchManager::saveAutosave() { | |||||
| system::moveFile(tmpPath, patchPath); | system::moveFile(tmpPath, patchPath); | ||||
| } | } | ||||
| bool PatchManager::load(std::string path) { | |||||
| void PatchManager::load(std::string path) { | |||||
| INFO("Loading patch %s", path.c_str()); | INFO("Loading patch %s", path.c_str()); | ||||
| filesystem::remove_all(asset::autosavePath); | filesystem::remove_all(asset::autosavePath); | ||||
| filesystem::create_directories(asset::autosavePath); | filesystem::create_directories(asset::autosavePath); | ||||
| system::unarchiveToFolder(path, asset::autosavePath); | |||||
| system::unarchiveToFolder(filesystem::u8path(path), asset::autosavePath); | |||||
| loadAutosave(); | loadAutosave(); | ||||
| return true; | |||||
| } | } | ||||
| void PatchManager::loadTemplate() { | void PatchManager::loadTemplate() { | ||||
| this->path = ""; | this->path = ""; | ||||
| APP->history->setSaved(); | APP->history->setSaved(); | ||||
| if (load(asset::templatePath)) { | |||||
| try { | |||||
| load(asset::templatePath); | |||||
| return; | return; | ||||
| } | } | ||||
| catch (Exception& e) { | |||||
| INFO("Could not load user template patch, attempting system template patch: %s", e.what()); | |||||
| } | |||||
| if (load(asset::system("template.vcv"))) { | |||||
| try { | |||||
| load(asset::system("template.vcv")); | |||||
| return; | return; | ||||
| } | } | ||||
| catch (Exception& e) { | |||||
| WARN("Could not load system template patch, clearing rack: %s", e.what()); | |||||
| } | |||||
| clear(); | clear(); | ||||
| } | } | ||||
| @@ -199,9 +226,14 @@ void PatchManager::loadAutosave() { | |||||
| } | } | ||||
| void PatchManager::loadAction(std::string path) { | void PatchManager::loadAction(std::string path) { | ||||
| if (!load(path)) { | |||||
| try { | |||||
| load(path); | |||||
| } | |||||
| catch (Exception& e) { | |||||
| WARN("Could not load patch: %s", e.what()); | |||||
| return; | return; | ||||
| } | } | ||||
| this->path = path; | this->path = path; | ||||
| APP->history->setSaved(); | APP->history->setSaved(); | ||||
| pushRecentPath(path); | pushRecentPath(path); | ||||
| @@ -249,7 +281,14 @@ void PatchManager::revertDialog() { | |||||
| if (!promptClear("Revert patch to the last saved state?")) | if (!promptClear("Revert patch to the last saved state?")) | ||||
| return; | return; | ||||
| load(path); | |||||
| try { | |||||
| load(path); | |||||
| } | |||||
| catch (Exception& e) { | |||||
| WARN("Could not load patch: %s", e.what()); | |||||
| return; | |||||
| } | |||||
| APP->history->setSaved(); | APP->history->setSaved(); | ||||
| } | } | ||||
| @@ -46,8 +46,8 @@ namespace plugin { | |||||
| static void* loadLibrary(std::string libraryPath) { | static void* loadLibrary(std::string libraryPath) { | ||||
| #if defined ARCH_WIN | #if defined ARCH_WIN | ||||
| SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); | SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); | ||||
| std::u16string libraryFilenameU16 = string::UTF8toUTF16(libraryPath); | |||||
| HINSTANCE handle = LoadLibraryW((wchar_t*) libraryFilenameU16.c_str()); | |||||
| std::wstring libraryFilenameW = string::U8toU16(libraryPath); | |||||
| HINSTANCE handle = LoadLibraryW(libraryFilenameW.c_str()); | |||||
| SetErrorMode(0); | SetErrorMode(0); | ||||
| if (!handle) { | if (!handle) { | ||||
| int error = GetLastError(); | int error = GetLastError(); | ||||
| @@ -127,11 +127,11 @@ std::string absolutePath(const std::string& path) { | |||||
| if (absPathC) | if (absPathC) | ||||
| return absPathC; | return absPathC; | ||||
| #elif defined ARCH_WIN | #elif defined ARCH_WIN | ||||
| std::u16string pathU16 = UTF8toUTF16(path); | |||||
| char16_t buf[PATH_MAX]; | |||||
| char16_t* absPathC = (char16_t*) _wfullpath((wchar_t*) buf, (wchar_t*) pathU16.c_str(), PATH_MAX); | |||||
| std::wstring pathW = U8toU16(path); | |||||
| wchar_t buf[PATH_MAX]; | |||||
| wchar_t* absPathC = _wfullpath(buf, pathW.c_str(), PATH_MAX); | |||||
| if (absPathC) | if (absPathC) | ||||
| return UTF16toUTF8(absPathC); | |||||
| return U16toU8(absPathC); | |||||
| #endif | #endif | ||||
| return ""; | return ""; | ||||
| } | } | ||||
| @@ -290,33 +290,33 @@ bool CaseInsensitiveCompare::operator()(const std::string& a, const std::string& | |||||
| #if defined ARCH_WIN | #if defined ARCH_WIN | ||||
| std::string UTF16toUTF8(const std::u16string& sU16) { | |||||
| if (sU16.empty()) | |||||
| std::string U16toU8(const std::wstring& w) { | |||||
| if (w.empty()) | |||||
| return ""; | return ""; | ||||
| // Compute length of output buffer | // Compute length of output buffer | ||||
| int len = WideCharToMultiByte(CP_UTF8, 0, (wchar_t*) &sU16[0], sU16.size(), NULL, 0, NULL, NULL); | |||||
| int len = WideCharToMultiByte(CP_UTF8, 0, &w[0], w.size(), NULL, 0, NULL, NULL); | |||||
| assert(len > 0); | assert(len > 0); | ||||
| std::string s; | std::string s; | ||||
| // Allocate enough space for null character | // Allocate enough space for null character | ||||
| s.resize(len); | s.resize(len); | ||||
| len = WideCharToMultiByte(CP_UTF8, 0, (wchar_t*) &sU16[0], sU16.size(), &s[0], len, 0, 0); | |||||
| len = WideCharToMultiByte(CP_UTF8, 0, &w[0], w.size(), &s[0], len, 0, 0); | |||||
| assert(len > 0); | assert(len > 0); | ||||
| return s; | return s; | ||||
| } | } | ||||
| std::u16string UTF8toUTF16(const std::string& s) { | |||||
| std::wstring U8toU16(const std::string& s) { | |||||
| if (s.empty()) | if (s.empty()) | ||||
| return u""; | |||||
| return L""; | |||||
| // Compute length of output buffer | // Compute length of output buffer | ||||
| int len = MultiByteToWideChar(CP_UTF8, 0, &s[0], s.size(), NULL, 0); | int len = MultiByteToWideChar(CP_UTF8, 0, &s[0], s.size(), NULL, 0); | ||||
| assert(len > 0); | assert(len > 0); | ||||
| std::u16string sU16; | |||||
| std::wstring w; | |||||
| // Allocate enough space for null character | // Allocate enough space for null character | ||||
| sU16.resize(len); | |||||
| len = MultiByteToWideChar(CP_UTF8, 0, &s[0], s.size(), (wchar_t*) &sU16[0], len); | |||||
| w.resize(len); | |||||
| len = MultiByteToWideChar(CP_UTF8, 0, &s[0], s.size(), &w[0], len); | |||||
| assert(len > 0); | assert(len > 0); | ||||
| return sU16; | |||||
| return w; | |||||
| } | } | ||||
| #endif | #endif | ||||
| @@ -132,8 +132,8 @@ void copyFile(const std::string& srcPath, const std::string& destPath) { | |||||
| void createDirectory(const std::string& path) { | void createDirectory(const std::string& path) { | ||||
| #if defined ARCH_WIN | #if defined ARCH_WIN | ||||
| std::u16string pathU16 = string::UTF8toUTF16(path); | |||||
| _wmkdir((wchar_t*) pathU16.c_str()); | |||||
| std::wstring pathW = string::U8toU16(path); | |||||
| _wmkdir(pathW.c_str()); | |||||
| #else | #else | ||||
| mkdir(path.c_str(), 0755); | mkdir(path.c_str(), 0755); | ||||
| #endif | #endif | ||||
| @@ -152,8 +152,8 @@ void createDirectories(const std::string& path) { | |||||
| void removeDirectory(const std::string& path) { | void removeDirectory(const std::string& path) { | ||||
| #if defined ARCH_WIN | #if defined ARCH_WIN | ||||
| std::u16string pathU16 = string::UTF8toUTF16(path); | |||||
| _wrmdir((wchar_t*) pathU16.c_str()); | |||||
| std::wstring pathW = string::U8toU16(path); | |||||
| _wrmdir(pathW.c_str()); | |||||
| #else | #else | ||||
| rmdir(path.c_str()); | rmdir(path.c_str()); | ||||
| #endif | #endif | ||||
| @@ -172,9 +172,9 @@ void removeDirectories(const std::string& path) { | |||||
| std::string getWorkingDirectory() { | std::string getWorkingDirectory() { | ||||
| #if defined ARCH_WIN | #if defined ARCH_WIN | ||||
| char16_t buf[4096] = u""; | |||||
| GetCurrentDirectory(sizeof(buf), (wchar_t*) buf); | |||||
| return string::UTF16toUTF8(buf); | |||||
| wchar_t buf[4096] = L""; | |||||
| GetCurrentDirectory(sizeof(buf), buf); | |||||
| return string::U16toU8(buf); | |||||
| #else | #else | ||||
| char buf[4096] = ""; | char buf[4096] = ""; | ||||
| getcwd(buf, sizeof(buf)); | getcwd(buf, sizeof(buf)); | ||||
| @@ -185,8 +185,8 @@ std::string getWorkingDirectory() { | |||||
| void setWorkingDirectory(const std::string& path) { | void setWorkingDirectory(const std::string& path) { | ||||
| #if defined ARCH_WIN | #if defined ARCH_WIN | ||||
| std::u16string pathU16 = string::UTF8toUTF16(path); | |||||
| SetCurrentDirectory((wchar_t*) pathU16.c_str()); | |||||
| std::wstring pathW = string::U8toU16(path); | |||||
| SetCurrentDirectory(pathW.c_str()); | |||||
| #else | #else | ||||
| chdir(path.c_str()); | chdir(path.c_str()); | ||||
| #endif | #endif | ||||
| @@ -303,8 +303,8 @@ void openBrowser(const std::string& url) { | |||||
| std::system(command.c_str()); | std::system(command.c_str()); | ||||
| #endif | #endif | ||||
| #if defined ARCH_WIN | #if defined ARCH_WIN | ||||
| std::u16string urlW = string::UTF8toUTF16(url); | |||||
| ShellExecuteW(NULL, L"open", (wchar_t*) urlW.c_str(), NULL, NULL, SW_SHOWDEFAULT); | |||||
| std::wstring urlW = string::U8toU16(url); | |||||
| ShellExecuteW(NULL, L"open", urlW.c_str(), NULL, NULL, SW_SHOWDEFAULT); | |||||
| #endif | #endif | ||||
| } | } | ||||
| @@ -319,8 +319,8 @@ void openFolder(const std::string& path) { | |||||
| std::system(command.c_str()); | std::system(command.c_str()); | ||||
| #endif | #endif | ||||
| #if defined ARCH_WIN | #if defined ARCH_WIN | ||||
| std::u16string pathU16 = string::UTF8toUTF16(path); | |||||
| ShellExecuteW(NULL, L"explore", (wchar_t*) pathU16.c_str(), NULL, NULL, SW_SHOWDEFAULT); | |||||
| std::wstring pathW = string::U8toU16(path); | |||||
| ShellExecuteW(NULL, L"explore", pathW.c_str(), NULL, NULL, SW_SHOWDEFAULT); | |||||
| #endif | #endif | ||||
| } | } | ||||
| @@ -332,8 +332,8 @@ void runProcessDetached(const std::string& path) { | |||||
| shExInfo.cbSize = sizeof(shExInfo); | shExInfo.cbSize = sizeof(shExInfo); | ||||
| shExInfo.lpVerb = L"runas"; | shExInfo.lpVerb = L"runas"; | ||||
| std::u16string pathU16 = string::UTF8toUTF16(path); | |||||
| shExInfo.lpFile = (wchar_t*) pathU16.c_str(); | |||||
| std::wstring pathW = string::U8toU16(path); | |||||
| shExInfo.lpFile = pathW.c_str(); | |||||
| shExInfo.nShow = SW_SHOW; | shExInfo.nShow = SW_SHOW; | ||||
| if (ShellExecuteExW(&shExInfo)) { | if (ShellExecuteExW(&shExInfo)) { | ||||
| @@ -430,6 +430,104 @@ int unzipToFolder(const std::string& zipPath, const std::string& dir) { | |||||
| } | } | ||||
| /** Behaves like `std::filesystem::relative()`. | |||||
| Limitation: `p` must be a descendant of `base`. Doesn't support adding `../` to the return path. | |||||
| */ | |||||
| static filesystem::path getRelativePath(filesystem::path p, filesystem::path base = filesystem::current_path()) { | |||||
| p = filesystem::absolute(p); | |||||
| base = filesystem::absolute(base); | |||||
| std::string pStr = p.generic_u8string(); | |||||
| std::string baseStr = base.generic_u8string(); | |||||
| if (pStr.size() < baseStr.size()) | |||||
| throw Exception("getRelativePath() error: path is shorter than base"); | |||||
| if (!std::equal(baseStr.begin(), baseStr.end(), pStr.begin())) | |||||
| throw Exception("getRelativePath() error: path does not begin with base"); | |||||
| // If p == base, this correctly returns "." | |||||
| return "." + std::string(pStr.begin() + baseStr.size(), pStr.end()); | |||||
| } | |||||
| void archiveFolder(const filesystem::path& archivePath, const filesystem::path& folderPath){ | |||||
| // Based on minitar.c create() in libarchive examples | |||||
| int r; | |||||
| // Open archive for writing | |||||
| struct archive* a = archive_write_new(); | |||||
| DEFER({archive_write_free(a);}); | |||||
| archive_write_set_format_ustar(a); | |||||
| archive_write_add_filter_zstd(a); | |||||
| #if defined ARCH_WIN | |||||
| r = archive_write_open_filename_w(a, archivePath.generic_wstring().c_str()); | |||||
| #else | |||||
| r = archive_write_open_filename(a, archivePath.generic_u8string().c_str()); | |||||
| #endif | |||||
| if (r < ARCHIVE_OK) | |||||
| throw Exception(string::f("archiveFolder() could not open archive %s for writing: %s", archivePath.generic_u8string().c_str(), archive_error_string(a))); | |||||
| DEFER({archive_write_close(a);}); | |||||
| // Open folder for reading | |||||
| struct archive* disk = archive_read_disk_new(); | |||||
| DEFER({archive_read_free(disk);}); | |||||
| #if defined ARCH_WIN | |||||
| r = archive_read_disk_open_w(disk, folderPath.generic_wstring().c_str()); | |||||
| #else | |||||
| r = archive_read_disk_open(disk, folderPath.generic_u8string().c_str()); | |||||
| #endif | |||||
| if (r < ARCHIVE_OK) | |||||
| throw Exception(string::f("archiveFolder() could not open folder %s for reading: %s", folderPath.generic_u8string().c_str(), archive_error_string(a))); | |||||
| DEFER({archive_read_close(a);}); | |||||
| // Iterate folder | |||||
| for (;;) { | |||||
| struct archive_entry* entry = archive_entry_new(); | |||||
| DEFER({archive_entry_free(entry);}); | |||||
| r = archive_read_next_header2(disk, entry); | |||||
| if (r == ARCHIVE_EOF) | |||||
| break; | |||||
| if (r < ARCHIVE_OK) | |||||
| throw Exception(string::f("archiveFolder() could not get next entry from archive: %s", archive_error_string(disk))); | |||||
| // Recurse dirs | |||||
| archive_read_disk_descend(disk); | |||||
| // Convert absolute path to relative path | |||||
| filesystem::path entryPath = | |||||
| #if defined ARCH_WIN | |||||
| archive_entry_pathname_w(entry); | |||||
| #else | |||||
| archive_entry_pathname(entry); | |||||
| #endif | |||||
| entryPath = getRelativePath(entryPath, folderPath); | |||||
| #if defined ARCH_WIN | |||||
| archive_entry_copy_pathname_w(entry, entryPath.generic_wstring().c_str()); | |||||
| #else | |||||
| archive_entry_set_pathname(entry, entryPath.generic_u8string().c_str()); | |||||
| #endif | |||||
| // Write file to archive | |||||
| r = archive_write_header(a, entry); | |||||
| if (r < ARCHIVE_OK) | |||||
| throw Exception(string::f("archiveFolder() could not write entry to archive: %s", archive_error_string(a))); | |||||
| // Manually copy data | |||||
| #if defined ARCH_WIN | |||||
| filesystem::path entrySourcePath = archive_entry_sourcepath_w(entry); | |||||
| #else | |||||
| filesystem::path entrySourcePath = archive_entry_sourcepath(entry); | |||||
| #endif | |||||
| FILE* f = std::fopen(entrySourcePath.generic_u8string().c_str(), "rb"); | |||||
| DEFER({std::fclose(f);}); | |||||
| char buf[1 << 14]; | |||||
| ssize_t len; | |||||
| while ((len = std::fread(buf, 1, sizeof(buf), f)) > 0) { | |||||
| archive_write_data(a, buf, len); | |||||
| } | |||||
| } | |||||
| } | |||||
| void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::path& folderPath) { | void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::path& folderPath) { | ||||
| // Based on minitar.c extract() in libarchive examples | // Based on minitar.c extract() in libarchive examples | ||||
| int r; | int r; | ||||
| @@ -441,10 +539,14 @@ void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::pa | |||||
| // archive_read_support_filter_all(a); | // archive_read_support_filter_all(a); | ||||
| archive_read_support_format_tar(a); | archive_read_support_format_tar(a); | ||||
| // archive_read_support_format_all(a); | // archive_read_support_format_all(a); | ||||
| // TODO Fix unicode filenames on Windows? | |||||
| r = archive_read_open_filename(a, archivePath.c_str(), 1 << 14); | |||||
| if (r != ARCHIVE_OK) | |||||
| throw Exception(string::f("unzipToFolder() could not open archive %s: %s", archivePath.c_str(), archive_error_string(a))); | |||||
| DEBUG("opening %s %s", archivePath.generic_string().c_str(), archivePath.string().c_str()); | |||||
| #if defined ARCH_WIN | |||||
| r = archive_read_open_filename_w(a, archivePath.generic_wstring().c_str(), 1 << 14); | |||||
| #else | |||||
| r = archive_read_open_filename(a, archivePath.generic_u8string().c_str(), 1 << 14); | |||||
| #endif | |||||
| if (r < ARCHIVE_OK) | |||||
| throw Exception(string::f("unzipToFolder() could not open archive %s: %s", archivePath.generic_u8string().c_str(), archive_error_string(a))); | |||||
| DEFER({archive_read_close(a);}); | DEFER({archive_read_close(a);}); | ||||
| // Open folder for writing | // Open folder for writing | ||||
| @@ -465,12 +567,16 @@ void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::pa | |||||
| if (r < ARCHIVE_OK) | if (r < ARCHIVE_OK) | ||||
| throw Exception(string::f("unzipToFolder() could not read entry from archive: %s", archive_error_string(a))); | throw Exception(string::f("unzipToFolder() could not read entry from archive: %s", archive_error_string(a))); | ||||
| // Set pathname relative to folderPath | |||||
| filesystem::path entryPath = archive_entry_pathname(entry); | |||||
| // Convert relative pathname to absolute based on folderPath | |||||
| filesystem::path entryPath = filesystem::u8path(archive_entry_pathname(entry)); | |||||
| if (!entryPath.is_relative()) | if (!entryPath.is_relative()) | ||||
| throw Exception(string::f("unzipToFolder() does not support absolute paths: %s", entryPath.c_str())); | |||||
| throw Exception(string::f("unzipToFolder() does not support absolute paths: %s", entryPath.generic_u8string().c_str())); | |||||
| entryPath = filesystem::absolute(entryPath, folderPath); | entryPath = filesystem::absolute(entryPath, folderPath); | ||||
| archive_entry_set_pathname(entry, entryPath.c_str()); | |||||
| #if defined ARCH_WIN | |||||
| archive_entry_copy_pathname_w(entry, entryPath.generic_wstring().c_str()); | |||||
| #else | |||||
| archive_entry_set_pathname(entry, entryPath.generic_u8string().c_str()); | |||||
| #endif | |||||
| // Write entry to disk | // Write entry to disk | ||||
| r = archive_write_header(disk, entry); | r = archive_write_header(disk, entry); | ||||
| @@ -503,81 +609,5 @@ void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::pa | |||||
| } | } | ||||
| /** Behaves like `std::filesystem::relative()`. | |||||
| Limitation: `p` must be a descendant of `base`. Doesn't support adding `../` to the return path. | |||||
| */ | |||||
| static filesystem::path getRelativePath(filesystem::path p, filesystem::path base = filesystem::current_path()) { | |||||
| p = filesystem::absolute(p); | |||||
| base = filesystem::absolute(base); | |||||
| std::string pStr = p; | |||||
| std::string baseStr = base; | |||||
| if (pStr.size() < baseStr.size()) | |||||
| throw Exception("getRelativePath() error: path is shorter than base"); | |||||
| if (!std::equal(baseStr.begin(), baseStr.end(), pStr.begin())) | |||||
| throw Exception("getRelativePath() error: path does not begin with base"); | |||||
| // If p == base, this correctly returns "." | |||||
| return "." + std::string(pStr.begin() + baseStr.size(), pStr.end()); | |||||
| } | |||||
| void archiveFolder(const filesystem::path& archivePath, const filesystem::path& folderPath){ | |||||
| // Based on minitar.c create() in libarchive examples | |||||
| int r; | |||||
| // Open archive for writing | |||||
| struct archive* a = archive_write_new(); | |||||
| DEFER({archive_write_free(a);}); | |||||
| archive_write_set_format_ustar(a); | |||||
| archive_write_add_filter_zstd(a); | |||||
| r = archive_write_open_filename(a, archivePath.c_str()); | |||||
| if (r != ARCHIVE_OK) | |||||
| throw Exception(string::f("archiveFolder() could not open archive %s for writing: %s", archivePath.c_str(), archive_error_string(a))); | |||||
| DEFER({archive_write_close(a);}); | |||||
| // Open folder for reading | |||||
| struct archive* disk = archive_read_disk_new(); | |||||
| DEFER({archive_read_free(disk);}); | |||||
| r = archive_read_disk_open(disk, folderPath.c_str()); | |||||
| if (r != ARCHIVE_OK) | |||||
| throw Exception(string::f("archiveFolder() could not open folder %s for reading: %s", folderPath.c_str(), archive_error_string(a))); | |||||
| DEFER({archive_read_close(a);}); | |||||
| // Iterate folder | |||||
| for (;;) { | |||||
| struct archive_entry* entry = archive_entry_new(); | |||||
| DEFER({archive_entry_free(entry);}); | |||||
| r = archive_read_next_header2(disk, entry); | |||||
| if (r == ARCHIVE_EOF) | |||||
| break; | |||||
| if (r != ARCHIVE_OK) | |||||
| throw Exception(string::f("archiveFolder() could not get next entry from archive: %s", archive_error_string(disk))); | |||||
| // Recurse dirs | |||||
| archive_read_disk_descend(disk); | |||||
| // Convert absolute path to relative path | |||||
| filesystem::path entryPath = archive_entry_pathname(entry); | |||||
| entryPath = getRelativePath(entryPath, folderPath); | |||||
| archive_entry_set_pathname(entry, entryPath.c_str()); | |||||
| // Write file to archive | |||||
| r = archive_write_header(a, entry); | |||||
| if (r != ARCHIVE_OK) | |||||
| throw Exception("archiveFolder() could not write entry to archive"); | |||||
| // Manually copy data | |||||
| FILE* f = std::fopen(archive_entry_sourcepath(entry), "rb"); | |||||
| DEFER({std::fclose(f);}); | |||||
| static char buf[1 << 14]; | |||||
| ssize_t len; | |||||
| while ((len = std::fread(buf, 1, sizeof(buf), f)) > 0) { | |||||
| archive_write_data(a, buf, len); | |||||
| } | |||||
| } | |||||
| } | |||||
| } // namespace system | } // namespace system | ||||
| } // namespace rack | } // namespace rack | ||||
| @@ -56,11 +56,12 @@ static void fatalSignalHandler(int sig) { | |||||
| } | } | ||||
| // TODO Use -municode on Windows and change this to wmain. Convert argv to UTF-8. | |||||
| int main(int argc, char* argv[]) { | int main(int argc, char* argv[]) { | ||||
| #if defined ARCH_WIN | #if defined ARCH_WIN | ||||
| // Windows global mutex to prevent multiple instances | // Windows global mutex to prevent multiple instances | ||||
| // Handle will be closed by Windows when the process ends | // Handle will be closed by Windows when the process ends | ||||
| HANDLE instanceMutex = CreateMutexW(NULL, true, (wchar_t*) string::UTF8toUTF16(APP_NAME).c_str()); | |||||
| HANDLE instanceMutex = CreateMutexW(NULL, true, string::U8toU16(APP_NAME).c_str()); | |||||
| if (GetLastError() == ERROR_ALREADY_EXISTS) { | if (GetLastError() == ERROR_ALREADY_EXISTS) { | ||||
| osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Rack is already running. Multiple Rack instances are not supported."); | osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Rack is already running. Multiple Rack instances are not supported."); | ||||
| exit(1); | exit(1); | ||||