Acc Library [AHK_L] (updated 09/27/2012)
if !window := Acc_Parent(Acc_ObjectFromWindow(WinExist("ahk_class Notepad"))) { MsgBox, Epic FAIL d00d. return } For each, object in Acc_Children(window) {} Until (object.accName="Application") Sleep, 100 For each, mainMenu in Acc_Children(object) {} Until (mainMenu.accName="File") WinGet, b, List, ahk_class #32768 mainMenu.accDoDefaultAction(1), a := b Sleep, 50 While (a=b) WinGet, a, List, ahk_class #32768 fileMenu := Acc_ObjectFromWindow(a%a%) For each, item in Acc_Children(fileMenu) {} Until InStr(fileMenu.accName(item),"Open") fileMenu.accDoDefaultAction(item)
And once I got this working I tried going the "silent" route and found I was correct; you have to invoke the menu item window in order for the menu item to execute.
What confuses me is the need to add the Sleep. Not in the case of the Sleep after accDoDefaultAction, since the menu window needs a moment to appear. But if I don't have that Sleep, 100 after the first For-loop, the code will never execute. I only caught it because I was manually adding message boxes at every step just to verify I had the right object, and when I added the message box after that loop and dismissed it, everything suddenly worked just like it was supposed to. It's as if something hasn't caught up yet.
EDIT: A couple of menu item helper functions for your consideration in the library, will greatly ease code bloat when trying to find the newest menu window when navigating through multiple menus:
Acc_getMenus() { obj := {} WinGet, menu, List, ahk_class #32768 Loop % menu obj[menu%A_Index%] := 1 return obj } Acc_getNewMenu(obj) { new := obj.MaxIndex() While (new=obj.MaxIndex()) WinGet, new, List, ahk_class #32768 Loop % new if !obj[new%A_Index%] return new%A_Index% }
Example usage:
For each, mainMenu in Acc_Children(object) {} Until (mainMenu.accName="File") win := Acc_getMenus(), mainMenu.accDoDefaultAction(1) Sleep, 50 fileMenu := Acc_ObjectFromWindow(Acc_getNewMenu(win))
EDIT: Modified object creation in Acc_getMenus() function per Lexikos' advice.
When you do this, any previously inserted handles with values greater than menu%A_Index% will be incremented by 1, thus corrupting them. Use obj[menu%A_Index%] := 1 instead if you really want to use the handles as keys vs values.obj.Insert(menu%A_Index%,1)
My Scripts are written for the latest released version of AutoHotkey.
Need a secure, accessible place to backup your stuff? Use Dropbox!
Here's my copy of the library file:
;------------------------------------------------------------------------------ ; 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) { 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, 262420, Acc_Children Failed, % "File: " error.file "`nLine: " 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) }
I am also very interested in this possibility. I noticed two entries in the Accessibility constants:I have another question, some programs like Firefox don't allow detection of caret position, can we use ACC to detect caret position in any window?
I've tried a few things but as of now I have no idea what function to use or how.OBJID_CARET = 0xFFFFFFF8
ROLE_SYSTEM_CARET = 0x00000007
Can anybody please give me an example?
Rodolfo U. Batista
Pulover's Macro Creator - Automation Tool (Recorder & Script Writer) | Class_LV_Rows - Copy, Cut, Paste and Drag ListViews | Class_Toolbar - Create and modify | Class_Rebar - Adjustable GUI controls
Join the New AutoHotkey Forum!
See - Firefox Bug 574672 - Caret object unavailable until focus change. Workaround in red, which also causes A_CaretX/Y to work:I am also very interested in this possibility ... Can anybody please give me an example?I have another question, some programs like Firefox don't allow detection of caret position ...
SetTitleMatchMode, 2 OBJID_CARET := 0xFFFFFFF8 F1:: WinGet, hwnd, ID, Firefox [color=#FF0000]WinGetPos, x, y, w, h, ahk_id %hwnd% Loop 2 {[/color] Acc_Caret := Acc_ObjectFromWindow(hwnd, OBJID_CARET) Caret_Location := Acc_Location(Acc_Caret) [color=#FF0000]if (Caret_Location.pos != "x0 y0 w0 h0") or (A_Cursor != "IBeam") break else { Acc_ObjectFromPoint(child, (x+w)/2, (y+h)/2) ControlSend, ahk_parent, {tab}, ahk_id %hwnd% Sleep 1000 ControlSend, ahk_parent, {shift down}{tab}, ahk_id %hwnd% Sleep 250 } }[/color] for k,v in Caret_Location output .= k " =`t" v "`n" WinGetTitle, title, ahk_id %hwnd% ToolTip % output "`nA_CaretX = " A_CaretX+x "`nA_CaretY = " A_CaretY+y output := ""
QUESTION: For the Acc_WindowFromObject function, Sean had used the client for the default ObjectId parameter. Can anyone think of a good reason why the default should't be the Window/Self ObjectId? This would seem more intuitive to me ...
It solves a problem with my project. I couldn't get it to work on Thunderbird, though... is it possible?
Thanks again!
Rodolfo U. Batista
Pulover's Macro Creator - Automation Tool (Recorder & Script Writer) | Class_LV_Rows - Copy, Cut, Paste and Drag ListViews | Class_Toolbar - Create and modify | Class_Rebar - Adjustable GUI controls
Join the New AutoHotkey Forum!
When trying to run the version of Acc.ahk in your first post. Same thing happens when I use #Include for it in another script. Unfortunately I'm not familiar enough with your library to make any sense out of the error message. I suspect that I'm missing some .dll file? However not sure if that's correct, and not sure which.
I couldn't get it to work on Thunderbird, though...
More specifically, I'm not able to obtain Thunderbird's menu windows. Have you experimented with Thunderbird at all jethrow?
NoHave you experimented with Thunderbird at all jethrow?
I'm working on implementing functions that will allow the user to get information from the screen almost as easily as ControlGetText. The ChildPath needed will be obtained using Accessible Info Viewer, which I'm planning on updating sometime. (currently it accesses the client object, rather than the window object, to start the path). Here is an example of getting the url in Firefox v15:
SetTitleMatchMode 2 [color=#008000]/* Acc_Get(Cmd, ChildPath="", ChildID=0, WinTitle="", WinText="", ExcludeTitle="", ExcludeText="") RunTime errors can be handled with ErrorLevel, or by turning on the Acc Errors via Acc_Error(true) */[/color] MsgBox % Acc_Get("Value", "4.19.2.4.2", 0, "Firefox") [color=#008000]/* AHK v2 command syntax: Acc_Get, FF_Url, Value, 4.19.2.4.2, 0, Firefox MsgBox %FF_Url% */[/color] _path = (join. application property page tool bar2 combo box editable text ) [color=#008000]/* alertnative: can use _ instead of space; no number = 1 _path = application1.property_page1.tool_bar2.combo_box1.editable_text1 */[/color] MsgBox % Acc_Get("Value", _path, 0, "ahk_class MozillaWindowClass")
I'm interested in any input ... good idea, bad idea, suggestions ...
Acc_Error(p="") { static setting:=0 return p=""?setting:setting:=p } Acc_Children(Acc) { if ComObjType(Acc,"Name") != "IAccessible" ErrorLevel := "Invalid IAccessible Object" 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)=9?Acc_Query(child):child), NumGet(varChildren,i-8)=9?ObjRelease(child): return Children.MaxIndex()?Children: } else ErrorLevel := "AccessibleChildren DllCall Failed" } if Acc_Error() throw Exception(ErrorLevel,-1) } Acc_ChildrenByRole(Acc, Role) { if ComObjType(Acc,"Name")!="IAccessible" ErrorLevel := "Invalid IAccessible Object" 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) if NumGet(varChildren,i-8)=9 AccChild:=Acc_Query(child), ObjRelease(child), Acc_Role(AccChild)=Role?Children.Insert(AccChild): else Acc_Role(Acc, child)=Role?Children.Insert(child): } return Children.MaxIndex()?Children:, ErrorLevel:=0 } else ErrorLevel := "AccessibleChildren DllCall Failed" } if Acc_Error() throw Exception(ErrorLevel,-1) } Acc_Get(Cmd, ChildPath="", ChildID=0, WinTitle="", WinText="", ExcludeTitle="", ExcludeText="") { AccObj := IsObject(WinTitle)? WinTitle : Acc_ObjectFromWindow( WinExist(WinTitle, WinText, ExcludeTitle, ExcludeText), 0 ) if ComObjType(AccObj, "Name") != "IAccessible" ErrorLevel := "Could not access an IAccessible Object" else { StringReplace, ChildPath, ChildPath, _, %A_Space%, All AccError:=Acc_Error(), Acc_Error(true) Loop Parse, ChildPath, ., %A_Space% try { if A_LoopField is digit Children:=Acc_Children(AccObj), m2:=A_LoopField ; mimic "m2" output in else-statement regex else RegExMatch(A_LoopField, "(\D*)(\d*)", m), Children:=Acc_ChildrenByRole(AccObj, m1), m2:=(m2?m2:1) if Not Children.HasKey(m2) throw AccObj := Children[m2] } catch { ErrorLevel:="Cannot access ChildPath Item #" A_Index " -> " A_LoopField, Acc_Error(AccError) if Acc_Error() throw Exception("Cannot access ChildPath Item", -1, "Item #" A_Index " -> " A_LoopField) return } Acc_Error(AccError) try ret_val := AccObj["acc" Cmd](ChildID+0) catch { ErrorLevel := """" Cmd """ Command Not Implemented" if Acc_Error() throw Exception("Command Not Implemented", -1, Cmd) return } return ret_val, ErrorLevel:=0 } if Acc_Error() throw Exception(ErrorLevel,-1) }
I have a problem with Acc_ObjectFromWindow. I was working on a horizontal scrolling script. It works as expected in Word and Excel, however in PPT only scrolls to the right and never to the left.
I have tried replacing the variables by hardcoding the classes and text but no luck, any idea?
;Define Matching Method SetTitleMatchMode, RegEx ;For MS Office #IfWinActive ahk_class (PPT|OpusApp|XLMAIN) WheelLeft:: WheelRight:: Acc_ObjectFromWindow(ActiveWinClassNN(), -16).SmallScroll(0,0,InStr(A_ThisHotkey,"Left")? -1:1) return #IfWinActive ActiveWinClassNN() { MouseGetPos, , , id, class, 2 return class }
Asked a question about an error message related to the Acc Library in the below post. Maybe someone following this thread here can help? (Hope my little cross-post doesn't annoy anyone...)
http://www.autohotke...-link/?p=567672
Thanks.