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

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

Merged
merged 5 commits into from
Jun 21, 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
42 changes: 32 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,9 @@ def _getWordOffsetsInThisLine(self, offset, lineInfo):
min(end.value, max(1, len(lineText) - 2))
)

def __ne__(self,other):
return not self==other


class consoleUIAWindow(Window):
def _get_focusRedirect(self):
Expand Down Expand Up @@ -215,6 +235,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 = 2

def _reportNewText(self, line):
# Additional typed character filtering beyond that in LiveText
Expand Down
5 changes: 4 additions & 1 deletion source/_UIAHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,10 @@
}

if winVersion.isWin10():
UIAEventIdsToNVDAEventNames[UIA_Text_TextChangedEventId] = "textChange"
UIAEventIdsToNVDAEventNames.update({
UIA_Text_TextChangedEventId: "textChange",
UIA_Text_TextSelectionChangedEventId:"caret"
})

ignoreWinEventsMap = {
UIA_AutomationPropertyChangedEventId: list(UIAPropertyIdsToNVDAEventNames.keys()),
Expand Down
7 changes: 7 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 All @@ -81,6 +84,10 @@ def _hasCaretMoved(self, bookmark, retryInterval=0.01, timeout=None, origWord=No
if eventHandler.isPendingEvents("gainFocus"):
log.debug("Focus event. Elapsed: %d ms" % elapsed)
return (True,None)
# Some controls implement a caret event
if eventHandler.isPendingEvents("caret"):
newInfo = self.makeTextInfo(textInfos.POSITION_CARET)
return (True, newInfo)
# If the focus changes after this point, fetching the caret may fail,
# but we still want to stay in this loop.
try:
Expand Down
2 changes: 0 additions & 2 deletions source/textInfos/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,6 @@ def __eq__(self,other):
if isinstance(other,Bookmark) and self.infoClass==other.infoClass and self.data==other.data:
return True

def __ne__(self,other):
return not self==other

#Unit constants
UNIT_CHARACTER="character"
Expand Down