1818 Set ,
1919 Tuple ,
2020 Union ,
21+ Type ,
2122)
2223from locale import strxfrm
2324
355356#: braille displays should be automatically detected and used.
356357#: @type: str
357358AUTO_DISPLAY_NAME = AUTOMATIC_PORT [0 ]
359+
360+ NO_BRAILLE_DISPLAY_NAME : str = "noBraille"
361+ """The name of the noBraille display driver."""
362+
358363#: A port name which indicates that USB should be used.
359364#: @type: tuple
360365# Translators: String representing the USB port selection for braille displays.
@@ -377,7 +382,7 @@ def NVDAObjectHasUsefulText(obj: "NVDAObject") -> bool:
377382 return obj ._hasNavigableText
378383
379384
380- def _getDisplayDriver (moduleName , caseSensitive = True ):
385+ def _getDisplayDriver (moduleName : str , caseSensitive : bool = True ) -> Type [ "BrailleDisplayDriver" ] :
381386 try :
382387 return importlib .import_module ("brailleDisplayDrivers.%s" % moduleName , package = "brailleDisplayDrivers" ).BrailleDisplayDriver
383388 except ImportError as initialException :
@@ -1837,7 +1842,13 @@ def getFocusRegions(
18371842 from NVDAObjects import NVDAObject
18381843 if isinstance (obj , CursorManager ):
18391844 region2 = (ReviewTextInfoRegion if review else CursorManagerRegion )(obj )
1840- elif isinstance (obj , DocumentTreeInterceptor ) or (isinstance (obj ,NVDAObject ) and NVDAObjectHasUsefulText (obj )):
1845+ elif (
1846+ isinstance (obj , DocumentTreeInterceptor )
1847+ or (
1848+ isinstance (obj , NVDAObject )
1849+ and NVDAObjectHasUsefulText (obj )
1850+ )
1851+ ):
18411852 region2 = (ReviewTextInfoRegion if review else TextInfoRegion )(obj )
18421853 else :
18431854 region2 = None
@@ -1967,7 +1978,6 @@ def __init__(self):
19671978 self ._tether = TetherTo .FOCUS .value
19681979 else :
19691980 self ._tether = config .conf ["braille" ]["tetherTo" ]
1970- self ._detectionEnabled = False
19711981 self ._detector = None
19721982 self ._rawText = u""
19731983
@@ -2078,98 +2088,99 @@ def _handleEnabledDecisionFalse(self):
20782088 even if it failed and has fallen back to no braille.
20792089 """
20802090
2081- # C901 'setDisplayByName' is too complex
2082- # Note: when working on setDisplayByName, look for opportunities to simplify
2083- # and move logic out into smaller helper functions.
2084- def setDisplayByName ( # noqa: C901
2091+ def setDisplayByName (
20852092 self ,
20862093 name : str ,
2087- isFallback = False ,
2094+ isFallback : bool = False ,
20882095 detected : typing .Optional [bdDetect .DeviceMatch ] = None ,
2089- ):
2090- if not isFallback :
2091- # #8032: Take note of the display requested, even if it is going to fail.
2092- self ._lastRequestedDisplayName = name
2096+ ) -> bool :
20932097 if name == AUTO_DISPLAY_NAME :
2094- self ._enableDetection (keepCurrentDisplay = False )
2098+ # Calling _enableDetection will set the display to noBraille until a display is detected.
2099+ # Note that L{isFallback} is ignored in these cases.
2100+ self ._enableDetection ()
2101+ return True
2102+ elif not isFallback :
2103+ # #8032: Take note of the display requested, even if it is going to fail.
2104+ self ._lastRequestedDisplayName = name
2105+ if not detected :
2106+ self ._disableDetection ()
2107+
2108+ try :
2109+ newDisplayClass = _getDisplayDriver (name )
2110+ self ._setDisplay (newDisplayClass , isFallback = isFallback , detected = detected )
2111+ if not isFallback :
2112+ if not detected :
2113+ config .conf ["braille" ]["display" ] = newDisplayClass .name
2114+ elif 'bluetoothName' in detected .deviceInfo or detected .deviceInfo .get ("provider" ) == "bluetooth" :
2115+ # As USB devices have priority over Bluetooth, keep a detector running to switch to USB when connected.
2116+ # Note that the detector should always be running in this situation, so we can trigger a rescan.
2117+ self ._detector .rescan (bluetooth = False , limitToDevices = [newDisplayClass .name ])
2118+ else :
2119+ self ._disableDetection ()
20952120 return True
2096- elif not isFallback and not detected :
2097- self ._disableDetection ()
2121+ except Exception :
2122+ # For auto display detection, logging an error for every failure is too obnoxious.
2123+ if not detected :
2124+ log .error (f"Error initializing display driver { name !r} " , exc_info = True )
2125+ elif bdDetect ._isDebug ():
2126+ log .debugWarning (f"Couldn't initialize display driver { name !r} " , exc_info = True )
2127+ fallbackDisplayClass = _getDisplayDriver (NO_BRAILLE_DISPLAY_NAME )
2128+ # Only initialize the fallback if it is not already set
2129+ if self .display .__class__ == fallbackDisplayClass :
2130+ self ._setDisplay (fallbackDisplayClass , isFallback = False )
2131+ return False
2132+
2133+ def _switchDisplay (
2134+ self ,
2135+ oldDisplay : Optional ["BrailleDisplayDriver" ],
2136+ newDisplayClass : Type ["BrailleDisplayDriver" ],
2137+ ** kwargs
2138+ ) -> "BrailleDisplayDriver" :
2139+ sameDisplayReInit = newDisplayClass == oldDisplay .__class__
2140+ if sameDisplayReInit :
2141+ # This is the same driver as was already set, so just re-initialize it.
2142+ log .debug (f"Reinitializing { newDisplayClass .name !r} braille display" )
2143+ oldDisplay .terminate ()
2144+ newDisplay = oldDisplay
2145+ else :
2146+ newDisplay = newDisplayClass .__new__ (newDisplayClass )
2147+ extensionPoints .callWithSupportedKwargs (newDisplay .__init__ , ** kwargs )
2148+ if not sameDisplayReInit :
2149+ if oldDisplay :
2150+ log .debug (f"Switching braille display from { oldDisplay .name !r} to { newDisplay .name !r} " )
2151+ try :
2152+ oldDisplay .terminate ()
2153+ except Exception :
2154+ log .error ("Error terminating previous display driver" , exc_info = True )
2155+ newDisplay .initSettings ()
2156+ return newDisplay
20982157
2158+ def _setDisplay (
2159+ self ,
2160+ newDisplayClass : Type ["BrailleDisplayDriver" ],
2161+ isFallback : bool = False ,
2162+ detected : typing .Optional [bdDetect .DeviceMatch ] = None ,
2163+ ):
20992164 kwargs = {}
21002165 if detected :
21012166 kwargs ["port" ] = detected
21022167 else :
21032168 # See if the user has defined a specific port to connect to
21042169 try :
2105- port = config .conf ["braille" ][name ]["port" ]
2170+ kwargs [ " port" ] = config .conf ["braille" ][newDisplayClass . name ]["port" ]
21062171 except KeyError :
2107- port = None
2108- # Here we try to keep compatible with old drivers that don't support port setting
2109- # or situations where the user hasn't set any port.
2110- if port :
2111- kwargs ["port" ] = port
2172+ pass
21122173
2113- try :
2114- newDisplay = _getDisplayDriver (name )
2115- oldDisplay = self .display
2116- if detected and bdDetect ._isDebug ():
2117- log .debug ("Possibly detected display '%s'" % newDisplay .description )
2118- sameDisplayReInit = newDisplay == oldDisplay .__class__
2119- if sameDisplayReInit :
2120- # This is the same driver as was already set, so just re-initialise it.
2121- log .debug ("Reinitializing %s braille display" % name )
2122- oldDisplay .terminate ()
2123- newDisplay = oldDisplay
2124- try :
2125- newDisplay .__init__ (** kwargs )
2126- except TypeError :
2127- # Re-initialize with supported kwargs.
2128- extensionPoints .callWithSupportedKwargs (newDisplay .__init__ , ** kwargs )
2129- else :
2130- try :
2131- newDisplay = newDisplay (** kwargs )
2132- except TypeError :
2133- newDisplay = newDisplay .__new__ (newDisplay )
2134- # initialize with supported kwargs.
2135- extensionPoints .callWithSupportedKwargs (newDisplay .__init__ , ** kwargs )
2136- if self .display :
2137- log .debug ("Switching braille display from %s to %s" % (self .display .name ,name ))
2138- try :
2139- self .display .terminate ()
2140- except :
2141- log .error ("Error terminating previous display driver" , exc_info = True )
2142- self .display = newDisplay
2143- newDisplay .initSettings ()
2144- if isFallback :
2145- if self ._detectionEnabled and not self ._detector :
2146- # As this is the fallback display, which is usually noBraille,
2147- # we can keep the current display when enabling detection.
2148- # Note that in this case, L{_detectionEnabled} is set by L{handleDisplayUnavailable}
2149- self ._enableDetection (keepCurrentDisplay = True )
2150- elif not detected :
2151- config .conf ["braille" ]["display" ] = name
2152- else : # detected:
2153- self ._disableDetection ()
2154- log .info (f"Loaded braille display driver { name !r} , current display has { newDisplay .numCells } cells." )
2155- queueHandler .queueFunction (queueHandler .eventQueue , self .initialDisplay )
2156- if detected and 'bluetoothName' in detected .deviceInfo :
2157- self ._enableDetection (bluetooth = False , keepCurrentDisplay = True , limitToDevices = [name ])
2158- # #14503: optimization, avoid notifications of unnecessary re-initialization
2159- # of the noBraille display
2160- # When setDisplayByName is refactored, ensure that braille display detection no longer triggers
2161- # an unnecessary reinit of noBraille.
2162- if not (sameDisplayReInit and newDisplay .name == "noBraille" ):
2163- displayChanged .notify (display = newDisplay , isFallback = isFallback , detected = detected )
2164- return True
2165- except :
2166- # For auto display detection, logging an error for every failure is too obnoxious.
2167- if not detected :
2168- log .error ("Error initializing display driver %s for kwargs %r" % (name ,kwargs ), exc_info = True )
2169- elif bdDetect ._isDebug ():
2170- log .debugWarning ("Couldn't initialize display driver for kwargs %r" % (kwargs ,), exc_info = True )
2171- self .setDisplayByName ("noBraille" , isFallback = True )
2172- return False
2174+ if bdDetect ._isDebug () and detected :
2175+ log .debug (f"Possibly detected display { newDisplayClass .description !r} " )
2176+ oldDisplay = self .display
2177+ newDisplay = self ._switchDisplay (oldDisplay , newDisplayClass , ** kwargs )
2178+ self .display = newDisplay
2179+ log .info (
2180+ f"Loaded braille display driver { newDisplay .name !r} , current display has { newDisplay .numCells } cells."
2181+ )
2182+ displayChanged .notify (display = newDisplay , isFallback = isFallback , detected = detected )
2183+ queueHandler .queueFunction (queueHandler .eventQueue , self .initialDisplay )
21732184
21742185 def _onBrailleViewerChangedState (self , created ):
21752186 if created :
@@ -2525,31 +2536,42 @@ def handleDisplayUnavailable(self):
25252536 but drivers can also call it themselves if appropriate.
25262537 """
25272538 log .error ("Braille display unavailable. Disabling" , exc_info = True )
2528- self ._detectionEnabled = config .conf ["braille" ]["display" ] == AUTO_DISPLAY_NAME
2529- self .setDisplayByName ("noBraille" , isFallback = True )
2539+ newDisplay = (
2540+ AUTO_DISPLAY_NAME
2541+ if config .conf ["braille" ]["display" ] == AUTO_DISPLAY_NAME
2542+ else NO_BRAILLE_DISPLAY_NAME
2543+ )
2544+ self .setDisplayByName (newDisplay , isFallback = True )
25302545
2531- def _enableDetection (self , usb = True , bluetooth = True , keepCurrentDisplay = False , limitToDevices = None ):
2546+ def _enableDetection (
2547+ self ,
2548+ usb : bool = True ,
2549+ bluetooth : bool = True ,
2550+ limitToDevices : Optional [List [str ]] = None
2551+ ):
25322552 """Enables automatic detection of braille displays.
25332553 When auto detection is already active, this will force a rescan for devices.
25342554 This should also be executed when auto detection should be resumed due to loss of display connectivity.
2555+ In that case, it is triggered by L{setDisplayByname}.
2556+ @param usb: Whether to scan for USB devices
2557+ @param bluetooth: Whether to scan for Bluetooth devices.
2558+ @param limitToDevices: An optional list of driver names a scan should be limited to.
2559+ This is used when a Bluetooth device is detected, in order to switch to USB
2560+ when an USB device for the same driver is found.
2561+ C{None} if no driver filtering should occur.
25352562 """
2536- if self ._detectionEnabled and self ._detector :
2563+ self .setDisplayByName (NO_BRAILLE_DISPLAY_NAME , isFallback = True )
2564+ if self ._detector :
25372565 self ._detector .rescan (usb = usb , bluetooth = bluetooth , limitToDevices = limitToDevices )
25382566 return
25392567 config .conf ["braille" ]["display" ] = AUTO_DISPLAY_NAME
2540- if not keepCurrentDisplay :
2541- self .setDisplayByName ("noBraille" , isFallback = True )
25422568 self ._detector = bdDetect .Detector (usb = usb , bluetooth = bluetooth , limitToDevices = limitToDevices )
2543- self ._detectionEnabled = True
25442569
25452570 def _disableDetection (self ):
25462571 """Disables automatic detection of braille displays."""
2547- if not self ._detectionEnabled :
2548- return
25492572 if self ._detector :
25502573 self ._detector .terminate ()
25512574 self ._detector = None
2552- self ._detectionEnabled = False
25532575
25542576 def _bgThreadExecutor (self , param : int ):
25552577 """Executed as APC when cells have to be written to a display asynchronously.
0 commit comments