From 515d517a99dd623429b4d5014dcd8609b6cc9e92 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Wed, 20 May 2026 23:53:13 +0800 Subject: [PATCH] [feat] add portable build storage mode, refs #80 --- .github/workflows/build.yml | 5 ++ src/path_manager/path_manager.cpp | 97 +++++++++++++++++++++++++++- tests/path_manager_portable_test.cpp | 78 ++++++++++++++++++++++ xmake/options.lua | 9 ++- xmake/targets.lua | 10 ++- 5 files changed, 196 insertions(+), 3 deletions(-) create mode 100644 tests/path_manager_portable_test.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 887623c..98fd57b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -242,6 +242,11 @@ jobs: cd "${{ github.workspace }}\scripts\windows" makensis /DVERSION=$env:VERSION_NUM nsis_script.nsi + - name: Build Portable CrossDesk + run: | + xmake f --CROSSDESK_VERSION=${{ env.VERSION_NUM }} --USE_CUDA=true --CROSSDESK_PORTABLE=true -y + xmake b -vy crossdesk + - name: Package Portable shell: pwsh run: | diff --git a/src/path_manager/path_manager.cpp b/src/path_manager/path_manager.cpp index 1d629e9..d6bd2ae 100644 --- a/src/path_manager/path_manager.cpp +++ b/src/path_manager/path_manager.cpp @@ -1,12 +1,98 @@ #include "path_manager.h" +#include #include +#include + +#ifndef CROSSDESK_PORTABLE +#define CROSSDESK_PORTABLE 0 +#endif + +#if CROSSDESK_PORTABLE +#if defined(__APPLE__) +#include +#elif !defined(_WIN32) +#include +#include +#endif +#endif + +namespace { + +#if CROSSDESK_PORTABLE +std::filesystem::path GetExecutableDirectory() { +#ifdef _WIN32 + std::vector buffer(MAX_PATH); + while (true) { + DWORD length = + GetModuleFileNameW(nullptr, buffer.data(), + static_cast(buffer.size())); + if (length == 0) { + return {}; + } + if (length < buffer.size()) { + return std::filesystem::path(buffer.data(), buffer.data() + length) + .parent_path(); + } + if (buffer.size() >= 32768) { + return {}; + } + buffer.resize(buffer.size() * 2); + } +#elif defined(__APPLE__) + uint32_t size = 0; + _NSGetExecutablePath(nullptr, &size); + std::vector buffer(size + 1); + if (_NSGetExecutablePath(buffer.data(), &size) != 0) { + return {}; + } + + std::error_code ec; + std::filesystem::path executable = + std::filesystem::weakly_canonical(buffer.data(), ec); + if (ec) { + executable = buffer.data(); + } + return executable.parent_path(); +#else + std::vector buffer(PATH_MAX); + while (true) { + ssize_t length = readlink("/proc/self/exe", buffer.data(), + buffer.size() - 1); + if (length <= 0) { + return {}; + } + if (static_cast(length) < buffer.size() - 1) { + buffer[static_cast(length)] = '\0'; + return std::filesystem::path(buffer.data()).parent_path(); + } + buffer.resize(buffer.size() * 2); + } +#endif +} + +std::filesystem::path GetPortableRootPath() { + std::filesystem::path executable_dir = GetExecutableDirectory(); + if (!executable_dir.empty()) { + return executable_dir; + } + + std::error_code ec; + std::filesystem::path current = std::filesystem::current_path(ec); + return ec ? std::filesystem::path(".") : current; +} +#endif + +} // namespace namespace crossdesk { PathManager::PathManager(const std::string& app_name) : app_name_(app_name) {} std::filesystem::path PathManager::GetConfigPath() { +#if CROSSDESK_PORTABLE + return GetPortableRootPath() / "data"; +#else #ifdef _WIN32 return GetKnownFolder(FOLDERID_RoamingAppData) / app_name_; #elif __APPLE__ @@ -14,9 +100,13 @@ std::filesystem::path PathManager::GetConfigPath() { #else return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") / app_name_; #endif +#endif } std::filesystem::path PathManager::GetCachePath() { +#if CROSSDESK_PORTABLE + return GetPortableRootPath() / "data"; +#else #ifdef _WIN32 #ifdef CROSSDESK_DEBUG return "cache"; @@ -28,9 +118,13 @@ std::filesystem::path PathManager::GetCachePath() { #else return GetEnvOrDefault("XDG_CACHE_HOME", GetHome() + "/.cache") / app_name_; #endif +#endif } std::filesystem::path PathManager::GetLogPath() { +#if CROSSDESK_PORTABLE + return GetPortableRootPath() / "logs"; +#else #ifdef _WIN32 return GetKnownFolder(FOLDERID_LocalAppData) / app_name_ / "logs"; #elif __APPLE__ @@ -38,6 +132,7 @@ std::filesystem::path PathManager::GetLogPath() { #else return GetCachePath() / "logs"; #endif +#endif } bool PathManager::CreateDirectories(const std::filesystem::path& p) { @@ -93,4 +188,4 @@ std::filesystem::path PathManager::GetEnvOrDefault(const char* env_var, return std::filesystem::path(def); } -} // namespace crossdesk \ No newline at end of file +} // namespace crossdesk diff --git a/tests/path_manager_portable_test.cpp b/tests/path_manager_portable_test.cpp new file mode 100644 index 0000000..6689a01 --- /dev/null +++ b/tests/path_manager_portable_test.cpp @@ -0,0 +1,78 @@ +#include "path_manager.h" + +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#elif defined(__APPLE__) +#include +#include +#else +#include +#include +#endif + +namespace { + +std::filesystem::path GetExecutableDirectory() { +#ifdef _WIN32 + wchar_t buffer[MAX_PATH] = {}; + DWORD length = GetModuleFileNameW(nullptr, buffer, MAX_PATH); + if (length == 0 || length == MAX_PATH) { + return {}; + } + return std::filesystem::path(buffer).parent_path(); +#elif defined(__APPLE__) + char buffer[PATH_MAX] = {}; + uint32_t size = sizeof(buffer); + if (_NSGetExecutablePath(buffer, &size) != 0) { + return {}; + } + return std::filesystem::weakly_canonical(buffer).parent_path(); +#else + char buffer[PATH_MAX] = {}; + ssize_t length = readlink("/proc/self/exe", buffer, sizeof(buffer) - 1); + if (length <= 0) { + return {}; + } + buffer[length] = '\0'; + return std::filesystem::path(buffer).parent_path(); +#endif +} + +bool ExpectEqual(const char* name, + const std::filesystem::path& actual, + const std::filesystem::path& expected) { + if (actual.lexically_normal() == expected.lexically_normal()) { + return true; + } + + std::cerr << name << " mismatch\n" + << " expected: " << expected.string() << "\n" + << " actual: " << actual.string() << "\n"; + return false; +} + +} // namespace + +int main() { + const std::filesystem::path exe_dir = GetExecutableDirectory(); + if (exe_dir.empty()) { + std::cerr << "failed to resolve executable directory\n"; + return 1; + } + + crossdesk::PathManager path_manager("CrossDesk"); + const std::filesystem::path expected_data = exe_dir / "data"; + const std::filesystem::path expected_logs = exe_dir / "logs"; + + bool ok = true; + ok &= ExpectEqual("config path", path_manager.GetConfigPath(), expected_data); + ok &= ExpectEqual("cache path", path_manager.GetCachePath(), expected_data); + ok &= ExpectEqual("log path", path_manager.GetLogPath(), expected_logs); + + return ok ? 0 : 1; +} diff --git a/xmake/options.lua b/xmake/options.lua index 2a61950..af08dd0 100644 --- a/xmake/options.lua +++ b/xmake/options.lua @@ -23,6 +23,12 @@ function setup_options_and_dependencies() set_description("Enable DRM capture on Linux (assumes dependencies are installed)") option_end() + option("CROSSDESK_PORTABLE") + set_default(false) + set_showmenu(true) + set_description("Build CrossDesk as a portable package that stores data beside the executable") + option_end() + add_rules("mode.release", "mode.debug") set_languages("c++17") set_encodings("utf-8") @@ -35,6 +41,7 @@ function setup_options_and_dependencies() add_defines("USE_CUDA=" .. (is_config("USE_CUDA", true) and "1" or "0")) add_defines("USE_WAYLAND=" .. (is_config("USE_WAYLAND", true) and "1" or "0")) add_defines("USE_DRM=" .. (is_config("USE_DRM", true) and "1" or "0")) + add_defines("CROSSDESK_PORTABLE=" .. (is_config("CROSSDESK_PORTABLE", true) and "1" or "0")) if is_mode("debug") then add_defines("CROSSDESK_DEBUG") @@ -47,4 +54,4 @@ function setup_options_and_dependencies() add_requires("nlohmann_json 3.11.3") add_requires("cpp-httplib v0.26.0", {configs = {ssl = true}}) add_requires("tinyfiledialogs 3.15.1") -end \ No newline at end of file +end diff --git a/xmake/targets.lua b/xmake/targets.lua index 206054b..3352a1f 100644 --- a/xmake/targets.lua +++ b/xmake/targets.lua @@ -25,6 +25,14 @@ function setup_targets() add_files("src/path_manager/*.cpp") add_includedirs("src/path_manager", {public = true}) + target("path_manager_portable_test") + set_kind("binary") + set_default(false) + add_defines("CROSSDESK_PORTABLE=1") + add_includedirs("src/path_manager") + add_files("tests/path_manager_portable_test.cpp", + "src/path_manager/path_manager.cpp") + target("screen_capturer") set_kind("object") add_deps("rd_log", "common") @@ -195,4 +203,4 @@ function setup_targets() add_deps("wgc_plugin", "crossdesk_service", "crossdesk_session_helper") add_files("scripts/windows/crossdesk.rc") end -end \ No newline at end of file +end