diff --git a/apps/desktop/src/app/daemon.cpp b/apps/desktop/src/app/daemon.cpp deleted file mode 100644 index 7e3474a7..00000000 --- a/apps/desktop/src/app/daemon.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include "daemon.h" - -#include -#include -#include -#include - -#include "platform/daemon_backend.h" - -namespace { - -constexpr int kRestartDelayMs = 1000; - -} // namespace - -Daemon::Daemon(const std::string& name) : name_(name), running_(false) {} - -void Daemon::stop() { - running_.store(false); - crossdesk::platform::RequestDaemonStop(); -} - -bool Daemon::isRunning() const { - return running_.load() && !crossdesk::platform::DaemonStopRequested(); -} - -bool Daemon::start(MainLoopFunc loop) { - crossdesk::platform::ResetDaemonStop(); - if (!crossdesk::platform::PrepareDaemon()) return false; - running_.store(true); - return runWithRestart(std::move(loop)); -} - -bool Daemon::runWithRestart(MainLoopFunc loop) { - int restart_count = 0; - const std::string executable_path = - crossdesk::platform::GetDaemonExecutablePath(); - if (executable_path.empty()) { - std::cerr - << "Failed to get executable path, falling back to direct execution" - << std::endl; - while (isRunning()) { - try { - loop(); - break; - } catch (...) { - ++restart_count; - std::cerr << "Exception caught, restarting... (attempt " - << restart_count << ")" << std::endl; - std::this_thread::sleep_for( - std::chrono::milliseconds(kRestartDelayMs)); - } - } - return true; - } - - while (isRunning()) { - const crossdesk::platform::DaemonChildResult result = - crossdesk::platform::RunDaemonChild( - executable_path, [this] { return isRunning(); }); - if (!isRunning() || result.normal_exit) break; - - ++restart_count; - if (!result.started) { - std::cerr << "Failed to create child process"; - } else if (result.terminated_by_signal) { - std::cerr << "Child process crashed with signal " << result.exit_code; - } else { - std::cerr << "Child process exited with code " << result.exit_code; - } - std::cerr << ", restarting... (attempt " << restart_count << ")" - << std::endl; - std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs)); - } - return true; -} diff --git a/apps/desktop/src/app/daemon.h b/apps/desktop/src/app/daemon.h deleted file mode 100644 index 45715bcd..00000000 --- a/apps/desktop/src/app/daemon.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * @Author: DI JUNKUN - * @Date: 2025-11-19 - * Copyright (c) 2025 by DI JUNKUN, All Rights Reserved. - */ - -#ifndef _DAEMON_H_ -#define _DAEMON_H_ - -#include -#include -#include - -class Daemon { - public: - using MainLoopFunc = std::function; - - Daemon(const std::string& name); - - bool start(MainLoopFunc loop); - - void stop(); - - bool isRunning() const; - - private: - std::string name_; - bool runWithRestart(MainLoopFunc loop); - - std::atomic running_; -}; - -#endif diff --git a/apps/desktop/src/app/main.cpp b/apps/desktop/src/app/main.cpp index 12a79daf..59bb55bf 100644 --- a/apps/desktop/src/app/main.cpp +++ b/apps/desktop/src/app/main.cpp @@ -28,7 +28,6 @@ #endif #include "config_center.h" -#include "daemon.h" #include "path_manager.h" #include "platform/instance_activation.h" #include "platform/single_instance.h" @@ -47,6 +46,10 @@ #include "platform/linux/runtime_daemon.h" #endif +#ifdef __APPLE__ +#include "platform/macos/runtime_daemon.h" +#endif + #ifdef _WIN32 namespace { @@ -275,9 +278,7 @@ static int ExitForExistingInstance( return result == crossdesk::platform::InstanceResult::kAlreadyRunning ? 0 : 1; } -static int RunApplication( - int argc, char* argv[], - crossdesk::platform::SingleInstanceGuard& client_instance) { +static int RunApplication(int argc, char* argv[]) { // The instance guards are held before any configuration or logger is opened. auto path_manager = std::make_unique("CrossDesk"); crossdesk::InitLogger(path_manager->GetLogPath().string()); @@ -289,7 +290,7 @@ static int RunApplication( #endif if (IsDaemonChild(argc, argv)) { - // child process: run render directly + // Compatibility with a supervisor from an older, still-running build. crossdesk::Render render; return render.Run(); } @@ -327,62 +328,34 @@ static int RunApplication( 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; #elif defined(_WIN32) auto& runtime_daemon = crossdesk::platform::WindowsRuntimeDaemon::Instance(); runtime_daemon.Initialize(); +#elif defined(__APPLE__) + auto& runtime_daemon = crossdesk::platform::MacRuntimeDaemon::Instance(); + runtime_daemon.Initialize(argc, argv); +#endif +#if defined(__linux__) || defined(_WIN32) || defined(__APPLE__) std::string daemon_error; if (!runtime_daemon.SetEnabled(enable_daemon, daemon_error)) { crossdesk::get_logger()->warn("Could not start daemon: {}", daemon_error); } +#endif crossdesk::Render render; const int result = render.Run(); +#if defined(__linux__) || defined(_WIN32) || defined(__APPLE__) runtime_daemon.Shutdown(result != 0); - return result; -#else - - if (enable_daemon) { - // The launcher lease stays in main for the supervisor's entire lifetime. - // Each --child must acquire its own client lease, so it is neither blocked - // by its supervisor nor able to bypass the single-client restriction. - client_instance.Release(); - // start daemon with restart monitoring - Daemon daemon("CrossDesk"); - - // define main loop function: run render and stop daemon on normal exit - Daemon::MainLoopFunc main_loop = [&daemon, &client_instance]() { - // Executable discovery can fail and make the supervisor run the GUI in - // process. This fallback must retain the same client ownership rule. - if (AcquireInstance(client_instance, - crossdesk::platform::InstanceRole::kClient) != - crossdesk::platform::InstanceResult::kAcquired) { - daemon.stop(); - return; - } - crossdesk::Render render; - render.Run(); - daemon.stop(); - }; - - // start daemon and return result - bool success = daemon.start(main_loop); - return success ? 0 : 1; - } - - // run without daemon: direct execution - crossdesk::Render render; - return render.Run(); #endif + return result; } int main(int argc, char* argv[]) { +#ifdef __APPLE__ + if (argc >= 2 && + std::strcmp(argv[1], crossdesk::platform::kMacDaemonArgument) == 0) { + return crossdesk::platform::RunMacRuntimeDaemon(argc, argv); + } +#endif #ifdef _WIN32 if (argc >= 2 && std::strcmp(argv[1], crossdesk::platform::kWindowsDaemonArgument) == 0) { @@ -503,7 +476,7 @@ int main(int argc, char* argv[]) { #ifdef __linux__ const int exit_code = crossdesk::RunWithLinuxDisplay( - argc, argv, [&] { return RunApplication(argc, argv, client_instance); }, [&] { + argc, argv, [&] { return RunApplication(argc, argv); }, [&] { crossdesk::PathManager paths("CrossDesk"); if (console.Enable(paths.GetLogPath(), paths.GetCachePath() / "config.ini")) return true; std::cerr << "无法初始化日志或控制台,启动失败。\n"; @@ -516,6 +489,6 @@ int main(int argc, char* argv[]) { } return exit_code; #else - return RunApplication(argc, argv, client_instance); + return RunApplication(argc, argv); #endif } diff --git a/apps/desktop/src/config_center/config_center.cpp b/apps/desktop/src/config_center/config_center.cpp index 190b78dd..55ea17e1 100644 --- a/apps/desktop/src/config_center/config_center.cpp +++ b/apps/desktop/src/config_center/config_center.cpp @@ -17,6 +17,10 @@ #include "platform/windows/service/autostart_control.h" #endif +#ifdef __APPLE__ +#include "platform/macos/runtime_daemon.h" +#endif + #if defined(__linux__) && !defined(__OHOS__) #include "platform/linux/autostart.h" #include "platform/linux/runtime_daemon.h" @@ -459,15 +463,17 @@ int ConfigCenter::SetAutostart(bool enabled) { } int ConfigCenter::SetDaemon(bool enabled) { -#if defined(_WIN32) || (defined(__linux__) && !defined(__OHOS__)) +#if defined(_WIN32) || defined(__APPLE__) || (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(_WIN32) || (defined(__linux__) && !defined(__OHOS__)) +#if defined(_WIN32) || defined(__APPLE__) || (defined(__linux__) && !defined(__OHOS__)) std::string error; #ifdef _WIN32 auto& daemon = platform::WindowsRuntimeDaemon::Instance(); +#elif defined(__APPLE__) + auto& daemon = platform::MacRuntimeDaemon::Instance(); #else auto& daemon = platform::LinuxRuntimeDaemon::Instance(); #endif diff --git a/apps/desktop/src/platform/common/posix_daemon_backend.cpp b/apps/desktop/src/platform/common/posix_daemon_backend.cpp deleted file mode 100644 index da3d185b..00000000 --- a/apps/desktop/src/platform/common/posix_daemon_backend.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "platform/daemon_backend.h" - -#include -#include -#include -#include - -#include -#include - -namespace crossdesk::platform { - -volatile std::sig_atomic_t g_daemon_stop_requested = 0; - -void ResetDaemonStop() { g_daemon_stop_requested = 0; } - -void RequestDaemonStop() { g_daemon_stop_requested = 1; } - -bool DaemonStopRequested() { return g_daemon_stop_requested != 0; } - -DaemonChildResult RunDaemonChild(const std::string& executable_path, - const std::function& keep_running) { - DaemonChildResult result; - const pid_t child = fork(); - if (child == 0) { - execl(executable_path.c_str(), executable_path.c_str(), "--child", - nullptr); - _exit(1); - } - if (child < 0) return result; - - result.started = true; - int status = 0; - pid_t waited = -1; - while (keep_running()) { - waited = waitpid(child, &status, WNOHANG); - if (waited == child || (waited < 0 && errno != EINTR)) break; - std::this_thread::sleep_for(std::chrono::milliseconds(200)); - } - if (!keep_running() && waited != child) { - kill(child, SIGTERM); - waited = waitpid(child, &status, 0); - } - if (waited < 0) return result; - - if (WIFEXITED(status)) { - result.exit_code = WEXITSTATUS(status); - result.normal_exit = result.exit_code == 0; - } else if (WIFSIGNALED(status)) { - result.terminated_by_signal = true; - result.exit_code = WTERMSIG(status); - } - return result; -} - -} // namespace crossdesk::platform diff --git a/apps/desktop/src/platform/daemon_backend.h b/apps/desktop/src/platform/daemon_backend.h deleted file mode 100644 index 7eb4e3dc..00000000 --- a/apps/desktop/src/platform/daemon_backend.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef CROSSDESK_PLATFORM_DAEMON_BACKEND_H_ -#define CROSSDESK_PLATFORM_DAEMON_BACKEND_H_ - -#include -#include -#include - -namespace crossdesk::platform { - -extern volatile std::sig_atomic_t g_daemon_stop_requested; - -struct DaemonChildResult { - bool started = false; - bool normal_exit = false; - bool terminated_by_signal = false; - int exit_code = 0; -}; - -void ResetDaemonStop(); -void RequestDaemonStop(); -bool DaemonStopRequested(); -bool PrepareDaemon(); -std::string GetDaemonExecutablePath(); -DaemonChildResult RunDaemonChild(const std::string& executable_path, - const std::function& keep_running); - -} // namespace crossdesk::platform - -#endif // CROSSDESK_PLATFORM_DAEMON_BACKEND_H_ diff --git a/apps/desktop/src/platform/linux/daemon_backend.cpp b/apps/desktop/src/platform/linux/daemon_backend.cpp deleted file mode 100644 index 54b5043e..00000000 --- a/apps/desktop/src/platform/linux/daemon_backend.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "platform/daemon_backend.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace crossdesk::platform { - -bool PrepareDaemon() { - const bool from_terminal = - isatty(STDIN_FILENO) != 0 || isatty(STDOUT_FILENO) != 0; - pid_t process = fork(); - if (process < 0) return false; - if (process > 0) _exit(0); - if (setsid() < 0) return false; - - process = fork(); - if (process < 0) return false; - if (process > 0) _exit(0); - - umask(0); - if (chdir("/") != 0) { - std::cerr << "Failed to change daemon working directory to /: " - << std::strerror(errno) << std::endl; - return false; - } - - const int null_fd = open("/dev/null", O_RDWR); - if (null_fd >= 0) { - dup2(null_fd, STDIN_FILENO); - if (!from_terminal) { - dup2(null_fd, STDOUT_FILENO); - dup2(null_fd, STDERR_FILENO); - } - if (null_fd > STDERR_FILENO) close(null_fd); - } - - signal(SIGTERM, [](int) { g_daemon_stop_requested = 1; }); - signal(SIGINT, [](int) { g_daemon_stop_requested = 1; }); - signal(SIGPIPE, SIG_IGN); - return true; -} - -std::string GetDaemonExecutablePath() { - 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(); -} - -} // namespace crossdesk::platform diff --git a/apps/desktop/src/platform/macos/daemon_backend.cpp b/apps/desktop/src/platform/macos/daemon_backend.cpp deleted file mode 100644 index 23482e7e..00000000 --- a/apps/desktop/src/platform/macos/daemon_backend.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "platform/daemon_backend.h" - -#include -#include - -#include - -namespace crossdesk::platform { - -bool PrepareDaemon() { return true; } - -std::string GetDaemonExecutablePath() { - char path[PATH_MAX] = {}; - uint32_t size = sizeof(path); - if (_NSGetExecutablePath(path, &size) != 0) return {}; - char resolved[PATH_MAX] = {}; - return realpath(path, resolved) ? std::string(resolved) : std::string(path); -} - -} // namespace crossdesk::platform diff --git a/apps/desktop/src/platform/macos/runtime_daemon.h b/apps/desktop/src/platform/macos/runtime_daemon.h new file mode 100644 index 00000000..3ebc0485 --- /dev/null +++ b/apps/desktop/src/platform/macos/runtime_daemon.h @@ -0,0 +1,40 @@ +#ifndef CROSSDESK_PLATFORM_MACOS_RUNTIME_DAEMON_H_ +#define CROSSDESK_PLATFORM_MACOS_RUNTIME_DAEMON_H_ + +#include +#include +#include + +namespace crossdesk::platform { + +inline constexpr char kMacDaemonArgument[] = "--macos-daemon"; + +// Attach a separate crash watcher to the running GUI without restarting it or +// forking its threads. The watcher shares the GUI's login session. +class MacRuntimeDaemon { + public: + static MacRuntimeDaemon& Instance(); + ~MacRuntimeDaemon(); + + void Initialize(int argc, char* argv[]); + bool SetEnabled(bool enabled, std::string& error); + // Normal exit disarms the watcher; a failed startup lets it restart after + // this process has exited and released its single-instance leases. + void Shutdown(bool restart = false); + + private: + MacRuntimeDaemon() = default; + void Stop(); + std::mutex mutex_; + bool initialized_ = false; + std::vector arguments_; + int socket_ = -1; + int child_ = -1; +}; + +// Dispatch on the main thread before logging, GUI setup or instance locks. +int RunMacRuntimeDaemon(int argc, char* argv[]); + +} // namespace crossdesk::platform + +#endif diff --git a/apps/desktop/src/platform/macos/runtime_daemon.mm b/apps/desktop/src/platform/macos/runtime_daemon.mm new file mode 100644 index 00000000..a4e3b442 --- /dev/null +++ b/apps/desktop/src/platform/macos/runtime_daemon.mm @@ -0,0 +1,334 @@ +#include "platform/macos/runtime_daemon.h" + +#import +#import + +#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; +constexpr int kPollIntervalMs = 100; + +struct Descriptor { + int value = -1; + ~Descriptor() { if (value >= 0) close(value); } +}; + +std::string ExecutablePath() { + uint32_t size = 0; + _NSGetExecutablePath(nullptr, &size); + std::vector path(size); + if (!size || _NSGetExecutablePath(path.data(), &size) != 0) return {}; + char* resolved = realpath(path.data(), nullptr); + if (!resolved) return {}; + const std::string result(resolved); + std::free(resolved); + return result; +} + +bool ConfigureSocket(int socket) { + const int enabled = 1; + return fcntl(socket, F_SETFD, FD_CLOEXEC) == 0 && + setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &enabled, + sizeof(enabled)) == 0; +} + +bool Send(int socket, char command) { + ssize_t count; + do { + count = send(socket, &command, 1, 0); + } 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)); + } + // Still an unreaped direct child: its PID cannot have been reused. + kill(child, SIGKILL); + while (waitpid(child, nullptr, 0) < 0 && errno == EINTR) {} +} + +class LoginSession { + public: + LoginSession() { + // NSWorkspace does not require creating NSApplication or another Dock icon. + // Only logout/poweroff ends monitoring; sleep, lock and user switching do + // not end the login session. + center_ = NSWorkspace.sharedWorkspace.notificationCenter; + const auto ending = ending_; + observer_ = [center_ addObserverForName:NSWorkspaceWillPowerOffNotification + object:nil queue:nil + usingBlock:^(NSNotification*) { + ending->store(true); + }]; + } + ~LoginSession() { if (observer_) [center_ removeObserver:observer_]; } + + bool Alive() { + // The watcher has no GUI event loop. Deliver workspace notifications even + // while waiting for process teardown or delaying a crash restart. + @autoreleasepool { + while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) == + kCFRunLoopRunHandledSource) {} + return observer_ != nil && !ending_->load(); + } + } + + private: + std::shared_ptr> ending_ = + std::make_shared>(false); + NSNotificationCenter* center_ = nil; + id observer_ = nil; +}; + +int Watch(int argc, char* argv[]) { + Descriptor control{kControlSocket}; + int type = 0; + socklen_t size = sizeof(type); + uid_t user = 0; + gid_t group = 0; + const pid_t owner = getppid(); + if (owner <= 1 || + getsockopt(control.value, SOL_SOCKET, SO_TYPE, &type, &size) != 0 || + type != SOCK_STREAM || getpeereid(control.value, &user, &group) != 0 || + user != geteuid() || !ConfigureSocket(control.value)) return 1; + + Descriptor events{kqueue()}; + struct kevent change{}; + EV_SET(&change, owner, EVFILT_PROC, EV_ADD | EV_ONESHOT, NOTE_EXIT, 0, nullptr); + if (events.value < 0 || fcntl(events.value, F_SETFD, FD_CLOEXEC) != 0 || + kevent(events.value, &change, 1, nullptr, 0, nullptr) != 0) return 1; + + LoginSession session; + if (!session.Alive() || !Send(control.value, 'R')) return 1; + bool armed = false; + for (;;) { + if (!session.Alive()) return 0; + pollfd fd{control.value, POLLIN, 0}; + const int ready = poll(&fd, 1, kPollIntervalMs); + if (ready < 0 && errno == EINTR) continue; + if (ready < 0) return 1; + if (ready == 0) continue; + char command = 0; + const auto count = recv(control.value, &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; + // Drain a queued stop even if the GUI disappears before receiving this. + Send(control.value, 'A'); + } + if (!armed) return 0; + + // Closing the control socket can precede the GUI's destructors. kqueue + // watches the original process instance, so PID reuse cannot fool this wait. + for (;;) { + if (!session.Alive()) return 0; + struct kevent event{}; + const timespec timeout{0, kPollIntervalMs * 1000000}; + const int count = kevent(events.value, nullptr, 0, &event, 1, &timeout); + if (count < 0 && errno == EINTR) continue; + if (count < 0 || (count > 0 && (event.flags & EV_ERROR))) return 1; + if (count > 0 && (event.fflags & NOTE_EXIT)) break; + } + const auto restart_at = std::chrono::steady_clock::now() + + std::chrono::seconds(1); + do { + if (!session.Alive()) return 0; + std::this_thread::sleep_for(std::chrono::milliseconds(kPollIntervalMs)); + } while (std::chrono::steady_clock::now() < restart_at); + 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 + +MacRuntimeDaemon& MacRuntimeDaemon::Instance() { + static MacRuntimeDaemon daemon; + return daemon; +} + +MacRuntimeDaemon::~MacRuntimeDaemon() { Shutdown(); } + +void MacRuntimeDaemon::Initialize(int argc, char* argv[]) { + std::lock_guard lock(mutex_); + Stop(); + initialized_ = true; + arguments_.clear(); + const auto executable = ExecutablePath(); + if (executable.empty()) return; + arguments_.push_back(executable); + arguments_.emplace_back(kMacDaemonArgument); + for (int i = 1; i < argc; ++i) arguments_.emplace_back(argv[i]); +} + +bool MacRuntimeDaemon::SetEnabled(bool enabled, std::string& error) { + std::lock_guard lock(mutex_); + error.clear(); + // Configuration-only callers do not own a GUI watcher. + if (!initialized_) return true; + if (!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_STREAM, 0, sockets) != 0) { + error = std::strerror(errno); + return false; + } + // Keep IPC away from stdio and the fixed child descriptor, including when + // the application was launched with one or more standard streams closed. + Descriptor parent{fcntl(sockets[0], F_DUPFD_CLOEXEC, kControlSocket + 1)}; + Descriptor child_socket{fcntl(sockets[1], F_DUPFD_CLOEXEC, kControlSocket + 1)}; + const int duplicate_error = errno; + close(sockets[0]); + close(sockets[1]); + if (parent.value < 0 || child_socket.value < 0) { + error = std::strerror(duplicate_error); + return false; + } + if (!ConfigureSocket(parent.value) || !ConfigureSocket(child_socket.value)) { + 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, child_socket.value, + kControlSocket); + // Keep only the control socket and standard streams. In particular, never + // retain GUI sockets, files or single-instance locks after a crash. + for (int fd = 0; result == 0 && fd <= STDERR_FILENO; ++fd) { + result = fcntl(fd, F_GETFD) >= 0 + ? posix_spawn_file_actions_addinherit_np(&actions, fd) + : posix_spawn_file_actions_addopen(&actions, fd, "/dev/null", O_RDWR, 0); + } + 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_setpgroup(&attributes, 0); + if (result == 0) + result = posix_spawnattr_setflags(&attributes, + POSIX_SPAWN_CLOEXEC_DEFAULT | POSIX_SPAWN_SETPGROUP | + 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, arguments_[0].c_str(), &actions, &attributes, + args.data(), environ); + if (attributes_ready) posix_spawnattr_destroy(&attributes); + if (actions_ready) posix_spawn_file_actions_destroy(&actions); + if (result != 0) { + error = "Cannot start daemon: " + std::string(std::strerror(result)); + return false; + } + close(child_socket.value); + child_socket.value = -1; + socket_ = parent.value; + parent.value = -1; + child_ = child; + if (!Receive(socket_, 'R') || !Send(socket_, 'A') || !Receive(socket_, 'A')) { + error = "Daemon did not acknowledge startup"; + Stop(); + return false; + } + return true; +} + +void MacRuntimeDaemon::Stop() { + if (socket_ >= 0) { + Send(socket_, 'S'); + close(socket_); + socket_ = -1; + } + Reap(child_); + child_ = -1; +} + +void MacRuntimeDaemon::Shutdown(bool restart) { + std::lock_guard lock(mutex_); + if (!restart) { + Stop(); + } else { + if (socket_ >= 0) close(socket_); + socket_ = -1; + child_ = -1; + } +} + +int RunMacRuntimeDaemon(int argc, char* argv[]) { + if (argc < 2 || std::strcmp(argv[1], kMacDaemonArgument) != 0 || + argv[0][0] != '/') return 1; + @autoreleasepool { + return Watch(argc, argv); + } +} + +} // namespace crossdesk::platform diff --git a/apps/desktop/src/platform/windows/daemon_backend.cpp b/apps/desktop/src/platform/windows/daemon_backend.cpp deleted file mode 100644 index dccf4706..00000000 --- a/apps/desktop/src/platform/windows/daemon_backend.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "platform/daemon_backend.h" - -#include - -#include -#include -#include -#include -#include - -namespace crossdesk::platform { -namespace { - -std::atomic g_stop_requested{false}; - -} // namespace - -void ResetDaemonStop() { g_stop_requested.store(false); } - -void RequestDaemonStop() { g_stop_requested.store(true); } - -bool DaemonStopRequested() { return g_stop_requested.load(); } - -bool PrepareDaemon() { return true; } - -std::string GetDaemonExecutablePath() { - wchar_t path[32768] = {}; - const DWORD length = GetModuleFileNameW(nullptr, path, 32768); - return length > 0 && length < 32768 - ? std::filesystem::path(path, path + length).u8string() - : std::string(); -} - -DaemonChildResult RunDaemonChild(const std::string& executable_path, - const std::function& keep_running) { - DaemonChildResult result; - STARTUPINFOW startup = {sizeof(startup)}; - PROCESS_INFORMATION process = {}; - const auto path = std::filesystem::u8path(executable_path).wstring(); - std::wstring command = L"\"" + path + L"\" --child"; - if (!CreateProcessW(path.c_str(), command.data(), nullptr, nullptr, FALSE, - 0, nullptr, nullptr, &startup, &process)) { - return result; - } - - result.started = true; - while (keep_running()) { - const DWORD wait_result = WaitForSingleObject(process.hProcess, 200); - if (wait_result == WAIT_OBJECT_0 || wait_result == WAIT_FAILED) break; - } - if (!keep_running()) { - TerminateProcess(process.hProcess, 1); - WaitForSingleObject(process.hProcess, 3000); - } - - DWORD exit_code = 0; - GetExitCodeProcess(process.hProcess, &exit_code); - result.exit_code = static_cast(exit_code); - result.normal_exit = exit_code == 0; - CloseHandle(process.hProcess); - CloseHandle(process.hThread); - return result; -} - -} // namespace crossdesk::platform diff --git a/apps/desktop/xmake/targets.lua b/apps/desktop/xmake/targets.lua index 700c00ea..4270cf34 100644 --- a/apps/desktop/xmake/targets.lua +++ b/apps/desktop/xmake/targets.lua @@ -533,6 +533,8 @@ function setup_targets() add_deps("linux_runtime_daemon") elseif is_os("windows") then add_deps("windows_runtime_daemon") + elseif is_os("macosx") then + add_deps("macos_runtime_daemon") end add_files("apps/desktop/src/config_center/*.cpp") add_includedirs("apps/desktop/src", "apps/desktop/src/config_center", @@ -547,6 +549,15 @@ function setup_targets() add_syslinks("dl", {public = true}) end + if is_os("macosx") then + target("macos_runtime_daemon") + set_kind("static") + add_includedirs("apps/desktop/src", {public = true}) + add_files("apps/desktop/src/platform/macos/runtime_daemon.mm") + add_mxxflags("-fobjc-arc") + add_frameworks("AppKit", "CoreFoundation", {public = true}) + end + target("assets") set_kind("headeronly") add_includedirs("apps/desktop/src/gui/assets/localization", @@ -775,8 +786,7 @@ function setup_targets() add_files("apps/desktop/src/app/*.cpp") add_includedirs("apps/desktop/src", "apps/desktop/src/app", {public = true}) if is_os("windows") then - add_files("apps/desktop/src/platform/windows/daemon_backend.cpp", - "apps/desktop/src/platform/windows/service/autostart_control.cpp") + add_files("apps/desktop/src/platform/windows/service/autostart_control.cpp") add_files("apps/desktop/src/platform/windows/service/service_host.cpp", "apps/desktop/src/platform/windows/service/rdp_session_handoff.cpp") add_includedirs("apps/desktop/src/platform/windows/service", @@ -784,13 +794,8 @@ function setup_targets() add_links("Advapi32", "Wtsapi32", "Ole32", "Userenv") add_deps("wgc_plugin", "crossdesk_service", "crossdesk_session_helper") add_files(crossdesk_windows_resource, {defines = windows_resource_defines}) - elseif is_os("macosx") then - add_files("apps/desktop/src/platform/common/posix_daemon_backend.cpp", - "apps/desktop/src/platform/macos/daemon_backend.cpp") elseif is_os("linux") then - add_files("apps/desktop/src/platform/common/posix_daemon_backend.cpp", - "apps/desktop/src/platform/linux/daemon_backend.cpp", - "apps/desktop/src/platform/linux/headless/headless_session.cpp") + add_files("apps/desktop/src/platform/linux/headless/headless_session.cpp") end add_rules("crossdesk.slint_runtime") after_build(copy_usbmmidd_package)