1010from typing import (
1111 TYPE_CHECKING ,
1212 Any ,
13+ Callable ,
1314 Dict ,
1415 Generator ,
1516 Iterable ,
6162from autoSettingsUtils .driverSetting import BooleanDriverSetting , NumericDriverSetting
6263from utils .security import objectBelowLockScreenAndWindowsIsLocked
6364import hwIo
65+ from buildVersion import version_year
66+ import NVDAState
6467
6568if 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+
399403def 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+
432429class 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+
27312739class 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
0 commit comments