Skip to content

Commit

Permalink
V2 of UI Automation in Windows Console: work around Microsoft bugs on…
Browse files Browse the repository at this point in the history
… Windows 10 version 1903 and improve caret movement (nvaccess#9802)

* Check-in winConsoleUIA.py.

* Check-in editableText.py (without caret event handling)

* Reduce _caretMovementTimeoutMultiplier for consoles to balance performance and ssh stability.
  • Loading branch information
codeofdusk authored and michaelDCurran committed Jun 24, 2019
1 parent e03e697 commit 4a78085
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 10 deletions.
43 changes: 33 additions & 10 deletions source/NVDAObjects/UIA/winConsoleUIA.py
Expand Up @@ -21,19 +21,25 @@

class consoleUIATextInfo(UIATextInfo):
#: At least on Windows 10 1903, expanding then collapsing the text info
#: causes review to get stuck, so disable it.
#: caused review to get stuck, so disable it.
#: There may be no need to disable this anymore, but doing so doesn't seem
#: to do much good either.
_expandCollapseBeforeReview = False

def __init__(self, obj, position, _rangeObj=None):
super(consoleUIATextInfo, self).__init__(obj, position, _rangeObj)
if position == textInfos.POSITION_CARET and isWin10(1903, atLeast=False):
# The UIA implementation in 1903 causes the caret to be
# off-by-one, so move it one position to the right
# to compensate.
self._rangeObj.MoveEndpointByUnit(
def collapse(self,end=False):
"""Works around a UIA bug on Windows 10 1903 and later."""
if not isWin10(1903):
return super(consoleUIATextInfo, self).collapse(end=end)
# When collapsing, consoles seem to incorrectly push the start of the
# textRange back one character.
# Correct this by bringing the start back up to where the end is.
oldInfo=self.copy()
super(consoleUIATextInfo,self).collapse()
if not end:
self._rangeObj.MoveEndpointByRange(
UIAHandler.TextPatternRangeEndpoint_Start,
UIAHandler.NVDAUnitsToUIAUnits[textInfos.UNIT_CHARACTER],
1
oldInfo._rangeObj,
UIAHandler.TextPatternRangeEndpoint_Start
)

def move(self, unit, direction, endPoint=None):
Expand Down Expand Up @@ -139,6 +145,17 @@ def expand(self, unit):
else:
return super(consoleUIATextInfo, self).expand(unit)

def _get_isCollapsed(self):
"""Works around a UIA bug on Windows 10 1903 and later."""
if not isWin10(1903):
return super(consoleUIATextInfo, self)._get_isCollapsed()
# Even when a console textRange's start and end have been moved to the
# same position, the console incorrectly reports the end as being
# past the start.
# Therefore to decide if the textRange is collapsed,
# Check if it has no text.
return not bool(self._rangeObj.getText(1))

def _getCurrentOffsetInThisLine(self, lineInfo):
"""
Given a caret textInfo expanded to line, returns the index into the
Expand Down Expand Up @@ -187,6 +204,10 @@ def _getWordOffsetsInThisLine(self, offset, lineInfo):
min(end.value, max(1, len(lineText) - 2))
)

def __ne__(self,other):
"""Support more accurate caret move detection."""
return not self==other


class consoleUIAWindow(Window):
def _get_focusRedirect(self):
Expand Down Expand Up @@ -215,6 +236,8 @@ class WinConsoleUIA(Terminal):
#: Whether the console got new text lines in its last update.
#: Used to determine if typed character/word buffers should be flushed.
_hasNewLines = False
#: the caret in consoles can take a while to move on Windows 10 1903 and later.
_caretMovementTimeoutMultiplier = 1.5

def _reportNewText(self, line):
# Additional typed character filtering beyond that in LiveText
Expand Down
3 changes: 3 additions & 0 deletions source/editableText.py
Expand Up @@ -48,6 +48,8 @@ class EditableText(TextContainerObject,ScriptableObject):

_hasCaretMoved_minWordTimeoutMs=30 #: The minimum amount of time that should elapse before checking if the word under the caret has changed

_caretMovementTimeoutMultiplier = 1

def _hasCaretMoved(self, bookmark, retryInterval=0.01, timeout=None, origWord=None):
"""
Waits for the caret to move, for a timeout to elapse, or for a new focus event or script to be queued.
Expand All @@ -69,6 +71,7 @@ def _hasCaretMoved(self, bookmark, retryInterval=0.01, timeout=None, origWord=No
else:
# This function's arguments are in seconds, but we want ms.
timeoutMs = timeout * 1000
timeoutMs *= self._caretMovementTimeoutMultiplier
# time.sleep accepts seconds, so retryInterval is in seconds.
# Convert to integer ms to avoid floating point precision errors when adding to elapsed.
retryMs = int(retryInterval * 1000)
Expand Down

0 comments on commit 4a78085

Please sign in to comment.