Repository: nvaccess/nvda

Issue:
Unstable auto detect for Humanware Brailliant and APH Mantis Q40 braille displays
### Steps to reproduce:
1. Pair a Mantis Q40 over Bluetooth
2. Start NVDA with braille auto detection enabled.

### Actual behavior:
NVDA sometimes fails detecting the display, because it tries the HID prottocol driver first. This driver reports as available but is not supported by the display. Interesting enough, as soon as you select the HID driver manually, NVDA connects to the display but the displays stays empty. When you open the display dialog again, nothing is selected and the HID driver disappeared.
Note that sometimes, NVDA manages to connect to the display with the Brailliant driver, but behavior is pretty instable on my system. 

### Expected behavior:
Direct connection using the Brailliant driver when using auto detect.

### System configuration
#### NVDA installed/portable/running from source:
Installed

#### NVDA version:
2021.3

#### Windows version:
Windows 11 22000.348

### Other questions
#### Does the issue still occur after restarting your computer?
Yes

#### Have you tried any other versions of NVDA? If so, please report their behaviors.
Same behavior as current Alpha.

#### If NVDA add-ons are disabled, is your problem still occurring?
Yes

#### Does the issue still occur after you run the COM Registration Fixing Tool in NVDA's tools menu?
Yes


Discussion:

Resolving patch:
diff --git a/source/bdDetect.py b/source/bdDetect.py
--- a/source/bdDetect.py
+++ b/source/bdDetect.py
@@ -123,13 +123,16 @@ def getDriversForConnectedUsbDevices():
 			for port in deviceInfoFetcher.comPorts if "usbID" in port)
 	)
 	for match in usbDevs:
-		# check for the Braille HID protocol before any other device matching.
-		if match.type == KEY_HID and match.deviceInfo.get('HIDUsagePage') == HID_USAGE_PAGE_BRAILLE:
-			yield ("hid", match)
 		for driver, devs in _driverDevices.items():
 			for type, ids in devs.items():
 				if match.type==type and match.id in ids:
 					yield driver, match
+		# Check for the Braille HID protocol after any other device matching.
+		# This ensures that a vendor specific driver is preferred over the braille HID protocol.
+		# This preference may change in the future.
+		if match.type == KEY_HID and match.deviceInfo.get('HIDUsagePage') == HID_USAGE_PAGE_BRAILLE:
+			yield ("hid", match)
+
 
 def getDriversForPossibleBluetoothDevices():
 	"""Get any matching drivers for possible Bluetooth devices.
@@ -144,15 +147,18 @@ def getDriversForPossibleBluetoothDevices():
 			for port in deviceInfoFetcher.hidDevices if port["provider"]=="bluetooth"),
 	)
 	for match in btDevs:
-		# check for the Braille HID protocol before any other device matching.
-		if match.type == KEY_HID and match.deviceInfo.get('HIDUsagePage') == HID_USAGE_PAGE_BRAILLE:
-			yield ("hid", match)
 		for driver, devs in _driverDevices.items():
 			matchFunc = devs[KEY_BLUETOOTH]
 			if not callable(matchFunc):
 				continue
 			if matchFunc(match):
 				yield driver, match
+		# Check for the Braille HID protocol after any other device matching.
+		# This ensures that a vendor specific driver is preferred over the braille HID protocol.
+		# This preference may change in the future.
+		if match.type == KEY_HID and match.deviceInfo.get('HIDUsagePage') == HID_USAGE_PAGE_BRAILLE:
+			yield ("hid", match)
+
 
 class _DeviceInfoFetcher(AutoPropertyObject):
 	"""Utility class that caches fetched info for available devices for the duration of one core pump cycle."""
@@ -343,7 +349,6 @@ def getConnectedUsbDevicesForDriver(driver) -> Iterable[DeviceMatch]:
 			for port in deviceInfoFetcher.comPorts if "usbID" in port)
 	)
 	for match in usbDevs:
-		# check for the Braille HID protocol before any other device matching.
 		if driver == "hid":
 			if match.type == KEY_HID and match.deviceInfo.get('HIDUsagePage') == HID_USAGE_PAGE_BRAILLE:
 				yield match
@@ -361,7 +366,6 @@ def getPossibleBluetoothDevicesForDriver(driver) -> Iterable[DeviceMatch]:
 	@raise LookupError: If there is no detection data for this driver.
 	"""
 	if driver == "hid":
-		# check for the Braille HID protocol before any other device matching.
 		def matchFunc(match):
 			return match.type == KEY_HID and match.deviceInfo.get('HIDUsagePage') == HID_USAGE_PAGE_BRAILLE
 	else:


Test patch: