#include #include #include #include #include #include #if defined(OS_WINDOWS) #include #include #elif defined(OS_LINUX) #include #include #endif #include #include #include #include namespace hex::fs { static std::function s_fileBrowserErrorCallback; void setFileBrowserErrorCallback(const std::function &callback) { s_fileBrowserErrorCallback = callback; } // With help from https://github.com/owncloud/client/blob/cba22aa34b3677406e0499aadd126ce1d94637a2/src/gui/openfilemanager.cpp void openFileExternal(const std::fs::path &filePath) { if (!wolv::io::fs::exists(filePath)) return; #if defined(OS_WINDOWS) hex::unused( ShellExecute(nullptr, "open", wolv::util::toUTF8String(filePath).c_str(), nullptr, nullptr, SW_SHOWNORMAL) ); #elif defined(OS_MACOS) hex::unused(system( hex::format("open {}", wolv::util::toUTF8String(filePath)).c_str() )); #elif defined(OS_LINUX) hex::unused(system( hex::format("xdg-open {}", wolv::util::toUTF8String(filePath)).c_str() )); #endif } void openFolderExternal(const std::fs::path &dirPath) { if (!wolv::io::fs::exists(dirPath)) return; #if defined(OS_WINDOWS) hex::unused(system( hex::format("explorer.exe {}", wolv::util::toUTF8String(dirPath)).c_str() )); #elif defined(OS_MACOS) hex::unused(system( hex::format("open {}", wolv::util::toUTF8String(dirPath)).c_str() )); #elif defined(OS_LINUX) hex::unused(system( hex::format("xdg-open {}", wolv::util::toUTF8String(dirPath)).c_str() )); #endif } void openFolderWithSelectionExternal(const std::fs::path &selectedFilePath) { if (!wolv::io::fs::exists(selectedFilePath)) return; #if defined(OS_WINDOWS) hex::unused(system( hex::format(R"(explorer.exe /select,"{}")", wolv::util::toUTF8String(selectedFilePath)).c_str() )); #elif defined(OS_MACOS) hex::unused(system( hex::format( R"(osascript -e 'tell application "Finder" to reveal POSIX file "{}"')", wolv::util::toUTF8String(selectedFilePath) ).c_str() )); system(R"(osascript -e 'tell application "Finder" to activate')"); #elif defined(OS_LINUX) // fallback to only opening the folder for now // TODO actually select the file hex::unused(system( hex::format("xdg-open {}", wolv::util::toUTF8String(selectedFilePath.parent_path())).c_str() )); #endif } bool openFileBrowser(DialogMode mode, const std::vector &validExtensions, const std::function &callback, const std::string &defaultPath, bool multiple) { NFD::ClearError(); if (NFD::Init() != NFD_OKAY) { log::error("NFD init returned an error: {}", NFD::GetError()); if (s_fileBrowserErrorCallback != nullptr) s_fileBrowserErrorCallback(NFD::GetError() ? NFD::GetError() : "No details"); return false; } NFD::UniquePathU8 outPath; NFD::UniquePathSet outPaths; nfdresult_t result; switch (mode) { case DialogMode::Open: if (multiple) result = NFD::OpenDialogMultiple(outPaths, validExtensions.data(), validExtensions.size(), defaultPath.empty() ? nullptr : defaultPath.c_str()); else result = NFD::OpenDialog(outPath, validExtensions.data(), validExtensions.size(), defaultPath.empty() ? nullptr : defaultPath.c_str()); break; case DialogMode::Save: result = NFD::SaveDialog(outPath, validExtensions.data(), validExtensions.size(), defaultPath.empty() ? nullptr : defaultPath.c_str()); break; case DialogMode::Folder: result = NFD::PickFolder(outPath, defaultPath.empty() ? nullptr : defaultPath.c_str()); break; default: std::unreachable(); } if (result == NFD_OKAY){ if(outPath != nullptr) { callback(reinterpret_cast(outPath.get())); } if (outPaths != nullptr) { nfdpathsetsize_t numPaths = 0; if (NFD::PathSet::Count(outPaths, numPaths) == NFD_OKAY) { for (size_t i = 0; i < numPaths; i++) { NFD::UniquePathSetPath path; if (NFD::PathSet::GetPath(outPaths, i, path) == NFD_OKAY) callback(reinterpret_cast(path.get())); } } } } else if (result == NFD_ERROR) { log::error("Requested file dialog returned an error: {}", NFD::GetError()); if (s_fileBrowserErrorCallback != nullptr) s_fileBrowserErrorCallback(NFD::GetError() ? NFD::GetError() : "No details"); } NFD::Quit(); return result == NFD_OKAY; } std::vector getDataPaths() { std::vector paths; #if defined(OS_WINDOWS) if (!ImHexApi::System::isPortableVersion()) { PWSTR wAppDataPath = nullptr; if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, nullptr, &wAppDataPath))) { paths.emplace_back(wAppDataPath); CoTaskMemFree(wAppDataPath); } } #elif defined(OS_MACOS) paths.push_back(wolv::io::fs::getApplicationSupportDirectoryPath()); #elif defined(OS_LINUX) paths.push_back(xdg::DataHomeDir()); auto dataDirs = xdg::DataDirs(); std::copy(dataDirs.begin(), dataDirs.end(), std::back_inserter(paths)); #endif for (auto &path : paths) path = path / "imhex"; #if defined(OS_MACOS) if (auto executablePath = wolv::io::fs::getExecutablePath(); executablePath.has_value()) paths.push_back(*executablePath); #else if (auto executablePath = wolv::io::fs::getExecutablePath(); executablePath.has_value()) paths.push_back(executablePath->parent_path()); #endif auto additionalDirs = ImHexApi::System::getAdditionalFolderPaths(); std::copy(additionalDirs.begin(), additionalDirs.end(), std::back_inserter(paths)); if (ProjectFile::hasPath()) { paths.push_back(ProjectFile::getPath().parent_path()); } return paths; } static std::vector getConfigPaths() { #if defined(OS_WINDOWS) return getDataPaths(); #elif defined(OS_MACOS) return getDataPaths(); #elif defined(OS_LINUX) return {xdg::ConfigHomeDir() / "imhex"}; #endif } std::vector appendPath(std::vector paths, const std::fs::path &folder) { for (auto &path : paths) path = path / folder; return paths; } std::vector getPluginPaths() { std::vector paths = getDataPaths(); #if defined(OS_LINUX) && defined(SYSTEM_PLUGINS_LOCATION) paths.push_back(SYSTEM_PLUGINS_LOCATION); #endif return paths; } std::vector getDefaultPaths(ImHexPath path, bool listNonExisting) { std::vector result; switch (path) { case ImHexPath::END: return { }; case ImHexPath::Constants: result = appendPath(getDataPaths(), "constants"); break; case ImHexPath::Config: result = appendPath(getConfigPaths(), "config"); break; case ImHexPath::Encodings: result = appendPath(getDataPaths(), "encodings"); break; case ImHexPath::Logs: result = appendPath(getDataPaths(), "logs"); break; case ImHexPath::Plugins: result = appendPath(getPluginPaths(), "plugins"); break; case ImHexPath::Libraries: result = appendPath(getPluginPaths(), "lib"); break; case ImHexPath::Resources: result = appendPath(getDataPaths(), "resources"); break; case ImHexPath::Magic: result = appendPath(getDataPaths(), "magic"); break; case ImHexPath::Patterns: result = appendPath(getDataPaths(), "patterns"); break; case ImHexPath::PatternsInclude: result = appendPath(getDataPaths(), "includes"); break; case ImHexPath::Yara: result = appendPath(getDataPaths(), "yara"); break; case ImHexPath::Recent: result = appendPath(getConfigPaths(), "recent"); break; case ImHexPath::Scripts: result = appendPath(getDataPaths(), "scripts"); break; case ImHexPath::Inspectors: result = appendPath(getDefaultPaths(ImHexPath::Scripts), "inspectors"); break; case ImHexPath::Nodes: result = appendPath(getDefaultPaths(ImHexPath::Scripts), "nodes"); break; case ImHexPath::Themes: result = appendPath(getDataPaths(), "themes"); break; case ImHexPath::Layouts: result = appendPath(getDataPaths(), "layouts"); break; } if (!listNonExisting) { result.erase(std::remove_if(result.begin(), result.end(), [](const auto &path) { return !wolv::io::fs::isDirectory(path); }), result.end()); } return result; } bool isPathWritable(const std::fs::path &path) { constexpr static auto TestFileName = "__imhex__tmp__"; { wolv::io::File file(path / TestFileName, wolv::io::File::Mode::Read); if (file.isValid()) { if (!file.remove()) return false; } } wolv::io::File file(path / TestFileName, wolv::io::File::Mode::Create); bool result = file.isValid(); if (!file.remove()) return false; return result; } std::fs::path toShortPath(const std::fs::path &path) { #if defined(OS_WINDOWS) size_t size = GetShortPathNameW(path.c_str(), nullptr, 0); if (size == 0) return path; std::wstring newPath(size, 0x00); GetShortPathNameW(path.c_str(), newPath.data(), newPath.size()); newPath.pop_back(); return newPath; #else return path; #endif } }