获取文本插入点光标坐标——增强版

之前写的通过hook资源管理器获取文本插入点坐标 – AutoAHK 这篇文章用了hook方式获取光标坐标,这是一种高成本高风险的方式,老实说不太推荐使用。经过一段时间的研究发现,除了过去已有的ACC(MSAA)途径,UIA也提供了相应的一种获取光标坐标的方法:IUIAutomationTextRange::GetBoundingRectangles 它一直被我忽略了。Explorer应该也是调用了这个方法。

下面提供封装好的函数,它结合了ACC和UIA,目前测试下来适用于包括单纯靠Win32的GetCaretPos和ACC无法获取坐标的Windows Terminal窗口,以及UWP这种Metro风格窗口在内的大部分窗口。对部分游戏和工业软件的自绘窗口可能依然无能为力,毕竟实现MSAA和UIA接口的主动权在软件开发者手里。

V1:

/*
f1::
    CoordMode, ToolTip, Screen
    if (hwnd := GetCaretPosEx(x, y, w, h)) {
        WinGetClass, classname, ahk_id %hwnd%
        ToolTip, %classname%, x, y + h
    }
    else {
        ToolTip
    }
return
*/

GetCaretPosEx(byref x = 0, byref y = 0, byref w = 0, byref h = 0) {
    x := y := w := h := hwnd := 0
    static iUIAutomation, hOleacc, IID_IAccessible, guiThreadInfo, init
    if !init {
        init := true
        try
            iUIAutomation := ComObjCreate("{E22AD333-B25F-460C-83D0-0581107395C9}", "{30CBE57D-D9D0-452A-AB13-7AC5AC4825EE}")
        hOleacc := DllCall("LoadLibrary", "str", "Oleacc.dll", "ptr")
        VarSetCapacity(IID_IAccessible, 16), NumPut(0x11CF3C3D618736E0, IID_IAccessible, "int64"), NumPut(0x719B3800AA000C81, IID_IAccessible, 8, "int64")
        VarSetCapacity(guiThreadInfo, size := (A_PtrSize == 8 ? 72 : 48)), NumPut(size, guiThreadInfo, "uint")
    }
    if !iUIAutomation || DllCall(NumGet(NumGet(iUIAutomation + 0), 8 * A_PtrSize), "ptr", iUIAutomation, "ptr*", eleFocus) || !eleFocus
        goto useAccLocation
    ; Check read only property
    if !DllCall(NumGet(NumGet(eleFocus + 0), 16 * A_PtrSize), "ptr", eleFocus, "int", 10002, "ptr*", valuePattern) && valuePattern
        if !DllCall(NumGet(NumGet(valuePattern + 0), 5 * A_PtrSize), "ptr", valuePattern, "int*", isReadOnly) && isReadOnly
            goto cleanUp
    ; Plan A applies to windows that implement IAccessible, such as chrome
    useAccLocation:
    if DllCall("GetGUIThreadInfo", "uint", DllCall("GetWindowThreadProcessId", "ptr", WinExist("A"), "ptr", 0, "uint"), "ptr", &guiThreadInfo)
        hwndFocus := NumGet(guiThreadInfo, A_PtrSize == 8 ? 16 : 12, "ptr")
    if !hwndFocus
        hwndFocus := WinExist()
    if hOleacc && !DllCall("Oleacc\AccessibleObjectFromWindow", "ptr", hwndFocus, "uint", 0xFFFFFFF8, "ptr", &IID_IAccessible, "ptr*", accCaret) && accCaret {
        VarSetCapacity(id, 24, 0), NumPut(3, id, "ushort")
        if !DllCall(NumGet(NumGet(accCaret + 0), 22 * A_PtrSize), "ptr", accCaret, "int*", x, "int*", y, "int*", w, "int*", h, "ptr", &id) {
            hwnd := hwndFocus
            goto cleanUp
        }
    }
    if iUIAutomation && eleFocus {
        ; use IUIAutomationTextPattern2::GetCaretRange
        if DllCall(NumGet(NumGet(eleFocus + 0), 16 * A_PtrSize), "ptr", eleFocus, "int", 10024, "ptr*", textPattern2, "int") || !textPattern2
        || DllCall(NumGet(NumGet(textPattern2 + 0), 10 * A_PtrSize), "ptr", textPattern2, "int*", isActive, "ptr*", caretTextRange) || !caretTextRange || !isActive
        || DllCall(NumGet(NumGet(caretTextRange + 0), 10 * A_PtrSize), "ptr", caretTextRange, "ptr*", rects) || !rects || (rects := ComObject(0x2005, rects, 1)).MaxIndex() < 3
            goto useGetSelection
        x := rects[0], y := rects[1], w := rects[2], h := rects[3], hwnd := hwndFocus
        goto cleanUp
        useGetSelection:
        ; use IUIAutomationTextPattern::GetSelection
        if DllCall(NumGet(NumGet(eleFocus + 0), 16 * A_PtrSize), "ptr", eleFocus, "int", 10014, "ptr*", textPattern) || !textPattern
        || DllCall(NumGet(NumGet(textPattern + 0), 5 * A_PtrSize), "ptr", textPattern, "ptr*", selectionRangeArray) || !selectionRangeArray
        || DllCall(NumGet(NumGet(selectionRangeArray + 0), 3 * A_PtrSize), "ptr", selectionRangeArray, "int*", length) || !length
        || DllCall(NumGet(NumGet(selectionRangeArray + 0), 4 * A_PtrSize), "ptr", selectionRangeArray, "int", 0, "ptr*", selectionRange) || !selectionRange
        || DllCall(NumGet(NumGet(selectionRange + 0), 10 * A_PtrSize), "ptr", selectionRange, "ptr*", rects) || !rects
            goto useGUITHREADINFO
        rects := ComObject(0x2005, rects, 1)
        if rects.MaxIndex() < 3 && DllCall(NumGet(NumGet(selectionRange + 0), 6 * A_PtrSize), "ptr", selectionRange, "int", 0)
        || DllCall(NumGet(NumGet(selectionRange + 0), 10 * A_PtrSize), "ptr", selectionRange, "ptr*", rects) || !rects || (rects := ComObject(0x2005, rects, 1)).MaxIndex() < 3
            goto useGUITHREADINFO
        x := rects[0], y := rects[1], w := rects[2], h := rects[3], hwnd := hwndFocus
        goto cleanUp
    }
    useGUITHREADINFO:
    if hwndCaret := NumGet(guiThreadInfo, A_PtrSize == 8 ? 48 : 28, "ptr") {
        VarSetCapacity(clientRect, 16)
        if DllCall("GetWindowRect", "ptr", hwndCaret, "ptr", &clientRect) {
            offset := A_PtrSize == 8 ? 56 : 32
            w := NumGet(guiThreadInfo, offset + 8, "int") - NumGet(guiThreadInfo, offset, "int")
            h := NumGet(guiThreadInfo, offset + 12, "int") - NumGet(guiThreadInfo, offset + 4, "int")
            DllCall("ClientToScreen", "ptr", hwndCaret, "ptr", &guiThreadInfo + offset)
            x := NumGet(guiThreadInfo, offset, "int")
            y := NumGet(guiThreadInfo, offset + 4, "int")
            hwnd := hwndCaret
        }
    }
    cleanUp:
    for _, p in [eleFocus, valuePattern, textPattern2, caretTextRange, textPattern, selectionRangeArray, selectionRange, accCaret]
        (p && ObjRelease(p))
    return hwnd
}

V2:

/*
f1::{
    CoordMode("ToolTip", "Screen")
    if hwnd := GetCaretPosEx(&x, &y, &w, &h)
        ToolTip(WinGetClass(hwnd), x, y + h)
    else
        ToolTip()
}
*/
GetCaretPosEx(&x?, &y?, &w?, &h?) {
    x := h := w := h := 0
    static iUIAutomation := 0, hOleacc := 0, IID_IAccessible, guiThreadInfo, _ := init()
    if !iUIAutomation || ComCall(8, iUIAutomation, "ptr*", eleFocus := ComValue(13, 0), "int") || !eleFocus.Ptr
        goto useAccLocation
    if !ComCall(16, eleFocus, "int", 10002, "ptr*", valuePattern := ComValue(13, 0), "int") && valuePattern.Ptr
        if !ComCall(5, valuePattern, "int*", &isReadOnly := 0) && isReadOnly
            return 0
    useAccLocation:
    ; use IAccessible::accLocation
    hwndFocus := DllCall("GetGUIThreadInfo", "uint", DllCall("GetWindowThreadProcessId", "ptr", WinExist("A"), "ptr", 0, "uint"), "ptr", guiThreadInfo) && NumGet(guiThreadInfo, A_PtrSize == 8 ? 16 : 12, "ptr") || WinExist()
    if hOleacc && !DllCall("Oleacc\AccessibleObjectFromWindow", "ptr", hwndFocus, "uint", 0xFFFFFFF8, "ptr", IID_IAccessible, "ptr*", accCaret := ComValue(13, 0), "int") && accCaret.Ptr {
        NumPut("ushort", 3, varChild := Buffer(24, 0))
        if !ComCall(22, accCaret, "int*", &x := 0, "int*", &y := 0, "int*", &w := 0, "int*", &h := 0, "ptr", varChild, "int")
            return hwndFocus
    }
    if iUIAutomation && eleFocus {
        ; use IUIAutomationTextPattern2::GetCaretRange
        if ComCall(16, eleFocus, "int", 10024, "ptr*", textPattern2 := ComValue(13, 0), "int") || !textPattern2.Ptr
            goto useGetSelection
        if ComCall(10, textPattern2, "int*", &isActive := 0, "ptr*", caretTextRange := ComValue(13, 0), "int") || !caretTextRange.Ptr || !isActive
            goto useGetSelection
        if !ComCall(10, caretTextRange, "ptr*", &rects := 0, "int") && rects && (rects := ComValue(0x2005, rects, 1)).MaxIndex() >= 3 {
            x := rects[0], y := rects[1], w := rects[2], h := rects[3]
            return hwndFocus
        }
        useGetSelection:
        ; use IUIAutomationTextPattern::GetSelection
        if textPattern2.Ptr
            textPattern := textPattern2
        else if ComCall(16, eleFocus, "int", 10014, "ptr*", textPattern := ComValue(13, 0), "int") || !textPattern.Ptr
            goto useGUITHREADINFO
        if ComCall(5, textPattern, "ptr*", selectionRangeArray := ComValue(13, 0), "int") || !selectionRangeArray.Ptr
            goto useGUITHREADINFO
        if ComCall(3, selectionRangeArray, "int*", &length := 0, "int") || length <= 0
            goto useGUITHREADINFO
        if ComCall(4, selectionRangeArray, "int", 0, "ptr*", selectionRange := ComValue(13, 0), "int") || !selectionRange.Ptr
            goto useGUITHREADINFO
        if ComCall(10, selectionRange, "ptr*", &rects := 0, "int") || !rects
            goto useGUITHREADINFO
        rects := ComValue(0x2005, rects, 1)
        if rects.MaxIndex() < 3 {
            if ComCall(6, selectionRange, "int", 0, "int") || ComCall(10, selectionRange, "ptr*", &rects := 0, "int") || !rects
                goto useGUITHREADINFO
            rects := ComValue(0x2005, rects, 1)
            if rects.MaxIndex() < 3
                goto useGUITHREADINFO
        }
        x := rects[0], y := rects[1], w := rects[2], h := rects[3]
        return hwndFocus
    }
    useGUITHREADINFO:
    if hwndCaret := NumGet(guiThreadInfo, A_PtrSize == 8 ? 48 : 28, "ptr") {
        if DllCall("GetWindowRect", "ptr", hwndCaret, "ptr", clientRect := Buffer(16)) {
            w := NumGet(guiThreadInfo, 64, "int") - NumGet(guiThreadInfo, 56, "int")
            h := NumGet(guiThreadInfo, 68, "int") - NumGet(guiThreadInfo, 60, "int")
            DllCall("ClientToScreen", "ptr", hwndCaret, "ptr", guiThreadInfo.Ptr + 56)
            x := NumGet(guiThreadInfo, 56, "int")
            y := NumGet(guiThreadInfo, 60, "int")
            return hwndCaret
        }
    }
    return 0
    static init() {
        try
            iUIAutomation := ComObject("{E22AD333-B25F-460C-83D0-0581107395C9}", "{30CBE57D-D9D0-452A-AB13-7AC5AC4825EE}")
        hOleacc := DllCall("LoadLibraryW", "str", "Oleacc.dll", "ptr")
        NumPut("int64", 0x11CF3C3D618736E0, "int64", 0x719B3800AA000C81, IID_IAccessible := Buffer(16))
        guiThreadInfo := Buffer(A_PtrSize == 8 ? 72 : 48), NumPut("uint", guiThreadInfo.Size, guiThreadInfo)
    }
}

C++源码:

#include <Windows.h>
#include <atlbase.h>
#include <atlsafe.h>
#include <UIAutomation.h>
#include <oleacc.h>
#include <iostream>

#pragma comment(lib, "Oleacc.lib")

HWND GetCaretPosEx(long *pX, long *pY, long *pW, long *pH);

int main() {
    if (FAILED(CoInitialize(nullptr))) {
        return 0;
    }
    int count = 0;
    while (1) {
        long x = 0, y = 0, w = 0, h = 0;
        HWND hwnd = GetCaretPosEx(&x, &y, &w, &h);
        WCHAR className[255];
        if (hwnd != nullptr) {
            GetClassNameW(hwnd, className, sizeof(className) / sizeof(*className));
            std::wcout << count++ << "\t" << className << "\tx: " << x << "\ty: " << y << "\tw: " << w << "\th: " << h << std::endl;
        }
        Sleep(500);
    }
    CoUninitialize();
    return 0;
}

HWND GetCaretPosEx(long *pX, long *pY, long *pW, long *pH) {
    CComPtr<IUIAutomation> uia;
    CComPtr<IUIAutomationElement> eleFocus;
    CComPtr<IUIAutomationValuePattern> valuePattern;
    if (S_OK != uia.CoCreateInstance(CLSID_CUIAutomation) || uia == nullptr) {
        return nullptr;
    }
    if (S_OK != uia->GetFocusedElement(&eleFocus) || eleFocus == nullptr) {
        goto useAccLocation;
    }
    if (S_OK == eleFocus->GetCurrentPatternAs(UIA_ValuePatternId, IID_PPV_ARGS(&valuePattern)) && valuePattern != nullptr) {
        BOOL isReadOnly;
        if (S_OK == valuePattern->get_CurrentIsReadOnly(&isReadOnly) && isReadOnly) {
#ifdef DEBUG
        std::wcout<<L"Read Only"<<std::endl;
#endif // DEBUG
            return nullptr;
        }
    }

useAccLocation:
    //use IAccessible::accLocation
    GUITHREADINFO guiThreadInfo = {sizeof(guiThreadInfo)};
    HWND hwndFocus = GetForegroundWindow();
    GetGUIThreadInfo(GetWindowThreadProcessId(hwndFocus, nullptr), &guiThreadInfo);
    hwndFocus = guiThreadInfo.hwndFocus ? guiThreadInfo.hwndFocus : hwndFocus;

    CComPtr<IAccessible> accCaret;
    if (S_OK == AccessibleObjectFromWindow(hwndFocus, OBJID_CARET, IID_PPV_ARGS(&accCaret)) && accCaret != nullptr) {
        CComVariant varChild = CComVariant(0);
        if (S_OK == accCaret->accLocation(pX, pY, pW, pH, varChild)) {
#ifdef DEBUG
        std::wcout<<L"IAccessible::accLocation Succeeded"<<std::endl;
#endif // DEBUG
            return hwndFocus;
        }
    }
    if (eleFocus == nullptr) {
        return nullptr;
    }

    // use IUIAutomationTextPattern2::GetCaretRange
    CComPtr<IUIAutomationTextPattern2> textPattern2;
    CComPtr<IUIAutomationTextRange> caretTextRange;
    CComSafeArray<double> rects;
    void *pVal = nullptr;
    BOOL IsActive = FALSE;
    if (S_OK != eleFocus->GetCurrentPatternAs(UIA_TextPattern2Id, IID_PPV_ARGS(&textPattern2)) || textPattern2 == nullptr) {
        goto useGetSelection;
    }
    if (S_OK != textPattern2->GetCaretRange(&IsActive, &caretTextRange) || caretTextRange == nullptr || !IsActive) {
        goto useGetSelection;
    }
    if (S_OK == caretTextRange->GetBoundingRectangles(rects.GetSafeArrayPtr()) && rects != nullptr && SUCCEEDED(SafeArrayLock(rects)) && rects.GetCount() >= 4) {
        *pX = long(rects[0]);
        *pY = long(rects[1]);
        *pW = long(rects[2]);
        *pH = long(rects[3]);
#ifdef DEBUG
        std::wcout<<L"IUIAutomationTextPattern2::GetCaretRange Succeeded"<<std::endl;
#endif // DEBUG
        return hwndFocus;
    }

useGetSelection:
    // use IUIAutomationTextPattern::GetSelection
    CComPtr<IUIAutomationTextPattern> textPattern;
    CComPtr<IUIAutomationTextRangeArray> selectionRangeArray;
    CComPtr<IUIAutomationTextRange> selectionRange;
    if (textPattern2 == nullptr) {
        if (S_OK != eleFocus->GetCurrentPatternAs(UIA_TextPatternId, IID_PPV_ARGS(&textPattern)) || textPattern == nullptr) {
            return nullptr;
        }
    }
    else {
        textPattern = textPattern2;
    }
    if (S_OK != textPattern->GetSelection(&selectionRangeArray) || selectionRangeArray == nullptr) {
        return nullptr;
    }
    int length = 0;
    if (S_OK != selectionRangeArray->get_Length(&length) || length <= 0) {
        return nullptr;
    }
    if (S_OK != selectionRangeArray->GetElement(0, &selectionRange) || selectionRange == nullptr) {
        return nullptr;
    }
    if (S_OK != selectionRange->GetBoundingRectangles(rects.GetSafeArrayPtr()) || rects == nullptr || FAILED(SafeArrayLock(rects))) {
        return nullptr;
    }
    if (rects.GetCount() < 4) {
        if (S_OK != selectionRange->ExpandToEnclosingUnit(TextUnit_Character)) {
            return nullptr;
        }
        if (S_OK != selectionRange->GetBoundingRectangles(rects.GetSafeArrayPtr()) || rects == nullptr || FAILED(SafeArrayLock(rects)) || rects.GetCount() < 4) {
            return nullptr;
        }
    }
    *pX = long(rects[0]);
    *pY = long(rects[1]);
    *pW = long(rects[2]);
    *pH = long(rects[3]);
#ifdef DEBUG
        std::wcout<<L"IUIAutomationTextPattern::GetSelection Succeeded"<<std::endl;
#endif // DEBUG
    return hwndFocus;
}

给TA捐赠
共{{data.count}}人
人已捐赠
其他

vimd修改

2022-11-7 10:59:36

其他函数

取时间戳函数[毫秒级]

2022-12-11 22:14:43

12 条回复 A文章作者 M管理员
  1. AHK中文社区
    11010010给您捐赠了¥2
  2. AHK中文社区

    可否分享下c++源码

  3. 蜜獾哥

    我觉得uia获取部分是不是应该作一个系统判断,IUIAutomationTextPattern2只有在win8+才有效

    • Tebayaki

      不支持接口的话就直接返回了,没啥必要多做一个判断

  4. 蜜獾哥
    蜜獾哥给您捐赠了¥5
  5. AHK中文社区
    11010010给您捐赠了¥2
  6. 蜜獾哥

    iUIAutomation := ComObjCreate(“{E22AD333-B25F-460C-83D0-0581107395C9}”, “{30CBE57D-D9D0-452A-AB13-7AC5AC4825EE}”) win7上可能会弹窗报错没有注册类

  7. ahker
    ahker给您捐赠了¥4
  8. Ash
    Ash给您捐赠了¥5
个人中心
购物车
优惠劵
有新私信 私信列表
搜索