From e07a2efbccd0337e368e77a8d6d91fc3b2683f48 Mon Sep 17 00:00:00 2001 From: Nick Bolton Date: Mon, 10 Feb 2025 19:15:41 +0000 Subject: [PATCH] refactor: Implement Windows event loop in AppUtilWindows for graceful shutdown handling --- src/lib/common/common.h | 8 ++++ src/lib/deskflow/App.cpp | 2 +- src/lib/deskflow/App.h | 1 + src/lib/deskflow/win32/AppUtilWindows.cpp | 54 ++++++++++++++++++++--- src/lib/deskflow/win32/AppUtilWindows.h | 14 +++++- src/lib/platform/MSWindowsProcess.cpp | 10 +++++ src/lib/platform/MSWindowsWatchdog.cpp | 1 + 7 files changed, 82 insertions(+), 8 deletions(-) diff --git a/src/lib/common/common.h b/src/lib/common/common.h index ba127398d..a99125f98 100644 --- a/src/lib/common/common.h +++ b/src/lib/common/common.h @@ -41,3 +41,11 @@ enum kExitArgs = 3, // bad arguments kExitConfig = 4, // cannot read configuration }; + +#if WINAPI_MSWINDOWS +namespace deskflow::common { + +const auto kCloseEventName = "Global\\DeskflowCloseEvent"; + +} +#endif diff --git a/src/lib/deskflow/App.cpp b/src/lib/deskflow/App.cpp index b417c41b0..0973eacf1 100644 --- a/src/lib/deskflow/App.cpp +++ b/src/lib/deskflow/App.cpp @@ -23,6 +23,7 @@ #include "deskflow/protocol_types.h" #include "ipc/IpcMessage.h" #include "ipc/IpcServerProxy.h" +#include #if SYSAPI_WIN32 #include "arch/win32/ArchMiscWindows.h" @@ -165,7 +166,6 @@ void App::loggingFilterWarning() void App::initApp(int argc, const char **argv) { - std::string configFilename; CLI::App cliApp{kAppDescription}; cliApp.add_option("--config-toml", configFilename, "Use TOML configuration file"); diff --git a/src/lib/deskflow/App.h b/src/lib/deskflow/App.h index 1faef6178..072d0c375 100644 --- a/src/lib/deskflow/App.h +++ b/src/lib/deskflow/App.h @@ -20,6 +20,7 @@ #endif #include +#include namespace deskflow { class Screen; diff --git a/src/lib/deskflow/win32/AppUtilWindows.cpp b/src/lib/deskflow/win32/AppUtilWindows.cpp index 969cd2202..ca32daaa5 100644 --- a/src/lib/deskflow/win32/AppUtilWindows.cpp +++ b/src/lib/deskflow/win32/AppUtilWindows.cpp @@ -1,6 +1,6 @@ /* * Deskflow -- mouse and keyboard sharing utility - * SPDX-FileCopyrightText: (C) 2012 Symless Ltd. + * SPDX-FileCopyrightText: (C) 2012 - 2025 Symless Ltd. * SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception */ @@ -10,11 +10,10 @@ #include "arch/win32/ArchMiscWindows.h" #include "arch/win32/XArchWindows.h" #include "base/Event.h" -#include "base/EventQueue.h" #include "base/IEventQueue.h" #include "base/Log.h" #include "base/log_outputters.h" -#include "common/constants.h" +#include "common/common.h" #include "deskflow/App.h" #include "deskflow/ArgsBase.h" #include "deskflow/Screen.h" @@ -24,9 +23,7 @@ #include #include #include -#include #include -#include #if HAVE_WINTOAST #include "wintoastlib.h" @@ -37,10 +34,21 @@ AppUtilWindows::AppUtilWindows(IEventQueue *events) : m_events(events), m_exitMo if (SetConsoleCtrlHandler((PHANDLER_ROUTINE)consoleHandler, TRUE) == FALSE) { throw XArch(new XArchEvalWindows()); } + + m_eventThread = std::thread(&AppUtilWindows::eventLoop, this); // NOSONAR - No jthread on Windows + + // Waiting for the event loop start prevents race condition in fast fail scenario, + // where the dtor is called just before the event loop starts. + LOG_DEBUG("waiting for event thread to start"); + std::unique_lock lock(m_eventThreadStartedMutex); + m_eventThreadStartedCond.wait(lock, [this] { return m_eventThreadRunning; }); + LOG_DEBUG("event thread started"); } AppUtilWindows::~AppUtilWindows() { + m_eventThreadRunning = false; + m_eventThread.join(); } BOOL WINAPI AppUtilWindows::consoleHandler(DWORD) @@ -263,3 +271,39 @@ void AppUtilWindows::showNotification(const std::string &title, const std::strin LOG((CLOG_INFO "toast notifications are not supported")); #endif } + +void AppUtilWindows::eventLoop() +{ + HANDLE hCloseEvent = CreateEventA(nullptr, TRUE, FALSE, deskflow::common::kCloseEventName); + if (!hCloseEvent) { + LOG_CRIT("failed to create event for windows event loop"); + throw XArch(new XArchEvalWindows()); + } + + LOG_DEBUG("windows event loop running"); + { + std::lock_guard lock(m_eventThreadStartedMutex); + m_eventThreadRunning = true; + } + m_eventThreadStartedCond.notify_one(); + + while (m_eventThreadRunning) { + // Wait for 100ms at most so that we can stop the loop when the app is closing, if not already stopped. + DWORD closeEventResult = MsgWaitForMultipleObjects(1, &hCloseEvent, FALSE, 100, QS_ALLINPUT); + + if (closeEventResult == WAIT_OBJECT_0) { + LOG_DEBUG("windows event loop received close event"); + m_events->addEvent(Event(Event::kQuit)); + m_eventThreadRunning = false; + } else if (closeEventResult == WAIT_OBJECT_0 + 1) { + MSG msg; + while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } + + CloseHandle(hCloseEvent); + LOG_DEBUG("windows event loop finished"); +} diff --git a/src/lib/deskflow/win32/AppUtilWindows.h b/src/lib/deskflow/win32/AppUtilWindows.h index 1cb557189..cd94005cb 100644 --- a/src/lib/deskflow/win32/AppUtilWindows.h +++ b/src/lib/deskflow/win32/AppUtilWindows.h @@ -1,6 +1,6 @@ /* * Deskflow -- mouse and keyboard sharing utility - * SPDX-FileCopyrightText: (C) 2012 - 2016 Symless Ltd. + * SPDX-FileCopyrightText: (C) 2012 - 2025 Symless Ltd. * SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception */ @@ -10,7 +10,11 @@ #include "deskflow/AppUtil.h" #define WIN32_LEAN_AND_MEAN -#include "Windows.h" +#include "Windows.h" // IWYU pragma: keep + +#include +#include +#include #define ARCH_APP_UTIL AppUtilWindows @@ -44,6 +48,12 @@ public: private: AppExitMode m_exitMode; IEventQueue *m_events; + std::thread m_eventThread; // NOSONAR - No jthread on Windows + bool m_eventThreadRunning = false; + std::condition_variable m_eventThreadStartedCond; + std::mutex m_eventThreadStartedMutex; + + void eventLoop(); static BOOL WINAPI consoleHandler(DWORD Event); }; diff --git a/src/lib/platform/MSWindowsProcess.cpp b/src/lib/platform/MSWindowsProcess.cpp index 8de7964e7..9ec075ba4 100644 --- a/src/lib/platform/MSWindowsProcess.cpp +++ b/src/lib/platform/MSWindowsProcess.cpp @@ -122,6 +122,16 @@ void MSWindowsProcess::shutdown(HANDLE handle, DWORD pid, int timeout) { LOG_DEBUG("shutting down process %d", pid); + LOG_DEBUG("sending close event to close process gracefully"); + HANDLE hCloseEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, deskflow::common::kCloseEventName); + if (hCloseEvent) { // NOSONAR -- Readability + SetEvent(hCloseEvent); + CloseHandle(hCloseEvent); + } else { + LOG((CLOG_WARN "could not send close event to process")); + throw XArch(new XArchEvalWindows); + } + DWORD exitCode; GetExitCodeProcess(handle, &exitCode); if (exitCode != STILL_ACTIVE) { diff --git a/src/lib/platform/MSWindowsWatchdog.cpp b/src/lib/platform/MSWindowsWatchdog.cpp index 9b55c5daf..c694a06ca 100644 --- a/src/lib/platform/MSWindowsWatchdog.cpp +++ b/src/lib/platform/MSWindowsWatchdog.cpp @@ -167,6 +167,7 @@ MSWindowsWatchdog::getUserToken(LPSECURITY_ATTRIBUTES security, bool elevatedTok void MSWindowsWatchdog::mainLoop(void *) { LOG_DEBUG("starting main loop"); + shutdownExistingProcesses(); // the SendSAS function is used to send a sas (secure attention sequence) to the