Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V2 of UI Automation in Windows Console: work around Microsoft bugs on Windows 10 version 1903 and improve caret movement #9802

Merged
merged 3 commits into from
Jun 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 33 additions & 10 deletions source/NVDAObjects/UIA/winConsoleUIA.py
Original file line number Diff line number Diff line change
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."""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does ne relate to caret move detection?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain exactly what happens if you don't implement this method?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__ne__ is necessary as UIATextInfo has overridden __eq__```. So we want ```__ne__``` to return the opposite. This should really actually be on UIATextInfo itself, but this has caused strange bugs in the past, espcially in Edge. We are not sure why IUIAutomationTextRange.compare does not work correctly for some implementations. In consoles however, it does work okay. Note that in Python3 it will no longer be neceesary to provide a ```__ne__``` here as Python3 automatically implements ```__ne__ as the negative of __eq__.

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
Original file line number Diff line number Diff line change
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