Last time, we learned how to use Active Accessibility to find the caret, but what if the focus is not on an edit control? In that case, we want to find the focus object.
GUITHREADINFO info = { sizeof(GUITHREADINFO) };
if (GetGUIThreadInfo(0, &info))
{
if (info.flags & GUI_CARETBLINKING) {
⟦ ... ⟧
}
if (info.hwndFocus != nullptr) {
wil::com_ptr_nothrow<IAccessible> acc;
if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CARET,
IID_PPV_ARGS(acc.put()))) && acc) {
⟦ ... ⟧
}
if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CLIENT,
IID_PPV_ARGS(acc.put()))) && acc) {
wil::unique_variant vt;
if (acc->get_accFocus(&vt) == S_OK) {
... to be written ...
}
}
}
}
If we cannot get the location of the system caret, and the focus window denies that it has any caret at all (not even a custom one), then we ask the focus window for the object that represents the window itself (OBJID_), and then ask that object for an identifier for the currently-focused child. We use a wil:: to ensure that the variant is cleaned up with VariantÂClear(), thereby avoiding a leak if the variant type requires cleanup.
Getting the location of the focused child from the child identifier requires a different algorithm depending on the form of the identifier, as documented in “How child IDs are used in output parameters“.
- If the identifier is in the form of a
VT_DISPATCH, then convert thepdispValtoIAccessibleand useCHILDID_SELFas the child ID. - If the identifier is in the form of a
VT_I4, then useget_accChildwith thelValto get anIDispatch, which you can then convert toIAccessible, and useCHILDID_SELFas the child ID. - If the identifier is in the form of a
VT_I4, butget_accChildfails, then use the original object and thelValas the child ID.
There’s also a small optimization: If the identifier is in the form of a VT_I4 and it is already CHILDID_SELF, then use the original object with CHILDID_SELF as the child ID. This optimization works because CHILDID_SELF is always rejected by get_accChild since it can never be the child ID of a child item. And the optimization is useful because it saves a cross-process get_accChild call.
To encapsulate all this logic, we can use a helper function.
std::tuple<wil::com_ptr_nothrow<IAccessible>, LONG>
GetChild(IAccessible* pacc, VARIANT const& vt)
{
if (vt.vt == VT_I4) {
wil::com_ptr_nothrow<IDispatch> disp;
if (vt.lVal != CHILDID_SELF &&
SUCCEEDED(pacc->get_accChild(vt, disp.put())) &&
disp) {
return { disp.try_query<IAccessible>(), CHILDID_SELF };
} else {
return { pacc, vt.lVal };
}
} else if (vt.vt == VT_DISPATCH) {
return { wil::try_com_query_nothrow<IAccessible>(vt.pdispVal),
CHILDID_SELF };
}
return {};
}
The GetChild function takes an IAccessible and a VARIANT produced from its get_accChild and converts it to an IAccessible+childId pair that describes how to access the child item.
if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CLIENT,
IID_PPV_ARGS(acc.put()))) && acc) {
wil::unique_variant vt;
if (acc->get_accFocus(&vt) == S_OK) {
auto [childAcc, childId] = GetChild(acc.get(), vt);
if (childAcc) {
... move cursor to the child location ...
}
}
}
We’ll refactor out the “move the cursor to the object location” into a helper function since we’re using it for both the caret and for child items.
bool SetCursorPosToLocation(IAccessible* acc, LONG childId)
{
long x, y, cx, cy;
VARIANT vt;
vt.vt = VT_I4;
vt.lVal = childId;
if (acc->accLocation(&x, &y, &cx, &cy, vt) == S_OK) {
SetCursorPos(x + cx - 1, y + cy - 1);
return true;
}
return false;
}
That leaves us with this:
GUITHREADINFO info = { sizeof(GUITHREADINFO) };
if (GetGUIThreadInfo(0, &info))
{
if (info.flags & GUI_CARETBLINKING) {
MapWindowPoints(info.hwndCaret, nullptr, (POINT*)&:info.rcCaret, 2);
SetCursorPos(info.rcCaret.right - 1, info.rcCaret.bottom - 1);
return;
}
if (info.hwndFocus != nullptr) {
wil::com_ptr_nothrow<IAccessible> acc;
if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CARET,
IID_PPV_ARGS(acc.put()))) && acc) {
if (SetCursorPosToLocation(acc.get(), CHILDID_SELF)) {
return;
}
}
if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CLIENT,
IID_PPV_ARGS(acc.put()))) && acc) {
wil::unique_variant vt;
if (acc->get_accFocus(&vt) == S_OK) {
auto [childAcc, childId] = GetChild(acc.get(), vt);
if (childAcc && SetCursorPosToLocation(childAcc.get(), childId)) {
return;
}
}
}
}
}
But we’re not done yet. There’s another quirk we’ll have to deal with. Next time.
Accessibility is fun! Since I'm toying with both side; a GUI automation library using IAccessibility and both a custom GUI library itself, I want to have it behave both properly.
Are there guidelines on how the server side of the accessibility must behave? E.g. how the library must implement the interfaces?
What I'm aiming at, for example I had to workaround .NET Forms button interface. Invoke would somehow wait for the button click event handler to be completed. Which absolutely won't work if the button click opens a modal dialog (a Open File dialog in this example). Basically I had to...