mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-21 20:18:12 +08:00
[fix] preserve Linux desktop options when restarting after updates
This commit is contained in:
@@ -5,10 +5,32 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
namespace crossdesk::updater {
|
namespace crossdesk::updater {
|
||||||
|
namespace {
|
||||||
|
std::vector<std::string> HeadlessArguments(const std::vector<std::string>& arguments) {
|
||||||
|
std::vector<std::string> 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"); }
|
fs::path ExecutablePath() { return fs::read_symlink("/proc/self/exe"); }
|
||||||
bool SupportedInstallation(const fs::path& application) {
|
bool SupportedInstallation(const fs::path& application) {
|
||||||
return application == fs::path("/usr/lib/crossdesk/crossdesk-bin") &&
|
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)
|
"-o", "Dpkg::Options::=--force-confold", package.string()}) != 0)
|
||||||
throw std::runtime_error("The package manager could not install the update");
|
throw std::runtime_error("The package manager could not install the update");
|
||||||
}
|
}
|
||||||
void Restart(const fs::path&) { Launch("/usr/bin/crossdesk", {}); }
|
std::vector<std::string> 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<std::string> arguments;
|
||||||
|
while (std::getline(command_line, argument, '\0')) arguments.push_back(argument);
|
||||||
|
return HeadlessArguments(arguments);
|
||||||
|
}
|
||||||
|
void Restart(const fs::path&, const std::vector<std::string>& arguments) {
|
||||||
|
Launch("/usr/bin/crossdesk", HeadlessArguments(arguments));
|
||||||
|
}
|
||||||
} // namespace crossdesk::updater
|
} // namespace crossdesk::updater
|
||||||
|
|||||||
@@ -68,5 +68,8 @@ void InstallPackage(const fs::path& package, const fs::path&) {
|
|||||||
if (Run("/usr/sbin/installer", {"-pkg", package.string(), "-target", "/"}) != 0)
|
if (Run("/usr/sbin/installer", {"-pkg", package.string(), "-target", "/"}) != 0)
|
||||||
throw std::runtime_error("The macOS installer could not install the update");
|
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<std::string> RestartArguments() { return {}; }
|
||||||
|
void Restart(const fs::path&, const std::vector<std::string>&) {
|
||||||
|
Launch("/usr/bin/open", {"/Applications/CrossDesk.app"});
|
||||||
|
}
|
||||||
} // namespace crossdesk::updater
|
} // namespace crossdesk::updater
|
||||||
|
|||||||
@@ -274,7 +274,10 @@ void RestoreServices() {
|
|||||||
CloseServiceHandle(manager);
|
CloseServiceHandle(manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Restart(const fs::path& application) { Launch(application, {}); }
|
std::vector<std::string> RestartArguments() { return {}; }
|
||||||
|
void Restart(const fs::path& application, const std::vector<std::string>&) {
|
||||||
|
Launch(application, {});
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<void> PinHelper(const fs::path& helper) {
|
std::shared_ptr<void> PinHelper(const fs::path& helper) {
|
||||||
HANDLE handle = CreateFileW(helper.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
|
HANDLE handle = CreateFileW(helper.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ namespace {
|
|||||||
struct Request {
|
struct Request {
|
||||||
fs::path application;
|
fs::path application;
|
||||||
Artifact artifact;
|
Artifact artifact;
|
||||||
|
std::vector<std::string> restart_arguments;
|
||||||
};
|
};
|
||||||
Request ReadRequest(const fs::path& job) {
|
Request ReadRequest(const fs::path& job) {
|
||||||
const auto request = nlohmann::json::parse(ReadText(job / "request.json"));
|
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");
|
if (!SupportedInstallation(application)) throw std::runtime_error("Invalid installation destination");
|
||||||
const auto artifact = VerifyEnvelope(request.at("envelope").get<std::string>(), PublicKey(),
|
const auto artifact = VerifyEnvelope(request.at("envelope").get<std::string>(), PublicKey(),
|
||||||
PlatformKey(application), BuildVersion());
|
PlatformKey(application), BuildVersion());
|
||||||
return {application, artifact};
|
return {application, artifact,
|
||||||
|
request.value("restart_arguments", std::vector<std::string>{})};
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaitForCommit(const fs::path& job) {
|
void WaitForCommit(const fs::path& job) {
|
||||||
@@ -81,6 +83,7 @@ int Install(const fs::path& job) {
|
|||||||
|
|
||||||
int Coordinate(const fs::path& job) {
|
int Coordinate(const fs::path& job) {
|
||||||
fs::path application;
|
fs::path application;
|
||||||
|
std::vector<std::string> restart_arguments;
|
||||||
bool committed = false;
|
bool committed = false;
|
||||||
bool success = false;
|
bool success = false;
|
||||||
bool exited = false;
|
bool exited = false;
|
||||||
@@ -89,6 +92,7 @@ int Coordinate(const fs::path& job) {
|
|||||||
try {
|
try {
|
||||||
const auto request = ReadRequest(job);
|
const auto request = ReadRequest(job);
|
||||||
application = request.application;
|
application = request.application;
|
||||||
|
restart_arguments = request.restart_arguments;
|
||||||
auto pinned = PinHelper(ExecutablePath());
|
auto pinned = PinHelper(ExecutablePath());
|
||||||
{
|
{
|
||||||
maintenance = std::make_unique<MaintenanceLock>(application);
|
maintenance = std::make_unique<MaintenanceLock>(application);
|
||||||
@@ -139,7 +143,7 @@ int Coordinate(const fs::path& job) {
|
|||||||
maintenance.reset();
|
maintenance.reset();
|
||||||
if (committed && exited) {
|
if (committed && exited) {
|
||||||
try {
|
try {
|
||||||
Restart(application);
|
Restart(application, restart_arguments);
|
||||||
if (success) {
|
if (success) {
|
||||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(90);
|
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(90);
|
||||||
while (!fs::exists(job / "started") && std::chrono::steady_clock::now() < deadline)
|
while (!fs::exists(job / "started") && std::chrono::steady_clock::now() < deadline)
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ void Controller::Start(const std::string& envelope) {
|
|||||||
const auto job = PrivateDirectory();
|
const auto job = PrivateDirectory();
|
||||||
try {
|
try {
|
||||||
WriteText(job / "request.json", nlohmann::json{
|
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_);
|
DownloadPackage(artifact, job / artifact.filename, progress_, cancel_);
|
||||||
if (cancel_) throw std::runtime_error("Update cancelled");
|
if (cancel_) throw std::runtime_error("Update cancelled");
|
||||||
// Relocate the coordinator so package installation can replace the
|
// Relocate the coordinator so package installation can replace the
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ int Run(const fs::path& executable, const std::vector<std::string>& arguments,
|
|||||||
int RunElevated(const fs::path& executable, const fs::path& job);
|
int RunElevated(const fs::path& executable, const fs::path& job);
|
||||||
void WaitForApplicationExit(const fs::path& application);
|
void WaitForApplicationExit(const fs::path& application);
|
||||||
void InstallPackage(const fs::path& package, const fs::path& application);
|
void InstallPackage(const fs::path& package, const fs::path& application);
|
||||||
void Restart(const fs::path& application);
|
std::vector<std::string> RestartArguments();
|
||||||
|
void Restart(const fs::path& application, const std::vector<std::string>& arguments);
|
||||||
void RestoreServices();
|
void RestoreServices();
|
||||||
// Pin relocated helper files against changes during elevation (Windows).
|
// Pin relocated helper files against changes during elevation (Windows).
|
||||||
std::shared_ptr<void> PinHelper(const fs::path& helper);
|
std::shared_ptr<void> PinHelper(const fs::path& helper);
|
||||||
|
|||||||
@@ -102,6 +102,9 @@ xmake b crossdesk
|
|||||||
- Linux:支持 `/usr/lib/crossdesk/crossdesk-bin`,根据 `package-name`
|
- Linux:支持 `/usr/lib/crossdesk/crossdesk-bin`,根据 `package-name`
|
||||||
选择普通包或虚拟桌面包。使用 `pkexec` 授权和 `apt-get install`
|
选择普通包或虚拟桌面包。使用 `pkexec` 授权和 `apt-get install`
|
||||||
安装本地 DEB;禁止包管理器自动删除依赖。需要可用的授权代理。
|
安装本地 DEB;禁止包管理器自动删除依赖。需要可用的授权代理。
|
||||||
|
重启时恢复原来的 `--headless`、`--no-headless`、`--headless-display`、
|
||||||
|
`--headless-size` 和 `--headless-session` 参数,包括安装失败后的重启;
|
||||||
|
内部运行模式参数不会重放。
|
||||||
自定义 systemd/headless supervisor 必须由管理员先协调停止;
|
自定义 systemd/headless supervisor 必须由管理员先协调停止;
|
||||||
检测到仍在运行的程序会中止升级,不猜测或停止其他服务。
|
检测到仍在运行的程序会中止升级,不猜测或停止其他服务。
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user