[Keyboard Manager] Replace text update (#46046)

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Doing multiple text replacements in a row was unresponsive and laggy.
This PR addresses that issue.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #46031 

## Validation steps
- Tested before according to the above issue, observed the bug
- Tested and refined the experience manually both locally and with the
exe from the CI build
This commit is contained in:
Jaylyn Barbee
2026-03-10 21:31:14 -04:00
committed by GitHub
parent 8404bfbebb
commit 70e082ce4f

View File

@@ -1,5 +1,8 @@
#include "pch.h" #include "pch.h"
#include "Helpers.h" #include "Helpers.h"
#include <atomic>
#include <condition_variable>
#include <queue>
#include <sstream> #include <sstream>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
@@ -410,69 +413,127 @@ namespace Helpers
SendInput(ARRAYSIZE(pasteInputs), pasteInputs, sizeof(INPUT)); SendInput(ARRAYSIZE(pasteInputs), pasteInputs, sizeof(INPUT));
} }
// Serializes clipboard operations so rapid text remappings don't race. // Single background worker that processes clipboard-paste requests
static std::mutex clipboardMutex; // 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<std::wstring> s_clipboardQueue;
static std::mutex s_queueMutex;
static std::condition_variable s_queueCV;
static std::once_flag s_workerInitFlag;
static std::atomic<bool> s_shutdown{ false };
static std::thread s_workerThread;
// Function to send text via clipboard paste (Ctrl+V). static void ClipboardWorkerLoop()
// 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)
{ {
// Acquire the mutex so that the entire snapshot-paste-restore cycle while (true)
// is atomic with respect to other text remapping calls.
std::unique_lock<std::mutex> lock(clipboardMutex);
// Snapshot current clipboard state
bool hadOriginalText = false;
std::wstring originalClipboardText;
if (OpenClipboard(nullptr))
{ {
if (IsClipboardFormatAvailable(CF_UNICODETEXT)) std::wstring text;
{ {
HANDLE hData = GetClipboardData(CF_UNICODETEXT); std::unique_lock<std::mutex> lock(s_queueMutex);
if (hData) s_queueCV.wait(lock, [] { return !s_clipboardQueue.empty() || s_shutdown.load(); });
if (s_shutdown.load())
{ {
wchar_t* pText = static_cast<wchar_t*>(GlobalLock(hData)); break;
if (pText) }
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; HANDLE hData = GetClipboardData(CF_UNICODETEXT);
hadOriginalText = true; if (hData)
GlobalUnlock(hData); {
wchar_t* pText = static_cast<wchar_t*>(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(); catch (const std::exception& ex)
}
// 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)
{ {
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(); std::lock_guard<std::mutex> lock(s_queueMutex);
CloseClipboard(); 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<std::mutex> lock(s_queueMutex);
if (!s_clipboardQueue.empty())
{
return true;
} }
}).detach(); s_clipboardQueue.push(text);
}
s_queueCV.notify_one();
return true; return true;
} }