diff --git a/source/brailleDisplayDrivers/brailliantB.py b/source/brailleDisplayDrivers/brailliantB.py index 67670d80e9d..730ea1552b9 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. @@ -147,6 +148,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( 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 0ebf692d4e0..01af04dad80 100644 --- a/user_docs/en/changes.md +++ b/user_docs/en/changes.md @@ -4,6 +4,9 @@ ### Bug Fixes +* 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. (#17522, @LeonarddeR) +* The Humanware Brailliant driver is now more reliable in selecting the right connection endpoint, resulting in better connection stability and less errors. (#17522, @LeonarddeR) + ## 2024.4.1 This is a patch release to fix a bug when saving speech symbol dictionaries.