diff --git a/addon/brailleDisplayDrivers/remote.py b/addon/brailleDisplayDrivers/remote.py index 8d74c48..a158a43 100644 --- a/addon/brailleDisplayDrivers/remote.py +++ b/addon/brailleDisplayDrivers/remote.py @@ -79,8 +79,6 @@ def _command_executeGesture(self, payload: bytes): log.error("Unexpected NoInputGestureAction", exc_info=True) def display(self, cells: List[int]): - #if len(cells) == 0: - # return # cells will already be padded up to numCells. arg = bytes(cells) self.writeMessage(protocol.BrailleCommand.DISPLAY, arg) diff --git a/addon/globalPlugins/rdAccess/__init__.py b/addon/globalPlugins/rdAccess/__init__.py index 2712bb5..1ebce43 100644 --- a/addon/globalPlugins/rdAccess/__init__.py +++ b/addon/globalPlugins/rdAccess/__init__.py @@ -4,7 +4,6 @@ import globalPluginHandler import addonHandler -import hwIo from . import directoryChanges, settingsPanel import typing from fnmatch import fnmatch diff --git a/addon/globalPlugins/rdAccess/secureDesktop.py b/addon/globalPlugins/rdAccess/secureDesktop.py index 102db0d..790060c 100644 --- a/addon/globalPlugins/rdAccess/secureDesktop.py +++ b/addon/globalPlugins/rdAccess/secureDesktop.py @@ -11,9 +11,7 @@ import os.path import braille import synthDriverHandler -from ctypes import WinError import weakref -from logHandler import log if typing.TYPE_CHECKING: from ...lib import ioThreadEx diff --git a/addon/lib/driver/__init__.py b/addon/lib/driver/__init__.py index 134cb72..4ab035d 100644 --- a/addon/lib/driver/__init__.py +++ b/addon/lib/driver/__init__.py @@ -20,7 +20,6 @@ from .settingsAccessor import SettingsAccessorBase import sys from baseObject import AutoPropertyObject -import time from utils.security import post_sessionLockStateChanged, isRunningOnSecureDesktop ERROR_INVALID_HANDLE = 0x6 diff --git a/addon/lib/ioThreadEx.py b/addon/lib/ioThreadEx.py index e8cba61..9d30f60 100644 --- a/addon/lib/ioThreadEx.py +++ b/addon/lib/ioThreadEx.py @@ -1,5 +1,9 @@ -from ctypes import POINTER, WINFUNCTYPE, WinError, addressof, byref, cast, windll -from ctypes.wintypes import BOOL, BOOLEAN, DWORD, HANDLE, LPVOID, LPHANDLE +# RDAccess: Remote Desktop Accessibility for NVDA +# Copyright 2023 Leonard de Ruijter +# License: GNU General Public License version 2.0 + +from ctypes import POINTER, WINFUNCTYPE, WinError, addressof, byref, windll +from ctypes.wintypes import BOOL, BOOLEAN, DWORD, HANDLE, LPVOID from inspect import ismethod import threading from typing import Callable, Dict, Tuple, Union @@ -25,7 +29,14 @@ ] ] windll.kernel32.RegisterWaitForSingleObject.restype = BOOL -windll.kernel32.RegisterWaitForSingleObject.argtypes = (POINTER(HANDLE), HANDLE, WaitOrTimerCallback, LPVOID, DWORD, DWORD) +windll.kernel32.RegisterWaitForSingleObject.argtypes = ( + POINTER(HANDLE), + HANDLE, + WaitOrTimerCallback, + LPVOID, + DWORD, + DWORD +) class IoThreadEx(hwIo.ioThread.IoThread): @@ -33,7 +44,10 @@ class IoThreadEx(hwIo.ioThread.IoThread): @WaitOrTimerCallback def _internalWaitOrTimerCallback(param: WaitOrTimerCallbackIdT, timerOrWaitFired: bool): - (threadIdent, waitObject, reference, actualParam) = IoThreadEx._waitOrTimerCallbackStore.pop(param, (0, 0, None, 0)) + (threadIdent, waitObject, reference, actualParam) = IoThreadEx._waitOrTimerCallbackStore.pop( + param, + (0, 0, None, 0) + ) threadInst: IoThreadEx = threading._active.get(threadIdent) if not isinstance(threadInst, IoThreadEx): log.error(f"Internal WaitOrTimerCallback called from unknown thread") @@ -45,14 +59,18 @@ def _internalWaitOrTimerCallback(param: WaitOrTimerCallbackIdT, timerOrWaitFired function = reference() if not function: log.debugWarning( - f"Not executing queued WaitOrTimerCallback {param}:{reference.funcName} with param {actualParam} because reference died" + f"Not executing queued WaitOrTimerCallback {param}:{reference.funcName} with param {actualParam} " + "because reference died" ) return try: function(actualParam, bool(timerOrWaitFired)) except Exception: - log.error(f"Error in WaitOrTimerCallback function {function!r} with id {param} queued to IoThread", exc_info=True) + log.error( + f"Error in WaitOrTimerCallback function {function!r} with id {param} queued to IoThread", + exc_info=True + ) finally: threadInst.queueAsApc(threadInst._postWaitOrTimerCallback, waitObject) @@ -73,9 +91,20 @@ def waitForSingleObjectWithCallback( waitObject = HANDLE() reference = BoundMethodWeakref(func) if ismethod(func) else AnnotatableWeakref(func) + reference.funcName = repr(func) waitObjectAddr = addressof(waitObject) self._waitOrTimerCallbackStore[waitObjectAddr] = (self.ident, waitObject, reference, param) - waitRes = windll.kernel32.RegisterWaitForSingleObject(byref(waitObject), objectHandle, self._internalWaitOrTimerCallback, waitObjectAddr, waitTime, flags) - if not waitRes: - raise WinError() - reference.funcName = repr(func) + try: + waitRes = windll.kernel32.RegisterWaitForSingleObject( + byref(waitObject), + objectHandle, + self._internalWaitOrTimerCallback, + waitObjectAddr, + waitTime, + flags + ) + if not waitRes: + raise WinError() + except Exception: + del self._waitOrTimerCallbackStore[waitObjectAddr] + raise diff --git a/addon/lib/namedPipe.py b/addon/lib/namedPipe.py index 908e000..8c8e415 100644 --- a/addon/lib/namedPipe.py +++ b/addon/lib/namedPipe.py @@ -6,7 +6,6 @@ from hwIo.ioThread import IoThread from typing import Callable, Iterator, Optional, Union from ctypes import ( - WINFUNCTYPE, byref, c_ulong, GetLastError, @@ -15,7 +14,7 @@ windll, WinError, ) -from ctypes.wintypes import BOOL, BOOLEAN, HANDLE, DWORD, LPCWSTR, LPVOID +from ctypes.wintypes import BOOL, HANDLE, DWORD, LPCWSTR from serial.win32 import ( CreateFile, ERROR_IO_PENDING, @@ -203,13 +202,12 @@ def _handleConnect(self): self._ioDone(error, 0, byref(ol)) def _handleConnectCallback(self, parameter: int, timerOrWaitFired: bool): - assert timerOrWaitFired == False log.debug(f"Event set for {self.pipeName}") numberOfBytes = DWORD() log.debug(f"Getting overlapped result for {self.pipeName} after wait for event") if not windll.kernel32.GetOverlappedResult(self._file, byref(self._connectOl), byref(numberOfBytes), False): error = GetLastError() - log.debugWarning(f"Error while getting overlapped result for {self.pipeName}: {WinError(error)}") + log.debug(f"Error while getting overlapped result for {self.pipeName}: {WinError(error)}") self._ioDone(error, 0, byref(self._connectOl)) return self._connected = True @@ -226,9 +224,8 @@ def _handleConnectCallback(self, parameter: int, timerOrWaitFired: bool): self._initialRead() def _onReadError(self, error: int): - import tones - tones.beep(440, 30) winErr = WinError(error) + log.debug(f"Read error: {winErr}") if isinstance(winErr, BrokenPipeError): self.disconnect() self._initialRead() @@ -243,7 +240,7 @@ def _asyncRead(self, param: Optional[int] = None): super()._asyncRead() def disconnect(self): - if not windll.kernel32.DisconnectNamedPipe(self._file): + if not windll.kernel32.DisconnectNamedPipe(self._file): raise WinError() self._connected = False self.pipeProcessId = None diff --git a/addon/lib/protocol/__init__.py b/addon/lib/protocol/__init__.py index 92050b6..3048031 100644 --- a/addon/lib/protocol/__init__.py +++ b/addon/lib/protocol/__init__.py @@ -343,10 +343,13 @@ def terminate(self): superTerminate = getattr(super(), "terminate", None) if superTerminate: superTerminate() + # We must sleep before closing the connection as not doing this + # can leave a remote handler in a bad state where it can not be re-initialized. + time.sleep(self.timeout / 10) finally: self.terminateIo() self._attributeValueProcessor.clearCache() - self._bgExecutor.shutdown(False) + self._bgExecutor.shutdown() def _onReceive(self, message: bytes): if self._receiveBuffer: