January 9th, 2026
like1 reaction

Using Active Accessibility to find out where the focus item is

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_CLIENT), and then ask that object for an identifier for the currently-focused child. We use a wil::unique_variant 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 the pdispVal to IAccessible and use CHILDID_SELF as the child ID.
  • If the identifier is in the form of a VT_I4, then use get_accChild with the lVal to get an IDispatch, which you can then convert to IAccessible, and use CHILDID_SELF as the child ID.
  • If the identifier is in the form of a VT_I4, but get_accChild fails, then use the original object and the lVal as 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.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

1 comment

Sort by :
  • Georg Rottensteiner · Edited

    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...

    Read more