From f83b1426f85e589a1d5fe2c6104571343fae5b9d Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Tue, 18 Jun 2019 22:09:17 -0400 Subject: [PATCH 1/5] Work around UIA caret/selection bugs in Windows 10 1903 and later. --- source/NVDAObjects/UIA/winConsoleUIA.py | 37 ++++++++++++++++++------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py index ba725d1ca3f..36070902fc0 100644 --- a/source/NVDAObjects/UIA/winConsoleUIA.py +++ b/source/NVDAObjects/UIA/winConsoleUIA.py @@ -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): @@ -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 From b5d6753e878f4a367e9f0d2472caff11b341ec07 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Tue, 18 Jun 2019 23:54:35 -0400 Subject: [PATCH 2/5] On Windows 10, use UIA caret events for determining caret movement. --- source/NVDAObjects/UIA/__init__.py | 2 ++ source/_UIAHandler.py | 5 ++++- source/editableText.py | 7 +++++++ source/textInfos/__init__.py | 3 +++ source/winVersion.py | 1 + 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index f4479ca8429..faa9ef7a680 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -868,6 +868,8 @@ def findOverlayClasses(self,clsList): # Add editableText support if UIA supports a text pattern if self.TextInfo==UIATextInfo: clsList.append(EditableTextWithoutAutoSelectDetection) + # Some UIA objects take a while to send caret events. + self._caretMovementTimeoutMultiplier = 2 clsList.append(UIA) diff --git a/source/_UIAHandler.py b/source/_UIAHandler.py index 91209981952..f45b907fadc 100644 --- a/source/_UIAHandler.py +++ b/source/_UIAHandler.py @@ -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()), diff --git a/source/editableText.py b/source/editableText.py index a8e5fd5e518..db948ce88bc 100755 --- a/source/editableText.py +++ b/source/editableText.py @@ -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. @@ -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) @@ -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: diff --git a/source/textInfos/__init__.py b/source/textInfos/__init__.py index 4497e183436..b975ce63f38 100755 --- a/source/textInfos/__init__.py +++ b/source/textInfos/__init__.py @@ -551,6 +551,9 @@ def getMathMl(self, field): """ raise NotImplementedError + def __ne__(self, other): + return not self.__eq__(other) + RE_EOL = re.compile("\r\n|[\n\r]") def convertToCrlf(text): """Convert a string so that it contains only CRLF line endings. diff --git a/source/winVersion.py b/source/winVersion.py index 242403ee18d..22d766402b3 100644 --- a/source/winVersion.py +++ b/source/winVersion.py @@ -34,6 +34,7 @@ def isWin10(version=1507, atLeast=True): @param version: a release version of Windows 10 (such as 1903). @param atLeast: return True if NVDA is running on at least this Windows 10 build (i.e. this version or higher). """ + return True from logHandler import log win10VersionsToBuilds={ 1507: 10240, From 0abed24c0fc988932d9645bea9970e5ac4dcc6c8 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Wed, 19 Jun 2019 00:31:34 -0400 Subject: [PATCH 3/5] Remove hardcoded development value. --- source/winVersion.py | 1 - 1 file changed, 1 deletion(-) diff --git a/source/winVersion.py b/source/winVersion.py index 22d766402b3..242403ee18d 100644 --- a/source/winVersion.py +++ b/source/winVersion.py @@ -34,7 +34,6 @@ def isWin10(version=1507, atLeast=True): @param version: a release version of Windows 10 (such as 1903). @param atLeast: return True if NVDA is running on at least this Windows 10 build (i.e. this version or higher). """ - return True from logHandler import log win10VersionsToBuilds={ 1507: 10240, From a1ed2cd724a66ff883b2e2ac9654cfe6fd5ac97f Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Thu, 20 Jun 2019 21:22:21 -0400 Subject: [PATCH 4/5] Move caret fixes to consoles. --- source/NVDAObjects/UIA/__init__.py | 2 -- source/NVDAObjects/UIA/winConsoleUIA.py | 5 +++++ source/textInfos/__init__.py | 5 ----- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index faa9ef7a680..f4479ca8429 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -868,8 +868,6 @@ def findOverlayClasses(self,clsList): # Add editableText support if UIA supports a text pattern if self.TextInfo==UIATextInfo: clsList.append(EditableTextWithoutAutoSelectDetection) - # Some UIA objects take a while to send caret events. - self._caretMovementTimeoutMultiplier = 2 clsList.append(UIA) diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py index 36070902fc0..20e6d1d8412 100644 --- a/source/NVDAObjects/UIA/winConsoleUIA.py +++ b/source/NVDAObjects/UIA/winConsoleUIA.py @@ -204,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): @@ -232,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. + _caretMovementTimeout = 2 def _reportNewText(self, line): # Additional typed character filtering beyond that in LiveText diff --git a/source/textInfos/__init__.py b/source/textInfos/__init__.py index b975ce63f38..16d3946fdd5 100755 --- a/source/textInfos/__init__.py +++ b/source/textInfos/__init__.py @@ -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" @@ -551,9 +549,6 @@ def getMathMl(self, field): """ raise NotImplementedError - def __ne__(self, other): - return not self.__eq__(other) - RE_EOL = re.compile("\r\n|[\n\r]") def convertToCrlf(text): """Convert a string so that it contains only CRLF line endings. From 8d119b1b5f2a157b213be88dac1ae7c08f02a0e4 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Thu, 20 Jun 2019 21:32:04 -0400 Subject: [PATCH 5/5] Review actions. --- source/NVDAObjects/UIA/winConsoleUIA.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py index 20e6d1d8412..0fedbdfaaa6 100644 --- a/source/NVDAObjects/UIA/winConsoleUIA.py +++ b/source/NVDAObjects/UIA/winConsoleUIA.py @@ -236,7 +236,7 @@ class WinConsoleUIA(Terminal): #: 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. - _caretMovementTimeout = 2 + _caretMovementTimeoutMultiplier = 2 def _reportNewText(self, line): # Additional typed character filtering beyond that in LiveText