Skip to content

Commit 45bceb0

Browse files
authored
Merge 378a24d into ba9215c
2 parents ba9215c + 378a24d commit 45bceb0

19 files changed

Lines changed: 727 additions & 538 deletions

source/bdDetect.py

Lines changed: 174 additions & 346 deletions
Large diffs are not rendered by default.

source/braille.py

Lines changed: 77 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from typing import (
1111
TYPE_CHECKING,
1212
Any,
13+
Callable,
1314
Dict,
1415
Generator,
1516
Iterable,
@@ -398,6 +399,7 @@ def _getDisplayDriver(moduleName: str, caseSensitive: bool = True) -> Type["Brai
398399
else:
399400
raise initialException
400401

402+
401403
def getDisplayList(excludeNegativeChecks=True) -> List[Tuple[str, str]]:
402404
"""Gets a list of available display driver names with their descriptions.
403405
@param excludeNegativeChecks: excludes all drivers for which the check method returns C{False}.
@@ -407,30 +409,23 @@ def getDisplayList(excludeNegativeChecks=True) -> List[Tuple[str, str]]:
407409
displayList = []
408410
# The display that should be placed at the end of the list.
409411
lastDisplay = None
410-
for loader, name, isPkg in pkgutil.iter_modules(brailleDisplayDrivers.__path__):
411-
if name.startswith('_'):
412-
continue
413-
try:
414-
display = _getDisplayDriver(name)
415-
except:
416-
log.error("Error while importing braille display driver %s" % name,
417-
exc_info=True)
418-
continue
412+
for display in getDisplayDrivers():
419413
try:
420414
if not excludeNegativeChecks or display.check():
421415
if display.name == "noBraille":
422416
lastDisplay = (display.name, display.description)
423417
else:
424418
displayList.append((display.name, display.description))
425419
else:
426-
log.debugWarning("Braille display driver %s reports as unavailable, excluding" % name)
420+
log.debugWarning(f"Braille display driver {display.name} reports as unavailable, excluding")
427421
except:
428422
log.error("", exc_info=True)
429423
displayList.sort(key=lambda d: strxfrm(d[1]))
430424
if lastDisplay:
431425
displayList.append(lastDisplay)
432426
return displayList
433427

428+
434429
class Region(object):
435430
"""A region of braille to be displayed.
436431
Each portion of braille to be displayed is represented by a region.
@@ -2613,9 +2608,19 @@ def handlePostConfigProfileSwitch(self):
26132608
# The display in the new profile is equal to the last requested display name
26142609
display == self._lastRequestedDisplayName
26152610
# or the new profile uses auto detection, which supports detection of the currently active display.
2616-
or (display == AUTO_DISPLAY_NAME and bdDetect.driverSupportsAutoDetection(self.display.name))
2611+
or (display == AUTO_DISPLAY_NAME and bdDetect.driverIsEnabledForAutoDetection(self.display.name))
26172612
):
26182613
self.setDisplayByName(display)
2614+
elif (
2615+
# Auto detection should be active
2616+
display == AUTO_DISPLAY_NAME and self._detector is not None
2617+
# And the current display should be no braille.
2618+
# If not, there is an active detector for the current driver
2619+
# to switch from bluetooth to USB.
2620+
and self.display.name == NO_BRAILLE_DISPLAY_NAME
2621+
):
2622+
self._detector._limitToDevices = bdDetect.getBrailleDisplayDriversEnabledForDetection()
2623+
26192624
self._tether = config.conf["braille"]["tetherTo"]
26202625

26212626
def handleDisplayUnavailable(self):
@@ -2757,6 +2762,11 @@ class BrailleDisplayDriver(driverHandler.Driver):
27572762
At a minimum, drivers must set L{name} and L{description} and override the L{check} method.
27582763
To display braille, L{numCells} and L{display} must be implemented.
27592764
2765+
To support automatic detection of braille displays belonging to this driver:
2766+
* The driver must be thread safe and L{isThreadSafe} should be set to C{True}
2767+
* L{supportsAutomaticDetection} must be set to C{True}.
2768+
* L{registerAutomaticDetection} must be implemented.
2769+
27602770
Drivers should dispatch input such as presses of buttons, wheels or other controls
27612771
using the L{inputCore} framework.
27622772
They should subclass L{BrailleDisplayGesture}
@@ -2779,19 +2789,19 @@ class BrailleDisplayDriver(driverHandler.Driver):
27792789
#: which means the rest of NVDA is not blocked while this occurs,
27802790
#: thus resulting in better performance.
27812791
#: This is also required to use the L{hwIo} module.
2782-
#: @type: bool
2783-
isThreadSafe = False
2792+
isThreadSafe: bool = False
2793+
#: Whether this driver is supported for automatic detection of braille displays.
2794+
supportsAutomaticDetection: bool = False
27842795
#: Whether displays for this driver return acknowledgements for sent packets.
27852796
#: L{_handleAck} should be called when an ACK is received.
27862797
#: Note that thread safety is required for the generic implementation to function properly.
27872798
#: If a display is not thread safe, a driver should manually implement ACK processing.
2788-
#: @type: bool
2789-
receivesAckPackets = False
2799+
receivesAckPackets: bool = False
27902800
#: Whether this driver is awaiting an Ack for a connected display.
27912801
#: This is set to C{True} after displaying cells when L{receivesAckPackets} is True,
27922802
#: and set to C{False} by L{_handleAck} or when C{timeout} has elapsed.
27932803
#: This is for internal use by NVDA core code only and shouldn't be touched by a driver itself.
2794-
_awaitingAck = False
2804+
_awaitingAck: bool = False
27952805
#: Maximum timeout to use for communication with a device (in seconds).
27962806
#: This can be used for serial connections.
27972807
#: Furthermore, it is used to stop waiting for missed acknowledgement packets.
@@ -2809,24 +2819,43 @@ def __init__(self, port: typing.Union[None, str, bdDetect.DeviceMatch] = None):
28092819
super().__init__()
28102820

28112821
@classmethod
2812-
def check(cls):
2822+
def check(cls) -> bool:
28132823
"""Determine whether this braille display is available.
28142824
The display will be excluded from the list of available displays if this method returns C{False}.
28152825
For example, if this display is not present, C{False} should be returned.
28162826
@return: C{True} if this display is available, C{False} if not.
2817-
@rtype: bool
28182827
"""
28192828
if cls.isThreadSafe:
2820-
if bdDetect.driverHasPossibleDevices(cls.name):
2821-
return True
2822-
try:
2823-
next(cls.getManualPorts())
2824-
except (StopIteration, NotImplementedError):
2825-
pass
2826-
else:
2829+
supportsAutomaticDetection = cls.supportsAutomaticDetection
2830+
if not supportsAutomaticDetection and NVDAState._allowDeprecatedAPI() and version_year < 2024:
2831+
log.warning(
2832+
"Starting from NVDA 2024.1, drivers that rely on bdDetect for the default check method "
2833+
"should have supportsAutomaticDetection set to True"
2834+
)
2835+
supportsAutomaticDetection = True
2836+
if supportsAutomaticDetection and bdDetect.driverHasPossibleDevices(cls.name):
28272837
return True
2838+
try:
2839+
next(cls.getManualPorts())
2840+
except (StopIteration, NotImplementedError):
2841+
pass
2842+
else:
2843+
return True
28282844
return False
28292845

2846+
@classmethod
2847+
def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar):
2848+
"""
2849+
This method may register the braille display driver in the braille display automatic detection framework.
2850+
The framework provides a L{bdDetect.DriverRegistrar} object as its only parameter.
2851+
The methods on the driver registrar can be used to register devices or device scanners.
2852+
This method should only register itself with the bdDetect framework,
2853+
and should refrain from doing anything else.
2854+
Drivers with L{supportsAutomaticDetection} set to C{True} must implement this method.
2855+
@param driverRegistrar: An object containing several methods to register device identifiers for this driver.
2856+
"""
2857+
raise NotImplementedError
2858+
28302859
def terminate(self):
28312860
"""Terminate this display driver.
28322861
This will be called when NVDA is finished with this display driver.
@@ -3227,3 +3256,26 @@ def getSerialPorts(filterFunc=None) -> typing.Iterator[typing.Tuple[str, str]]:
32273256
yield (info["port"],
32283257
# Translators: Name of a serial communications port.
32293258
_("Serial: {portName}").format(portName=info["friendlyName"]))
3259+
3260+
3261+
def getDisplayDrivers(
3262+
filterFunc: Optional[Callable[[Type[BrailleDisplayDriver]], bool]] = None
3263+
) -> Generator[Type[BrailleDisplayDriver], Any, Any]:
3264+
"""Gets an iterator of braille display drivers meeting the given filter callable.
3265+
@param filterFunc: an optional callable that receives a driver as its only argument and returns
3266+
either True or False.
3267+
@return: Iterator of braille display drivers.
3268+
"""
3269+
for loader, name, isPkg in pkgutil.iter_modules(brailleDisplayDrivers.__path__):
3270+
if name.startswith('_'):
3271+
continue
3272+
try:
3273+
display = _getDisplayDriver(name)
3274+
except Exception:
3275+
log.error(
3276+
f"Error while importing braille display driver {name}",
3277+
exc_info=True
3278+
)
3279+
continue
3280+
if not filterFunc or filterFunc(display):
3281+
yield display

source/brailleDisplayDrivers/albatross/driver.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import time
1313

1414
from collections import deque
15+
from bdDetect import KEY_SERIAL, DriverRegistrar
1516
from logHandler import log
1617
from serial.win32 import (
1718
PURGE_RXABORT,
@@ -79,6 +80,13 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver):
7980
# Translators: Names of braille displays.
8081
description = _("Caiku Albatross 46/80")
8182
isThreadSafe = True
83+
supportsAutomaticDetection = True
84+
85+
@classmethod
86+
def registerAutomaticDetection(cls, driverRegistrar: DriverRegistrar):
87+
driverRegistrar.addUsbDevices(KEY_SERIAL, {
88+
"VID_0403&PID_6001", # Caiku Albatross 46/80
89+
})
8290

8391
@classmethod
8492
def getManualPorts(cls):

source/brailleDisplayDrivers/alva.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,22 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject):
108108
# Translators: The name of a braille display.
109109
description = _("Optelec ALVA 6 series/protocol converter")
110110
isThreadSafe = True
111+
supportsAutomaticDetection = True
111112
timeout = 0.2
112113
supportedSettings = (
113114
braille.BrailleDisplayDriver.HIDInputSetting(useConfig=False),
114115
)
115116

117+
@classmethod
118+
def registerAutomaticDetection(cls, driverRegistrar):
119+
driverRegistrar.addUsbDevices(bdDetect.KEY_HID, {
120+
"VID_0798&PID_0640", # BC640
121+
"VID_0798&PID_0680", # BC680
122+
"VID_0798&PID_0699", # USB protocol converter
123+
})
124+
125+
driverRegistrar.addBluetoothDevices(lambda m: m.id.startswith("ALVA "))
126+
116127
@classmethod
117128
def getManualPorts(cls):
118129
return braille.getSerialPorts(filterFunc=lambda info: info.get("bluetoothName","").startswith("ALVA "))

source/brailleDisplayDrivers/baum.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,67 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver):
6262
# Translators: Names of braille displays.
6363
description = _("Baum/HumanWare/APH/Orbit braille displays")
6464
isThreadSafe = True
65+
supportsAutomaticDetection = True
66+
67+
@classmethod
68+
def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar):
69+
driverRegistrar.addUsbDevices(bdDetect.KEY_HID, {
70+
"VID_0904&PID_3001", # RefreshaBraille 18
71+
"VID_0904&PID_6101", # VarioUltra 20
72+
"VID_0904&PID_6103", # VarioUltra 32
73+
"VID_0904&PID_6102", # VarioUltra 40
74+
"VID_0904&PID_4004", # Pronto! 18 V3
75+
"VID_0904&PID_4005", # Pronto! 40 V3
76+
"VID_0904&PID_4007", # Pronto! 18 V4
77+
"VID_0904&PID_4008", # Pronto! 40 V4
78+
"VID_0904&PID_6001", # SuperVario2 40
79+
"VID_0904&PID_6002", # SuperVario2 24
80+
"VID_0904&PID_6003", # SuperVario2 32
81+
"VID_0904&PID_6004", # SuperVario2 64
82+
"VID_0904&PID_6005", # SuperVario2 80
83+
"VID_0904&PID_6006", # Brailliant2 40
84+
"VID_0904&PID_6007", # Brailliant2 24
85+
"VID_0904&PID_6008", # Brailliant2 32
86+
"VID_0904&PID_6009", # Brailliant2 64
87+
"VID_0904&PID_600A", # Brailliant2 80
88+
"VID_0904&PID_6201", # Vario 340
89+
"VID_0483&PID_A1D3", # Orbit Reader 20
90+
"VID_0904&PID_6301", # Vario 4
91+
})
92+
93+
driverRegistrar.addUsbDevices(bdDetect.KEY_SERIAL, {
94+
"VID_0403&PID_FE70", # Vario 40
95+
"VID_0403&PID_FE71", # PocketVario
96+
"VID_0403&PID_FE72", # SuperVario/Brailliant 40
97+
"VID_0403&PID_FE73", # SuperVario/Brailliant 32
98+
"VID_0403&PID_FE74", # SuperVario/Brailliant 64
99+
"VID_0403&PID_FE75", # SuperVario/Brailliant 80
100+
"VID_0904&PID_2001", # EcoVario 24
101+
"VID_0904&PID_2002", # EcoVario 40
102+
"VID_0904&PID_2007", # VarioConnect/BrailleConnect 40
103+
"VID_0904&PID_2008", # VarioConnect/BrailleConnect 32
104+
"VID_0904&PID_2009", # VarioConnect/BrailleConnect 24
105+
"VID_0904&PID_2010", # VarioConnect/BrailleConnect 64
106+
"VID_0904&PID_2011", # VarioConnect/BrailleConnect 80
107+
"VID_0904&PID_2014", # EcoVario 32
108+
"VID_0904&PID_2015", # EcoVario 64
109+
"VID_0904&PID_2016", # EcoVario 80
110+
"VID_0904&PID_3000", # RefreshaBraille 18
111+
})
112+
113+
driverRegistrar.addBluetoothDevices(lambda m: any(m.id.startswith(prefix) for prefix in (
114+
"Baum SuperVario",
115+
"Baum PocketVario",
116+
"Baum SVario",
117+
"HWG Brailliant",
118+
"Refreshabraille",
119+
"VarioConnect",
120+
"BrailleConnect",
121+
"Pronto!",
122+
"VarioUltra",
123+
"Orbit Reader 20",
124+
"Vario 4",
125+
)))
65126

66127
@classmethod
67128
def getManualPorts(cls):

source/brailleDisplayDrivers/brailleNote.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
#brailleDisplayDrivers/brailleNote.py
2-
#A part of NonVisual Desktop Access (NVDA)
3-
#This file is covered by the GNU General Public License.
4-
#See the file COPYING for more details.
1+
# A part of NonVisual Desktop Access (NVDA)
2+
# This file is covered by the GNU General Public License.
3+
# See the file COPYING for more details.
54
# Copyright (C) 2011-2018 NV access Limited, Rui Batista, Joseph Lee
65

76
""" Braille Display driver for the BrailleNote notetakers in terminal mode.
@@ -13,6 +12,7 @@
1312
from typing import List, Optional
1413

1514
import serial
15+
import bdDetect
1616
import braille
1717
import brailleInput
1818
import inputCore
@@ -125,6 +125,22 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver):
125125
# Translators: Names of braille displays
126126
description = _("HumanWare BrailleNote")
127127
isThreadSafe = True
128+
supportsAutomaticDetection = True
129+
130+
@classmethod
131+
def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar):
132+
driverRegistrar.addUsbDevices(bdDetect.KEY_SERIAL, {
133+
"VID_1C71&PID_C004", # Apex
134+
})
135+
driverRegistrar.addBluetoothDevices(lambda m: (
136+
any(
137+
first <= m.deviceInfo.get("bluetoothAddress", 0) <= last
138+
for first, last in (
139+
(0x0025EC000000, 0x0025EC01869F), # Apex
140+
)
141+
)
142+
or m.id.startswith("Braillenote")
143+
))
128144

129145
@classmethod
130146
def getManualPorts(cls):

0 commit comments

Comments
 (0)