mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 09:46:54 +02:00
[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:
@@ -1,5 +1,8 @@
|
||||
#include "pch.h"
|
||||
#include "Helpers.h"
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <queue>
|
||||
#include <sstream>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
@@ -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<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).
|
||||
// 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<std::mutex> 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<std::mutex> lock(s_queueMutex);
|
||||
s_queueCV.wait(lock, [] { return !s_clipboardQueue.empty() || s_shutdown.load(); });
|
||||
if (s_shutdown.load())
|
||||
{
|
||||
wchar_t* pText = static_cast<wchar_t*>(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<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();
|
||||
}
|
||||
|
||||
// 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<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;
|
||||
}
|
||||
}).detach();
|
||||
s_clipboardQueue.push(text);
|
||||
}
|
||||
s_queueCV.notify_one();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user