diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py index c0d847b0bd3..d52ca1b1feb 100644 --- a/source/NVDAObjects/UIA/winConsoleUIA.py +++ b/source/NVDAObjects/UIA/winConsoleUIA.py @@ -1,7 +1,7 @@ # A part of NonVisual Desktop Access (NVDA) # This file is covered by the GNU General Public License. # See the file COPYING for more details. -# Copyright (C) 2019-2020 Bill Dengler +# Copyright (C) 2019-2021 Bill Dengler import ctypes import NVDAHelper @@ -16,7 +16,7 @@ from ..window import Window -class consoleUIATextInfo(UIATextInfo): +class TextInfo(UIATextInfo): def __init__(self, obj, position, _rangeObj=None): collapseToEnd = None # We want to limit textInfos to just the visible part of the console. @@ -33,7 +33,7 @@ def __init__(self, obj, position, _rangeObj=None): log.warning("Couldn't get bounding range for console", exc_info=True) # Fall back to presenting the entire buffer. _rangeObj, collapseToEnd = None, None - super(consoleUIATextInfo, self).__init__(obj, position, _rangeObj) + super(TextInfo, self).__init__(obj, position, _rangeObj) if collapseToEnd is not None: self.collapse(end=collapseToEnd) @@ -90,7 +90,7 @@ def move(self, unit, direction, endPoint=None): def _move(self, unit, direction, endPoint=None): "Perform a move without respect to bounding." - return super(consoleUIATextInfo, self).move(unit, direction, endPoint) + return super(TextInfo, self).move(unit, direction, endPoint) def __ne__(self, other): """Support more accurate caret move detection.""" @@ -99,17 +99,17 @@ def __ne__(self, other): def _get_text(self): # #10036: return a space if the text range is empty. # Consoles don't actually store spaces, the character is merely left blank. - res = super(consoleUIATextInfo, self)._get_text() + res = super(TextInfo, self)._get_text() if not res: return ' ' else: return res -class consoleUIATextInfoPre21H1(consoleUIATextInfo): - """Fixes expand/collapse on end inclusive UIA text ranges, uses rangeFromPoint - instead of broken GetVisibleRanges for bounding, and implements word - movement support.""" +class TextInfoWorkaroundEndInclusive(TextInfo): + """Implementation of various workarounds for pre-microsoft/terminal#4018 + conhost: fixes expand/collapse, uses rangeFromPoint instead of broken + GetVisibleRanges for bounding, and implements word movement support.""" def _getBoundingRange(self, obj, position): # We could use IUIAutomationTextRange::getVisibleRanges, but it seems very broken in consoles # once more than a few screens worth of content has been written to the console. @@ -139,12 +139,12 @@ def _getBoundingRange(self, obj, position): return (_rangeObj, None) def collapse(self, end=False): - """Works around a UIA bug on Windows 10 versions before 21H1. + """Works around a UIA bug on conhost versions before microsoft/terminal#4018. 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(end=end) + super(TextInfo, self).collapse(end=end) if not end: self._rangeObj.MoveEndpointByRange( UIAHandler.TextPatternRangeEndpoint_Start, @@ -153,7 +153,7 @@ def collapse(self, end=False): ) def compareEndPoints(self, other, which): - """Works around a UIA bug on Windows 10 versions before 21H1. + """Works around a UIA bug on conhost versions before microsoft/terminal#4018. 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. @@ -168,7 +168,7 @@ def compareEndPoints(self, other, which): def setEndPoint(self, other, which): """Override of L{textInfos.TextInfo.setEndPoint}. - Works around a UIA bug on Windows 10 versions before 21H1 that means we can + Works around a UIA bug on conhost versions before microsoft/terminal#4018 that means we can not trust the "end" endpoint of a collapsed (empty) text range for comparisons. """ @@ -205,11 +205,11 @@ def expand(self, unit): wordEndPoints[1] ) else: - return super(consoleUIATextInfo, self).expand(unit) + return super(TextInfo, self).expand(unit) def _move(self, unit, direction, endPoint=None): if unit == textInfos.UNIT_WORD and direction != 0: - # On Windows 10 versions before 21H1, UIA doesn't implement word + # On conhost versions before microsoft/terminal#4018, UIA doesn't implement word # movement, so we need to do it manually. # Relative to the current line, calculate our offset # and the current word's offsets. @@ -261,7 +261,7 @@ def _move(self, unit, direction, endPoint=None): endPoint=endPoint ) else: # moving by a unit other than word - res = super(consoleUIATextInfo, self).move(unit, direction, + res = super(TextInfo, self).move(unit, direction, endPoint) if not endPoint: # #10191: IUIAutomationTextRange::move in consoles does not correctly produce a collapsed range @@ -309,7 +309,7 @@ def _getWordOffsetsInThisLine(self, offset, lineInfo): ) def _isCollapsed(self): - """Works around a UIA bug on Windows 10 versions before 21H1 that means we + """Works around a UIA bug on conhost versions before microsoft/terminal#4018 that means we cannot trust the "end" endpoint of a collapsed (empty) text range for comparisons. Instead we check to see if we can get the first character from the @@ -348,19 +348,31 @@ def _get_windowThreadID(self): threadID = super().windowThreadID return threadID - def _get_is21H1Plus(self): - "Returns whether this is a newer version of Windows Console with an improved UIA implementation." + def _get_isImprovedTextRangeAvailable(self): + """This property determines whether microsoft/terminal#4495 + and by extension microsoft/terminal#4018 are present in this conhost. + In consoles before these PRs, a number of workarounds were needed + in our UIA implementation. However, these do not fix all bugs and are + problematic on newer console releases. This property is therefore used + internally to determine whether to activate workarounds and as a + convenience when debugging. + """ # microsoft/terminal#4495: In newer consoles, # IUIAutomationTextRange::getVisibleRanges returns one visible range. + # Therefore, if exactly one range is returned, it is almost definitely a newer console. return self.UIATextPattern.GetVisibleRanges().length == 1 def _get_TextInfo(self): """Overriding _get_TextInfo and thus the TextInfo property on NVDAObjects.UIA.UIA - consoleUIATextInfo bounds review to the visible text. - ConsoleUIATextInfoPre21H1 fixes expand/collapse and implements word - movement.""" - return consoleUIATextInfo if self.is21H1Plus else consoleUIATextInfoPre21H1 + TextInfo bounds review to the visible text. + TextInfoWorkaroundEndInclusive fixes expand/collapse and implements + word movement.""" + return ( + TextInfo + if self.isImprovedTextRangeAvailable + else TextInfoWorkaroundEndInclusive + ) def detectPossibleSelectionChange(self): try: @@ -384,4 +396,4 @@ def findExtraOverlayClasses(obj, clsList): class WinTerminalUIA(EnhancedTermTypedCharSupport): def _get_TextInfo(self): - return consoleUIATextInfo + return TextInfo