1919 Tuple ,
2020 Union ,
2121 Type ,
22+ Callable ,
2223)
2324from locale import strxfrm
2425
5354import re
5455import scriptHandler
5556import collections
57+ from collections import deque
5658import extensionPoints
5759import hwPortUtils
5860import bdDetect
@@ -1569,6 +1571,7 @@ def bufferPosToRegionPos(self, bufferPos):
15691571 raise LookupError ("No such position" )
15701572
15711573 def regionPosToBufferPos (self , region , pos , allowNearest = False ):
1574+ start : int = 0
15721575 for testRegion , start , end in self .regionsWithPositions :
15731576 if region == testRegion :
15741577 if pos < end - start :
@@ -2003,6 +2006,46 @@ def formatCellsForLog(cells: List[int]) -> str:
20032006"""
20042007
20052008
2009+ class RepeatedTimer :
2010+ """Repeating timer.
2011+ Timer is used to try to ensure that display content is always up to date.
2012+ """
2013+
2014+ def __init__ (
2015+ self ,
2016+ interval : float ,
2017+ updateFunction : Callable [[], None ]
2018+ ):
2019+ """Constructor.
2020+ @param interval: Checking frequency
2021+ @param updateFunction: Update display
2022+ """
2023+ self ._interval = interval
2024+ self ._timer = threading .Timer (self ._interval , self ._run )
2025+ self ._updateFunction = updateFunction
2026+ self .is_running = False
2027+ self .start ()
2028+
2029+ def _run (self ):
2030+ self .is_running = False
2031+ self .start ()
2032+ self ._updateFunction ()
2033+
2034+ def start (self ):
2035+ if not self .is_running :
2036+ self ._timer = threading .Timer (self ._interval , self ._run )
2037+ self ._timer .start ()
2038+ self .is_running = True
2039+
2040+ def stop (self ):
2041+ self ._timer .cancel ()
2042+ self .is_running = False
2043+
2044+
2045+ UPDATE_DISPLAY_PERIODICALLY_INTERVAL : float = 0.5
2046+ """Timer interval for L{BrailleHandler._updateDisplayPeriodically}."""
2047+
2048+
20062049class BrailleHandler (baseObject .AutoPropertyObject ):
20072050 # TETHER_AUTO, TETHER_FOCUS, TETHER_REVIEW and tetherValues
20082051 # are deprecated, but remain to retain API backwards compatibility
@@ -2042,6 +2085,13 @@ def __init__(self):
20422085 self ._cursorPos = None
20432086 self ._cursorBlinkUp = True
20442087 self ._cells = []
2088+ self ._oldCells : List [int ] = []
2089+ self ._handleUpdateQueue = deque (maxlen = 1 )
2090+ self ._updateTimer = RepeatedTimer (
2091+ UPDATE_DISPLAY_PERIODICALLY_INTERVAL ,
2092+ self ._updateDisplayPeriodically
2093+ )
2094+ self ._updateTimer .start ()
20452095 self ._cursorBlinkTimer = None
20462096 config .post_configProfileSwitch .register (self .handlePostConfigProfileSwitch )
20472097 if config .conf ["braille" ]["tetherTo" ] == TetherTo .AUTO .value :
@@ -2064,6 +2114,9 @@ def terminate(self):
20642114 if self ._cursorBlinkTimer :
20652115 self ._cursorBlinkTimer .Stop ()
20662116 self ._cursorBlinkTimer = None
2117+ if self ._updateTimer :
2118+ self ._updateTimer .stop ()
2119+ self ._updateTimer = None
20672120 config .post_configProfileSwitch .unregister (self .handlePostConfigProfileSwitch )
20682121 if self .display :
20692122 self .display .terminate ()
@@ -2468,6 +2521,12 @@ def handleCaretMove(
24682521 if shouldAutoTether :
24692522 self .setTether (TetherTo .FOCUS .value , auto = True )
24702523 if self ._tether != TetherTo .FOCUS .value :
2524+ # Braille display content is updated in case where:
2525+ # braille is tethered to review, review cursor does not follow system caret,
2526+ # and focus object is navigator object.
2527+ if not config .conf ["reviewCursor" ]["followCaret" ]:
2528+ if obj == api .getNavigatorObject ():
2529+ self .handleUpdate (obj )
24712530 return
24722531 region = self .mainBuffer .regions [- 1 ] if self .mainBuffer .regions else None
24732532 if region and region .obj == obj :
@@ -2526,7 +2585,7 @@ def _handleProgressBarUpdate(
25262585 self .handleUpdate (obj )
25272586 return
25282587
2529- def handleUpdate (self , obj : "NVDAObject" ) -> None :
2588+ def handleUpdate (self , obj : "NVDAObject" , fromTimer : bool = False ) -> None :
25302589 if not self .enabled :
25312590 return
25322591 if objectBelowLockScreenAndWindowsIsLocked (obj ):
@@ -2549,11 +2608,15 @@ def handleUpdate(self, obj: "NVDAObject") -> None:
25492608 self ._handleProgressBarUpdate (obj )
25502609 return
25512610 self .mainBuffer .saveWindow ()
2611+ log .debug ("saadaanko debug-varoitus" )
25522612 region .update ()
25532613 self .mainBuffer .update ()
25542614 self .mainBuffer .restoreWindow ()
25552615 if self .buffer is self .mainBuffer :
2616+ if fromTimer and self ._oldCells == self .buffer .windowBrailleCells :
2617+ return
25562618 self .update ()
2619+ self ._oldCells = self .buffer .windowBrailleCells .copy ()
25572620 elif self .buffer is self .messageBuffer and keyboardHandler .keyCounter > self ._keyCountForLastMessage :
25582621 self ._dismissMessage ()
25592622
@@ -2686,6 +2749,19 @@ def _ackTimeoutResetter(self, param: int):
26862749 self .display ._awaitingAck = False
26872750 self ._writeCellsInBackground ()
26882751
2752+ def _updateDisplayPeriodically (self ):
2753+ """Timer runs this function periodically to ensure content is up to date."""
2754+ if self .buffer is not self .mainBuffer :
2755+ return
2756+ obj : NVDAObject
2757+ if api .isObjectInActiveTreeInterceptor (api .getNavigatorObject ()):
2758+ obj = api .getCaretObject ()
2759+ elif handler .getTether () == TetherTo .FOCUS .value :
2760+ obj = api .getFocusObject ()
2761+ else :
2762+ obj = api .getNavigatorObject ()
2763+ self ._handleUpdateQueue .append (obj )
2764+
26892765
26902766# Maps old braille display driver names to new drivers that supersede old drivers.
26912767# Ensure that if a user has set a preferred driver which has changed name, the new
@@ -2720,14 +2796,17 @@ def initialize():
27202796 handler .setDisplayByName (config .conf ["braille" ]["display" ])
27212797
27222798def pumpAll ():
2723- """Runs tasks at the end of each core cycle. For now just caret updates."""
2799+ """Runs tasks at the end of each core cycle."""
2800+ if len (handler ._handleUpdateQueue ):
2801+ handler .handleUpdate (handler ._handleUpdateQueue .popleft (), True )
27242802 handler .handlePendingCaretUpdate ()
27252803
27262804def terminate ():
27272805 global handler
27282806 handler .terminate ()
27292807 handler = None
27302808
2809+
27312810class BrailleDisplayDriver (driverHandler .Driver ):
27322811 """Abstract base braille display driver.
27332812 Each braille display driver should be a separate Python module in the root brailleDisplayDrivers directory
0 commit comments