mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-17 17:33:21 +08:00
[fix] reduce release log verbosity and enforce single instance
This commit is contained in:
@@ -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;
|
||||
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()) {
|
||||
|
||||
@@ -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 ||
|
||||
(software_cursor_output_ &&
|
||||
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={} "
|
||||
"compose_cursor={} embedded={}",
|
||||
frame_monitor, software_cursor_output_,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user