Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
58 changes: 45 additions & 13 deletions source/NVDAObjects/behaviors.py
Original file line number Diff line number Diff line change
@@ -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
Comment thread
codeofdusk marked this conversation as resolved.

"""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.
Expand All @@ -11,6 +11,7 @@
import os
import time
import threading
import math
import tones
import queueHandler
import eventHandler
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions source/config/configSpec.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment on lines +312 to +317

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the hidden config options, in favour of constants stored in code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? This makes them immutable at runtime.

Might it be better to expose a visible feature flag to control the new behaviour instead?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which need to be exposed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At minimum, "beep for skipped lines" (already in settings) and a toggle (hidden or user-facing) to switch back to the old behaviour. Right now, maxNewLines of 0 disables the new behaviour and setting different values tunes it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think exposing maxNewLines is fine, but the rest I think should remain in code and be removed from config

diffAlgo = option("auto", "dmp", "difflib", default="auto")
wtStrategy = featureFlag(optionsEnum="WindowsTerminalStrategyFlag", behaviorOfDefault="diffing")

Expand Down
21 changes: 21 additions & 0 deletions source/gui/settingsDialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -4750,6 +4766,7 @@ def haveConfigDefaultsBeenRestored(self):
== self.keyboardSupportInLegacyCheckBox.defaultValue
and self.winConsoleSpeakPasswordsCheckBox.IsChecked()
== self.winConsoleSpeakPasswordsCheckBox.defaultValue
and self.beepForSkippedLinesCheckBox.IsChecked() == self.beepForSkippedLinesCheckBox.defaultValue
Comment thread
codeofdusk marked this conversation as resolved.
and self.diffAlgoCombo.GetSelection() == self.diffAlgoCombo.defaultValue
and self.wtStrategyCombo.isValueConfigSpecDefault()
and self.cancelExpiredFocusSpeechCombo.GetSelection()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion user_docs/en/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment thread
codeofdusk marked this conversation as resolved.
* 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)
Expand Down
10 changes: 10 additions & 0 deletions user_docs/en/userGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Comment thread
codeofdusk marked this conversation as resolved.

| . {.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.
Comment thread
codeofdusk marked this conversation as resolved.

##### Diff algorithm {#DiffAlgo}

This setting controls how NVDA determines the new text to speak in terminals.
Expand Down
Loading