Skip to content

Commit b8f389c

Browse files
authored
Merge 714d968 into 72545d7
2 parents 72545d7 + 714d968 commit b8f389c

19 files changed

Lines changed: 732 additions & 542 deletions

source/bdDetect.py

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

source/braille.py

Lines changed: 80 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,
@@ -61,6 +62,8 @@
6162
from autoSettingsUtils.driverSetting import BooleanDriverSetting, NumericDriverSetting
6263
from utils.security import objectBelowLockScreenAndWindowsIsLocked
6364
import hwIo
65+
from buildVersion import version_year
66+
import NVDAState
6467

6568
if TYPE_CHECKING:
6669
from NVDAObjects import NVDAObject
@@ -396,6 +399,7 @@ def _getDisplayDriver(moduleName: str, caseSensitive: bool = True) -> Type["Brai
396399
else:
397400
raise initialException
398401

402+
399403
def getDisplayList(excludeNegativeChecks=True) -> List[Tuple[str, str]]:
400404
"""Gets a list of available display driver names with their descriptions.
401405
@param excludeNegativeChecks: excludes all drivers for which the check method returns C{False}.
@@ -405,30 +409,23 @@ def getDisplayList(excludeNegativeChecks=True) -> List[Tuple[str, str]]:
405409
displayList = []
406410
# The display that should be placed at the end of the list.
407411
lastDisplay = None
408-
for loader, name, isPkg in pkgutil.iter_modules(brailleDisplayDrivers.__path__):
409-
if name.startswith('_'):
410-
continue
411-
try:
412-
display = _getDisplayDriver(name)
413-
except:
414-
log.error("Error while importing braille display driver %s" % name,
415-
exc_info=True)
416-
continue
412+
for display in getDisplayDrivers():
417413
try:
418414
if not excludeNegativeChecks or display.check():
419415
if display.name == "noBraille":
420416
lastDisplay = (display.name, display.description)
421417
else:
422418
displayList.append((display.name, display.description))
423419
else:
424-
log.debugWarning("Braille display driver %s reports as unavailable, excluding" % name)
420+
log.debugWarning(f"Braille display driver {display.name} reports as unavailable, excluding")
425421
except:
426422
log.error("", exc_info=True)
427423
displayList.sort(key=lambda d: strxfrm(d[1]))
428424
if lastDisplay:
429425
displayList.append(lastDisplay)
430426
return displayList
431427

428+
432429
class Region(object):
433430
"""A region of braille to be displayed.
434431
Each portion of braille to be displayed is represented by a region.
@@ -2593,9 +2590,19 @@ def handlePostConfigProfileSwitch(self):
25932590
# The display in the new profile is equal to the last requested display name
25942591
display == self._lastRequestedDisplayName
25952592
# or the new profile uses auto detection, which supports detection of the currently active display.
2596-
or (display == AUTO_DISPLAY_NAME and bdDetect.driverSupportsAutoDetection(self.display.name))
2593+
or (display == AUTO_DISPLAY_NAME and bdDetect.driverIsEnabledForAutoDetection(self.display.name))
25972594
):
25982595
self.setDisplayByName(display)
2596+
elif (
2597+
# Auto detection should be active
2598+
display == AUTO_DISPLAY_NAME and self._detector is not None
2599+
# And the current display should be no braille.
2600+
# If not, there is an active detector for the current driver
2601+
# to switch from bluetooth to USB.
2602+
and self.display.name == NO_BRAILLE_DISPLAY_NAME
2603+
):
2604+
self._detector._limitToDevices = bdDetect.getBrailleDisplayDriversEnabledForDetection()
2605+
25992606
self._tether = config.conf["braille"]["tetherTo"]
26002607

26012608
def handleDisplayUnavailable(self):
@@ -2728,6 +2735,7 @@ def terminate():
27282735
handler.terminate()
27292736
handler = None
27302737

2738+
27312739
class BrailleDisplayDriver(driverHandler.Driver):
27322740
"""Abstract base braille display driver.
27332741
Each braille display driver should be a separate Python module in the root brailleDisplayDrivers directory
@@ -2736,6 +2744,11 @@ class BrailleDisplayDriver(driverHandler.Driver):
27362744
At a minimum, drivers must set L{name} and L{description} and override the L{check} method.
27372745
To display braille, L{numCells} and L{display} must be implemented.
27382746
2747+
To support automatic detection of braille displays belonging to this driver:
2748+
* The driver must be thread safe and L{isThreadSafe} should be set to C{True}
2749+
* L{supportsAutomaticDetection} must be set to C{True}.
2750+
* L{registerAutomaticDetection} must be implemented.
2751+
27392752
Drivers should dispatch input such as presses of buttons, wheels or other controls
27402753
using the L{inputCore} framework.
27412754
They should subclass L{BrailleDisplayGesture}
@@ -2758,19 +2771,19 @@ class BrailleDisplayDriver(driverHandler.Driver):
27582771
#: which means the rest of NVDA is not blocked while this occurs,
27592772
#: thus resulting in better performance.
27602773
#: This is also required to use the L{hwIo} module.
2761-
#: @type: bool
2762-
isThreadSafe = False
2774+
isThreadSafe: bool = False
2775+
#: Whether this driver is supported for automatic detection of braille displays.
2776+
supportsAutomaticDetection: bool = False
27632777
#: Whether displays for this driver return acknowledgements for sent packets.
27642778
#: L{_handleAck} should be called when an ACK is received.
27652779
#: Note that thread safety is required for the generic implementation to function properly.
27662780
#: If a display is not thread safe, a driver should manually implement ACK processing.
2767-
#: @type: bool
2768-
receivesAckPackets = False
2781+
receivesAckPackets: bool = False
27692782
#: Whether this driver is awaiting an Ack for a connected display.
27702783
#: This is set to C{True} after displaying cells when L{receivesAckPackets} is True,
27712784
#: and set to C{False} by L{_handleAck} or when C{timeout} has elapsed.
27722785
#: This is for internal use by NVDA core code only and shouldn't be touched by a driver itself.
2773-
_awaitingAck = False
2786+
_awaitingAck: bool = False
27742787
#: Maximum timeout to use for communication with a device (in seconds).
27752788
#: This can be used for serial connections.
27762789
#: Furthermore, it is used to stop waiting for missed acknowledgement packets.
@@ -2788,24 +2801,43 @@ def __init__(self, port: typing.Union[None, str, bdDetect.DeviceMatch] = None):
27882801
super().__init__()
27892802

27902803
@classmethod
2791-
def check(cls):
2804+
def check(cls) -> bool:
27922805
"""Determine whether this braille display is available.
27932806
The display will be excluded from the list of available displays if this method returns C{False}.
27942807
For example, if this display is not present, C{False} should be returned.
27952808
@return: C{True} if this display is available, C{False} if not.
2796-
@rtype: bool
27972809
"""
27982810
if cls.isThreadSafe:
2799-
if bdDetect.driverHasPossibleDevices(cls.name):
2800-
return True
2801-
try:
2802-
next(cls.getManualPorts())
2803-
except (StopIteration, NotImplementedError):
2804-
pass
2805-
else:
2811+
supportsAutomaticDetection = cls.supportsAutomaticDetection
2812+
if not supportsAutomaticDetection and NVDAState._allowDeprecatedAPI() and version_year < 2024:
2813+
log.warning(
2814+
"Starting from NVDA 2024.1, drivers that rely on bdDetect for the default check method "
2815+
"should have supportsAutomaticDetection set to True"
2816+
)
2817+
supportsAutomaticDetection = True
2818+
if supportsAutomaticDetection and bdDetect.driverHasPossibleDevices(cls.name):
28062819
return True
2820+
try:
2821+
next(cls.getManualPorts())
2822+
except (StopIteration, NotImplementedError):
2823+
pass
2824+
else:
2825+
return True
28072826
return False
28082827

2828+
@classmethod
2829+
def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar):
2830+
"""
2831+
This method may register the braille display driver in the braille display automatic detection framework.
2832+
The framework provides a L{bdDetect.DriverRegistrar} object as its only parameter.
2833+
The methods on the driver registrar can be used to register devices or device scanners.
2834+
This method should only register itself with the bdDetect framework,
2835+
and should refrain from doing anything else.
2836+
Drivers with L{supportsAutomaticDetection} set to C{True} must implement this method.
2837+
@param driverRegistrar: An object containing several methods to register device identifiers for this driver.
2838+
"""
2839+
raise NotImplementedError
2840+
28092841
def terminate(self):
28102842
"""Terminate this display driver.
28112843
This will be called when NVDA is finished with this display driver.
@@ -3206,3 +3238,26 @@ def getSerialPorts(filterFunc=None) -> typing.Iterator[typing.Tuple[str, str]]:
32063238
yield (info["port"],
32073239
# Translators: Name of a serial communications port.
32083240
_("Serial: {portName}").format(portName=info["friendlyName"]))
3241+
3242+
3243+
def getDisplayDrivers(
3244+
filterFunc: Optional[Callable[[Type[BrailleDisplayDriver]], bool]] = None
3245+
) -> Generator[Type[BrailleDisplayDriver], Any, Any]:
3246+
"""Gets an iterator of braille display drivers meeting the given filter callable.
3247+
@param filterFunc: an optional callable that receives a driver as its only argument and returns
3248+
either True or False.
3249+
@return: Iterator of braille display drivers.
3250+
"""
3251+
for loader, name, isPkg in pkgutil.iter_modules(brailleDisplayDrivers.__path__):
3252+
if name.startswith('_'):
3253+
continue
3254+
try:
3255+
display = _getDisplayDriver(name)
3256+
except Exception:
3257+
log.error(
3258+
f"Error while importing braille display driver {name}",
3259+
exc_info=True
3260+
)
3261+
continue
3262+
if not filterFunc or filterFunc(display):
3263+
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,
@@ -77,6 +78,13 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver):
7778
# Translators: Names of braille displays.
7879
description = _("Caiku Albatross 46/80")
7980
isThreadSafe = True
81+
supportsAutomaticDetection = True
82+
83+
@classmethod
84+
def registerAutomaticDetection(cls, driverRegistrar: DriverRegistrar):
85+
driverRegistrar.addUsbDevices(KEY_SERIAL, {
86+
"VID_0403&PID_6001", # Caiku Albatross 46/80
87+
})
8088

8189
@classmethod
8290
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)