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

Keystrokes to adjust applications volume and mute [take 2] #16591

Merged
merged 73 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
c11dfeb
Applications volume adjusting
mltony May 21, 2024
bbbf25b
settings gui
mltony May 23, 2024
19b8e08
Update source/audio/appsVolume.py
mltony May 23, 2024
36513c3
Update source/audio/appsVolume.py
mltony May 23, 2024
b8baa90
Update source/config/configSpec.py
mltony May 24, 2024
2748aba
Update source/audio/appsVolume.py
mltony May 24, 2024
e000846
Update source/audio/appsVolume.py
mltony May 24, 2024
e0e1e75
Update source/audio/appsVolume.py
mltony May 24, 2024
5d4b9f6
Update source/audio/appsVolume.py
mltony May 24, 2024
4766d8a
Update source/audio/appsVolume.py
mltony May 27, 2024
48d0c13
Update source/audio/appsVolume.py
mltony May 27, 2024
eeb5a06
Update source/audio/appsVolume.py
mltony May 27, 2024
4268370
Update source/audio/appsVolume.py
mltony May 27, 2024
62d0d36
Update source/audio/appsVolume.py
mltony May 27, 2024
1a60a17
Update source/gui/settingsDialogs.py
mltony May 27, 2024
d65714c
Merge branch 'master' into appsVolume2
mltony May 27, 2024
5a4d407
Fix merging errors
mltony May 27, 2024
4da7ae6
Fix volume jump between enabled and mute states
mltony May 27, 2024
135c364
Apply suggestions from code review
seanbudd May 28, 2024
480f63c
Addressing feedback
mltony Jun 1, 2024
eb8615a
lint
mltony Jun 1, 2024
774d455
lint
mltony Jun 1, 2024
595666d
Merge branch 'master' into appsVolume2
mltony Jun 1, 2024
f072918
Addressing comments
mltony Jun 2, 2024
d02272b
Apply suggestions from code review
seanbudd Jun 7, 2024
54ee032
Update source/gui/settingsDialogs.py
mltony Jun 8, 2024
6dc8deb
Addressing comments
mltony Jun 8, 2024
eebfce9
Merge branch 'master' into appsVolume2
mltony Jun 8, 2024
f0ccb57
Update source/audio/__init__.py
mltony Jun 8, 2024
6c16a0f
Update source/audio/__init__.py
mltony Jun 10, 2024
5f3c67d
Gesture description
mltony Jun 10, 2024
ba03e7b
Addressing comments
mltony Jun 15, 2024
40d7ed2
Merge branch 'master' into appsVolume2
mltony Jun 15, 2024
3c35252
Addressing comments
mltony Jun 22, 2024
ea10921
Merge branch 'master' into appsVolume2
mltony Jun 22, 2024
28207bf
Update source/audio/appsVolume.py
mltony Jun 29, 2024
1f9f228
Addressing feedback
mltony Jun 29, 2024
4159906
Update source/audio/utils.py
mltony Jun 29, 2024
190ffec
lint
mltony Jun 29, 2024
2070e59
Merge commit '03364662e' into appsVolume2
mltony Jul 6, 2024
3e9fee2
Merge commit '8fb8ffcaba' into appsVolume2
mltony Jul 6, 2024
1bde672
renormalize files
mltony Jul 6, 2024
28fded1
Merge remote-tracking branch 'origin/master' into appsVolume2
mltony Jul 6, 2024
3bbe911
Update changes
mltony Jul 14, 2024
19c8082
Merge branch 'master' into appsVolume2
mltony Jul 14, 2024
b805c3a
lint
mltony Jul 15, 2024
e5e3a4a
lint
mltony Jul 15, 2024
ef42ee5
Merge branch 'master' into appsVolume2
mltony Jul 15, 2024
168400b
lint
mltony Jul 16, 2024
df40ee9
Update user_docs/en/userGuide.md
mltony Jul 16, 2024
9f92805
Addressing suggestions
mltony Jul 16, 2024
bd5ba26
Addressing suggestions
mltony Jul 16, 2024
8ff1189
Merge branch 'master' into appsVolume2
mltony Jul 16, 2024
aa91d04
docs
mltony Jul 21, 2024
58e7069
Merge branch 'master' into appsVolume2
mltony Jul 21, 2024
4bc85d6
Update user_docs/en/userGuide.md
seanbudd Jul 29, 2024
bb8dcc3
Update user_docs/en/userGuide.md
seanbudd Jul 29, 2024
5b1def2
Update user_docs/en/changes.md
mltony Aug 10, 2024
e8dc3e9
Update user_docs/en/changes.md
mltony Aug 10, 2024
9880c4b
Merge branch 'master' into appsVolume2
mltony Aug 10, 2024
29dd081
Pre-commit auto-fix
pre-commit-ci[bot] Aug 10, 2024
56d8c47
Add separate mute other apps command and checkbox
mltony Aug 10, 2024
02e0bfa
Pre-commit auto-fix
pre-commit-ci[bot] Aug 10, 2024
7d2fbbb
docs
mltony Aug 10, 2024
992bd7e
Pre-commit auto-fix
pre-commit-ci[bot] Aug 10, 2024
847f631
Merge branch 'master' into appsVolume2
mltony Aug 17, 2024
d2360db
Addressing feedback
mltony Aug 17, 2024
d281d57
Update user_docs/en/userGuide.md
mltony Aug 31, 2024
d7f8736
Merge branch 'master' into appsVolume2
mltony Aug 31, 2024
090ec98
doc
mltony Aug 31, 2024
3ad3af8
Update user_docs/en/changes.md
seanbudd Sep 2, 2024
cfd83c3
Update user_docs/en/changes.md
mltony Sep 2, 2024
3d08419
Merge branch 'master' into appsVolume2
mltony Sep 2, 2024
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
39 changes: 39 additions & 0 deletions source/audio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,48 @@
_setSoundSplitState,
_toggleSoundSplitState,
)
from . import appsVolume, soundSplit, utils
import atexit
import nvwave
from pycaw.utils import AudioUtilities
from comtypes import COMError
from logHandler import log
seanbudd marked this conversation as resolved.
Show resolved Hide resolved

__all__ = [
"SoundSplitState",
"_setSoundSplitState",
"_toggleSoundSplitState",
]

audioUtilitiesInitialized: bool = False


def initialize() -> None:
if nvwave.usingWasapiWavePlayer():
try:
AudioUtilities.GetAudioSessionManager()
except COMError:
log.exception("Could not initialize audio session manager")
return
log.debug("Initializing utils")
utils.initialize()
log.debug("Initializing appsVolume")
appsVolume.initialize()
log.debug("Initializing soundSplit")
soundSplit.initialize()
global audioUtilitiesInitialized
audioUtilitiesInitialized = True
else:
log.debug("Cannot initialize audio utilities as WASAPI is disabled")
seanbudd marked this conversation as resolved.
Show resolved Hide resolved

mltony marked this conversation as resolved.
Show resolved Hide resolved

@atexit.register
def terminate():
if not audioUtilitiesInitialized:
log.debug("Skipping terminating audio utilities as initialization was skipped.")
elif not nvwave.usingWasapiWavePlayer():
log.debug("Skipping terminating audio utilites as WASAPI is disabled.")
else:
soundSplit.terminate()
appsVolume.terminate()
utils.terminate()
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
160 changes: 160 additions & 0 deletions source/audio/appsVolume.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2024 NV Access Limited
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

import config
import globalVars
from logHandler import log
import nvwave
from pycaw.utils import AudioSession
import ui
from dataclasses import dataclass
from threading import Lock
from config.featureFlagEnums import AppsVolumeAdjusterFlag
from typing import NamedTuple
from .utils import AudioSessionCallback, DummyAudioSessionCallback


class VolumeAndMute(NamedTuple):
volume: float
mute: bool


_appVolumesCache: dict[int, VolumeAndMute] = {}
_appVolumesCacheLock = Lock()
_activeCallback: DummyAudioSessionCallback | None = None


def initialize() -> None:
state = config.conf["audio"]["applicationsVolumeMode"]
if state == AppsVolumeAdjusterFlag.MUTED:
state = AppsVolumeAdjusterFlag.ENABLED
config.conf["audio"]["applicationsVolumeMode"] = state
volume = config.conf["audio"]["applicationsSoundVolume"]
_updateAppsVolumeImpl(volume / 100.0, state)

seanbudd marked this conversation as resolved.
Show resolved Hide resolved
seanbudd marked this conversation as resolved.
Show resolved Hide resolved

def terminate():
global _activeCallback
if _activeCallback is not None:
_activeCallback.unregister()
_activeCallback = None


@dataclass(unsafe_hash=True)
class VolumeSetter(AudioSessionCallback):
volumeAndMute: VolumeAndMute | None = None

def getOriginalVolumeAndMute(self, pid: int) -> VolumeAndMute:
try:
with _appVolumesCacheLock:
originalVolumeAndMute = _appVolumesCache[pid]
del _appVolumesCache[pid]
except KeyError:
originalVolumeAndMute = VolumeAndMute(volume=1.0, mute=False)
return originalVolumeAndMute

def onSessionUpdate(self, session: AudioSession) -> None:
pid = session.ProcessId
simpleVolume = session.SimpleAudioVolume
with _appVolumesCacheLock:
if pid not in _appVolumesCache:
_appVolumesCache[pid] = VolumeAndMute(
volume=simpleVolume.GetMasterVolume(),
mute=simpleVolume.GetMute(),
)
if pid != globalVars.appPid:
simpleVolume.SetMasterVolume(self.volumeAndMute.volume, None)
simpleVolume.SetMute(self.volumeAndMute.mute, None)

def onSessionTerminated(self, session: AudioSession) -> None:
pid = session.ProcessId
simpleVolume = session.SimpleAudioVolume
originalVolumeAndMute = self.getOriginalVolumeAndMute(pid)
try:
simpleVolume.SetMasterVolume(originalVolumeAndMute.volume, None)
simpleVolume.SetMute(originalVolumeAndMute.mute, None)
except Exception:
log.exception(f"Could not restore master volume of process {pid} upon exit.")

seanbudd marked this conversation as resolved.
Show resolved Hide resolved
mltony marked this conversation as resolved.
Show resolved Hide resolved

def _updateAppsVolumeImpl(
volume: float,
state: AppsVolumeAdjusterFlag,
):
global _activeCallback
if state == AppsVolumeAdjusterFlag.DISABLED:
newCallback = DummyAudioSessionCallback()
runTerminators = True
else:
newCallback = VolumeSetter(
volumeAndMute=VolumeAndMute(
volume=volume,
mute=state == AppsVolumeAdjusterFlag.MUTED,
)
)
runTerminators = False
if _activeCallback is not None:
_activeCallback.unregister(runTerminators=runTerminators)
_activeCallback = newCallback
_activeCallback.register()


def _adjustAppsVolume(
volumeAdjustment: int | None = None,
):
if not nvwave.usingWasapiWavePlayer():
message = _(
# Translators: error message when wasapi is turned off.
"Other applications' volume cannot be adjusted. "
"Please enable WASAPI in the Advanced category in NVDA Settings to use it."
)
ui.message(message)
return
volume: int = config.conf["audio"]["applicationsSoundVolume"]
state = config.conf["audio"]["applicationsVolumeMode"]
if state != AppsVolumeAdjusterFlag.ENABLED:
# Translators: error message when applications' volume is disabled
msg = _("Please enable applications' volume adjuster in order to adjust applications' volume")
ui.message(msg)
return
volume += volumeAdjustment
volume = max(0, min(100, volume))
log.debug(f"Adjusting applications volume by {volumeAdjustment}% to {volume}%")
config.conf["audio"]["applicationsSoundVolume"] = volume

# We skip running terminators here to avoid application volume spiking to 100% for a split second.
_updateAppsVolumeImpl(volume / 100.0, state)
# Translators: Announcing new applications' volume message
msg = _("Applications volume {}").format(volume)
ui.message(msg)
seanbudd marked this conversation as resolved.
Show resolved Hide resolved


_APPS_VOLUME_STATES_ORDER = [
AppsVolumeAdjusterFlag.DISABLED,
AppsVolumeAdjusterFlag.ENABLED,
AppsVolumeAdjusterFlag.MUTED,
]


def _toggleAppsVolumeState():
if not nvwave.usingWasapiWavePlayer():
message = _(
# Translators: error message when wasapi is turned off.
"Other applications' volume cannot be adjusted. "
"Please enable WASAPI in the Advanced category in NVDA Settings to use it."
)
ui.message(message)
return
state = config.conf["audio"]["applicationsVolumeMode"]
volume: int = config.conf["audio"]["applicationsSoundVolume"]
try:
index = _APPS_VOLUME_STATES_ORDER.index(state)
except ValueError:
index = -1
index = (index + 1) % len(_APPS_VOLUME_STATES_ORDER)
state = _APPS_VOLUME_STATES_ORDER[index]
config.conf["audio"]["applicationsVolumeMode"] = state.name
_updateAppsVolumeImpl(volume / 100.0, state)
ui.message(state.displayString)
Loading