From 5f541f5c8b5c8919adc73e8ef01eaaddd99d8755 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Tue, 28 Apr 2026 10:25:16 +0800 Subject: [PATCH] [feat] make CrossDesk service start and stop with the app --- scripts/windows/nsis_script.nsi | 9 +- src/app/main.cpp | 26 +++- src/service/windows/service_host.cpp | 176 ++++++++++++++++++++++++++- src/service/windows/service_host.h | 3 + 4 files changed, 198 insertions(+), 16 deletions(-) diff --git a/scripts/windows/nsis_script.nsi b/scripts/windows/nsis_script.nsi index c3ef88d..335e42c 100644 --- a/scripts/windows/nsis_script.nsi +++ b/scripts/windows/nsis_script.nsi @@ -195,14 +195,7 @@ Function RegisterInstalledService Abort ${EndIf} - DetailPrint "Starting CrossDesk service" - ExecWait '"$INSTDIR\CrossDesk.exe" --service-start' $0 - ${If} $0 != 0 - ExecWait '"$INSTDIR\CrossDesk.exe" --service-uninstall' $1 - ExecWait '"$SYSDIR\sc.exe" delete ${PRODUCT_SERVICE_NAME}' $1 - MessageBox MB_ICONSTOP|MB_OK "The CrossDesk service was registered but could not be started. The installation will be aborted." - Abort - ${EndIf} + DetailPrint "CrossDesk service registered for on-demand start" Return diff --git a/src/app/main.cpp b/src/app/main.cpp index d90a1f9..f38f92a 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -14,6 +14,7 @@ #ifdef _WIN32 #include + #include "service_host.h" #endif @@ -53,7 +54,8 @@ void PrintServiceCliUsage() { << " --service-uninstall Remove the installed Windows service\n" << " --service-start Start the Windows service\n" << " --service-stop Stop the Windows service\n" - << " --service-sas Ask the service to send Secure Attention Sequence\n" + << " --service-sas Ask the service to send Secure Attention " + "Sequence\n" << " --service-ping Ping the service over named pipe IPC\n" << " --service-status Query service runtime status\n" << " --service-help Show this help\n"; @@ -87,12 +89,25 @@ bool IsServiceCliCommand(const char* arg) { std::strcmp(arg, "--service-uninstall") == 0 || std::strcmp(arg, "--service-start") == 0 || std::strcmp(arg, "--service-stop") == 0 || - std::strcmp(arg, "--service-sas") == 0 || + std::strcmp(arg, "--service-sas") == 0 || std::strcmp(arg, "--service-ping") == 0 || std::strcmp(arg, "--service-status") == 0 || std::strcmp(arg, "--service-help") == 0; } +void TryStartManagedWindowsService() { + std::filesystem::path service_path = GetSiblingServiceExecutablePath(); + if (service_path.empty() || !std::filesystem::exists(service_path)) { + return; + } + + if (!crossdesk::IsCrossDeskServiceInstalled()) { + return; + } + + crossdesk::StartCrossDeskService(); +} + int HandleServiceCliCommand(const std::string& command) { EnsureConsoleForCli(); @@ -120,8 +135,7 @@ int HandleServiceCliCommand(const std::string& command) { if (command == "--service-uninstall") { bool success = crossdesk::UninstallCrossDeskService(); - std::cout << (success ? "uninstall ok" : "uninstall failed") - << std::endl; + std::cout << (success ? "uninstall ok" : "uninstall failed") << std::endl; return success ? 0 : 1; } @@ -182,6 +196,10 @@ int main(int argc, char* argv[]) { return 0; } +#ifdef _WIN32 + TryStartManagedWindowsService(); +#endif + bool enable_daemon = false; auto path_manager = std::make_unique("CrossDesk"); if (path_manager) { diff --git a/src/service/windows/service_host.cpp b/src/service/windows/service_host.cpp index c135f6b..1a1cfed 100644 --- a/src/service/windows/service_host.cpp +++ b/src/service/windows/service_host.cpp @@ -1,5 +1,6 @@ #include "service_host.h" +#include #include #include #include @@ -27,6 +28,9 @@ using Json = nlohmann::json; constexpr char kSecureDesktopKeyboardIpcCommandPrefix[] = "secure-input-key:"; constexpr char kSecureDesktopMouseIpcCommandPrefix[] = "secure-input-mouse:"; +constexpr wchar_t kCrossDeskClientProcessName[] = L"crossdesk.exe"; +constexpr DWORD kCrossDeskClientMonitorIntervalMs = 1000; +constexpr ULONGLONG kCrossDeskClientMonitorStartupGraceMs = 5000; using SendSasFunction = VOID(WINAPI*)(BOOL); @@ -157,6 +161,97 @@ std::string BuildErrorJson(const char* error, DWORD error_code = 0) { return stream.str(); } +bool HasRunningCrossDeskClientProcess() { + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snapshot == INVALID_HANDLE_VALUE) { + LOG_ERROR("CreateToolhelp32Snapshot failed, error={}", GetLastError()); + return true; + } + + PROCESSENTRY32W entry{}; + entry.dwSize = sizeof(entry); + if (!Process32FirstW(snapshot, &entry)) { + DWORD error = GetLastError(); + CloseHandle(snapshot); + if (error != ERROR_NO_MORE_FILES) { + LOG_ERROR("Process32FirstW failed, error={}", error); + return true; + } + return false; + } + + do { + if (_wcsicmp(entry.szExeFile, kCrossDeskClientProcessName) == 0) { + CloseHandle(snapshot); + return true; + } + } while (Process32NextW(snapshot, &entry)); + + DWORD error = GetLastError(); + CloseHandle(snapshot); + if (error != ERROR_NO_MORE_FILES) { + LOG_ERROR("Process32NextW failed, error={}", error); + return true; + } + + return false; +} + +bool GrantCrossDeskServiceStartAccessToAuthenticatedUsers(SC_HANDLE service) { + if (service == nullptr) { + return false; + } + + PACL existing_dacl = nullptr; + PACL updated_dacl = nullptr; + PSECURITY_DESCRIPTOR security_descriptor = nullptr; + DWORD error = + GetSecurityInfo(service, SE_SERVICE, DACL_SECURITY_INFORMATION, nullptr, + nullptr, &existing_dacl, nullptr, &security_descriptor); + if (error != ERROR_SUCCESS) { + LOG_ERROR("GetSecurityInfo failed, error={}", error); + return false; + } + + BYTE sid_buffer[SECURITY_MAX_SID_SIZE] = {0}; + DWORD sid_size = sizeof(sid_buffer); + if (!CreateWellKnownSid(WinAuthenticatedUserSid, nullptr, sid_buffer, + &sid_size)) { + error = GetLastError(); + LOG_ERROR("CreateWellKnownSid failed, error={}", error); + LocalFree(security_descriptor); + return false; + } + + EXPLICIT_ACCESSW access{}; + access.grfAccessPermissions = SERVICE_START | SERVICE_QUERY_STATUS; + access.grfAccessMode = SET_ACCESS; + access.grfInheritance = NO_INHERITANCE; + access.Trustee.TrusteeForm = TRUSTEE_IS_SID; + access.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; + access.Trustee.ptstrName = reinterpret_cast(sid_buffer); + + error = SetEntriesInAclW(1, &access, existing_dacl, &updated_dacl); + if (error != ERROR_SUCCESS) { + LOG_ERROR("SetEntriesInAclW failed, error={}", error); + LocalFree(security_descriptor); + return false; + } + + error = SetSecurityInfo(service, SE_SERVICE, DACL_SECURITY_INFORMATION, + nullptr, nullptr, updated_dacl, nullptr); + if (error != ERROR_SUCCESS) { + LOG_ERROR("SetSecurityInfo failed, error={}", error); + LocalFree(updated_dacl); + LocalFree(security_descriptor); + return false; + } + + LocalFree(updated_dacl); + LocalFree(security_descriptor); + return true; +} + std::string QueryNamedPipeMessage(const std::wstring& pipe_name, const std::string& command, DWORD timeout_ms) { @@ -812,6 +907,10 @@ int CrossDeskServiceHost::InitializeRuntime() { RefreshSessionState(); EnsureSessionHelper(); ipc_thread_ = std::thread(&CrossDeskServiceHost::IpcServerLoop, this); + if (!console_mode_) { + client_process_monitor_thread_ = + std::thread(&CrossDeskServiceHost::ClientProcessMonitorLoop, this); + } LOG_INFO("CrossDesk service runtime initialized, session_id={}", active_session_id_); return 0; @@ -825,6 +924,10 @@ void CrossDeskServiceHost::ShutdownRuntime() { SetEvent(stop_event_); } + if (client_process_monitor_thread_.joinable()) { + client_process_monitor_thread_.join(); + } + if (ipc_thread_.joinable()) { ipc_thread_.join(); } @@ -841,6 +944,34 @@ void CrossDeskServiceHost::RequestStop() { } } +void CrossDeskServiceHost::ClientProcessMonitorLoop() { + const ULONGLONG monitor_started_at = GetTickCount64(); + + while (stop_event_ != nullptr) { + DWORD wait_result = + WaitForSingleObject(stop_event_, kCrossDeskClientMonitorIntervalMs); + if (wait_result == WAIT_OBJECT_0) { + return; + } + if (wait_result != WAIT_TIMEOUT) { + continue; + } + + if (GetTickCount64() - monitor_started_at < + kCrossDeskClientMonitorStartupGraceMs) { + continue; + } + + if (HasRunningCrossDeskClientProcess()) { + continue; + } + + LOG_INFO("No crossdesk client process detected, stopping service"); + RequestStop(); + return; + } +} + void CrossDeskServiceHost::ReportServiceStatus(DWORD current_state, DWORD win32_exit_code, DWORD wait_hint) { @@ -1845,7 +1976,7 @@ bool InstallCrossDeskService(const std::wstring& binary_path) { std::wstring service_command = L"\"" + binary_path + L"\" --service"; SC_HANDLE service = CreateServiceW( manager, kCrossDeskServiceName, kCrossDeskServiceDisplayName, - SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, + SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, service_command.c_str(), nullptr, nullptr, nullptr, nullptr, nullptr); @@ -1858,14 +1989,15 @@ bool InstallCrossDeskService(const std::wstring& binary_path) { } service = OpenServiceW(manager, kCrossDeskServiceName, - SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS); + SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | + READ_CONTROL | WRITE_DAC); if (service == nullptr) { LOG_ERROR("OpenServiceW failed, error={}", GetLastError()); CloseServiceHandle(manager); return false; } - if (!ChangeServiceConfigW(service, SERVICE_NO_CHANGE, SERVICE_AUTO_START, + if (!ChangeServiceConfigW(service, SERVICE_NO_CHANGE, SERVICE_DEMAND_START, SERVICE_NO_CHANGE, service_command.c_str(), nullptr, nullptr, nullptr, nullptr, nullptr, kCrossDeskServiceDisplayName)) { @@ -1876,6 +2008,39 @@ bool InstallCrossDeskService(const std::wstring& binary_path) { } } + if (!GrantCrossDeskServiceStartAccessToAuthenticatedUsers(service)) { + CloseServiceHandle(service); + CloseServiceHandle(manager); + return false; + } + + CloseServiceHandle(service); + CloseServiceHandle(manager); + return true; +} + +bool IsCrossDeskServiceInstalled() { + SC_HANDLE manager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT); + if (manager == nullptr) { + LOG_ERROR("OpenSCManagerW failed, error={}", GetLastError()); + return false; + } + + SC_HANDLE service = + OpenServiceW(manager, kCrossDeskServiceName, SERVICE_QUERY_STATUS); + if (service == nullptr) { + DWORD error = GetLastError(); + CloseServiceHandle(manager); + if (error == ERROR_SERVICE_DOES_NOT_EXIST) { + return false; + } + if (error == ERROR_ACCESS_DENIED) { + return true; + } + LOG_ERROR("OpenServiceW failed, error={}", error); + return false; + } + CloseServiceHandle(service); CloseServiceHandle(manager); return true; @@ -1891,7 +2056,10 @@ bool StartCrossDeskService() { SC_HANDLE service = OpenServiceW(manager, kCrossDeskServiceName, SERVICE_START); if (service == nullptr) { - LOG_ERROR("OpenServiceW failed, error={}", GetLastError()); + DWORD error = GetLastError(); + if (error != ERROR_SERVICE_DOES_NOT_EXIST) { + LOG_ERROR("OpenServiceW failed, error={}", error); + } CloseServiceHandle(manager); return false; } diff --git a/src/service/windows/service_host.h b/src/service/windows/service_host.h index f851ffb..9bf7b89 100644 --- a/src/service/windows/service_host.h +++ b/src/service/windows/service_host.h @@ -33,6 +33,7 @@ class CrossDeskServiceHost { int InitializeRuntime(); void ShutdownRuntime(); void RequestStop(); + void ClientProcessMonitorLoop(); void ReportServiceStatus(DWORD current_state, DWORD win32_exit_code, DWORD wait_hint); void IpcServerLoop(); @@ -71,6 +72,7 @@ class CrossDeskServiceHost { SERVICE_STATUS service_status_{}; HANDLE stop_event_ = nullptr; std::thread ipc_thread_; + std::thread client_process_monitor_thread_; std::mutex state_mutex_; DWORD active_session_id_ = 0xFFFFFFFF; DWORD process_session_id_ = 0xFFFFFFFF; @@ -128,6 +130,7 @@ class CrossDeskServiceHost { static CrossDeskServiceHost* instance_; }; +bool IsCrossDeskServiceInstalled(); bool InstallCrossDeskService(const std::wstring& binary_path); bool UninstallCrossDeskService(); bool StartCrossDeskService();