mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-09 17:06:24 +08:00
[fix] apply Windows daemon settings immediately
Add a runtime watchdog with Unicode process launch and session-end handling. Report autostart save failures and document the updated Windows behavior. Validated with a Windows Release build, daemon and autostart integration checks, GUI crash recovery, tray exit, real logoff/login, and full reboot/login.
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#include "platform/windows/autostart_task.h"
|
#include "platform/windows/autostart_task.h"
|
||||||
#include "platform/windows/gui/slint_backend.h"
|
#include "platform/windows/gui/slint_backend.h"
|
||||||
|
#include "platform/windows/runtime_daemon.h"
|
||||||
#include "service_host.h"
|
#include "service_host.h"
|
||||||
#include "service_lifetime.h"
|
#include "service_lifetime.h"
|
||||||
#include "shutdown_order.h"
|
#include "shutdown_order.h"
|
||||||
@@ -395,6 +396,17 @@ static int RunApplication(
|
|||||||
const int result = render.Run();
|
const int result = render.Run();
|
||||||
runtime_daemon.Shutdown(result != 0);
|
runtime_daemon.Shutdown(result != 0);
|
||||||
return result;
|
return result;
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
auto& runtime_daemon = crossdesk::platform::WindowsRuntimeDaemon::Instance();
|
||||||
|
runtime_daemon.Initialize();
|
||||||
|
std::string daemon_error;
|
||||||
|
if (!runtime_daemon.SetEnabled(enable_daemon, daemon_error)) {
|
||||||
|
crossdesk::get_logger()->warn("Could not start daemon: {}", daemon_error);
|
||||||
|
}
|
||||||
|
crossdesk::Render render;
|
||||||
|
const int result = render.Run();
|
||||||
|
runtime_daemon.Shutdown(result != 0);
|
||||||
|
return result;
|
||||||
#else
|
#else
|
||||||
|
|
||||||
if (enable_daemon) {
|
if (enable_daemon) {
|
||||||
@@ -433,8 +445,12 @@ static int RunApplication(
|
|||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// Apply in every process, including the daemon and --child. Windows does
|
if (argc >= 2 &&
|
||||||
// not inherit shutdown priority from a parent process.
|
std::strcmp(argv[1], crossdesk::platform::kWindowsDaemonArgument) == 0) {
|
||||||
|
return crossdesk::platform::RunWindowsRuntimeDaemon();
|
||||||
|
}
|
||||||
|
// Apply in every GUI process, including legacy --child launches. The watcher
|
||||||
|
// keeps the default shutdown priority so it ends before this remote host.
|
||||||
const DWORD shutdown_error = crossdesk::ConfigureWindowsShutdownOrder(
|
const DWORD shutdown_error = crossdesk::ConfigureWindowsShutdownOrder(
|
||||||
crossdesk::WindowsShutdownRole::host);
|
crossdesk::WindowsShutdownRole::host);
|
||||||
if (shutdown_error != ERROR_SUCCESS) {
|
if (shutdown_error != ERROR_SUCCESS) {
|
||||||
|
|||||||
@@ -12,6 +12,10 @@
|
|||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
#include <minirtc.h>
|
#include <minirtc.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include "platform/windows/runtime_daemon.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(__linux__) && !defined(__OHOS__)
|
#if defined(__linux__) && !defined(__OHOS__)
|
||||||
#include "platform/linux/autostart.h"
|
#include "platform/linux/autostart.h"
|
||||||
#include "platform/linux/runtime_daemon.h"
|
#include "platform/linux/runtime_daemon.h"
|
||||||
@@ -424,14 +428,19 @@ int ConfigCenter::SetAutostart(bool enabled) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int ConfigCenter::SetDaemon(bool enabled) {
|
int ConfigCenter::SetDaemon(bool enabled) {
|
||||||
#if defined(__linux__) && !defined(__OHOS__)
|
#if defined(_WIN32) || (defined(__linux__) && !defined(__OHOS__))
|
||||||
const bool previous = enable_daemon_;
|
const bool previous = enable_daemon_;
|
||||||
#endif
|
#endif
|
||||||
if (StoreValues({{"enable_daemon", enabled ? "true" : "false"}}) != 0) return -1;
|
if (StoreValues({{"enable_daemon", enabled ? "true" : "false"}}) != 0) return -1;
|
||||||
enable_daemon_ = enabled;
|
enable_daemon_ = enabled;
|
||||||
#if defined(__linux__) && !defined(__OHOS__)
|
#if defined(_WIN32) || (defined(__linux__) && !defined(__OHOS__))
|
||||||
std::string error;
|
std::string error;
|
||||||
if (!platform::LinuxRuntimeDaemon::Instance().SetEnabled(enabled, error)) {
|
#ifdef _WIN32
|
||||||
|
auto& daemon = platform::WindowsRuntimeDaemon::Instance();
|
||||||
|
#else
|
||||||
|
auto& daemon = platform::LinuxRuntimeDaemon::Instance();
|
||||||
|
#endif
|
||||||
|
if (!daemon.SetEnabled(enabled, error)) {
|
||||||
LOG_ERROR("Cannot apply daemon setting: {}", error);
|
LOG_ERROR("Cannot apply daemon setting: {}", error);
|
||||||
if (StoreValues({{"enable_daemon", previous ? "true" : "false"}}) == 0) {
|
if (StoreValues({{"enable_daemon", previous ? "true" : "false"}}) == 0) {
|
||||||
enable_daemon_ = previous;
|
enable_daemon_ = previous;
|
||||||
|
|||||||
@@ -4983,7 +4983,13 @@ bool GuiApplication::SaveSettingsFromUi(bool gallery_window) {
|
|||||||
config_center_->SetForceRelay(main->get_force_relay_enabled());
|
config_center_->SetForceRelay(main->get_force_relay_enabled());
|
||||||
main->set_force_relay_enabled(config_center_->IsForceRelay());
|
main->set_force_relay_enabled(config_center_->IsForceRelay());
|
||||||
#if !defined(__OHOS__)
|
#if !defined(__OHOS__)
|
||||||
config_center_->SetAutostart(enable_autostart_);
|
if (config_center_->SetAutostart(enable_autostart_) != 0) {
|
||||||
|
enable_autostart_ = config_center_->IsEnableAutostart();
|
||||||
|
main->set_autostart_enabled(enable_autostart_);
|
||||||
|
main->set_settings_save_error(UiText(
|
||||||
|
localization::Literal("保存失败,请重试", localization_language_index_)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
if (config_center_->SetDaemon(enable_daemon_) != 0) {
|
if (config_center_->SetDaemon(enable_daemon_) != 0) {
|
||||||
enable_daemon_ = config_center_->IsEnableDaemon();
|
enable_daemon_ = config_center_->IsEnableDaemon();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -23,21 +24,21 @@ bool DaemonStopRequested() { return g_stop_requested.load(); }
|
|||||||
bool PrepareDaemon() { return true; }
|
bool PrepareDaemon() { return true; }
|
||||||
|
|
||||||
std::string GetDaemonExecutablePath() {
|
std::string GetDaemonExecutablePath() {
|
||||||
char path[32768] = {};
|
wchar_t path[32768] = {};
|
||||||
const DWORD length = GetModuleFileNameA(nullptr, path, sizeof(path));
|
const DWORD length = GetModuleFileNameW(nullptr, path, 32768);
|
||||||
return length > 0 && length < sizeof(path) ? std::string(path, length)
|
return length > 0 && length < 32768
|
||||||
: std::string();
|
? std::filesystem::path(path, path + length).u8string()
|
||||||
|
: std::string();
|
||||||
}
|
}
|
||||||
|
|
||||||
DaemonChildResult RunDaemonChild(const std::string& executable_path,
|
DaemonChildResult RunDaemonChild(const std::string& executable_path,
|
||||||
const std::function<bool()>& keep_running) {
|
const std::function<bool()>& keep_running) {
|
||||||
DaemonChildResult result;
|
DaemonChildResult result;
|
||||||
STARTUPINFOA startup = {sizeof(startup)};
|
STARTUPINFOW startup = {sizeof(startup)};
|
||||||
PROCESS_INFORMATION process = {};
|
PROCESS_INFORMATION process = {};
|
||||||
std::string command = "\"" + executable_path + "\" --child";
|
const auto path = std::filesystem::u8path(executable_path).wstring();
|
||||||
std::vector<char> command_buffer(command.begin(), command.end());
|
std::wstring command = L"\"" + path + L"\" --child";
|
||||||
command_buffer.push_back('\0');
|
if (!CreateProcessW(path.c_str(), command.data(), nullptr, nullptr, FALSE,
|
||||||
if (!CreateProcessA(nullptr, command_buffer.data(), nullptr, nullptr, FALSE,
|
|
||||||
0, nullptr, nullptr, &startup, &process)) {
|
0, nullptr, nullptr, &startup, &process)) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,290 @@
|
|||||||
|
#include "platform/windows/runtime_daemon.h"
|
||||||
|
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cwchar>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace crossdesk::platform {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr DWORD kStartupTimeoutMs = 3000;
|
||||||
|
constexpr DWORD kRestartDelayMs = 1000;
|
||||||
|
constexpr wchar_t kWindowClass[] = L"CrossDesk.RuntimeDaemon";
|
||||||
|
|
||||||
|
struct Handle {
|
||||||
|
HANDLE value = nullptr;
|
||||||
|
~Handle() { if (value) CloseHandle(value); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
int count = 0;
|
||||||
|
wchar_t** values = CommandLineToArgvW(GetCommandLineW(), &count);
|
||||||
|
~Arguments() { if (values) LocalFree(values); }
|
||||||
|
};
|
||||||
|
|
||||||
|
std::wstring ExecutablePath() {
|
||||||
|
std::vector<wchar_t> path(32768);
|
||||||
|
const DWORD count = GetModuleFileNameW(nullptr, path.data(),
|
||||||
|
static_cast<DWORD>(path.size()));
|
||||||
|
if (!count || count >= path.size()) return {};
|
||||||
|
return {path.data(), count};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring QuoteArgument(const std::wstring& value) {
|
||||||
|
std::wstring result = L"\"";
|
||||||
|
size_t slashes = 0;
|
||||||
|
for (const wchar_t c : value) {
|
||||||
|
if (c == L'\\') {
|
||||||
|
++slashes;
|
||||||
|
} else {
|
||||||
|
result.append(c == L'"' ? slashes * 2 + 1 : slashes, L'\\');
|
||||||
|
result += c;
|
||||||
|
slashes = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.append(slashes * 2, L'\\');
|
||||||
|
return result + L'"';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Failed(const char* operation, std::string& error,
|
||||||
|
DWORD code = GetLastError()) {
|
||||||
|
error = std::string(operation) + " failed (Windows error " +
|
||||||
|
std::to_string(code ? code : ERROR_GEN_FAILURE) + ")";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE ParseHandle(const wchar_t* text) {
|
||||||
|
wchar_t* end = nullptr;
|
||||||
|
errno = 0;
|
||||||
|
const auto value = std::wcstoull(text, &end, 10);
|
||||||
|
if (errno || !text[0] || *end || text[0] == L'-' || !value ||
|
||||||
|
value > (std::numeric_limits<uintptr_t>::max)()) return nullptr;
|
||||||
|
return reinterpret_cast<HANDLE>(static_cast<uintptr_t>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SessionWindow {
|
||||||
|
HWND window = nullptr;
|
||||||
|
bool query_pending = false;
|
||||||
|
bool ending = false;
|
||||||
|
|
||||||
|
~SessionWindow() { if (window) DestroyWindow(window); }
|
||||||
|
|
||||||
|
static LRESULT CALLBACK WindowProc(HWND window, UINT message,
|
||||||
|
WPARAM wparam, LPARAM lparam) {
|
||||||
|
if (message == WM_NCCREATE) {
|
||||||
|
const auto* create = reinterpret_cast<CREATESTRUCTW*>(lparam);
|
||||||
|
SetWindowLongPtrW(window, GWLP_USERDATA,
|
||||||
|
reinterpret_cast<LONG_PTR>(create->lpCreateParams));
|
||||||
|
}
|
||||||
|
auto* state = reinterpret_cast<SessionWindow*>(
|
||||||
|
GetWindowLongPtrW(window, GWLP_USERDATA));
|
||||||
|
if (state && message == WM_QUERYENDSESSION) {
|
||||||
|
state->query_pending = true;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
if (state && message == WM_ENDSESSION) {
|
||||||
|
state->query_pending = false;
|
||||||
|
state->ending = wparam != FALSE;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return DefWindowProcW(window, message, wparam, lparam);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Create(DWORD owner) {
|
||||||
|
WNDCLASSW klass{};
|
||||||
|
klass.lpfnWndProc = WindowProc;
|
||||||
|
klass.hInstance = GetModuleHandleW(nullptr);
|
||||||
|
klass.lpszClassName = kWindowClass;
|
||||||
|
if (!RegisterClassW(&klass) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
|
||||||
|
return false;
|
||||||
|
const auto title = std::to_wstring(owner);
|
||||||
|
// Message-only windows do not receive session-end broadcasts.
|
||||||
|
window = CreateWindowExW(WS_EX_TOOLWINDOW, kWindowClass, title.c_str(),
|
||||||
|
WS_OVERLAPPED, 0, 0, 0, 0, nullptr, nullptr,
|
||||||
|
klass.hInstance, this);
|
||||||
|
return window != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Pump() {
|
||||||
|
MSG message{};
|
||||||
|
while (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE)) {
|
||||||
|
if (message.message == WM_QUIT) ending = true;
|
||||||
|
TranslateMessage(&message);
|
||||||
|
DispatchMessageW(&message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
WindowsRuntimeDaemon& WindowsRuntimeDaemon::Instance() {
|
||||||
|
static WindowsRuntimeDaemon daemon;
|
||||||
|
return daemon;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsRuntimeDaemon::~WindowsRuntimeDaemon() { Shutdown(); }
|
||||||
|
|
||||||
|
void WindowsRuntimeDaemon::Initialize() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
initialized_ = true;
|
||||||
|
executable_ = ExecutablePath();
|
||||||
|
arguments_.clear();
|
||||||
|
Arguments arguments;
|
||||||
|
if (!arguments.values) {
|
||||||
|
executable_.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 1; i < arguments.count; ++i)
|
||||||
|
arguments_.emplace_back(arguments.values[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsRuntimeDaemon::SetEnabled(bool enabled, std::string& error) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
error.clear();
|
||||||
|
// Config-only callers and the unattended service do not own a GUI watcher.
|
||||||
|
if (!initialized_) return true;
|
||||||
|
if (!enabled) return Stop(error);
|
||||||
|
if (process_ && WaitForSingleObject(process_, 0) == WAIT_TIMEOUT) return true;
|
||||||
|
if (!Stop(error)) return false;
|
||||||
|
if (executable_.empty())
|
||||||
|
return Failed("Resolve daemon executable", error, ERROR_FILE_NOT_FOUND);
|
||||||
|
|
||||||
|
SECURITY_ATTRIBUTES security{sizeof(security), nullptr, TRUE};
|
||||||
|
Handle owner;
|
||||||
|
if (!DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(),
|
||||||
|
GetCurrentProcess(), &owner.value,
|
||||||
|
SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION, TRUE, 0))
|
||||||
|
return Failed("Duplicate daemon owner", error);
|
||||||
|
Handle stop{CreateEventW(&security, TRUE, FALSE, nullptr)};
|
||||||
|
Handle ready{CreateEventW(&security, TRUE, FALSE, nullptr)};
|
||||||
|
if (!stop.value || !ready.value) return Failed("Create daemon events", error);
|
||||||
|
|
||||||
|
SIZE_T bytes = 0;
|
||||||
|
InitializeProcThreadAttributeList(nullptr, 1, 0, &bytes);
|
||||||
|
std::vector<unsigned char> attributes(bytes);
|
||||||
|
STARTUPINFOEXW startup{};
|
||||||
|
startup.StartupInfo.cb = sizeof(startup);
|
||||||
|
startup.lpAttributeList =
|
||||||
|
reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(attributes.data());
|
||||||
|
if (!InitializeProcThreadAttributeList(startup.lpAttributeList, 1, 0, &bytes))
|
||||||
|
return Failed("Initialize daemon handle list", error);
|
||||||
|
// Inherit only these private handles, never GUI locks, sockets or files.
|
||||||
|
HANDLE inherited[] = {owner.value, stop.value, ready.value};
|
||||||
|
bool started = UpdateProcThreadAttribute(
|
||||||
|
startup.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
|
||||||
|
inherited, sizeof(inherited), nullptr, nullptr) != FALSE;
|
||||||
|
PROCESS_INFORMATION child{};
|
||||||
|
if (started) {
|
||||||
|
std::wstring command = QuoteArgument(executable_) + L" --windows-daemon";
|
||||||
|
for (HANDLE handle : inherited)
|
||||||
|
command += L" " + std::to_wstring(reinterpret_cast<uintptr_t>(handle));
|
||||||
|
for (const auto& argument : arguments_)
|
||||||
|
command += L" " + QuoteArgument(argument);
|
||||||
|
started = CreateProcessW(executable_.c_str(), command.data(), nullptr,
|
||||||
|
nullptr, TRUE, EXTENDED_STARTUPINFO_PRESENT | CREATE_NO_WINDOW,
|
||||||
|
nullptr, nullptr, &startup.StartupInfo, &child) != FALSE;
|
||||||
|
}
|
||||||
|
const DWORD code = GetLastError();
|
||||||
|
DeleteProcThreadAttributeList(startup.lpAttributeList);
|
||||||
|
if (!started) return Failed("Start daemon", error, code);
|
||||||
|
CloseHandle(child.hThread);
|
||||||
|
process_ = child.hProcess;
|
||||||
|
stop_ = stop.value;
|
||||||
|
stop.value = nullptr;
|
||||||
|
HANDLE startup_events[] = {process_, ready.value};
|
||||||
|
if (WaitForMultipleObjects(2, startup_events, FALSE, kStartupTimeoutMs) !=
|
||||||
|
WAIT_OBJECT_0 + 1) {
|
||||||
|
std::string cleanup;
|
||||||
|
Stop(cleanup);
|
||||||
|
return Failed("Wait for daemon readiness", error, ERROR_PROCESS_ABORTED);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsRuntimeDaemon::Stop(std::string& error) {
|
||||||
|
if (!process_) return true;
|
||||||
|
SetEvent(stop_);
|
||||||
|
if (WaitForSingleObject(process_, kStartupTimeoutMs) != WAIT_OBJECT_0) {
|
||||||
|
// The handle still identifies our helper even if its PID has been reused.
|
||||||
|
if (!TerminateProcess(process_, ERROR_PROCESS_ABORTED) ||
|
||||||
|
WaitForSingleObject(process_, kStartupTimeoutMs) != WAIT_OBJECT_0)
|
||||||
|
return Failed("Stop daemon", error);
|
||||||
|
}
|
||||||
|
CloseHandle(process_);
|
||||||
|
CloseHandle(stop_);
|
||||||
|
process_ = stop_ = nullptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsRuntimeDaemon::Shutdown(bool restart) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
if (!restart) {
|
||||||
|
std::string error;
|
||||||
|
Stop(error);
|
||||||
|
} else {
|
||||||
|
// Leave the helper armed until this process exits and releases its leases.
|
||||||
|
if (process_) CloseHandle(process_);
|
||||||
|
if (stop_) CloseHandle(stop_);
|
||||||
|
process_ = stop_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int RunWindowsRuntimeDaemon() {
|
||||||
|
Arguments arguments;
|
||||||
|
if (!arguments.values || arguments.count < 5 ||
|
||||||
|
std::wcscmp(arguments.values[1], L"--windows-daemon") != 0) return 1;
|
||||||
|
Handle owner{ParseHandle(arguments.values[2])};
|
||||||
|
Handle stop{ParseHandle(arguments.values[3])};
|
||||||
|
Handle ready{ParseHandle(arguments.values[4])};
|
||||||
|
const DWORD owner_id = GetProcessId(owner.value);
|
||||||
|
if (!owner_id || owner_id == GetCurrentProcessId() || !stop.value ||
|
||||||
|
!ready.value) return 1;
|
||||||
|
for (HANDLE handle : {owner.value, stop.value, ready.value}) {
|
||||||
|
DWORD flags = 0;
|
||||||
|
if (!GetHandleInformation(handle, &flags) || !(flags & HANDLE_FLAG_INHERIT) ||
|
||||||
|
!SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0)) return 1;
|
||||||
|
}
|
||||||
|
SessionWindow session;
|
||||||
|
if (!session.Create(owner_id) || !SetEvent(ready.value)) return 1;
|
||||||
|
|
||||||
|
bool exited = false;
|
||||||
|
ULONGLONG restart_at = 0;
|
||||||
|
HANDLE watched[] = {stop.value, owner.value};
|
||||||
|
for (;;) {
|
||||||
|
session.Pump();
|
||||||
|
if (session.ending || WaitForSingleObject(stop.value, 0) == WAIT_OBJECT_0)
|
||||||
|
return 0;
|
||||||
|
const auto now = GetTickCount64();
|
||||||
|
if (exited && now >= restart_at && !session.query_pending &&
|
||||||
|
!GetSystemMetrics(SM_SHUTTINGDOWN)) break;
|
||||||
|
const DWORD timeout = exited && now < restart_at
|
||||||
|
? static_cast<DWORD>(restart_at - now) : INFINITE;
|
||||||
|
const DWORD count = exited ? 1 : 2;
|
||||||
|
const DWORD result = MsgWaitForMultipleObjectsEx(
|
||||||
|
count, watched, timeout, QS_ALLINPUT, MWMO_INPUTAVAILABLE);
|
||||||
|
if (result == WAIT_OBJECT_0) return 0;
|
||||||
|
if (!exited && result == WAIT_OBJECT_0 + 1) {
|
||||||
|
exited = true;
|
||||||
|
restart_at = GetTickCount64() + kRestartDelayMs;
|
||||||
|
} else if (result != WAIT_OBJECT_0 + count && result != WAIT_TIMEOUT) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto executable = ExecutablePath();
|
||||||
|
if (executable.empty()) return 1;
|
||||||
|
std::wstring command = QuoteArgument(executable);
|
||||||
|
for (int i = 5; i < arguments.count; ++i)
|
||||||
|
command += L" " + QuoteArgument(arguments.values[i]);
|
||||||
|
STARTUPINFOW startup{sizeof(startup)};
|
||||||
|
PROCESS_INFORMATION child{};
|
||||||
|
if (!CreateProcessW(executable.c_str(), command.data(), nullptr, nullptr,
|
||||||
|
FALSE, 0, nullptr, nullptr, &startup, &child)) return 1;
|
||||||
|
CloseHandle(child.hThread);
|
||||||
|
CloseHandle(child.hProcess);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crossdesk::platform
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
#ifndef CROSSDESK_PLATFORM_WINDOWS_RUNTIME_DAEMON_H_
|
||||||
|
#define CROSSDESK_PLATFORM_WINDOWS_RUNTIME_DAEMON_H_
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace crossdesk::platform {
|
||||||
|
|
||||||
|
inline constexpr char kWindowsDaemonArgument[] = "--windows-daemon";
|
||||||
|
|
||||||
|
// Watches the GUI from a separate process, so settings can start/stop crash
|
||||||
|
// recovery without replacing the GUI or interrupting its sessions.
|
||||||
|
class WindowsRuntimeDaemon {
|
||||||
|
public:
|
||||||
|
static WindowsRuntimeDaemon& Instance();
|
||||||
|
~WindowsRuntimeDaemon();
|
||||||
|
|
||||||
|
void Initialize();
|
||||||
|
bool SetEnabled(bool enabled, std::string& error);
|
||||||
|
void Shutdown(bool restart = false);
|
||||||
|
|
||||||
|
private:
|
||||||
|
WindowsRuntimeDaemon() = default;
|
||||||
|
bool Stop(std::string& error);
|
||||||
|
std::mutex mutex_;
|
||||||
|
bool initialized_ = false;
|
||||||
|
std::wstring executable_;
|
||||||
|
std::vector<std::wstring> arguments_;
|
||||||
|
HANDLE process_ = nullptr;
|
||||||
|
HANDLE stop_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dispatch before logging, configuration, service startup and instance locks.
|
||||||
|
// Reads the Unicode command line itself, including the original GUI arguments.
|
||||||
|
int RunWindowsRuntimeDaemon();
|
||||||
|
|
||||||
|
} // namespace crossdesk::platform
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -504,6 +504,12 @@ function setup_targets()
|
|||||||
add_defines("CROSSDESK_PORTABLE=1")
|
add_defines("CROSSDESK_PORTABLE=1")
|
||||||
end
|
end
|
||||||
add_files("apps/desktop/tests/windows_autostart_test.cpp")
|
add_files("apps/desktop/tests/windows_autostart_test.cpp")
|
||||||
|
|
||||||
|
target("windows_runtime_daemon")
|
||||||
|
set_kind("static")
|
||||||
|
add_includedirs("apps/desktop/src", {public = true})
|
||||||
|
add_files("apps/desktop/src/platform/windows/runtime_daemon.cpp")
|
||||||
|
add_syslinks("User32", "Shell32", {public = true})
|
||||||
end
|
end
|
||||||
|
|
||||||
target("config_center")
|
target("config_center")
|
||||||
@@ -511,6 +517,8 @@ function setup_targets()
|
|||||||
add_deps("rd_log", "autostart")
|
add_deps("rd_log", "autostart")
|
||||||
if is_os("linux") then
|
if is_os("linux") then
|
||||||
add_deps("linux_runtime_daemon")
|
add_deps("linux_runtime_daemon")
|
||||||
|
elseif is_os("windows") then
|
||||||
|
add_deps("windows_runtime_daemon")
|
||||||
end
|
end
|
||||||
add_files("apps/desktop/src/config_center/*.cpp")
|
add_files("apps/desktop/src/config_center/*.cpp")
|
||||||
add_includedirs("apps/desktop/src", "apps/desktop/src/config_center",
|
add_includedirs("apps/desktop/src", "apps/desktop/src/config_center",
|
||||||
|
|||||||
+1
-1
@@ -158,7 +158,7 @@ sudo apt install "./crossdesk-virtual-desktop-xfce-linux-amd64-<version>.deb"
|
|||||||
| 被连接时开启隐私屏 | 设置本机被远程连接时是否自动启用隐私屏;会话中可从控制端工具栏切换 |
|
| 被连接时开启隐私屏 | 设置本机被远程连接时是否自动启用隐私屏;会话中可从控制端工具栏切换 |
|
||||||
| 自托管配置 | 点击“编辑”填写服务器地址和信令服务端口,使用右侧开关启用 |
|
| 自托管配置 | 点击“编辑”填写服务器地址和信令服务端口,使用右侧开关启用 |
|
||||||
| 开机自启 | 保存后设置登录桌面时自动启动 |
|
| 开机自启 | 保存后设置登录桌面时自动启动 |
|
||||||
| 启用守护进程 | Linux 桌面模式保存后立即启用或停用异常退出后的自动重启,无需重启程序;正常退出不会重新拉起。Windows / macOS 下次启动生效,无头控制台模式不启用内置守护 |
|
| 启用守护进程 | Windows / Linux 桌面模式保存后立即启用或停用异常退出后的自动重启,无需重启程序;正常退出或注销不会重新拉起。macOS 下次启动生效,无头控制台模式不启用内置守护 |
|
||||||
| 文件保存路径 | 选择接收文件的目录 |
|
| 文件保存路径 | 选择接收文件的目录 |
|
||||||
|
|
||||||
网络连接分组中的 IP 直连开关独立于 ID 连接使用的中继设置:
|
网络连接分组中的 IP 直连开关独立于 ID 连接使用的中继设置:
|
||||||
|
|||||||
Reference in New Issue
Block a user