163 lines
5.2 KiB
C++
163 lines
5.2 KiB
C++
#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
|