[更新非 Hook 版]中英状态提示 AHK 缝合版

最近论坛上有人发个获取输入光标的新方法,想起了之前论坛里火热的输入法状态提示工具,于是上网找了些 AHK 代码进行缝合:grin:

功能

测试环境:AHKV2.0-beta.6+,微软五笔shift 中英切换,其它没测试,有需要自己改代码就行。新方法基本支持所有窗口。

  • 在输入光标附近显示当前中英状态(Win 10 1607及以上支持多屏不同缩放)

源码

新版

GetCaret(&X?, &Y?, &W?, &H?) {
    ; UIA2 caret
    static IUIA := ComObject("{e22ad333-b25f-460c-83d0-0581107395c9}", "{34723aff-0c9d-49d0-9896-7ab52df8cd8a}")
    try {
        ComCall(8, IUIA, "ptr*", &FocusedEl:=0) ; GetFocusedElement
        ComCall(16, FocusedEl, "int", 10024, "ptr*", &patternObject:=0), ObjRelease(FocusedEl) ; GetCurrentPattern. TextPatternElement2 = 10024
        if patternObject {
            ComCall(10, patternObject, "int*", &IsActive:=1, "ptr*", &caretRange:=0), ObjRelease(patternObject) ; GetCaretRange
            ComCall(10, caretRange, "ptr*", &boundingRects:=0), ObjRelease(caretRange) ; GetBoundingRectangles
            if (Rect := ComValue(0x2005, boundingRects)).MaxIndex() = 3 { ; VT_ARRAY | VT_R8
                X:=Round(Rect[0]), Y:=Round(Rect[1]), W:=Round(Rect[2]), H:=Round(Rect[3])
                return
            }
        }
    }

    ; Acc caret
    static _ := DllCall("LoadLibrary", "Str","oleacc", "Ptr")
    try {
        idObject := 0xFFFFFFF8 ; OBJID_CARET
        if DllCall("oleacc\AccessibleObjectFromWindow", "ptr", WinExist("A"), "uint",idObject &= 0xFFFFFFFF
            , "ptr",-16 + NumPut("int64", idObject == 0xFFFFFFF0 ? 0x46000000000000C0 : 0x719B3800AA000C81, NumPut("int64", idObject == 0xFFFFFFF0 ? 0x0000000000020400 : 0x11CF3C3D618736E0, IID := Buffer(16)))
            , "ptr*", oAcc := ComValue(9,0)) = 0 {
            x:=Buffer(4), y:=Buffer(4), w:=Buffer(4), h:=Buffer(4)
            oAcc.accLocation(ComValue(0x4003, x.ptr, 1), ComValue(0x4003, y.ptr, 1), ComValue(0x4003, w.ptr, 1), ComValue(0x4003, h.ptr, 1), 0)
            X:=NumGet(x,0,"int"), Y:=NumGet(y,0,"int"), W:=NumGet(w,0,"int"), H:=NumGet(h,0,"int")
            if (X | Y) != 0
                return
        }
    }

    ; Default caret
    savedCaret := A_CoordModeCaret, W := 4, H := 20
    ;DPIfresh()
    CoordMode "Caret", "Screen"
    CaretGetPos(&X, &Y)
    CoordMode "Caret", savedCaret
}    

#NoTrayIcon    
SetWinDelay 0    
DetectHiddenWindows True
Persistent
loop {
    main()
}

main() {
    static lastHasCaret := 0, lastX := 0, lastY := 0, caretX := 0, caretY := 0, caretW := 0, caretH := 0
    DPIfresh()
    GetCaret(&caretX, &caretY, &caretW, &caretH )
    hasCaret := caretX and caretY
    if lastHasCaret and !hasCaret {
        ToolTip()
    }
    lastHasCaret := hasCaret
    if hasCaret {
        DPIfresh()
        oldCoordMode := A_CoordModeToolTip
        DPIfresh()
        A_CoordModeToolTip := "Screen"
        topleftY := SysGet(79)
        if ( caretY < topleftY ) {
            caretY := Mod( caretY, Abs(topleftY))
        }
        
        Capstatus := GetKeyState("CapsLock", "T") ? "A" : ""
        ToolTip GetIMEStatus() Capstatus, caretX - caretW, caretY + caretH      
        lastX := caretX , lastY := caretY
        A_CoordModeToolTip := oldCoordMode
    }
    sleep 10
    return 0                          
}

; DPI 感知
DPIfresh() {
    Version := RegExReplace(A_OSVersion,"\.")
    if Version >= 10014393 and Version < 10015063
	  DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr")
    if Version >= 10015063
        DllCall("SetThreadDpiAwarenessContext", "ptr", -4, "ptr")
}


; 中英状态获取
DetectHiddenWindows True
GetIMEStatus() {
    fgWin	:= WinExist("A")

    if WinActive("ahk_class vguiPopupWindow") or WinActive("ahk_class ApplicationFrameWindow")  or WinActive("ahk_class CabinetWClass")  or WinActive("ahk_class CASCADIA_HOSTING_WINDOW_CLASS")  or WinActive("ahk_class Windows.UI.Core.CoreWindow") { 
        Focused	:= ControlGetFocus("A")
        if Focused {
            CtrlID	:= ControlGetHwnd(Focused, "A")
            fgWin 	:= CtrlID
        }

    }
    
    return SendMessage(0x283, 0x001, 0,, "ahk_id " DllCall("imm32\ImmGetDefaultIMEWnd", "Uint", fgWin, "Uint")) ? "中 " : ""
}  



Hook 版

;#NoTrayIcon

; DPI 感知
Version := RegExReplace(A_OSVersion,"\.")
if Version >= 10014393 and Version < 10015063
	DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr")
if Version >= 10015063
	DllCall("SetThreadDpiAwarenessContext", "ptr", -4, "ptr")


; 位置获取
    RegisterCaretMessageHook(flag) {
        static pShellCode := 0
        hProcess := hMsvcrt := 0
        if !pid := ProcessExist("explorer.exe")
            throw Error('"explorer.exe" not found')
        if !hProcess := DllCall("OpenProcess", "uint", 0x1fffff, "int", false, "uint", pid, "ptr")
            throw OSError()
        try {
            if !hMsvcrt := DllCall("LoadLibraryW", "str", "msvcrt.dll", "ptr")
                throw OSError()
            pLocalMemcpy := DllCall("GetProcAddress", "ptr", hMsvcrt, "astr", "memcpy", "ptr")
            if !msvcrtEntry := ToolHelpFindModuleByName("msvcrt.dll", pid)
                throw Error('"msvcrt.dll" not found')
            pMemcpy := msvcrtEntry.modBaseAddr + pLocalMemcpy - hMsvcrt
            msg := DllCall("RegisterWindowMessage", "str", "WM_CARETPOSCHANGED")
            if !flag || pShellCode {
                if !flag {
                    OnExit(unregister, 0)
                    DllCall("ChangeWindowMessageFilterEx", "ptr", A_ScriptHwnd, "uint", msg, "uint", 0, "ptr", 0)
                }
                if !DllCall("WriteProcessMemory", "ptr", hProcess, "ptr", pMemcpy, "ptr", pLocalMemcpy, "uptr", 16, "uptr*", 0)
                    throw OSError()
                if pShellCode {
                    DllCall("VirtualFreeEx", "ptr", hProcess, "ptr", pShellCode, "uptr", 0, "uint", 0x8000)
                    pShellCode := 0
                }
                return
            }
            memcpyBytes := DllCall("GetProcAddress", "ptr", hMsvcrt, "astr", "memset", "ptr") - pLocalMemcpy
            if !user32Entry := ToolHelpFindModuleByName("user32.dll", pid)
                throw Error('"User32.dll" not found')
            hUser32 := DllCall("GetModuleHandle", "str", "user32", "ptr")
            pPostMessageW := user32Entry.modBaseAddr + DllCall("GetProcAddress", "ptr", hUser32, "astr", "PostMessageW", "ptr") - hUser32
            if !coreUiComponentsEntry := ToolHelpFindModuleByName("CoreUIComponents.dll", pid)
                throw Error('"CoreUIComponents.dll" not found')
            CryptHexStringToBinary("00000000000000000000000000000000000000000000000090909090909090904983F8100F85970000004C8B54240849BB00000000000000004D29DA4981FA000000000F8378000000837914090F840A000000837914070F856400000083791C000F855A0000004C8B124C8B5A084C3B158BFFFFFF0F850D0000004C3B1D86FFFFFF0F84390000004C891571FFFFFF4C891D72FFFFFF415052514883EC404D8BCB4D8BC2BA0000000048B90000000000000000FF1557FFFFFF4883C440595A4158", &shellCodeBuf)
            NumPut("ptr", pPostMessageW, shellCodeBuf, 0x10)
            NumPut("ptr", coreUiComponentsEntry.modBaseAddr, shellCodeBuf, 0x31)
            NumPut("uint", coreUiComponentsEntry.modBaseSize, shellCodeBuf, 0x3F)
            NumPut("uint", msg, shellCodeBuf, 0xA5)
            NumPut("ptr", A_ScriptHwnd, shellCodeBuf, 0xAB)
            if !pShellCode := DllCall("VirtualAllocEx", "ptr", hProcess, "ptr", 0, "uptr", shellCodeBuf.Size + memcpyBytes, "uint", 0x1000, "uint", 0x40, "ptr")
                throw OSError()
            CryptHexStringToBinary("48B80000000000000000FFD0C3", &hookBuf)
            NumPut("ptr", pShellCode + 32, hookBuf, 2)
            if !DllCall("WriteProcessMemory", "ptr", hProcess, "ptr", pShellCode, "ptr", shellCodeBuf, "uptr", shellCodeBuf.Size, "uptr*", 0)
                throw OSError()
            if !DllCall("WriteProcessMemory", "ptr", hProcess, "ptr", pShellCode + shellCodeBuf.Size, "ptr", pLocalMemcpy, "uptr", memcpyBytes, "uptr*", 0)
                throw OSError()
            if !DllCall("WriteProcessMemory", "ptr", hProcess, "ptr", pMemcpy, "ptr", hookBuf, "uptr", hookBuf.Size, "uptr*", 0)
                throw OSError()
            DllCall("ChangeWindowMessageFilterEx", "ptr", A_ScriptHwnd, "uint", msg, "uint", 1, "ptr", 0)
            OnExit(unregister)
        }
        catch as e {
            if pShellCode {
                DllCall("VirtualFreeEx", "ptr", hProcess, "ptr", pShellCode, "uptr", 0, "uint", 0x8000)
                pShellCode := 0
            }
            throw e
        }
        finally {
            if hMsvcrt
                DllCall("FreeLibrary", "ptr", hMsvcrt)
            DllCall("CloseHandle", "ptr", hProcess)
        }
        return msg
        static unregister(*) => RegisterCaretMessageHook(false)
    }
    /*
    alloc(data, 2048)
    label(begin)
    label(ready)
    label(memcpy)
    label(L1)
    data:
    dq 0,0,0
    dq 9090909090909090
    begin:
    cmp r8,10
    jne memcpy
    mov r10,[rsp+08]
    mov r11,0000000000000000 // uicorecomponents address
    sub r10,r11
    cmp r10,00000000 // uicorecomponents size
    jae memcpy
    cmp dword ptr[rcx+14],9
    je L1
    cmp dword ptr[rcx+14],7
    jne memcpy
    L1:
    cmp dword ptr[rcx+1C],0
    jne memcpy
    mov r10,[rdx]
    mov r11,[rdx+08]
    cmp r10,[data]
    jne ready
    cmp r11,[data+08]
    je memcpy
    ready:
    mov [data],r10
    mov [data+08],r11
    push r8
    push rdx
    push rcx
    sub rsp,40
    mov r9,r11
    mov r8,r10
    mov edx,0 // msg
    mov rcx,0 // hwnd
    call [data+10]
    add rsp,40
    pop rcx
    pop rdx
    pop r8
    memcpy:
    */
    CryptHexStringToBinary(hexString, &binary){
        DllCall("crypt32\CryptStringToBinaryW", "str", hexString, "uint", len := StrLen(hexString), "uint", 4, "ptr", 0, "uint*", &bytes := 0, "ptr", 0, "ptr", 0)
        return DllCall("crypt32\CryptStringToBinaryW", "str", hexString, "uint", len, "uint", 4, "ptr", binary := binary ?? Buffer(bytes), "uint*", bytes, "ptr", 0, "ptr", 0)
    }
    ToolHelpFindModuleByName(moduleName, pid := 0) {
        if !snapshot := DllCall("CreateToolhelp32Snapshot", "uint", 0x18, "uint", pid, "ptr")
            return
        entry := tagMODULEENTRY32W()
        entry.dwSize := entry.Size
        next := DllCall("GetProcAddress", "Ptr", DllCall("GetModuleHandle", "str", "kernel32", "ptr"), "astr", "Module32NextW", "ptr")
        res := DllCall("Module32FirstW", "ptr", snapshot, "ptr", entry)
        while res {
            if entry.szModule = moduleName {
                res := entry
                break
            }
            res := DllCall(next, "ptr", snapshot, "ptr", entry)
        }
        DllCall("CloseHandle", "ptr", snapshot)
        return res
    }
    class tagMODULEENTRY32W {
        Size := 1080
        __New() => this.Ptr := (this._ := Buffer(this.Size)).Ptr
        dwSize {
            get => NumGet(this, "uint")
            set => NumPut("uint", Value, this)
        }
        th32ModuleID => NumGet(this, 4, "uint")
        th32ProcessID => NumGet(this, 8, "uint")
        GlblcntUsage => NumGet(this, 12, "uint")
        ProccntUsage => NumGet(this, 16, "uint")
        modBaseAddr => NumGet(this, 24, "ptr")
        modBaseSize => NumGet(this, 32, "uint")
        hModule => NumGet(this, 40, "ptr")
        szModule =>  StrGet(this.Ptr + 48)
        szExePath => StrGet(this.Ptr + 560)
    }

; 中英状态提示    
DetectHiddenWindows True
Persistent
    OnMessage(RegisterCaretMessageHook(true), CaretMsgHandler2)
    CaretMsgHandler2(leftTop, rightBottom, msg, hwnd){
            static showToolTip := 0
            if rightBottom == leftTop + 1 {
                DllCall("SystemParametersInfo", "uint", 48, "uint", 0, "ptr", rect := Buffer(16), "uint", 0)
                if NumGet(rect, 8, "int64") == rightBottom + 0x100000000
                return 0
            }
            oldCoordMode := A_CoordModeToolTip
            A_CoordModeToolTip := "Screen"
            x := rightBottom & 0xffffffff
            y := rightBottom >> 32
            if x > 0x7fffffff
                x -= 0x100000000
            if y > 0x7fffffff
                y -= 0x100000000
            try
                Capstatus := GetKeyState("CapsLock", "T") ? " A" : ""
                ToolTip GetIMEStatus() Capstatus, x, y
                SetTimer(StopShowToolTip, -500)
            A_CoordModeToolTip := oldCoordMode
            static StopShowToolTip() => (ToolTip(), showToolTip := 0)
            return 0
            }
; 中英状态获取
DetectHiddenWindows True
GetIMEStatus() {
  fgWin	:= WinExist("A")
  if WinActive("ahk_class vguiPopupWindow") or WinActive("ahk_class ApplicationFrameWindow")  or WinActive("ahk_class CabinetWClass")  or WinActive("ahk_class CASCADIA_HOSTING_WINDOW_CLASS")  or WinActive("ahk_class Windows.UI.Core.CoreWindow") { 
      Focused	:= ControlGetFocus("A")
      if Focused 
      {
          CtrlID	:= ControlGetHwnd(Focused, "A")
          fgWin 	:= CtrlID
      }

    }
  return SendMessage(0x283, 0x001, 0,, "ahk_id " DllCall("imm32\ImmGetDefaultIMEWnd", "Uint", fgWin, "Uint")) ? "中" : "英"
  }           


参考

以下是缝合时参考的文章

https://www.autohotkey.com/boards/viewtopic.php?t=84140

2 个赞

加个管理员权限?

试了下,不是这个的问题

没看懂,这个脚本是干啥的?

类似 imtip

1 个赞

我都是看右下角指示栏欸)

1 个赞

但想在输入的位置直接提示( :rofl:

不错哇,好用!

其他中英切换也没问题,我是ctrl space 切换中英的。

加了几个 ahk_class 识别窗口,又好了,代码已更新 :sweat_smile:

下载了打开提示:

Error: Call to nonexistent function.

▶	003: DllCall("SetThreadDpiAwarenessContext", "ptr", -4, "ptr")

The current thread will exit.

Call stack:
*#1 (3) : [DllCall] DllCall("SetThreadDpiAwarenessContext", "ptr", -4, "ptr")
*#1 (3) : [] DllCall("SetThreadDpiAwarenessContext", "ptr", -4, "ptr")
> Auto-execute

这个可能是系统不支持,要改下代码:joy:,我的是win 11 22h2

更新了,你试下现在正常不?

还是一样,我系统是win7,不知道是不是这个原因,代码放到ahk编辑器里执行,提示第16行,

The leftmost character above is illegal in an expression.
     Specifically: '"explorer.exe"

安装包的提示还是跟之前一样?还有你的ahk版本是多少?

提示一样,版本是1.1啊

我的代码是 v2.0-beta.6+ :joy:,你换个版本试下,不行就把 DPI 感知那段删掉

用非 hook 的方法重置了一版,感觉更加稳定了,hook 版在我这开机自启经常报错:rofl:

能不能去掉那堆数字坐标,挡住了我其他的窗口

你用的哪一版?印象中好像没有数字坐标:joy:

找到了在哪里了,我晚点改下:rofl: