Last time, we used Active Accessibility to locate the focus item, but I noted that there was a quirk.
The quirk is that the focus item might be clipped.
For example, if you take a File Explorer window, put it into Details mode, and then resize the window to be narrow enough that a horizontal scroll bar appears, then when you ask for the focus item, the accLocation reports where item is on the screen, even though some of it might be clipped out. Our program ends up putting the cursor far outside the File Explorer window, pointing to the bottom right corner of the unclipped item.
My first attempt to fix this was to clip to the focus window.
bool SetCursorPosToLocation(IAccessible* acc, LONG childId, HWND hwndClip) { long x, y, cx, cy; VARIANT vt; vt.vt = VT_I4; vt.lVal = childId; if (acc->accLocation(&x, &y, &cx, &cy, vt) == S_OK) { RECT rcObject = { x, y, x + cx, y + cy }; RECT rcClient; if (hwndClip && GetClientRect(hwndClip, &rcClip)) { MapWindowPoints(hwndClip, nullptr, (POINT*)&rcObject, 2); if (IsWindow(hwndClip)) { IntersectRect(&rcObject, &rcObject, &rcClient); } } SetCursorPos(rcObject.right - 1, rcObject.bottom - 1); return true; } return false; }
We add a parameter to SetÂCursorÂPosÂToÂLocation to provide an optional clip window. If present, we clip the accessible object’s rectangle to the window’s client area. Getting the window’s client area in screen coordinates is a bit tricky: We get the client area in client coordinates, then map the coordinates to screen coordinates. A zero return value from MapÂWindowÂPoints function is ambiguous: It could mean failure, or it could mean that the rectangle did not need to be adjusted. Instead of trying to figure out which case we are in, I just revalidate the window handle afterward, on the theory that the only thing that could result in a failure from MapÂWindowÂPoints (aside from invalid pointers) is that the window was destroyed while we were talking about it.
If we are able to get the screen coordinates of the clip window’s client area, we intersect it with the item rectangle to find the visible rectangle for the focus item. And that’s the rectangle that we use to position the cursor.
Of course, we also have to pass the clip window.
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,
info.hwndFocus)) {
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,
info.hwndFocus)) {
return;
}
}
}
}
}
I tried this out, and it worked for the most part, but for some programs, the clipping was too aggressive. Sometimes the focus window was 0×0! One way this could happen is when a program creates a dummy window and keeps keyboard focus on it, using it to soak up and distribute all of the program’s input to other windows. As a result, the focus item is not actually constrained by the focus window.
We’ll address this problem next time.
0 comments
Be the first to start the discussion.