From 33228c644a69f6086c9224be23ddcbdd343a5c88 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Sat, 14 Dec 2024 13:56:00 +0100 Subject: [PATCH 01/21] Refactor fallback and allow match funcs for device registration --- source/bdDetect.py | 136 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 104 insertions(+), 32 deletions(-) diff --git a/source/bdDetect.py b/source/bdDetect.py index 28a32d7929a..3a263598f9a 100644 --- a/source/bdDetect.py +++ b/source/bdDetect.py @@ -12,6 +12,8 @@ For drivers in add-ons, this must be done in a global plugin. """ +from dataclasses import dataclass, field +from functools import partial import itertools import threading from concurrent.futures import ThreadPoolExecutor, Future @@ -99,15 +101,36 @@ class DeviceMatch(NamedTuple): MatchFuncT = Callable[[DeviceMatch], bool] -DriverDictT = defaultdict[DeviceType, set[str] | MatchFuncT] -_driverDevices = OrderedDict[str, DriverDictT]() -fallBackDevices: set[tuple[str, DeviceType, str]] = set() -""" -Used to store fallback devices. -When registered as a fallback device, it will be yielded last among the connected USB devices. -""" +def _DEFAULT_MATCH_FUNC(match): + return True + + +@dataclass(frozen=True) +class _UsbDeviceRegistryEntry: + """An internal class that contains information specific to an USB device registration.""" + + id: str + """The identifier of the device.""" + useAsFallback: bool = field(default=False, compare=False) + """ + determine how a device is associated with a driver. + If False (default), the device is immediately available for use with the driver. + If True, the device is added to a fallback list and is used only if the primary driver cannot use + initial devices, serving as a backup option in case of compatibility issues. + This provides flexibility and robustness in managing driver-device connections. + """ + matchFunc: MatchFuncT = field(default=_DEFAULT_MATCH_FUNC, compare=False) + """ + A function which determines whether a given device matches. + It takes a L{DeviceMatch} as its only argument + and returns a C{bool} indicating whether it matched.""" + + +DriverDictT = defaultdict[DeviceType, set[_UsbDeviceRegistryEntry] | MatchFuncT] + +_driverDevices = OrderedDict[str, DriverDictT]() scanForDevices = extensionPoints.Chain[Tuple[str, DeviceMatch]]() """ @@ -164,12 +187,17 @@ def getDriversForConnectedUsbDevices( for driver, devs in _driverDevices.items(): if limitToDevices and driver not in limitToDevices: continue - for type, ids in devs.items(): - if match.type == type and match.id in ids: - if (driver, match.type, match.id) in fallBackDevices: + for type, typeDefs in devs.items(): + if match.type == type and ( + typeDef := next( + (t for t in typeDefs if isinstance(t, _UsbDeviceRegistryEntry) and t.id == match.Id), + None, + ) + ): + if typeDef.useAsFallback: fallbackDriversAndMatches.append({driver, match}) else: - yield driver, match + yield (driver, match) hidName = _getStandardHidDriverName() if limitToDevices and hidName not in limitToDevices: @@ -179,10 +207,7 @@ def getDriversForConnectedUsbDevices( # This ensures that a vendor specific driver is preferred over the braille HID protocol. # This preference may change in the future. if _isHIDBrailleMatch(match): - if (driver, match.type, match.id) in fallBackDevices: - fallbackDriversAndMatches.append({hidName, match}) - else: - yield hidName, match + yield hidName, match for driver, match in fallbackDriversAndMatches: yield driver, match @@ -195,8 +220,16 @@ def _getStandardHidDriverName() -> str: return brailleDisplayDrivers.hidBrailleStandard.HidBrailleDriver.name +def _isHIDUsagePageMatch(match: DeviceMatch, usagePage: int) -> bool: + return match.type == DeviceType.HID and match.deviceInfo.get("HIDUsagePage") == usagePage + + def _isHIDBrailleMatch(match: DeviceMatch) -> bool: - return match.type == DeviceType.HID and match.deviceInfo.get("HIDUsagePage") == HID_USAGE_PAGE_BRAILLE + return _isHIDUsagePageMatch(match, HID_USAGE_PAGE_BRAILLE) + + +def HIDUsagePageMatchFuncFactory(usagePage: int) -> MatchFuncT: + return partial(_isHIDUsagePageMatch, usagePage=usagePage) def getDriversForPossibleBluetoothDevices( @@ -467,8 +500,6 @@ def terminate(self): appModuleHandler.post_appSwitch.unregister(self.pollBluetoothDevices) messageWindow.pre_handleWindowMessage.unregister(self.handleWindowMessage) self._stopBgScan() - # Clear the fallback devices - fallBackDevices.clear() # Clear the cache of bluetooth devices so new devices can be picked up with a new instance. deviceInfoFetcher.btDevsCache = None self._executor.shutdown(wait=False) @@ -501,15 +532,17 @@ def getConnectedUsbDevicesForDriver(driver: str) -> Iterator[DeviceMatch]: for match in usbDevs: if driver == _getStandardHidDriverName(): if _isHIDBrailleMatch(match): - if (driver, match.type, match.id) in fallBackDevices: - fallbackMatches.append(match) - else: - yield match + yield match else: devs = _driverDevices[driver] - for type, ids in devs.items(): - if match.type == type and match.id in ids: - if (driver, match.type, match.id) in fallBackDevices: + for type, typeDefs in devs.items(): + if match.type == type and ( + typeDef := next( + (t for t in typeDefs if isinstance(t, _UsbDeviceRegistryEntry) and t.id == match.Id), + None, + ) + ): + if typeDef.useAsFallback: fallbackMatches.append(match) else: yield match @@ -646,18 +679,60 @@ def _getDriverDict(self) -> DriverDictT: ret = _driverDevices[self._driver] = DriverDictT(set) return ret - def addUsbDevices(self, type: DeviceType, ids: set[str], useAsFallBack: bool = False): + def addUsbDevice( + self, + type: DeviceType, + id: str, + useAsFallback: bool = False, + matchFunc: MatchFuncT = _DEFAULT_MATCH_FUNC, + ): + """Associate an USB device with the driver on this instance. + :param type: The type of the driver. + :param id: A USB ID in the form C{"VID_xxxx&PID_XXXX"}. + Note that alphabetical characters in hexadecimal numbers should be uppercase. + :param useAsFallback: A boolean flag to determine how this USB device is associated with the driver. + If False (default), the device is added directly to the primary driver list for the specified type, + meaning it is immediately available for use with the driver. + If True, the device is used only if the primary driver cannot use + the initial devices, serving as a backup option in case of compatibility issues. + This provides flexibility and robustness in managing driver-device connections. + @param matchFunc: A function which determines whether a given device matches. + It takes a L{DeviceMatch} as its only argument + and returns a C{bool} indicating whether it matched. + It can be used to further constrain a device registration, such as for a specific HID usage page. + It defaults to a no-op that always returns C{True}. + :raise ValueError: When the provided ID is malformed. + """ + if not isinstance(id, str) or not USB_ID_REGEX.match(id): + raise ValueError( + f"Invalid ID provided for driver {self._driver!r}, type {type!r}: " f"{id!r}", + ) + devs = self._getDriverDict() + driverUsb = devs[type] + driverUsb.add(_UsbDeviceRegistryEntry(id, useAsFallback, matchFunc)) + + def addUsbDevices( + self, + type: DeviceType, + ids: set[str], + useAsFallback: bool = False, + matchFunc: MatchFuncT = _DEFAULT_MATCH_FUNC, + ): """Associate USB devices with the driver on this instance. :param type: The type of the driver. :param ids: A set of USB IDs in the form C{"VID_xxxx&PID_XXXX"}. Note that alphabetical characters in hexadecimal numbers should be uppercase. - :param useAsFallBack: A boolean flag to determine how USB devices are associated with the driver. - + :param useAsFallback: A boolean flag to determine how USB devices are associated with the driver. If False (default), the devices are added directly to the primary driver list for the specified type, meaning they are immediately available for use with the driver. If True, the devices are added to a fallback list and are used only if the primary driver cannot use the initial devices, serving as a backup option in case of compatibility issues. This provides flexibility and robustness in managing driver-device connections. + @param matchFunc: A function which determines whether a given device matches. + It takes a L{DeviceMatch} as its only argument + and returns a C{bool} indicating whether it matched. + It can be used to further constrain device registrations, such as for a specific HID usage page. + It defaults to a no-op that always returns C{True}. :raise ValueError: When one of the provided IDs is malformed. """ malformedIds = [id for id in ids if not isinstance(id, str) or not USB_ID_REGEX.match(id)] @@ -666,12 +741,9 @@ def addUsbDevices(self, type: DeviceType, ids: set[str], useAsFallBack: bool = F f"Invalid IDs provided for driver {self._driver!r}, type {type!r}: " f"{', '.join(malformedIds)}", ) - if useAsFallBack: - fallBackDevices.update((self._driver, type, id) for id in ids) - devs = self._getDriverDict() driverUsb = devs[type] - driverUsb.update(ids) + driverUsb.update(_UsbDeviceRegistryEntry(id, useAsFallback, matchFunc) for id in ids) def addBluetoothDevices(self, matchFunc: MatchFuncT): """Associate Bluetooth HID or COM ports with the driver on this instance. From 1b95e815cd33d78f5387a5477cf092190b4f6c6f Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Mon, 16 Dec 2024 20:58:20 +0100 Subject: [PATCH 02/21] Update albatross --- source/brailleDisplayDrivers/albatross/driver.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/source/brailleDisplayDrivers/albatross/driver.py b/source/brailleDisplayDrivers/albatross/driver.py index 311dd145ceb..9a982f3d0d6 100644 --- a/source/brailleDisplayDrivers/albatross/driver.py +++ b/source/brailleDisplayDrivers/albatross/driver.py @@ -60,7 +60,6 @@ BOTH_BYTES, KC_INTERVAL, BUS_DEVICE_DESC, - VID_AND_PID, ) from .gestures import _gestureMap @@ -84,11 +83,11 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver): @classmethod def registerAutomaticDetection(cls, driverRegistrar: DriverRegistrar): - driverRegistrar.addUsbDevices( + driverRegistrar.addUsbDevice( DeviceType.SERIAL, - { - "VID_0403&PID_6001", # Caiku Albatross 46/80 - }, + "VID_0403&PID_6001", # Caiku Albatross 46/80 + # Filter for bus reported device description, which should be "Albatross Braille Display". + matchFunc=lambda match: match.deviceInfo.get("busReportedDeviceDescription") == BUS_DEVICE_DESC, ) @classmethod @@ -168,11 +167,6 @@ def _searchPorts(self, originalPort: str): """ for self._baudRate in BAUD_RATE: for portType, portId, port, portInfo in self._getTryPorts(originalPort): - # Block port if its vid and pid are correct but bus reported - # device description is not "Albatross Braille Display". - if portId == VID_AND_PID and portInfo.get("busReportedDeviceDescription") != BUS_DEVICE_DESC: - log.debug(f"port {port} blocked; port information: {portInfo}") - continue # For reconnection self._currentPort = port self._tryToConnect = True From ce4d2330debb6c8e03bcfb7f3f5e748983df8836 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Mon, 16 Dec 2024 21:08:24 +0100 Subject: [PATCH 03/21] Add changes --- user_docs/en/changes.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/user_docs/en/changes.md b/user_docs/en/changes.md index 37ab4b9e3b8..db83f655a51 100644 --- a/user_docs/en/changes.md +++ b/user_docs/en/changes.md @@ -124,6 +124,11 @@ Add-ons will need to be re-tested and have their manifest updated. * Added the following extension points (#17428, @ctoth): * `inputCore.decide_handleRawKey`: called on each keypress * `speech.extensions.post_speechPaused`: called when speech is paused or unpaused +* Changes to braille display auto detection registration in `bdDetect.DriverRegistrar`: (#17521, @LeonarddeR) + * Added the `addUsbDevice` method to register one USB device at a time. + * Added the `matchFunc` parameter to `addUsbDevices` which is also available on `addUsbDevice`. + * This way device detection can be constrained further in cases where a VID/PID-combination is shared by multiple devices across multiple drivers, or when a HID device offers multiple endpoints, for example. + * See the method documentation as well as examples in the albatross and brailliantB drivers for more information. #### API Breaking Changes @@ -149,6 +154,7 @@ As the NVDA update check URL is now configurable directly within NVDA, no replac * In `NVDAObjects.window.scintilla.ScintillaTextInfo`, if no text is selected, the `collapse` method is overriden to expand to line if the `end` parameter is set to `True` (#17431, @nvdaes) * The following symbols have been removed with no replacement: `languageHandler.getLanguageCliArgs`, `__main__.quitGroup` and `__main__.installGroup` . (#17486, @CyrilleB79) * Prefix matching on command line flags, e.g. using `--di` for `--disable-addons` is no longer supported. (#11644, @CyrilleB79) +* The `useAsFallBack` keyword argument of `bdDetect.DriverRegistrar` has been renamed to `useAsFallback`. (#17521, @LeonarddeR) #### Deprecations From 1791b18b7198d3d1d6dc2923afbdec2ec1123375 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Mon, 16 Dec 2024 21:22:11 +0100 Subject: [PATCH 04/21] Add brailliant --- source/brailleDisplayDrivers/brailliantB.py | 42 +++++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/source/brailleDisplayDrivers/brailliantB.py b/source/brailleDisplayDrivers/brailliantB.py index 67670d80e9d..58f0e9ad324 100644 --- a/source/brailleDisplayDrivers/brailliantB.py +++ b/source/brailleDisplayDrivers/brailliantB.py @@ -35,6 +35,7 @@ HR_KEYS = b"\x04" HR_BRAILLE = b"\x05" HR_POWEROFF = b"\x07" +HID_USAGE_PAGE = 0x93 KEY_NAMES = { 1: "power", # Brailliant BI 32, 40 and 80. @@ -93,12 +94,18 @@ def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): { "VID_1C71&PID_C111", # Mantis Q 40 "VID_1C71&PID_C101", # Chameleon 20 + "VID_1C71&PID_C131", # Brailliant BI 40X + "VID_1C71&PID_C141", # Brailliant BI 20X + }, + matchFunc=bdDetect.HIDUsagePageMatchFuncFactory(HID_USAGE_PAGE), + ) + driverRegistrar.addUsbDevices( + bdDetect.DeviceType.HID, + { "VID_1C71&PID_C121", # Humanware BrailleOne 20 HID "VID_1C71&PID_CE01", # NLS eReader 20 HID "VID_1C71&PID_C006", # Brailliant BI 32, 40 and 80 "VID_1C71&PID_C022", # Brailliant BI 14 - "VID_1C71&PID_C131", # Brailliant BI 40X - "VID_1C71&PID_C141", # Brailliant BI 20X "VID_1C71&PID_C00A", # BrailleNote Touch "VID_1C71&PID_C00E", # BrailleNote Touch v2 }, @@ -120,16 +127,24 @@ def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): or ( m.type == bdDetect.DeviceType.HID and m.deviceInfo.get("manufacturer") == "Humanware" - and m.deviceInfo.get("product") - in ( - "Brailliant HID", - "APH Chameleon 20", - "APH Mantis Q40", - "Humanware BrailleOne", - "NLS eReader", - "NLS eReader Humanware", - "Brailliant BI 40X", - "Brailliant BI 20X", + and ( + ( + m.deviceInfo.get("product") + in ( + "APH Chameleon 20", + "APH Mantis Q40", + "Brailliant BI 40X", + "Brailliant BI 20X", + ) + and bdDetect._isHIDUsagePageMatch(m, HID_USAGE_PAGE) + ) + or m.deviceInfo.get("product") + in ( + "Brailliant HID", + "Humanware BrailleOne", + "NLS eReader", + "NLS eReader Humanware", + ) ) ), ) @@ -147,6 +162,9 @@ def __init__(self, port="auto"): # Try talking to the display. try: if self.isHid: + if (usasePage := portInfo.get("HIDUsagePage")) != HID_USAGE_PAGE: + log.debugWarning(f"Ignoring device {port!r} with usage page {usasePage!r}") + continue self._dev = hwIo.Hid(port, onReceive=self._hidOnReceive) else: self._dev = hwIo.Serial( From 9011882764b2ecf72711d2dc86662e57e459158d Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 17 Dec 2024 10:30:52 +0100 Subject: [PATCH 05/21] Actually use the matchFunc and make it optional --- source/bdDetect.py | 50 +++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/source/bdDetect.py b/source/bdDetect.py index 3a263598f9a..ffb91d9584f 100644 --- a/source/bdDetect.py +++ b/source/bdDetect.py @@ -103,10 +103,6 @@ class DeviceMatch(NamedTuple): MatchFuncT = Callable[[DeviceMatch], bool] -def _DEFAULT_MATCH_FUNC(match): - return True - - @dataclass(frozen=True) class _UsbDeviceRegistryEntry: """An internal class that contains information specific to an USB device registration.""" @@ -121,9 +117,9 @@ class _UsbDeviceRegistryEntry: initial devices, serving as a backup option in case of compatibility issues. This provides flexibility and robustness in managing driver-device connections. """ - matchFunc: MatchFuncT = field(default=_DEFAULT_MATCH_FUNC, compare=False) + matchFunc: MatchFuncT | None = field(default=None, compare=False) """ - A function which determines whether a given device matches. + An optional function which determines whether a given device matches. It takes a L{DeviceMatch} as its only argument and returns a C{bool} indicating whether it matched.""" @@ -188,11 +184,19 @@ def getDriversForConnectedUsbDevices( if limitToDevices and driver not in limitToDevices: continue for type, typeDefs in devs.items(): - if match.type == type and ( - typeDef := next( - (t for t in typeDefs if isinstance(t, _UsbDeviceRegistryEntry) and t.id == match.Id), - None, + if ( + match.type == type + and ( + typeDef := next( + ( + t + for t in typeDefs + if isinstance(t, _UsbDeviceRegistryEntry) and t.id == match.Id + ), + None, + ) ) + and (not callable(typeDef.matchFunc) or typeDef.matchFunc(match)) ): if typeDef.useAsFallback: fallbackDriversAndMatches.append({driver, match}) @@ -536,11 +540,19 @@ def getConnectedUsbDevicesForDriver(driver: str) -> Iterator[DeviceMatch]: else: devs = _driverDevices[driver] for type, typeDefs in devs.items(): - if match.type == type and ( - typeDef := next( - (t for t in typeDefs if isinstance(t, _UsbDeviceRegistryEntry) and t.id == match.Id), - None, + if ( + match.type == type + and ( + typeDef := next( + ( + t + for t in typeDefs + if isinstance(t, _UsbDeviceRegistryEntry) and t.id == match.Id + ), + None, + ) ) + and (not callable(typeDef.matchFunc) or typeDef.matchFunc(match)) ): if typeDef.useAsFallback: fallbackMatches.append(match) @@ -684,7 +696,7 @@ def addUsbDevice( type: DeviceType, id: str, useAsFallback: bool = False, - matchFunc: MatchFuncT = _DEFAULT_MATCH_FUNC, + matchFunc: MatchFuncT | None = None, ): """Associate an USB device with the driver on this instance. :param type: The type of the driver. @@ -696,11 +708,10 @@ def addUsbDevice( If True, the device is used only if the primary driver cannot use the initial devices, serving as a backup option in case of compatibility issues. This provides flexibility and robustness in managing driver-device connections. - @param matchFunc: A function which determines whether a given device matches. + @param matchFunc: An optional function which determines whether a given device matches. It takes a L{DeviceMatch} as its only argument and returns a C{bool} indicating whether it matched. It can be used to further constrain a device registration, such as for a specific HID usage page. - It defaults to a no-op that always returns C{True}. :raise ValueError: When the provided ID is malformed. """ if not isinstance(id, str) or not USB_ID_REGEX.match(id): @@ -716,7 +727,7 @@ def addUsbDevices( type: DeviceType, ids: set[str], useAsFallback: bool = False, - matchFunc: MatchFuncT = _DEFAULT_MATCH_FUNC, + matchFunc: MatchFuncT = None, ): """Associate USB devices with the driver on this instance. :param type: The type of the driver. @@ -728,11 +739,10 @@ def addUsbDevices( If True, the devices are added to a fallback list and are used only if the primary driver cannot use the initial devices, serving as a backup option in case of compatibility issues. This provides flexibility and robustness in managing driver-device connections. - @param matchFunc: A function which determines whether a given device matches. + @param matchFunc: An optional function which determines whether a given device matches. It takes a L{DeviceMatch} as its only argument and returns a C{bool} indicating whether it matched. It can be used to further constrain device registrations, such as for a specific HID usage page. - It defaults to a no-op that always returns C{True}. :raise ValueError: When one of the provided IDs is malformed. """ malformedIds = [id for id in ids if not isinstance(id, str) or not USB_ID_REGEX.match(id)] From d0c3bced74659a720234379833b495f26f3eb693 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Fri, 20 Dec 2024 17:59:46 +0100 Subject: [PATCH 06/21] Fixup typos --- source/bdDetect.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/bdDetect.py b/source/bdDetect.py index ffb91d9584f..5f275d3fd23 100644 --- a/source/bdDetect.py +++ b/source/bdDetect.py @@ -191,7 +191,7 @@ def getDriversForConnectedUsbDevices( ( t for t in typeDefs - if isinstance(t, _UsbDeviceRegistryEntry) and t.id == match.Id + if isinstance(t, _UsbDeviceRegistryEntry) and t.id == match.id ), None, ) @@ -547,7 +547,7 @@ def getConnectedUsbDevicesForDriver(driver: str) -> Iterator[DeviceMatch]: ( t for t in typeDefs - if isinstance(t, _UsbDeviceRegistryEntry) and t.id == match.Id + if isinstance(t, _UsbDeviceRegistryEntry) and t.id == match.id ), None, ) @@ -647,7 +647,7 @@ def getBrailleDisplayDriversEnabledForDetection() -> Generator[str, Any, Any]: def initialize(): """Initializes bdDetect, such as detection data. - Calls to addUsbDevices, and addBluetoothDevices. + Calls to addUsbDevice, addUsbDevices, and addBluetoothDevices. Specify the requirements for a detected device to be considered a match for a specific driver. """ @@ -753,7 +753,7 @@ def addUsbDevices( ) devs = self._getDriverDict() driverUsb = devs[type] - driverUsb.update(_UsbDeviceRegistryEntry(id, useAsFallback, matchFunc) for id in ids) + driverUsb.update((_UsbDeviceRegistryEntry(id, useAsFallback, matchFunc) for id in ids)) def addBluetoothDevices(self, matchFunc: MatchFuncT): """Associate Bluetooth HID or COM ports with the driver on this instance. From b726cf6e55218b46e8dc3d5a9daf2a566f6e7fa7 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Fri, 20 Dec 2024 18:01:05 +0100 Subject: [PATCH 07/21] Use constant in Albatros --- source/brailleDisplayDrivers/albatross/driver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/brailleDisplayDrivers/albatross/driver.py b/source/brailleDisplayDrivers/albatross/driver.py index 9a982f3d0d6..976f4a38c41 100644 --- a/source/brailleDisplayDrivers/albatross/driver.py +++ b/source/brailleDisplayDrivers/albatross/driver.py @@ -60,6 +60,7 @@ BOTH_BYTES, KC_INTERVAL, BUS_DEVICE_DESC, + VID_AND_PID, ) from .gestures import _gestureMap @@ -85,7 +86,7 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver): def registerAutomaticDetection(cls, driverRegistrar: DriverRegistrar): driverRegistrar.addUsbDevice( DeviceType.SERIAL, - "VID_0403&PID_6001", # Caiku Albatross 46/80 + VID_AND_PID, # Caiku Albatross 46/80 # Filter for bus reported device description, which should be "Albatross Braille Display". matchFunc=lambda match: match.deviceInfo.get("busReportedDeviceDescription") == BUS_DEVICE_DESC, ) From 98d01bc3723f5ef0232ff2ca973a6850f5d21e77 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Fri, 20 Dec 2024 18:04:52 +0100 Subject: [PATCH 08/21] Uniform yield --- source/bdDetect.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/bdDetect.py b/source/bdDetect.py index 5f275d3fd23..cc2ce89e18b 100644 --- a/source/bdDetect.py +++ b/source/bdDetect.py @@ -201,7 +201,7 @@ def getDriversForConnectedUsbDevices( if typeDef.useAsFallback: fallbackDriversAndMatches.append({driver, match}) else: - yield (driver, match) + yield (zdriver, match) hidName = _getStandardHidDriverName() if limitToDevices and hidName not in limitToDevices: @@ -211,10 +211,10 @@ def getDriversForConnectedUsbDevices( # This ensures that a vendor specific driver is preferred over the braille HID protocol. # This preference may change in the future. if _isHIDBrailleMatch(match): - yield hidName, match + yield (hidName, match) for driver, match in fallbackDriversAndMatches: - yield driver, match + yield (driver, match) def _getStandardHidDriverName() -> str: @@ -271,7 +271,7 @@ def getDriversForPossibleBluetoothDevices( if not callable(matchFunc): continue if matchFunc(match): - yield driver, match + yield (driver, match) hidName = _getStandardHidDriverName() if limitToDevices and hidName not in limitToDevices: From 3b95bd7447b3f1b823c45ee800199d783c20fdd7 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 26 Dec 2024 11:11:04 +0100 Subject: [PATCH 09/21] use cache for HID device info --- source/hwPortUtils.py | 41 ++++++++++++++++++++++++++------------ source/winAPI/constants.py | 2 ++ user_docs/en/changes.md | 2 ++ 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/source/hwPortUtils.py b/source/hwPortUtils.py index 29dc7ed0002..de926c07a78 100644 --- a/source/hwPortUtils.py +++ b/source/hwPortUtils.py @@ -16,6 +16,7 @@ import hidpi import winKernel from logHandler import log +from winAPI.constants import SystemErrorCodes from winKernel import SYSTEMTIME @@ -496,7 +497,10 @@ def __init__(self, **kwargs): super().__init__(Size=ctypes.sizeof(HIDD_ATTRIBUTES), **kwargs) -def _getHidInfo(hwId, path): +_getHidInfoCache: dict[str, dict] = {} + + +def _getHidInfo(hwId: str, path: str) -> dict[str, typing.Any]: info = { "hardwareID": hwId, "devicePath": path, @@ -517,18 +521,28 @@ def _getHidInfo(hwId, path): # Fetch additional info about the HID device. from serial.win32 import FILE_FLAG_OVERLAPPED, INVALID_HANDLE_VALUE, CreateFile - handle = CreateFile( - path, - 0, - winKernel.FILE_SHARE_READ | winKernel.FILE_SHARE_WRITE, - None, - winKernel.OPEN_EXISTING, - FILE_FLAG_OVERLAPPED, - None, - ) - if handle == INVALID_HANDLE_VALUE: - if _isDebug(): - log.debugWarning(f"Opening device {path} to get additional info failed: {ctypes.WinError()}") + if ( + handle := CreateFile( + path, + 0, + winKernel.FILE_SHARE_READ | winKernel.FILE_SHARE_WRITE, + None, + winKernel.OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + None, + ) + ) == INVALID_HANDLE_VALUE: + if (err := ctypes.GetLastError()) == SystemErrorCodes.SHARING_VIOLATION: + if _isDebug(): + log.debugWarning( + f"Opening device {path} to get additional info failed because the device is being used. " + "Falling back to cache for device info", + ) + if cachedInfo := _getHidInfoCache.get(path): + cachedInfo.update(info) + return cachedInfo + elif _isDebug(): + log.debugWarning(f"Opening device {path} to get additional info failed: {ctypes.WinError(err)}") return info try: attribs = HIDD_ATTRIBUTES() @@ -555,6 +569,7 @@ def _getHidInfo(hwId, path): ctypes.windll.hid.HidD_FreePreparsedData(pd) finally: winKernel.closeHandle(handle) + _getHidInfoCache[path] = info return info diff --git a/source/winAPI/constants.py b/source/winAPI/constants.py index a7f1bc07fe3..41e60a3562b 100644 --- a/source/winAPI/constants.py +++ b/source/winAPI/constants.py @@ -28,6 +28,8 @@ class SystemErrorCodes(enum.IntEnum): ACCESS_DENIED = 0x5 INVALID_DATA = 0xD NOT_READY = 0x15 + SHARING_VIOLATION = 0x20 + """The process cannot access the file because it is being used by another process.""" INVALID_PARAMETER = 0x57 MOD_NOT_FOUND = 0x7E CANCELLED = 0x4C7 diff --git a/user_docs/en/changes.md b/user_docs/en/changes.md index db83f655a51..09ac77a8150 100644 --- a/user_docs/en/changes.md +++ b/user_docs/en/changes.md @@ -81,6 +81,8 @@ Specifically, MathML inside of span and other elements that have the attribute ` In any document, if the cursor is on the last line, it will be moved to the end when using this command. (#17251, #17430, @nvdaes) * In web browsers, changes to text selection no longer sometimes fail to be reported in editable text controls. (#17501, @jcsteh) +* When the Standard HID Braille Display driver is explicitly selected as the braille display driver, and the braille display list is opened, NVDA correctly identifies the HID driver as the selected driver instead of showing no driver selected. (#17537, @LeonarddeR) +* The Humanware Brailliant driver is now more reliable in selecting the right connection endpoint, resulting in better connection stability and less errors. (#17537, @LeonarddeR) ### Changes for Developers From 673d742049c95f202dab37bdb9af60a3816ca649 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 26 Dec 2024 11:14:55 +0100 Subject: [PATCH 10/21] Fix typo --- source/bdDetect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/bdDetect.py b/source/bdDetect.py index cc2ce89e18b..cccec5f204b 100644 --- a/source/bdDetect.py +++ b/source/bdDetect.py @@ -201,7 +201,7 @@ def getDriversForConnectedUsbDevices( if typeDef.useAsFallback: fallbackDriversAndMatches.append({driver, match}) else: - yield (zdriver, match) + yield (driver, match) hidName = _getStandardHidDriverName() if limitToDevices and hidName not in limitToDevices: From dcd8ffda7fc40c747676cf3b1a4e17f3578fef4d Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Fri, 27 Dec 2024 21:20:53 +0100 Subject: [PATCH 11/21] Deprecations --- source/bdDetect.py | 141 ++++++++++-------- source/braille.py | 2 +- .../brailleDisplayDrivers/albatross/driver.py | 4 +- source/brailleDisplayDrivers/alva.py | 4 +- source/brailleDisplayDrivers/baum.py | 6 +- source/brailleDisplayDrivers/brailleNote.py | 2 +- source/brailleDisplayDrivers/brailliantB.py | 12 +- .../eurobraille/driver.py | 12 +- .../eurobraille/gestures.py | 2 +- .../freedomScientific.py | 4 +- source/brailleDisplayDrivers/handyTech.py | 8 +- .../hidBrailleStandard.py | 2 +- source/brailleDisplayDrivers/hims.py | 16 +- source/brailleDisplayDrivers/nattiqbraille.py | 2 +- source/brailleDisplayDrivers/seikantk.py | 8 +- source/brailleDisplayDrivers/superBrl.py | 2 +- tests/unit/test_bdDetect.py | 2 +- user_docs/en/changes.md | 1 + 18 files changed, 123 insertions(+), 107 deletions(-) diff --git a/source/bdDetect.py b/source/bdDetect.py index cccec5f204b..8fa0e297f68 100644 --- a/source/bdDetect.py +++ b/source/bdDetect.py @@ -17,7 +17,7 @@ import itertools import threading from concurrent.futures import ThreadPoolExecutor, Future -from enum import StrEnum +from enum import Enum from typing import ( Any, Callable, @@ -53,15 +53,46 @@ USB_ID_REGEX = re.compile(r"^VID_[0-9A-F]{4}&PID_[0-9A-F]{4}$", re.U) -class DeviceType(StrEnum): +class ProtocolType(str, Enum): HID = "hid" """HID devices""" SERIAL = "serial" """Serial devices (COM ports)""" CUSTOM = "custom" - """Devices with a manufacturer specific driver""" + """Devices with a manufacturer specific protocol""" + + +class CommunicationType(str, Enum): BLUETOOTH = "bluetooth" """Bluetooth devices""" + USB = "usb" + """USB devices""" + + +class _DeviceTypeMeta(type): + # Mapping old attributes to the new enums + _mapping = { + "HID": ProtocolType.HID, + "SERIAL": ProtocolType.SERIAL, + "CUSTOM": ProtocolType.CUSTOM, + "BLUETOOTH": CommunicationType.BLUETOOTH, + } + + def __getattr__(cls, name: str) -> ProtocolType | CommunicationType: + repl = cls._mapping.get(name) + if repl is not None and NVDAState._allowDeprecatedAPI(): + log.warning( + f"{cls.__name__}.{name} is deprecated. Use {repl} instead.", + ) + return repl + raise AttributeError(f"'{cls.__name__}' object has no attribute '{name}'") + + +class DeviceType(metaclass=_DeviceTypeMeta): + """This class is kept for backwards compatibility. + Former members were split into the L{ProtocolType} and L{CommunicationType} enums.""" + + ... def __getattr__(attrName: str) -> Any: @@ -73,25 +104,25 @@ def __getattr__(attrName: str) -> Any: log.warning(f"{attrName} is deprecated.") return 2 _deprecatedConstantsMap = { - "KEY_HID": DeviceType.HID, - "KEY_SERIAL": DeviceType.SERIAL, - "KEY_BLUETOOTH": DeviceType.BLUETOOTH, - "KEY_CUSTOM": DeviceType.CUSTOM, + "KEY_HID": ProtocolType.HID, + "KEY_SERIAL": ProtocolType.SERIAL, + "KEY_BLUETOOTH": CommunicationType.BLUETOOTH, + "KEY_CUSTOM": ProtocolType.CUSTOM, } if attrName in _deprecatedConstantsMap and NVDAState._allowDeprecatedAPI(): replacementSymbol = _deprecatedConstantsMap[attrName] log.warning( - f"{attrName} is deprecated. " f"Use bdDetect.DeviceType.{replacementSymbol.name} instead. ", + f"{attrName} is deprecated. " f"Use bdDetect.{replacementSymbol} instead. ", ) - return replacementSymbol + return replacementSymbol.value raise AttributeError(f"module {repr(__name__)} has no attribute {repr(attrName)}") class DeviceMatch(NamedTuple): """Represents a detected device.""" - type: DeviceType - """The type of the device.""" + type: ProtocolType + """The protocol type of the device.""" id: str """The identifier of the device.""" port: str @@ -109,6 +140,8 @@ class _UsbDeviceRegistryEntry: id: str """The identifier of the device.""" + type: ProtocolType + """The protocol type of the device.""" useAsFallback: bool = field(default=False, compare=False) """ determine how a device is associated with a driver. @@ -123,9 +156,16 @@ class _UsbDeviceRegistryEntry: It takes a L{DeviceMatch} as its only argument and returns a C{bool} indicating whether it matched.""" + def matches(self, deviceMatch: DeviceMatch) -> bool: + """Returns whether this registry entry matches a specific device.""" + if deviceMatch.type != self.type or deviceMatch.id != self.id: + return False + if self.matchFunc is None: + return True + return self.matchFunc(deviceMatch) -DriverDictT = defaultdict[DeviceType, set[_UsbDeviceRegistryEntry] | MatchFuncT] +DriverDictT = defaultdict[CommunicationType, set[_UsbDeviceRegistryEntry] | MatchFuncT] _driverDevices = OrderedDict[str, DriverDictT]() scanForDevices = extensionPoints.Chain[Tuple[str, DeviceMatch]]() @@ -158,11 +198,11 @@ def getDriversForConnectedUsbDevices( @return: Generator of pairs of drivers and device information. """ usbCustomDeviceMatches = ( - DeviceMatch(DeviceType.CUSTOM, port["usbID"], port["devicePath"], port) + DeviceMatch(ProtocolType.CUSTOM, port["usbID"], port["devicePath"], port) for port in deviceInfoFetcher.usbDevices ) usbComDeviceMatches = ( - DeviceMatch(DeviceType.SERIAL, port["usbID"], port["port"], port) + DeviceMatch(ProtocolType.SERIAL, port["usbID"], port["port"], port) for port in deviceInfoFetcher.usbComPorts ) # Tee is used to ensure that the DeviceMatches aren't created multiple times. @@ -172,7 +212,7 @@ def getDriversForConnectedUsbDevices( # device matches), if one is found early the iteration can stop. usbHidDeviceMatches, usbHidDeviceMatchesForCustom = itertools.tee( ( - DeviceMatch(DeviceType.HID, port["usbID"], port["devicePath"], port) + DeviceMatch(ProtocolType.HID, port["usbID"], port["devicePath"], port) for port in deviceInfoFetcher.hidDevices if port["provider"] == "usb" ) @@ -183,21 +223,9 @@ def getDriversForConnectedUsbDevices( for driver, devs in _driverDevices.items(): if limitToDevices and driver not in limitToDevices: continue - for type, typeDefs in devs.items(): - if ( - match.type == type - and ( - typeDef := next( - ( - t - for t in typeDefs - if isinstance(t, _UsbDeviceRegistryEntry) and t.id == match.id - ), - None, - ) - ) - and (not callable(typeDef.matchFunc) or typeDef.matchFunc(match)) - ): + typeDefs = devs[CommunicationType.USB] + for typeDef in typeDefs: + if typeDef.matches(match): if typeDef.useAsFallback: fallbackDriversAndMatches.append({driver, match}) else: @@ -225,7 +253,7 @@ def _getStandardHidDriverName() -> str: def _isHIDUsagePageMatch(match: DeviceMatch, usagePage: int) -> bool: - return match.type == DeviceType.HID and match.deviceInfo.get("HIDUsagePage") == usagePage + return match.type == ProtocolType.HID and match.deviceInfo.get("HIDUsagePage") == usagePage def _isHIDBrailleMatch(match: DeviceMatch) -> bool: @@ -247,7 +275,7 @@ def getDriversForPossibleBluetoothDevices( @return: Generator of pairs of drivers and port information. """ btSerialMatchesForCustom = ( - DeviceMatch(DeviceType.SERIAL, port["bluetoothName"], port["port"], port) + DeviceMatch(ProtocolType.SERIAL, port["bluetoothName"], port["port"], port) for port in deviceInfoFetcher.comPorts if "bluetoothName" in port ) @@ -258,7 +286,7 @@ def getDriversForPossibleBluetoothDevices( # device matches), if one is found early the iteration can stop. btHidDevMatchesForHid, btHidDevMatchesForCustom = itertools.tee( ( - DeviceMatch(DeviceType.HID, port["hardwareID"], port["devicePath"], port) + DeviceMatch(ProtocolType.HID, port["hardwareID"], port["devicePath"], port) for port in deviceInfoFetcher.hidDevices if port["provider"] == "bluetooth" ) @@ -267,7 +295,7 @@ def getDriversForPossibleBluetoothDevices( for driver, devs in _driverDevices.items(): if limitToDevices and driver not in limitToDevices: continue - matchFunc = devs[DeviceType.BLUETOOTH] + matchFunc = devs[CommunicationType.BLUETOOTH] if not callable(matchFunc): continue if matchFunc(match): @@ -517,16 +545,16 @@ def getConnectedUsbDevicesForDriver(driver: str) -> Iterator[DeviceMatch]: """ usbDevs = itertools.chain( ( - DeviceMatch(DeviceType.CUSTOM, port["usbID"], port["devicePath"], port) + DeviceMatch(ProtocolType.CUSTOM, port["usbID"], port["devicePath"], port) for port in deviceInfoFetcher.usbDevices ), ( - DeviceMatch(DeviceType.HID, port["usbID"], port["devicePath"], port) + DeviceMatch(ProtocolType.HID, port["usbID"], port["devicePath"], port) for port in deviceInfoFetcher.hidDevices if port["provider"] == "usb" ), ( - DeviceMatch(DeviceType.SERIAL, port["usbID"], port["port"], port) + DeviceMatch(ProtocolType.SERIAL, port["usbID"], port["port"], port) for port in deviceInfoFetcher.usbComPorts ), ) @@ -538,23 +566,10 @@ def getConnectedUsbDevicesForDriver(driver: str) -> Iterator[DeviceMatch]: if _isHIDBrailleMatch(match): yield match else: - devs = _driverDevices[driver] - for type, typeDefs in devs.items(): - if ( - match.type == type - and ( - typeDef := next( - ( - t - for t in typeDefs - if isinstance(t, _UsbDeviceRegistryEntry) and t.id == match.id - ), - None, - ) - ) - and (not callable(typeDef.matchFunc) or typeDef.matchFunc(match)) - ): - if typeDef.useAsFallback: + devs = _driverDevices[driver][CommunicationType.USB] + for dev in devs: + if dev.matches(match): + if dev.useAsFallback: fallbackMatches.append(match) else: yield match @@ -572,17 +587,17 @@ def getPossibleBluetoothDevicesForDriver(driver: str) -> Iterator[DeviceMatch]: if driver == _getStandardHidDriverName(): matchFunc = _isHIDBrailleMatch else: - matchFunc = _driverDevices[driver][DeviceType.BLUETOOTH] + matchFunc = _driverDevices[driver][CommunicationType.BLUETOOTH] if not callable(matchFunc): return btDevs = itertools.chain( ( - DeviceMatch(DeviceType.SERIAL, port["bluetoothName"], port["port"], port) + DeviceMatch(ProtocolType.SERIAL, port["bluetoothName"], port["port"], port) for port in deviceInfoFetcher.comPorts if "bluetoothName" in port ), ( - DeviceMatch(DeviceType.HID, port["hardwareID"], port["devicePath"], port) + DeviceMatch(ProtocolType.HID, port["hardwareID"], port["devicePath"], port) for port in deviceInfoFetcher.hidDevices if port["provider"] == "bluetooth" ), @@ -693,7 +708,7 @@ def _getDriverDict(self) -> DriverDictT: def addUsbDevice( self, - type: DeviceType, + type: ProtocolType, id: str, useAsFallback: bool = False, matchFunc: MatchFuncT | None = None, @@ -719,8 +734,8 @@ def addUsbDevice( f"Invalid ID provided for driver {self._driver!r}, type {type!r}: " f"{id!r}", ) devs = self._getDriverDict() - driverUsb = devs[type] - driverUsb.add(_UsbDeviceRegistryEntry(id, useAsFallback, matchFunc)) + driverUsb = devs[CommunicationType.USB] + driverUsb.add(_UsbDeviceRegistryEntry(type, id, useAsFallback, matchFunc)) def addUsbDevices( self, @@ -752,8 +767,8 @@ def addUsbDevices( f"{', '.join(malformedIds)}", ) devs = self._getDriverDict() - driverUsb = devs[type] - driverUsb.update((_UsbDeviceRegistryEntry(id, useAsFallback, matchFunc) for id in ids)) + driverUsb = devs[CommunicationType.USB] + driverUsb.update((_UsbDeviceRegistryEntry(id, type, useAsFallback, matchFunc) for id in ids)) def addBluetoothDevices(self, matchFunc: MatchFuncT): """Associate Bluetooth HID or COM ports with the driver on this instance. @@ -762,7 +777,7 @@ def addBluetoothDevices(self, matchFunc: MatchFuncT): and returns a C{bool} indicating whether it matched. """ devs = self._getDriverDict() - devs[DeviceType.BLUETOOTH] = matchFunc + devs[CommunicationType.BLUETOOTH] = matchFunc def addDeviceScanner( self, diff --git a/source/braille.py b/source/braille.py index ba44325c07b..54a70dac138 100644 --- a/source/braille.py +++ b/source/braille.py @@ -3517,7 +3517,7 @@ def _getTryPorts( pass else: yield bdDetect.DeviceMatch( - bdDetect.DeviceType.SERIAL, + bdDetect.ProtocolType.SERIAL, portInfo["bluetoothName" if "bluetoothName" in portInfo else "friendlyName"], portInfo["port"], portInfo, diff --git a/source/brailleDisplayDrivers/albatross/driver.py b/source/brailleDisplayDrivers/albatross/driver.py index 976f4a38c41..55a8568268e 100644 --- a/source/brailleDisplayDrivers/albatross/driver.py +++ b/source/brailleDisplayDrivers/albatross/driver.py @@ -12,7 +12,7 @@ import time from collections import deque -from bdDetect import DeviceType, DriverRegistrar +from bdDetect import DriverRegistrar, ProtocolType from logHandler import log from serial.win32 import ( PURGE_RXABORT, @@ -85,7 +85,7 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver): @classmethod def registerAutomaticDetection(cls, driverRegistrar: DriverRegistrar): driverRegistrar.addUsbDevice( - DeviceType.SERIAL, + ProtocolType.SERIAL, VID_AND_PID, # Caiku Albatross 46/80 # Filter for bus reported device description, which should be "Albatross Braille Display". matchFunc=lambda match: match.deviceInfo.get("busReportedDeviceDescription") == BUS_DEVICE_DESC, diff --git a/source/brailleDisplayDrivers/alva.py b/source/brailleDisplayDrivers/alva.py index 0402bf31692..73f8aaf63be 100644 --- a/source/brailleDisplayDrivers/alva.py +++ b/source/brailleDisplayDrivers/alva.py @@ -158,7 +158,7 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject): @classmethod def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): driverRegistrar.addUsbDevices( - bdDetect.DeviceType.HID, + bdDetect.ProtocolType.HID, { "VID_0798&PID_0640", # BC640 "VID_0798&PID_0680", # BC680 @@ -216,7 +216,7 @@ def __init__(self, port="auto"): self._deviceId = None for portType, portId, port, portInfo in self._getTryPorts(port): - self.isHid = portType == bdDetect.DeviceType.HID + self.isHid = portType == bdDetect.ProtocolType.HID # Try talking to the display. try: if self.isHid: diff --git a/source/brailleDisplayDrivers/baum.py b/source/brailleDisplayDrivers/baum.py index 5ba90c7cc36..e734cf7e234 100644 --- a/source/brailleDisplayDrivers/baum.py +++ b/source/brailleDisplayDrivers/baum.py @@ -84,7 +84,7 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver): @classmethod def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): driverRegistrar.addUsbDevices( - bdDetect.DeviceType.HID, + bdDetect.ProtocolType.HID, { "VID_0904&PID_3001", # RefreshaBraille 18 "VID_0904&PID_6101", # VarioUltra 20 @@ -111,7 +111,7 @@ def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): ) driverRegistrar.addUsbDevices( - bdDetect.DeviceType.SERIAL, + bdDetect.ProtocolType.SERIAL, { "VID_0403&PID_FE70", # Vario 40 "VID_0403&PID_FE71", # PocketVario @@ -164,7 +164,7 @@ def __init__(self, port="auto"): for portType, portId, port, portInfo in self._getTryPorts(port): # At this point, a port bound to this display has been found. # Try talking to the display. - self.isHid = portType == bdDetect.DeviceType.HID + self.isHid = portType == bdDetect.ProtocolType.HID try: if self.isHid: self._dev = hwIo.Hid(port, onReceive=self._onReceive) diff --git a/source/brailleDisplayDrivers/brailleNote.py b/source/brailleDisplayDrivers/brailleNote.py index e3ca3b471de..3d843cf8e3d 100644 --- a/source/brailleDisplayDrivers/brailleNote.py +++ b/source/brailleDisplayDrivers/brailleNote.py @@ -130,7 +130,7 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver): @classmethod def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): driverRegistrar.addUsbDevices( - bdDetect.DeviceType.SERIAL, + bdDetect.ProtocolType.SERIAL, { "VID_1C71&PID_C004", # Apex }, diff --git a/source/brailleDisplayDrivers/brailliantB.py b/source/brailleDisplayDrivers/brailliantB.py index 58f0e9ad324..cea8bb80658 100644 --- a/source/brailleDisplayDrivers/brailliantB.py +++ b/source/brailleDisplayDrivers/brailliantB.py @@ -90,7 +90,7 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver): @classmethod def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): driverRegistrar.addUsbDevices( - bdDetect.DeviceType.HID, + bdDetect.ProtocolType.HID, { "VID_1C71&PID_C111", # Mantis Q 40 "VID_1C71&PID_C101", # Chameleon 20 @@ -100,7 +100,7 @@ def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): matchFunc=bdDetect.HIDUsagePageMatchFuncFactory(HID_USAGE_PAGE), ) driverRegistrar.addUsbDevices( - bdDetect.DeviceType.HID, + bdDetect.ProtocolType.HID, { "VID_1C71&PID_C121", # Humanware BrailleOne 20 HID "VID_1C71&PID_CE01", # NLS eReader 20 HID @@ -111,7 +111,7 @@ def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): }, ) driverRegistrar.addUsbDevices( - bdDetect.DeviceType.SERIAL, + bdDetect.ProtocolType.SERIAL, { "VID_1C71&PID_C005", # Brailliant BI 32, 40 and 80 "VID_1C71&PID_C021", # Brailliant BI 14 @@ -119,13 +119,13 @@ def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): ) driverRegistrar.addBluetoothDevices( lambda m: ( - m.type == bdDetect.DeviceType.SERIAL + m.type == bdDetect.ProtocolType.SERIAL and ( m.id.startswith("Brailliant B") or m.id == "Brailliant 80" or "BrailleNote Touch" in m.id ) ) or ( - m.type == bdDetect.DeviceType.HID + m.type == bdDetect.ProtocolType.HID and m.deviceInfo.get("manufacturer") == "Humanware" and ( ( @@ -158,7 +158,7 @@ def __init__(self, port="auto"): self.numCells = 0 for portType, portId, port, portInfo in self._getTryPorts(port): - self.isHid = portType == bdDetect.DeviceType.HID + self.isHid = portType == bdDetect.ProtocolType.HID # Try talking to the display. try: if self.isHid: diff --git a/source/brailleDisplayDrivers/eurobraille/driver.py b/source/brailleDisplayDrivers/eurobraille/driver.py index 2911d90b9fc..77d22c97df8 100644 --- a/source/brailleDisplayDrivers/eurobraille/driver.py +++ b/source/brailleDisplayDrivers/eurobraille/driver.py @@ -45,7 +45,7 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject): @classmethod def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): driverRegistrar.addUsbDevices( - bdDetect.DeviceType.HID, + bdDetect.ProtocolType.HID, { "VID_C251&PID_1122", # Esys (version < 3.0, no SD card "VID_C251&PID_1123", # Esys (version >= 3.0, with HID keyboard, no SD card @@ -67,7 +67,7 @@ def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): }, ) driverRegistrar.addUsbDevices( - bdDetect.DeviceType.SERIAL, + bdDetect.ProtocolType.SERIAL, { "VID_28AC&PID_0012", # b.note "VID_28AC&PID_0013", # b.note 2 @@ -97,7 +97,7 @@ def __init__(self, port="Auto"): for portType, portId, port, portInfo in self._getTryPorts(port): # At this point, a port bound to this display has been found. # Try talking to the display. - self.isHid = portType == bdDetect.DeviceType.HID + self.isHid = portType == bdDetect.ProtocolType.HID try: if self.isHid: self._dev = hwIo.Hid( @@ -141,7 +141,7 @@ def __init__(self, port="Auto"): port=port, ), ) - if self.deviceType.startswith(("bnote", "bbook")): + if self.ProtocolType.startswith(("bnote", "bbook")): # send identifier to bnote / bbook with current COM port comportNumber = f'{int(re.match(".*?([0-9]+)$", port).group(1)):02d}' identifier = f"NVDA/{comportNumber}".encode() @@ -158,7 +158,7 @@ def __init__(self, port="Auto"): def terminate(self): try: - if self.deviceType.startswith(("bnote", "bbook")): + if self.ProtocolType.startswith(("bnote", "bbook")): # reset identifier to bnote / bbook with current COM port self._sendPacket(constants.EB_SYSTEM, constants.EB_CONNECTION_NAME, b"") super().terminate() @@ -287,7 +287,7 @@ def _handleKeyPacket(self, group: bytes, data: bytes): log.debug("Ignoring key repetition") return self.keysDown[group] |= arg - isIris = self.deviceType.startswith("Iris") + isIris = self.ProtocolType.startswith("Iris") if not isIris and group == constants.EB_KEY_COMMAND and arg >= self.keysDown[group]: # Started a gesture including command keys self._ignoreCommandKeyReleases = False diff --git a/source/brailleDisplayDrivers/eurobraille/gestures.py b/source/brailleDisplayDrivers/eurobraille/gestures.py index b02e307cb8b..4e056b4ebd6 100644 --- a/source/brailleDisplayDrivers/eurobraille/gestures.py +++ b/source/brailleDisplayDrivers/eurobraille/gestures.py @@ -162,7 +162,7 @@ class InputGesture(braille.BrailleDisplayGesture, brailleInput.BrailleInputGestu def __init__(self, display: "BrailleDisplayDriver"): super().__init__() - self.model = display.deviceType.lower().split(" ")[0] + self.model = display.ProtocolType.lower().split(" ")[0] keysDown = dict(display.keysDown) self.keyNames = names = [] diff --git a/source/brailleDisplayDrivers/freedomScientific.py b/source/brailleDisplayDrivers/freedomScientific.py index fe00e79e721..ce6ebb00901 100755 --- a/source/brailleDisplayDrivers/freedomScientific.py +++ b/source/brailleDisplayDrivers/freedomScientific.py @@ -199,7 +199,7 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject): @classmethod def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): driverRegistrar.addUsbDevices( - bdDetect.DeviceType.CUSTOM, + bdDetect.ProtocolType.CUSTOM, { "VID_0F4E&PID_0100", # Focus 1 "VID_0F4E&PID_0111", # PAC Mate @@ -243,7 +243,7 @@ def __init__(self, port="auto"): self.gestureMap.add("br(freedomScientific):rightWizWheelDown", *action[2]) super(BrailleDisplayDriver, self).__init__() for portType, portId, port, portInfo in self._getTryPorts(port): - self.isUsb = portType == bdDetect.DeviceType.CUSTOM + self.isUsb = portType == bdDetect.ProtocolType.CUSTOM # Try talking to the display. try: if self.isUsb: diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index a5a808027ef..e68555bf72d 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -689,7 +689,7 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject): @classmethod def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): driverRegistrar.addUsbDevices( - bdDetect.DeviceType.SERIAL, + bdDetect.ProtocolType.SERIAL, { "VID_0403&PID_6001", # FTDI chip "VID_0921&PID_1200", # GoHubs chip @@ -698,7 +698,7 @@ def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): # Newer Handy Tech displays have a native HID processor driverRegistrar.addUsbDevices( - bdDetect.DeviceType.HID, + bdDetect.ProtocolType.HID, { "VID_1FE4&PID_0054", # Active Braille "VID_1FE4&PID_0055", # Connect Braille @@ -723,7 +723,7 @@ def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): # Some older HT displays use a HID converter and an internal serial interface driverRegistrar.addUsbDevices( - bdDetect.DeviceType.HID, + bdDetect.ProtocolType.HID, { "VID_1FE4&PID_0003", # USB-HID adapter "VID_1FE4&PID_0074", # Braille Star 40 @@ -774,7 +774,7 @@ def __init__(self, port="auto"): for portType, portId, port, portInfo in self._getTryPorts(port): # At this point, a port bound to this display has been found. # Try talking to the display. - self.isHid = portType == bdDetect.DeviceType.HID + self.isHid = portType == bdDetect.ProtocolType.HID self.isHidSerial = portId in USB_IDS_HID_CONVERTER self.port = port try: diff --git a/source/brailleDisplayDrivers/hidBrailleStandard.py b/source/brailleDisplayDrivers/hidBrailleStandard.py index 3e06d5e3912..c7e27ea5300 100644 --- a/source/brailleDisplayDrivers/hidBrailleStandard.py +++ b/source/brailleDisplayDrivers/hidBrailleStandard.py @@ -95,7 +95,7 @@ def __init__(self, port="auto"): self.numCols = 0 for portType, portId, port, portInfo in self._getTryPorts(port): - if portType != bdDetect.DeviceType.HID: + if portType != bdDetect.ProtocolType.HID: continue # Try talking to the display. try: diff --git a/source/brailleDisplayDrivers/hims.py b/source/brailleDisplayDrivers/hims.py index a129c8c68fd..25bd648bebd 100644 --- a/source/brailleDisplayDrivers/hims.py +++ b/source/brailleDisplayDrivers/hims.py @@ -281,20 +281,20 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver): @classmethod def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): deviceTypes = { - bdDetect.DeviceType.HID: ( + bdDetect.ProtocolType.HID: ( { "VID_045E&PID_940A", # Braille Edge3S 40 }, True, ), - bdDetect.DeviceType.CUSTOM: ( + bdDetect.ProtocolType.CUSTOM: ( { "VID_045E&PID_930A", # Braille Sense & Smart Beetle "VID_045E&PID_930B", # Braille EDGE 40 }, False, ), - bdDetect.DeviceType.SERIAL: ( + bdDetect.ProtocolType.SERIAL: ( { "VID_0403&PID_6001", "VID_1A86&PID_55D3", # Braille Edge2S 40 @@ -329,17 +329,17 @@ def __init__(self, port="auto"): for match in self._getTryPorts(port): portType, portId, port, portInfo = match - self.isBulk = portType == bdDetect.DeviceType.CUSTOM - self.isHID = portType == bdDetect.DeviceType.HID + self.isBulk = portType == bdDetect.ProtocolType.CUSTOM + self.isHID = portType == bdDetect.ProtocolType.HID # Try talking to the display. try: match portType: - case bdDetect.DeviceType.HID: + case bdDetect.ProtocolType.HID: self._dev = hwIo.Hid(port, onReceive=self._hidOnReceive) - case bdDetect.DeviceType.CUSTOM: + case bdDetect.ProtocolType.CUSTOM: # onReceiveSize based on max packet size according to USB endpoint information. self._dev = hwIo.Bulk(port, 0, 1, self._onReceive, onReceiveSize=64) - case bdDetect.DeviceType.SERIAL: + case bdDetect.ProtocolType.SERIAL: self._dev = hwIo.Serial( port, baudrate=BAUD_RATE, diff --git a/source/brailleDisplayDrivers/nattiqbraille.py b/source/brailleDisplayDrivers/nattiqbraille.py index d83955d8130..9088ed5c080 100644 --- a/source/brailleDisplayDrivers/nattiqbraille.py +++ b/source/brailleDisplayDrivers/nattiqbraille.py @@ -39,7 +39,7 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver): @classmethod def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): driverRegistrar.addUsbDevices( - bdDetect.DeviceType.SERIAL, + bdDetect.ProtocolType.SERIAL, { "VID_2341&PID_8036", # Atmel-based USB Serial for Nattiq nBraille }, diff --git a/source/brailleDisplayDrivers/seikantk.py b/source/brailleDisplayDrivers/seikantk.py index 28beb399ae2..e7e1b0d16b4 100644 --- a/source/brailleDisplayDrivers/seikantk.py +++ b/source/brailleDisplayDrivers/seikantk.py @@ -16,7 +16,7 @@ import serial import braille -from bdDetect import DeviceType, DeviceMatch, DriverRegistrar +from bdDetect import DeviceMatch, DriverRegistrar import brailleInput import inputCore import bdDetect @@ -107,7 +107,7 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver): @classmethod def registerAutomaticDetection(cls, driverRegistrar: DriverRegistrar): driverRegistrar.addUsbDevices( - DeviceType.HID, + bdDetect.ProtocolType.HID, { vidpid, # Seika Notetaker }, @@ -134,8 +134,8 @@ def __init__(self, port: typing.Union[None, str, DeviceMatch]): log.debug(f"Seika Notetaker braille driver: ({port!r})") dev: typing.Optional[typing.Union[hwIo.Hid, hwIo.Serial]] = None for match in self._getTryPorts(port): - self.isHid = match.type == bdDetect.DeviceType.HID - self.isSerial = match.type == bdDetect.DeviceType.SERIAL + self.isHid = match.type == bdDetect.ProtocolType.HID + self.isSerial = match.type == bdDetect.ProtocolType.SERIAL try: if self.isHid: log.info("Trying Seika notetaker on USB-HID") diff --git a/source/brailleDisplayDrivers/superBrl.py b/source/brailleDisplayDrivers/superBrl.py index 10a28ee856a..beef924445f 100644 --- a/source/brailleDisplayDrivers/superBrl.py +++ b/source/brailleDisplayDrivers/superBrl.py @@ -34,7 +34,7 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver): @classmethod def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar): driverRegistrar.addUsbDevices( - bdDetect.DeviceType.SERIAL, + bdDetect.ProtocolType.SERIAL, { "VID_10C4&PID_EA60", # SuperBraille 3.2 }, diff --git a/tests/unit/test_bdDetect.py b/tests/unit/test_bdDetect.py index 7a85583d376..180bb0f9b05 100644 --- a/tests/unit/test_bdDetect.py +++ b/tests/unit/test_bdDetect.py @@ -1,7 +1,7 @@ # A part of NonVisual Desktop Access (NVDA) # This file is covered by the GNU General Public License. # See the file COPYING for more details. -# Copyright (C) 2023 NV Access Limited, Babbage B.V., Leonard de Ruijter +# Copyright (C) 2023-2025 NV Access Limited, Babbage B.V., Leonard de Ruijter """Unit tests for the bdDetect module.""" diff --git a/user_docs/en/changes.md b/user_docs/en/changes.md index 09ac77a8150..8fcba6a107a 100644 --- a/user_docs/en/changes.md +++ b/user_docs/en/changes.md @@ -165,6 +165,7 @@ Please use `braille.filter_displayDimensions` instead. (#17011) * The following symbols are deprecated (#17486, @CyrilleB79): * `NoConsoleOptionParser`, `stringToBool`, `stringToLang` in `__main__`; use the same symbols in `argsParsing` instead. * `__main__.parser`; use `argsParsing.getParser()` instead. +* `bdDetect.DeviceType` is deprecated in favour of `bdDetect.ProtocolType` and `bdDetect.CommunicationType` to take into account the fact that both HID and Serial communication can take place over USB and Bluetooth. (#17537 , @LeonarddeR) ## 2024.4.1 From 0be066ceab6d3d97cd6239db5b3c1e955a2de926 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Fri, 27 Dec 2024 21:24:17 +0100 Subject: [PATCH 12/21] Use communication types --- source/bdDetect.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/bdDetect.py b/source/bdDetect.py index 8fa0e297f68..43667e131f7 100644 --- a/source/bdDetect.py +++ b/source/bdDetect.py @@ -214,7 +214,7 @@ def getDriversForConnectedUsbDevices( ( DeviceMatch(ProtocolType.HID, port["usbID"], port["devicePath"], port) for port in deviceInfoFetcher.hidDevices - if port["provider"] == "usb" + if port["provider"] == CommunicationType.USB ) ) @@ -288,7 +288,7 @@ def getDriversForPossibleBluetoothDevices( ( DeviceMatch(ProtocolType.HID, port["hardwareID"], port["devicePath"], port) for port in deviceInfoFetcher.hidDevices - if port["provider"] == "bluetooth" + if port["provider"] == CommunicationType.BLUETOOTH ) ) for match in itertools.chain(btSerialMatchesForCustom, btHidDevMatchesForCustom): @@ -551,7 +551,7 @@ def getConnectedUsbDevicesForDriver(driver: str) -> Iterator[DeviceMatch]: ( DeviceMatch(ProtocolType.HID, port["usbID"], port["devicePath"], port) for port in deviceInfoFetcher.hidDevices - if port["provider"] == "usb" + if port["provider"] == CommunicationType.USB ), ( DeviceMatch(ProtocolType.SERIAL, port["usbID"], port["port"], port) @@ -599,7 +599,7 @@ def getPossibleBluetoothDevicesForDriver(driver: str) -> Iterator[DeviceMatch]: ( DeviceMatch(ProtocolType.HID, port["hardwareID"], port["devicePath"], port) for port in deviceInfoFetcher.hidDevices - if port["provider"] == "bluetooth" + if port["provider"] == CommunicationType.BLUETOOTH ), ) for match in btDevs: From 6908f9d60d7a6328f01752b690eaae3ffe755919 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Fri, 27 Dec 2024 21:29:36 +0100 Subject: [PATCH 13/21] Renames --- source/bdDetect.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/bdDetect.py b/source/bdDetect.py index 43667e131f7..f2fc53ce514 100644 --- a/source/bdDetect.py +++ b/source/bdDetect.py @@ -223,10 +223,10 @@ def getDriversForConnectedUsbDevices( for driver, devs in _driverDevices.items(): if limitToDevices and driver not in limitToDevices: continue - typeDefs = devs[CommunicationType.USB] - for typeDef in typeDefs: - if typeDef.matches(match): - if typeDef.useAsFallback: + usbDefinitions = devs[CommunicationType.USB] + for definition in usbDefinitions: + if definition.matches(match): + if definition.useAsFallback: fallbackDriversAndMatches.append({driver, match}) else: yield (driver, match) @@ -566,10 +566,10 @@ def getConnectedUsbDevicesForDriver(driver: str) -> Iterator[DeviceMatch]: if _isHIDBrailleMatch(match): yield match else: - devs = _driverDevices[driver][CommunicationType.USB] - for dev in devs: - if dev.matches(match): - if dev.useAsFallback: + usbDefinitions = _driverDevices[driver][CommunicationType.USB] + for definition in usbDefinitions: + if definition.matches(match): + if definition.useAsFallback: fallbackMatches.append(match) else: yield match From 3a485cd46f3530b3137f6a1b0ced49e8938e0a6f Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter <3049216+LeonarddeR@users.noreply.github.com> Date: Mon, 30 Dec 2024 08:18:10 +0100 Subject: [PATCH 14/21] Update source/brailleDisplayDrivers/eurobraille/driver.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- source/brailleDisplayDrivers/eurobraille/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/brailleDisplayDrivers/eurobraille/driver.py b/source/brailleDisplayDrivers/eurobraille/driver.py index 77d22c97df8..a860263e99a 100644 --- a/source/brailleDisplayDrivers/eurobraille/driver.py +++ b/source/brailleDisplayDrivers/eurobraille/driver.py @@ -141,7 +141,7 @@ def __init__(self, port="Auto"): port=port, ), ) - if self.ProtocolType.startswith(("bnote", "bbook")): +if self.deviceType.startswith(("bnote", "bbook")): # send identifier to bnote / bbook with current COM port comportNumber = f'{int(re.match(".*?([0-9]+)$", port).group(1)):02d}' identifier = f"NVDA/{comportNumber}".encode() From edcfe14b4c736d84919abf0b20bc4307afb6170a Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter <3049216+LeonarddeR@users.noreply.github.com> Date: Mon, 30 Dec 2024 08:18:32 +0100 Subject: [PATCH 15/21] Update source/brailleDisplayDrivers/eurobraille/driver.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- source/brailleDisplayDrivers/eurobraille/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/brailleDisplayDrivers/eurobraille/driver.py b/source/brailleDisplayDrivers/eurobraille/driver.py index a860263e99a..a16bee1abce 100644 --- a/source/brailleDisplayDrivers/eurobraille/driver.py +++ b/source/brailleDisplayDrivers/eurobraille/driver.py @@ -158,7 +158,7 @@ def __init__(self, port="Auto"): def terminate(self): try: - if self.ProtocolType.startswith(("bnote", "bbook")): +if self.deviceType.startswith(("bnote", "bbook")): # reset identifier to bnote / bbook with current COM port self._sendPacket(constants.EB_SYSTEM, constants.EB_CONNECTION_NAME, b"") super().terminate() From a3e129ae1f42c4e4b240e95cc693cd788799107f Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter <3049216+LeonarddeR@users.noreply.github.com> Date: Thu, 2 Jan 2025 08:18:55 +0100 Subject: [PATCH 16/21] Update source/brailleDisplayDrivers/brailliantB.py --- source/brailleDisplayDrivers/brailliantB.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/source/brailleDisplayDrivers/brailliantB.py b/source/brailleDisplayDrivers/brailliantB.py index 6d33c4e0794..e411a7c8b31 100644 --- a/source/brailleDisplayDrivers/brailliantB.py +++ b/source/brailleDisplayDrivers/brailliantB.py @@ -162,9 +162,6 @@ def __init__(self, port="auto"): # Try talking to the display. try: if self.isHid: - if (usasePage := portInfo.get("HIDUsagePage")) != HID_USAGE_PAGE: - log.debugWarning(f"Ignoring device {port!r} with usage page {usasePage!r}") - continue self._dev = hwIo.Hid(port, onReceive=self._hidOnReceive) else: self._dev = hwIo.Serial( From 2baa9a98d5da1399f04a665b81d7c03bf520ca50 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter <3049216+LeonarddeR@users.noreply.github.com> Date: Thu, 2 Jan 2025 08:29:18 +0100 Subject: [PATCH 17/21] Update source/brailleDisplayDrivers/eurobraille/driver.py --- source/brailleDisplayDrivers/eurobraille/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/brailleDisplayDrivers/eurobraille/driver.py b/source/brailleDisplayDrivers/eurobraille/driver.py index a16bee1abce..2e26c43b2e2 100644 --- a/source/brailleDisplayDrivers/eurobraille/driver.py +++ b/source/brailleDisplayDrivers/eurobraille/driver.py @@ -287,7 +287,7 @@ def _handleKeyPacket(self, group: bytes, data: bytes): log.debug("Ignoring key repetition") return self.keysDown[group] |= arg - isIris = self.ProtocolType.startswith("Iris") + isIris = self.deviceType.startswith("Iris") if not isIris and group == constants.EB_KEY_COMMAND and arg >= self.keysDown[group]: # Started a gesture including command keys self._ignoreCommandKeyReleases = False From b0283da4801b758c0227249cac215e2d3f84c471 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 2 Jan 2025 08:13:37 +0100 Subject: [PATCH 18/21] Use StrEnum again --- source/bdDetect.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/bdDetect.py b/source/bdDetect.py index f2fc53ce514..6a236018018 100644 --- a/source/bdDetect.py +++ b/source/bdDetect.py @@ -17,7 +17,7 @@ import itertools import threading from concurrent.futures import ThreadPoolExecutor, Future -from enum import Enum +from enum import StrEnum from typing import ( Any, Callable, @@ -53,7 +53,7 @@ USB_ID_REGEX = re.compile(r"^VID_[0-9A-F]{4}&PID_[0-9A-F]{4}$", re.U) -class ProtocolType(str, Enum): +class ProtocolType(StrEnum): HID = "hid" """HID devices""" SERIAL = "serial" @@ -62,7 +62,7 @@ class ProtocolType(str, Enum): """Devices with a manufacturer specific protocol""" -class CommunicationType(str, Enum): +class CommunicationType(StrEnum): BLUETOOTH = "bluetooth" """Bluetooth devices""" USB = "usb" @@ -82,7 +82,7 @@ def __getattr__(cls, name: str) -> ProtocolType | CommunicationType: repl = cls._mapping.get(name) if repl is not None and NVDAState._allowDeprecatedAPI(): log.warning( - f"{cls.__name__}.{name} is deprecated. Use {repl} instead.", + f"{cls.__name__}.{name} is deprecated. Use {repl.__class__.__name__}.{repl} instead.", ) return repl raise AttributeError(f"'{cls.__name__}' object has no attribute '{name}'") @@ -112,7 +112,7 @@ def __getattr__(attrName: str) -> Any: if attrName in _deprecatedConstantsMap and NVDAState._allowDeprecatedAPI(): replacementSymbol = _deprecatedConstantsMap[attrName] log.warning( - f"{attrName} is deprecated. " f"Use bdDetect.{replacementSymbol} instead. ", + f"{attrName} is deprecated. " f"Use bdDetect.{replacementSymbol.__class__.__name__}.{replacementSymbol.value} instead. ", ) return replacementSymbol.value raise AttributeError(f"module {repr(__name__)} has no attribute {repr(attrName)}") From c176ce6b0949a16f2e379fe6a2e2103a754f199b Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 2 Jan 2025 08:14:09 +0100 Subject: [PATCH 19/21] Fix eurobraille mistakes due to coderabbit suggestions --- source/brailleDisplayDrivers/eurobraille/driver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/brailleDisplayDrivers/eurobraille/driver.py b/source/brailleDisplayDrivers/eurobraille/driver.py index 2e26c43b2e2..ddad0ad50db 100644 --- a/source/brailleDisplayDrivers/eurobraille/driver.py +++ b/source/brailleDisplayDrivers/eurobraille/driver.py @@ -141,7 +141,7 @@ def __init__(self, port="Auto"): port=port, ), ) -if self.deviceType.startswith(("bnote", "bbook")): + if self.deviceType.startswith(("bnote", "bbook")): # send identifier to bnote / bbook with current COM port comportNumber = f'{int(re.match(".*?([0-9]+)$", port).group(1)):02d}' identifier = f"NVDA/{comportNumber}".encode() @@ -158,7 +158,7 @@ def __init__(self, port="Auto"): def terminate(self): try: -if self.deviceType.startswith(("bnote", "bbook")): + if self.deviceType.startswith(("bnote", "bbook")): # reset identifier to bnote / bbook with current COM port self._sendPacket(constants.EB_SYSTEM, constants.EB_CONNECTION_NAME, b"") super().terminate() @@ -173,7 +173,7 @@ def terminate(self): def _prepFirstByteStreamAndData( self, data: bytes, - ) -> (bytes, Union[BytesIO, hwIo.IoBase], bytes): + ) -> tuple[bytes, Union[BytesIO, hwIo.IoBase], bytes]: if self.isHid: # data contains the entire packet. # HID Packets start with 0x00. From 47e6fc5fee004e8a74294e01824f45c33d1ec2f6 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 2 Jan 2025 08:23:43 +0100 Subject: [PATCH 20/21] Add doc string --- source/bdDetect.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/source/bdDetect.py b/source/bdDetect.py index 6a236018018..03cb71ef710 100644 --- a/source/bdDetect.py +++ b/source/bdDetect.py @@ -112,7 +112,8 @@ def __getattr__(attrName: str) -> Any: if attrName in _deprecatedConstantsMap and NVDAState._allowDeprecatedAPI(): replacementSymbol = _deprecatedConstantsMap[attrName] log.warning( - f"{attrName} is deprecated. " f"Use bdDetect.{replacementSymbol.__class__.__name__}.{replacementSymbol.value} instead. ", + f"{attrName} is deprecated. " + f"Use bdDetect.{replacementSymbol.__class__.__name__}.{replacementSymbol.value} instead. ", ) return replacementSymbol.value raise AttributeError(f"module {repr(__name__)} has no attribute {repr(attrName)}") @@ -261,6 +262,11 @@ def _isHIDBrailleMatch(match: DeviceMatch) -> bool: def HIDUsagePageMatchFuncFactory(usagePage: int) -> MatchFuncT: + """ + Creates a match function that checks if a given HID usage page matches the specified usage page. + :param usagePage: The HID usage page to match against. + :returns: A partial function that takes an HID usage page and returns True if it matches the specified usage page, False otherwise. + """ return partial(_isHIDUsagePageMatch, usagePage=usagePage) From 543f783a965c846478592a40a540d7fa5f7d1f83 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 2 Jan 2025 08:32:24 +0100 Subject: [PATCH 21/21] Undo more unrelated/faulty changes --- source/brailleDisplayDrivers/eurobraille/gestures.py | 2 +- tests/unit/test_bdDetect.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/brailleDisplayDrivers/eurobraille/gestures.py b/source/brailleDisplayDrivers/eurobraille/gestures.py index 4e056b4ebd6..b02e307cb8b 100644 --- a/source/brailleDisplayDrivers/eurobraille/gestures.py +++ b/source/brailleDisplayDrivers/eurobraille/gestures.py @@ -162,7 +162,7 @@ class InputGesture(braille.BrailleDisplayGesture, brailleInput.BrailleInputGestu def __init__(self, display: "BrailleDisplayDriver"): super().__init__() - self.model = display.ProtocolType.lower().split(" ")[0] + self.model = display.deviceType.lower().split(" ")[0] keysDown = dict(display.keysDown) self.keyNames = names = [] diff --git a/tests/unit/test_bdDetect.py b/tests/unit/test_bdDetect.py index 180bb0f9b05..7a85583d376 100644 --- a/tests/unit/test_bdDetect.py +++ b/tests/unit/test_bdDetect.py @@ -1,7 +1,7 @@ # A part of NonVisual Desktop Access (NVDA) # This file is covered by the GNU General Public License. # See the file COPYING for more details. -# Copyright (C) 2023-2025 NV Access Limited, Babbage B.V., Leonard de Ruijter +# Copyright (C) 2023 NV Access Limited, Babbage B.V., Leonard de Ruijter """Unit tests for the bdDetect module."""