Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions source/baseObject.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 41 additions & 0 deletions source/comtypesMonkeyPatches.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 12 additions & 5 deletions source/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 2 additions & 3 deletions source/logHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,16 +167,15 @@ 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()

exc = exc_info[1]
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:
Expand Down
15 changes: 1 addition & 14 deletions source/watchdog.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from logHandler import log
import globalVars
import core
from core import CallCancelled
import NVDAHelper

#settings
Expand Down Expand Up @@ -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.
"""
Expand Down Expand Up @@ -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.
"""
Expand All @@ -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()
Expand All @@ -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)),
Expand Down