Skip to content

Commit

Permalink
UI Automation in Windows Console: add "API levels" as a better isImpr…
Browse files Browse the repository at this point in the history
…ovedTextRangeAvailable (#12660)

Summary of the issue:
The Windows 11 inbox console will ship with most, but not all, fixes required to make UIA by default a good experience. However, a new conhost version will be delivered "out-of-box" including additional functionality and bug fixes. Therefore, isImprovedTextRangeAvailable (a boolean value) is now insufficient to express the range of possible UIA console versions now in the wild.

Description of how this pull request fixes the issue:
Adds an enumeration, WinConsoleAPILevel, with comments describing the meaning of each member.
Refactors NVDA's current code to use the new enum without changing current functionality. Functionality changes to follow in subsequent PRs.
Expose apiLevel in dev info to ease debugging.
Deprecate isImprovedTextRangeAvailable, but refactor it for now to use apiLevel.

Co-authored-by: Leonard de Ruijter <leonardder@users.noreply.github.com>
Co-authored-by: buddsean <sean@nvaccess.org>
  • Loading branch information
3 people authored Jul 22, 2021
1 parent 23d0417 commit 5407954
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 26 deletions.
38 changes: 26 additions & 12 deletions source/NVDAObjects/UIA/winConsoleUIA.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@

from comtypes import COMError
from logHandler import log
from UIAUtils import _isImprovedConhostTextRangeAvailable
from UIAUtils import _getConhostAPILevel
from _UIAConstants import WinConsoleAPILevel
from . import UIATextInfo
from ..behaviors import EnhancedTermTypedCharSupport, KeyboardHandlerBasedTypedCharSupport
from ..window import Window
Expand Down Expand Up @@ -335,9 +336,21 @@ class WinConsoleUIA(KeyboardHandlerBasedTypedCharSupport):
#: a lot of text.
STABILIZE_DELAY = 0.03

def _get_apiLevel(self) -> WinConsoleAPILevel:
"""
This property shows which of several console UIA workarounds are
needed in a given conhost instance.
See the comments on the WinConsoleAPILevel enum for details.
"""
self.apiLevel = _getConhostAPILevel(self.windowHandle)
return self.apiLevel

def _get__caretMovementTimeoutMultiplier(self):
"On older consoles, the caret can take a while to move."
return 1 if self.isImprovedTextRangeAvailable else 1.5
return (
1 if self.apiLevel >= WinConsoleAPILevel.IMPROVED
else 1.5
)

def _get_windowThreadID(self):
# #10113: Windows forces the thread of console windows to match the thread of the first attached process.
Expand All @@ -351,15 +364,11 @@ def _get_windowThreadID(self):
return threadID

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.
"""
return _isImprovedConhostTextRangeAvailable(self.windowHandle)
log.warning(
"winConsole.isImprovedTextRangeAvailable is deprecated and will be "
"removed in NVDA 2022.1. Please use apiLevel instead."
)
return self.apiLevel >= WinConsoleAPILevel.IMPROVED

def _get_TextInfo(self):
"""Overriding _get_ConsoleUIATextInfo and thus the ConsoleUIATextInfo property
Expand All @@ -369,10 +378,15 @@ def _get_TextInfo(self):
word movement."""
return (
ConsoleUIATextInfo
if self.isImprovedTextRangeAvailable
if self.apiLevel >= WinConsoleAPILevel.IMPROVED
else ConsoleUIATextInfoWorkaroundEndInclusive
)

def _get_devInfo(self):
info = super().devInfo
info.append(f"API level: {self.apiLevel.name}")
return info

def detectPossibleSelectionChange(self):
try:
return super().detectPossibleSelectionChange()
Expand Down
37 changes: 24 additions & 13 deletions source/UIAUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import weakref
from functools import lru_cache
from logHandler import log
from _UIAConstants import WinConsoleAPILevel


def createUIAMultiPropertyCondition(*dicts):
Expand Down Expand Up @@ -298,22 +299,20 @@ def _shouldUseUIAConsole(hwnd: int) -> bool:
# #7497: the UIA implementation in old conhost is incomplete, therefore we
# should ignore it.
# When the UIA implementation is improved, the below line will be replaced
# with a call to _isImprovedConhostTextRangeAvailable.
# with a check that _getConhostAPILevel >= FORMATTED.
return False


@lru_cache(maxsize=10)
def _isImprovedConhostTextRangeAvailable(hwnd: int) -> bool:
"""This function 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 function is therefore used
to determine whether this console's UIA implementation is good enough to
use by default."""
# microsoft/terminal#4495: In newer consoles,
def _getConhostAPILevel(hwnd: int) -> WinConsoleAPILevel:
"""
This function determines which of several console UIA workarounds are
needed in a given conhost instance.
See the comments on the WinConsoleAPILevel enum for details.
"""
# microsoft/terminal#4495: In IMPROVED consoles,
# IUIAutomationTextRange::getVisibleRanges returns one visible range.
# Therefore, if exactly one range is returned, it is almost definitely a newer console.
# Therefore, if exactly one range is returned, it is almost definitely an IMPROVED console.
try:
UIAElement = UIAHandler.handler.clientObject.ElementFromHandleBuildCache(
hwnd, UIAHandler.handler.baseCacheRequest
Expand All @@ -330,7 +329,19 @@ def _isImprovedConhostTextRangeAvailable(hwnd: int) -> bool:
UIATextPattern = textArea.GetCurrentPattern(
UIAHandler.UIA_TextPatternId
).QueryInterface(UIAHandler.IUIAutomationTextPattern)
return UIATextPattern.GetVisibleRanges().length == 1
visiRanges = UIATextPattern.GetVisibleRanges()
if visiRanges.length == 1:
# Microsoft/terminal#2161: FORMATTED consoles expose text formatting
# information to UIA.
if isinstance(
visiRanges.GetElement(0).GetAttributeValue(UIAHandler.UIA_FontNameAttributeId),
str
):
return WinConsoleAPILevel.FORMATTED
else:
return WinConsoleAPILevel.IMPROVED
else:
return WinConsoleAPILevel.END_INCLUSIVE
except (COMError, ValueError):
log.exception()
return False
return WinConsoleAPILevel.END_INCLUSIVE
24 changes: 23 additions & 1 deletion source/_UIAConstants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2021 NV Access Limited
# Copyright (C) 2021 NV Access Limited, Bill Dengler
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

Expand Down Expand Up @@ -60,3 +60,25 @@ class UIAutomationType(enum.IntEnum):
OUT_POINT_ARRAY = 28
OUT_RECT_ARRAY = 29
OUT_ELEMENT_ARRAY = 30


class WinConsoleAPILevel(enum.IntEnum):
"""
Defines actively used Windows Console versions and the levels of custom code required
for each.
"""
# Represents a console before microsoft/terminal#4018 was merged.
# These consoles do not support UIA word navigation and require a number
# of text range workarounds.
END_INCLUSIVE = 0
# Represents a console with microsoft/terminal#4018, but without
# resolution of microsoft/terminal#2161 (text formatting)
# or microsoft/terminal#6986 (extraneous empty lines).
# This is a significant improvement over END_INCLUSIVE, so fewer workarounds
# are required. However, these consoles lack some information
# (such as text formatting) and require bounding, so are unsuitable for
# usage by default.
IMPROVED = 1
# Represents an IMPROVED console that exposes text formatting and a
# buffer that does not contain extraneous empty lines.
FORMATTED = 2
4 changes: 4 additions & 0 deletions user_docs/en/changes.t2t
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ What's New in NVDA


== Changes for Developers ==
- ``NVDAObjects.UIA.winConsoleUIA.WinConsoleUIA.isImprovedTextRangeAvailable`` has been deprecated for removal in 2022.1. (#12660)
- Instead use ``apiLevel`` (see the comments at ``_UIAConstants.WinConsoleAPILevel`` for details).
-
-


= 2021.2 =
Expand Down

0 comments on commit 5407954

Please sign in to comment.