Last time, we learned how to query the global caret position, but we found that it works only for programs that use the system caret. Fancy programs think that the system caret is old and stodgy and prefer to draw their own caret. How can we learn about those custom carets?
We can use the classic Active Accessibility interface IAccessible and ask the focus window for its caret. Programs that draw their own custom caret are expected to respond to this by telling you where their custom caret is.
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) {
Microsoft::WRL::ComPtr<IAccessible> acc;
if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CARET,
IID_PPV_ARGS(&acc))) && acc) {
long x, y, cx, cy;
VARIANT vt{};
vt.vt = VT_I4;
vt.lVal = CHILDID_SELF;
if (acc->accLocation(&x, &y, &cx, &cy, vt) == S_OK) {
SetCursorPos(x + cx - 1, y + cy - 1);
return;
}
}
}
}
This detects the caret in most programs that use a custom caret. I tried Visual Studio, Chromium-based programs, and Microsoft Word, and they all worked. (However, Terminal and Calculator in Worksheet mode didn’t work. They fail to report a caret at all. Sad.)
In the original problem formulation, the goal was to move the cursor to the keyboard focus. The keyboard focus might be represented by a caret, but it might be a selected item on the desktop or some other non-textual focus. We’ll look at that next time.
Is it a coincidence that the two applications you call out in the article are both Microsoft applications that are part of Windows and are open source with their source available on GitHub? Can we look forward to an article where you dig into why one or both of these apps don’t report the caret and maybe see it get fixed?
Why retain the MapWindowPoints call? Are there circumstances in which the system caret data might not appear through IAccessible? Or are we just trying to avoid the COM overhead?
An app might (inadvisedly) decide not to support IAccessible, but if it uses SetCaretPos we would still be able to find it.
By Terminal, do you mean Microsoft’s Windows Terminal app? I just tested and the Windows 10 emoji picker knows where the caret is, so I guess they expose it to IME but not to accessibility?