diff --git a/addon/lib/driver/__init__.py b/addon/lib/driver/__init__.py index 40d0237..7ea0bb3 100644 --- a/addon/lib/driver/__init__.py +++ b/addon/lib/driver/__init__.py @@ -1,6 +1,7 @@ # RDAccess: Remote Desktop Accessibility for NVDA # Copyright 2023 Leonard de Ruijter # License: GNU General Public License version 2.0 +from __future__ import annotations import sys import time @@ -20,6 +21,10 @@ from ..detection import bgScanRD from .settingsAccessor import SettingsAccessorBase +_AUTO_PROPERTY_OBJECT_NAMES: frozenset[str] = frozenset( + n for n in dir(AutoPropertyObject) if not n.startswith("_") +) + ERROR_INVALID_HANDLE = 0x6 ERROR_PIPE_NOT_CONNECTED = 0xE9 MSG_XON = 0x11 @@ -108,12 +113,10 @@ def terminate(self): def __getattribute__(self, name: str) -> Any: getter = super().__getattribute__ - if (name.startswith("_") and not name.startswith("_get_")) or name in ( - n for n in dir(AutoPropertyObject) if not n.startswith("_") - ): + if (name.startswith("_") and not name.startswith("_get_")) or name in _AUTO_PROPERTY_OBJECT_NAMES: return getter(name) accessor = getter("_settingsAccessor") - if accessor and name in dir(accessor): + if accessor and name in accessor._publicNames: return getattr(accessor, name) return getter(name) diff --git a/addon/lib/driver/settingsAccessor.py b/addon/lib/driver/settingsAccessor.py index 8046e31..feca526 100644 --- a/addon/lib/driver/settingsAccessor.py +++ b/addon/lib/driver/settingsAccessor.py @@ -21,6 +21,7 @@ class SettingsAccessorBase(AutoPropertyObject): _driverRef: weakref.ref[RemoteDriver] driver: RemoteDriver _settingNames: list[str] + _publicNames: frozenset[str] cachePropertiesByDefault = True @classmethod @@ -52,6 +53,7 @@ def __init__(self, driver: RemoteDriver, settingNames: list[str]): self._settingNames = settingNames for name in self._settingNames: driver.requestRemoteAttribute(self._getSettingAttributeName(name)) + self._publicNames = frozenset(n for n in dir(self) if not n.startswith("_")) @classmethod def _getSettingAttributeName(cls, setting: str) -> protocol.AttributeT: diff --git a/addon/lib/protocol/__init__.py b/addon/lib/protocol/__init__.py index d1b79df..fa5292b 100644 --- a/addon/lib/protocol/__init__.py +++ b/addon/lib/protocol/__init__.py @@ -200,8 +200,25 @@ def attributeReceiver( class CommandHandlerStore(HandlerRegistrar): + _commandIndex: dict[CommandT, CommandHandlerT] + + def __init__(self, *, _deprecationMessage: str | None = None): + super().__init__(_deprecationMessage=_deprecationMessage) + self._commandIndex = {} + + def register(self, handler: CommandHandlerT): + super().register(handler) + self._commandIndex[handler._command] = handler + + def unregister(self, handler): + result = super().unregister(handler) + if result: + # handler may be a weakref wrapper at this point; rebuild index from live handlers. + self._commandIndex = {h._command: h for h in self.handlers} + return result + def _getHandler(self, command: CommandT) -> CommandHandlerT: - handler = next((v for v in self.handlers if command == v._command), None) + handler = self._commandIndex.get(command) if handler is None: raise NotImplementedError(f"No command handler for command {command!r}") return handler @@ -215,8 +232,42 @@ def __call__(self, command: CommandT, payload: bytes): class AttributeHandlerStore[ AttributeHandlerT: (attributeFetcherT, AttributeReceiverUnboundT, WildCardAttributeReceiverUnboundT), ](HandlerRegistrar[AttributeHandler]): + _exactIndex: dict[AttributeT, AttributeHandler] + _wildcardHandlers: list[AttributeHandler] + + def __init__(self, *, _deprecationMessage: str | None = None): + super().__init__(_deprecationMessage=_deprecationMessage) + self._exactIndex = {} + self._wildcardHandlers = [] + + def _rebuildIndex(self): + self._exactIndex = {} + self._wildcardHandlers = [] + for h in self.handlers: + if h._isCatchAll: + self._wildcardHandlers.append(h) + else: + self._exactIndex[h._attribute] = h + + def register(self, handler: AttributeHandler): + super().register(handler) + if handler._isCatchAll: + self._wildcardHandlers.append(handler) + else: + self._exactIndex[handler._attribute] = handler + + def unregister(self, handler): + result = super().unregister(handler) + if result: + self._rebuildIndex() + return result + def _getRawHandler(self, attribute: AttributeT) -> AttributeHandler: - handler = next((v for v in self.handlers if fnmatch(attribute, v._attribute)), None) + # Exact match takes priority over wildcard patterns (e.g. a specific attribute beats setting_*) + handler = self._exactIndex.get(attribute) + if handler is not None: + return handler + handler = next((h for h in self._wildcardHandlers if fnmatch(attribute, h._attribute)), None) if handler is None: raise NotImplementedError(f"No attribute sender for attribute {attribute}") return handler