1818 Set ,
1919 Tuple ,
2020 Union ,
21+ Type
2122)
2223from locale import strxfrm
2324
348349#: braille displays should be automatically detected and used.
349350#: @type: str
350351AUTO_DISPLAY_NAME = AUTOMATIC_PORT [0 ]
352+ #: The name of the noBraille display driver
353+ #: @type: str
354+ NO_BRAILLE_DISPLAY_NAME = "noBraille"
351355#: A port name which indicates that USB should be used.
352356#: @type: tuple
353357# Translators: String representing the USB port selection for braille displays.
@@ -370,7 +374,7 @@ def NVDAObjectHasUsefulText(obj: "NVDAObject") -> bool:
370374 return obj ._hasNavigableText
371375
372376
373- def _getDisplayDriver (moduleName , caseSensitive = True ):
377+ def _getDisplayDriver (moduleName : str , caseSensitive : bool = True ) -> Type [ "BrailleDisplayDriver" ] :
374378 try :
375379 return importlib .import_module ("brailleDisplayDrivers.%s" % moduleName , package = "brailleDisplayDrivers" ).BrailleDisplayDriver
376380 except ImportError as initialException :
@@ -1145,7 +1149,7 @@ def _addTextWithFields(self, info, formatConfig, isSelection=False):
11451149 if not inClickable and formatConfig ['reportClickable' ]:
11461150 states = field .get ('states' )
11471151 if states and controlTypes .State .CLICKABLE in states :
1148- # We have entered an outer most clickable or entered a new clickable after exiting a previous one
1152+ # We have entered an outer most clickable or entered a new clickable after exiting a previous one
11491153 # Report it if there is nothing else interesting about the field
11501154 field ._presCat = presCat = field .getPresentationCategory (ctrlFields ,formatConfig )
11511155 if not presCat or presCat is field .PRESCAT_LAYOUT :
@@ -1551,7 +1555,7 @@ def _set_windowEndPos(self, endPos):
15511555 """Sets the end position for the braille window and recalculates the window start position based on several variables.
15521556 1. Braille display size.
15531557 2. Whether one of the regions should be shown hard left on the braille display;
1554- i.e. because of The configuration setting for focus context representation
1558+ i.e. because of The configuration setting for focus context representation
15551559 or whether the braille region that corresponds with the focus represents a multi line edit box.
15561560 3. Whether word wrap is enabled."""
15571561 startPos = endPos - self .handler .displaySize
@@ -1830,7 +1834,7 @@ def getFocusRegions(
18301834 from NVDAObjects import NVDAObject
18311835 if isinstance (obj , CursorManager ):
18321836 region2 = (ReviewTextInfoRegion if review else CursorManagerRegion )(obj )
1833- elif isinstance (obj , DocumentTreeInterceptor ) or (isinstance (obj ,NVDAObject ) and NVDAObjectHasUsefulText (obj )):
1837+ elif isinstance (obj , DocumentTreeInterceptor ) or (isinstance (obj ,NVDAObject ) and NVDAObjectHasUsefulText (obj )):
18341838 region2 = (ReviewTextInfoRegion if review else TextInfoRegion )(obj )
18351839 else :
18361840 region2 = None
@@ -1891,7 +1895,6 @@ def __init__(self):
18911895 self ._tether = TetherTo .FOCUS .value
18921896 else :
18931897 self ._tether = config .conf ["braille" ]["tetherTo" ]
1894- self ._detectionEnabled = False
18951898 self ._detector = None
18961899 self ._rawText = u""
18971900
@@ -1959,92 +1962,99 @@ def _get_enabled(self):
19591962 even if it failed and has fallen back to no braille.
19601963 """
19611964
1962- # C901 'setDisplayByName' is too complex
1963- # Note: when working on setDisplayByName, look for opportunities to simplify
1964- # and move logic out into smaller helper functions.
1965- def setDisplayByName ( # noqa: C901
1965+ def setDisplayByName (
19661966 self ,
19671967 name : str ,
1968- isFallback = False ,
1968+ isFallback : bool = False ,
19691969 detected : typing .Optional [bdDetect .DeviceMatch ] = None ,
19701970 ):
1971- if not isFallback :
1972- # #8032: Take note of the display requested, even if it is going to fail.
1973- self ._lastRequestedDisplayName = name
19741971 if name == AUTO_DISPLAY_NAME :
1975- self ._enableDetection (keepCurrentDisplay = False )
1972+ # Calling _enableDetection will set the display to noBraille until a display is detected.
1973+ # Note that L{isFallback} is ignored in these cases.
1974+ self ._enableDetection ()
19761975 return True
1977- elif not isFallback and not detected :
1978- self ._disableDetection ()
1976+ elif not isFallback :
1977+ # #8032: Take note of the display requested, even if it is going to fail.
1978+ self ._lastRequestedDisplayName = name
1979+ if not detected :
1980+ self ._disableDetection ()
19791981
1982+ try :
1983+ newDisplayClass = _getDisplayDriver (name )
1984+ self ._setDisplay (newDisplayClass , isFallback = isFallback , detected = detected )
1985+ if not isFallback :
1986+ if not detected :
1987+ config .conf ["braille" ]["display" ] = newDisplayClass .name
1988+ elif 'bluetoothName' in detected .deviceInfo or detected .deviceInfo .get ("provider" ) == "bluetooth" :
1989+ # As USB devices have priority over Bluetooth, keep a detector running to switch to USB when connected.
1990+ # Note that the detector should always be running in this situation, so we can trigger a rescan.
1991+ self ._detector .rescan (bluetooth = False , limitToDevices = [newDisplayClass .name ])
1992+ else :
1993+ self ._disableDetection ()
1994+ return True
1995+ except Exception :
1996+ # For auto display detection, logging an error for every failure is too obnoxious.
1997+ if not detected :
1998+ log .error (f"Error initializing display driver { name !r} " , exc_info = True )
1999+ elif bdDetect ._isDebug ():
2000+ log .debugWarning (f"Couldn't initialize display driver { name !r} " , exc_info = True )
2001+ fallbackDisplayClass = _getDisplayDriver (NO_BRAILLE_DISPLAY_NAME )
2002+ # Only initialize the fallback if it is not already set
2003+ if self .display .__class__ == fallbackDisplayClass :
2004+ self ._setDisplay (fallbackDisplayClass , isFallback = False )
2005+ return False
2006+
2007+ def _switchDisplay (
2008+ self ,
2009+ oldDisplay : Optional ["BrailleDisplayDriver" ],
2010+ newDisplayClass : Type ["BrailleDisplayDriver" ],
2011+ ** kwargs
2012+ ) -> "BrailleDisplayDriver" :
2013+ sameDisplayReinit = newDisplayClass == oldDisplay .__class__
2014+ if sameDisplayReinit :
2015+ # This is the same driver as was already set, so just re-initialize it.
2016+ log .debug (f"Reinitializing { newDisplayClass .name !r} braille display" )
2017+ oldDisplay .terminate ()
2018+ newDisplay = oldDisplay
2019+ else :
2020+ newDisplay = newDisplayClass .__new__ (newDisplayClass )
2021+ extensionPoints .callWithSupportedKwargs (newDisplay .__init__ , ** kwargs )
2022+ if not sameDisplayReinit :
2023+ if oldDisplay :
2024+ log .debug (f"Switching braille display from { oldDisplay .name !r} to { newDisplay .name !r} " )
2025+ try :
2026+ oldDisplay .terminate ()
2027+ except Exception :
2028+ log .error ("Error terminating previous display driver" , exc_info = True )
2029+ newDisplay .initSettings ()
2030+ return newDisplay
2031+
2032+ def _setDisplay (
2033+ self ,
2034+ newDisplayClass : Type ["BrailleDisplayDriver" ],
2035+ isFallback : bool = False ,
2036+ detected : typing .Optional [bdDetect .DeviceMatch ] = None ,
2037+ ):
19802038 kwargs = {}
19812039 if detected :
19822040 kwargs ["port" ] = detected
19832041 else :
19842042 # See if the user has defined a specific port to connect to
19852043 try :
1986- port = config .conf ["braille" ][name ]["port" ]
2044+ kwargs [ " port" ] = config .conf ["braille" ][newDisplayClass . name ]["port" ]
19872045 except KeyError :
1988- port = None
1989- # Here we try to keep compatible with old drivers that don't support port setting
1990- # or situations where the user hasn't set any port.
1991- if port :
1992- kwargs ["port" ] = port
2046+ pass
19932047
1994- try :
1995- newDisplay = _getDisplayDriver (name )
1996- oldDisplay = self .display
1997- if detected and bdDetect ._isDebug ():
1998- log .debug ("Possibly detected display '%s'" % newDisplay .description )
1999- if newDisplay == oldDisplay .__class__ :
2000- # This is the same driver as was already set, so just re-initialise it.
2001- log .debug ("Reinitializing %s braille display" % name )
2002- oldDisplay .terminate ()
2003- newDisplay = oldDisplay
2004- try :
2005- newDisplay .__init__ (** kwargs )
2006- except TypeError :
2007- # Re-initialize with supported kwargs.
2008- extensionPoints .callWithSupportedKwargs (newDisplay .__init__ , ** kwargs )
2009- else :
2010- try :
2011- newDisplay = newDisplay (** kwargs )
2012- except TypeError :
2013- newDisplay = newDisplay .__new__ (newDisplay )
2014- # initialize with supported kwargs.
2015- extensionPoints .callWithSupportedKwargs (newDisplay .__init__ , ** kwargs )
2016- if self .display :
2017- log .debug ("Switching braille display from %s to %s" % (self .display .name ,name ))
2018- try :
2019- self .display .terminate ()
2020- except :
2021- log .error ("Error terminating previous display driver" , exc_info = True )
2022- self .display = newDisplay
2023- newDisplay .initSettings ()
2024- self ._displaySize = newDisplay .numCells
2025- if isFallback :
2026- if self ._detectionEnabled and not self ._detector :
2027- # As this is the fallback display, which is usually noBraille,
2028- # we can keep the current display when enabling detection.
2029- # Note that in this case, L{_detectionEnabled} is set by L{handleDisplayUnavailable}
2030- self ._enableDetection (keepCurrentDisplay = True )
2031- elif not detected :
2032- config .conf ["braille" ]["display" ] = name
2033- else : # detected:
2034- self ._disableDetection ()
2035- log .info ("Loaded braille display driver %s, current display has %d cells." % (name , self .displaySize ))
2036- queueHandler .queueFunction (queueHandler .eventQueue , self .initialDisplay )
2037- if detected and 'bluetoothName' in detected .deviceInfo :
2038- self ._enableDetection (bluetooth = False , keepCurrentDisplay = True , limitToDevices = [name ])
2039- return True
2040- except :
2041- # For auto display detection, logging an error for every failure is too obnoxious.
2042- if not detected :
2043- log .error ("Error initializing display driver %s for kwargs %r" % (name ,kwargs ), exc_info = True )
2044- elif bdDetect ._isDebug ():
2045- log .debugWarning ("Couldn't initialize display driver for kwargs %r" % (kwargs ,), exc_info = True )
2046- self .setDisplayByName ("noBraille" , isFallback = True )
2047- return False
2048+ if bdDetect ._isDebug () and detected :
2049+ log .debug (f"Possibly detected display { newDisplayClass .description !r} " )
2050+ oldDisplay = self .display
2051+ newDisplay = self ._switchDisplay (oldDisplay , newDisplayClass , ** kwargs )
2052+ self .display = newDisplay
2053+ self ._displaySize = newDisplay .numCells
2054+ log .info (
2055+ f"Loaded braille display driver { newDisplay .name !r} , current display has { newDisplay .numCells } cells."
2056+ )
2057+ queueHandler .queueFunction (queueHandler .eventQueue , self .initialDisplay )
20482058
20492059 def _onBrailleViewerChangedState (self , created ):
20502060 if created :
@@ -2361,15 +2371,25 @@ def initialDisplay(self):
23612371 log .debugWarning ("Error in initial display" , exc_info = True )
23622372
23632373 def handlePostConfigProfileSwitch (self ):
2364- display = config .conf ["braille" ]["display" ]
2374+ displayName = config .conf ["braille" ]["display" ]
2375+ try :
2376+ port = config .conf ["braille" ][displayName ]["port" ]
2377+ except KeyError :
2378+ port = None
2379+ coveredByAutoDetect = (
2380+ displayName == AUTO_DISPLAY_NAME
2381+ and bdDetect .driverSupportsAutoDetection (self .display .name )
2382+ )
23652383 # Do not choose a new display if:
23662384 if not (
23672385 # The display in the new profile is equal to the last requested display name
2368- display == self ._lastRequestedDisplayName
2369- # or the new profile uses auto detection, which supports detection of the currently active display.
2370- or (display == AUTO_DISPLAY_NAME and bdDetect .driverSupportsAutoDetection (self .display .name ))
2386+ # and it has no explicit port defined
2387+ displayName == self ._lastRequestedDisplayName and port is None
2388+ # or the new profile uses auto detection,
2389+ # and the currently active display is supported by auto detection.
2390+ or coveredByAutoDetect
23712391 ):
2372- self .setDisplayByName (display )
2392+ self .setDisplayByName (displayName )
23732393 self ._tether = config .conf ["braille" ]["tetherTo" ]
23742394
23752395 def handleDisplayUnavailable (self ):
@@ -2379,31 +2399,30 @@ def handleDisplayUnavailable(self):
23792399 but drivers can also call it themselves if appropriate.
23802400 """
23812401 log .error ("Braille display unavailable. Disabling" , exc_info = True )
2382- self ._detectionEnabled = config .conf ["braille" ]["display" ] == AUTO_DISPLAY_NAME
2383- self .setDisplayByName ("noBraille" , isFallback = True )
2402+ newDisplay = (
2403+ AUTO_DISPLAY_NAME
2404+ if config .conf ["braille" ]["display" ] == AUTO_DISPLAY_NAME
2405+ else NO_BRAILLE_DISPLAY_NAME
2406+ )
2407+ self .setDisplayByName (newDisplay , isFallback = True )
23842408
2385- def _enableDetection (self , usb = True , bluetooth = True , keepCurrentDisplay = False , limitToDevices = None ):
2409+ def _enableDetection (self , usb = True , bluetooth = True , limitToDevices = None ):
23862410 """Enables automatic detection of braille displays.
23872411 When auto detection is already active, this will force a rescan for devices.
23882412 This should also be executed when auto detection should be resumed due to loss of display connectivity.
23892413 """
2390- if self ._detectionEnabled and self ._detector :
2414+ self .setDisplayByName ("noBraille" , isFallback = True )
2415+ if self ._detector :
23912416 self ._detector .rescan (usb = usb , bluetooth = bluetooth , limitToDevices = limitToDevices )
23922417 return
23932418 config .conf ["braille" ]["display" ] = AUTO_DISPLAY_NAME
2394- if not keepCurrentDisplay :
2395- self .setDisplayByName ("noBraille" , isFallback = True )
23962419 self ._detector = bdDetect .Detector (usb = usb , bluetooth = bluetooth , limitToDevices = limitToDevices )
2397- self ._detectionEnabled = True
23982420
23992421 def _disableDetection (self ):
24002422 """Disables automatic detection of braille displays."""
2401- if not self ._detectionEnabled :
2402- return
24032423 if self ._detector :
24042424 self ._detector .terminate ()
24052425 self ._detector = None
2406- self ._detectionEnabled = False
24072426
24082427 def _bgThreadExecutor (self , param : int ):
24092428 """Executed as APC when cells have to be written to a display asynchronously.
0 commit comments