Release v0.6.18: verify conversion results and log problems only
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
#pragma once
|
||||
#include "platform.h"
|
||||
#include <uiautomation.h>
|
||||
|
||||
namespace ks
|
||||
{
|
||||
// Read-only UI Automation: never select, move the caret, or touch the clipboard.
|
||||
class PasteProbe
|
||||
{
|
||||
IUIAutomation2 *automation{};
|
||||
IUIAutomationElement *element{};
|
||||
IUIAutomationTextPattern *pattern{};
|
||||
|
||||
public:
|
||||
std::wstring status = L"not-requested";
|
||||
PasteProbe(bool enabled, const std::wstring &original)
|
||||
{
|
||||
if (!enabled)
|
||||
return;
|
||||
status = L"accessibility-unavailable";
|
||||
if (FAILED(CoCreateInstance(CLSID_CUIAutomation8, nullptr, CLSCTX_INPROC_SERVER, IID_IUIAutomation2,
|
||||
(void **)&automation)))
|
||||
return;
|
||||
automation->put_ConnectionTimeout(200);
|
||||
automation->put_TransactionTimeout(200);
|
||||
if (FAILED(automation->GetFocusedElement(&element)) || !element)
|
||||
return;
|
||||
BOOL password = TRUE;
|
||||
if (FAILED(element->get_CurrentIsPassword(&password)) || password)
|
||||
{
|
||||
status = L"protected-or-inaccessible-field";
|
||||
return;
|
||||
}
|
||||
if (FAILED(element->GetCurrentPatternAs(UIA_TextPatternId, IID_IUIAutomationTextPattern,
|
||||
(void **)&pattern)) ||
|
||||
!pattern)
|
||||
return;
|
||||
auto selected = read(1, false);
|
||||
auto normalize = [](std::wstring value)
|
||||
{
|
||||
for (size_t at = 0; (at = value.find(L"\r\n", at)) != value.npos;)
|
||||
value.erase(at, 1);
|
||||
return value;
|
||||
};
|
||||
if (!selected || normalize(*selected) != normalize(original))
|
||||
{
|
||||
if (selected)
|
||||
status = L"initial-selection-mismatch";
|
||||
if (pattern)
|
||||
pattern->Release();
|
||||
pattern = nullptr;
|
||||
return;
|
||||
}
|
||||
status = L"ready";
|
||||
}
|
||||
PasteProbe(const PasteProbe &) = delete;
|
||||
~PasteProbe()
|
||||
{
|
||||
if (pattern)
|
||||
pattern->Release();
|
||||
if (element)
|
||||
element->Release();
|
||||
if (automation)
|
||||
automation->Release();
|
||||
}
|
||||
std::optional<std::wstring> read(int characters, bool allowCaret = true)
|
||||
{
|
||||
if (!pattern)
|
||||
return {};
|
||||
if (characters <= 0 || characters > 4096)
|
||||
{
|
||||
status = L"readback-size-limit";
|
||||
return {};
|
||||
}
|
||||
IUIAutomationElement *focused{};
|
||||
BOOL same = FALSE;
|
||||
if (SUCCEEDED(automation->GetFocusedElement(&focused)) && focused)
|
||||
{
|
||||
automation->CompareElements(element, focused, &same);
|
||||
}
|
||||
if (!same)
|
||||
{
|
||||
if (focused)
|
||||
focused->Release();
|
||||
status = L"focused-control-changed";
|
||||
return {};
|
||||
}
|
||||
BOOL password = TRUE;
|
||||
if (FAILED(focused->get_CurrentIsPassword(&password)) || password)
|
||||
{
|
||||
focused->Release();
|
||||
status = L"protected-or-inaccessible-field";
|
||||
return {};
|
||||
}
|
||||
// Native Edit accessibility proxies can retain a text snapshot in the
|
||||
// pattern object. Obtain a fresh pattern after paste, not the pre-paste one.
|
||||
pattern->Release();
|
||||
pattern = nullptr;
|
||||
HRESULT refreshed =
|
||||
focused->GetCurrentPatternAs(UIA_TextPatternId, IID_IUIAutomationTextPattern, (void **)&pattern);
|
||||
focused->Release();
|
||||
if (FAILED(refreshed) || !pattern)
|
||||
{
|
||||
status = L"accessibility-unavailable";
|
||||
return {};
|
||||
}
|
||||
IUIAutomationTextRangeArray *ranges{};
|
||||
if (FAILED(pattern->GetSelection(&ranges)) || !ranges)
|
||||
{
|
||||
status = L"selection-unavailable";
|
||||
return {};
|
||||
}
|
||||
int count{};
|
||||
ranges->get_Length(&count);
|
||||
IUIAutomationTextRange *range{};
|
||||
if (count == 1)
|
||||
ranges->GetElement(0, &range);
|
||||
ranges->Release();
|
||||
if (!range)
|
||||
{
|
||||
status = L"selection-ambiguous";
|
||||
return {};
|
||||
}
|
||||
int difference{};
|
||||
HRESULT hr = range->CompareEndpoints(TextPatternRangeEndpoint_Start, range,
|
||||
TextPatternRangeEndpoint_End, &difference);
|
||||
if (SUCCEEDED(hr) && difference == 0)
|
||||
{
|
||||
if (!allowCaret)
|
||||
{
|
||||
range->Release();
|
||||
status = L"initial-selection-unavailable";
|
||||
return {};
|
||||
}
|
||||
int moved{};
|
||||
hr = range->MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Character, -characters,
|
||||
&moved);
|
||||
if (SUCCEEDED(hr) && moved != -characters)
|
||||
hr = E_FAIL;
|
||||
}
|
||||
BSTR value{};
|
||||
if (SUCCEEDED(hr))
|
||||
hr = range->GetText(8193, &value);
|
||||
range->Release();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
SysFreeString(value);
|
||||
status = L"range-read-failed";
|
||||
return {};
|
||||
}
|
||||
std::wstring text(value ? value : L"", SysStringLen(value));
|
||||
SysFreeString(value);
|
||||
if (text.size() > 8192)
|
||||
{
|
||||
status = L"readback-size-limit";
|
||||
return {};
|
||||
}
|
||||
status = L"read-back-via-accessibility";
|
||||
return text;
|
||||
}
|
||||
};
|
||||
} // namespace ks
|
||||
Reference in New Issue
Block a user