1010from 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+
401403def 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+
434429class 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
0 commit comments