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
304304)
305305SELECTION_SHAPE = 0xC0 #: Dots 7 and 8
306306
307+ #: The braille shape shown on a braille display when
308+ #: the number of cells used by the braille handler is lower than the actual number of cells.
309+ #: The 0 based position of the shape is equal to the number of cells used by the braille handler.
310+ END_OF_BRAILLE_OUTPUT_SHAPE = 0xFF # All dots
311+
307312#: Unicode braille indicator at the start of untranslated braille input.
308313INPUT_START_IND = u"⣏"
309314#: Unicode braille indicator at the end of untranslated braille input.
@@ -1874,9 +1879,6 @@ class BrailleHandler(baseObject.AutoPropertyObject):
18741879 def __init__ (self ):
18751880 louisHelper .initialize ()
18761881 self .display : Optional [BrailleDisplayDriver ] = None
1877- #: Number of cells the connected device (or if no device connected, what braille viewer has)
1878- #: Zero cells disables braille. See L{_get_enabled}
1879- self ._displaySize : int = 0
18801882 self .mainBuffer = BrailleBuffer (self )
18811883 self .messageBuffer = BrailleBuffer (self )
18821884 self ._messageCallLater = None
@@ -1901,6 +1903,29 @@ def __init__(self):
19011903
19021904 brailleViewer .postBrailleViewerToolToggledAction .register (self ._onBrailleViewerChangedState )
19031905
1906+ #: Notifies when cells are about to be written to a braille display.
1907+ #: This allows components and add-ons to perform an action.
1908+ #: For example, when a system is controlled by a braille enabled remote system,
1909+ #: the remote system should know what cells to show on its display.
1910+ #: @param cells: The list of braille cells.
1911+ #: @type cells: [int]
1912+ #: @param rawText: The raw text that corresponds with the cells.
1913+ #: @type rawText: str
1914+ self .pre_writeCells = extensionPoints .Action ()
1915+
1916+ #: Filter that allows components or add-ons to change the display size used for braille output.
1917+ #: For example, when a system is controlled by a remote system while having a 80 cells display connected,
1918+ #: the display size should be lowered to 40 whenever the remote system has a 40 cells display connected.
1919+ #: @param value: the number of cells of the current display.
1920+ #: @type value: int
1921+ self .filter_displaySize = extensionPoints .Filter ()
1922+
1923+ #: Allows components or add-ons to decide whether the braille handler should be forcefully disabled.
1924+ #: For example, when a system is controlling a remote system with braille,
1925+ #: the local braille handler should be disabled as long as the system is in control of the remote system.
1926+ #: Handlers are called without arguments.
1927+ self .decide_enabled = extensionPoints .Decider ()
1928+
19041929 def terminate (self ):
19051930 self ._disableDetection ()
19061931 if self ._messageCallLater :
@@ -1939,20 +1964,41 @@ def _get_shouldAutoTether(self) -> bool:
19391964 displaySize : int
19401965
19411966 def _get_displaySize (self ):
1942- if self ._displaySize == 0 and brailleViewer .isBrailleViewerActive ():
1943- return brailleViewer .DEFAULT_NUM_CELLS
1944- return self ._displaySize
1945-
1946- def _set_displaySize (self , numCells ):
1947- """The display size can be changed while a display is connected, for instance
1948- see L{brailleDisplayDrivers.alva.BrailleDisplayDriver} split point feature.
1967+ """Returns the display size to use for braille output.
1968+ Handlers can register themselves to L{filter_displaySize} to change this value on the fly.
1969+ Therefore, this is a read only property and can't be set.
19491970 """
1950- self ._displaySize = numCells
1971+ numCells = self .display .numCells if self .display else 0
1972+ return self .filter_displaySize .apply (numCells )
1973+
1974+ def _set_displaySize (self , value ):
1975+ """While the display size can be changed while a display is connected
1976+ (for instance see L{brailleDisplayDrivers.alva.BrailleDisplayDriver} split point feature),
1977+ it is not possible to override the display size using this property.
1978+ Consider registering a handler to L{filter_displaySize} instead.
1979+ """
1980+ raise AttributeError (
1981+ f"Can't set displaySize to { value } , consider registering a handler to filter_displaySize"
1982+ )
19511983
19521984 enabled : bool
19531985
19541986 def _get_enabled (self ):
1955- return bool (self .displaySize )
1987+ """Returns whether braille is enabled.
1988+ Handlers can register themselves to L{decide_enabled} and return C{False}
1989+ to forcefully disable the braille handler.
1990+ If components need to change the state from disabled to enabled instead,
1991+ they should register to L{filter_displaySize}.
1992+ By default, the enabled/disabled state is based on the boolean value of L{displaySize},
1993+ and thus is C{True} when the display size is greater than 0.
1994+ This is a read only property and can't be set.
1995+ """
1996+ return bool (self .displaySize ) and self .decide_enabled .decide ()
1997+
1998+ def _set_enabled (self , value ):
1999+ raise AttributeError (
2000+ f"Can't set enabled to { value } , consider registering a handler to decide_enabled or filter_displaySize"
2001+ )
19562002
19572003 _lastRequestedDisplayName = None
19582004 """The name of the last requested braille display driver with setDisplayByName,
@@ -2021,7 +2067,6 @@ def setDisplayByName( # noqa: C901
20212067 log .error ("Error terminating previous display driver" , exc_info = True )
20222068 self .display = newDisplay
20232069 newDisplay .initSettings ()
2024- self ._displaySize = newDisplay .numCells
20252070 if isFallback :
20262071 if self ._detectionEnabled and not self ._detector :
20272072 # As this is the fallback display, which is usually noBraille,
@@ -2068,7 +2113,26 @@ def _updateDisplay(self):
20682113 wx .CallAfter (self ._cursorBlinkTimer .Start ,blinkRate )
20692114
20702115 def _writeCells (self , cells : List [int ]):
2071- brailleViewer .update (cells , self ._rawText )
2116+ self .pre_writeCells .notify (cells = cells , rawText = self ._rawText )
2117+ displayCellCount = self .display .numCells
2118+ handlerCellCount = self .displaySize
2119+ if not displayCellCount :
2120+ # No physical display to write to
2121+ return
2122+ # Braille displays expect cells to be padded up to displayCellCount.
2123+ # However, the braille handler uses handlerCellCount to calculate the number of cells.
2124+ cellCountDif = displayCellCount - len (cells )
2125+ if cellCountDif < 0 :
2126+ # There are more cells than the connected display could take.
2127+ log .warning (
2128+ f"Connected display { self .display .name !r} has { displayCellCount } cells, "
2129+ f"while braille handler is using { handlerCellCount } cells"
2130+ )
2131+ cells = cells [:displayCellCount ]
2132+ elif cellCountDif > 0 :
2133+ # The connected display could take more cells than the braille handler produces.
2134+ # Displays expect cells to be padded up to the number of cells.
2135+ cells += [END_OF_BRAILLE_OUTPUT_SHAPE ] + [0 ] * (cellCountDif - 1 )
20722136 if not self .display .isThreadSafe :
20732137 try :
20742138 self .display .display (cells )
0 commit comments