From ef02ef2e6c87900bef98f2fd211db69c793ba3ce Mon Sep 17 00:00:00 2001 From: dijunkun Date: Wed, 21 Oct 2026 03:16:23 +0800 Subject: [PATCH] [fix] preserve Linux desktop options when restarting after updates --- .../linux/updater/update_platform.cpp | 36 ++++++++++++++++++- .../macos/updater/update_platform.cpp | 5 ++- .../windows/updater/update_platform.cpp | 5 ++- apps/desktop/src/updater/main.cpp | 8 +++-- .../desktop/src/updater/update_controller.cpp | 3 +- apps/desktop/src/updater/update_platform.h | 3 +- docs/UPDATER.md | 3 ++ 7 files changed, 56 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/platform/linux/updater/update_platform.cpp b/apps/desktop/src/platform/linux/updater/update_platform.cpp index 23fe77f7..14d928b9 100644 --- a/apps/desktop/src/platform/linux/updater/update_platform.cpp +++ b/apps/desktop/src/platform/linux/updater/update_platform.cpp @@ -5,10 +5,32 @@ #include #include #include +#include #include #include namespace crossdesk::updater { +namespace { +std::vector HeadlessArguments(const std::vector& arguments) { + std::vector result; + for (std::size_t i = 0; i < arguments.size(); ++i) { + const auto& argument = arguments[i]; + if (argument == "--headless" || argument == "--no-headless") { + result.push_back(argument); + } else if (argument == "--headless-display" || argument == "--headless-size" || + argument == "--headless-session") { + if (i + 1 == arguments.size() || arguments[i + 1].empty() || + arguments[i + 1].rfind("--", 0) == 0) + throw std::runtime_error("Missing headless restart argument"); + result.push_back(argument); + result.push_back(arguments[++i]); + } + // Internal modes such as --child must never replace a normal restart. + } + return result; +} +} // namespace + fs::path ExecutablePath() { return fs::read_symlink("/proc/self/exe"); } bool SupportedInstallation(const fs::path& application) { return application == fs::path("/usr/lib/crossdesk/crossdesk-bin") && @@ -50,5 +72,17 @@ void InstallPackage(const fs::path& package, const fs::path&) { "-o", "Dpkg::Options::=--force-confold", package.string()}) != 0) throw std::runtime_error("The package manager could not install the update"); } -void Restart(const fs::path&) { Launch("/usr/bin/crossdesk", {}); } +std::vector RestartArguments() { + // The built-in headless supervisor forks the GUI without exec, so procfs + // still contains its original display/session options in the GUI process. + std::istringstream command_line(ReadText("/proc/self/cmdline", 32768)); + std::string argument; + std::getline(command_line, argument, '\0'); // Executable name. + std::vector arguments; + while (std::getline(command_line, argument, '\0')) arguments.push_back(argument); + return HeadlessArguments(arguments); +} +void Restart(const fs::path&, const std::vector& arguments) { + Launch("/usr/bin/crossdesk", HeadlessArguments(arguments)); +} } // namespace crossdesk::updater diff --git a/apps/desktop/src/platform/macos/updater/update_platform.cpp b/apps/desktop/src/platform/macos/updater/update_platform.cpp index d529f679..b1d19f3e 100644 --- a/apps/desktop/src/platform/macos/updater/update_platform.cpp +++ b/apps/desktop/src/platform/macos/updater/update_platform.cpp @@ -68,5 +68,8 @@ void InstallPackage(const fs::path& package, const fs::path&) { if (Run("/usr/sbin/installer", {"-pkg", package.string(), "-target", "/"}) != 0) throw std::runtime_error("The macOS installer could not install the update"); } -void Restart(const fs::path&) { Launch("/usr/bin/open", {"/Applications/CrossDesk.app"}); } +std::vector RestartArguments() { return {}; } +void Restart(const fs::path&, const std::vector&) { + Launch("/usr/bin/open", {"/Applications/CrossDesk.app"}); +} } // namespace crossdesk::updater diff --git a/apps/desktop/src/platform/windows/updater/update_platform.cpp b/apps/desktop/src/platform/windows/updater/update_platform.cpp index f95181d5..ebc1cd2f 100644 --- a/apps/desktop/src/platform/windows/updater/update_platform.cpp +++ b/apps/desktop/src/platform/windows/updater/update_platform.cpp @@ -274,7 +274,10 @@ void RestoreServices() { CloseServiceHandle(manager); } -void Restart(const fs::path& application) { Launch(application, {}); } +std::vector RestartArguments() { return {}; } +void Restart(const fs::path& application, const std::vector&) { + Launch(application, {}); +} std::shared_ptr PinHelper(const fs::path& helper) { HANDLE handle = CreateFileW(helper.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, diff --git a/apps/desktop/src/updater/main.cpp b/apps/desktop/src/updater/main.cpp index a81d6be0..c799fd86 100644 --- a/apps/desktop/src/updater/main.cpp +++ b/apps/desktop/src/updater/main.cpp @@ -17,6 +17,7 @@ namespace { struct Request { fs::path application; Artifact artifact; + std::vector restart_arguments; }; Request ReadRequest(const fs::path& job) { const auto request = nlohmann::json::parse(ReadText(job / "request.json")); @@ -24,7 +25,8 @@ Request ReadRequest(const fs::path& job) { if (!SupportedInstallation(application)) throw std::runtime_error("Invalid installation destination"); const auto artifact = VerifyEnvelope(request.at("envelope").get(), PublicKey(), PlatformKey(application), BuildVersion()); - return {application, artifact}; + return {application, artifact, + request.value("restart_arguments", std::vector{})}; } void WaitForCommit(const fs::path& job) { @@ -81,6 +83,7 @@ int Install(const fs::path& job) { int Coordinate(const fs::path& job) { fs::path application; + std::vector restart_arguments; bool committed = false; bool success = false; bool exited = false; @@ -89,6 +92,7 @@ int Coordinate(const fs::path& job) { try { const auto request = ReadRequest(job); application = request.application; + restart_arguments = request.restart_arguments; auto pinned = PinHelper(ExecutablePath()); { maintenance = std::make_unique(application); @@ -139,7 +143,7 @@ int Coordinate(const fs::path& job) { maintenance.reset(); if (committed && exited) { try { - Restart(application); + Restart(application, restart_arguments); if (success) { const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(90); while (!fs::exists(job / "started") && std::chrono::steady_clock::now() < deadline) diff --git a/apps/desktop/src/updater/update_controller.cpp b/apps/desktop/src/updater/update_controller.cpp index 7b06dbd5..9ba39f9d 100644 --- a/apps/desktop/src/updater/update_controller.cpp +++ b/apps/desktop/src/updater/update_controller.cpp @@ -72,7 +72,8 @@ void Controller::Start(const std::string& envelope) { const auto job = PrivateDirectory(); try { WriteText(job / "request.json", nlohmann::json{ - {"application", PathUtf8(application)}, {"envelope", envelope}}.dump()); + {"application", PathUtf8(application)}, {"envelope", envelope}, + {"restart_arguments", RestartArguments()}}.dump()); DownloadPackage(artifact, job / artifact.filename, progress_, cancel_); if (cancel_) throw std::runtime_error("Update cancelled"); // Relocate the coordinator so package installation can replace the diff --git a/apps/desktop/src/updater/update_platform.h b/apps/desktop/src/updater/update_platform.h index e8d6e343..a67c190e 100644 --- a/apps/desktop/src/updater/update_platform.h +++ b/apps/desktop/src/updater/update_platform.h @@ -23,7 +23,8 @@ int Run(const fs::path& executable, const std::vector& arguments, int RunElevated(const fs::path& executable, const fs::path& job); void WaitForApplicationExit(const fs::path& application); void InstallPackage(const fs::path& package, const fs::path& application); -void Restart(const fs::path& application); +std::vector RestartArguments(); +void Restart(const fs::path& application, const std::vector& arguments); void RestoreServices(); // Pin relocated helper files against changes during elevation (Windows). std::shared_ptr PinHelper(const fs::path& helper); diff --git a/docs/UPDATER.md b/docs/UPDATER.md index 56e412f8..f20fb19d 100644 --- a/docs/UPDATER.md +++ b/docs/UPDATER.md @@ -102,6 +102,9 @@ xmake b crossdesk - Linux:支持 `/usr/lib/crossdesk/crossdesk-bin`,根据 `package-name` 选择普通包或虚拟桌面包。使用 `pkexec` 授权和 `apt-get install` 安装本地 DEB;禁止包管理器自动删除依赖。需要可用的授权代理。 + 重启时恢复原来的 `--headless`、`--no-headless`、`--headless-display`、 + `--headless-size` 和 `--headless-session` 参数,包括安装失败后的重启; + 内部运行模式参数不会重放。 自定义 systemd/headless supervisor 必须由管理员先协调停止; 检测到仍在运行的程序会中止升级,不猜测或停止其他服务。