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

Prototype for Windows Terminal: Use notifications instead of monitoring for new text #14047

Merged
merged 27 commits into from
Oct 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c5a4a8c
Prototype for Windows Terminal: Use notifications instead of monitori…
LeonarddeR Aug 22, 2022
60d4b36
Comments
LeonarddeR Aug 22, 2022
5054d0c
Apply suggestions from code review
LeonarddeR Aug 24, 2022
f9771b3
Fix imports
LeonarddeR Aug 24, 2022
2cafc67
Import ordering
LeonarddeR Aug 24, 2022
f39870b
Merge branch 'master' into wtNotifications
codeofdusk Aug 29, 2022
1176505
Remove Windows Terminal from textChangeUIAClassNames as it no longer …
codeofdusk Aug 29, 2022
9735811
Suppress more blanks
codeofdusk Aug 29, 2022
3b678c4
Merge branch 'master' into wtNotifications
codeofdusk Sep 20, 2022
f79d0f6
Merge branch 'master' into wtNotifications
codeofdusk Sep 29, 2022
a2bff15
Merge branch 'master' into wtNotifications
codeofdusk Oct 18, 2022
40ca1e0
Place new functionality behind a feature flag
codeofdusk Oct 18, 2022
73b5367
Remove dead code
codeofdusk Oct 18, 2022
497e3f3
Rename flag enum
codeofdusk Oct 20, 2022
2cba013
Apply suggestions from code review
codeofdusk Oct 20, 2022
69f621e
Rename _shouldUseWtNotifications to _shouldUseWindowsTerminalNotifica…
codeofdusk Oct 20, 2022
81fe5e0
Apply suggestions from code review
codeofdusk Oct 21, 2022
6a1bbb2
Style
codeofdusk Oct 21, 2022
e043da9
Add types
codeofdusk Oct 24, 2022
b6620a7
Fix hyphen
codeofdusk Oct 24, 2022
f992db5
Add alias
codeofdusk Oct 24, 2022
1b915a0
Use getattr
codeofdusk Oct 24, 2022
715168b
Update source/NVDAObjects/UIA/winConsoleUIA.py
codeofdusk Oct 24, 2022
5139a56
Rename
codeofdusk Oct 24, 2022
b706a19
Merge branch 'wtNotifications' of https://github.com/leonardder/nvda …
codeofdusk Oct 24, 2022
5fe8559
Merge remote-tracking branch 'origin/master' into wtNotifications
seanbudd Oct 25, 2022
f6f6563
update changes
seanbudd Oct 25, 2022
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
14 changes: 12 additions & 2 deletions source/NVDAObjects/UIA/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
iterUIARangeByUnit,
UIAMixedAttributeError,
UIATextRangeFromElement,
_shouldUseWindowsTerminalNotifications,
)
from NVDAObjects.window import Window
from NVDAObjects import (
Expand Down Expand Up @@ -1211,7 +1212,10 @@ def findOverlayClasses(self,clsList):
winConsoleUIA.findExtraOverlayClasses(self, clsList)
elif UIAClassName in _all_wt_UIAClassNames:
from . import winConsoleUIA
clsList.append(winConsoleUIA.WinTerminalUIA)
if _shouldUseWindowsTerminalNotifications():
clsList.append(winConsoleUIA._NotificationsBasedWinTerminalUIA)
else:
clsList.append(winConsoleUIA._DiffBasedWinTerminalUIA)

# Add editableText support if UIA supports a text pattern
if self.TextInfo==UIATextInfo:
Expand Down Expand Up @@ -2100,7 +2104,13 @@ def event_UIA_systemAlert(self):
# Ideally, we wouldn't use getPropertiesBraille directly.
braille.handler.message(braille.getPropertiesBraille(name=self.name, role=self.role))

def event_UIA_notification(self, notificationKind=None, notificationProcessing=UIAHandler.NotificationProcessing_CurrentThenMostRecent, displayString=None, activityId=None):
def event_UIA_notification(
self,
notificationKind: Optional[int] = None,
notificationProcessing: Optional[int] = UIAHandler.NotificationProcessing_CurrentThenMostRecent,
displayString: Optional[str] = None,
activityId: Optional[str] = None
):
"""
Introduced in Windows 10 Fall Creators Update (build 16299).
This base implementation announces all notifications from the UIA element.
Expand Down
77 changes: 65 additions & 12 deletions source/NVDAObjects/UIA/winConsoleUIA.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
# 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-2022 Bill Dengler
# Copyright (C) 2019-2022 Bill Dengler, Leonard de Ruijter

import api
import config
import controlTypes
import ctypes
import NVDAHelper
import NVDAState
import speech
import textInfos
import textUtils
import UIAHandler

from comtypes import COMError
from diffHandler import prefer_difflib
from logHandler import log
from UIAHandler.utils import _getConhostAPILevel
from typing import (
Any,
Optional,
)
from UIAHandler.utils import _getConhostAPILevel, _shouldUseWindowsTerminalNotifications
from UIAHandler.constants import WinConsoleAPILevel
from . import UIATextInfo
from . import UIA, UIATextInfo
from ..behaviors import EnhancedTermTypedCharSupport, KeyboardHandlerBasedTypedCharSupport
LeonarddeR marked this conversation as resolved.
Show resolved Hide resolved
from ..window import Window

Expand Down Expand Up @@ -419,14 +428,58 @@ def findExtraOverlayClasses(obj, clsList):
clsList.append(consoleUIAWindow)


class WinTerminalUIA(EnhancedTermTypedCharSupport):
class _DiffBasedWinTerminalUIA(EnhancedTermTypedCharSupport):
"""
An overlay class for Windows Terminal (wt.exe) that uses diffing to speak
new text.
"""

def event_UIA_notification(self, **kwargs):
"""
In an upcoming terminal release, UIA notification events will be sent
to announce new text. Block these for now to avoid double-reporting of
text changes.
@note: In the longer term, NVDA should leverage these events in place
of the current LiveText strategy, as performance will likely be
significantly improved and #11002 can be completely mitigated.
"""
"Block notification events when diffing to prevent double reporting."
log.debugWarning(f"Notification event blocked to avoid double-report: {kwargs}")


class _NotificationsBasedWinTerminalUIA(UIA):
"""
An overlay class for Windows Terminal (wt.exe) that uses UIA notification
events provided by the application to speak new text.
"""

#: Override the role, which is controlTypes.Role.STATICTEXT by default.
role = controlTypes.Role.TERMINAL
#: New line text is announced using UIA notification events
announceNewLineText = False

def event_UIA_notification(
self,
notificationKind: Optional[int] = None,
notificationProcessing: Optional[int] = UIAHandler.NotificationProcessing_CurrentThenMostRecent,
displayString: Optional[str] = None,
activityId: Optional[str] = None
):
# Do not announce output from background terminals.
if self.appModule != api.getFocusObject().appModule:
return
# microsoft/terminal#12358: Automatic reading of terminal output
# is provided by UIA notifications. If the user does not want
# automatic reporting of dynamic output, suppress this notification.
if not config.conf["presentation"]["reportDynamicContentChanges"]:
return
for line in displayString.splitlines():
if line and not line.isspace(): # Don't say "blank" during autoread
speech.speakText(line)


def __getattr__(attrName: str) -> Any:
"""Module level `__getattr__` used to preserve backward compatibility."""
if attrName == "WinTerminalUIA" and NVDAState._allowDeprecatedAPI():
log.warning(
"WinTerminalUIA is deprecated. "
"Instead use _DiffBasedWinTerminalUIA or _NotificationsBasedWinTerminalUIA"
)
return (
_NotificationsBasedWinTerminalUIA
if _shouldUseWindowsTerminalNotifications()
else _DiffBasedWinTerminalUIA
)
raise AttributeError(f"module {repr(__name__)} has no attribute {repr(attrName)}")
11 changes: 11 additions & 0 deletions source/UIAHandler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@

textChangeUIAClassNames = (
"_WwG", # Microsoft Word
)

windowsTerminalUIAClassNames = (
"TermControl",
"TermControl2",
"WPFTermControl",
Expand Down Expand Up @@ -648,6 +651,10 @@ def func():
if (
element.currentClassName in textChangeUIAClassNames
or element.CachedAutomationID in textChangeUIAAutomationIDs
or (
not utils._shouldUseWindowsTerminalNotifications()
and element.currentClassName in windowsTerminalUIAClassNames
)
):
group = self.localEventHandlerGroupWithTextChanges
logPrefix = "Explicitly"
Expand Down Expand Up @@ -704,6 +711,10 @@ def IUIAutomationEventHandler_HandleAutomationEvent(self,sender,eventID):
if (
sender.currentClassName in textChangeUIAClassNames
or sender.CachedAutomationID in textChangeUIAAutomationIDs
or (
not utils._shouldUseWindowsTerminalNotifications()
and sender.currentClassName in windowsTerminalUIAClassNames
)
):
NVDAEventName = "textChange"
else:
Expand Down
6 changes: 6 additions & 0 deletions source/UIAHandler/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import operator
from comtypes import COMError
import config
from config.featureFlagEnums import WindowsTerminalStrategyFlag
import ctypes
import UIAHandler
import weakref
Expand Down Expand Up @@ -362,3 +363,8 @@ def _shouldSelectivelyRegister() -> bool:
return False
else:
return winVersion.getWinVer() >= winVersion.WIN11_22H2


def _shouldUseWindowsTerminalNotifications() -> bool:
"Determines whether to use notifications for new text reporting in Windows Terminal."
return config.conf["terminals"]["wtStrategy"] == WindowsTerminalStrategyFlag.NOTIFICATIONS
1 change: 1 addition & 0 deletions source/config/configSpec.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@
speakPasswords = boolean(default=false)
keyboardSupportInLegacy = boolean(default=True)
diffAlgo = option("auto", "dmp", "difflib", default="auto")
wtStrategy = featureFlag(optionsEnum="WindowsTerminalStrategyFlag", behaviorOfDefault="diffing")

[update]
autoCheck = boolean(default=true)
Expand Down
22 changes: 21 additions & 1 deletion source/config/featureFlagEnums.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2022 NV Access Limited
# Copyright (C) 2022 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 @@ -67,6 +67,26 @@ def __bool__(self):
return self == BoolFlag.ENABLED


class WindowsTerminalStrategyFlag(DisplayStringEnum):
"""
A feature flag for defining how new text is calculated in Windows Terminal
(wt.exe).
"""

@property
def _displayStringLabels(self):
return {
# Translators: Label for an option in NVDA settings.
self.DIFFING: _("Diffing"),
# Translators: Label for an option in NVDA settings.
self.NOTIFICATIONS: _("UIA notifications"),
}

DEFAULT = enum.auto()
DIFFING = enum.auto()
NOTIFICATIONS = enum.auto()


def getAvailableEnums() -> typing.Generator[typing.Tuple[str, FlagValueEnum], None, None]:
for name, value in globals().items():
if (
Expand Down
14 changes: 14 additions & 0 deletions source/gui/settingsDialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2908,6 +2908,17 @@ def __init__(self, parent):
self._getDefaultValue(["terminals", "diffAlgo"])
)

self.wtStrategyCombo: nvdaControls.FeatureFlagCombo = terminalsGroup.addLabeledControl(
labelText=_(
# Translators: This is the label for a combo-box in the Advanced settings panel.
"Speak new text in Windows Terminal via:"
),
wxCtrlClass=nvdaControls.FeatureFlagCombo,
keyPath=["terminals", "wtStrategy"],
conf=config.conf,
)
self.bindHelpEvent("WtStrategy", self.wtStrategyCombo)

# Translators: This is the label for a group of advanced options in the
# Advanced settings panel
label = _("Speech")
Expand Down Expand Up @@ -3082,6 +3093,7 @@ def haveConfigDefaultsBeenRestored(self):
and self.keyboardSupportInLegacyCheckBox.IsChecked() == self.keyboardSupportInLegacyCheckBox.defaultValue
and self.winConsoleSpeakPasswordsCheckBox.IsChecked() == self.winConsoleSpeakPasswordsCheckBox.defaultValue
and self.diffAlgoCombo.GetSelection() == self.diffAlgoCombo.defaultValue
and self.wtStrategyCombo.isValueConfigSpecDefault()
and self.cancelExpiredFocusSpeechCombo.GetSelection() == self.cancelExpiredFocusSpeechCombo.defaultValue
and self.loadChromeVBufWhenBusyCombo.isValueConfigSpecDefault()
and self.caretMoveTimeoutSpinControl.GetValue() == self.caretMoveTimeoutSpinControl.defaultValue
Expand All @@ -3106,6 +3118,7 @@ def restoreToDefaults(self):
self.winConsoleSpeakPasswordsCheckBox.SetValue(self.winConsoleSpeakPasswordsCheckBox.defaultValue)
self.keyboardSupportInLegacyCheckBox.SetValue(self.keyboardSupportInLegacyCheckBox.defaultValue)
self.diffAlgoCombo.SetSelection(self.diffAlgoCombo.defaultValue == 'auto')
self.wtStrategyCombo.resetToConfigSpecDefault()
self.cancelExpiredFocusSpeechCombo.SetSelection(self.cancelExpiredFocusSpeechCombo.defaultValue)
self.loadChromeVBufWhenBusyCombo.resetToConfigSpecDefault()
self.caretMoveTimeoutSpinControl.SetValue(self.caretMoveTimeoutSpinControl.defaultValue)
Expand Down Expand Up @@ -3135,6 +3148,7 @@ def onSave(self):
config.conf['terminals']['diffAlgo'] = (
self.diffAlgoVals[diffAlgoChoice]
)
self.wtStrategyCombo.saveCurrentValueToConf()
config.conf["editableText"]["caretMoveTimeoutMs"]=self.caretMoveTimeoutSpinControl.GetValue()
config.conf["documentFormatting"]["reportTransparentColor"] = (
self.reportTransparentColorCheckBox.IsChecked()
Expand Down
5 changes: 5 additions & 0 deletions user_docs/en/changes.t2t
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ What's New in NVDA
- This is not referring to screen reader specific headers via named ranges, which is currently not supported via UI automation.
-
- An unassigned script has been added to toggle delayed character descriptions. (#14267)
- Added an experimental option to leverage the UIA notification support in Windows Terminal to report new or changed text in the terminal, resulting in improved stability and responsivity. (#13781)
- Consult the user guide for limitations of this experimental option.
-
-


Expand Down Expand Up @@ -51,6 +54,8 @@ Please open a GitHub issue if your Add-on has an issue with updating to the new


=== Deprecations ===
- ``NVDAObjects.UIA.winConsoleUIA.WinTerminalUIA`` is deprecated and usage is dscouraged. (#14047)
-


= 2022.4 =
Expand Down
19 changes: 19 additions & 0 deletions user_docs/en/userGuide.t2t
Original file line number Diff line number Diff line change
Expand Up @@ -2231,6 +2231,25 @@ This setting may stabilize reading of incoming text in some applications.
However, in terminals, when inserting or deleting a character in the middle of a line, the text after the caret will be read out.
-

==== Speak new text in Windows Terminal via ====[WtStrategy]
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
: Default
Diffing
: Options
Diffing, UIA notifications
:

This option selects how NVDA determines what text is "new" (and thus what to speak when "report dynamic content changes" is enabled) in Windows Terminal and the WPF Windows Terminal control used in Visual Studio 2022.
It does not affect the Windows Console (``conhost.exe``).
The Speak new text in Windows Terminal combo box has three options:
- Default: This option is currently equivalent to "diffing", but it is anticipated to change once support for UIA notifications is further developed.
- Diffing: This option uses the selected diff algorithm to calculate changes each time the terminal renders new text.
This is identical to NVDA's behaviour in versions 2022.4 and earlier.
- UIA notifications: This option defers the responsibility of determining what text to speak to Windows Terminal itself, meanning that NVDA no longer has to determine what text currently on-screen is "new".
This should markedly improve performance and stability of Windows Terminal, but this feature is not yet complete.
In particular, typed characters that are not displayed on-screen, such as passwords, are reported when this option is selected.
Additionally, contiguous spans of output of over 1,000 characters may not be reported accurately.
-

==== Attempt to cancel speech for expired focus events ====[CancelExpiredFocusSpeech]
This option enables behaviour which attempts to cancel speech for expired focus events.
In particular moving quickly through messages in Gmail with Chrome can cause NVDA to speak outdated information.
Expand Down