How to get the full ACC path for control on cursor? Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
CyL0N
Posts: 211
Joined: 27 Sep 2018, 09:58

How to get the full ACC path for control on cursor?

28 Sep 2018, 01:29

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.
live ? long & prosper : regards
tmplinshi
Posts: 1604
Joined: 01 Oct 2013, 14:57

Re: How to get the full ACC path for control on cursor?  Topic is solved

30 Sep 2018, 01:49

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)
}
DRocks
Posts: 565
Joined: 08 May 2018, 10:20

Re: How to get the full ACC path for control on cursor?

30 Sep 2018, 06:36

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.
CyL0N
Posts: 211
Joined: 27 Sep 2018, 09:58

Re: How to get the full ACC path for control on cursor?

01 Oct 2018, 06:34

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
CyL0N
Posts: 211
Joined: 27 Sep 2018, 09:58

Re: How to get the full ACC path for control on cursor?

01 Oct 2018, 06:35

duplicatePost
Last edited by CyL0N on 01 Oct 2018, 06:46, edited 1 time in total.
live ? long & prosper : regards
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: How to get the full ACC path for control on cursor?

15 Dec 2018, 23:18

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

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
		}
	}
}
- 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.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: How to get the full ACC path for control on cursor?

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.

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

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
The stupid one
Posts: 3
Joined: 08 Jan 2020, 18:44

Re: How to get the full ACC path for control on cursor?

07 Mar 2020, 01:39

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.
Hi,
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.
Jhartje
Posts: 10
Joined: 23 May 2019, 08:20

Re: How to get the full ACC path for control on cursor?

24 Mar 2021, 11:55

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'
image.png
image.png (11.65 KiB) Viewed 3826 times
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)
}
User avatar
labrint
Posts: 379
Joined: 14 Jun 2017, 05:06
Location: Malta

Re: How to get the full ACC path for control on cursor?

14 Sep 2021, 10:45

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?
User avatar
labrint
Posts: 379
Joined: 14 Jun 2017, 05:06
Location: Malta

Re: How to get the full ACC path for control on cursor?

14 Sep 2021, 11:43

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

BoBo
Posts: 6564
Joined: 13 May 2014, 17:15

Re: How to get the full ACC path for control on cursor?

11 May 2022, 16:42

tmplinshi wrote:
30 Sep 2018, 01:49
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)
}
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 :arrow: JEE_AccGetTextAll. Do you see a chance to replace your current scripts search option with JEE_AccGetTextAll()'s more capable detection feature?
Thx for listening :)
MasterZ
Posts: 4
Joined: 18 Jul 2022, 17:38

Re: How to get the full ACC path for control on cursor?

20 Jul 2022, 17:37

labrint wrote:
14 Sep 2021, 11:43
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

Thanks for posting this! You undoubtedly saved me a lot of time trying to figure this out for myself.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Anput, doodles333, Nerafius, ShatterCoder and 81 guests