mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-21 03:40:40 +08:00
[fix] reduce release log verbosity and enforce single instance
This commit is contained in:
@@ -23,6 +23,7 @@
|
|||||||
#include "config_center.h"
|
#include "config_center.h"
|
||||||
#include "daemon.h"
|
#include "daemon.h"
|
||||||
#include "path_manager.h"
|
#include "path_manager.h"
|
||||||
|
#include "platform/single_instance.h"
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
#include "render.h"
|
#include "render.h"
|
||||||
|
|
||||||
@@ -183,28 +184,34 @@ int HandleServiceCliCommand(const std::string& command) {
|
|||||||
} // namespace
|
} // namespace
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static int RunApplication(int argc, char* argv[]) {
|
static bool IsDaemonChild(int argc, char* argv[]) {
|
||||||
// Service operations and config migration can log before the GUI starts.
|
for (int i = 1; i < argc; ++i) {
|
||||||
// Configure the directory first, including CLI and daemon child paths.
|
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::PathManager>("CrossDesk");
|
auto path_manager = std::make_unique<crossdesk::PathManager>("CrossDesk");
|
||||||
crossdesk::InitLogger(path_manager->GetLogPath().string());
|
crossdesk::InitLogger(path_manager->GetLogPath().string());
|
||||||
|
|
||||||
#ifdef _WIN32
|
if (IsDaemonChild(argc, argv)) {
|
||||||
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) {
|
|
||||||
// child process: run render directly
|
// child process: run render directly
|
||||||
crossdesk::Render render;
|
crossdesk::Render render;
|
||||||
return render.Run();
|
return render.Run();
|
||||||
@@ -230,11 +237,23 @@ static int RunApplication(int argc, char* argv[]) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (enable_daemon) {
|
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
|
// start daemon with restart monitoring
|
||||||
Daemon daemon("CrossDesk");
|
Daemon daemon("CrossDesk");
|
||||||
|
|
||||||
// define main loop function: run render and stop daemon on normal exit
|
// 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;
|
crossdesk::Render render;
|
||||||
render.Run();
|
render.Run();
|
||||||
daemon.stop();
|
daemon.stop();
|
||||||
@@ -267,26 +286,51 @@ int main(int argc, char* argv[]) {
|
|||||||
std::cout << console.Text("console_cli_help");
|
std::cout << console.Text("console_cli_help");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const int result = crossdesk::RunWithLinuxDisplay(
|
#endif
|
||||||
argc, argv, [&] { return RunApplication(argc, argv); }, [&] {
|
#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");
|
crossdesk::PathManager paths("CrossDesk");
|
||||||
if (console.Enable(paths.GetLogPath(), paths.GetCachePath() / "config.ini")) return true;
|
if (console.Enable(paths.GetLogPath(), paths.GetCachePath() / "config.ini")) return true;
|
||||||
std::cerr << "无法初始化日志或控制台,启动失败。\n";
|
std::cerr << "无法初始化日志或控制台,启动失败。\n";
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
if (console.active()) {
|
if (console.active()) {
|
||||||
console.Notify(result == 0 ? console.Text("console_exited")
|
console.Notify(exit_code == 0 ? console.Text("console_exited")
|
||||||
: console.Text("console_stopped") + " " + std::to_string(result) +
|
: console.Text("console_stopped") + " " + std::to_string(exit_code) +
|
||||||
"; " + console.Text("console_log_file") + ": " + console.diagnostic_path());
|
"; " + console.Text("console_log_file") + ": " + console.diagnostic_path());
|
||||||
}
|
}
|
||||||
return result;
|
return exit_code;
|
||||||
#else
|
#else
|
||||||
#ifdef _WIN32
|
return RunApplication(argc, argv, client_instance);
|
||||||
if (argc == 2 &&
|
|
||||||
std::strcmp(argv[1], crossdesk::kSlintRendererProbeArgument) == 0) {
|
|
||||||
return crossdesk::RunSlintRendererProbe();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return RunApplication(argc, argv);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2029,7 +2029,7 @@ void GuiApplication::ShareLocalCursorState() {
|
|||||||
if (!delivery.has_sent ||
|
if (!delivery.has_sent ||
|
||||||
delivery.last_sent.render_mode != outgoing.render_mode ||
|
delivery.last_sent.render_mode != outgoing.render_mode ||
|
||||||
delivery.last_sent.hidden_reason != outgoing.hidden_reason) {
|
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<int>(outgoing.render_mode),
|
recipient.remote_id, static_cast<int>(outgoing.render_mode),
|
||||||
static_cast<int>(outgoing.hidden_reason));
|
static_cast<int>(outgoing.hidden_reason));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ void SessionDeviceManager::RecordCaptureCadence(
|
|||||||
: capture_secure_callbacks_ == capture_callbacks_
|
: capture_secure_callbacks_ == capture_callbacks_
|
||||||
? "secure_desktop"
|
? "secure_desktop"
|
||||||
: "mixed";
|
: "mixed";
|
||||||
LOG_INFO(
|
LOG_DEBUG(
|
||||||
"Capture delivery: source={} normal_callbacks={} secure_callbacks={} "
|
"Capture delivery: source={} normal_callbacks={} secure_callbacks={} "
|
||||||
"target_fps={} input_fps={:.2f} "
|
"target_fps={} input_fps={:.2f} "
|
||||||
"forwarded_fps={:.2f} callbacks={} rate_dropped={} "
|
"forwarded_fps={:.2f} callbacks={} rate_dropped={} "
|
||||||
@@ -225,7 +225,7 @@ int SessionDeviceManager::InitializeScreenCapturer() {
|
|||||||
last_video_frame_stream_id_ != stream_id;
|
last_video_frame_stream_id_ != stream_id;
|
||||||
if (resumed_after_gap || stream_changed) {
|
if (resumed_after_gap || stream_changed) {
|
||||||
if (RequestVideoKeyFrame(owner_.peer_, stream_id.c_str()) == 0) {
|
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='{}', gap_ms={}, stream_changed={}",
|
||||||
stream_id, duration_ms, stream_changed);
|
stream_id, duration_ms, stream_changed);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
|||||||
props->remote_cursor_state_received_ = true;
|
props->remote_cursor_state_received_ = true;
|
||||||
props->cursor_presentation_.Observe(merged, SDL_GetTicks());
|
props->cursor_presentation_.Observe(merged, SDL_GetTicks());
|
||||||
if (changed) {
|
if (changed) {
|
||||||
LOG_INFO("Received cursor state: seq={}, visible={}, shape={}, "
|
LOG_DEBUG("Received cursor state: seq={}, visible={}, shape={}, "
|
||||||
"mode={}, reason={}",
|
"mode={}, reason={}",
|
||||||
remote_action.cs.seq, remote_action.cs.visible,
|
remote_action.cs.seq, remote_action.cs.visible,
|
||||||
static_cast<int>(remote_action.cs.shape),
|
static_cast<int>(remote_action.cs.shape),
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <ctime>
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <iomanip>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <sstream>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "spdlog/sinks/rotating_file_sink.h"
|
#include "spdlog/sinks/rotating_file_sink.h"
|
||||||
@@ -16,6 +12,7 @@ namespace crossdesk {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
std::string g_log_dir = "logs";
|
std::string g_log_dir = "logs";
|
||||||
|
std::string g_logger_name = LOGGER_NAME;
|
||||||
std::once_flag g_logger_once_flag;
|
std::once_flag g_logger_once_flag;
|
||||||
std::mutex g_logger_mutex;
|
std::mutex g_logger_mutex;
|
||||||
std::shared_ptr<spdlog::logger> g_logger;
|
std::shared_ptr<spdlog::logger> g_logger;
|
||||||
@@ -29,20 +26,20 @@ std::string NormalizeLogDirectory(const std::string& log_dir) {
|
|||||||
|
|
||||||
} // namespace
|
} // 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);
|
const auto directory = NormalizeLogDirectory(log_dir);
|
||||||
std::lock_guard<std::mutex> lock(g_logger_mutex);
|
std::lock_guard<std::mutex> lock(g_logger_mutex);
|
||||||
if (g_logger) {
|
if (g_logger) {
|
||||||
if (directory != g_log_dir) {
|
if (directory != g_log_dir || logger_name != g_logger_name) {
|
||||||
g_logger->warn(
|
g_logger->warn(
|
||||||
"InitLogger called after logger initialized. Ignoring log_dir: {}, "
|
"InitLogger called after logger initialized. Keeping logger {} in {}",
|
||||||
"using previous log_dir: {}",
|
g_logger_name, g_log_dir);
|
||||||
directory, g_log_dir);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_log_dir = directory;
|
g_log_dir = directory;
|
||||||
|
g_logger_name = logger_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<spdlog::logger> get_logger() {
|
std::shared_ptr<spdlog::logger> get_logger() {
|
||||||
@@ -53,32 +50,23 @@ std::shared_ptr<spdlog::logger> get_logger() {
|
|||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
std::filesystem::create_directories(g_log_dir, ec);
|
std::filesystem::create_directories(g_log_dir, ec);
|
||||||
|
|
||||||
auto now = std::chrono::system_clock::now() + std::chrono::hours(8);
|
// Reuse the same rotation set across restarts. Timestamped base names
|
||||||
auto now_time = std::chrono::system_clock::to_time_t(now);
|
// give each launch its own backups and let the directory grow forever.
|
||||||
|
auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
|
||||||
std::tm tm_info;
|
g_log_dir + "/" + g_logger_name + ".log", 5 * 1024 * 1024, 3);
|
||||||
#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();
|
|
||||||
|
|
||||||
std::vector<spdlog::sink_ptr> sinks;
|
std::vector<spdlog::sink_ptr> sinks;
|
||||||
sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
|
sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
|
||||||
sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
|
sinks.push_back(file_sink);
|
||||||
filename, 5 * 1024 * 1024, 3));
|
|
||||||
|
|
||||||
auto logger = std::make_shared<spdlog::logger>(LOGGER_NAME, sinks.begin(),
|
auto logger = std::make_shared<spdlog::logger>(g_logger_name, sinks.begin(),
|
||||||
sinks.end());
|
sinks.end());
|
||||||
|
logger->set_level(SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_DEBUG
|
||||||
|
? spdlog::level::debug
|
||||||
|
: spdlog::level::info);
|
||||||
logger->flush_on(spdlog::level::info);
|
logger->flush_on(spdlog::level::info);
|
||||||
spdlog::register_logger(logger);
|
spdlog::register_logger(logger);
|
||||||
logger->info("Logger initialized: path={}", filename);
|
logger->info("Logger initialized: path={}", file_sink->filename());
|
||||||
g_logger = std::move(logger);
|
g_logger = std::move(logger);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -10,21 +10,26 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "spdlog/spdlog.h"
|
#ifndef SPDLOG_ACTIVE_LEVEL
|
||||||
|
|
||||||
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO
|
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
constexpr auto LOGGER_NAME = "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<spdlog::logger> get_logger();
|
std::shared_ptr<spdlog::logger> 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_INFO(...) SPDLOG_LOGGER_INFO(get_logger(), __VA_ARGS__)
|
||||||
#define LOG_WARN(...) SPDLOG_LOGGER_WARN(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_ERROR(...) SPDLOG_LOGGER_ERROR(get_logger(), __VA_ARGS__)
|
||||||
#define LOG_FATAL(...) SPDLOG_LOGGER_CRITICAL(get_logger(), __VA_ARGS__)
|
#define LOG_FATAL(...) SPDLOG_LOGGER_CRITICAL(get_logger(), __VA_ARGS__)
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#include "platform/single_instance.h"
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/file.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
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<int>(handle_));
|
||||||
|
handle_ = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crossdesk::platform
|
||||||
@@ -78,7 +78,7 @@ bool HeadlessConsole::Enable(const std::filesystem::path& log_directory,
|
|||||||
error_ = err;
|
error_ = err;
|
||||||
interactive_ = isatty(STDIN_FILENO) && isatty(output_);
|
interactive_ = isatty(STDIN_FILENO) && isatty(output_);
|
||||||
// Initialize after redirecting output so even the logger's first message
|
// 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 {
|
try {
|
||||||
InitLogger(log_directory.string());
|
InitLogger(log_directory.string());
|
||||||
for (const auto& sink : get_logger()->sinks()) {
|
for (const auto& sink : get_logger()->sinks()) {
|
||||||
|
|||||||
@@ -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 <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -547,7 +547,7 @@ void ScreenCapturerDxgi::CaptureLoop() {
|
|||||||
if (last_logged_cursor_embedding_ < 0 ||
|
if (last_logged_cursor_embedding_ < 0 ||
|
||||||
(software_cursor_output_ &&
|
(software_cursor_output_ &&
|
||||||
last_logged_cursor_embedding_ != static_cast<int>(cursor_embedded))) {
|
last_logged_cursor_embedding_ != static_cast<int>(cursor_embedded))) {
|
||||||
LOG_INFO("DXGI cursor: monitor={} usbmmidd={} mouse_update={} "
|
LOG_DEBUG("DXGI cursor: monitor={} usbmmidd={} mouse_update={} "
|
||||||
"separate_pointer_visible={} pointer_shape_bytes={} "
|
"separate_pointer_visible={} pointer_shape_bytes={} "
|
||||||
"compose_cursor={} embedded={}",
|
"compose_cursor={} embedded={}",
|
||||||
frame_monitor, software_cursor_output_,
|
frame_monitor, software_cursor_output_,
|
||||||
|
|||||||
@@ -1638,7 +1638,7 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
|
|||||||
const ULONGLONG elapsed = GetTickCount64() - stats_started;
|
const ULONGLONG elapsed = GetTickCount64() - stats_started;
|
||||||
if (elapsed == 0 || (!force && elapsed < 5000)) return;
|
if (elapsed == 0 || (!force && elapsed < 5000)) return;
|
||||||
if (shared_frames || fallback_frames || shared_waits || shared_restarts) {
|
if (shared_frames || fallback_frames || shared_waits || shared_restarts) {
|
||||||
LOG_INFO(
|
LOG_DEBUG(
|
||||||
"Secure capture delivery: shared_fps={:.1f}, fallback_fps={:.1f}, "
|
"Secure capture delivery: shared_fps={:.1f}, fallback_fps={:.1f}, "
|
||||||
"pending_waits={}, shared_restarts={}, status_max_ms={}",
|
"pending_waits={}, shared_restarts={}, status_max_ms={}",
|
||||||
shared_frames * 1000.0 / elapsed, fallback_frames * 1000.0 / elapsed,
|
shared_frames * 1000.0 / elapsed, fallback_frames * 1000.0 / elapsed,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ void InitializePluginLogger() {
|
|||||||
static std::once_flag once;
|
static std::once_flag once;
|
||||||
std::call_once(once, []() {
|
std::call_once(once, []() {
|
||||||
crossdesk::PathManager path_manager("CrossDesk");
|
crossdesk::PathManager path_manager("CrossDesk");
|
||||||
crossdesk::InitLogger(path_manager.GetLogPath().string());
|
crossdesk::InitLogger(path_manager.GetLogPath().string(), "crossdesk-wgc");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#include "platform/single_instance.h"
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <sddl.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<unsigned char> 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<TOKEN_USER*>(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<std::intptr_t>(mutex);
|
||||||
|
return InstanceResult::kAcquired;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleInstanceGuard::Release() {
|
||||||
|
if (handle_ == -1) return;
|
||||||
|
CloseHandle(reinterpret_cast<HANDLE>(handle_));
|
||||||
|
handle_ = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crossdesk::platform
|
||||||
@@ -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_WAYLAND=" .. (is_config("USE_WAYLAND", true) and "1" or "0"))
|
||||||
add_defines("USE_DRM=" .. (is_config("USE_DRM", true) and "1" or "0"))
|
add_defines("USE_DRM=" .. (is_config("USE_DRM", true) and "1" or "0"))
|
||||||
if is_mode("debug") then
|
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
|
end
|
||||||
|
|
||||||
add_requireconfs("**.python", {version = "3.12", override = true, configs = {pgo = false}})
|
add_requireconfs("**.python", {version = "3.12", override = true, configs = {pgo = false}})
|
||||||
|
|||||||
@@ -135,6 +135,16 @@ function setup_targets()
|
|||||||
"apps/desktop/src/gui/runtime")
|
"apps/desktop/src/gui/runtime")
|
||||||
add_files("apps/desktop/tests/cursor_position_test.cpp")
|
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")
|
target("repository_structure_test")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
set_default(false)
|
set_default(false)
|
||||||
@@ -540,7 +550,7 @@ function setup_targets()
|
|||||||
|
|
||||||
target("crossdesk")
|
target("crossdesk")
|
||||||
set_kind("binary")
|
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_files("apps/desktop/src/app/*.cpp")
|
||||||
add_includedirs("apps/desktop/src", "apps/desktop/src/app", {public = true})
|
add_includedirs("apps/desktop/src", "apps/desktop/src/app", {public = true})
|
||||||
if is_os("windows") then
|
if is_os("windows") then
|
||||||
|
|||||||
Vendored
+1
-1
Submodule deps/submodules/minirtc updated: 77cabf43cb...2ef58ae5af
Reference in New Issue
Block a user