Release v0.6.30: guard paste retries and clear failed conversion state
This commit is contained in:
+50
@@ -338,6 +338,32 @@ class Transaction
|
||||
Sleep(300);
|
||||
return true;
|
||||
}
|
||||
bool nativeSelectionIs(const std::wstring &original) const
|
||||
{
|
||||
if (!valid()) return false;
|
||||
GUITHREADINFO gui{sizeof(gui)};
|
||||
if (!GetGUIThreadInfo(GetWindowThreadProcessId(target, nullptr), &gui) || !gui.hwndFocus) return false;
|
||||
wchar_t cls[32]{};
|
||||
GetClassNameW(gui.hwndFocus, cls, 32);
|
||||
if (_wcsicmp(cls, L"Edit") != 0 ||
|
||||
(GetWindowLongPtrW(gui.hwndFocus, GWL_STYLE) & (ES_PASSWORD | ES_READONLY))) return false;
|
||||
auto query = [&](UINT message, WPARAM wp, LPARAM lp, DWORD_PTR &result)
|
||||
{
|
||||
return SendMessageTimeoutW(gui.hwndFocus, message, wp, lp,
|
||||
SMTO_ABORTIFHUNG | SMTO_BLOCK, 100, &result) != 0;
|
||||
};
|
||||
DWORD_PTR range{}, length{}, copied{};
|
||||
if (!query(EM_GETSEL, 0, 0, range) || (DWORD)range == 0xffffffff ||
|
||||
size_t(HIWORD(range) - LOWORD(range)) != original.size() ||
|
||||
!query(WM_GETTEXTLENGTH, 0, 0, length) || length > 65534 || HIWORD(range) > length) return false;
|
||||
std::wstring content(length + 1, L'\0');
|
||||
if (!query(WM_GETTEXT, content.size(), (LPARAM)content.data(), copied) || copied != length) return false;
|
||||
DWORD_PTR after{};
|
||||
GUITHREADINFO focused{sizeof(focused)};
|
||||
return query(EM_GETSEL, 0, 0, after) && after == range && valid() &&
|
||||
GetGUIThreadInfo(GetWindowThreadProcessId(target, nullptr), &focused) && focused.hwndFocus == gui.hwndFocus &&
|
||||
content.substr(LOWORD(range), original.size()) == original;
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
App::App(HINSTANCE h, bool test, const std::filesystem::path &dataRoot) : instance(h), smoke(test)
|
||||
@@ -1263,6 +1289,11 @@ void App::transform(int command, int snippet)
|
||||
actual = text;
|
||||
verification = L"selection-mismatch";
|
||||
reason = L"previous-result-verification-mismatch";
|
||||
{
|
||||
std::lock_guard lock(stateMutex);
|
||||
last.reset();
|
||||
run.reset();
|
||||
}
|
||||
if (tx.valid())
|
||||
chord(VK_RIGHT);
|
||||
return;
|
||||
@@ -1332,11 +1363,30 @@ void App::transform(int command, int snippet)
|
||||
if (tx.valid())
|
||||
{
|
||||
auto observed = probe.read((int)caretSteps(result));
|
||||
// A native Edit may silently ignore Ctrl+V while the clipboard is busy.
|
||||
// Retry once only with fresh native proof that the exact original range
|
||||
// is still selected. Never retry an unknown or changed editor state.
|
||||
if (observed && newlines(*observed) == newlines(text) && tx.nativeSelectionIs(text))
|
||||
{
|
||||
diagnosticEvent(L"Paste retry: native Edit still selects the unchanged original");
|
||||
if (tx.paste(textClip(result)) && tx.valid())
|
||||
observed = probe.read((int)caretSteps(result));
|
||||
else
|
||||
observed.reset();
|
||||
}
|
||||
verification = probe.status;
|
||||
if (observed && tx.valid())
|
||||
{
|
||||
actual = *observed;
|
||||
reason = conversionReadbackOutcome(newlines(actual), newlines(text), newlines(result));
|
||||
if (newlines(actual) != newlines(result))
|
||||
{
|
||||
std::lock_guard lock(stateMutex);
|
||||
last.reset();
|
||||
run.reset();
|
||||
diagnosticEvent(L"Paste rejected by readback: previous result cleared; layout not switched");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else reason = L"Paste not verified";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user