diff --git a/source/config/configSpec.py b/source/config/configSpec.py index 658b5ce78d8..91375da0bcc 100644 --- a/source/config/configSpec.py +++ b/source/config/configSpec.py @@ -57,7 +57,7 @@ # Audio settings [audio] audioDuckingMode = integer(default=0) - wasapi = boolean(default=true) + WASAPI = featureFlag(optionsEnum="BoolFlag", behaviorOfDefault="disabled") soundVolumeFollowsVoice = boolean(default=false) soundVolume = integer(default=100, min=0, max=100) diff --git a/source/config/profileUpgradeSteps.py b/source/config/profileUpgradeSteps.py index 541ae93e14b..cc4ed3f9e71 100644 --- a/source/config/profileUpgradeSteps.py +++ b/source/config/profileUpgradeSteps.py @@ -23,9 +23,6 @@ ReportTableHeaders, ReportCellBorders, ) -from typing import ( - Dict, -) import configobj.validate from configobj import ConfigObj diff --git a/source/gui/nvdaControls.py b/source/gui/nvdaControls.py index f128f4dafca..7002a00b483 100644 --- a/source/gui/nvdaControls.py +++ b/source/gui/nvdaControls.py @@ -459,7 +459,7 @@ def __init__( name=name, ) - self.SetSelection(self._getChoiceIndex(self._getConfigValue().value)) + self.SetSelection(self._getChoiceIndex(configValue.value)) self.defaultValue = self._getConfSpecDefaultValue() """The default value of the config spec. Not the "behavior of default". This is provided to maintain compatibility with other controls in the @@ -499,10 +499,18 @@ def resetToConfigSpecDefault(self) -> None: """ self.SetSelection(self._getChoiceIndex(self.defaultValue)) + def _getControlCurrentValue(self) -> enum.Enum: + return list(self._translatedOptions.keys())[self.GetSelection()] + + def _getControlCurrentFlag(self) -> FeatureFlag: + flagValue = self._getControlCurrentValue() + currentFlag = self._getConfigValue() + return FeatureFlag(flagValue, currentFlag.behaviorOfDefault) + def saveCurrentValueToConf(self) -> None: """ Set the config value to the current value of the control. """ - flagValue: enum.Enum = list(self._translatedOptions.keys())[self.GetSelection()] + flagValue = self._getControlCurrentValue() keyPath = self._confPath if not keyPath or len(keyPath) < 1: raise ValueError("Key path not provided") diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 9906a420bdb..a13b6c706b1 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -55,6 +55,7 @@ List, Optional, Set, + cast, ) import core import keyboardHandler @@ -3074,19 +3075,17 @@ def __init__(self, parent): audioGroup = guiHelper.BoxSizerHelper(self, sizer=audio) sHelper.addItem(audioGroup) - # Translators: This is the label for a checkbox control in the - # Advanced settings panel. + # Translators: This is the label for a checkbox control in the Advanced settings panel. label = _("Use WASAPI for audio output (requires restart)") - self.wasapiCheckBox: wx.CheckBox = audioGroup.addItem( - wx.CheckBox(audioBox, label=label) - ) - self.bindHelpEvent("WASAPI", self.wasapiCheckBox) - self.wasapiCheckBox.Bind(wx.EVT_CHECKBOX, self.onAudioCheckBoxChange) - self.wasapiCheckBox.SetValue( - config.conf["audio"]["wasapi"] - ) - self.wasapiCheckBox.defaultValue = self._getDefaultValue( - ["audio", "wasapi"]) + self.wasapiComboBox = cast(nvdaControls.FeatureFlagCombo, audioGroup.addLabeledControl( + labelText=label, + wxCtrlClass=nvdaControls.FeatureFlagCombo, + keyPath=["audio", "WASAPI"], + conf=config.conf, + )) + self.bindHelpEvent("WASAPI", self.wasapiComboBox) + self.wasapiComboBox.Bind(wx.EVT_CHOICE, self._onWASAPIChange) + # Translators: This is the label for a checkbox control in the # Advanced settings panel. label = _("Volume of NVDA sounds follows voice volume (requires WASAPI)") @@ -3094,7 +3093,7 @@ def __init__(self, parent): wx.CheckBox(audioBox, label=label) ) self.bindHelpEvent("SoundVolumeFollowsVoice", self.soundVolFollowCheckBox) - self.soundVolFollowCheckBox.Bind(wx.EVT_CHECKBOX, self.onAudioCheckBoxChange) + self.soundVolFollowCheckBox.Bind(wx.EVT_CHECKBOX, self._onWASAPIChange) self.soundVolFollowCheckBox.SetValue( config.conf["audio"]["soundVolumeFollowsVoice"] ) @@ -3115,7 +3114,7 @@ def __init__(self, parent): ) self.soundVolSlider.defaultValue = self._getDefaultValue( ["audio", "soundVolume"]) - self.onAudioCheckBoxChange() + self._onWASAPIChange() # Translators: This is the label for a group of advanced options in the # Advanced settings panel @@ -3178,8 +3177,8 @@ def onOpenScratchpadDir(self,evt): path=config.getScratchpadDir(ensureExists=True) os.startfile(path) - def onAudioCheckBoxChange(self, evt: Optional[wx.CommandEvent] = None): - wasapi = self.wasapiCheckBox.IsChecked() + def _onWASAPIChange(self, evt: Optional[wx.CommandEvent] = None): + wasapi = bool(self.wasapiComboBox._getControlCurrentFlag()) self.soundVolFollowCheckBox.Enable(wasapi) self.soundVolSlider.Enable( wasapi @@ -3213,7 +3212,7 @@ def haveConfigDefaultsBeenRestored(self): and self.loadChromeVBufWhenBusyCombo.isValueConfigSpecDefault() and self.caretMoveTimeoutSpinControl.GetValue() == self.caretMoveTimeoutSpinControl.defaultValue and self.reportTransparentColorCheckBox.GetValue() == self.reportTransparentColorCheckBox.defaultValue - and self.wasapiCheckBox.GetValue() == self.wasapiCheckBox.defaultValue + and self.wasapiComboBox.isValueConfigSpecDefault() and self.soundVolFollowCheckBox.GetValue() == self.soundVolFollowCheckBox.defaultValue and self.soundVolSlider.GetValue() == self.soundVolSlider.defaultValue and set(self.logCategoriesList.CheckedItems) == set(self.logCategoriesList.defaultCheckedItems) @@ -3242,7 +3241,7 @@ def restoreToDefaults(self): self.loadChromeVBufWhenBusyCombo.resetToConfigSpecDefault() self.caretMoveTimeoutSpinControl.SetValue(self.caretMoveTimeoutSpinControl.defaultValue) self.reportTransparentColorCheckBox.SetValue(self.reportTransparentColorCheckBox.defaultValue) - self.wasapiCheckBox.SetValue(self.wasapiCheckBox.defaultValue) + self.wasapiComboBox.resetToConfigSpecDefault() self.soundVolFollowCheckBox.SetValue(self.soundVolFollowCheckBox.defaultValue) self.soundVolSlider.SetValue(self.soundVolSlider.defaultValue) self.logCategoriesList.CheckedItems = self.logCategoriesList.defaultCheckedItems @@ -3275,7 +3274,7 @@ def onSave(self): config.conf["documentFormatting"]["reportTransparentColor"] = ( self.reportTransparentColorCheckBox.IsChecked() ) - config.conf["audio"]["wasapi"] = self.wasapiCheckBox.IsChecked() + self.wasapiComboBox.saveCurrentValueToConf() config.conf["audio"]["soundVolumeFollowsVoice"] = self.soundVolFollowCheckBox.IsChecked() config.conf["audio"]["soundVolume"] = self.soundVolSlider.GetValue() config.conf["annotations"]["reportDetails"] = self.annotationsDetailsCheckBox.IsChecked() @@ -3288,6 +3287,7 @@ def onSave(self): config.conf['debugLog'][key]=self.logCategoriesList.IsChecked(index) config.conf["featureFlag"]["playErrorSound"] = self.playErrorSoundCombo.GetSelection() + class AdvancedPanel(SettingsPanel): enableControlsCheckBox = None # type: wx.CheckBox # Translators: This is the label for the Advanced settings panel. diff --git a/source/nvwave.py b/source/nvwave.py index d5e7494b71f..f8dd9a7d4cd 100644 --- a/source/nvwave.py +++ b/source/nvwave.py @@ -36,7 +36,7 @@ UINT, LPUINT ) -from comtypes import HRESULT, BSTR, GUID +from comtypes import HRESULT, BSTR from comtypes.hresult import S_OK import atexit import weakref @@ -1044,7 +1044,7 @@ def _deviceNameToId(name, fallbackToDefault=True): def initialize(): global WavePlayer - if not config.conf["audio"]["wasapi"]: + if not config.conf["audio"]["WASAPI"]: return WavePlayer = WasapiWavePlayer NVDAHelper.localLib.wasPlay_create.restype = c_void_p diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index d3e79a040aa..c5a008118f5 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -13,14 +13,6 @@ What's New in NVDA - The Add-ons Manager has been removed and replaced by the Add-on Store. - For more information please read the updated user guide. - -- Enhanced sound management: - - NVDA now outputs audio via the Windows Audio Session API (WASAPI), which may improve the responsiveness, performance and stability of NVDA speech and sounds. - This can be disabled in Advanced settings if audio problems are encountered. (#14697) - - It is now possible to have the volume of NVDA sounds and beeps follow the volume setting of the voice you are using. - This option can be enabled in Advanced settings. (#1409) - - You can now separately control the volume of NVDA sounds. - This can be configured in Advanced settings. (#1409) - - - New input gestures: - An unbound gesture to cycle through the available languages for Windows OCR. (#13036) - An unbound gesture to cycle through the Braille show messages modes. (#14864) @@ -57,6 +49,15 @@ What's New in NVDA - When colors are enabled Document Formatting, background colours are now reported in Microsoft Word. (#5866) - When using Excel shortcuts to toggle format such as bold, italic, underline and strike through of a cell in Excel, the result is now reported. (#14923) - +- Experimental enhanced sound management: + - NVDA can now output audio via the Windows Audio Session API (WASAPI), which may improve the responsiveness, performance and stability of NVDA speech and sounds. (#14697) + - WASAPI usage can be enabled in Advanced settings. + Additionally, if WASAPI is enabled, the following Advanced settings can also be configured. + - An option to have the volume of NVDA sounds and beeps follow the volume setting of the voice you are using. (#1409) + - An option to separately configure the volume of NVDA sounds. (#1409, #15038) + - + - There is a known issue with intermittent crashing when WASAPI is enabled. (#15150) + - - In Mozilla Firefox and Google Chrome, NVDA now reports when a control opens a dialog, grid, list or tree if the author has specified this using aria-haspopup. (#14709) - It is now possible to use system variables (such as ``%temp%`` or ``%homepath%``) in the path specification while creating portable copies of NVDA. (#14680) - In Windows 10 May 2019 Update and later, NVDA can announce virtual desktop names when opening, changing, and closing them. (#5641) diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 3906cbe208d..200200d2940 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -2358,9 +2358,14 @@ In some situations, the text background may be entirely transparent, with the te With several historically popular GUI APIs, the text may be rendered with a transparent background, but visually the background color is accurate. ==== Use WASAPI for audio output ====[WASAPI] +: Default + Disabled +: Options + Disabled, Enabled +: + This option enables audio output via the Windows Audio Session API (WASAPI). WASAPI is a more modern audio framework which may improve the responsiveness, performance and stability of NVDA audio output, including both speech and sounds. -This option is enabled by default. After changing this option, you will need to restart NVDA for the change to take effect. ==== Volume of NVDA sounds follows voice volume ====[SoundVolumeFollowsVoice]