diff --git a/source/baseObject.py b/source/baseObject.py index 5819f05f34d..2605ecbdb8e 100755 --- a/source/baseObject.py +++ b/source/baseObject.py @@ -140,9 +140,12 @@ def __new__(cls, *args, **kwargs): def _getPropertyViaCache(self,getterMethod=None): if not getterMethod: raise ValueError("getterMethod is None") + missing=False try: val=self._propertyCache[getterMethod] except KeyError: + missing=True + if missing: val=getterMethod(self) self._propertyCache[getterMethod]=val return val diff --git a/source/comtypesMonkeyPatches.py b/source/comtypesMonkeyPatches.py index 57d84c06416..c5031c13f6c 100644 --- a/source/comtypesMonkeyPatches.py +++ b/source/comtypesMonkeyPatches.py @@ -3,6 +3,47 @@ #This file is covered by the GNU General Public License. #See the file COPYING for more details. +import ctypes +import _ctypes + +# A version of ctypes.WINFUNCTYPE +# that produces a WinFunctionType class whose instance will convert COMError into a CallCancelled exception when called as a function. +old_WINFUNCTYPE=ctypes.WINFUNCTYPE +def new_WINFUNCTYPE(restype,*argtypes,**kwargs): + cls=old_WINFUNCTYPE(restype,*argtypes,**kwargs) + class WinFunctionType(cls): + # We must manually pull the mandatory class variables from the super class, + # as the metaclass of _ctypes.CFuncPtr seems to expect these on the outermost subclass. + _argtypes_=cls._argtypes_ + _restype_=cls._restype_ + _flags_=cls._flags_ + def __call__(self,*args,**kwargs): + try: + return super().__call__(*args,**kwargs) + except _ctypes.COMError as e: + from core import CallCancelled, RPC_E_CALL_CANCELED + if e.args[0]==RPC_E_CALL_CANCELED: + # As this is a cancelled COM call, + # raise CallCancelled instead of the original COMError. + # Also raising from None gives a cleaner traceback, + # Hiding the fact we were already in an except block. + raise CallCancelled("COM call cancelled") from None + # Otherwise, just continue the original COMError exception up the stack. + raise + return WinFunctionType + +# While importing comtypes, +# Replace WINFUNCTYPE in ctypes with our own version, +# So that comtypes will use this in all its COM method calls. +# As comtypes imports WINFUNCTYPE from ctypes by name, +# We only need to replace it for the duration of importing comtypes, +# as it will then have it for ever. +ctypes.WINFUNCTYPE=new_WINFUNCTYPE +try: + import comtypes +finally: + ctypes.WINFUNCTYPE=old_WINFUNCTYPE + from logHandler import log from comtypes import COMError diff --git a/source/core.py b/source/core.py index 608fbc1dadc..c0d5a81c62d 100644 --- a/source/core.py +++ b/source/core.py @@ -7,16 +7,23 @@ """NVDA core""" -# Do this first to initialise comtypes.client.gen_dir and the comtypes.gen search path. +RPC_E_CALL_CANCELED = -2147418110 + +class CallCancelled(Exception): + """Raised when a call is cancelled. + """ + +# Apply several monky patches to comtypes +import comtypesMonkeyPatches + +# Initialise comtypes.client.gen_dir and the comtypes.gen search path +# and Append our comInterfaces directory to the comtypes.gen search path. +import comtypes import comtypes.client -# Append our comInterfaces directory to the comtypes.gen search path. import comtypes.gen import comInterfaces comtypes.gen.__path__.append(comInterfaces.__path__[0]) -#Apply several monky patches to comtypes -import comtypesMonkeyPatches - import sys import winVersion import threading diff --git a/source/logHandler.py b/source/logHandler.py index 833b444e0f7..74d3003ed71 100755 --- a/source/logHandler.py +++ b/source/logHandler.py @@ -167,8 +167,7 @@ def exception(self, msg="", exc_info=True, **kwargs): However, certain exceptions which aren't considered errors (or aren't errors that we can fix) are expected and will therefore be logged at a lower level. """ import comtypes - import watchdog - from watchdog import RPC_E_CALL_CANCELED + from core import CallCancelled, RPC_E_CALL_CANCELED if exc_info is True: exc_info = sys.exc_info() @@ -176,7 +175,7 @@ def exception(self, msg="", exc_info=True, **kwargs): if ( (isinstance(exc, WindowsError) and exc.winerror in (ERROR_INVALID_WINDOW_HANDLE, ERROR_TIMEOUT, RPC_S_SERVER_UNAVAILABLE, RPC_S_CALL_FAILED_DNE, EPT_S_NOT_REGISTERED, RPC_E_CALL_CANCELED)) or (isinstance(exc, comtypes.COMError) and (exc.hresult in (E_ACCESSDENIED, CO_E_OBJNOTCONNECTED, EVENT_E_ALL_SUBSCRIBERS_FAILED, RPC_E_CALL_REJECTED, RPC_E_CALL_CANCELED, RPC_E_DISCONNECTED) or exc.hresult & 0xFFFF == RPC_S_SERVER_UNAVAILABLE)) - or isinstance(exc, watchdog.CallCancelled) + or isinstance(exc, CallCancelled) ): level = self.DEBUGWARNING else: diff --git a/source/watchdog.py b/source/watchdog.py index b554799cf8e..bdaa45ca9d2 100644 --- a/source/watchdog.py +++ b/source/watchdog.py @@ -19,6 +19,7 @@ from logHandler import log import globalVars import core +from core import CallCancelled import NVDAHelper #settings @@ -46,10 +47,6 @@ _watcherThread=None _cancelCallEvent = None -class CallCancelled(Exception): - """Raised when a call is cancelled. - """ - def alive(): """Inform the watchdog that the core is alive. """ @@ -196,13 +193,6 @@ def sendMessageCallCanceller(frame, event, arg): raise CallCancelled sys.setprofile(sendMessageCallCanceller) -RPC_E_CALL_CANCELED = -2147418110 -_orig_COMError_init = comtypes.COMError.__init__ -def _COMError_init(self, hresult, text, details): - if hresult == RPC_E_CALL_CANCELED: - raise CallCancelled - _orig_COMError_init(self, hresult, text, details) - def initialize(): """Initialize the watchdog. """ @@ -218,8 +208,6 @@ def initialize(): "cancelCallEvent") # Handle cancelled SendMessage calls. NVDAHelper._setDllFuncPointer(NVDAHelper.localLib, "_notifySendMessageCancelled", _notifySendMessageCancelled) - # Monkey patch comtypes to specially handle cancelled COM calls. - comtypes.COMError.__init__ = _COMError_init _watcherThread=threading.Thread(target=_watcher) alive() _watcherThread.start() @@ -232,7 +220,6 @@ def terminate(): return isRunning=False oledll.ole32.CoDisableCallCancellation(None) - comtypes.COMError.__init__ = _orig_COMError_init # Wake up the watcher so it knows to finish. windll.kernel32.SetWaitableTimer(_coreDeadTimer, ctypes.byref(ctypes.wintypes.LARGE_INTEGER(0)),