I use ahkspy/accviewer a lot,and i know how to get all the information except Acc information for use in other scripts but it's really tedious writing the bits for ACC, having to manually figure out the 'Path' for the control ACC is displaying text.
Acc: get text from all window/control elements Is the closet thing i've found to an ACC script that gives you the full path to a control by searching through it's output,but i was looking for something more along the lines of AccViewer that outputs code to retrieve currently active control...
Thanks for any help...
btw,despite this being my first post,i'm quite familiar with ahk,so i'm cool with any pointers as to how i could modify accviewer my self,at the very least,i just don't want to start from zero... I've literally always found what i was looking for so never needed to post on the forum,until now i guess.
How to get the full ACC path for control on cursor? Topic is solved
How to get the full ACC path for control on cursor?
live ? long & prosper : regards
Re: How to get the full ACC path for control on cursor? Topic is solved
The function GetAccPath and GetEnumIndex were extracted from AccViewer.
Code: Select all
#NoEnv
#SingleInstance force
SetBatchLines, -1
F12::
obj := GetInfoUnderCursor()
msgbox % obj.path
return
GetInfoUnderCursor() {
Acc := Acc_ObjectFromPoint(child)
if !value := Acc.accValue(child)
value := Acc.accName(child)
accPath := GetAccPath(acc, hwnd).path
return {text: value, path: accPath, hwnd: hwnd}
}
GetAccPath(Acc, byref hwnd="") {
hwnd := Acc_WindowFromObject(Acc)
WinObj := Acc_ObjectFromWindow(hwnd)
WinObjPos := Acc_Location(WinObj).pos
while Acc_WindowFromObject(Parent:=Acc_Parent(Acc)) = hwnd {
t2 := GetEnumIndex(Acc) "." t2
if Acc_Location(Parent).pos = WinObjPos
return {AccObj:Parent, Path:SubStr(t2,1,-1)}
Acc := Parent
}
while Acc_WindowFromObject(Parent:=Acc_Parent(WinObj)) = hwnd
t1.="P.", WinObj:=Parent
return {AccObj:Acc, Path:t1 SubStr(t2,1,-1)}
}
GetEnumIndex(Acc, ChildId=0) {
if Not ChildId {
ChildPos := Acc_Location(Acc).pos
For Each, child in Acc_Children(Acc_Parent(Acc))
if IsObject(child) and Acc_Location(child).pos=ChildPos
return A_Index
}
else {
ChildPos := Acc_Location(Acc,ChildId).pos
For Each, child in Acc_Children(Acc)
if Not IsObject(child) and Acc_Location(Acc,child).pos=ChildPos
return A_Index
}
}
; Acc.ahk
;------------------------------------------------------------------------------
; Acc.ahk Standard Library
; by Sean
; Updated by jethrow:
; Modified ComObjEnwrap params from (9,pacc) --> (9,pacc,1)
; Changed ComObjUnwrap to ComObjValue in order to avoid AddRef (thanks fincs)
; Added Acc_GetRoleText & Acc_GetStateText
; Added additional functions - commented below
; Removed original Acc_Children function
; last updated 2/19/2012
;------------------------------------------------------------------------------
Acc_Init()
{
Static h
If Not h
h:=DllCall("LoadLibrary","Str","oleacc","Ptr")
}
Acc_ObjectFromEvent(ByRef _idChild_, hWnd, idObject, idChild)
{
Acc_Init()
If DllCall("oleacc\AccessibleObjectFromEvent", "Ptr", hWnd, "UInt", idObject, "UInt", idChild, "Ptr*", pacc, "Ptr", VarSetCapacity(varChild,8+2*A_PtrSize,0)*0+&varChild)=0
Return ComObjEnwrap(9,pacc,1), _idChild_:=NumGet(varChild,8,"UInt")
}
Acc_ObjectFromPoint(ByRef _idChild_ = "", x = "", y = "")
{
Acc_Init()
If DllCall("oleacc\AccessibleObjectFromPoint", "Int64", x==""||y==""?0*DllCall("GetCursorPos","Int64*",pt)+pt:x&0xFFFFFFFF|y<<32, "Ptr*", pacc, "Ptr", VarSetCapacity(varChild,8+2*A_PtrSize,0)*0+&varChild)=0
Return ComObjEnwrap(9,pacc,1), _idChild_:=NumGet(varChild,8,"UInt")
}
Acc_ObjectFromWindow(hWnd, idObject = -4)
{
Acc_Init()
If DllCall("oleacc\AccessibleObjectFromWindow", "Ptr", hWnd, "UInt", idObject&=0xFFFFFFFF, "Ptr", -VarSetCapacity(IID,16)+NumPut(idObject==0xFFFFFFF0?0x46000000000000C0:0x719B3800AA000C81,NumPut(idObject==0xFFFFFFF0?0x0000000000020400:0x11CF3C3D618736E0,IID,"Int64"),"Int64"), "Ptr*", pacc)=0
Return ComObjEnwrap(9,pacc,1)
}
Acc_WindowFromObject(pacc)
{
If DllCall("oleacc\WindowFromAccessibleObject", "Ptr", IsObject(pacc)?ComObjValue(pacc):pacc, "Ptr*", hWnd)=0
Return hWnd
}
Acc_GetRoleText(nRole)
{
nSize := DllCall("oleacc\GetRoleText", "Uint", nRole, "Ptr", 0, "Uint", 0)
VarSetCapacity(sRole, (A_IsUnicode?2:1)*nSize)
DllCall("oleacc\GetRoleText", "Uint", nRole, "str", sRole, "Uint", nSize+1)
Return sRole
}
Acc_GetStateText(nState)
{
nSize := DllCall("oleacc\GetStateText", "Uint", nState, "Ptr", 0, "Uint", 0)
VarSetCapacity(sState, (A_IsUnicode?2:1)*nSize)
DllCall("oleacc\GetStateText", "Uint", nState, "str", sState, "Uint", nSize+1)
Return sState
}
; Written by jethrow
Acc_Role(Acc, ChildId=0) {
try return ComObjType(Acc,"Name")="IAccessible"?Acc_GetRoleText(Acc.accRole(ChildId)):"invalid object"
}
Acc_State(Acc, ChildId=0) {
try return ComObjType(Acc,"Name")="IAccessible"?Acc_GetStateText(Acc.accState(ChildId)):"invalid object"
}
Acc_Children(Acc) {
if ComObjType(Acc,"Name")!="IAccessible"
error_message := "Cause:`tInvalid IAccessible Object`n`n"
else {
Acc_Init()
cChildren:=Acc.accChildCount, Children:=[]
if DllCall("oleacc\AccessibleChildren", "Ptr", ComObjValue(Acc), "Int", 0, "Int", cChildren, "Ptr", VarSetCapacity(varChildren,cChildren*(8+2*A_PtrSize),0)*0+&varChildren, "Int*", cChildren)=0 {
Loop %cChildren%
i:=(A_Index-1)*(A_PtrSize*2+8)+8, child:=NumGet(varChildren,i), Children.Insert(NumGet(varChildren,i-8)=3?child:Acc_Query(child)), ObjRelease(child)
return Children
}
}
error:=Exception("",-1)
MsgBox, 262148, Acc_Children Failed, % (error_message?error_message:"") "File:`t" (error.file==A_ScriptFullPath?A_ScriptName:error.file) "`nLine:`t" error.line "`n`nContinue Script?"
IfMsgBox, No
ExitApp
}
Acc_Location(Acc, ChildId=0) { ; adapted from Sean's code
try Acc.accLocation(ComObj(0x4003,&x:=0), ComObj(0x4003,&y:=0), ComObj(0x4003,&w:=0), ComObj(0x4003,&h:=0), ChildId)
catch
return
return {x:NumGet(x,0,"int"), y:NumGet(y,0,"int"), w:NumGet(w,0,"int"), h:NumGet(h,0,"int")
, pos:"x" NumGet(x,0,"int")" y" NumGet(y,0,"int") " w" NumGet(w,0,"int") " h" NumGet(h,0,"int")}
}
Acc_Parent(Acc) {
try parent:=Acc.accParent
return parent?Acc_Query(parent):
}
Acc_Child(Acc, ChildId=0) {
try child:=Acc.accChild(ChildId)
return child?Acc_Query(child):
}
Acc_Query(Acc) { ; thanks Lexikos - www.autohotkey.com/forum/viewtopic.php?t=81731&p=509530#509530
try return ComObj(9, ComObjQuery(Acc,"{618736e0-3c3d-11cf-810c-00aa00389b71}"), 1)
}
Re: How to get the full ACC path for control on cursor?
Ive been looking for this in vain and this is a really nice reponse. Thanks for asking for help and thanks for the complete answer tmplinshi
BTW what I am unable to figure out is how to use this info to Control Focus the given AccPath? This is needed in the example of a custom class of listview that don't report different button names. The only way to identify them is either this method, or image search. I've sticked with image search for the last 3 months but if this retrieved path could then be focused OR get the position of the control, it would be more stable than imagesearch I think.
BTW what I am unable to figure out is how to use this info to Control Focus the given AccPath? This is needed in the example of a custom class of listview that don't report different button names. The only way to identify them is either this method, or image search. I've sticked with image search for the last 3 months but if this retrieved path could then be focused OR get the position of the control, it would be more stable than imagesearch I think.
Re: How to get the full ACC path for control on cursor?
tmplinshi wrote:The function GetAccPath and GetEnumIndex were extracted from AccViewer.
Code: Select all
#NoEnv #SingleInstance force SetBatchLines, -1 F12:: obj := GetInfoUnderCursor() msgbox % obj.path return GetInfoUnderCursor() { Acc := Acc_ObjectFromPoint(child) if !value := Acc.accValue(child) value := Acc.accName(child) accPath := GetAccPath(acc, hwnd).path return {text: value, path: accPath, hwnd: hwnd} } GetAccPath(Acc, byref hwnd="") { hwnd := Acc_WindowFromObject(Acc) WinObj := Acc_ObjectFromWindow(hwnd) WinObjPos := Acc_Location(WinObj).pos while Acc_WindowFromObject(Parent:=Acc_Parent(Acc)) = hwnd { t2 := GetEnumIndex(Acc) "." t2 if Acc_Location(Parent).pos = WinObjPos return {AccObj:Parent, Path:SubStr(t2,1,-1)} Acc := Parent } while Acc_WindowFromObject(Parent:=Acc_Parent(WinObj)) = hwnd t1.="P.", WinObj:=Parent return {AccObj:Acc, Path:t1 SubStr(t2,1,-1)} } GetEnumIndex(Acc, ChildId=0) { if Not ChildId { ChildPos := Acc_Location(Acc).pos For Each, child in Acc_Children(Acc_Parent(Acc)) if IsObject(child) and Acc_Location(child).pos=ChildPos return A_Index } else { ChildPos := Acc_Location(Acc,ChildId).pos For Each, child in Acc_Children(Acc) if Not IsObject(child) and Acc_Location(Acc,child).pos=ChildPos return A_Index } } ; Acc.ahk ;------------------------------------------------------------------------------ ; Acc.ahk Standard Library ; by Sean ; Updated by jethrow: ; Modified ComObjEnwrap params from (9,pacc) --> (9,pacc,1) ; Changed ComObjUnwrap to ComObjValue in order to avoid AddRef (thanks fincs) ; Added Acc_GetRoleText & Acc_GetStateText ; Added additional functions - commented below ; Removed original Acc_Children function ; last updated 2/19/2012 ;------------------------------------------------------------------------------ Acc_Init() { Static h If Not h h:=DllCall("LoadLibrary","Str","oleacc","Ptr") } Acc_ObjectFromEvent(ByRef _idChild_, hWnd, idObject, idChild) { Acc_Init() If DllCall("oleacc\AccessibleObjectFromEvent", "Ptr", hWnd, "UInt", idObject, "UInt", idChild, "Ptr*", pacc, "Ptr", VarSetCapacity(varChild,8+2*A_PtrSize,0)*0+&varChild)=0 Return ComObjEnwrap(9,pacc,1), _idChild_:=NumGet(varChild,8,"UInt") } Acc_ObjectFromPoint(ByRef _idChild_ = "", x = "", y = "") { Acc_Init() If DllCall("oleacc\AccessibleObjectFromPoint", "Int64", x==""||y==""?0*DllCall("GetCursorPos","Int64*",pt)+pt:x&0xFFFFFFFF|y<<32, "Ptr*", pacc, "Ptr", VarSetCapacity(varChild,8+2*A_PtrSize,0)*0+&varChild)=0 Return ComObjEnwrap(9,pacc,1), _idChild_:=NumGet(varChild,8,"UInt") } Acc_ObjectFromWindow(hWnd, idObject = -4) { Acc_Init() If DllCall("oleacc\AccessibleObjectFromWindow", "Ptr", hWnd, "UInt", idObject&=0xFFFFFFFF, "Ptr", -VarSetCapacity(IID,16)+NumPut(idObject==0xFFFFFFF0?0x46000000000000C0:0x719B3800AA000C81,NumPut(idObject==0xFFFFFFF0?0x0000000000020400:0x11CF3C3D618736E0,IID,"Int64"),"Int64"), "Ptr*", pacc)=0 Return ComObjEnwrap(9,pacc,1) } Acc_WindowFromObject(pacc) { If DllCall("oleacc\WindowFromAccessibleObject", "Ptr", IsObject(pacc)?ComObjValue(pacc):pacc, "Ptr*", hWnd)=0 Return hWnd } Acc_GetRoleText(nRole) { nSize := DllCall("oleacc\GetRoleText", "Uint", nRole, "Ptr", 0, "Uint", 0) VarSetCapacity(sRole, (A_IsUnicode?2:1)*nSize) DllCall("oleacc\GetRoleText", "Uint", nRole, "str", sRole, "Uint", nSize+1) Return sRole } Acc_GetStateText(nState) { nSize := DllCall("oleacc\GetStateText", "Uint", nState, "Ptr", 0, "Uint", 0) VarSetCapacity(sState, (A_IsUnicode?2:1)*nSize) DllCall("oleacc\GetStateText", "Uint", nState, "str", sState, "Uint", nSize+1) Return sState } ; Written by jethrow Acc_Role(Acc, ChildId=0) { try return ComObjType(Acc,"Name")="IAccessible"?Acc_GetRoleText(Acc.accRole(ChildId)):"invalid object" } Acc_State(Acc, ChildId=0) { try return ComObjType(Acc,"Name")="IAccessible"?Acc_GetStateText(Acc.accState(ChildId)):"invalid object" } Acc_Children(Acc) { if ComObjType(Acc,"Name")!="IAccessible" error_message := "Cause:`tInvalid IAccessible Object`n`n" else { Acc_Init() cChildren:=Acc.accChildCount, Children:=[] if DllCall("oleacc\AccessibleChildren", "Ptr", ComObjValue(Acc), "Int", 0, "Int", cChildren, "Ptr", VarSetCapacity(varChildren,cChildren*(8+2*A_PtrSize),0)*0+&varChildren, "Int*", cChildren)=0 { Loop %cChildren% i:=(A_Index-1)*(A_PtrSize*2+8)+8, child:=NumGet(varChildren,i), Children.Insert(NumGet(varChildren,i-8)=3?child:Acc_Query(child)), ObjRelease(child) return Children } } error:=Exception("",-1) MsgBox, 262148, Acc_Children Failed, % (error_message?error_message:"") "File:`t" (error.file==A_ScriptFullPath?A_ScriptName:error.file) "`nLine:`t" error.line "`n`nContinue Script?" IfMsgBox, No ExitApp } Acc_Location(Acc, ChildId=0) { ; adapted from Sean's code try Acc.accLocation(ComObj(0x4003,&x:=0), ComObj(0x4003,&y:=0), ComObj(0x4003,&w:=0), ComObj(0x4003,&h:=0), ChildId) catch return return {x:NumGet(x,0,"int"), y:NumGet(y,0,"int"), w:NumGet(w,0,"int"), h:NumGet(h,0,"int") , pos:"x" NumGet(x,0,"int")" y" NumGet(y,0,"int") " w" NumGet(w,0,"int") " h" NumGet(h,0,"int")} } Acc_Parent(Acc) { try parent:=Acc.accParent return parent?Acc_Query(parent): } Acc_Child(Acc, ChildId=0) { try child:=Acc.accChild(ChildId) return child?Acc_Query(child): } Acc_Query(Acc) { ; thanks Lexikos - www.autohotkey.com/forum/viewtopic.php?t=81731&p=509530#509530 try return ComObj(9, ComObjQuery(Acc,"{618736e0-3c3d-11cf-810c-00aa00389b71}"), 1) }
Thanks a ton dude.
live ? long & prosper : regards
Re: How to get the full ACC path for control on cursor?
duplicatePost
Last edited by CyL0N on 01 Oct 2018, 06:46, edited 1 time in total.
live ? long & prosper : regards
Re: How to get the full ACC path for control on cursor?
- (An acc path is a list of numbers, e.g. '1.2.3'. If you specify an hWnd and '1.2.3', then you start at the GUI element corresponding to the hWnd, you get its 1st child, then that element's 2nd child, then that element's 3rd child, you have then specified a GUI element relative to an hWnd.)
- (So, for an acc path, you start at an hWnd and check various descendants, top-down, as in this script.)
Acc: get text from all window/control elements - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=40615
- Re. starting at a GUI element and discovering its ancestors, until you reach an hWnd, bottom-up. I've investigated the situation, and AFAICS the answer isn't easy, and this may explain why AccViewer isn't perfect in stating the path of an element.
- The accParent property makes it easy to get an element's parent, and you can use this to get information about each ancestor element, producing an Acc path from right-to-left. However, it is not necessarily clear how to determine at which ancestor element you should stop. I.e. which should be considered the first (leftmost) element. Possible criteria: check the hWnd associated with the GUI element, check the role number of the GUI element.
- There is another problem, you start with an element, you get its parent, you then need to know that the child element is the nth child of the parent. AccViewer uses a function called GetEnumIndex to establish this, and it uses the positions of GUI elements to try and establish a child element's number. However, testing demonstrated that this function, and therefore my code below, can sometimes return the wrong number for an element. (But note: if one number is wrong, the other numbers can still be correct, the parent element is always correctly identified, but the child number relative to the parent can be wrong.)
- One other thing to mention is that the Acc library's Acc_Location function returns an object with x/y/w/h keys. However, the Acc_Location function used by AccViewer has been modified to also return a pos key. I wrote my code to be more compatible, it assumes that the pos key is not available. Cheers.
- (So, for an acc path, you start at an hWnd and check various descendants, top-down, as in this script.)
Acc: get text from all window/control elements - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=40615
- Re. starting at a GUI element and discovering its ancestors, until you reach an hWnd, bottom-up. I've investigated the situation, and AFAICS the answer isn't easy, and this may explain why AccViewer isn't perfect in stating the path of an element.
- The accParent property makes it easy to get an element's parent, and you can use this to get information about each ancestor element, producing an Acc path from right-to-left. However, it is not necessarily clear how to determine at which ancestor element you should stop. I.e. which should be considered the first (leftmost) element. Possible criteria: check the hWnd associated with the GUI element, check the role number of the GUI element.
- There is another problem, you start with an element, you get its parent, you then need to know that the child element is the nth child of the parent. AccViewer uses a function called GetEnumIndex to establish this, and it uses the positions of GUI elements to try and establish a child element's number. However, testing demonstrated that this function, and therefore my code below, can sometimes return the wrong number for an element. (But note: if one number is wrong, the other numbers can still be correct, the parent element is always correctly identified, but the child number relative to the parent can be wrong.)
Code: Select all
q:: ;acc get path of element under the cursor (note: may be unreliable, see function comments)
WinGet, hWnd, ID, A
;hWnd := -1 ;get all possible ancestors
oAcc := Acc_ObjectFromPoint(vChildID)
vAccPath := JEE_AccGetPath(oAcc, hWnd)
if vChildID
MsgBox, % Clipboard := vAccPath " c" vChildID
else
MsgBox, % Clipboard := vAccPath
;use acc path
oAcc := Acc_Get("Object", vAccPath, vChildID, "ahk_id " hWnd)
vName := vValue := ""
try vName := oAcc.accName(vChildID)
try vValue := oAcc.accValue(vChildID)
MsgBox, % vName "`r`n" vValue
return
;note: path might be too short e.g. menu items
;note: path might be too long (I haven't seen this happen)
;note: path might be wrong (e.g. more than one item with the same position, JEE_AccGetEnumIndex is not guaranteed to give correct results)
JEE_AccGetPath(oAcc, hWnd:="")
{
local
if (hWnd = "")
hWnd := Acc_WindowFromObject(oAcc)
, hWnd := DllCall("user32\GetParent", Ptr,hWnd, Ptr)
vAccPath := ""
vIsMatch := 0
if (hWnd = -1) ;get all possible ancestors
Loop
{
vIndex := JEE_AccGetEnumIndex(oAcc)
if !vIndex
break
vAccPath := vIndex (A_Index=1?"":".") vAccPath
oAcc := oAcc.accParent
}
else
Loop
{
vIndex := JEE_AccGetEnumIndex(oAcc)
hWnd2 := Acc_WindowFromObject(oAcc)
if !vIsMatch && (hWnd = hWnd2)
vIsMatch := 1
if vIsMatch && !(hWnd = hWnd2)
break
vAccPath := vIndex (A_Index=1?"":".") vAccPath
if vIsMatch
break
oAcc := oAcc.accParent
}
return vAccPath
}
;note: AccViewer's GetEnumIndex function uses a mod of Acc_Location that returns a 'pos' key
JEE_AccGetEnumIndex(oAcc, vChildID:=0)
{
local
if !vChildID
{
Acc_Location(oAcc, 0, vChildPos)
for _, oChild in Acc_Children(Acc_Parent(oAcc))
{
Acc_Location(oChild, 0, vPos)
if IsObject(oChild) && (vPos = vChildPos)
return A_Index
}
}
else
{
Acc_Location(oAcc, vChildID, vChildPos)
for _, oChild in Acc_Children(oAcc)
{
Acc_Location(oAcc, oChild, vPos)
if !IsObject(oChild) && (vPos = vChildPos)
return A_Index
}
}
}
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
Re: How to get the full ACC path for control on cursor?
- I've updated both functions. The two issues resolved were: get the right numbers (if there is ambiguity, all possible indexes are returned, separated by 'or'), get the right number of numbers (it worked for all of the tests I did).
- Do notify of any issues. Thanks.
- To get the right numbers (indexes), you must compare the GUI element against itself and all of its siblings. You need some unique info. The function already checked the element locations, and it now checks the element states (e.g. STATE_SYSTEM_INVISIBLE := 0x8000). If it is still unable to distinguish the correct element, it separates candidate numbers with 'or' e.g. '1.2or3.4'. (It's possible that other identifying info could also be used to confirm the right element.)
- To get the right number of numbers, I improved the function based on the test below, e.g. I used it on a Notepad menubar item and Edit control. You start at the GUI element, repeatedly getting the next parent element, you continue until you reach a GUI element with the hWnd that you specified, and you then continue until you reach a GUI element with a different hWnd. Cheers.
- Do notify of any issues. Thanks.
Code: Select all
q:: ;acc get path of element under the cursor
WinGet, hWnd, ID, A
;hWnd := -1 ;get all possible ancestors
oAcc := Acc_ObjectFromPoint(vChildID)
vAccPath := JEE_AccGetPath(oAcc, hWnd)
if vChildID
MsgBox, % Clipboard := vAccPath " c" vChildID
else
MsgBox, % Clipboard := vAccPath
;use acc path (check that the acc path is correct)
oAcc := Acc_Get("Object", vAccPath, vChildID, "ahk_id " hWnd)
vName := vValue := ""
try vName := oAcc.accName(vChildID)
try vValue := oAcc.accValue(vChildID)
MsgBox, % vName "`r`n" vValue
return
;==================================================
;note: path might be too short e.g. menu items
;note: path might be too long (I haven't seen this happen)
;note: if there is ambiguity identifying the index, 'or' is used (see JEE_AccGetEnumIndex)
JEE_AccGetPath(oAcc, hWnd:="")
{
local
if (hWnd = "")
hWnd := Acc_WindowFromObject(oAcc)
, hWnd := DllCall("user32\GetParent", Ptr,hWnd, Ptr)
vAccPath := ""
vIsMatch := 0
if (hWnd = -1) ;get all possible ancestors
Loop
{
vIndex := JEE_AccGetEnumIndex(oAcc)
if !vIndex
break
vAccPath := vIndex (A_Index=1?"":".") vAccPath
oAcc := oAcc.accParent
}
else
Loop
{
vIndex := JEE_AccGetEnumIndex(oAcc)
hWnd2 := Acc_WindowFromObject(oAcc)
if !vIsMatch && (hWnd = hWnd2)
vIsMatch := 1
if vIsMatch && !(hWnd = hWnd2)
break
vAccPath := vIndex (A_Index=1?"":".") vAccPath
oAcc := oAcc.accParent
}
if vIsMatch
return SubStr(vAccPath, InStr(vAccPath, ".")+1)
return vAccPath
}
;==================================================
;note: AccViewer uses a mod of Acc_Location that returns a 'pos' key,
;this function is compatible with the older and AccViewer versions of Acc_Location
;note: if there is ambiguity identifying the index, 'or' is used
JEE_AccGetEnumIndex(oAcc, vChildID:=0)
{
local
vOutput := ""
vAccState := oAcc.accState(0)
if !vChildID
{
Acc_Location(oAcc, 0, vChildPos)
for _, oChild in Acc_Children(Acc_Parent(oAcc))
{
if !(vAccState = oChild.accState(0))
continue
Acc_Location(oChild, 0, vPos)
if IsObject(oChild) && (vPos = vChildPos)
vOutput .= A_Index "or"
}
}
else
{
Acc_Location(oAcc, vChildID, vChildPos)
for _, oChild in Acc_Children(oAcc)
{
if !(vAccState = oChild.accState(0))
continue
Acc_Location(oAcc, oChild, vPos)
if !IsObject(oChild) && (vPos = vChildPos)
vOutput .= A_Index "or"
}
}
return SubStr(vOutput, 1, -2)
}
;==================================================
- To get the right number of numbers, I improved the function based on the test below, e.g. I used it on a Notepad menubar item and Edit control. You start at the GUI element, repeatedly getting the next parent element, you continue until you reach a GUI element with the hWnd that you specified, and you then continue until you reach a GUI element with a different hWnd. Cheers.
Code: Select all
q:: ;acc get info for element under the cursor and ancestors
WinGet, hWnd, ID, A
oAcc := Acc_ObjectFromPoint()
vOutput := ""
Loop, 10
{
vOutput .= Acc_WindowFromObject(oAcc) "|" Format("0x{:X}", oAcc.accRole(0)) "|" RegExReplace(oAcc.accName(0), "s)[`r`n].*", "...") "|" RegExReplace(oAcc.accValue(0), "s)[`r`n].*", "...") "`r`n"
oAcc := oAcc.accParent
}
Clipboard := vOutput
MsgBox, % vOutput
return
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
-
- Posts: 3
- Joined: 08 Jan 2020, 18:44
Re: How to get the full ACC path for control on cursor?
Hi,jeeswg wrote: ↑26 Dec 2018, 12:34- I've updated both functions. The two issues resolved were: get the right numbers (if there is ambiguity, all possible indexes are returned, separated by 'or'), get the right number of numbers (it worked for all of the tests I did).
- Do notify of any issues. Thanks.
I have just learnt about the use of acc library.
First of all I have to thankyou for all of your tutorial.
I noticed one issue.
According to the document, the acc path for the same element should be the same across instances, i.e. same after reopen the same app or restart computer.
Few days ago, I check that, the toolbar Bookmark button of firefox is 4.22.1.5
Today, I wanted to run the same script again but it failed.
Then I check the acc path again, and found that it changed to 4.21.1.5
I did not modified the firefox ui,
in case of this issue, how should we handle it?
Thanks.
Re: How to get the full ACC path for control on cursor?
I ran across an issue using this when it came to getting a parent IAccessible object for controls with a 'drop down' role. What is happening is it would grab the first parent and work fine, then the second path through it isn't able to get the next parent so it grabs the grand parent so the second to last index is missed. So instead of seeing a path like '4.1.4.1.4.7.4.1.4.3.4.1' I would see '4.1.4.1.4.7.4.1.4.3.1'. I attached a screen shot of the tree for reference, but it would skip parsing the children of the 'window' object and jump straight to the 'client' causing it to miss the parent 'drop down'
I was able to get it to work by making the below changes to your JEE_AccGetEnumIndex function. Does anyone know why I would be seeing this behavior?
Code: Select all
JEE_AccGetEnumIndex(oAcc, vChildID:=0)
{
local
vOutput := ""
vAccState := oAcc.accState(0)
vAccRole := oAcc.accRole(0)
if !vChildID
{
Acc_Location(oAcc, 0, vChildPos)
for _, oChild in Acc_Children(Acc_Parent(oAcc))
{
test := oChild.accState(0)
if !(vAccState = oChild.accState(0))
continue
IF !(vAccRole = oChild.accRole(0))
{
for index, sibling in Acc_Children(oChild)
{
if !(vAccState = sibling.accState(0))
continue
Acc_Location(sibling, 0, vPos)
if IsObject(sibling) && (vPos = vChildPos)
vOutput .= _ "." index "or"
}
}
Else IF (vAccRole = oChild.accRole(0))
{
Acc_Location(oChild, 0, vPos)
if IsObject(oChild) && (vPos = vChildPos)
vOutput .= A_Index "or"
}
}
}
else
{
Acc_Location(oAcc, vChildID, vChildPos)
for _, oChild in Acc_Children(oAcc)
{
if !(vAccState = oChild.accState(0))
continue
IF !(vAccRole = oChild.accRole(0))
{
for index, sibling in Acc_Children(oChild)
{
if !(vAccState = sibling.accState(0))
continue
Acc_Location(oAcc, sibling, vPos)
if !IsObject(sibling) && (vPos = vChildPos)
vOutput .= index "or"
}
}
Else IF (vAccRole = oChild.accRole(0))
{
Acc_Location(oAcc, oChild, vPos)
if IsObject(oChild) && (vPos = vChildPos)
vOutput .= A_Index "or"
}
}
}
return SubStr(vOutput, 1, -2)
}
Re: How to get the full ACC path for control on cursor?
I need to get the coordinates by using the acc please. I know the path (4.1.4.5.4.3) and need to get the window coordinates.
Can anyone help me?
Can anyone help me?
Re: How to get the full ACC path for control on cursor?
Sorted.
Code: Select all
WinGet, hWnd, ID, ahk_exe exampleprogram.exe
;~ MsgBox, % hWnd
oAcc := Acc_Get("Object", "4.1.4.5.4.5", 0, "ahk_id " hWnd)
coords := Acc_Location(oAcc , 0, byref Position="")
xcoor := coords.x
ycoor := coords.y
xcoor += 10
ycoor += 10
MouseMove, xcoor, ycoor
Re: How to get the full ACC path for control on cursor?
Very nice script, and exactly what I'm looking for. Unfortunately, it's acting like AccViewer once it comes to deeply nested elements - it's not showing such an element's 'path' setting. The only way I've found to get such a path is using JEE_AccGetTextAll. Do you see a chance to replace your current scripts search option with JEE_AccGetTextAll()'s more capable detection feature?tmplinshi wrote: ↑30 Sep 2018, 01:49The function GetAccPath and GetEnumIndex were extracted from AccViewer.
Code: Select all
#NoEnv #SingleInstance force SetBatchLines, -1 F12:: obj := GetInfoUnderCursor() msgbox % obj.path return GetInfoUnderCursor() { Acc := Acc_ObjectFromPoint(child) if !value := Acc.accValue(child) value := Acc.accName(child) accPath := GetAccPath(acc, hwnd).path return {text: value, path: accPath, hwnd: hwnd} } GetAccPath(Acc, byref hwnd="") { hwnd := Acc_WindowFromObject(Acc) WinObj := Acc_ObjectFromWindow(hwnd) WinObjPos := Acc_Location(WinObj).pos while Acc_WindowFromObject(Parent:=Acc_Parent(Acc)) = hwnd { t2 := GetEnumIndex(Acc) "." t2 if Acc_Location(Parent).pos = WinObjPos return {AccObj:Parent, Path:SubStr(t2,1,-1)} Acc := Parent } while Acc_WindowFromObject(Parent:=Acc_Parent(WinObj)) = hwnd t1.="P.", WinObj:=Parent return {AccObj:Acc, Path:t1 SubStr(t2,1,-1)} } GetEnumIndex(Acc, ChildId=0) { if Not ChildId { ChildPos := Acc_Location(Acc).pos For Each, child in Acc_Children(Acc_Parent(Acc)) if IsObject(child) and Acc_Location(child).pos=ChildPos return A_Index } else { ChildPos := Acc_Location(Acc,ChildId).pos For Each, child in Acc_Children(Acc) if Not IsObject(child) and Acc_Location(Acc,child).pos=ChildPos return A_Index } } ; Acc.ahk ;------------------------------------------------------------------------------ ; Acc.ahk Standard Library ; by Sean ; Updated by jethrow: ; Modified ComObjEnwrap params from (9,pacc) --> (9,pacc,1) ; Changed ComObjUnwrap to ComObjValue in order to avoid AddRef (thanks fincs) ; Added Acc_GetRoleText & Acc_GetStateText ; Added additional functions - commented below ; Removed original Acc_Children function ; last updated 2/19/2012 ;------------------------------------------------------------------------------ Acc_Init() { Static h If Not h h:=DllCall("LoadLibrary","Str","oleacc","Ptr") } Acc_ObjectFromEvent(ByRef _idChild_, hWnd, idObject, idChild) { Acc_Init() If DllCall("oleacc\AccessibleObjectFromEvent", "Ptr", hWnd, "UInt", idObject, "UInt", idChild, "Ptr*", pacc, "Ptr", VarSetCapacity(varChild,8+2*A_PtrSize,0)*0+&varChild)=0 Return ComObjEnwrap(9,pacc,1), _idChild_:=NumGet(varChild,8,"UInt") } Acc_ObjectFromPoint(ByRef _idChild_ = "", x = "", y = "") { Acc_Init() If DllCall("oleacc\AccessibleObjectFromPoint", "Int64", x==""||y==""?0*DllCall("GetCursorPos","Int64*",pt)+pt:x&0xFFFFFFFF|y<<32, "Ptr*", pacc, "Ptr", VarSetCapacity(varChild,8+2*A_PtrSize,0)*0+&varChild)=0 Return ComObjEnwrap(9,pacc,1), _idChild_:=NumGet(varChild,8,"UInt") } Acc_ObjectFromWindow(hWnd, idObject = -4) { Acc_Init() If DllCall("oleacc\AccessibleObjectFromWindow", "Ptr", hWnd, "UInt", idObject&=0xFFFFFFFF, "Ptr", -VarSetCapacity(IID,16)+NumPut(idObject==0xFFFFFFF0?0x46000000000000C0:0x719B3800AA000C81,NumPut(idObject==0xFFFFFFF0?0x0000000000020400:0x11CF3C3D618736E0,IID,"Int64"),"Int64"), "Ptr*", pacc)=0 Return ComObjEnwrap(9,pacc,1) } Acc_WindowFromObject(pacc) { If DllCall("oleacc\WindowFromAccessibleObject", "Ptr", IsObject(pacc)?ComObjValue(pacc):pacc, "Ptr*", hWnd)=0 Return hWnd } Acc_GetRoleText(nRole) { nSize := DllCall("oleacc\GetRoleText", "Uint", nRole, "Ptr", 0, "Uint", 0) VarSetCapacity(sRole, (A_IsUnicode?2:1)*nSize) DllCall("oleacc\GetRoleText", "Uint", nRole, "str", sRole, "Uint", nSize+1) Return sRole } Acc_GetStateText(nState) { nSize := DllCall("oleacc\GetStateText", "Uint", nState, "Ptr", 0, "Uint", 0) VarSetCapacity(sState, (A_IsUnicode?2:1)*nSize) DllCall("oleacc\GetStateText", "Uint", nState, "str", sState, "Uint", nSize+1) Return sState } ; Written by jethrow Acc_Role(Acc, ChildId=0) { try return ComObjType(Acc,"Name")="IAccessible"?Acc_GetRoleText(Acc.accRole(ChildId)):"invalid object" } Acc_State(Acc, ChildId=0) { try return ComObjType(Acc,"Name")="IAccessible"?Acc_GetStateText(Acc.accState(ChildId)):"invalid object" } Acc_Children(Acc) { if ComObjType(Acc,"Name")!="IAccessible" error_message := "Cause:`tInvalid IAccessible Object`n`n" else { Acc_Init() cChildren:=Acc.accChildCount, Children:=[] if DllCall("oleacc\AccessibleChildren", "Ptr", ComObjValue(Acc), "Int", 0, "Int", cChildren, "Ptr", VarSetCapacity(varChildren,cChildren*(8+2*A_PtrSize),0)*0+&varChildren, "Int*", cChildren)=0 { Loop %cChildren% i:=(A_Index-1)*(A_PtrSize*2+8)+8, child:=NumGet(varChildren,i), Children.Insert(NumGet(varChildren,i-8)=3?child:Acc_Query(child)), ObjRelease(child) return Children } } error:=Exception("",-1) MsgBox, 262148, Acc_Children Failed, % (error_message?error_message:"") "File:`t" (error.file==A_ScriptFullPath?A_ScriptName:error.file) "`nLine:`t" error.line "`n`nContinue Script?" IfMsgBox, No ExitApp } Acc_Location(Acc, ChildId=0) { ; adapted from Sean's code try Acc.accLocation(ComObj(0x4003,&x:=0), ComObj(0x4003,&y:=0), ComObj(0x4003,&w:=0), ComObj(0x4003,&h:=0), ChildId) catch return return {x:NumGet(x,0,"int"), y:NumGet(y,0,"int"), w:NumGet(w,0,"int"), h:NumGet(h,0,"int") , pos:"x" NumGet(x,0,"int")" y" NumGet(y,0,"int") " w" NumGet(w,0,"int") " h" NumGet(h,0,"int")} } Acc_Parent(Acc) { try parent:=Acc.accParent return parent?Acc_Query(parent): } Acc_Child(Acc, ChildId=0) { try child:=Acc.accChild(ChildId) return child?Acc_Query(child): } Acc_Query(Acc) { ; thanks Lexikos - www.autohotkey.com/forum/viewtopic.php?t=81731&p=509530#509530 try return ComObj(9, ComObjQuery(Acc,"{618736e0-3c3d-11cf-810c-00aa00389b71}"), 1) }
Thx for listening
Re: How to get the full ACC path for control on cursor?
Thanks for posting this! You undoubtedly saved me a lot of time trying to figure this out for myself.labrint wrote: ↑14 Sep 2021, 11:43Sorted.
Code: Select all
WinGet, hWnd, ID, ahk_exe exampleprogram.exe ;~ MsgBox, % hWnd oAcc := Acc_Get("Object", "4.1.4.5.4.5", 0, "ahk_id " hWnd) coords := Acc_Location(oAcc , 0, byref Position="") xcoor := coords.x ycoor := coords.y xcoor += 10 ycoor += 10 MouseMove, xcoor, ycoor
Who is online
Users browsing this forum: FanaticGuru, filipemb and 298 guests