#IAccessibleHandler.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2006-2007 NVDA Contributors
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
import heapq
import itertools
import struct
import weakref
from ctypes import *
from ctypes.wintypes import HANDLE
from comtypes import IUnknown, IServiceProvider, COMError
import comtypes.client
import comtypes.client.lazybind
import oleacc
import UIAHandler
from comInterfaces.Accessibility import *
from comInterfaces.IAccessible2Lib import *
from logHandler import log
import JABHandler
import eventHandler
import winUser
import api
import NVDAObjects.IAccessible
import NVDAObjects.window
import appModuleHandler
import mouseHandler
import controlTypes
import keyboardHandler
import core
MAX_WINEVENTS=500
MAX_WINEVENTS_PER_THREAD=10
#Special Mozilla gecko MSAA constant additions
NAVRELATION_LABEL_FOR=0x1002
NAVRELATION_LABELLED_BY=0x1003
NAVRELATION_NODE_CHILD_OF=0x1005
NAVRELATION_EMBEDS=0x1009
# IAccessible2 relations (not included in the typelib)
IA2_RELATION_FLOWS_FROM = "flowsFrom"
IA2_RELATION_FLOWS_TO = "flowsTo"
MENU_EVENTIDS=(winUser.EVENT_SYSTEM_MENUSTART,winUser.EVENT_SYSTEM_MENUEND,winUser.EVENT_SYSTEM_MENUPOPUPSTART,winUser.EVENT_SYSTEM_MENUPOPUPEND)
class OrderedWinEventLimiter(object):
"""Collects and limits winEvents based on whether they are focus changes, or just generic (all other ones).
Only allow a max of L{maxFocusItems}, if more are added then the oldest focus event is removed to make room.
Only allow one event for one specific object at a time, though push it further forward in time if a duplicate tries to get added. This is true for both generic and focus events.
"""
def __init__(self,maxFocusItems=3):
"""
@param maxFocusItems: the amount of focus changed events allowed to be queued.
@type maxFocusItems: integer
"""
self.maxFocusItems=maxFocusItems
self._focusEventCache={}
self._genericEventCache={}
self._eventHeap=[]
self._eventCounter=itertools.count()
self._lastMenuEvent=None
def addEvent(self,eventID,window,objectID,childID,threadID):
"""Adds a winEvent to the limiter.
@param eventID: the winEvent type
@type eventID: integer
@param window: the window handle of the winEvent
@type window: integer
@param objectID: the objectID of the winEvent
@type objectID: integer
@param childID: the childID of the winEvent
@type childID: integer
@param threadID: the threadID of the winEvent
@type threadID: integer
@return: C{True} if the event was added, C{False} if it was discarded.
@rtype: bool
"""
if eventID==winUser.EVENT_OBJECT_FOCUS:
if objectID in (winUser.OBJID_SYSMENU,winUser.OBJID_MENU) and childID==0:
# This is a focus event on a menu bar itself, which is just silly. Ignore it.
return False
#We do not need a focus event on an object if we already got a foreground event for it
if (winUser.EVENT_SYSTEM_FOREGROUND,window,objectID,childID,threadID) in self._focusEventCache:
return False
self._focusEventCache[(eventID,window,objectID,childID,threadID)]=next(self._eventCounter)
return True
elif eventID==winUser.EVENT_SYSTEM_FOREGROUND:
self._focusEventCache.pop((winUser.EVENT_OBJECT_FOCUS,window,objectID,childID,threadID),None)
self._focusEventCache[(eventID,window,objectID,childID,threadID)]=next(self._eventCounter)
elif eventID==winUser.EVENT_OBJECT_SHOW:
k=(winUser.EVENT_OBJECT_HIDE,window,objectID,childID,threadID)
if k in self._genericEventCache:
del self._genericEventCache[k]
return True
elif eventID==winUser.EVENT_OBJECT_HIDE:
k=(winUser.EVENT_OBJECT_SHOW,window,objectID,childID,threadID)
if k in self._genericEventCache:
del self._genericEventCache[k]
return True
elif eventID in MENU_EVENTIDS:
self._lastMenuEvent=(next(self._eventCounter),eventID,window,objectID,childID,threadID)
return True
self._genericEventCache[(eventID,window,objectID,childID,threadID)]=next(self._eventCounter)
return True
def flushEvents(self):
"""Returns a list of winEvents (tuples of eventID,window,objectID,childID) that have been added, though due to limiting, it will not necessarily be all the winEvents that were originally added. They are definitely garenteed to be in the correct order though.
"""
if self._lastMenuEvent is not None:
heapq.heappush(self._eventHeap,self._lastMenuEvent)
self._lastMenuEvent=None
g=self._genericEventCache
self._genericEventCache={}
threadCounters={}
for k,v in sorted(g.iteritems(),key=lambda item: item[1],reverse=True):
threadCount=threadCounters.get(k[-1],0)
if threadCount>MAX_WINEVENTS_PER_THREAD:
continue
heapq.heappush(self._eventHeap,(v,)+k)
threadCounters[k[-1]]=threadCount+1
f=self._focusEventCache
self._focusEventCache={}
for k,v in sorted(f.iteritems(),key=lambda item: item[1])[0-self.maxFocusItems:]:
heapq.heappush(self._eventHeap,(v,)+k)
e=self._eventHeap
self._eventHeap=[]
r=[]
for count in xrange(len(e)):
event=heapq.heappop(e)[1:-1]
r.append(event)
return r
#The win event limiter for all winEvents
winEventLimiter=OrderedWinEventLimiter()
#A place to store live IAccessible NVDAObjects, that can be looked up by their window,objectID,childID event params.
liveNVDAObjectTable=weakref.WeakValueDictionary()
# #3831: Stuff related to deferring of events for foreground changes.
# See pumpAll for details.
MAX_FOREGROUND_DEFERS=2
_deferUntilForegroundWindow = None
_foregroundDefers = 0
IAccessibleRolesToNVDARoles={
oleacc.ROLE_SYSTEM_WINDOW:controlTypes.ROLE_WINDOW,
oleacc.ROLE_SYSTEM_CLIENT:controlTypes.ROLE_PANE,
oleacc.ROLE_SYSTEM_TITLEBAR:controlTypes.ROLE_TITLEBAR,
oleacc.ROLE_SYSTEM_DIALOG:controlTypes.ROLE_DIALOG,
oleacc.ROLE_SYSTEM_PANE:controlTypes.ROLE_PANE,
oleacc.ROLE_SYSTEM_CHECKBUTTON:controlTypes.ROLE_CHECKBOX,
oleacc.ROLE_SYSTEM_RADIOBUTTON:controlTypes.ROLE_RADIOBUTTON,
oleacc.ROLE_SYSTEM_STATICTEXT:controlTypes.ROLE_STATICTEXT,
oleacc.ROLE_SYSTEM_TEXT:controlTypes.ROLE_EDITABLETEXT,
oleacc.ROLE_SYSTEM_PUSHBUTTON:controlTypes.ROLE_BUTTON,
oleacc.ROLE_SYSTEM_MENUBAR:controlTypes.ROLE_MENUBAR,
oleacc.ROLE_SYSTEM_MENUITEM:controlTypes.ROLE_MENUITEM,
oleacc.ROLE_SYSTEM_MENUPOPUP:controlTypes.ROLE_POPUPMENU,
oleacc.ROLE_SYSTEM_COMBOBOX:controlTypes.ROLE_COMBOBOX,
oleacc.ROLE_SYSTEM_LIST:controlTypes.ROLE_LIST,
oleacc.ROLE_SYSTEM_LISTITEM:controlTypes.ROLE_LISTITEM,
oleacc.ROLE_SYSTEM_GRAPHIC:controlTypes.ROLE_GRAPHIC,
oleacc.ROLE_SYSTEM_HELPBALLOON:controlTypes.ROLE_HELPBALLOON,
oleacc.ROLE_SYSTEM_TOOLTIP:controlTypes.ROLE_TOOLTIP,
oleacc.ROLE_SYSTEM_LINK:controlTypes.ROLE_LINK,
oleacc.ROLE_SYSTEM_OUTLINE:controlTypes.ROLE_TREEVIEW,
oleacc.ROLE_SYSTEM_OUTLINEITEM:controlTypes.ROLE_TREEVIEWITEM,
oleacc.ROLE_SYSTEM_OUTLINEBUTTON:controlTypes.ROLE_TREEVIEWITEM,
oleacc.ROLE_SYSTEM_PAGETAB:controlTypes.ROLE_TAB,
oleacc.ROLE_SYSTEM_PAGETABLIST:controlTypes.ROLE_TABCONTROL,
oleacc.ROLE_SYSTEM_SLIDER:controlTypes.ROLE_SLIDER,
oleacc.ROLE_SYSTEM_PROGRESSBAR:controlTypes.ROLE_PROGRESSBAR,
oleacc.ROLE_SYSTEM_SCROLLBAR:controlTypes.ROLE_SCROLLBAR,
oleacc.ROLE_SYSTEM_STATUSBAR:controlTypes.ROLE_STATUSBAR,
oleacc.ROLE_SYSTEM_TABLE:controlTypes.ROLE_TABLE,
oleacc.ROLE_SYSTEM_CELL:controlTypes.ROLE_TABLECELL,
oleacc.ROLE_SYSTEM_COLUMN:controlTypes.ROLE_TABLECOLUMN,
oleacc.ROLE_SYSTEM_ROW:controlTypes.ROLE_TABLEROW,
oleacc.ROLE_SYSTEM_TOOLBAR:controlTypes.ROLE_TOOLBAR,
oleacc.ROLE_SYSTEM_COLUMNHEADER:controlTypes.ROLE_TABLECOLUMNHEADER,
oleacc.ROLE_SYSTEM_ROWHEADER:controlTypes.ROLE_TABLEROWHEADER,
oleacc.ROLE_SYSTEM_SPLITBUTTON:controlTypes.ROLE_SPLITBUTTON,
oleacc.ROLE_SYSTEM_BUTTONDROPDOWN:controlTypes.ROLE_DROPDOWNBUTTON,
oleacc.ROLE_SYSTEM_SEPARATOR:controlTypes.ROLE_SEPARATOR,
oleacc.ROLE_SYSTEM_DOCUMENT:controlTypes.ROLE_DOCUMENT,
oleacc.ROLE_SYSTEM_ANIMATION:controlTypes.ROLE_ANIMATION,
oleacc.ROLE_SYSTEM_APPLICATION:controlTypes.ROLE_APPLICATION,
oleacc.ROLE_SYSTEM_GROUPING:controlTypes.ROLE_GROUPING,
oleacc.ROLE_SYSTEM_PROPERTYPAGE:controlTypes.ROLE_PROPERTYPAGE,
oleacc.ROLE_SYSTEM_ALERT:controlTypes.ROLE_ALERT,
oleacc.ROLE_SYSTEM_BORDER:controlTypes.ROLE_BORDER,
oleacc.ROLE_SYSTEM_BUTTONDROPDOWNGRID:controlTypes.ROLE_DROPDOWNBUTTONGRID,
oleacc.ROLE_SYSTEM_CARET:controlTypes.ROLE_CARET,
oleacc.ROLE_SYSTEM_CHARACTER:controlTypes.ROLE_CHARACTER,
oleacc.ROLE_SYSTEM_CHART:controlTypes.ROLE_CHART,
oleacc.ROLE_SYSTEM_CURSOR:controlTypes.ROLE_CURSOR,
oleacc.ROLE_SYSTEM_DIAGRAM:controlTypes.ROLE_DIAGRAM,
oleacc.ROLE_SYSTEM_DIAL:controlTypes.ROLE_DIAL,
oleacc.ROLE_SYSTEM_DROPLIST:controlTypes.ROLE_DROPLIST,
oleacc.ROLE_SYSTEM_BUTTONMENU:controlTypes.ROLE_MENUBUTTON,
oleacc.ROLE_SYSTEM_EQUATION:controlTypes.ROLE_MATH,
oleacc.ROLE_SYSTEM_GRIP:controlTypes.ROLE_GRIP,
oleacc.ROLE_SYSTEM_HOTKEYFIELD:controlTypes.ROLE_HOTKEYFIELD,
oleacc.ROLE_SYSTEM_INDICATOR:controlTypes.ROLE_INDICATOR,
oleacc.ROLE_SYSTEM_SPINBUTTON:controlTypes.ROLE_SPINBUTTON,
oleacc.ROLE_SYSTEM_SOUND:controlTypes.ROLE_SOUND,
oleacc.ROLE_SYSTEM_WHITESPACE:controlTypes.ROLE_WHITESPACE,
oleacc.ROLE_SYSTEM_IPADDRESS:controlTypes.ROLE_IPADDRESS,
oleacc.ROLE_SYSTEM_OUTLINEBUTTON:controlTypes.ROLE_TREEVIEWBUTTON,
oleacc.ROLE_SYSTEM_CLOCK:controlTypes.ROLE_CLOCK,
#IAccessible2 roles
IA2_ROLE_UNKNOWN:controlTypes.ROLE_UNKNOWN,
IA2_ROLE_CANVAS:controlTypes.ROLE_CANVAS,
IA2_ROLE_CAPTION:controlTypes.ROLE_CAPTION,
IA2_ROLE_CHECK_MENU_ITEM:controlTypes.ROLE_CHECKMENUITEM,
IA2_ROLE_COLOR_CHOOSER:controlTypes.ROLE_COLORCHOOSER,
IA2_ROLE_DATE_EDITOR:controlTypes.ROLE_DATEEDITOR,
IA2_ROLE_DESKTOP_ICON:controlTypes.ROLE_DESKTOPICON,
IA2_ROLE_DESKTOP_PANE:controlTypes.ROLE_DESKTOPPANE,
IA2_ROLE_DIRECTORY_PANE:controlTypes.ROLE_DIRECTORYPANE,
IA2_ROLE_EDITBAR:controlTypes.ROLE_EDITBAR,
IA2_ROLE_EMBEDDED_OBJECT:controlTypes.ROLE_EMBEDDEDOBJECT,
IA2_ROLE_ENDNOTE:controlTypes.ROLE_ENDNOTE,
IA2_ROLE_FILE_CHOOSER:controlTypes.ROLE_FILECHOOSER,
IA2_ROLE_FONT_CHOOSER:controlTypes.ROLE_FONTCHOOSER,
IA2_ROLE_FOOTER:controlTypes.ROLE_FOOTER,
IA2_ROLE_FOOTNOTE:controlTypes.ROLE_FOOTNOTE,
IA2_ROLE_FORM:controlTypes.ROLE_FORM,
IA2_ROLE_FRAME:controlTypes.ROLE_FRAME,
IA2_ROLE_GLASS_PANE:controlTypes.ROLE_GLASSPANE,
IA2_ROLE_HEADER:controlTypes.ROLE_HEADER,
IA2_ROLE_HEADING:controlTypes.ROLE_HEADING,
IA2_ROLE_ICON:controlTypes.ROLE_ICON,
IA2_ROLE_IMAGE_MAP:controlTypes.ROLE_IMAGEMAP,
IA2_ROLE_INPUT_METHOD_WINDOW:controlTypes.ROLE_INPUTWINDOW,
IA2_ROLE_INTERNAL_FRAME:controlTypes.ROLE_INTERNALFRAME,
IA2_ROLE_LABEL:controlTypes.ROLE_LABEL,
IA2_ROLE_LAYERED_PANE:controlTypes.ROLE_LAYEREDPANE,
IA2_ROLE_NOTE:controlTypes.ROLE_NOTE,
IA2_ROLE_OPTION_PANE:controlTypes.ROLE_OPTIONPANE,
IA2_ROLE_PAGE:controlTypes.ROLE_PAGE,
IA2_ROLE_PARAGRAPH:controlTypes.ROLE_PARAGRAPH,
IA2_ROLE_RADIO_MENU_ITEM:controlTypes.ROLE_RADIOMENUITEM,
IA2_ROLE_REDUNDANT_OBJECT:controlTypes.ROLE_REDUNDANTOBJECT,
IA2_ROLE_ROOT_PANE:controlTypes.ROLE_ROOTPANE,
IA2_ROLE_RULER:controlTypes.ROLE_RULER,
IA2_ROLE_SCROLL_PANE:controlTypes.ROLE_SCROLLPANE,
IA2_ROLE_SECTION:controlTypes.ROLE_SECTION,
IA2_ROLE_SHAPE:controlTypes.ROLE_SHAPE,
IA2_ROLE_SPLIT_PANE:controlTypes.ROLE_SPLITPANE,
IA2_ROLE_TEAR_OFF_MENU:controlTypes.ROLE_TEAROFFMENU,
IA2_ROLE_TERMINAL:controlTypes.ROLE_TERMINAL,
IA2_ROLE_TEXT_FRAME:controlTypes.ROLE_TEXTFRAME,
IA2_ROLE_TOGGLE_BUTTON:controlTypes.ROLE_TOGGLEBUTTON,
IA2_ROLE_VIEW_PORT:controlTypes.ROLE_VIEWPORT,
#some common string roles
"frame":controlTypes.ROLE_FRAME,
"iframe":controlTypes.ROLE_INTERNALFRAME,
"page":controlTypes.ROLE_PAGE,
"form":controlTypes.ROLE_FORM,
"div":controlTypes.ROLE_SECTION,
"li":controlTypes.ROLE_LISTITEM,
"ul":controlTypes.ROLE_LIST,
"tbody":controlTypes.ROLE_TABLEBODY,
"browser":controlTypes.ROLE_WINDOW,
"h1":controlTypes.ROLE_HEADING1,
"h2":controlTypes.ROLE_HEADING2,
"h3":controlTypes.ROLE_HEADING3,
"h4":controlTypes.ROLE_HEADING4,
"h5":controlTypes.ROLE_HEADING5,
"h6":controlTypes.ROLE_HEADING6,
"p":controlTypes.ROLE_PARAGRAPH,
"hbox":controlTypes.ROLE_BOX,
"embed":controlTypes.ROLE_EMBEDDEDOBJECT,
"object":controlTypes.ROLE_EMBEDDEDOBJECT,
"applet":controlTypes.ROLE_EMBEDDEDOBJECT,
}
IAccessibleStatesToNVDAStates={
oleacc.STATE_SYSTEM_TRAVERSED:controlTypes.STATE_VISITED,
oleacc.STATE_SYSTEM_UNAVAILABLE:controlTypes.STATE_UNAVAILABLE,
oleacc.STATE_SYSTEM_FOCUSED:controlTypes.STATE_FOCUSED,
oleacc.STATE_SYSTEM_SELECTED:controlTypes.STATE_SELECTED,
oleacc.STATE_SYSTEM_BUSY:controlTypes.STATE_BUSY,
oleacc.STATE_SYSTEM_PRESSED:controlTypes.STATE_PRESSED,
oleacc.STATE_SYSTEM_CHECKED:controlTypes.STATE_CHECKED,
oleacc.STATE_SYSTEM_MIXED:controlTypes.STATE_HALFCHECKED,
oleacc.STATE_SYSTEM_READONLY:controlTypes.STATE_READONLY,
oleacc.STATE_SYSTEM_EXPANDED:controlTypes.STATE_EXPANDED,
oleacc.STATE_SYSTEM_COLLAPSED:controlTypes.STATE_COLLAPSED,
oleacc.STATE_SYSTEM_OFFSCREEN:controlTypes.STATE_OFFSCREEN,
oleacc.STATE_SYSTEM_INVISIBLE:controlTypes.STATE_INVISIBLE,
oleacc.STATE_SYSTEM_TRAVERSED:controlTypes.STATE_VISITED,
oleacc.STATE_SYSTEM_LINKED:controlTypes.STATE_LINKED,
oleacc.STATE_SYSTEM_HASPOPUP:controlTypes.STATE_HASPOPUP,
oleacc.STATE_SYSTEM_PROTECTED:controlTypes.STATE_PROTECTED,
oleacc.STATE_SYSTEM_SELECTABLE:controlTypes.STATE_SELECTABLE,
oleacc.STATE_SYSTEM_FOCUSABLE:controlTypes.STATE_FOCUSABLE,
}
IAccessible2StatesToNVDAStates={
IA2_STATE_REQUIRED:controlTypes.STATE_REQUIRED,
IA2_STATE_DEFUNCT:controlTypes.STATE_DEFUNCT,
#IA2_STATE_STALE:controlTypes.STATE_DEFUNCT,
IA2_STATE_INVALID_ENTRY:controlTypes.STATE_INVALID_ENTRY,
IA2_STATE_MODAL:controlTypes.STATE_MODAL,
IA2_STATE_SUPPORTS_AUTOCOMPLETION:controlTypes.STATE_AUTOCOMPLETE,
IA2_STATE_MULTI_LINE:controlTypes.STATE_MULTILINE,
IA2_STATE_ICONIFIED:controlTypes.STATE_ICONIFIED,
IA2_STATE_EDITABLE:controlTypes.STATE_EDITABLE,
IA2_STATE_PINNED:controlTypes.STATE_PINNED,
}
#A list to store handles received from setWinEventHook, for use with unHookWinEvent
winEventHookIDs=[]
def normalizeIAccessible(pacc,childID=0):
if not isinstance(pacc,IAccessible):
try:
pacc=pacc.QueryInterface(IAccessible)
except COMError:
raise RuntimeError("%s Not an IAccessible"%pacc)
# #2558: IAccessible2 doesn't support simple children.
# Therefore, it doesn't make sense to use IA2 if the child ID is non-0.
if childID==0 and not isinstance(pacc,IAccessible2):
try:
s=pacc.QueryInterface(IServiceProvider)
pacc2=s.QueryService(IAccessible._iid_,IAccessible2)
if not pacc2:
# QueryService should fail if IA2 is not supported, but some applications such as AIM 7 misbehave and return a null COM pointer.
# Treat this as if QueryService failed.
raise ValueError
pacc=pacc2
except:
pass
return pacc
def accessibleObjectFromEvent(window,objectID,childID):
try:
pacc,childID=oleacc.AccessibleObjectFromEvent(window,objectID,childID)
except Exception as e:
log.debugWarning("oleacc.AccessibleObjectFromEvent with window %s, objectID %s and childID %s: %s"%(window,objectID,childID,e))
return None
return (normalizeIAccessible(pacc,childID),childID)
def accessibleObjectFromPoint(x,y):
try:
pacc, child = oleacc.AccessibleObjectFromPoint(x, y)
except:
return None
return (normalizeIAccessible(pacc,child),child)
def windowFromAccessibleObject(ia):
try:
return oleacc.WindowFromAccessibleObject(ia)
except:
return 0
def accessibleChildren(ia,startIndex,numChildren):
# #4091: AccessibleChildren can throw WindowsError (blocked by callee) e.g. Outlook 2010 Email setup and new profiles dialogs
try:
rawChildren=oleacc.AccessibleChildren(ia,startIndex,numChildren)
except (WindowsError,COMError):
log.debugWarning("AccessibleChildren failed",exc_info=True)
return []
children=[]
for child in rawChildren:
if child is None:
# This is a bug in the server.
# Filtering these out here makes life easier for the caller.
continue
elif isinstance(child,comtypes.client.lazybind.Dispatch) or isinstance(child,comtypes.client.dynamic._Dispatch) or isinstance(child,IUnknown):
child=(normalizeIAccessible(child),0)
elif isinstance(child,int):
child=(ia,child)
children.append(child)
return children
def accFocus(ia):
try:
res=ia.accFocus
if isinstance(res,comtypes.client.lazybind.Dispatch) or isinstance(res,comtypes.client.dynamic._Dispatch) or isinstance(res,IUnknown):
new_ia=normalizeIAccessible(res)
new_child=0
elif res==0:
# #3005: Don't call accChild for CHILDID_SELF.
new_ia=ia
new_child=res
elif isinstance(res,int):
# accFocus can return a child ID even when there is actually an IAccessible for that child; e.g. Lotus Symphony.
try:
new_ia=ia.accChild(res)
except:
new_ia=None
if new_ia:
new_ia=normalizeIAccessible(new_ia)
new_child=0
else:
new_ia=ia
new_child=res
else:
return None
return (new_ia,new_child)
except:
return None
def accHitTest(ia,x,y):
try:
res=ia.accHitTest(x,y)
except COMError:
return None
if isinstance(res,comtypes.client.lazybind.Dispatch) or isinstance(res,comtypes.client.dynamic._Dispatch) or isinstance(res,IUnknown):
return accHitTest(normalizeIAccessible(res),x,y),0
elif isinstance(res,int):
return ia,res
return None
def accChild(ia,child):
try:
res=ia.accChild(child)
if not res:
return (ia,child)
elif isinstance(res,comtypes.client.lazybind.Dispatch) or isinstance(res,comtypes.client.dynamic._Dispatch) or isinstance(res,IUnknown):
return normalizeIAccessible(res),0
except:
pass
return None
def accParent(ia,child):
try:
if not child:
res=ia.accParent
if isinstance(res,comtypes.client.lazybind.Dispatch) or isinstance(res,comtypes.client.dynamic._Dispatch) or isinstance(res,IUnknown):
new_ia=normalizeIAccessible(res)
new_child=0
else:
raise ValueError("no IAccessible interface")
else:
new_ia=ia
new_child=0
return (new_ia,new_child)
except:
return None
def accNavigate(pacc,childID,direction):
try:
res=pacc.accNavigate(direction,childID)
except COMError:
res=None
if not res:
return None
elif isinstance(res,int):
if childID==0 and oleacc.NAVDIR_UP<=direction<=oleacc.NAVDIR_PREVIOUS:
parentRes=accParent(pacc,0)
if not parentRes:
return None
pacc=parentRes[0]
return pacc,res
elif isinstance(res,comtypes.client.lazybind.Dispatch) or isinstance(res,comtypes.client.dynamic._Dispatch) or isinstance(res,IUnknown):
return normalizeIAccessible(res,0),0
else:
log.debugWarning("Unknown IAccessible type: %s"%res,stack_info=True)
return None
winEventIDsToNVDAEventNames={
winUser.EVENT_SYSTEM_DESKTOPSWITCH:"desktopSwitch",
winUser.EVENT_SYSTEM_FOREGROUND:"gainFocus",
winUser.EVENT_SYSTEM_ALERT:"alert",
winUser.EVENT_SYSTEM_MENUSTART:"menuStart",
winUser.EVENT_SYSTEM_MENUEND:"menuEnd",
winUser.EVENT_SYSTEM_MENUPOPUPSTART:"menuStart",
winUser.EVENT_SYSTEM_MENUPOPUPEND:"menuEnd",
winUser.EVENT_SYSTEM_SCROLLINGSTART:"scrollingStart",
# We don't need switchStart.
winUser.EVENT_SYSTEM_SWITCHEND:"switchEnd",
winUser.EVENT_OBJECT_FOCUS:"gainFocus",
winUser.EVENT_OBJECT_SHOW:"show",
winUser.EVENT_OBJECT_HIDE:"hide",
winUser.EVENT_OBJECT_DESTROY:"destroy",
winUser.EVENT_OBJECT_DESCRIPTIONCHANGE:"descriptionChange",
winUser.EVENT_OBJECT_LOCATIONCHANGE:"locationChange",
winUser.EVENT_OBJECT_NAMECHANGE:"nameChange",
winUser.EVENT_OBJECT_REORDER:"reorder",
winUser.EVENT_OBJECT_SELECTION:"selection",
winUser.EVENT_OBJECT_SELECTIONADD:"selectionAdd",
winUser.EVENT_OBJECT_SELECTIONREMOVE:"selectionRemove",
winUser.EVENT_OBJECT_SELECTIONWITHIN:"selectionWithIn",
winUser.EVENT_OBJECT_STATECHANGE:"stateChange",
winUser.EVENT_OBJECT_VALUECHANGE:"valueChange",
IA2_EVENT_TEXT_CARET_MOVED:"caret",
IA2_EVENT_DOCUMENT_LOAD_COMPLETE:"documentLoadComplete",
IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED:"IA2AttributeChange",
IA2_EVENT_PAGE_CHANGED:"pageChange",
}
def winEventToNVDAEvent(eventID,window,objectID,childID,useCache=True):
"""Tries to convert a win event ID to an NVDA event name, and instanciate or fetch an NVDAObject for the win event parameters.
@param eventID: the win event ID (type)
@type eventID: integer
@param window: the win event's window handle
@type window: integer
@param objectID: the win event's object ID
@type objectID: integer
@param childID: the win event's childID
@type childID: the win event's childID
@param useCache: C{True} to use the L{liveNVDAObjectTable} cache when retrieving an NVDAObject, C{False} if the cache should not be used.
@type useCache: boolean
@returns: the NVDA event name and the NVDAObject the event is for
@rtype: tuple of string and L{NVDAObjects.IAccessible.IAccessible}
"""
NVDAEventName=winEventIDsToNVDAEventNames.get(eventID,None)
if not NVDAEventName:
return None
#Ignore any events with invalid window handles
if not window or not winUser.isWindow(window):
return None
#Make sure this window does not have a ghost window if possible
if NVDAObjects.window.GhostWindowFromHungWindow and NVDAObjects.window.GhostWindowFromHungWindow(window):
return None
#We do not support MSAA object proxied from native UIA
if UIAHandler.handler and UIAHandler.handler.isUIAWindow(window):
return None
obj=None
if useCache:
#See if we already know an object by this win event info
obj=liveNVDAObjectTable.get((window,objectID,childID),None)
#If we don't yet have the object, then actually instanciate it.
if not obj:
obj=NVDAObjects.IAccessible.getNVDAObjectFromEvent(window,objectID,childID)
#At this point if we don't have an object then we can't do any more
if not obj:
return None
#SDM MSAA objects sometimes don't contain enough information to be useful
#Sometimes there is a real window that does, so try to get the SDMChild property on the NVDAObject, and if successull use that as obj instead.
if 'bosa_sdm' in obj.windowClassName:
SDMChild=getattr(obj,'SDMChild',None)
if SDMChild: obj=SDMChild
return (NVDAEventName,obj)
def winEventCallback(handle,eventID,window,objectID,childID,threadID,timestamp):
try:
#Ignore all object IDs from alert onwards (sound, nativeom etc) as we don't support them
if objectID<=winUser.OBJID_ALERT:
return
#Ignore all locationChange events except ones for the caret
if eventID==winUser.EVENT_OBJECT_LOCATIONCHANGE and objectID!=winUser.OBJID_CARET:
return
if eventID==winUser.EVENT_OBJECT_DESTROY:
processDestroyWinEvent(window,objectID,childID)
return
#Change window objIDs to client objIDs for better reporting of objects
if (objectID==0) and (childID==0):
objectID=winUser.OBJID_CLIENT
#Ignore events with invalid window handles
isWindow = winUser.isWindow(window) if window else 0
if window==0 or (not isWindow and eventID in (winUser.EVENT_SYSTEM_SWITCHSTART,winUser.EVENT_SYSTEM_SWITCHEND,winUser.EVENT_SYSTEM_MENUEND,winUser.EVENT_SYSTEM_MENUPOPUPEND)):
window=winUser.getDesktopWindow()
elif not isWindow:
return
if childID<0:
tempWindow=window
while tempWindow and not winUser.getWindowStyle(tempWindow)&winUser.WS_POPUP and winUser.getClassName(tempWindow)=="MozillaWindowClass":
tempWindow=winUser.getAncestor(tempWindow,winUser.GA_PARENT)
if tempWindow and winUser.getClassName(tempWindow).startswith('Mozilla'):
window=tempWindow
windowClassName=winUser.getClassName(window)
#At the moment we can't handle show, hide or reorder events on Mozilla Firefox Location bar,as there are just too many of them
#Ignore show, hide and reorder on MozillaDropShadowWindowClass windows.
if windowClassName.startswith('Mozilla') and eventID in (winUser.EVENT_OBJECT_SHOW,winUser.EVENT_OBJECT_HIDE,winUser.EVENT_OBJECT_REORDER) and childID<0:
#Mozilla Gecko can sometimes fire win events on a catch-all window which isn't really the real window
#Move up the ancestry to find the real mozilla Window and use that
if winUser.getClassName(window)=='MozillaDropShadowWindowClass':
return
if eventID==winUser.EVENT_SYSTEM_FOREGROUND:
#We never want to see foreground events for the Program Manager or Shell (task bar)
if windowClassName in ("Progman","Shell_TrayWnd"):
return
# #3831: Event handling can be deferred if Windows takes a while to change the foreground window.
# See pumpAll for details.
global _deferUntilForegroundWindow,_foregroundDefers
_deferUntilForegroundWindow=window
_foregroundDefers=0
if windowClassName=="MSNHiddenWindowClass":
# HACK: Events get fired by this window in Windows Live Messenger 2009 when it starts.
# If we send a WM_NULL to this window at this point (which happens in accessibleObjectFromEvent), Messenger will silently exit (#677).
# Therefore, completely ignore these events, which is useless to us anyway.
return
if winEventLimiter.addEvent(eventID,window,objectID,childID,threadID):
core.requestPump()
except:
log.error("winEventCallback", exc_info=True)
def processGenericWinEvent(eventID,window,objectID,childID):
"""Converts the win event to an NVDA event,
Checks to see if this NVDAObject equals the current focus.
If all goes well, then the event is queued and we return True
@param eventID: a win event ID (type)
@type eventID: integer
@param window: a win event's window handle
@type window: integer
@param objectID: a win event's object ID
@type objectID: integer
@param childID: a win event's child ID
@type childID: integer
@returns: True if the event was processed, False otherwise.
@rtype: boolean
"""
#Notify appModuleHandler of this new window
appModuleHandler.update(winUser.getWindowThreadProcessID(window)[0])
#Handle particular events for the special MSAA caret object just as if they were for the focus object
focus=eventHandler.lastQueuedFocusObject
if focus and objectID==winUser.OBJID_CARET and eventID in (winUser.EVENT_OBJECT_LOCATIONCHANGE,winUser.EVENT_OBJECT_SHOW):
NVDAEvent=("caret",focus)
else:
NVDAEvent=winEventToNVDAEvent(eventID,window,objectID,childID)
if not NVDAEvent:
return False
if NVDAEvent[0]=="nameChange" and objectID==winUser.OBJID_CURSOR:
mouseHandler.updateMouseShape(NVDAEvent[1].name)
return
if NVDAEvent[1]==focus:
NVDAEvent=(NVDAEvent[0],focus)
eventHandler.queueEvent(*NVDAEvent)
return True
def processFocusWinEvent(window,objectID,childID,force=False):
"""checks to see if the focus win event is not the same as the existing focus,
then converts the win event to an NVDA event (instanciating an NVDA Object) then calls processFocusNVDAEvent. If all is ok it returns True.
@type window: integer
@param objectID: a win event's object ID
@type objectID: integer
@param childID: a win event's child ID
@type childID: integer
@param force: If True, the shouldAllowIAccessibleFocusEvent property of the object is ignored.
@type force: boolean
@returns: True if the focus is valid and was handled, False otherwise.
@rtype: boolean
"""
windowClassName=winUser.getClassName(window)
# Generally, we must ignore focus on child windows of SDM windows as we only want the SDM MSAA events.
# However, we don't want to ignore focus if the child ID isn't 0,
# as this is a child control and the SDM MSAA events don't handle child controls.
if childID==0 and not windowClassName.startswith('bosa_sdm') and winUser.getClassName(winUser.getAncestor(window,winUser.GA_PARENT)).startswith('bosa_sdm'):
return False
#Notify appModuleHandler of this new foreground window
appModuleHandler.update(winUser.getWindowThreadProcessID(window)[0])
#If Java access bridge is running, and this is a java window, then pass it to java and forget about it
if childID==0 and objectID==winUser.OBJID_CLIENT and JABHandler.isRunning and JABHandler.isJavaWindow(window):
JABHandler.event_enterJavaWindow(window)
return True
#Convert the win event to an NVDA event
NVDAEvent=winEventToNVDAEvent(winUser.EVENT_OBJECT_FOCUS,window,objectID,childID,useCache=False)
if not NVDAEvent:
return False
eventName,obj=NVDAEvent
if (childID==0 and obj.IAccessibleRole==oleacc.ROLE_SYSTEM_LIST) or (objectID==winUser.OBJID_CLIENT and "SysListView32" in obj.windowClassName):
# Some controls incorrectly fire focus on child ID 0, even when there is a child with focus.
try:
realChildID=obj.IAccessibleObject.accFocus
except:
realChildID=None
if isinstance(realChildID,int) and realChildID>0 and realChildID!=childID:
realObj=NVDAObjects.IAccessible.IAccessible(IAccessibleObject=obj.IAccessibleObject,IAccessibleChildID=realChildID,event_windowHandle=window,event_objectID=objectID,event_childID=realChildID)
if realObj:
obj=realObj
return processFocusNVDAEvent(obj,force=force)
def processFocusNVDAEvent(obj,force=False):
"""Processes a focus NVDA event.
If the focus event is valid, it is queued.
@param obj: the NVDAObject the focus event is for
@type obj: L{NVDAObjects.NVDAObject}
@param force: If True, the shouldAllowIAccessibleFocusEvent property of the object is ignored.
@type force: boolean
@return: C{True} if the focus event is valid and was queued, C{False} otherwise.
@rtype: boolean
"""
if not force and isinstance(obj,NVDAObjects.IAccessible.IAccessible):
focus=eventHandler.lastQueuedFocusObject
if isinstance(focus,NVDAObjects.IAccessible.IAccessible) and focus.isDuplicateIAccessibleEvent(obj):
return True
if not obj.shouldAllowIAccessibleFocusEvent:
return False
eventHandler.queueEvent('gainFocus',obj)
return True
class SecureDesktopNVDAObject(NVDAObjects.window.Desktop):
def findOverlayClasses(self,clsList):
clsList.append(SecureDesktopNVDAObject)
return clsList
def _get_name(self):
# Translators: Message to indicate User Account Control (UAC) or other secure desktop screen is active.
return _("Secure Desktop")
def _get_role(self):
return controlTypes.ROLE_PANE
def event_gainFocus(self):
super(SecureDesktopNVDAObject, self).event_gainFocus()
# After handling the focus, NVDA should sleep while the secure desktop is active.
self.sleepMode = self.SLEEP_FULL
def processDesktopSwitchWinEvent(window,objectID,childID):
hDesk=windll.user32.OpenInputDesktop(0, False, 0)
if hDesk!=0:
windll.user32.CloseDesktop(hDesk)
core.callLater(200, _correctFocus)
else:
# Switching to a secure desktop.
# We don't receive key up events for any keys down before switching to a secure desktop,
# so clear our recorded modifiers.
keyboardHandler.currentModifiers.clear()
obj=SecureDesktopNVDAObject(windowHandle=window)
eventHandler.executeEvent("gainFocus",obj)
def _correctFocus():
eventHandler.queueEvent("gainFocus",api.getDesktopObject().objectWithFocus())
def processForegroundWinEvent(window,objectID,childID):
"""checks to see if the foreground win event is not the same as the existing focus or any of its parents,
then converts the win event to an NVDA event (instanciating an NVDA Object) and then checks the NVDAObject against the existing focus object.
If all is ok it queues the foreground event to NVDA and returns True.
@param window: a win event's window handle
@type window: integer
@param objectID: a win event's object ID
@type objectID: integer
@param childID: a win event's child ID
@type childID: integer
@returns: True if the foreground was processed, False otherwise.
@rtype: boolean
"""
#Ignore foreground events on windows that aren't the current foreground window
if window!=winUser.getForegroundWindow():
return False
# If there is a pending gainFocus, it will handle the foreground object.
oldFocus=eventHandler.lastQueuedFocusObject
#If this foreground win event's window is an ancestor of the existing focus's window, then ignore it
if isinstance(oldFocus,NVDAObjects.window.Window) and winUser.isDescendantWindow(window,oldFocus.windowHandle):
return False
#If the existing focus has the same win event params as these, then ignore this event
if isinstance(oldFocus,NVDAObjects.IAccessible.IAccessible) and window==oldFocus.event_windowHandle and objectID==oldFocus.event_objectID and childID==oldFocus.event_childID:
return False
#Notify appModuleHandler of this new foreground window
appModuleHandler.update(winUser.getWindowThreadProcessID(window)[0])
#If Java access bridge is running, and this is a java window, then pass it to java and forget about it
if JABHandler.isRunning and JABHandler.isJavaWindow(window):
JABHandler.event_enterJavaWindow(window)
return True
#Convert the win event to an NVDA event
NVDAEvent=winEventToNVDAEvent(winUser.EVENT_SYSTEM_FOREGROUND,window,objectID,childID,useCache=False)
if not NVDAEvent:
return False
eventHandler.queueEvent(*NVDAEvent)
return True
def processShowWinEvent(window,objectID,childID):
# eventHandler.shouldAcceptEvent only accepts show events for a few specific cases.
# Narrow this further to only accept events for clients or custom objects.
if objectID==winUser.OBJID_CLIENT or objectID>0:
NVDAEvent=winEventToNVDAEvent(winUser.EVENT_OBJECT_SHOW,window,objectID,childID)
if NVDAEvent:
eventHandler.queueEvent(*NVDAEvent)
def processDestroyWinEvent(window,objectID,childID):
"""Process a destroy win event.
This removes the object associated with the event parameters from L{liveNVDAObjectTable} if such an object exists.
"""
try:
del liveNVDAObjectTable[(window,objectID,childID)]
except KeyError:
pass
#Specific support for input method MSAA candidate lists.
#When their window is destroyed we must correct focus to its parent - which could be a composition string
# so can't use generic focus correction. (#2695)
focus=api.getFocusObject()
from NVDAObjects.IAccessible.mscandui import BaseCandidateItem
if objectID==0 and childID==0 and isinstance(focus,BaseCandidateItem) and window==focus.windowHandle and not eventHandler.isPendingEvents("gainFocus"):
obj=focus.container
if obj:
eventHandler.queueEvent("gainFocus",obj)
def processMenuStartWinEvent(eventID, window, objectID, childID, validFocus):
"""Process a menuStart win event.
@postcondition: Focus will be directed to the menu if appropriate.
"""
if validFocus:
lastFocus=eventHandler.lastQueuedFocusObject
if isinstance(lastFocus,NVDAObjects.IAccessible.IAccessible) and lastFocus.IAccessibleRole in (oleacc.ROLE_SYSTEM_MENUPOPUP, oleacc.ROLE_SYSTEM_MENUITEM):
# Focus has already been set to a menu or menu item, so we don't need to handle the menuStart.
return
NVDAEvent = winEventToNVDAEvent(eventID, window, objectID, childID)
if not NVDAEvent:
return
eventName, obj = NVDAEvent
if obj.IAccessibleRole != oleacc.ROLE_SYSTEM_MENUPOPUP:
# menuStart on anything other than a menu is silly.
return
processFocusNVDAEvent(obj, force=True)
def processFakeFocusWinEvent(eventID, window, objectID, childID):
"""Process a fake focus win event.
@postcondition: The focus will be found and an event generated for it if appropriate.
"""
# A suitable event for faking the focus has been received with no focus event, so we probably need to find the focus and fake it.
# However, it is possible that the focus event has simply been delayed, so wait a bit and only do it if the focus hasn't changed yet.
core.callLater(50, _fakeFocus, api.getFocusObject())
def _fakeFocus(oldFocus):
if oldFocus is not api.getFocusObject():
# The focus has changed - no need to fake it.
return
focus = api.getDesktopObject().objectWithFocus()
if not focus:
return
processFocusNVDAEvent(focus)
#Register internal object event with IAccessible
cWinEventCallback=WINFUNCTYPE(None,c_int,c_int,c_int,c_int,c_int,c_int,c_int)(winEventCallback)
accPropServices=None
def initialize():
global accPropServices
try:
accPropServices=comtypes.client.CreateObject(CAccPropServices)
except (WindowsError,COMError) as e:
log.debugWarning("AccPropServices is not available: %s"%e)
for eventType in winEventIDsToNVDAEventNames.keys():
hookID=winUser.setWinEventHook(eventType,eventType,0,cWinEventCallback,0,0,0)
if hookID:
winEventHookIDs.append(hookID)
else:
log.error("initialize: could not register callback for event %s (%s)"%(eventType,winEventIDsToNVDAEventNames[eventType]))
def pumpAll():
global _deferUntilForegroundWindow,_foregroundDefers
if _deferUntilForegroundWindow:
# #3831: Sometimes, a foreground event is fired,
# but GetForegroundWindow() takes a short while to return this new foreground.
if _foregroundDefers=groupLeft and (left+width)<=(groupLeft+groupWidth) and top>=groupTop and (top+height)<=(groupTop+groupHeight):
return groupObj
prevWindow=winUser.getPreviousWindow(prevWindow)
def getRecursiveTextFromIAccessibleTextObject(obj,startOffset=0,endOffset=-1):
if not isinstance(obj,IAccessibleText):
try:
textObject=obj.QueryInterface(IAccessibleText)
except:
textObject=None
else:
textObject=obj
if not isinstance(obj,IAccessible):
try:
accObject=obj.QueryInterface(IAccessible)
except:
return ""
else:
accObject=obj
try:
text=textObject.text(startOffset,endOffset)
except:
text=None
if not text or text.isspace():
try:
name=accObject.accName(0)
except:
name=None
try:
value=accObject.accValue(0)
except:
value=None
try:
description=accObject.accDescription(0)
except:
description=None
return " ".join([x for x in [name,value,description] if x and not x.isspace()])
try:
hypertextObject=accObject.QueryInterface(IAccessibleHypertext)
except:
return text
textList=[]
for i in xrange(len(text)):
t=text[i]
if ord(t)==0xFFFC:
try:
childTextObject=hypertextObject.hyperlink(hypertextObject.hyperlinkIndex(i+startOffset)).QueryInterface(IAccessible)
t=" %s "%getRecursiveTextFromIAccessibleTextObject(childTextObject)
except:
pass
textList.append(t)
return "".join(textList).replace(' ',' ')
def splitIA2Attribs(attribsString):
"""Split an IAccessible2 attributes string into a dict of attribute keys and values.
An invalid attributes string does not cause an error, but strange results may be returned.
Subattributes are handled. Subattribute keys and values are placed into a dict which becomes the value of the attribute.
@param attribsString: The IAccessible2 attributes string to convert.
@type attribsString: str
@return: A dict of the attribute keys and values, where values are strings or dicts.
@rtype: {str: str or {str: str}}
"""
attribsDict = {}
tmp = ""
key = ""
subkey = ""
subattr = {}
inEscape = False
for char in attribsString:
if inEscape:
tmp += char
inEscape = False
elif char == "\\":
inEscape = True
elif char == ":":
# We're about to move on to the value, so save the key and clear tmp.
key = tmp
tmp = ""
elif char == "=":
# This is a subattribute.
# Save the subattribute key and clear tmp, ready for the value.
subkey = tmp
tmp = ""
elif char == ",":
# We're about to move on to a new subattribute.
# Add this subattribute key/value pair to the dict.
if subkey:
subattr[subkey] = tmp
subkey = ""
tmp = ""
elif char == ";":
# We're about to move on to a new attribute.
if subkey:
# Add the last subattribute key/value pair to the dict.
subattr[subkey] = tmp
subkey = ""
if subattr:
# This attribute had subattributes.
# Add the key/subattribute pair to the dict.
attribsDict[key] = subattr
subattr = {}
elif key:
# Add this key/value pair to the dict.
attribsDict[key] = tmp
key = ""
tmp = ""
else:
tmp += char
# If there was no trailing semi-colon, we need to handle the last attribute.
if subkey:
# Add the last subattribute key/value pair to the dict.
subattr[subkey] = tmp
if subattr:
# This attribute had subattributes.
# Add the key/subattribute pair to the dict.
attribsDict[key] = subattr
elif key:
# Add this key/value pair to the dict.
attribsDict[key] = tmp
return attribsDict
def isMarshalledIAccessible(IAccessibleObject):
"""Looks at the location of the first function in the IAccessible object's vtable (IUnknown::AddRef) to see if it was implemented in oleacc.dll (its local) or ole32.dll (its marshalled)."""
if not isinstance(IAccessibleObject,IAccessible):
raise TypeError("object should be of type IAccessible, not %s"%IAccessibleObject)
buf=create_unicode_buffer(1024)
addr=POINTER(c_void_p).from_address(super(comtypes._compointer_base,IAccessibleObject).value).contents.value
handle=HANDLE()
windll.kernel32.GetModuleHandleExW(6,addr,byref(handle))
windll.kernel32.GetModuleFileNameW(handle,buf,1024)
return not buf.value.lower().endswith('oleacc.dll')