Module _UIAHandler
[hide private]
[frames] | no frames]

Source Code for Module _UIAHandler

  1  from ctypes import * 
  2  from ctypes.wintypes import * 
  3  import comtypes.client 
  4  from comtypes import * 
  5  import weakref 
  6  import threading 
  7  import re 
  8  import time 
  9  import api 
 10  import queueHandler 
 11  import controlTypes 
 12  import winKernel 
 13  import winUser 
 14  import eventHandler 
 15  from logHandler import log 
 16   
 17  from comtypes.gen.UIAutomationClient import * 
 18   
 19  re_MSAAProxyProviderDescription=re.compile(r'Microsoft: (Annotation|MSAA) Proxy \(unmanaged:uiautomationcore.dll\)',re.IGNORECASE) 
 20   
 21  badUIAWindowClassNames=[ 
 22          "SysTreeView32", 
 23          "WuDuiListView", 
 24          "ComboBox", 
 25          "msctls_progress32", 
 26          "Edit", 
 27          "CommonPlacesWrapperWndClass", 
 28          "SysMonthCal32", 
 29          "SUPERGRID", #Outlook 2010 message list 
 30  ] 
 31   
 32  UIAControlTypesToNVDARoles={ 
 33          UIA_ButtonControlTypeId:controlTypes.ROLE_BUTTON, 
 34          UIA_CalendarControlTypeId:controlTypes.ROLE_CALENDAR, 
 35          UIA_CheckBoxControlTypeId:controlTypes.ROLE_CHECKBOX, 
 36          UIA_ComboBoxControlTypeId:controlTypes.ROLE_COMBOBOX, 
 37          UIA_EditControlTypeId:controlTypes.ROLE_EDITABLETEXT, 
 38          UIA_HyperlinkControlTypeId:controlTypes.ROLE_LINK, 
 39          UIA_ImageControlTypeId:controlTypes.ROLE_GRAPHIC, 
 40          UIA_ListItemControlTypeId:controlTypes.ROLE_LISTITEM, 
 41          UIA_ListControlTypeId:controlTypes.ROLE_LIST, 
 42          UIA_MenuControlTypeId:controlTypes.ROLE_POPUPMENU, 
 43          UIA_MenuBarControlTypeId:controlTypes.ROLE_MENUBAR, 
 44          UIA_MenuItemControlTypeId:controlTypes.ROLE_MENUITEM, 
 45          UIA_ProgressBarControlTypeId:controlTypes.ROLE_PROGRESSBAR, 
 46          UIA_RadioButtonControlTypeId:controlTypes.ROLE_RADIOBUTTON, 
 47          UIA_ScrollBarControlTypeId:controlTypes.ROLE_SCROLLBAR, 
 48          UIA_SliderControlTypeId:controlTypes.ROLE_SLIDER, 
 49          UIA_SpinnerControlTypeId:controlTypes.ROLE_SPINBUTTON, 
 50          UIA_StatusBarControlTypeId:controlTypes.ROLE_STATUSBAR, 
 51          UIA_TabControlTypeId:controlTypes.ROLE_TABCONTROL, 
 52          UIA_TabItemControlTypeId:controlTypes.ROLE_TAB, 
 53          UIA_TextControlTypeId:controlTypes.ROLE_STATICTEXT, 
 54          UIA_ToolBarControlTypeId:controlTypes.ROLE_TOOLBAR, 
 55          UIA_ToolTipControlTypeId:controlTypes.ROLE_TOOLTIP, 
 56          UIA_TreeControlTypeId:controlTypes.ROLE_TREEVIEW, 
 57          UIA_TreeItemControlTypeId:controlTypes.ROLE_TREEVIEWITEM, 
 58          UIA_CustomControlTypeId:controlTypes.ROLE_UNKNOWN, 
 59          UIA_GroupControlTypeId:controlTypes.ROLE_GROUPING, 
 60          UIA_ThumbControlTypeId:controlTypes.ROLE_THUMB, 
 61          UIA_DataGridControlTypeId:controlTypes.ROLE_DATAGRID, 
 62          UIA_DataItemControlTypeId:controlTypes.ROLE_DATAITEM, 
 63          UIA_DocumentControlTypeId:controlTypes.ROLE_DOCUMENT, 
 64          UIA_SplitButtonControlTypeId:controlTypes.ROLE_SPLITBUTTON, 
 65          UIA_WindowControlTypeId:controlTypes.ROLE_WINDOW, 
 66          UIA_PaneControlTypeId:controlTypes.ROLE_PANE, 
 67          UIA_HeaderControlTypeId:controlTypes.ROLE_HEADER, 
 68          UIA_HeaderItemControlTypeId:controlTypes.ROLE_HEADERITEM, 
 69          UIA_TableControlTypeId:controlTypes.ROLE_TABLE, 
 70          UIA_TitleBarControlTypeId:controlTypes.ROLE_TITLEBAR, 
 71          UIA_SeparatorControlTypeId:controlTypes.ROLE_SEPARATOR, 
 72  } 
 73   
 74  UIAPropertyIdsToNVDAEventNames={ 
 75          UIA_NamePropertyId:"nameChange", 
 76          UIA_HelpTextPropertyId:"descriptionChange", 
 77          UIA_ExpandCollapseExpandCollapseStatePropertyId:"stateChange", 
 78          UIA_ToggleToggleStatePropertyId:"stateChange", 
 79          UIA_IsEnabledPropertyId:"stateChange", 
 80          UIA_ValueValuePropertyId:"valueChange", 
 81          UIA_RangeValueValuePropertyId:"valueChange", 
 82  } 
 83   
 84  UIAEventIdsToNVDAEventNames={ 
 85          #UIA_Text_TextChangedEventId:"textChanged", 
 86          UIA_SelectionItem_ElementSelectedEventId:"stateChange", 
 87          #UIA_MenuOpenedEventId:"gainFocus", 
 88          UIA_SelectionItem_ElementAddedToSelectionEventId:"stateChange", 
 89          UIA_SelectionItem_ElementRemovedFromSelectionEventId:"stateChange", 
 90          #UIA_MenuModeEndEventId:"menuModeEnd", 
 91          #UIA_Text_TextSelectionChangedEventId:"caret", 
 92          #UIA_ToolTipOpenedEventId:"show", 
 93          #UIA_AsyncContentLoadedEventId:"documentLoadComplete", 
 94          #UIA_ToolTipClosedEventId:"hide", 
 95  } 
 96   
97 -class UIAHandler(COMObject):
98 _com_interfaces_=[IUIAutomationEventHandler,IUIAutomationFocusChangedEventHandler,IUIAutomationPropertyChangedEventHandler] 99
100 - def __init__(self):
101 super(UIAHandler,self).__init__() 102 self.MTAThreadInitEvent=threading.Event() 103 self.MTAThreadStopEvent=threading.Event() 104 self.MTAThreadInitException=None 105 self.MTAThread=threading.Thread(target=self.MTAThreadFunc) 106 self.MTAThread.daemon=True 107 self.MTAThread.start() 108 self.MTAThreadInitEvent.wait(2) 109 if self.MTAThreadInitException: 110 raise self.MTAThreadInitException
111
112 - def terminate(self):
113 MTAThreadHandle=HANDLE(windll.kernel32.OpenThread(self.MTAThread.ident,False,winKernel.SYNCHRONIZE)) 114 self.MTAThreadStopEvent.set() 115 index=c_int() 116 #Wait for the MTAA thread to die (while still message pumping) 117 windll.user32.MsgWaitForMultipleObjects(1,byref(MTAThreadHandle),False,5000,0) 118 windll.kernel32.CloseHandle(MTAThreadHandle) 119 del self.MTAThread
120
121 - def MTAThreadFunc(self):
122 try: 123 oledll.ole32.CoInitializeEx(None,comtypes.COINIT_MULTITHREADED) 124 self.clientObject=CoCreateInstance(CUIAutomation._reg_clsid_,interface=IUIAutomation,clsctx=CLSCTX_INPROC_SERVER) 125 self.windowTreeWalker=self.clientObject.createTreeWalker(self.clientObject.CreateNotCondition(self.clientObject.CreatePropertyCondition(UIA_NativeWindowHandlePropertyId,0))) 126 self.windowCacheRequest=self.clientObject.CreateCacheRequest() 127 self.windowCacheRequest.AddProperty(UIA_NativeWindowHandlePropertyId) 128 self.UIAWindowHandleCache={} 129 self.baseTreeWalker=self.clientObject.RawViewWalker 130 self.baseCacheRequest=self.windowCacheRequest.Clone() 131 for propertyId in (UIA_ClassNamePropertyId,UIA_ControlTypePropertyId,UIA_IsKeyboardFocusablePropertyId,UIA_IsPasswordPropertyId,UIA_ProviderDescriptionPropertyId,UIA_ProcessIdPropertyId,UIA_IsSelectionItemPatternAvailablePropertyId,UIA_IsTextPatternAvailablePropertyId): 132 self.baseCacheRequest.addProperty(propertyId) 133 self.rootElement=self.clientObject.getRootElementBuildCache(self.baseCacheRequest) 134 self.reservedNotSupportedValue=self.clientObject.ReservedNotSupportedValue 135 self.clientObject.AddFocusChangedEventHandler(self.baseCacheRequest,self) 136 self.clientObject.AddPropertyChangedEventHandler(self.rootElement,TreeScope_Subtree,self.baseCacheRequest,self,UIAPropertyIdsToNVDAEventNames.keys()) 137 for x in UIAEventIdsToNVDAEventNames.iterkeys(): 138 self.clientObject.addAutomationEventHandler(x,self.rootElement,TreeScope_Subtree,self.baseCacheRequest,self) 139 except Exception as e: 140 self.MTAThreadInitException=e 141 finally: 142 self.MTAThreadInitEvent.set() 143 self.MTAThreadStopEvent.wait() 144 self.clientObject.RemoveAllEventHandlers()
145
146 - def IUIAutomationEventHandler_HandleAutomationEvent(self,sender,eventID):
147 if not self.MTAThreadInitEvent.isSet: 148 # UIAHandler hasn't finished initialising yet, so just ignore this event. 149 return 150 NVDAEventName=UIAEventIdsToNVDAEventNames.get(eventID,None) 151 if not NVDAEventName: 152 return 153 if not self.isNativeUIAElement(sender): 154 return 155 import NVDAObjects.UIA 156 obj=NVDAObjects.UIA.UIA(UIAElement=sender) 157 if not obj: 158 return 159 eventHandler.queueEvent(NVDAEventName,obj)
160
162 if not self.MTAThreadInitEvent.isSet: 163 # UIAHandler hasn't finished initialising yet, so just ignore this event. 164 return 165 if not self.isNativeUIAElement(sender): 166 return 167 try: 168 hasFocus=sender.currentHasKeyboardFocus 169 except COMError: 170 return 171 if not hasFocus: 172 return 173 import NVDAObjects.UIA 174 if isinstance(eventHandler.lastQueuedFocusObject,NVDAObjects.UIA.UIA): 175 lastFocus=eventHandler.lastQueuedFocusObject.UIAElement 176 # Ignore duplicate focus events. 177 # It seems that it is possible for compareElements to return True, even though the objects are different. 178 # Therefore, don't ignore the event if the last focus object has lost its hasKeyboardFocus state. 179 if self.clientObject.compareElements(sender,lastFocus) and lastFocus.currentHasKeyboardFocus: 180 return 181 obj=NVDAObjects.UIA.UIA(UIAElement=sender) 182 if not obj: 183 return 184 eventHandler.queueEvent("gainFocus",obj)
185
187 if not self.MTAThreadInitEvent.isSet: 188 # UIAHandler hasn't finished initialising yet, so just ignore this event. 189 return 190 NVDAEventName=UIAPropertyIdsToNVDAEventNames.get(propertyId,None) 191 if not NVDAEventName: 192 return 193 if not self.isNativeUIAElement(sender): 194 return 195 import NVDAObjects.UIA 196 obj=NVDAObjects.UIA.UIA(UIAElement=sender) 197 if not obj: 198 return 199 eventHandler.queueEvent(NVDAEventName,obj)
200
201 - def isUIAWindow(self,hwnd):
202 now=time.time() 203 v=self.UIAWindowHandleCache.get(hwnd,None) 204 if not v or (now-v[1])>0.5: 205 if windll.kernel32.GetCurrentProcessId()==winUser.getWindowThreadProcessID(hwnd)[0]: 206 isUIA=False 207 elif winUser.getClassName(hwnd) in badUIAWindowClassNames: 208 isUIA=False 209 else: 210 isUIA=windll.UIAutomationCore.UiaHasServerSideProvider(hwnd) 211 self.UIAWindowHandleCache[hwnd]=(isUIA,now) 212 return isUIA 213 return v[0]
214
215 - def getNearestWindowHandle(self,UIAElement):
216 try: 217 UIAElement=self.windowTreeWalker.NormalizeElementBuildCache(UIAElement,self.windowCacheRequest) 218 except COMError: 219 return None 220 try: 221 return UIAElement.cachedNativeWindowHandle 222 except COMError: 223 return None
224
225 - def isNativeUIAElement(self,UIAElement):
226 #Due to issues dealing with UIA elements coming from the same process, we do not class these UIA elements as usable. 227 #It seems to be safe enough to retreave the cached processID, but using tree walkers or fetching other properties causes a freeze. 228 try: 229 processID=UIAElement.cachedProcessId 230 except COMError: 231 return False 232 if processID==windll.kernel32.GetCurrentProcessId(): 233 return False 234 #If the element has a window handle, whether it is a native element depends on whether the window natively supports UIA. 235 try: 236 windowHandle=UIAElement.cachedNativeWindowHandle 237 except COMError: 238 windowHandle=None 239 if windowHandle: 240 return self.isUIAWindow(windowHandle) 241 try: 242 providerDescription=UIAElement.cachedProviderDescription 243 except COMError: 244 return True 245 if re_MSAAProxyProviderDescription.search(providerDescription): 246 # This is an MSAA proxy. 247 # Whether this is a native element depends on whether the nearest window handle natively supports UIA. 248 windowHandle=self.getNearestWindowHandle(UIAElement) 249 if not windowHandle: 250 return False 251 return self.isUIAWindow(windowHandle) 252 return True
253