[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,19 +413,35 @@ 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); std::wstring text;
{
std::unique_lock<std::mutex> lock(s_queueMutex);
s_queueCV.wait(lock, [] { return !s_clipboardQueue.empty() || s_shutdown.load(); });
if (s_shutdown.load())
{
break;
}
text = std::move(s_clipboardQueue.front());
s_clipboardQueue.pop();
}
try
{
// Snapshot current clipboard state // Snapshot current clipboard state
bool hadOriginalText = false; bool hadOriginalText = false;
std::wstring originalClipboardText; std::wstring originalClipboardText;
@@ -448,18 +467,16 @@ namespace Helpers
// Place our text on the clipboard (with history exclusion) // Place our text on the clipboard (with history exclusion)
if (!SetClipboardText(text)) if (!SetClipboardText(text))
{ {
return false; continue;
} }
SendPasteKeystroke(); SendPasteKeystroke();
// Restore clipboard after a delay on a background thread. // Ctrl+V is asynchronous (SendInput queues the input), so the
// Ctrl+V is asynchronous (SendInput queues the input), so the target // target app needs time to process the keystroke and read the
// app needs time to process the keystroke and read the clipboard. // clipboard before we restore the original content.
// The lock is moved into the thread so the next call blocks until Sleep(100);
// restoration completes.
std::thread([lock = std::move(lock), originalClipboardText = std::move(originalClipboardText), hadOriginalText]() {
Sleep(500);
if (hadOriginalText) if (hadOriginalText)
{ {
SetClipboardText(originalClipboardText); SetClipboardText(originalClipboardText);
@@ -472,7 +489,51 @@ namespace Helpers
CloseClipboard(); CloseClipboard();
} }
} }
}).detach(); }
catch (const std::exception& ex)
{
OutputDebugStringA("KBM ClipboardWorker exception: ");
OutputDebugStringA(ex.what());
OutputDebugStringA("\n");
}
catch (...)
{
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([] {
{
std::lock_guard<std::mutex> 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<std::mutex> lock(s_queueMutex);
if (!s_clipboardQueue.empty())
{
return true;
}
s_clipboardQueue.push(text);
}
s_queueCV.notify_one();
return true; return true;
} }