11# A part of NonVisual Desktop Access (NVDA)
22# This file is covered by the GNU General Public License.
33# See the file COPYING for more details.
4- # Copyright (C) 2008-2022 NV Access Limited, Joseph Lee, Babbage B.V., Davy Kager, Bram Duvigneau,
4+ # Copyright (C) 2008-2023 NV Access Limited, Joseph Lee, Babbage B.V., Davy Kager, Bram Duvigneau,
55# Leonard de Ruijter
66
77import itertools
300300)
301301SELECTION_SHAPE = 0xC0 #: Dots 7 and 8
302302
303+ #: The braille shape shown on a braille display when
304+ #: the number of cells used by the braille handler is lower than the actual number of cells.
305+ #: The 0 based position of the shape is equal to the number of cells used by the braille handler.
306+ END_OF_BRAILLE_OUTPUT_SHAPE = 0xFF # All dots
307+
303308#: Unicode braille indicator at the start of untranslated braille input.
304309INPUT_START_IND = u"⣏"
305310#: Unicode braille indicator at the end of untranslated braille input.
@@ -1779,9 +1784,6 @@ class BrailleHandler(baseObject.AutoPropertyObject):
17791784 def __init__ (self ):
17801785 louisHelper .initialize ()
17811786 self .display : Optional [BrailleDisplayDriver ] = None
1782- #: Number of cells the connected device (or if no device connected, what braille viewer has)
1783- #: Zero cells disables braille. See L{_get_enabled}
1784- self ._displaySize : int = 0
17851787 self .mainBuffer = BrailleBuffer (self )
17861788 self .messageBuffer = BrailleBuffer (self )
17871789 self ._messageCallLater = None
@@ -1806,6 +1808,29 @@ def __init__(self):
18061808
18071809 brailleViewer .postBrailleViewerToolToggledAction .register (self ._onBrailleViewerChangedState )
18081810
1811+ #: Notifies when cells are about to be written to a braille display.
1812+ #: This allows components and add-ons to perform an action.
1813+ #: For example, when a system is controlled by a braille enabled remote system,
1814+ #: the remote system should know what cells to show on its display.
1815+ #: @param cells: The list of braille cells.
1816+ #: @type cells: [int]
1817+ #: @param rawText: The raw text that corresponds with the cells.
1818+ #: @type rawText: str
1819+ self .pre_writeCells = extensionPoints .Action ()
1820+
1821+ #: Filter that allows components or add-ons to change the display size used for braille output.
1822+ #: For example, when a system is controlled by a remote system while having a 80 cells display connected,
1823+ #: the display size should be lowered to 40 whenever the remote system has a 40 cells display connected.
1824+ #: @param value: the number of cells of the current display.
1825+ #: @type value: int
1826+ self .filter_displaySize = extensionPoints .Filter ()
1827+
1828+ #: Allows components or add-ons to decide whether the braille handler should be forcefully disabled.
1829+ #: For example, when a system is controlling a remote system with braille,
1830+ #: the local braille handler should be disabled as long as the system is in control of the remote system.
1831+ #: Handlers are called without arguments.
1832+ self .decide_enabled = extensionPoints .Decider ()
1833+
18091834 def terminate (self ):
18101835 self ._disableDetection ()
18111836 if self ._messageCallLater :
@@ -1844,20 +1869,41 @@ def _get_shouldAutoTether(self) -> bool:
18441869 displaySize : int
18451870
18461871 def _get_displaySize (self ):
1847- if self ._displaySize == 0 and brailleViewer .isBrailleViewerActive ():
1848- return brailleViewer .DEFAULT_NUM_CELLS
1849- return self ._displaySize
1850-
1851- def _set_displaySize (self , numCells ):
1852- """The display size can be changed while a display is connected, for instance
1853- see L{brailleDisplayDrivers.alva.BrailleDisplayDriver} split point feature.
1872+ """Returns the display size to use for braille output.
1873+ Handlers can register themselves to L{filter_displaySize} to change this value on the fly.
1874+ Therefore, this is a read only property and can't be set.
18541875 """
1855- self ._displaySize = numCells
1876+ numCells = self .display .numCells if self .display else 0
1877+ return self .filter_displaySize .apply (numCells )
1878+
1879+ def _set_displaySize (self , value ):
1880+ """While the display size can be changed while a display is connected
1881+ (for instance see L{brailleDisplayDrivers.alva.BrailleDisplayDriver} split point feature),
1882+ it is not possible to override the display size using this property.
1883+ Consider registering a handler to L{filter_displaySize} instead.
1884+ """
1885+ raise AttributeError (
1886+ f"Can't set displaySize to { value } , consider registering a handler to filter_displaySize"
1887+ )
18561888
18571889 enabled : bool
18581890
18591891 def _get_enabled (self ):
1860- return bool (self .displaySize )
1892+ """Returns whether braille is enabled.
1893+ Handlers can register themselves to L{decide_enabled} and return C{False}
1894+ to forcefully disable the braille handler.
1895+ If components need to change the state from disabled to enabled instead,
1896+ they should register to L{filter_displaySize}.
1897+ By default, the enabled/disabled state is based on the boolean value of L{displaySize},
1898+ and thus is C{True} when the display size is greater than 0.
1899+ This is a read only property and can't be set.
1900+ """
1901+ return bool (self .displaySize ) and self .decide_enabled .decide ()
1902+
1903+ def _set_enabled (self , value ):
1904+ raise AttributeError (
1905+ f"Can't set enabled to { value } , consider registering a handler to decide_enabled or filter_displaySize"
1906+ )
18611907
18621908 _lastRequestedDisplayName = None
18631909 """The name of the last requested braille display driver with setDisplayByName,
@@ -1926,7 +1972,6 @@ def setDisplayByName( # noqa: C901
19261972 log .error ("Error terminating previous display driver" , exc_info = True )
19271973 self .display = newDisplay
19281974 newDisplay .initSettings ()
1929- self ._displaySize = newDisplay .numCells
19301975 if isFallback :
19311976 if self ._detectionEnabled and not self ._detector :
19321977 # As this is the fallback display, which is usually noBraille,
@@ -1973,7 +2018,26 @@ def _updateDisplay(self):
19732018 wx .CallAfter (self ._cursorBlinkTimer .Start ,blinkRate )
19742019
19752020 def _writeCells (self , cells : List [int ]):
1976- brailleViewer .update (cells , self ._rawText )
2021+ self .pre_writeCells .notify (cells = cells , rawText = self ._rawText )
2022+ displayCellCount = self .display .numCells
2023+ handlerCellCount = self .displaySize
2024+ if not displayCellCount :
2025+ # No physical display to write to
2026+ return
2027+ # Braille displays expect cells to be padded up to displayCellCount.
2028+ # However, the braille handler uses handlerCellCount to calculate the number of cells.
2029+ cellCountDif = displayCellCount - len (cells )
2030+ if cellCountDif < 0 :
2031+ # There are more cells than the connected display could take.
2032+ log .warning (
2033+ f"Connected display { self .display .name !r} has { displayCellCount } cells, "
2034+ f"while braille handler is using { handlerCellCount } cells"
2035+ )
2036+ cells = cells [:displayCellCount ]
2037+ elif cellCountDif > 0 :
2038+ # The connected display could take more cells than the braille handler produces.
2039+ # Displays expect cells to be padded up to the number of cells.
2040+ cells += [END_OF_BRAILLE_OUTPUT_SHAPE ] + [0 ] * (cellCountDif - 1 )
19772041 if not self .display .isThreadSafe :
19782042 try :
19792043 self .display .display (cells )
0 commit comments