diff --git a/apps/desktop/src/app/main.cpp b/apps/desktop/src/app/main.cpp index 9501a2a0..327a6dea 100644 --- a/apps/desktop/src/app/main.cpp +++ b/apps/desktop/src/app/main.cpp @@ -23,6 +23,7 @@ #include "config_center.h" #include "daemon.h" #include "path_manager.h" +#include "platform/single_instance.h" #include "rd_log.h" #include "render.h" @@ -183,28 +184,34 @@ int HandleServiceCliCommand(const std::string& command) { } // namespace #endif -static int RunApplication(int argc, char* argv[]) { - // Service operations and config migration can log before the GUI starts. - // Configure the directory first, including CLI and daemon child paths. +static bool IsDaemonChild(int argc, char* argv[]) { + for (int i = 1; i < argc; ++i) { + if (std::strcmp(argv[i], "--child") == 0) return true; + } + return false; +} + +static crossdesk::platform::InstanceResult AcquireInstance( + crossdesk::platform::SingleInstanceGuard& guard, + crossdesk::platform::InstanceRole role) { + std::string error; + const auto result = guard.TryAcquire(role, error); + if (result == crossdesk::platform::InstanceResult::kAlreadyRunning) { + std::cerr << "CrossDesk is already running for this user.\n"; + } else if (result == crossdesk::platform::InstanceResult::kError) { + std::cerr << "CrossDesk cannot start: " << error << '\n'; + } + return result; +} + +static int RunApplication( + int argc, char* argv[], + crossdesk::platform::SingleInstanceGuard& client_instance) { + // 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()); -#ifdef _WIN32 - if (argc > 1 && IsServiceCliCommand(argv[1])) { - return HandleServiceCliCommand(argv[1]); - } -#endif - - // check if running as child process - bool is_child = false; - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--child") == 0) { - is_child = true; - break; - } - } - - if (is_child) { + if (IsDaemonChild(argc, argv)) { // child process: run render directly crossdesk::Render render; return render.Run(); @@ -230,11 +237,23 @@ static int RunApplication(int argc, char* argv[]) { #endif 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]() { + 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(); @@ -267,26 +286,51 @@ int main(int argc, char* argv[]) { std::cout << console.Text("console_cli_help"); return 0; } - const int result = crossdesk::RunWithLinuxDisplay( - argc, argv, [&] { return RunApplication(argc, argv); }, [&] { +#endif +#ifdef _WIN32 + if (argc == 2 && + std::strcmp(argv[1], crossdesk::kSlintRendererProbeArgument) == 0) { + return crossdesk::RunSlintRendererProbe(); + } + if (argc > 1 && IsServiceCliCommand(argv[1])) { + EnsureConsoleForCli(); + crossdesk::platform::SingleInstanceGuard cli_instance; + if (AcquireInstance(cli_instance, crossdesk::platform::InstanceRole::kServiceCli) != + crossdesk::platform::InstanceResult::kAcquired) return 1; + crossdesk::PathManager paths("CrossDesk"); + crossdesk::InitLogger(paths.GetLogPath().string(), "crossdesk-service-cli"); + return HandleServiceCliCommand(argv[1]); + } +#endif + + using crossdesk::platform::InstanceResult; + using crossdesk::platform::InstanceRole; + crossdesk::platform::SingleInstanceGuard launcher_instance; + crossdesk::platform::SingleInstanceGuard client_instance; + if (!IsDaemonChild(argc, argv)) { + const auto result = AcquireInstance(launcher_instance, InstanceRole::kLauncher); + if (result != InstanceResult::kAcquired) + return result == InstanceResult::kAlreadyRunning ? 0 : 1; + } + const auto result = AcquireInstance(client_instance, InstanceRole::kClient); + if (result != InstanceResult::kAcquired) + return result == InstanceResult::kAlreadyRunning ? 0 : 1; + +#ifdef __linux__ + const int exit_code = crossdesk::RunWithLinuxDisplay( + argc, argv, [&] { return RunApplication(argc, argv, client_instance); }, [&] { crossdesk::PathManager paths("CrossDesk"); if (console.Enable(paths.GetLogPath(), paths.GetCachePath() / "config.ini")) return true; std::cerr << "无法初始化日志或控制台,启动失败。\n"; return false; }); if (console.active()) { - console.Notify(result == 0 ? console.Text("console_exited") - : console.Text("console_stopped") + " " + std::to_string(result) + + console.Notify(exit_code == 0 ? console.Text("console_exited") + : console.Text("console_stopped") + " " + std::to_string(exit_code) + "; " + console.Text("console_log_file") + ": " + console.diagnostic_path()); } - return result; + return exit_code; #else -#ifdef _WIN32 - if (argc == 2 && - std::strcmp(argv[1], crossdesk::kSlintRendererProbeArgument) == 0) { - return crossdesk::RunSlintRendererProbe(); - } -#endif - return RunApplication(argc, argv); + return RunApplication(argc, argv, client_instance); #endif } diff --git a/apps/desktop/src/gui/application/gui_application.cpp b/apps/desktop/src/gui/application/gui_application.cpp index 308cc6a0..9ce13e58 100644 --- a/apps/desktop/src/gui/application/gui_application.cpp +++ b/apps/desktop/src/gui/application/gui_application.cpp @@ -2029,7 +2029,7 @@ void GuiApplication::ShareLocalCursorState() { if (!delivery.has_sent || delivery.last_sent.render_mode != outgoing.render_mode || delivery.last_sent.hidden_reason != outgoing.hidden_reason) { - LOG_INFO("Send cursor presentation to [{}]: mode={}, reason={}", + LOG_DEBUG("Send cursor presentation to [{}]: mode={}, reason={}", recipient.remote_id, static_cast(outgoing.render_mode), static_cast(outgoing.hidden_reason)); } diff --git a/apps/desktop/src/gui/features/devices/session_device_manager.cpp b/apps/desktop/src/gui/features/devices/session_device_manager.cpp index b563e872..4209edd0 100644 --- a/apps/desktop/src/gui/features/devices/session_device_manager.cpp +++ b/apps/desktop/src/gui/features/devices/session_device_manager.cpp @@ -82,7 +82,7 @@ void SessionDeviceManager::RecordCaptureCadence( : capture_secure_callbacks_ == capture_callbacks_ ? "secure_desktop" : "mixed"; - LOG_INFO( + LOG_DEBUG( "Capture delivery: source={} normal_callbacks={} secure_callbacks={} " "target_fps={} input_fps={:.2f} " "forwarded_fps={:.2f} callbacks={} rate_dropped={} " @@ -225,7 +225,7 @@ int SessionDeviceManager::InitializeScreenCapturer() { last_video_frame_stream_id_ != stream_id; if (resumed_after_gap || stream_changed) { if (RequestVideoKeyFrame(owner_.peer_, stream_id.c_str()) == 0) { - LOG_INFO("Request video key frame before sending captured frame, " + LOG_DEBUG("Request video key frame before sending captured frame, " "stream='{}', gap_ms={}, stream_changed={}", stream_id, duration_ms, stream_changed); } diff --git a/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp b/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp index fd5df3e3..79ef7044 100644 --- a/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp +++ b/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp @@ -294,7 +294,7 @@ void PeerEventHandler::OnReceiveDataBuffer( props->remote_cursor_state_received_ = true; props->cursor_presentation_.Observe(merged, SDL_GetTicks()); if (changed) { - LOG_INFO("Received cursor state: seq={}, visible={}, shape={}, " + LOG_DEBUG("Received cursor state: seq={}, visible={}, shape={}, " "mode={}, reason={}", remote_action.cs.seq, remote_action.cs.visible, static_cast(remote_action.cs.shape), diff --git a/apps/desktop/src/log/rd_log.cpp b/apps/desktop/src/log/rd_log.cpp index 1b834dae..17e99a4c 100644 --- a/apps/desktop/src/log/rd_log.cpp +++ b/apps/desktop/src/log/rd_log.cpp @@ -1,11 +1,7 @@ #include "rd_log.h" -#include -#include #include -#include #include -#include #include #include "spdlog/sinks/rotating_file_sink.h" @@ -16,6 +12,7 @@ namespace crossdesk { namespace { std::string g_log_dir = "logs"; +std::string g_logger_name = LOGGER_NAME; std::once_flag g_logger_once_flag; std::mutex g_logger_mutex; std::shared_ptr g_logger; @@ -29,20 +26,20 @@ std::string NormalizeLogDirectory(const std::string& log_dir) { } // namespace -void InitLogger(const std::string& log_dir) { +void InitLogger(const std::string& log_dir, const std::string& logger_name) { const auto directory = NormalizeLogDirectory(log_dir); std::lock_guard lock(g_logger_mutex); if (g_logger) { - if (directory != g_log_dir) { + if (directory != g_log_dir || logger_name != g_logger_name) { g_logger->warn( - "InitLogger called after logger initialized. Ignoring log_dir: {}, " - "using previous log_dir: {}", - directory, g_log_dir); + "InitLogger called after logger initialized. Keeping logger {} in {}", + g_logger_name, g_log_dir); } return; } g_log_dir = directory; + g_logger_name = logger_name; } std::shared_ptr get_logger() { @@ -53,32 +50,23 @@ std::shared_ptr get_logger() { std::error_code ec; std::filesystem::create_directories(g_log_dir, ec); - auto now = std::chrono::system_clock::now() + std::chrono::hours(8); - auto now_time = std::chrono::system_clock::to_time_t(now); - - std::tm tm_info; -#ifdef _WIN32 - gmtime_s(&tm_info, &now_time); -#else - gmtime_r(&now_time, &tm_info); -#endif - - std::stringstream ss; - ss << LOGGER_NAME; - ss << std::put_time(&tm_info, "-%Y%m%d-%H%M%S.log"); - - std::string filename = g_log_dir + "/" + ss.str(); + // Reuse the same rotation set across restarts. Timestamped base names + // give each launch its own backups and let the directory grow forever. + auto file_sink = std::make_shared( + g_log_dir + "/" + g_logger_name + ".log", 5 * 1024 * 1024, 3); std::vector sinks; sinks.push_back(std::make_shared()); - sinks.push_back(std::make_shared( - filename, 5 * 1024 * 1024, 3)); + sinks.push_back(file_sink); - auto logger = std::make_shared(LOGGER_NAME, sinks.begin(), + auto logger = std::make_shared(g_logger_name, sinks.begin(), sinks.end()); + logger->set_level(SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_DEBUG + ? spdlog::level::debug + : spdlog::level::info); logger->flush_on(spdlog::level::info); spdlog::register_logger(logger); - logger->info("Logger initialized: path={}", filename); + logger->info("Logger initialized: path={}", file_sink->filename()); g_logger = std::move(logger); }); diff --git a/apps/desktop/src/log/rd_log.h b/apps/desktop/src/log/rd_log.h index 74e2f7aa..fc9d6c77 100644 --- a/apps/desktop/src/log/rd_log.h +++ b/apps/desktop/src/log/rd_log.h @@ -10,21 +10,26 @@ #include #include -#include "spdlog/spdlog.h" - +#ifndef SPDLOG_ACTIVE_LEVEL #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO +#endif + +#include "spdlog/spdlog.h" namespace crossdesk { constexpr auto LOGGER_NAME = "crossdesk"; -void InitLogger(const std::string& log_dir); +void InitLogger(const std::string& log_dir, + const std::string& logger_name = LOGGER_NAME); std::shared_ptr get_logger(); +#define LOG_TRACE(...) SPDLOG_LOGGER_TRACE(get_logger(), __VA_ARGS__) +#define LOG_DEBUG(...) SPDLOG_LOGGER_DEBUG(get_logger(), __VA_ARGS__) #define LOG_INFO(...) SPDLOG_LOGGER_INFO(get_logger(), __VA_ARGS__) #define LOG_WARN(...) SPDLOG_LOGGER_WARN(get_logger(), __VA_ARGS__) #define LOG_ERROR(...) SPDLOG_LOGGER_ERROR(get_logger(), __VA_ARGS__) #define LOG_FATAL(...) SPDLOG_LOGGER_CRITICAL(get_logger(), __VA_ARGS__) } // namespace crossdesk -#endif \ No newline at end of file +#endif diff --git a/apps/desktop/src/platform/common/posix_single_instance.cpp b/apps/desktop/src/platform/common/posix_single_instance.cpp new file mode 100644 index 00000000..50abdcaa --- /dev/null +++ b/apps/desktop/src/platform/common/posix_single_instance.cpp @@ -0,0 +1,68 @@ +#include "platform/single_instance.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace crossdesk::platform { + +SingleInstanceGuard::SingleInstanceGuard(std::string application) + : application_(std::move(application)) {} + +SingleInstanceGuard::~SingleInstanceGuard() { Release(); } + +InstanceResult SingleInstanceGuard::TryAcquire(InstanceRole role, + std::string& error) { + error.clear(); + if (handle_ != -1) return InstanceResult::kAcquired; + if (!ValidInstanceApplication(application_)) { + error = "Invalid single-instance application name"; + return InstanceResult::kError; + } + // A fixed local path also coordinates launches with different TMPDIR/XDG + // settings. O_CLOEXEC prevents unrelated desktop/helper processes retaining it. + const std::string path = "/tmp/" + application_ + "-" + + std::to_string(geteuid()) + "-" + + InstanceRoleName(role) + ".lock"; + const int fd = open(path.c_str(), O_CREAT | O_RDWR | O_CLOEXEC | O_NOFOLLOW, 0600); + if (fd < 0) { + error = "Cannot open single-instance lock: " + std::string(std::strerror(errno)); + return InstanceResult::kError; + } + struct stat info {}; + if (fstat(fd, &info) != 0 || !S_ISREG(info.st_mode) || info.st_uid != geteuid()) { + close(fd); + error = "Single-instance lock is not a regular file owned by this user"; + return InstanceResult::kError; + } + int result; + do { + result = flock(fd, LOCK_EX | LOCK_NB); + } while (result != 0 && errno == EINTR); + if (result == 0) { + handle_ = fd; + return InstanceResult::kAcquired; + } + const int lock_error = errno; + close(fd); + if (lock_error == EWOULDBLOCK || lock_error == EAGAIN) + return InstanceResult::kAlreadyRunning; + error = "Cannot acquire single-instance lock: " + + std::string(std::strerror(lock_error)); + return InstanceResult::kError; +} + +void SingleInstanceGuard::Release() { + if (handle_ == -1) return; + // Close rather than LOCK_UN: a forked headless application may still own the + // same open file description. Never unlink a lock that another launch opened. + close(static_cast(handle_)); + handle_ = -1; +} + +} // namespace crossdesk::platform diff --git a/apps/desktop/src/platform/linux/headless/headless_console.cpp b/apps/desktop/src/platform/linux/headless/headless_console.cpp index 5141645f..41baf404 100644 --- a/apps/desktop/src/platform/linux/headless/headless_console.cpp +++ b/apps/desktop/src/platform/linux/headless/headless_console.cpp @@ -78,7 +78,7 @@ bool HeadlessConsole::Enable(const std::filesystem::path& log_directory, error_ = err; interactive_ = isatty(STDIN_FILENO) && isatty(output_); // Initialize after redirecting output so even the logger's first message - // stays in its original crossdesk-YYYYMMDD-HHMMSS.log file. + // stays in crossdesk.log. try { InitLogger(log_directory.string()); for (const auto& sink : get_logger()->sinks()) { diff --git a/apps/desktop/src/platform/single_instance.h b/apps/desktop/src/platform/single_instance.h new file mode 100644 index 00000000..1f0e96f0 --- /dev/null +++ b/apps/desktop/src/platform/single_instance.h @@ -0,0 +1,53 @@ +/* + * @Author: DI JUNKUN + * @Date: 2026-09-26 + * Copyright (c) 2026 by DI JUNKUN, All Rights Reserved. + */ + +#ifndef _SINGLE_INSTANCE_H_ +#define _SINGLE_INSTANCE_H_ + +#include +#include + +namespace crossdesk::platform { + +enum class InstanceRole { kLauncher, kClient, kServiceCli }; +enum class InstanceResult { kAcquired, kAlreadyRunning, kError }; + +// Per-user ownership, independent of the install/portable directory. The OS +// releases ownership on process exit, including crashes; no PID files to age out. +class SingleInstanceGuard { + public: + explicit SingleInstanceGuard(std::string application = "CrossDesk"); + ~SingleInstanceGuard(); + SingleInstanceGuard(const SingleInstanceGuard&) = delete; + SingleInstanceGuard& operator=(const SingleInstanceGuard&) = delete; + + InstanceResult TryAcquire(InstanceRole role, std::string& error); + void Release(); + + private: + std::string application_; + std::intptr_t handle_ = -1; +}; + +inline const char* InstanceRoleName(InstanceRole role) { + switch (role) { + case InstanceRole::kLauncher: return "launcher"; + case InstanceRole::kClient: return "client"; + case InstanceRole::kServiceCli: return "service-cli"; + } + return "client"; +} + +inline bool ValidInstanceApplication(const std::string& application) { + return !application.empty() && application.size() <= 100 && + application.find_first_not_of( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") == + std::string::npos; +} + +} // namespace crossdesk::platform + +#endif \ No newline at end of file diff --git a/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_dxgi.cpp b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_dxgi.cpp index dcf4a8a0..5e5e36fe 100644 --- a/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_dxgi.cpp +++ b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_dxgi.cpp @@ -547,7 +547,7 @@ void ScreenCapturerDxgi::CaptureLoop() { if (last_logged_cursor_embedding_ < 0 || (software_cursor_output_ && last_logged_cursor_embedding_ != static_cast(cursor_embedded))) { - LOG_INFO("DXGI cursor: monitor={} usbmmidd={} mouse_update={} " + LOG_DEBUG("DXGI cursor: monitor={} usbmmidd={} mouse_update={} " "separate_pointer_visible={} pointer_shape_bytes={} " "compose_cursor={} embedded={}", frame_monitor, software_cursor_output_, diff --git a/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp index 3b114a74..0f0c7742 100644 --- a/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp +++ b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp @@ -1638,7 +1638,7 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() { const ULONGLONG elapsed = GetTickCount64() - stats_started; if (elapsed == 0 || (!force && elapsed < 5000)) return; if (shared_frames || fallback_frames || shared_waits || shared_restarts) { - LOG_INFO( + LOG_DEBUG( "Secure capture delivery: shared_fps={:.1f}, fallback_fps={:.1f}, " "pending_waits={}, shared_restarts={}, status_max_ms={}", shared_frames * 1000.0 / elapsed, fallback_frames * 1000.0 / elapsed, diff --git a/apps/desktop/src/platform/windows/screen_capturer/wgc_plugin_entry.cpp b/apps/desktop/src/platform/windows/screen_capturer/wgc_plugin_entry.cpp index 5df35759..d3ba39a4 100644 --- a/apps/desktop/src/platform/windows/screen_capturer/wgc_plugin_entry.cpp +++ b/apps/desktop/src/platform/windows/screen_capturer/wgc_plugin_entry.cpp @@ -12,7 +12,7 @@ void InitializePluginLogger() { static std::once_flag once; std::call_once(once, []() { crossdesk::PathManager path_manager("CrossDesk"); - crossdesk::InitLogger(path_manager.GetLogPath().string()); + crossdesk::InitLogger(path_manager.GetLogPath().string(), "crossdesk-wgc"); }); } diff --git a/apps/desktop/src/platform/windows/single_instance.cpp b/apps/desktop/src/platform/windows/single_instance.cpp new file mode 100644 index 00000000..4399d938 --- /dev/null +++ b/apps/desktop/src/platform/windows/single_instance.cpp @@ -0,0 +1,71 @@ +#include "platform/single_instance.h" + +#include +#include + +#include +#include + +namespace crossdesk::platform { + +SingleInstanceGuard::SingleInstanceGuard(std::string application) + : application_(std::move(application)) {} + +SingleInstanceGuard::~SingleInstanceGuard() { Release(); } + +InstanceResult SingleInstanceGuard::TryAcquire(InstanceRole role, + std::string& error) { + error.clear(); + if (handle_ != -1) return InstanceResult::kAcquired; + if (!ValidInstanceApplication(application_)) { + error = "Invalid single-instance application name"; + return InstanceResult::kError; + } + HANDLE token = nullptr; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { + error = "Cannot read current user token: " + std::to_string(GetLastError()); + return InstanceResult::kError; + } + DWORD size = 0; + GetTokenInformation(token, TokenUser, nullptr, 0, &size); + std::vector buffer(size); + if (!GetTokenInformation(token, TokenUser, buffer.data(), size, &size)) { + const DWORD code = GetLastError(); + CloseHandle(token); + error = "Cannot read current user SID: " + std::to_string(code); + return InstanceResult::kError; + } + CloseHandle(token); + wchar_t* sid = nullptr; + if (!ConvertSidToStringSidW(reinterpret_cast(buffer.data())->User.Sid, + &sid)) { + error = "Cannot format current user SID: " + std::to_string(GetLastError()); + return InstanceResult::kError; + } + const std::string role_name = InstanceRoleName(role); + const std::wstring name = L"Global\\" + + std::wstring(application_.begin(), application_.end()) + L"." + sid + L"." + + std::wstring(role_name.begin(), role_name.end()); + LocalFree(sid); + // Object lifetime is the lease. No thread-owned mutex to transfer to a daemon. + HANDLE mutex = CreateMutexW(nullptr, FALSE, name.c_str()); + const DWORD code = GetLastError(); + if (!mutex) { + error = "Cannot create single-instance mutex: " + std::to_string(code); + return InstanceResult::kError; + } + if (code == ERROR_ALREADY_EXISTS) { + CloseHandle(mutex); + return InstanceResult::kAlreadyRunning; + } + handle_ = reinterpret_cast(mutex); + return InstanceResult::kAcquired; +} + +void SingleInstanceGuard::Release() { + if (handle_ == -1) return; + CloseHandle(reinterpret_cast(handle_)); + handle_ = -1; +} + +} // namespace crossdesk::platform diff --git a/apps/desktop/xmake/options.lua b/apps/desktop/xmake/options.lua index 1a0faf82..10ce130a 100644 --- a/apps/desktop/xmake/options.lua +++ b/apps/desktop/xmake/options.lua @@ -38,7 +38,9 @@ function setup_options_and_dependencies() 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")) if is_mode("debug") then - add_defines("CROSSDESK_DEBUG") + add_defines("CROSSDESK_DEBUG", "SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_DEBUG") + else + add_defines("SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_INFO") end add_requireconfs("**.python", {version = "3.12", override = true, configs = {pgo = false}}) diff --git a/apps/desktop/xmake/targets.lua b/apps/desktop/xmake/targets.lua index 8896ce6c..8697c2c2 100644 --- a/apps/desktop/xmake/targets.lua +++ b/apps/desktop/xmake/targets.lua @@ -135,6 +135,16 @@ function setup_targets() "apps/desktop/src/gui/runtime") add_files("apps/desktop/tests/cursor_position_test.cpp") + target("single_instance") + set_kind("object") + add_includedirs("apps/desktop/src", {public = true}) + if is_os("windows") then + add_files("apps/desktop/src/platform/windows/single_instance.cpp") + add_syslinks("Advapi32") + else + add_files("apps/desktop/src/platform/common/posix_single_instance.cpp") + end + target("repository_structure_test") set_kind("binary") set_default(false) @@ -540,7 +550,7 @@ function setup_targets() target("crossdesk") set_kind("binary") - add_deps("rd_log", "desktop_common", "gui") + add_deps("rd_log", "desktop_common", "gui", "single_instance") add_files("apps/desktop/src/app/*.cpp") add_includedirs("apps/desktop/src", "apps/desktop/src/app", {public = true}) if is_os("windows") then diff --git a/deps/submodules/minirtc b/deps/submodules/minirtc index 77cabf43..2ef58ae5 160000 --- a/deps/submodules/minirtc +++ b/deps/submodules/minirtc @@ -1 +1 @@ -Subproject commit 77cabf43cbf686691a72b6ff518ede4d345897bf +Subproject commit 2ef58ae5af9c68cbce5644ff0884fb8a0586fdd9