Skip to content

Commit 7e3b7ec

Browse files
authored
Merge 672bfb1 into 180c9f2
2 parents 180c9f2 + 672bfb1 commit 7e3b7ec

1 file changed

Lines changed: 113 additions & 91 deletions

File tree

source/braille.py

Lines changed: 113 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
Set,
1919
Tuple,
2020
Union,
21+
Type,
2122
)
2223
from locale import strxfrm
2324

@@ -355,6 +356,10 @@
355356
#: braille displays should be automatically detected and used.
356357
#: @type: str
357358
AUTO_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

Comments
 (0)