diff --git a/src/modules/keyboardmanager/common/Helpers.cpp b/src/modules/keyboardmanager/common/Helpers.cpp index 596b4343c8..cb96c8030f 100644 --- a/src/modules/keyboardmanager/common/Helpers.cpp +++ b/src/modules/keyboardmanager/common/Helpers.cpp @@ -1,5 +1,8 @@ #include "pch.h" #include "Helpers.h" +#include +#include +#include #include #include #include @@ -410,69 +413,127 @@ namespace Helpers SendInput(ARRAYSIZE(pasteInputs), pasteInputs, sizeof(INPUT)); } - // Serializes clipboard operations so rapid text remappings don't race. - static std::mutex clipboardMutex; + // Single background worker that processes clipboard-paste requests + // sequentially. Keeping one thread avoids creating/tearing-down a + // thread on every keypress and naturally serializes clipboard access + // without a separate mutex. + static std::queue s_clipboardQueue; + static std::mutex s_queueMutex; + static std::condition_variable s_queueCV; + static std::once_flag s_workerInitFlag; + static std::atomic s_shutdown{ false }; + static std::thread s_workerThread; - // Function to send text via clipboard paste (Ctrl+V). - // Saves the previous clipboard content and restores it asynchronously. - // The clipboard entry is excluded from clipboard history via - // ExcludeClipboardContentFromMonitorProcessing (set in SetClipboardText). - bool SendTextViaClipboard(const std::wstring& text) + static void ClipboardWorkerLoop() { - // Acquire the mutex so that the entire snapshot-paste-restore cycle - // is atomic with respect to other text remapping calls. - std::unique_lock lock(clipboardMutex); - - // Snapshot current clipboard state - bool hadOriginalText = false; - std::wstring originalClipboardText; - if (OpenClipboard(nullptr)) + while (true) { - if (IsClipboardFormatAvailable(CF_UNICODETEXT)) + std::wstring text; { - HANDLE hData = GetClipboardData(CF_UNICODETEXT); - if (hData) + std::unique_lock lock(s_queueMutex); + s_queueCV.wait(lock, [] { return !s_clipboardQueue.empty() || s_shutdown.load(); }); + if (s_shutdown.load()) { - wchar_t* pText = static_cast(GlobalLock(hData)); - if (pText) + break; + } + text = std::move(s_clipboardQueue.front()); + s_clipboardQueue.pop(); + } + + try + { + // Snapshot current clipboard state + bool hadOriginalText = false; + std::wstring originalClipboardText; + if (OpenClipboard(nullptr)) + { + if (IsClipboardFormatAvailable(CF_UNICODETEXT)) { - originalClipboardText = pText; - hadOriginalText = true; - GlobalUnlock(hData); + HANDLE hData = GetClipboardData(CF_UNICODETEXT); + if (hData) + { + wchar_t* pText = static_cast(GlobalLock(hData)); + if (pText) + { + originalClipboardText = pText; + hadOriginalText = true; + GlobalUnlock(hData); + } + } + } + CloseClipboard(); + } + + // Place our text on the clipboard (with history exclusion) + if (!SetClipboardText(text)) + { + continue; + } + + SendPasteKeystroke(); + + // Ctrl+V is asynchronous (SendInput queues the input), so the + // target app needs time to process the keystroke and read the + // clipboard before we restore the original content. + Sleep(100); + + if (hadOriginalText) + { + SetClipboardText(originalClipboardText); + } + else + { + if (OpenClipboard(nullptr)) + { + EmptyClipboard(); + CloseClipboard(); } } } - CloseClipboard(); - } - - // Place our text on the clipboard (with history exclusion) - if (!SetClipboardText(text)) - { - return false; - } - - SendPasteKeystroke(); - - // Restore clipboard after a delay on a background thread. - // Ctrl+V is asynchronous (SendInput queues the input), so the target - // app needs time to process the keystroke and read the clipboard. - // The lock is moved into the thread so the next call blocks until - // restoration completes. - std::thread([lock = std::move(lock), originalClipboardText = std::move(originalClipboardText), hadOriginalText]() { - Sleep(500); - if (hadOriginalText) + catch (const std::exception& ex) { - SetClipboardText(originalClipboardText); + OutputDebugStringA("KBM ClipboardWorker exception: "); + OutputDebugStringA(ex.what()); + OutputDebugStringA("\n"); } - else + catch (...) { - if (OpenClipboard(nullptr)) + OutputDebugStringA("KBM ClipboardWorker unknown exception\n"); + } + } + } + + // Function to send text via clipboard paste (Ctrl+V). + // Saves the previous clipboard content and restores it asynchronously. + bool SendTextViaClipboard(const std::wstring& text) + { + // Lazily start the worker on first use. + std::call_once(s_workerInitFlag, [] { + s_workerThread = std::thread(ClipboardWorkerLoop); + std::atexit([] { { - EmptyClipboard(); - CloseClipboard(); + std::lock_guard lock(s_queueMutex); + s_shutdown.store(true); } + s_queueCV.notify_one(); + if (s_workerThread.joinable()) + { + s_workerThread.join(); + } + }); + }); + + // Enqueue the text and return immediately so we never block the + // low-level keyboard hook (WH_KEYBOARD_LL). + { + std::lock_guard lock(s_queueMutex); + if (!s_clipboardQueue.empty()) + { + return true; } - }).detach(); + s_clipboardQueue.push(text); + } + s_queueCV.notify_one(); return true; }