From f003e223e1202ab4e3a7285fcb59960da87d33bf Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Mon, 25 May 2026 19:39:37 -0700 Subject: [PATCH 1/3] Fixup attribution, add a skip-lines beep, and hidden config flags for the new LiveText speech generator --- source/NVDAObjects/behaviors.py | 58 +++++++++++++++++++++++++-------- source/config/configSpec.py | 6 ++++ source/gui/settingsDialogs.py | 21 ++++++++++++ user_docs/en/changes.md | 2 +- user_docs/en/userGuide.md | 10 ++++++ 5 files changed, 83 insertions(+), 14 deletions(-) diff --git a/source/NVDAObjects/behaviors.py b/source/NVDAObjects/behaviors.py index 07f785c2a34..e15d5e91222 100755 --- a/source/NVDAObjects/behaviors.py +++ b/source/NVDAObjects/behaviors.py @@ -1,8 +1,8 @@ # 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) 2006-2025 NV Access Limited, Peter Vágner, Joseph Lee, Bill Dengler, -# Burman's Computer and Education Ltd, Cary-rowen, Cyrille Bougot +# Copyright (C) 2006-2026 NV Access Limited, Peter Vágner, Joseph Lee, Bill Dengler, +# Burman's Computer and Education Ltd, Cary-rowen, Cyrille Bougot, Ethin Probst """Mix-in classes which provide common behaviour for particular types of controls across different APIs. Behaviors described in this mix-in include providing table navigation commands for certain table rows, terminal input and output support, announcing notifications and suggestion items and so on. @@ -11,6 +11,7 @@ import os import time import threading +import math import tones import queueHandler import eventHandler @@ -383,10 +384,6 @@ class LiveText(NVDAObject): # If the text is live, this is definitely content. presentationType = NVDAObject.presType_content - MAX_LINES: int = 100 - """The maximum number of lines that will be reported when a large number of lines are queued. - Subclasses may override this to allow custom line reporting batches. - """ announceNewLineText = False def initOverlayClass(self): @@ -468,20 +465,55 @@ def _reportNewLines(self, lines: list[str]) -> None: Subclasses may override this method to provide custom filtering of new text, where logic depends on multiple lines. """ - droppedCount = len(lines) - self.MAX_LINES - if droppedCount > 0: - lines = lines[-self.MAX_LINES :] + maxNewLines: int = config.conf["terminals"]["maxNewLines"] + if maxNewLines: + droppedCount = len(lines) - maxNewLines + if droppedCount > 0: + if ( + config.conf["terminals"]["beepForSkippedLines"] + and speech.getState().speechMode == speech.SpeechMode.talk + ): + tones.beep( + config.conf["terminals"]["skippedLinesBeepHz"], + self._getSkippedLinesBeepLength(droppedCount), + ) + lines = lines[-maxNewLines:] if self._reportNewLinesGenID is not None: queueHandler.cancelGeneratorObject(self._reportNewLinesGenID) self._reportNewLinesGenID = None - self._reportNewLinesGenID = queueHandler.registerGeneratorObject(self._reportNewLinesGenerator(lines)) + newLinesBatchSize: int = config.conf["terminals"]["newLinesBatchSize"] + if newLinesBatchSize <= 0: # Report synchronously + for line in lines: + self._reportNewText(line) + else: + self._reportNewLinesGenID = queueHandler.registerGeneratorObject( + self._reportNewLinesGenerator( + lines, + newLinesBatchSize, + ), + ) - def _reportNewLinesGenerator(self, lines: list[str]) -> Generator[None, None, None]: - YIELD_EVERY = 5 # Sweet spot between yielding on every line and a batch + @staticmethod + def _getSkippedLinesBeepLength(droppedCount: int) -> int: + minLengthMs: int = config.conf["terminals"]["skippedLinesBeepMinDurationMs"] + maxLengthMs: int = config.conf["terminals"]["skippedLinesBeepMaxDurationMs"] + if maxLengthMs < minLengthMs: + maxLengthMs = minLengthMs + droppedCount = max(droppedCount, 1) + maxNewLines: int = config.conf["terminals"]["maxNewLines"] + ratio = 1.0 if maxNewLines <= 1 else min(1.0, math.log(droppedCount, maxNewLines)) + lengthRange = maxLengthMs - minLengthMs + return round(minLengthMs + lengthRange * ratio) + + def _reportNewLinesGenerator( + self, + lines: list[str], + batchSize: int, + ) -> Generator[None, None, None]: try: for i, line in enumerate(lines, 1): self._reportNewText(line) - if i % YIELD_EVERY == 0: + if i % batchSize == 0: yield finally: self._reportNewLinesGenID = None diff --git a/source/config/configSpec.py b/source/config/configSpec.py index c8cef3ab87f..af3236135ea 100644 --- a/source/config/configSpec.py +++ b/source/config/configSpec.py @@ -309,6 +309,12 @@ [terminals] speakPasswords = boolean(default=false) keyboardSupportInLegacy = boolean(default=True) + maxNewLines = integer(min=0, default=100) + newLinesBatchSize = integer(min=0, default=5) + beepForSkippedLines = boolean(default=true) + skippedLinesBeepHz = integer(default=550, min=20) + skippedLinesBeepMinDurationMs = integer(default=10, min=5, max=5000) + skippedLinesBeepMaxDurationMs = integer(default=100, min=5, max=5000) diffAlgo = option("auto", "dmp", "difflib", default="auto") wtStrategy = featureFlag(optionsEnum="WindowsTerminalStrategyFlag", behaviorOfDefault="diffing") diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index d9fddfa3448..6ceac6b0eb2 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -4455,6 +4455,22 @@ def __init__(self, parent): ["terminals", "keyboardSupportInLegacy"], ) self.keyboardSupportInLegacyCheckBox.Enable(winVersion.getWinVer() >= winVersion.WIN10_1607) + # Translators: This is the label for a checkbox in the + # Advanced settings panel. + label = _("Beep for &skipped lines") + self.beepForSkippedLinesCheckBox = terminalsGroup.addItem( + wx.CheckBox(terminalsBox, label=label), + ) + self.bindHelpEvent( + "AdvancedSettingsBeepForSkippedLines", + self.beepForSkippedLinesCheckBox, + ) + self.beepForSkippedLinesCheckBox.SetValue( + config.conf["terminals"]["beepForSkippedLines"], + ) + self.beepForSkippedLinesCheckBox.defaultValue = self._getDefaultValue( + ["terminals", "beepForSkippedLines"], + ) # Translators: This is the label for a combo box for selecting a # method of detecting changed content in terminals in the advanced @@ -4750,6 +4766,7 @@ def haveConfigDefaultsBeenRestored(self): == self.keyboardSupportInLegacyCheckBox.defaultValue and self.winConsoleSpeakPasswordsCheckBox.IsChecked() == self.winConsoleSpeakPasswordsCheckBox.defaultValue + and self.beepForSkippedLinesCheckBox.IsChecked() == self.beepForSkippedLinesCheckBox.defaultValue and self.diffAlgoCombo.GetSelection() == self.diffAlgoCombo.defaultValue and self.wtStrategyCombo.isValueConfigSpecDefault() and self.cancelExpiredFocusSpeechCombo.GetSelection() @@ -4782,6 +4799,9 @@ def restoreToDefaults(self): self.brailleLiveRegionsCombo.resetToConfigSpecDefault() self.winConsoleSpeakPasswordsCheckBox.SetValue(self.winConsoleSpeakPasswordsCheckBox.defaultValue) self.keyboardSupportInLegacyCheckBox.SetValue(self.keyboardSupportInLegacyCheckBox.defaultValue) + self.beepForSkippedLinesCheckBox.SetValue( + self.beepForSkippedLinesCheckBox.defaultValue, + ) self.diffAlgoCombo.SetSelection(self.diffAlgoCombo.defaultValue) self.wtStrategyCombo.resetToConfigSpecDefault() self.cancelExpiredFocusSpeechCombo.SetSelection(self.cancelExpiredFocusSpeechCombo.defaultValue) @@ -4824,6 +4844,7 @@ def onSave(self): self.enhancedEventProcessingComboBox.saveCurrentValueToConf() config.conf["terminals"]["speakPasswords"] = self.winConsoleSpeakPasswordsCheckBox.IsChecked() config.conf["terminals"]["keyboardSupportInLegacy"] = self.keyboardSupportInLegacyCheckBox.IsChecked() + config.conf["terminals"]["beepForSkippedLines"] = self.beepForSkippedLinesCheckBox.IsChecked() diffAlgoChoice = self.diffAlgoCombo.GetSelection() config.conf["terminals"]["diffAlgo"] = self.diffAlgoVals[diffAlgoChoice] self.wtStrategyCombo.saveCurrentValueToConf() diff --git a/user_docs/en/changes.md b/user_docs/en/changes.md index cfd61ac4093..4a5ac2f627a 100644 --- a/user_docs/en/changes.md +++ b/user_docs/en/changes.md @@ -15,7 +15,7 @@ ### Bug Fixes * When moving to an ARIA grid cell in focus mode in web browsers, NVDA no longer reports both the row and column headers even if only the row or only the column changed. (#17750, @jcsteh) -* In live text regions, such as terminals, NVDA no longer freezes when substantial amounts of text are dumped to the screen. (#20177) +* In live text regions, such as terminals, NVDA no longer freezes when substantial amounts of text are dumped to the screen. (#20177, #20216, @ethindp, @codeofdusk) * When an application stops responding, NVDA no longer freezes or floods its log with errors; it stays responsive and drops UIA and MSAA events from the unresponsive application until it recovers. (#16749, @heath-toby) * Reduced lag on UI Automation text change events, improving the responsiveness of controls such as combo boxes and of File Explorer, by using the cached element class name instead of a live cross-process fetch. (#16749, @heath-toby) * In Mozilla Firefox, reporting annotation details now works correctly in focus mode on controls which are not editable text. (#20208, @jcsteh) diff --git a/user_docs/en/userGuide.md b/user_docs/en/userGuide.md index f9e5a2ec24c..c43c7d496b0 100644 --- a/user_docs/en/userGuide.md +++ b/user_docs/en/userGuide.md @@ -4166,6 +4166,16 @@ This feature is available and enabled by default on Windows 10 versions 1607 and Warning: with this option enabled, typed characters that do not appear onscreen, such as passwords, will not be suppressed. In untrusted environments, you may temporarily disable [speak typed characters](#KeyboardSettingsSpeakTypedCharacters) and [speak typed words](#KeyboardSettingsSpeakTypedWords) when entering passwords. +##### Beep for skipped lines {#AdvancedSettingsBeepForSkippedLines} + +| . {.hideHeaderRow} |.| +|---|---| +|Options |Disabled, Enabled| +|Default |Enabled| + +This setting controls whether NVDA plays a short beep when too many new lines arrive before they can all be reported. +The beep indicates that some lines were skipped, and becomes slightly longer as more lines are skipped. + ##### Diff algorithm {#DiffAlgo} This setting controls how NVDA determines the new text to speak in terminals. From c1141037c15122ed748c33fdd59cc8e10d1271e8 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Mon, 15 Jun 2026 17:17:10 -0700 Subject: [PATCH 2/3] Update user_docs/en/userGuide.md Co-authored-by: Sean Budd --- user_docs/en/userGuide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/user_docs/en/userGuide.md b/user_docs/en/userGuide.md index c43c7d496b0..35affe33557 100644 --- a/user_docs/en/userGuide.md +++ b/user_docs/en/userGuide.md @@ -4170,8 +4170,8 @@ In untrusted environments, you may temporarily disable [speak typed characters]( | . {.hideHeaderRow} |.| |---|---| -|Options |Disabled, Enabled| -|Default |Enabled| +| Options | Disabled, Enabled | +| Default | Enabled | This setting controls whether NVDA plays a short beep when too many new lines arrive before they can all be reported. The beep indicates that some lines were skipped, and becomes slightly longer as more lines are skipped. From fc443939c74110490d1950524a5bd7b47d9a33c3 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Mon, 15 Jun 2026 17:18:46 -0700 Subject: [PATCH 3/3] Update source/gui/settingsDialogs.py Co-authored-by: Sean Budd --- source/gui/settingsDialogs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 6ceac6b0eb2..63be0d83026 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -4456,7 +4456,7 @@ def __init__(self, parent): ) self.keyboardSupportInLegacyCheckBox.Enable(winVersion.getWinVer() >= winVersion.WIN10_1607) # Translators: This is the label for a checkbox in the - # Advanced settings panel. + # Advanced settings panel. label = _("Beep for &skipped lines") self.beepForSkippedLinesCheckBox = terminalsGroup.addItem( wx.CheckBox(terminalsBox, label=label),