Skip to content

Commit 02c1467

Browse files
authored
Merge 3c3b303 into 9157916
2 parents 9157916 + 3c3b303 commit 02c1467

1 file changed

Lines changed: 94 additions & 1 deletion

File tree

source/braille.py

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
Tuple,
2020
Union,
2121
Type,
22+
Callable,
2223
)
2324
from locale import strxfrm
2425

@@ -42,6 +43,7 @@
4243
TetherTo,
4344
ReportTableHeaders,
4445
)
46+
from config.featureFlag import FeatureFlag
4547
from config.featureFlagEnums import ReviewRoutingMovesSystemCaretFlag
4648
from logHandler import log
4749
import controlTypes
@@ -53,6 +55,7 @@
5355
import re
5456
import scriptHandler
5557
import collections
58+
from collections import deque
5659
import extensionPoints
5760
import hwPortUtils
5861
import bdDetect
@@ -1569,6 +1572,7 @@ def bufferPosToRegionPos(self, bufferPos):
15691572
raise LookupError("No such position")
15701573

15711574
def regionPosToBufferPos(self, region, pos, allowNearest=False):
1575+
start: int = 0
15721576
for testRegion, start, end in self.regionsWithPositions:
15731577
if region == testRegion:
15741578
if pos < end - start:
@@ -2003,6 +2007,44 @@ def formatCellsForLog(cells: List[int]) -> str:
20032007
"""
20042008

20052009

2010+
class UpdateTimer:
2011+
"""Repeating timer for keeping display content always up to date."""
2012+
2013+
def __init__(
2014+
self,
2015+
interval: float,
2016+
updateFunction: Callable[[], None]
2017+
):
2018+
"""Constructor.
2019+
@param interval: Checking frequency
2020+
@param updateFunction: Update display
2021+
"""
2022+
self._interval = interval
2023+
self._timer = threading.Timer(self._interval, self._run)
2024+
self._updateFunction = updateFunction
2025+
self.is_running = False
2026+
self.start()
2027+
2028+
def _run(self):
2029+
self.is_running = False
2030+
self.start()
2031+
self._updateFunction()
2032+
2033+
def start(self):
2034+
if not self.is_running:
2035+
self._timer = threading.Timer(self._interval, self._run)
2036+
self._timer.start()
2037+
self.is_running = True
2038+
2039+
def stop(self):
2040+
self._timer.cancel()
2041+
self.is_running = False
2042+
2043+
2044+
BRAILLE_UPDATE_CHECK_INTERVAL: float = 0.5
2045+
"""Timer interval in milliseconds for L{BrailleHandler._enqueueBrailleUpdateCheck}."""
2046+
2047+
20062048
class BrailleHandler(baseObject.AutoPropertyObject):
20072049
# TETHER_AUTO, TETHER_FOCUS, TETHER_REVIEW and tetherValues
20082050
# are deprecated, but remain to retain API backwards compatibility
@@ -2042,6 +2084,16 @@ def __init__(self):
20422084
self._cursorPos = None
20432085
self._cursorBlinkUp = True
20442086
self._cells = []
2087+
self._showSelection: FeatureFlag = config.conf["braille"]["showSelection"]
2088+
self._showCursor: bool = config.conf["braille"]["showCursor"]
2089+
# Was braille line updated during previous timer cycle.
2090+
self._alreadyUpdated: bool = False
2091+
self._handleUpdateQueue = deque(maxlen=1)
2092+
self._updateTimer = UpdateTimer(
2093+
BRAILLE_UPDATE_CHECK_INTERVAL,
2094+
self._enqueueBrailleUpdateCheck
2095+
)
2096+
self._updateTimer.start()
20452097
self._cursorBlinkTimer = None
20462098
config.post_configProfileSwitch.register(self.handlePostConfigProfileSwitch)
20472099
if config.conf["braille"]["tetherTo"] == TetherTo.AUTO.value:
@@ -2064,6 +2116,9 @@ def terminate(self):
20642116
if self._cursorBlinkTimer:
20652117
self._cursorBlinkTimer.Stop()
20662118
self._cursorBlinkTimer = None
2119+
if self._updateTimer:
2120+
self._updateTimer.stop()
2121+
self._updateTimer = None
20672122
config.post_configProfileSwitch.unregister(self.handlePostConfigProfileSwitch)
20682123
if self.display:
20692124
self.display.terminate()
@@ -2332,6 +2387,7 @@ def _blink(self):
23322387
self._displayWithCursor()
23332388

23342389
def update(self):
2390+
self._alreadyUpdated = True
23352391
cells = self.buffer.windowBrailleCells
23362392
self._rawText = self.buffer.windowRawText
23372393
if log.isEnabledFor(log.IO):
@@ -2341,6 +2397,8 @@ def update(self):
23412397
self._cells = cells + [0] * (self.displaySize - len(cells))
23422398
self._cursorPos = self.buffer.cursorWindowPos
23432399
self._updateDisplay()
2400+
self._showSelection = config.conf["braille"]["showSelection"]
2401+
self._showCursor = config.conf["braille"]["showCursor"]
23442402

23452403
def scrollForward(self):
23462404
self.buffer.scrollForward()
@@ -2468,6 +2526,12 @@ def handleCaretMove(
24682526
if shouldAutoTether:
24692527
self.setTether(TetherTo.FOCUS.value, auto=True)
24702528
if self._tether != TetherTo.FOCUS.value:
2529+
# Braille display content is updated in case where:
2530+
# braille is tethered to review, review cursor does not follow system caret,
2531+
# and focus object is navigator object.
2532+
if not config.conf["reviewCursor"]["followCaret"]:
2533+
if obj == api.getNavigatorObject():
2534+
self.handleUpdate(obj)
24712535
return
24722536
region = self.mainBuffer.regions[-1] if self.mainBuffer.regions else None
24732537
if region and region.obj==obj:
@@ -2686,6 +2750,32 @@ def _ackTimeoutResetter(self, param: int):
26862750
self.display._awaitingAck = False
26872751
self._writeCellsInBackground()
26882752

2753+
def _brailleUpdateCheck(self) -> None:
2754+
"""Braille may need update when show cursor or show selection state change or when in terminal window."""
2755+
if self.buffer is not self.mainBuffer:
2756+
return
2757+
if self._alreadyUpdated:
2758+
self._alreadyUpdated = False
2759+
return
2760+
obj: NVDAObject
2761+
if api.isObjectInActiveTreeInterceptor(api.getNavigatorObject()):
2762+
obj = api.getCaretObject()
2763+
elif self.getTether() == TetherTo.FOCUS.value:
2764+
obj = api.getFocusObject()
2765+
else:
2766+
obj = api.getNavigatorObject()
2767+
if (
2768+
hasattr(obj, "role") and obj.role == controlTypes.Role.TERMINAL
2769+
or self._showSelection != config.conf["braille"]["showSelection"]
2770+
):
2771+
self.handleUpdate(obj)
2772+
elif self._showCursor != config.conf["braille"]["showCursor"]:
2773+
self.update()
2774+
2775+
def _enqueueBrailleUpdateCheck(self) -> None:
2776+
"""Enques braille update check."""
2777+
self._handleUpdateQueue.append(self._brailleUpdateCheck)
2778+
26892779

26902780
# Maps old braille display driver names to new drivers that supersede old drivers.
26912781
# Ensure that if a user has set a preferred driver which has changed name, the new
@@ -2720,14 +2810,17 @@ def initialize():
27202810
handler.setDisplayByName(config.conf["braille"]["display"])
27212811

27222812
def pumpAll():
2723-
"""Runs tasks at the end of each core cycle. For now just caret updates."""
2813+
"""Runs tasks at the end of each core cycle."""
2814+
if len(handler._handleUpdateQueue):
2815+
handler._handleUpdateQueue.popleft()()
27242816
handler.handlePendingCaretUpdate()
27252817

27262818
def terminate():
27272819
global handler
27282820
handler.terminate()
27292821
handler = None
27302822

2823+
27312824
class BrailleDisplayDriver(driverHandler.Driver):
27322825
"""Abstract base braille display driver.
27332826
Each braille display driver should be a separate Python module in the root brailleDisplayDrivers directory

0 commit comments

Comments
 (0)