From c75394e498e0768caaa416f975f7c72f71eb039f Mon Sep 17 00:00:00 2001 From: dijunkun Date: Sun, 8 Nov 2026 15:57:58 +0800 Subject: [PATCH] [fix] repair Linux autostart and apply daemon settings immediately, refs #147 --- apps/desktop/src/app/main.cpp | 29 +- .../src/config_center/config_center.cpp | 28 ++ .../src/gui/application/gui_application.cpp | 8 +- apps/desktop/src/platform/linux/autostart.cpp | 92 ++++- apps/desktop/src/platform/linux/autostart.h | 14 + .../src/platform/linux/runtime_daemon.cpp | 320 ++++++++++++++++++ .../src/platform/linux/runtime_daemon.h | 40 +++ apps/desktop/xmake/targets.lua | 11 + docs/USER_GUIDE.md | 3 +- 9 files changed, 529 insertions(+), 16 deletions(-) create mode 100644 apps/desktop/src/platform/linux/autostart.h create mode 100644 apps/desktop/src/platform/linux/runtime_daemon.cpp create mode 100644 apps/desktop/src/platform/linux/runtime_daemon.h diff --git a/apps/desktop/src/app/main.cpp b/apps/desktop/src/app/main.cpp index 58e02ecd..d3ad9878 100644 --- a/apps/desktop/src/app/main.cpp +++ b/apps/desktop/src/app/main.cpp @@ -37,9 +37,11 @@ #ifdef __linux__ #include "SimpleIni.h" +#include "platform/linux/autostart.h" #include "platform/linux/headless/headless_console.h" #include "platform/linux/headless/headless_session.h" #include "platform/linux/privacy/privacy_guard.h" +#include "platform/linux/runtime_daemon.h" #endif #ifdef _WIN32 @@ -365,6 +367,13 @@ static int RunApplication( std::string cache_path = path_manager->GetCachePath().string(); crossdesk::ConfigCenter config_center(cache_path + "/config.ini"); enable_daemon = config_center.IsEnableDaemon(); +#ifdef __linux__ + if (config_center.IsEnableAutostart() && + !crossdesk::platform::MigrateLegacyAutostart("CrossDesk")) { + crossdesk::get_logger()->warn( + "Could not migrate the legacy Linux autostart entry"); + } +#endif } #ifdef __linux__ @@ -373,7 +382,20 @@ static int RunApplication( // session instead. if (std::getenv("CROSSDESK_HEADLESS_ACTIVE") || crossdesk::HeadlessConsole::Instance().active()) enable_daemon = false; -#endif + + auto& runtime_daemon = crossdesk::platform::LinuxRuntimeDaemon::Instance(); + runtime_daemon.Initialize( + argc, argv, !std::getenv("CROSSDESK_HEADLESS_ACTIVE") && + !crossdesk::HeadlessConsole::Instance().active()); + std::string daemon_error; + if (!runtime_daemon.SetEnabled(enable_daemon, daemon_error)) { + crossdesk::get_logger()->warn("Could not start daemon: {}", daemon_error); + } + crossdesk::Render render; + const int result = render.Run(); + runtime_daemon.Shutdown(result != 0); + return result; +#else if (enable_daemon) { // The launcher lease stays in main for the supervisor's entire lifetime. @@ -406,6 +428,7 @@ static int RunApplication( // run without daemon: direct execution crossdesk::Render render; return render.Run(); +#endif } int main(int argc, char* argv[]) { @@ -421,6 +444,10 @@ int main(int argc, char* argv[]) { #endif #ifdef __linux__ // Run before GUI, configuration, logging or display initialization. + if (argc >= 2 && + std::strcmp(argv[1], crossdesk::platform::kLinuxDaemonArgument) == 0) { + return crossdesk::platform::RunLinuxRuntimeDaemon(argc, argv); + } if (argc == 2 && std::strcmp(argv[1], crossdesk::kLinuxPrivacyGuardArgument) == 0) { return crossdesk::RunLinuxPrivacyGuard(); diff --git a/apps/desktop/src/config_center/config_center.cpp b/apps/desktop/src/config_center/config_center.cpp index a4ec12c0..5b87bcad 100644 --- a/apps/desktop/src/config_center/config_center.cpp +++ b/apps/desktop/src/config_center/config_center.cpp @@ -12,6 +12,11 @@ #include "rd_log.h" #include +#if defined(__linux__) && !defined(__OHOS__) +#include "platform/linux/autostart.h" +#include "platform/linux/runtime_daemon.h" +#endif + namespace crossdesk { struct ConfigCenter::IniStorage : CSimpleIniA {}; @@ -386,6 +391,14 @@ int ConfigCenter::SetSelfHosted(bool enabled) { int ConfigCenter::SetAutostart(bool enabled) { const bool previous = IsAutostartEnabled("CrossDesk"); +#if defined(__linux__) && !defined(__OHOS__) + // Repair old deb entries even when the saved boolean is unchanged, keeping + // desktop-managed disabled flags and custom commands intact. + if (enabled && previous && !platform::MigrateLegacyAutostart("CrossDesk")) { + LOG_ERROR("Failed to migrate Linux autostart"); + return -1; + } +#endif // Refresh the executable path and remove legacy registrations even when the // boolean state is unchanged. In particular, a Run entry no longer counts // as enabled for an elevated Windows build, but still needs deleting. @@ -411,8 +424,23 @@ int ConfigCenter::SetAutostart(bool enabled) { } int ConfigCenter::SetDaemon(bool enabled) { +#if defined(__linux__) && !defined(__OHOS__) + const bool previous = enable_daemon_; +#endif if (StoreValues({{"enable_daemon", enabled ? "true" : "false"}}) != 0) return -1; enable_daemon_ = enabled; +#if defined(__linux__) && !defined(__OHOS__) + std::string error; + if (!platform::LinuxRuntimeDaemon::Instance().SetEnabled(enabled, error)) { + LOG_ERROR("Cannot apply daemon setting: {}", error); + if (StoreValues({{"enable_daemon", previous ? "true" : "false"}}) == 0) { + enable_daemon_ = previous; + } else { + LOG_ERROR("Cannot restore daemon preference after startup failure"); + } + return -1; + } +#endif return 0; } diff --git a/apps/desktop/src/gui/application/gui_application.cpp b/apps/desktop/src/gui/application/gui_application.cpp index 2be3f19a..aa02fd49 100644 --- a/apps/desktop/src/gui/application/gui_application.cpp +++ b/apps/desktop/src/gui/application/gui_application.cpp @@ -4985,7 +4985,13 @@ bool GuiApplication::SaveSettingsFromUi(bool gallery_window) { #if !defined(__OHOS__) config_center_->SetAutostart(enable_autostart_); #endif - config_center_->SetDaemon(enable_daemon_); + if (config_center_->SetDaemon(enable_daemon_) != 0) { + enable_daemon_ = config_center_->IsEnableDaemon(); + main->set_daemon_enabled(enable_daemon_); + main->set_settings_save_error(UiText( + localization::Literal("保存失败,请重试", localization_language_index_))); + return false; + } if (config_center_->SetSaveRemotePreviews(main->get_save_remote_previews()) != 0) { LOG_ERROR("Failed to save remote preview preference"); } diff --git a/apps/desktop/src/platform/linux/autostart.cpp b/apps/desktop/src/platform/linux/autostart.cpp index cccf1c85..310a720a 100644 --- a/apps/desktop/src/platform/linux/autostart.cpp +++ b/apps/desktop/src/platform/linux/autostart.cpp @@ -1,15 +1,21 @@ #include "platform/autostart_backend.h" +#include "platform/linux/autostart.h" #include #include +#include #include #include #include +#include namespace crossdesk::platform { namespace { +constexpr const char* kInstalledExecutable = "/usr/lib/crossdesk/crossdesk-bin"; +constexpr const char* kInstalledLauncher = "/usr/bin/crossdesk"; + std::filesystem::path AutostartPath(const std::string& app_name) { const char* config = std::getenv("XDG_CONFIG_HOME"); if (config && *config) return std::filesystem::path(config) / "autostart" / (app_name + ".desktop"); @@ -19,13 +25,39 @@ std::filesystem::path AutostartPath(const std::string& app_name) { (app_name + ".desktop"); } +bool WriteDesktopEntry(const std::filesystem::path& path, + const std::string& contents) { + // In particular, a failed migration must leave the previous entry intact. + std::string temporary = path.string() + ".XXXXXX"; + const int fd = mkstemp(temporary.data()); + if (fd < 0) return false; + std::FILE* file = fdopen(fd, "w"); + if (!file) { + close(fd); + std::remove(temporary.c_str()); + return false; + } + const bool written = + std::fwrite(contents.data(), 1, contents.size(), file) == contents.size(); + const bool closed = std::fclose(file) == 0; + if (written && closed && std::rename(temporary.c_str(), path.c_str()) == 0) + return true; + std::remove(temporary.c_str()); + return false; +} + } // namespace std::string GetAutostartExecutablePath() { char path[PATH_MAX] = {}; const ssize_t count = readlink("/proc/self/exe", path, sizeof(path) - 1); - return count > 0 ? std::string(path, static_cast(count)) - : std::string(); + if (count <= 0 || count >= static_cast(sizeof(path) - 1)) return {}; + const std::string executable(path, static_cast(count)); + // The deb launcher sets LD_LIBRARY_PATH for the private Slint runtime. + // /proc/self/exe resolves to its exec() target and loses that setup. + if (executable == kInstalledExecutable && access(kInstalledLauncher, X_OK) == 0) + return kInstalledLauncher; + return executable; } bool EnableAutostart(const std::string& app_name, @@ -37,19 +69,53 @@ bool EnableAutostart(const std::string& app_name, std::filesystem::create_directories(path.parent_path(), error); if (error) return false; - std::ofstream file(path); + return WriteDesktopEntry(path, + "[Desktop Entry]\n" + "Type=Application\n" + "Exec=" + executable_path + "\n" + "Hidden=false\n" + "NoDisplay=false\n" + "X-GNOME-Autostart-enabled=true\n" + "Terminal=false\n" + "StartupNotify=false\n" + "Name=" + app_name + "\n"); +} + +bool MigrateLegacyAutostart(const std::string& app_name) { + // Development/standalone copies must not take over an installed entry. + if (GetAutostartExecutablePath() != kInstalledLauncher) return true; + const std::filesystem::path path = AutostartPath(app_name); + if (path.empty()) return false; + std::error_code error; + const bool exists = std::filesystem::exists(path, error); + if (error) return false; + if (!exists) return true; // Do not recreate entries removed by the user. + if (!std::filesystem::is_regular_file(path, error) || error) return false; + + std::ifstream file(path); if (!file.is_open()) return false; - file << "[Desktop Entry]\n" - << "Type=Application\n" - << "Exec=" << executable_path << "\n" - << "Hidden=false\n" - << "NoDisplay=false\n" - << "X-GNOME-Autostart-enabled=true\n" - << "Terminal=false\n" - << "StartupNotify=false\n" - << "Name=" << app_name << "\n"; + std::string contents((std::istreambuf_iterator(file)), {}); + if (file.bad()) return false; file.close(); - return file.good(); + + bool desktop_entry = false; + for (size_t start = 0; start < contents.size();) { + const size_t newline = contents.find('\n', start); + const size_t end = newline == std::string::npos ? contents.size() : newline; + size_t line_end = end; + if (line_end > start && contents[line_end - 1] == '\r') --line_end; + const std::string line = contents.substr(start, line_end - start); + if (!line.empty() && line.front() == '[') + desktop_entry = line == "[Desktop Entry]"; + if (desktop_entry && line == std::string("Exec=") + kInstalledExecutable) { + // Preserve Hidden, desktop restrictions and all other user settings. + contents.replace(start, line_end - start, + std::string("Exec=") + kInstalledLauncher); + return WriteDesktopEntry(path, contents); + } + start = end == contents.size() ? end : end + 1; + } + return true; } bool DisableAutostart(const std::string& app_name) { diff --git a/apps/desktop/src/platform/linux/autostart.h b/apps/desktop/src/platform/linux/autostart.h new file mode 100644 index 00000000..54c7e01d --- /dev/null +++ b/apps/desktop/src/platform/linux/autostart.h @@ -0,0 +1,14 @@ +#ifndef CROSSDESK_LINUX_AUTOSTART_H_ +#define CROSSDESK_LINUX_AUTOSTART_H_ + +#include + +namespace crossdesk::platform { + +// Repair the old deb entry at normal startup without enabling missing entries +// or overwriting desktop-managed settings and custom commands. +bool MigrateLegacyAutostart(const std::string& app_name); + +} // namespace crossdesk::platform + +#endif diff --git a/apps/desktop/src/platform/linux/runtime_daemon.cpp b/apps/desktop/src/platform/linux/runtime_daemon.cpp new file mode 100644 index 00000000..7fccb4c0 --- /dev/null +++ b/apps/desktop/src/platform/linux/runtime_daemon.cpp @@ -0,0 +1,320 @@ +#include "platform/linux/runtime_daemon.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +extern char** environ; + +namespace crossdesk::platform { +namespace { + +constexpr int kControlSocket = 3; +constexpr int kStartupTimeoutMs = 3000; + +// A lost GUI socket can mean either a crash or desktop logout. Remember the +// owner's login session before it disappears, and never carry a restart into +// another login that happens to reuse the same DISPLAY (notably under XRDP). +class LoginSession { + public: + explicit LoginSession(pid_t owner) { + library_ = dlopen("libsystemd.so.0", RTLD_LAZY | RTLD_LOCAL); + if (!library_) return; // Non-logind desktops retain crash monitoring. + const auto pid_session = reinterpret_cast( + dlsym(library_, "sd_pid_get_session")); + const auto uid_display = reinterpret_cast( + dlsym(library_, "sd_uid_get_display")); + state_ = reinterpret_cast( + dlsym(library_, "sd_session_get_state")); + if (!state_) return; + char* id = nullptr; + if (pid_session && pid_session(owner, &id) >= 0 && id) { + session_ = id; + } + std::free(id); + // GNOME can start applications in its user manager rather than directly + // in the session scope. Use the supplied session or the user's graphical + // session in that case; a process's actual scope always takes precedence. + if (session_.empty()) { + if (const char* inherited = std::getenv("XDG_SESSION_ID")) + session_ = inherited; + } + if (session_.empty() && uid_display) { + id = nullptr; + if (uid_display(geteuid(), &id) >= 0 && id) session_ = id; + std::free(id); + } + } + + ~LoginSession() { + if (library_) dlclose(library_); + } + + bool Alive() const { + if (!state_ || session_.empty()) return true; + char* state = nullptr; + const int result = state_(session_.c_str(), &state); + // "online" includes a locked/background session and must keep monitoring. + // A removed or closing session must never be restarted. + const bool alive = result >= 0 && state && std::strcmp(state, "closing") != 0; + std::free(state); + return alive; + } + + private: + void* library_ = nullptr; + int (*state_)(const char*, char**) = nullptr; + std::string session_; +}; + +bool Send(int socket, char command) { + ssize_t count; + do { + count = send(socket, &command, 1, MSG_NOSIGNAL); + } while (count < 0 && errno == EINTR); + return count == 1; +} + +bool Receive(int socket, char expected) { + pollfd fd{socket, POLLIN, 0}; + int result; + do { + result = poll(&fd, 1, kStartupTimeoutMs); + } while (result < 0 && errno == EINTR); + if (result <= 0) return false; + char reply = 0; + return recv(socket, &reply, 1, 0) == 1 && reply == expected; +} + +void Reap(int child) { + if (child < 0) return; + const auto deadline = std::chrono::steady_clock::now() + + std::chrono::seconds(2); + while (std::chrono::steady_clock::now() < deadline) { + const auto result = waitpid(child, nullptr, WNOHANG); + if (result == child || (result < 0 && errno != EINTR)) return; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + // This is an unreaped direct child, so its PID cannot have been reused. + kill(child, SIGKILL); + while (waitpid(child, nullptr, 0) < 0 && errno == EINTR) {} +} + +bool CloseInheritedDescriptors() { + // Libraries may own descriptors without CLOEXEC. Keeping those alive in the + // watcher could retain network ports or locks after the GUI crashes. + DIR* directory = opendir("/proc/self/fd"); + if (!directory) return false; + while (const auto* entry = readdir(directory)) { + char* end = nullptr; + const long fd = std::strtol(entry->d_name, &end, 10); + if (*end == '\0' && fd > kControlSocket && fd != dirfd(directory)) + close(static_cast(fd)); + } + closedir(directory); + return true; +} + +} // namespace + +LinuxRuntimeDaemon& LinuxRuntimeDaemon::Instance() { + static LinuxRuntimeDaemon daemon; + return daemon; +} + +LinuxRuntimeDaemon::~LinuxRuntimeDaemon() { Shutdown(); } + +void LinuxRuntimeDaemon::Initialize(int argc, char* argv[], bool allowed) { + std::lock_guard lock(mutex_); + Stop(); + allowed_ = allowed; + arguments_.clear(); + char path[PATH_MAX]{}; + const ssize_t count = readlink("/proc/self/exe", path, sizeof(path)); + if (count <= 0 || count >= static_cast(sizeof(path))) return; + arguments_.emplace_back(path, static_cast(count)); + arguments_.emplace_back(kLinuxDaemonArgument); + bool no_headless = false; + for (int i = 1; i < argc; ++i) { + arguments_.emplace_back(argv[i]); + no_headless |= arguments_.back() == "--no-headless"; + } + // A desktop watchdog must never create a headless display after logout. + if (!no_headless) arguments_.emplace_back("--no-headless"); +} + +bool LinuxRuntimeDaemon::SetEnabled(bool enabled, std::string& error) { + std::lock_guard lock(mutex_); + error.clear(); + // Configuration-only callers and the headless supervisor do not own a GUI + // watchdog. Their preference is still saved for the next desktop launch. + if (!allowed_ || !enabled) { + Stop(); + return true; + } + if (child_ >= 0) { + pollfd fd{socket_, POLLIN, 0}; + if (poll(&fd, 1, 0) == 0) return true; + Stop(); + } + if (arguments_.empty()) { + error = "Cannot resolve the executable for the daemon"; + return false; + } + int sockets[2]; + if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) { + error = std::strerror(errno); + return false; + } + + posix_spawn_file_actions_t actions; + posix_spawnattr_t attributes; + int result = posix_spawn_file_actions_init(&actions); + const bool actions_ready = result == 0; + bool attributes_ready = false; + if (result == 0) { + result = posix_spawnattr_init(&attributes); + attributes_ready = result == 0; + } + if (result == 0) + result = posix_spawn_file_actions_adddup2(&actions, sockets[1], kControlSocket); + if (result == 0 && sockets[0] != kControlSocket) + result = posix_spawn_file_actions_addclose(&actions, sockets[0]); + if (result == 0 && sockets[1] != kControlSocket) + result = posix_spawn_file_actions_addclose(&actions, sockets[1]); + sigset_t empty, defaults; + sigemptyset(&empty); + sigfillset(&defaults); + if (result == 0) result = posix_spawnattr_setsigmask(&attributes, &empty); + if (result == 0) result = posix_spawnattr_setsigdefault(&attributes, &defaults); + if (result == 0) + result = posix_spawnattr_setflags( + &attributes, POSIX_SPAWN_SETSID | POSIX_SPAWN_SETSIGMASK | + POSIX_SPAWN_SETSIGDEF); + std::vector args; + for (auto& argument : arguments_) args.push_back(argument.data()); + args.push_back(nullptr); + pid_t child = -1; + if (result == 0) + result = posix_spawn(&child, "/proc/self/exe", &actions, &attributes, + args.data(), environ); + if (attributes_ready) posix_spawnattr_destroy(&attributes); + if (actions_ready) posix_spawn_file_actions_destroy(&actions); + close(sockets[1]); + if (result != 0) { + close(sockets[0]); + error = "Cannot start daemon: " + std::string(std::strerror(result)); + return false; + } + socket_ = sockets[0]; + child_ = child; + // Do not report an enabled setting until the helper is ready and armed. + if (!Receive(socket_, 'R') || !Send(socket_, 'A') || !Receive(socket_, 'A')) { + error = "Daemon did not acknowledge startup"; + Stop(); + return false; + } + return true; +} + +void LinuxRuntimeDaemon::Stop() { + if (socket_ >= 0) { + Send(socket_, 'S'); + close(socket_); + socket_ = -1; + } + Reap(child_); + child_ = -1; +} + +void LinuxRuntimeDaemon::Shutdown(bool restart) { + std::lock_guard lock(mutex_); + if (!restart) { + Stop(); + } else { + if (socket_ >= 0) close(socket_); + socket_ = -1; + child_ = -1; + } +} + +int RunLinuxRuntimeDaemon(int argc, char* argv[]) { + int type = 0; + socklen_t size = sizeof(type); + ucred peer{}; + socklen_t peer_size = sizeof(peer); + if (argc < 2 || argv[0][0] != '/' || + getsockopt(kControlSocket, SOL_SOCKET, SO_TYPE, &type, &size) != 0 || + type != SOCK_SEQPACKET || + getsockopt(kControlSocket, SOL_SOCKET, SO_PEERCRED, &peer, &peer_size) != 0 || + peer.uid != geteuid() || !CloseInheritedDescriptors()) return 1; + LoginSession session(peer.pid); + if (!session.Alive()) return 1; + if (fcntl(kControlSocket, F_SETFD, FD_CLOEXEC) != 0 || + !Send(kControlSocket, 'R')) return 1; + + // Track the original process, not a potentially reused PID. A failed render + // can close the socket before its destructors have released instance locks. + int owner = -1; +#ifdef SYS_pidfd_open + owner = static_cast(syscall(SYS_pidfd_open, peer.pid, 0)); +#endif + + bool armed = false; + for (;;) { + if (!session.Alive()) return 0; + pollfd control{kControlSocket, POLLIN, 0}; + const int ready = poll(&control, 1, 1000); + if (ready < 0 && errno == EINTR) continue; + if (ready < 0) return 1; + if (ready == 0) continue; + char command = 0; + const auto count = recv(kControlSocket, &command, 1, 0); + if (count < 0 && errno == EINTR) continue; + if (count == 0 || (count < 0 && errno == ECONNRESET)) break; + if (count < 0) return 1; + if (command == 'S') return 0; + if (command != 'A' || armed) return 1; + armed = true; + // Even if the GUI disappears before receiving this acknowledgement, keep + // reading: a queued stop disarms us; otherwise the lost owner is restarted. + Send(kControlSocket, 'A'); + } + close(kControlSocket); + if (!armed) return 0; + if (owner >= 0) { + pollfd fd{owner, POLLIN, 0}; + int result; + do { + if (!session.Alive()) return 0; + result = poll(&fd, 1, 1000); + } while (result == 0 || (result < 0 && errno == EINTR)); + close(owner); + if (result < 0) return 1; + } + // Let process teardown release its single-instance leases, and rate-limit + // repeated startup failures. The new GUI acquires the normal leases itself. + std::this_thread::sleep_for(std::chrono::seconds(1)); + if (!session.Alive()) return 0; + std::vector arguments{argv[0]}; + for (int i = 2; i < argc; ++i) arguments.push_back(argv[i]); + arguments.push_back(nullptr); + execv(argv[0], arguments.data()); + return 1; +} + +} // namespace crossdesk::platform diff --git a/apps/desktop/src/platform/linux/runtime_daemon.h b/apps/desktop/src/platform/linux/runtime_daemon.h new file mode 100644 index 00000000..102cf921 --- /dev/null +++ b/apps/desktop/src/platform/linux/runtime_daemon.h @@ -0,0 +1,40 @@ +#ifndef CROSSDESK_PLATFORM_LINUX_RUNTIME_DAEMON_H_ +#define CROSSDESK_PLATFORM_LINUX_RUNTIME_DAEMON_H_ + +#include +#include +#include + +namespace crossdesk::platform { + +inline constexpr char kLinuxDaemonArgument[] = "--linux-daemon"; + +// A separate process watches this GUI over a private socket. It can be attached +// after the GUI has started, without forking its threads or restarting sessions. +class LinuxRuntimeDaemon { + public: + static LinuxRuntimeDaemon& Instance(); + ~LinuxRuntimeDaemon(); + + void Initialize(int argc, char* argv[], bool allowed); + bool SetEnabled(bool enabled, std::string& error); + // Normal exit disarms the watcher. An unsuccessful application exit lets it + // restart, just like an unexpected loss of the control socket. + void Shutdown(bool restart = false); + + private: + LinuxRuntimeDaemon() = default; + void Stop(); + std::mutex mutex_; + bool allowed_ = false; + std::vector arguments_; + int socket_ = -1; + int child_ = -1; +}; + +// Internal entry point, dispatched before logging, display or instance locks. +int RunLinuxRuntimeDaemon(int argc, char* argv[]); + +} // namespace crossdesk::platform + +#endif diff --git a/apps/desktop/xmake/targets.lua b/apps/desktop/xmake/targets.lua index 86552d2c..8aed5d09 100644 --- a/apps/desktop/xmake/targets.lua +++ b/apps/desktop/xmake/targets.lua @@ -509,11 +509,22 @@ function setup_targets() target("config_center") set_kind("object") add_deps("rd_log", "autostart") + if is_os("linux") then + add_deps("linux_runtime_daemon") + end add_files("apps/desktop/src/config_center/*.cpp") add_includedirs("apps/desktop/src", "apps/desktop/src/config_center", "deps/submodules/minirtc/src/api", {public = true}) + if is_os("linux") then + target("linux_runtime_daemon") + set_kind("static") + add_includedirs("apps/desktop/src", {public = true}) + add_files("apps/desktop/src/platform/linux/runtime_daemon.cpp") + add_syslinks("dl", {public = true}) + end + target("assets") set_kind("headeronly") add_includedirs("apps/desktop/src/gui/assets/localization", diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 665bbbc2..636810f2 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -157,7 +157,8 @@ sudo apt install "./crossdesk-virtual-desktop-xfce-linux-amd64-.deb" | 保存远程画面预览 | 默认开启,保存远程画面预览;关闭后缩略图区域显示被控端平台,并清除已有预览,保留连接记录和密码 | | 被连接时开启隐私屏 | 设置本机被远程连接时是否自动启用隐私屏;会话中可从控制端工具栏切换 | | 自托管配置 | 点击“编辑”填写服务器地址和信令服务端口,使用右侧开关启用 | -| 开机自启 / 启用守护进程 | 调整启动与进程守护行为,按界面提示重启后生效 | +| 开机自启 | 保存后设置登录桌面时自动启动 | +| 启用守护进程 | Linux 桌面模式保存后立即启用或停用异常退出后的自动重启,无需重启程序;正常退出不会重新拉起。Windows / macOS 下次启动生效,无头控制台模式不启用内置守护 | | 文件保存路径 | 选择接收文件的目录 | 网络连接分组中的 IP 直连开关独立于 ID 连接使用的中继设置: