From f73618ef2982c2fd58f7a8de6728db61cc772f32 Mon Sep 17 00:00:00 2001 From: mltony Date: Thu, 18 Jan 2024 14:50:22 -0800 Subject: [PATCH 01/44] Sound split --- source/audio.py | 210 ++++++++++++ .../comInterfaces/coreAudio.bak/__init__.py | 0 .../coreAudio.bak/audioclient/__init__.py | 155 +++++++++ .../coreAudio.bak/audioclient/depend.py | 18 ++ .../coreAudio.bak/audiopolicy/__init__.py | 299 +++++++++++++++++ .../comInterfaces/coreAudio.bak/constants.py | 56 ++++ .../coreAudio.bak/endpointvolume/__init__.py | 182 +++++++++++ .../coreAudio.bak/endpointvolume/depend.py | 22 ++ .../coreAudio.bak/mmdeviceapi/__init__.py | 186 +++++++++++ .../mmdeviceapi/depend/__init__.py | 52 +++ .../mmdeviceapi/depend/structures.py | 59 ++++ source/comInterfaces/coreAudio/__init__.py | 0 .../coreAudio/audioclient/__init__.py | 155 +++++++++ .../coreAudio/audioclient/depend.py | 22 ++ .../coreAudio/audiopolicy/__init__.py | 301 ++++++++++++++++++ source/comInterfaces/coreAudio/constants.py | 60 ++++ .../coreAudio/endpointvolume/__init__.py | 184 +++++++++++ .../coreAudio/endpointvolume/depend.py | 24 ++ .../coreAudio/mmdeviceapi/__init__.py | 188 +++++++++++ .../coreAudio/mmdeviceapi/depend/__init__.py | 54 ++++ .../mmdeviceapi/depend/structures.py | 61 ++++ source/config/configSpec.py | 2 + source/core.py | 3 + source/globalCommands.py | 16 + source/gui/settingsDialogs.py | 31 ++ user_docs/en/changes.t2t | 1 + user_docs/en/userGuide.t2t | 13 +- 27 files changed, 2353 insertions(+), 1 deletion(-) create mode 100644 source/audio.py create mode 100644 source/comInterfaces/coreAudio.bak/__init__.py create mode 100644 source/comInterfaces/coreAudio.bak/audioclient/__init__.py create mode 100644 source/comInterfaces/coreAudio.bak/audioclient/depend.py create mode 100644 source/comInterfaces/coreAudio.bak/audiopolicy/__init__.py create mode 100644 source/comInterfaces/coreAudio.bak/constants.py create mode 100644 source/comInterfaces/coreAudio.bak/endpointvolume/__init__.py create mode 100644 source/comInterfaces/coreAudio.bak/endpointvolume/depend.py create mode 100644 source/comInterfaces/coreAudio.bak/mmdeviceapi/__init__.py create mode 100644 source/comInterfaces/coreAudio.bak/mmdeviceapi/depend/__init__.py create mode 100644 source/comInterfaces/coreAudio.bak/mmdeviceapi/depend/structures.py create mode 100644 source/comInterfaces/coreAudio/__init__.py create mode 100644 source/comInterfaces/coreAudio/audioclient/__init__.py create mode 100644 source/comInterfaces/coreAudio/audioclient/depend.py create mode 100644 source/comInterfaces/coreAudio/audiopolicy/__init__.py create mode 100644 source/comInterfaces/coreAudio/constants.py create mode 100644 source/comInterfaces/coreAudio/endpointvolume/__init__.py create mode 100644 source/comInterfaces/coreAudio/endpointvolume/depend.py create mode 100644 source/comInterfaces/coreAudio/mmdeviceapi/__init__.py create mode 100644 source/comInterfaces/coreAudio/mmdeviceapi/depend/__init__.py create mode 100644 source/comInterfaces/coreAudio/mmdeviceapi/depend/structures.py diff --git a/source/audio.py b/source/audio.py new file mode 100644 index 00000000000..6fb652edec5 --- /dev/null +++ b/source/audio.py @@ -0,0 +1,210 @@ +# A part of NonVisual Desktop Access (NVDA) +# Copyright (C) 2015-2021 NV Access Limited +# This file is covered by the GNU General Public License. +# See the file COPYING for more details. + +import atexit +from comInterfaces.coreAudio.constants import ( + CLSID_MMDeviceEnumerator, + EDataFlow, + ERole, +) +import comInterfaces.coreAudio.audioclient as audioclient +import comInterfaces.coreAudio.audiopolicy as audiopolicy +import comInterfaces.coreAudio.mmdeviceapi as mmdeviceapi +import comtypes +import config +from enum import IntEnum, unique +import globalVars +from logHandler import log +import nvwave +from typing import Tuple, Optional, Dict, List, Callable, NoReturn +import ui +from utils.displayString import DisplayStringIntEnum + +VolumeTupleT = Tuple[float, float] + + +@unique +class SoundSplitState(DisplayStringIntEnum): + OFF = 0 + NVDA_LEFT = 1 + NVDA_RIGHT = 2 + + @property + def _displayStringLabels(self) -> Dict[IntEnum, str]: + return { + # Translators: Sound split state + SoundSplitState.OFF: _("Disabled sound split"), + # Translators: Sound split state + SoundSplitState.NVDA_LEFT: _("NVDA on the left and applications on the right"), + # Translators: Sound split state + SoundSplitState.NVDA_RIGHT: _("NVDA on the right and applications on the left"), + } + + def getAppVolume(self) -> VolumeTupleT: + return { + SoundSplitState.OFF: (1.0, 1.0), + SoundSplitState.NVDA_LEFT: (0.0, 1.0), + SoundSplitState.NVDA_RIGHT: (1.0, 0.0), + }[self] + + def getNVDAVolume(self) -> VolumeTupleT: + return { + SoundSplitState.OFF: (1.0, 1.0), + SoundSplitState.NVDA_LEFT: (1.0, 0.0), + SoundSplitState.NVDA_RIGHT: (0.0, 1.0), + }[self] + + +@unique +class SoundSplitToggleMode(DisplayStringIntEnum): + OFF_LEFT_RIGHT = 0 + OFF_AND_NVDA_LEFT = 1 + OFF_AND_NVDA_RIGHT = 2 + + @property + def _displayStringLabels(self) -> Dict[IntEnum, str]: + return { + # Translators: Sound split toggle mode + SoundSplitToggleMode.OFF_AND_NVDA_LEFT: _("Cycles through off and NVDA on the left"), + # Translators: Sound split toggle mode + SoundSplitToggleMode.OFF_AND_NVDA_RIGHT: _("Cycles through off and NVDA on the right"), + # Translators: Sound split toggle mode + SoundSplitToggleMode.OFF_LEFT_RIGHT: _("Cycles through off, NVDA on the left and NVDA on the right"), + } + + def getPossibleStates(self) -> List[SoundSplitState]: + result = [SoundSplitState.OFF] + if 'LEFT' in self.name: + result.append(SoundSplitState.NVDA_LEFT) + if 'RIGHT' in self.name: + result.append(SoundSplitState.NVDA_RIGHT) + return result + + def getClosestState(self, state: SoundSplitState) -> SoundSplitState: + states = self.getPossibleStates() + if state in states: + return state + return states[-1] + + +sessionManager: audiopolicy.IAudioSessionManager2 = None +activeCallback: Optional[comtypes.COMObject] = None + + +def initialize() -> None: + global sessionManager + sessionManager = getSessionManager() + if sessionManager is None: + log.error("Could not initialize audio session manager! ") + return + if nvwave.usingWasapiWavePlayer(): + state = SoundSplitState(config.conf['audio']['soundSplitState']) + global activeCallback + activeCallback = setSoundSplitState(state) + + +@atexit.register +def terminate(): + if nvwave.usingWasapiWavePlayer(): + setSoundSplitState(SoundSplitState.OFF) + if activeCallback is not None: + unregisterCallback(activeCallback) + + +def getDefaultAudioDevice(kind: EDataFlow = EDataFlow.eRender) -> Optional[mmdeviceapi.IMMDevice]: + deviceEnumerator = comtypes.CoCreateInstance( + CLSID_MMDeviceEnumerator, + mmdeviceapi.IMMDeviceEnumerator, + comtypes.CLSCTX_INPROC_SERVER, + ) + device = deviceEnumerator.GetDefaultAudioEndpoint( + kind.value, + ERole.eMultimedia.value, + ) + return device + + +def getSessionManager() -> audiopolicy.IAudioSessionManager2: + audioDevice = getDefaultAudioDevice() + if audioDevice is None: + raise RuntimeError("No default output audio device found!") + tmp = audioDevice.Activate(audiopolicy.IAudioSessionManager2._iid_, comtypes.CLSCTX_ALL, None) + sessionManager: audiopolicy.IAudioSessionManager2 = tmp.QueryInterface(audiopolicy.IAudioSessionManager2) + return sessionManager + + +def applyToAllAudioSessions( + func: Callable[[audiopolicy.IAudioSessionControl2], NoReturn], + applyToFuture: bool = True, +) -> Optional[comtypes.COMObject]: + sessionEnumerator: audiopolicy.IAudioSessionEnumerator = sessionManager.GetSessionEnumerator() + for i in range(sessionEnumerator.GetCount()): + session: audiopolicy.IAudioSessionControl = sessionEnumerator.GetSession(i) + session2: audiopolicy.IAudioSessionControl2 = session.QueryInterface(audiopolicy.IAudioSessionControl2) + func(session2) + if applyToFuture: + class AudioSessionNotification(comtypes.COMObject): + _com_interfaces_ = (audiopolicy.IAudioSessionNotification,) + + def OnSessionCreated(self, session: audiopolicy.IAudioSessionControl): + session2 = session.QueryInterface(audiopolicy.IAudioSessionControl2) + func(session2) + callback = AudioSessionNotification() + sessionManager.RegisterSessionNotification(callback) + return callback + else: + return None + + +def unregisterCallback(callback: comtypes.COMObject) -> None: + sessionManager .UnregisterSessionNotification(callback) + + +def setSoundSplitState(state: SoundSplitState) -> None: + global activeCallback + if activeCallback is not None: + unregisterCallback(activeCallback) + activeCallback = None + leftVolume, rightVolume = state.getAppVolume() + + def volumeSetter(session2: audiopolicy.IAudioSessionControl2) -> None: + channelVolume: audioclient.IChannelAudioVolume = session2.QueryInterface(audioclient.IChannelAudioVolume) + channelCount = channelVolume.GetChannelCount() + if channelCount != 2: + pid = session2.GetProcessId() + log.warning(f"Audio session for pid {pid} has {channelCount} channels instead of 2 - cannot set volume!") + return + pid: int = session2.GetProcessId() + if pid != globalVars.appPid: + channelVolume.SetChannelVolume(0, leftVolume, None) + channelVolume.SetChannelVolume(1, rightVolume, None) + else: + channelVolume.SetChannelVolume(1, leftVolume, None) + channelVolume.SetChannelVolume(0, rightVolume, None) + + activeCallback = applyToAllAudioSessions(volumeSetter) + + +def toggleSoundSplitState() -> None: + if not nvwave.usingWasapiWavePlayer(): + message = _( + # Translators: error message when wasapi is turned off. + "Sound split is only available in wasapi mode. " + "Please enable wasapi on the Advanced panel in NVDA Settings." + ) + ui.message(message) + return + toggleMode = SoundSplitToggleMode(config.conf['audio']['soundSplitToggleMode']) + state = SoundSplitState(config.conf['audio']['soundSplitState']) + allowedStates = toggleMode.getPossibleStates() + try: + i = allowedStates.index(state) + except ValueError: + i = -1 + i = (i + 1) % len(allowedStates) + newState = allowedStates[i] + setSoundSplitState(newState) + config.conf['audio']['soundSplitState'] = newState.value + ui.message(newState.displayString) diff --git a/source/comInterfaces/coreAudio.bak/__init__.py b/source/comInterfaces/coreAudio.bak/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/source/comInterfaces/coreAudio.bak/audioclient/__init__.py b/source/comInterfaces/coreAudio.bak/audioclient/__init__.py new file mode 100644 index 00000000000..5c13f49e4c7 --- /dev/null +++ b/source/comInterfaces/coreAudio.bak/audioclient/__init__.py @@ -0,0 +1,155 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from ctypes import HRESULT, POINTER, c_float +from ctypes import c_longlong as REFERENCE_TIME +from ctypes import c_uint32 as UINT32 +from ctypes.wintypes import BOOL, DWORD, HANDLE + +from comtypes import COMMETHOD, GUID, IUnknown + +from .depend import WAVEFORMATEX + + +class ISimpleAudioVolume(IUnknown): + _iid_ = GUID("{87CE5498-68D6-44E5-9215-6DA47EF883D8}") + _methods_ = ( + # HRESULT SetMasterVolume( + # [in] float fLevel, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "SetMasterVolume", + (["in"], c_float, "fLevel"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT GetMasterVolume([out] float *pfLevel); + COMMETHOD( + [], HRESULT, "GetMasterVolume", (["out"], POINTER(c_float), "pfLevel") + ), + # HRESULT SetMute( + # [in] BOOL bMute, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "SetMute", + (["in"], BOOL, "bMute"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT GetMute([out] BOOL *pbMute); + COMMETHOD([], HRESULT, "GetMute", (["out"], POINTER(BOOL), "pbMute")), + ) + + +class IAudioClient(IUnknown): + _iid_ = GUID("{1cb9ad4c-dbfa-4c32-b178-c2f568a703b2}") + _methods_ = ( + # HRESULT Initialize( + # [in] AUDCLNT_SHAREMODE ShareMode, + # [in] DWORD StreamFlags, + # [in] REFERENCE_TIME hnsBufferDuration, + # [in] REFERENCE_TIME hnsPeriodicity, + # [in] const WAVEFORMATEX *pFormat, + # [in] LPCGUID AudioSessionGuid); + COMMETHOD( + [], + HRESULT, + "Initialize", + (["in"], DWORD, "ShareMode"), + (["in"], DWORD, "StreamFlags"), + (["in"], REFERENCE_TIME, "hnsBufferDuration"), + (["in"], REFERENCE_TIME, "hnsPeriodicity"), + (["in"], POINTER(WAVEFORMATEX), "pFormat"), + (["in"], POINTER(GUID), "AudioSessionGuid"), + ), + # HRESULT GetBufferSize( + # [out] UINT32 *pNumBufferFrames); + COMMETHOD( + [], HRESULT, "GetBufferSize", (["out"], POINTER(UINT32), "pNumBufferFrames") + ), + # HRESULT GetStreamLatency( + # [out] REFERENCE_TIME *phnsLatency); + COMMETHOD( + [], + HRESULT, + "GetStreamLatency", + (["out"], POINTER(REFERENCE_TIME), "phnsLatency"), + ), + # HRESULT GetCurrentPadding( + # [out] UINT32 *pNumPaddingFrames); + COMMETHOD( + [], + HRESULT, + "GetCurrentPadding", + (["out"], POINTER(UINT32), "pNumPaddingFrames"), + ), + # HRESULT IsFormatSupported( + # [in] AUDCLNT_SHAREMODE ShareMode, + # [in] const WAVEFORMATEX *pFormat, + # [out,unique] WAVEFORMATEX **ppClosestMatch); + COMMETHOD( + [], + HRESULT, + "IsFormatSupported", + (["in"], DWORD, "ShareMode"), + (["in"], POINTER(WAVEFORMATEX), "pFormat"), + (["out"], POINTER(POINTER(WAVEFORMATEX)), "ppClosestMatch"), + ), + # HRESULT GetMixFormat( + # [out] WAVEFORMATEX **ppDeviceFormat + # ); + COMMETHOD( + [], + HRESULT, + "GetMixFormat", + (["out"], POINTER(POINTER(WAVEFORMATEX)), "ppDeviceFormat"), + ), + # HRESULT GetDevicePeriod( + # [out] REFERENCE_TIME *phnsDefaultDevicePeriod, + # [out] REFERENCE_TIME *phnsMinimumDevicePeriod); + COMMETHOD( + [], + HRESULT, + "GetDevicePeriod", + (["out"], POINTER(REFERENCE_TIME), "phnsDefaultDevicePeriod"), + (["out"], POINTER(REFERENCE_TIME), "phnsMinimumDevicePeriod"), + ), + # HRESULT Start(void); + COMMETHOD([], HRESULT, "Start"), + # HRESULT Stop(void); + COMMETHOD([], HRESULT, "Stop"), + # HRESULT Reset(void); + COMMETHOD([], HRESULT, "Reset"), + # HRESULT SetEventHandle([in] HANDLE eventHandle); + COMMETHOD( + [], + HRESULT, + "SetEventHandle", + (["in"], HANDLE, "eventHandle"), + ), + # HRESULT GetService( + # [in] REFIID riid, + # [out] void **ppv); + COMMETHOD( + [], + HRESULT, + "GetService", + (["in"], POINTER(GUID), "iid"), + (["out"], POINTER(POINTER(IUnknown)), "ppv"), + ), + ) + + +class IChannelAudioVolume (IUnknown): + _iid_ = GUID('{1c158861-b533-4b30-b1cf-e853e51c59b8}') + _methods_ = ( + COMMETHOD([], HRESULT, 'GetChannelCount', + (['out'], POINTER(UINT), 'pnChannelCount')), + COMMETHOD([], HRESULT, 'SetChannelVolume', + (['in'], UINT, 'dwIndex'), + (['in'], c_float, 'fLevel'), + (['in'], POINTER(GUID), 'EventContext')), + ) diff --git a/source/comInterfaces/coreAudio.bak/audioclient/depend.py b/source/comInterfaces/coreAudio.bak/audioclient/depend.py new file mode 100644 index 00000000000..666ff3f961f --- /dev/null +++ b/source/comInterfaces/coreAudio.bak/audioclient/depend.py @@ -0,0 +1,18 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from ctypes import Structure +from ctypes.wintypes import WORD + + +class WAVEFORMATEX(Structure): + _fields_ = [ + ("wFormatTag", WORD), + ("nChannels", WORD), + ("nSamplesPerSec", WORD), + ("nAvgBytesPerSec", WORD), + ("nBlockAlign", WORD), + ("wBitsPerSample", WORD), + ("cbSize", WORD), + ] diff --git a/source/comInterfaces/coreAudio.bak/audiopolicy/__init__.py b/source/comInterfaces/coreAudio.bak/audiopolicy/__init__.py new file mode 100644 index 00000000000..ac0a1a26a91 --- /dev/null +++ b/source/comInterfaces/coreAudio.bak/audiopolicy/__init__.py @@ -0,0 +1,299 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from ctypes import HRESULT, POINTER, c_float, c_uint32 +from ctypes.wintypes import BOOL, DWORD, INT, LPCWSTR, LPWSTR + +from comtypes import COMMETHOD, GUID, IUnknown + +from ..audioclient import ISimpleAudioVolume + + +class IAudioSessionEvents(IUnknown): + _iid_ = GUID("{073d618c-490a-4f9f-9d18-7bec6fc21121}") + _methods_ = ( + # HRESULT OnDisplayNameChanged( + # [in] LPCWSTR NewDisplayName, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "OnDisplayNameChanged", + (["in"], LPCWSTR, "NewDisplayName"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT OnIconPathChanged( + # [in] LPCWSTR NewIconPath, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "OnIconPathChanged", + (["in"], LPCWSTR, "NewIconPath"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT OnSimpleVolumeChanged( + # [in] float NewVolume, + # [in] BOOL NewMute, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "OnSimpleVolumeChanged", + (["in"], c_float, "NewVolume"), + (["in"], BOOL, "NewMute"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT OnChannelVolumeChanged( + # [in] DWORD ChannelCount, + # [in] float [] NewChannelVolumeArray, + # [in] DWORD ChangedChannel, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "OnChannelVolumeChanged", + (["in"], DWORD, "ChannelCount"), + (["in"], (c_float * 8), "NewChannelVolumeArray"), + (["in"], DWORD, "ChangedChannel"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT OnGroupingParamChanged( + # [in] LPCGUID NewGroupingParam, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "OnGroupingParamChanged", + (["in"], POINTER(GUID), "NewGroupingParam"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT OnStateChanged( + # AudioSessionState NewState); + COMMETHOD([], HRESULT, "OnStateChanged", (["in"], DWORD, "NewState")), + # HRESULT OnSessionDisconnected( + # [in] AudioSessionDisconnectReason DisconnectReason); + COMMETHOD( + [], HRESULT, "OnSessionDisconnected", (["in"], DWORD, "DisconnectReason") + ), + ) + + +class IAudioSessionControl(IUnknown): + _iid_ = GUID("{F4B1A599-7266-4319-A8CA-E70ACB11E8CD}") + _methods_ = ( + # HRESULT GetState ([out] AudioSessionState *pRetVal); + COMMETHOD([], HRESULT, "GetState", (["out"], POINTER(DWORD), "pRetVal")), + # HRESULT GetDisplayName([out] LPWSTR *pRetVal); + COMMETHOD([], HRESULT, "GetDisplayName", (["out"], POINTER(LPWSTR), "pRetVal")), + # HRESULT SetDisplayName( + # [in] LPCWSTR Value, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "SetDisplayName", + (["in"], LPCWSTR, "Value"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT GetIconPath([out] LPWSTR *pRetVal); + COMMETHOD([], HRESULT, "GetIconPath", (["out"], POINTER(LPWSTR), "pRetVal")), + # HRESULT SetIconPath( + # [in] LPCWSTR Value, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "SetIconPath", + (["in"], LPCWSTR, "Value"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT GetGroupingParam([out] GUID *pRetVal); + COMMETHOD([], HRESULT, "GetGroupingParam", (["out"], POINTER(GUID), "pRetVal")), + # HRESULT SetGroupingParam( + # [in] LPCGUID Grouping, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "SetGroupingParam", + (["in"], POINTER(GUID), "Grouping"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT RegisterAudioSessionNotification( + # [in] IAudioSessionEvents *NewNotifications); + COMMETHOD( + [], + HRESULT, + "RegisterAudioSessionNotification", + (["in"], POINTER(IAudioSessionEvents), "NewNotifications"), + ), + # HRESULT UnregisterAudioSessionNotification( + # [in] IAudioSessionEvents *NewNotifications); + COMMETHOD( + [], + HRESULT, + "UnregisterAudioSessionNotification", + (["in"], POINTER(IAudioSessionEvents), "NewNotifications"), + ), + ) + + +class IAudioSessionControl2(IAudioSessionControl): + _iid_ = GUID("{BFB7FF88-7239-4FC9-8FA2-07C950BE9C6D}") + _methods_ = ( + # HRESULT GetSessionIdentifier([out] LPWSTR *pRetVal); + COMMETHOD( + [], HRESULT, "GetSessionIdentifier", (["out"], POINTER(LPWSTR), "pRetVal") + ), + # HRESULT GetSessionInstanceIdentifier([out] LPWSTR *pRetVal); + COMMETHOD( + [], + HRESULT, + "GetSessionInstanceIdentifier", + (["out"], POINTER(LPWSTR), "pRetVal"), + ), + # HRESULT GetProcessId([out] DWORD *pRetVal); + COMMETHOD([], HRESULT, "GetProcessId", (["out"], POINTER(DWORD), "pRetVal")), + # HRESULT IsSystemSoundsSession(); + COMMETHOD([], HRESULT, "IsSystemSoundsSession"), + # HRESULT SetDuckingPreference([in] BOOL optOut); + COMMETHOD([], HRESULT, "SetDuckingPreferences", (["in"], BOOL, "optOut")), + ) + + +class IAudioSessionEnumerator(IUnknown): + _iid_ = GUID("{E2F5BB11-0570-40CA-ACDD-3AA01277DEE8}") + _methods_ = ( + # HRESULT GetCount([out] int *SessionCount); + COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(INT), "SessionCount")), + # HRESULT GetSession( + # [in] int SessionCount, + # [out] IAudioSessionControl **Session); + COMMETHOD( + [], + HRESULT, + "GetSession", + (["in"], INT, "SessionCount"), + (["out"], POINTER(POINTER(IAudioSessionControl)), "Session"), + ), + ) + + +class IAudioSessionManager(IUnknown): + _iid_ = GUID("{BFA971F1-4d5e-40bb-935e-967039bfbee4}") + _methods_ = ( + # HRESULT GetAudioSessionControl( + # [in] LPCGUID AudioSessionGuid, + # [in] DWORD StreamFlags, + # [out] IAudioSessionControl **SessionControl); + COMMETHOD( + [], + HRESULT, + "GetAudioSessionControl", + (["in"], POINTER(GUID), "AudioSessionGuid"), + (["in"], DWORD, "StreamFlags"), + (["out"], POINTER(POINTER(IAudioSessionControl)), "SessionControl"), + ), + # HRESULT GetSimpleAudioVolume( + # [in] LPCGUID AudioSessionGuid, + # [in] DWORD CrossProcessSession, + # [out] ISimpleAudioVolume **AudioVolume); + COMMETHOD( + [], + HRESULT, + "GetSimpleAudioVolume", + (["in"], POINTER(GUID), "AudioSessionGuid"), + (["in"], DWORD, "CrossProcessSession"), + (["out"], POINTER(POINTER(ISimpleAudioVolume)), "AudioVolume"), + ), + ) + + +class IAudioSessionNotification(IUnknown): + _iid_ = GUID("{8aad9bb7-39e1-4c62-a3ab-ff6e76dcf9c8}") + _methods_ = ( + # HRESULT OnSessionCreated( + # ['in'] IAudioSessionControl *NewSession + # ); + COMMETHOD( + [], + HRESULT, + "OnSessionCreated", + (["in"], POINTER(IAudioSessionControl), "NewSession"), + ), + ) + + +class IAudioVolumeDuckNotification(IUnknown): + _iid_ = GUID("{C3B284D4-6D39-4359-B3CF-B56DDB3BB39C}") + _methods_ = ( + # HRESULT OnVolumeDuckNotification( + # [in] LPCWSTR sessionID, + # [in] UINT32 countCommunicationSessions); + COMMETHOD( + [], + HRESULT, + "OnVolumeDuckNotification", + (["in"], LPCWSTR, "sessionID"), + (["in"], c_uint32, "countCommunicationSessions"), + ), + # HRESULT OnVolumeUnduckNotification( + # [in] LPCWSTR sessionID); + COMMETHOD( + [], + HRESULT, + "OnVolumeUnduckNotification", + (["in"], LPCWSTR, "sessionID"), + ), + ) + + +class IAudioSessionManager2(IAudioSessionManager): + _iid_ = GUID("{77aa99a0-1bd6-484f-8bc7-2c654c9a9b6f}") + _methods_ = ( + # HRESULT GetSessionEnumerator( + # [out] IAudioSessionEnumerator **SessionList); + COMMETHOD( + [], + HRESULT, + "GetSessionEnumerator", + (["out"], POINTER(POINTER(IAudioSessionEnumerator)), "SessionList"), + ), + # HRESULT RegisterSessionNotification( + # IAudioSessionNotification *SessionNotification); + COMMETHOD( + [], + HRESULT, + "RegisterSessionNotification", + (["in"], POINTER(IAudioSessionNotification), "SessionNotification"), + ), + # HRESULT UnregisterSessionNotification( + # IAudioSessionNotification *SessionNotification); + COMMETHOD( + [], + HRESULT, + "UnregisterSessionNotification", + (["in"], POINTER(IAudioSessionNotification), "SessionNotification"), + ), + # HRESULT RegisterDuckNotification( + # LPCWSTR SessionID, + # IAudioVolumeDuckNotification *duckNotification); + COMMETHOD( + [], + HRESULT, + "RegisterDuckNotification", + (["in"], LPCWSTR, "SessionID"), + (["in"], POINTER(IAudioVolumeDuckNotification), "duckNotification"), + ), + # HRESULT UnregisterDuckNotification( + # IAudioVolumeDuckNotification *duckNotification); + COMMETHOD( + [], + HRESULT, + "UnregisterDuckNotification", + (["in"], POINTER(IAudioVolumeDuckNotification), "duckNotification"), + ), + ) + diff --git a/source/comInterfaces/coreAudio.bak/constants.py b/source/comInterfaces/coreAudio.bak/constants.py new file mode 100644 index 00000000000..9ea4fb91f58 --- /dev/null +++ b/source/comInterfaces/coreAudio.bak/constants.py @@ -0,0 +1,56 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from enum import Enum, IntEnum + +from comtypes import GUID + +IID_Empty = GUID("{00000000-0000-0000-0000-000000000000}") + +CLSID_MMDeviceEnumerator = GUID("{BCDE0395-E52F-467C-8E3D-C4579291692E}") + + +class ERole(Enum): + eConsole = 0 + eMultimedia = 1 + eCommunications = 2 + ERole_enum_count = 3 + + +class EDataFlow(Enum): + eRender = 0 + eCapture = 1 + eAll = 2 + EDataFlow_enum_count = 3 + + +class DEVICE_STATE(Enum): + ACTIVE = 0x00000001 + DISABLED = 0x00000002 + NOTPRESENT = 0x00000004 + UNPLUGGED = 0x00000008 + MASK_ALL = 0x0000000F + + +class AudioDeviceState(Enum): + Active = 0x1 + Disabled = 0x2 + NotPresent = 0x4 + Unplugged = 0x8 + + +class STGM(Enum): + STGM_READ = 0x00000000 + + +class AUDCLNT_SHAREMODE(Enum): + AUDCLNT_SHAREMODE_SHARED = 0x00000001 + AUDCLNT_SHAREMODE_EXCLUSIVE = 0x00000002 + + +class AudioSessionState(IntEnum): + # IntEnum to make instances comparable. + Inactive = 0 + Active = 1 + Expired = 2 diff --git a/source/comInterfaces/coreAudio.bak/endpointvolume/__init__.py b/source/comInterfaces/coreAudio.bak/endpointvolume/__init__.py new file mode 100644 index 00000000000..c8d9cc190d0 --- /dev/null +++ b/source/comInterfaces/coreAudio.bak/endpointvolume/__init__.py @@ -0,0 +1,182 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from ctypes import HRESULT, POINTER, c_float +from ctypes.wintypes import BOOL, DWORD, UINT + +from comtypes import COMMETHOD, GUID, IUnknown + +from .depend import PAUDIO_VOLUME_NOTIFICATION_DATA + + +class IAudioEndpointVolumeCallback(IUnknown): + _iid_ = GUID("{b1136c83-b6b5-4add-98a5-a2df8eedf6fa}") + _methods_ = ( + # HRESULT OnNotify( + # [in] PAUDIO_VOLUME_NOTIFICATION_DATA pNotify); + COMMETHOD( + [], + HRESULT, + "OnNotify", + (["in"], PAUDIO_VOLUME_NOTIFICATION_DATA, "pNotify"), + ), + ) + + +class IAudioEndpointVolume(IUnknown): + _iid_ = GUID("{5CDF2C82-841E-4546-9722-0CF74078229A}") + _methods_ = ( + # HRESULT RegisterControlChangeNotify( + # [in] IAudioEndpointVolumeCallback *pNotify); + COMMETHOD( + [], + HRESULT, + "RegisterControlChangeNotify", + (["in"], POINTER(IAudioEndpointVolumeCallback), "pNotify"), + ), + # HRESULT UnregisterControlChangeNotify( + # [in] IAudioEndpointVolumeCallback *pNotify); + COMMETHOD( + [], + HRESULT, + "UnregisterControlChangeNotify", + (["in"], POINTER(IAudioEndpointVolumeCallback), "pNotify"), + ), + # HRESULT GetChannelCount([out] UINT *pnChannelCount); + COMMETHOD( + [], HRESULT, "GetChannelCount", (["out"], POINTER(UINT), "pnChannelCount") + ), + # HRESULT SetMasterVolumeLevel( + # [in] float fLevelDB, [in] LPCGUID pguidEventContext); + COMMETHOD( + [], + HRESULT, + "SetMasterVolumeLevel", + (["in"], c_float, "fLevelDB"), + (["in"], POINTER(GUID), "pguidEventContext"), + ), + # HRESULT SetMasterVolumeLevelScalar( + # [in] float fLevel, [in] LPCGUID pguidEventContext); + COMMETHOD( + [], + HRESULT, + "SetMasterVolumeLevelScalar", + (["in"], c_float, "fLevel"), + (["in"], POINTER(GUID), "pguidEventContext"), + ), + # HRESULT GetMasterVolumeLevel([out] float *pfLevelDB); + COMMETHOD( + [], + HRESULT, + "GetMasterVolumeLevel", + (["out"], POINTER(c_float), "pfLevelDB"), + ), + # HRESULT GetMasterVolumeLevelScalar([out] float *pfLevel); + COMMETHOD( + [], + HRESULT, + "GetMasterVolumeLevelScalar", + (["out"], POINTER(c_float), "pfLevelDB"), + ), + # HRESULT SetChannelVolumeLevel( + # [in] UINT nChannel, + # [in] float fLevelDB, + # [in] LPCGUID pguidEventContext); + COMMETHOD( + [], + HRESULT, + "SetChannelVolumeLevel", + (["in"], UINT, "nChannel"), + (["in"], c_float, "fLevelDB"), + (["in"], POINTER(GUID), "pguidEventContext"), + ), + # HRESULT SetChannelVolumeLevelScalar( + # [in] UINT nChannel, + # [in] float fLevel, + # [in] LPCGUID pguidEventContext); + COMMETHOD( + [], + HRESULT, + "SetChannelVolumeLevelScalar", + (["in"], DWORD, "nChannel"), + (["in"], c_float, "fLevelDB"), + (["in"], POINTER(GUID), "pguidEventContext"), + ), + # HRESULT GetChannelVolumeLevel( + # [in] UINT nChannel, + # [out] float *pfLevelDB); + COMMETHOD( + [], + HRESULT, + "GetChannelVolumeLevel", + (["in"], UINT, "nChannel"), + (["out"], POINTER(c_float), "pfLevelDB"), + ), + # HRESULT GetChannelVolumeLevelScalar( + # [in] UINT nChannel, + # [out] float *pfLevel); + COMMETHOD( + [], + HRESULT, + "GetChannelVolumeLevelScalar", + (["in"], DWORD, "nChannel"), + (["out"], POINTER(c_float), "pfLevelDB"), + ), + # HRESULT SetMute([in] BOOL bMute, [in] LPCGUID pguidEventContext); + COMMETHOD( + [], + HRESULT, + "SetMute", + (["in"], BOOL, "bMute"), + (["in"], POINTER(GUID), "pguidEventContext"), + ), + # HRESULT GetMute([out] BOOL *pbMute); + COMMETHOD([], HRESULT, "GetMute", (["out"], POINTER(BOOL), "pbMute")), + # HRESULT GetVolumeStepInfo( + # [out] UINT *pnStep, + # [out] UINT *pnStepCount); + COMMETHOD( + [], + HRESULT, + "GetVolumeStepInfo", + (["out"], POINTER(DWORD), "pnStep"), + (["out"], POINTER(DWORD), "pnStepCount"), + ), + # HRESULT VolumeStepUp([in] LPCGUID pguidEventContext); + COMMETHOD( + [], HRESULT, "VolumeStepUp", (["in"], POINTER(GUID), "pguidEventContext") + ), + # HRESULT VolumeStepDown([in] LPCGUID pguidEventContext); + COMMETHOD( + [], HRESULT, "VolumeStepDown", (["in"], POINTER(GUID), "pguidEventContext") + ), + # HRESULT QueryHardwareSupport([out] DWORD *pdwHardwareSupportMask); + COMMETHOD( + [], + HRESULT, + "QueryHardwareSupport", + (["out"], POINTER(DWORD), "pdwHardwareSupportMask"), + ), + # HRESULT GetVolumeRange( + # [out] float *pfLevelMinDB, + # [out] float *pfLevelMaxDB, + # [out] float *pfVolumeIncrementDB); + COMMETHOD( + [], + HRESULT, + "GetVolumeRange", + (["out"], POINTER(c_float), "pfMin"), + (["out"], POINTER(c_float), "pfMax"), + (["out"], POINTER(c_float), "pfIncr"), + ), + ) + + +class IAudioMeterInformation(IUnknown): + _iid_ = GUID("{C02216F6-8C67-4B5B-9D00-D008E73E0064}") + _methods_ = ( + # HRESULT GetPeakValue([out] c_float *pfPeak); + COMMETHOD([], HRESULT, "GetPeakValue", (["out"], POINTER(c_float), "pfPeak")), + ) + diff --git a/source/comInterfaces/coreAudio.bak/endpointvolume/depend.py b/source/comInterfaces/coreAudio.bak/endpointvolume/depend.py new file mode 100644 index 00000000000..be6bada134d --- /dev/null +++ b/source/comInterfaces/coreAudio.bak/endpointvolume/depend.py @@ -0,0 +1,22 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from ctypes import POINTER, Structure, c_float +from ctypes.wintypes import BOOL, UINT + +from comtypes import GUID + + +class AUDIO_VOLUME_NOTIFICATION_DATA(Structure): + _fields_ = [ + ("guidEventContext", GUID), + ("bMuted", BOOL), + ("fMasterVolume", c_float), + ("nChannels", UINT), + ("afChannelVolumes", c_float * 8), + ] + + +PAUDIO_VOLUME_NOTIFICATION_DATA = POINTER(AUDIO_VOLUME_NOTIFICATION_DATA) + diff --git a/source/comInterfaces/coreAudio.bak/mmdeviceapi/__init__.py b/source/comInterfaces/coreAudio.bak/mmdeviceapi/__init__.py new file mode 100644 index 00000000000..cb4d120fbf5 --- /dev/null +++ b/source/comInterfaces/coreAudio.bak/mmdeviceapi/__init__.py @@ -0,0 +1,186 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from ctypes import HRESULT, POINTER +from ctypes.wintypes import DWORD, LPCWSTR, LPWSTR, UINT + +from comtypes import COMMETHOD, GUID, IUnknown + +from .depend import PROPERTYKEY, IPropertyStore + + +class IMMDevice(IUnknown): + _iid_ = GUID("{D666063F-1587-4E43-81F1-B948E807363F}") + _methods_ = ( + # HRESULT Activate( + # [in] REFIID iid, + # [in] DWORD dwClsCtx, + # [in] PROPVARIANT *pActivationParams, + # [out] void **ppInterface); + COMMETHOD( + [], + HRESULT, + "Activate", + (["in"], POINTER(GUID), "iid"), + (["in"], DWORD, "dwClsCtx"), + (["in"], POINTER(DWORD), "pActivationParams"), + (["out"], POINTER(POINTER(IUnknown)), "ppInterface"), + ), + # HRESULT OpenPropertyStore( + # [in] DWORD stgmAccess, + # [out] IPropertyStore **ppProperties); + COMMETHOD( + [], + HRESULT, + "OpenPropertyStore", + (["in"], DWORD, "stgmAccess"), + (["out"], POINTER(POINTER(IPropertyStore)), "ppProperties"), + ), + # HRESULT GetId([out] LPWSTR *ppstrId); + COMMETHOD([], HRESULT, "GetId", (["out"], POINTER(LPWSTR), "ppstrId")), + # HRESULT GetState([out] DWORD *pdwState); + COMMETHOD([], HRESULT, "GetState", (["out"], POINTER(DWORD), "pdwState")), + ) + + +class IMMDeviceCollection(IUnknown): + _iid_ = GUID("{0BD7A1BE-7A1A-44DB-8397-CC5392387B5E}") + _methods_ = ( + # HRESULT GetCount([out] UINT *pcDevices); + COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(UINT), "pcDevices")), + # HRESULT Item([in] UINT nDevice, [out] IMMDevice **ppDevice); + COMMETHOD( + [], + HRESULT, + "Item", + (["in"], UINT, "nDevice"), + (["out"], POINTER(POINTER(IMMDevice)), "ppDevice"), + ), + ) + + +class IMMNotificationClient(IUnknown): + _case_insensitive_ = True + _iid_ = GUID("{7991EEC9-7E89-4D85-8390-6C703CEC60C0}") + _methods_ = ( + # HRESULT OnDeviceStateChanged( + # [in] LPCWSTR pwstrDeviceId, + # [in] DWORD dwNewState); + COMMETHOD( + [], + HRESULT, + "OnDeviceStateChanged", + (["in"], LPCWSTR, "pwstrDeviceId"), + (["in"], DWORD, "dwNewState"), + ), + # HRESULT OnDeviceAdded( + # [in] LPCWSTR pwstrDeviceId, + COMMETHOD( + [], + HRESULT, + "OnDeviceAdded", + (["in"], LPCWSTR, "pwstrDeviceId"), + ), + # HRESULT OnDeviceRemoved( + # [in] LPCWSTR pwstrDeviceId, + COMMETHOD( + [], + HRESULT, + "OnDeviceRemoved", + (["in"], LPCWSTR, "pwstrDeviceId"), + ), + # HRESULT OnDefaultDeviceChanged( + # [in] EDataFlow flow, + # [in] ERole role, + # [in] LPCWSTR pwstrDefaultDeviceId; + COMMETHOD( + [], + HRESULT, + "OnDefaultDeviceChanged", + (["in"], DWORD, "flow"), + (["in"], DWORD, "role"), + (["in"], LPCWSTR, "pwstrDefaultDeviceId"), + ), + # HRESULT OnPropertyValueChanged( + # [in] LPCWSTR pwstrDeviceId, + # [in] const PROPERTYKEY key); + COMMETHOD( + [], + HRESULT, + "OnPropertyValueChanged", + (["in"], LPCWSTR, "pwstrDeviceId"), + (["in"], PROPERTYKEY, "key"), + ), + ) + + +class IMMDeviceEnumerator(IUnknown): + _iid_ = GUID("{A95664D2-9614-4F35-A746-DE8DB63617E6}") + _methods_ = ( + # HRESULT EnumAudioEndpoints( + # [in] EDataFlow dataFlow, + # [in] DWORD dwStateMask, + # [out] IMMDeviceCollection **ppDevices); + COMMETHOD( + [], + HRESULT, + "EnumAudioEndpoints", + (["in"], DWORD, "dataFlow"), + (["in"], DWORD, "dwStateMask"), + (["out"], POINTER(POINTER(IMMDeviceCollection)), "ppDevices"), + ), + # HRESULT GetDefaultAudioEndpoint( + # [in] EDataFlow dataFlow, + # [in] ERole role, + # [out] IMMDevice **ppDevice); + COMMETHOD( + [], + HRESULT, + "GetDefaultAudioEndpoint", + (["in"], DWORD, "dataFlow"), + (["in"], DWORD, "role"), + (["out"], POINTER(POINTER(IMMDevice)), "ppDevices"), + ), + # HRESULT GetDevice( + # [in] LPCWSTR pwstrId, + # [out] IMMDevice **ppDevice); + COMMETHOD( + [], + HRESULT, + "GetDevice", + (["in"], LPCWSTR, "pwstrId"), + (["out"], POINTER(POINTER(IMMDevice)), "ppDevice"), + ), + # HRESULT RegisterEndpointNotificationCallback( + # [in] IMMNotificationClient *pClient); + COMMETHOD( + [], + HRESULT, + "RegisterEndpointNotificationCallback", + (["in"], POINTER(IMMNotificationClient), "pClient"), + ), + # HRESULT UnregisterEndpointNotificationCallback( + # [in] IMMNotificationClient *pClient); + COMMETHOD( + [], + HRESULT, + "UnregisterEndpointNotificationCallback", + (["in"], POINTER(IMMNotificationClient), "pClient"), + ), + ) + + +class IMMEndpoint(IUnknown): + _iid_ = GUID("{1BE09788-6894-4089-8586-9A2A6C265AC5}") + _methods_ = ( + # HRESULT GetDataFlow( + # [out] EDataFlow *pDataFlow); + COMMETHOD( + [], + HRESULT, + "GetDataFlow", + (["out"], POINTER(DWORD), "pDataFlow"), + ), + ) + diff --git a/source/comInterfaces/coreAudio.bak/mmdeviceapi/depend/__init__.py b/source/comInterfaces/coreAudio.bak/mmdeviceapi/depend/__init__.py new file mode 100644 index 00000000000..cb80b879332 --- /dev/null +++ b/source/comInterfaces/coreAudio.bak/mmdeviceapi/depend/__init__.py @@ -0,0 +1,52 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from ctypes import HRESULT, POINTER +from ctypes.wintypes import DWORD + +from comtypes import COMMETHOD, GUID, IUnknown + +from .structures import PROPERTYKEY, PROPVARIANT + + +class IPropertyStore(IUnknown): + _iid_ = GUID("{886d8eeb-8cf2-4446-8d02-cdba1dbdcf99}") + _methods_ = ( + # HRESULT GetCount([out] DWORD *cProps); + COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(DWORD), "cProps")), + # HRESULT GetAt( + # [in] DWORD iProp, + # [out] PROPERTYKEY *pkey); + COMMETHOD( + [], + HRESULT, + "GetAt", + (["in"], DWORD, "iProp"), + (["out"], POINTER(PROPERTYKEY), "pkey"), + ), + # HRESULT GetValue( + # [in] REFPROPERTYKEY key, + # [out] PROPVARIANT *pv); + COMMETHOD( + [], + HRESULT, + "GetValue", + (["in"], POINTER(PROPERTYKEY), "key"), + (["out"], POINTER(PROPVARIANT), "pv"), + ), + # HRESULT SetValue( + # [in] REFPROPERTYKEY key, + # [in] REFPROPVARIANT propvar + # ); + COMMETHOD( + [], + HRESULT, + "SetValue", + (["in"], POINTER(PROPERTYKEY), "key"), + (["in"], POINTER(PROPVARIANT), "propvar"), + ), + # HRESULT Commit(); + COMMETHOD([], HRESULT, "Commit"), + ) + diff --git a/source/comInterfaces/coreAudio.bak/mmdeviceapi/depend/structures.py b/source/comInterfaces/coreAudio.bak/mmdeviceapi/depend/structures.py new file mode 100644 index 00000000000..d902dc80d08 --- /dev/null +++ b/source/comInterfaces/coreAudio.bak/mmdeviceapi/depend/structures.py @@ -0,0 +1,59 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from ctypes import Structure, Union, byref, windll +from ctypes.wintypes import DWORD, LONG, LPWSTR, ULARGE_INTEGER, VARIANT_BOOL, WORD + +from comtypes import GUID +from comtypes.automation import VARTYPE, VT_BOOL, VT_CLSID, VT_LPWSTR, VT_UI4 + + +class PROPVARIANT_UNION(Union): + _fields_ = [ + ("lVal", LONG), + ("uhVal", ULARGE_INTEGER), + ("boolVal", VARIANT_BOOL), + ("pwszVal", LPWSTR), + ("puuid", GUID), + ] + + +class PROPVARIANT(Structure): + _fields_ = [ + ("vt", VARTYPE), + ("reserved1", WORD), + ("reserved2", WORD), + ("reserved3", WORD), + ("union", PROPVARIANT_UNION), + ] + + def GetValue(self): + vt = self.vt + if vt == VT_BOOL: + return self.union.boolVal != 0 + elif vt == VT_LPWSTR: + # return Marshal.PtrToStringUni(union.pwszVal) + return self.union.pwszVal + elif vt == VT_UI4: + return self.union.lVal + elif vt == VT_CLSID: + # TODO + # return (Guid)Marshal.PtrToStructure(union.puuid, typeof(Guid)) + return + else: + return "%s:?" % (vt) + + def clear(self): + windll.ole32.PropVariantClear(byref(self)) + + +class PROPERTYKEY(Structure): + _fields_ = [ + ("fmtid", GUID), + ("pid", DWORD), + ] + + def __str__(self): + return "%s %s" % (self.fmtid, self.pid) + diff --git a/source/comInterfaces/coreAudio/__init__.py b/source/comInterfaces/coreAudio/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/source/comInterfaces/coreAudio/audioclient/__init__.py b/source/comInterfaces/coreAudio/audioclient/__init__.py new file mode 100644 index 00000000000..f58a7da1e60 --- /dev/null +++ b/source/comInterfaces/coreAudio/audioclient/__init__.py @@ -0,0 +1,155 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from ctypes import HRESULT, POINTER, c_float +from ctypes import c_longlong as REFERENCE_TIME +from ctypes import c_uint32 as UINT32 +from ctypes.wintypes import BOOL, DWORD, HANDLE, UINT + +from comtypes import COMMETHOD, GUID, IUnknown + +from .depend import WAVEFORMATEX + + +class ISimpleAudioVolume(IUnknown): + _iid_ = GUID("{87CE5498-68D6-44E5-9215-6DA47EF883D8}") + _methods_ = ( + # HRESULT SetMasterVolume( + # [in] float fLevel, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "SetMasterVolume", + (["in"], c_float, "fLevel"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT GetMasterVolume([out] float *pfLevel); + COMMETHOD( + [], HRESULT, "GetMasterVolume", (["out"], POINTER(c_float), "pfLevel") + ), + # HRESULT SetMute( + # [in] BOOL bMute, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "SetMute", + (["in"], BOOL, "bMute"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT GetMute([out] BOOL *pbMute); + COMMETHOD([], HRESULT, "GetMute", (["out"], POINTER(BOOL), "pbMute")), + ) + + +class IAudioClient(IUnknown): + _iid_ = GUID("{1cb9ad4c-dbfa-4c32-b178-c2f568a703b2}") + _methods_ = ( + # HRESULT Initialize( + # [in] AUDCLNT_SHAREMODE ShareMode, + # [in] DWORD StreamFlags, + # [in] REFERENCE_TIME hnsBufferDuration, + # [in] REFERENCE_TIME hnsPeriodicity, + # [in] const WAVEFORMATEX *pFormat, + # [in] LPCGUID AudioSessionGuid); + COMMETHOD( + [], + HRESULT, + "Initialize", + (["in"], DWORD, "ShareMode"), + (["in"], DWORD, "StreamFlags"), + (["in"], REFERENCE_TIME, "hnsBufferDuration"), + (["in"], REFERENCE_TIME, "hnsPeriodicity"), + (["in"], POINTER(WAVEFORMATEX), "pFormat"), + (["in"], POINTER(GUID), "AudioSessionGuid"), + ), + # HRESULT GetBufferSize( + # [out] UINT32 *pNumBufferFrames); + COMMETHOD( + [], HRESULT, "GetBufferSize", (["out"], POINTER(UINT32), "pNumBufferFrames") + ), + # HRESULT GetStreamLatency( + # [out] REFERENCE_TIME *phnsLatency); + COMMETHOD( + [], + HRESULT, + "GetStreamLatency", + (["out"], POINTER(REFERENCE_TIME), "phnsLatency"), + ), + # HRESULT GetCurrentPadding( + # [out] UINT32 *pNumPaddingFrames); + COMMETHOD( + [], + HRESULT, + "GetCurrentPadding", + (["out"], POINTER(UINT32), "pNumPaddingFrames"), + ), + # HRESULT IsFormatSupported( + # [in] AUDCLNT_SHAREMODE ShareMode, + # [in] const WAVEFORMATEX *pFormat, + # [out,unique] WAVEFORMATEX **ppClosestMatch); + COMMETHOD( + [], + HRESULT, + "IsFormatSupported", + (["in"], DWORD, "ShareMode"), + (["in"], POINTER(WAVEFORMATEX), "pFormat"), + (["out"], POINTER(POINTER(WAVEFORMATEX)), "ppClosestMatch"), + ), + # HRESULT GetMixFormat( + # [out] WAVEFORMATEX **ppDeviceFormat + # ); + COMMETHOD( + [], + HRESULT, + "GetMixFormat", + (["out"], POINTER(POINTER(WAVEFORMATEX)), "ppDeviceFormat"), + ), + # HRESULT GetDevicePeriod( + # [out] REFERENCE_TIME *phnsDefaultDevicePeriod, + # [out] REFERENCE_TIME *phnsMinimumDevicePeriod); + COMMETHOD( + [], + HRESULT, + "GetDevicePeriod", + (["out"], POINTER(REFERENCE_TIME), "phnsDefaultDevicePeriod"), + (["out"], POINTER(REFERENCE_TIME), "phnsMinimumDevicePeriod"), + ), + # HRESULT Start(void); + COMMETHOD([], HRESULT, "Start"), + # HRESULT Stop(void); + COMMETHOD([], HRESULT, "Stop"), + # HRESULT Reset(void); + COMMETHOD([], HRESULT, "Reset"), + # HRESULT SetEventHandle([in] HANDLE eventHandle); + COMMETHOD( + [], + HRESULT, + "SetEventHandle", + (["in"], HANDLE, "eventHandle"), + ), + # HRESULT GetService( + # [in] REFIID riid, + # [out] void **ppv); + COMMETHOD( + [], + HRESULT, + "GetService", + (["in"], POINTER(GUID), "iid"), + (["out"], POINTER(POINTER(IUnknown)), "ppv"), + ), + ) + + +class IChannelAudioVolume (IUnknown): + _iid_ = GUID('{1c158861-b533-4b30-b1cf-e853e51c59b8}') + _methods_ = ( + COMMETHOD([], HRESULT, 'GetChannelCount', + (['out'], POINTER(UINT), 'pnChannelCount')), + COMMETHOD([], HRESULT, 'SetChannelVolume', + (['in'], UINT, 'dwIndex'), + (['in'], c_float, 'fLevel'), + (['in'], POINTER(GUID), 'EventContext')), + ) diff --git a/source/comInterfaces/coreAudio/audioclient/depend.py b/source/comInterfaces/coreAudio/audioclient/depend.py new file mode 100644 index 00000000000..581b0279e4c --- /dev/null +++ b/source/comInterfaces/coreAudio/audioclient/depend.py @@ -0,0 +1,22 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from ctypes import Structure +from ctypes.wintypes import WORD + + +class WAVEFORMATEX(Structure): + _fields_ = [ + ("wFormatTag", WORD), + ("nChannels", WORD), + ("nSamplesPerSec", WORD), + ("nAvgBytesPerSec", WORD), + ("nBlockAlign", WORD), + ("wBitsPerSample", WORD), + ("cbSize", WORD), + ] + + + + diff --git a/source/comInterfaces/coreAudio/audiopolicy/__init__.py b/source/comInterfaces/coreAudio/audiopolicy/__init__.py new file mode 100644 index 00000000000..46493574219 --- /dev/null +++ b/source/comInterfaces/coreAudio/audiopolicy/__init__.py @@ -0,0 +1,301 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from ctypes import HRESULT, POINTER, c_float, c_uint32 +from ctypes.wintypes import BOOL, DWORD, INT, LPCWSTR, LPWSTR + +from comtypes import COMMETHOD, GUID, IUnknown + +from ..audioclient import ISimpleAudioVolume + + +class IAudioSessionEvents(IUnknown): + _iid_ = GUID("{073d618c-490a-4f9f-9d18-7bec6fc21121}") + _methods_ = ( + # HRESULT OnDisplayNameChanged( + # [in] LPCWSTR NewDisplayName, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "OnDisplayNameChanged", + (["in"], LPCWSTR, "NewDisplayName"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT OnIconPathChanged( + # [in] LPCWSTR NewIconPath, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "OnIconPathChanged", + (["in"], LPCWSTR, "NewIconPath"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT OnSimpleVolumeChanged( + # [in] float NewVolume, + # [in] BOOL NewMute, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "OnSimpleVolumeChanged", + (["in"], c_float, "NewVolume"), + (["in"], BOOL, "NewMute"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT OnChannelVolumeChanged( + # [in] DWORD ChannelCount, + # [in] float [] NewChannelVolumeArray, + # [in] DWORD ChangedChannel, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "OnChannelVolumeChanged", + (["in"], DWORD, "ChannelCount"), + (["in"], (c_float * 8), "NewChannelVolumeArray"), + (["in"], DWORD, "ChangedChannel"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT OnGroupingParamChanged( + # [in] LPCGUID NewGroupingParam, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "OnGroupingParamChanged", + (["in"], POINTER(GUID), "NewGroupingParam"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT OnStateChanged( + # AudioSessionState NewState); + COMMETHOD([], HRESULT, "OnStateChanged", (["in"], DWORD, "NewState")), + # HRESULT OnSessionDisconnected( + # [in] AudioSessionDisconnectReason DisconnectReason); + COMMETHOD( + [], HRESULT, "OnSessionDisconnected", (["in"], DWORD, "DisconnectReason") + ), + ) + + +class IAudioSessionControl(IUnknown): + _iid_ = GUID("{F4B1A599-7266-4319-A8CA-E70ACB11E8CD}") + _methods_ = ( + # HRESULT GetState ([out] AudioSessionState *pRetVal); + COMMETHOD([], HRESULT, "GetState", (["out"], POINTER(DWORD), "pRetVal")), + # HRESULT GetDisplayName([out] LPWSTR *pRetVal); + COMMETHOD([], HRESULT, "GetDisplayName", (["out"], POINTER(LPWSTR), "pRetVal")), + # HRESULT SetDisplayName( + # [in] LPCWSTR Value, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "SetDisplayName", + (["in"], LPCWSTR, "Value"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT GetIconPath([out] LPWSTR *pRetVal); + COMMETHOD([], HRESULT, "GetIconPath", (["out"], POINTER(LPWSTR), "pRetVal")), + # HRESULT SetIconPath( + # [in] LPCWSTR Value, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "SetIconPath", + (["in"], LPCWSTR, "Value"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT GetGroupingParam([out] GUID *pRetVal); + COMMETHOD([], HRESULT, "GetGroupingParam", (["out"], POINTER(GUID), "pRetVal")), + # HRESULT SetGroupingParam( + # [in] LPCGUID Grouping, + # [in] LPCGUID EventContext); + COMMETHOD( + [], + HRESULT, + "SetGroupingParam", + (["in"], POINTER(GUID), "Grouping"), + (["in"], POINTER(GUID), "EventContext"), + ), + # HRESULT RegisterAudioSessionNotification( + # [in] IAudioSessionEvents *NewNotifications); + COMMETHOD( + [], + HRESULT, + "RegisterAudioSessionNotification", + (["in"], POINTER(IAudioSessionEvents), "NewNotifications"), + ), + # HRESULT UnregisterAudioSessionNotification( + # [in] IAudioSessionEvents *NewNotifications); + COMMETHOD( + [], + HRESULT, + "UnregisterAudioSessionNotification", + (["in"], POINTER(IAudioSessionEvents), "NewNotifications"), + ), + ) + + +class IAudioSessionControl2(IAudioSessionControl): + _iid_ = GUID("{BFB7FF88-7239-4FC9-8FA2-07C950BE9C6D}") + _methods_ = ( + # HRESULT GetSessionIdentifier([out] LPWSTR *pRetVal); + COMMETHOD( + [], HRESULT, "GetSessionIdentifier", (["out"], POINTER(LPWSTR), "pRetVal") + ), + # HRESULT GetSessionInstanceIdentifier([out] LPWSTR *pRetVal); + COMMETHOD( + [], + HRESULT, + "GetSessionInstanceIdentifier", + (["out"], POINTER(LPWSTR), "pRetVal"), + ), + # HRESULT GetProcessId([out] DWORD *pRetVal); + COMMETHOD([], HRESULT, "GetProcessId", (["out"], POINTER(DWORD), "pRetVal")), + # HRESULT IsSystemSoundsSession(); + COMMETHOD([], HRESULT, "IsSystemSoundsSession"), + # HRESULT SetDuckingPreference([in] BOOL optOut); + COMMETHOD([], HRESULT, "SetDuckingPreferences", (["in"], BOOL, "optOut")), + ) + + +class IAudioSessionEnumerator(IUnknown): + _iid_ = GUID("{E2F5BB11-0570-40CA-ACDD-3AA01277DEE8}") + _methods_ = ( + # HRESULT GetCount([out] int *SessionCount); + COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(INT), "SessionCount")), + # HRESULT GetSession( + # [in] int SessionCount, + # [out] IAudioSessionControl **Session); + COMMETHOD( + [], + HRESULT, + "GetSession", + (["in"], INT, "SessionCount"), + (["out"], POINTER(POINTER(IAudioSessionControl)), "Session"), + ), + ) + + +class IAudioSessionManager(IUnknown): + _iid_ = GUID("{BFA971F1-4d5e-40bb-935e-967039bfbee4}") + _methods_ = ( + # HRESULT GetAudioSessionControl( + # [in] LPCGUID AudioSessionGuid, + # [in] DWORD StreamFlags, + # [out] IAudioSessionControl **SessionControl); + COMMETHOD( + [], + HRESULT, + "GetAudioSessionControl", + (["in"], POINTER(GUID), "AudioSessionGuid"), + (["in"], DWORD, "StreamFlags"), + (["out"], POINTER(POINTER(IAudioSessionControl)), "SessionControl"), + ), + # HRESULT GetSimpleAudioVolume( + # [in] LPCGUID AudioSessionGuid, + # [in] DWORD CrossProcessSession, + # [out] ISimpleAudioVolume **AudioVolume); + COMMETHOD( + [], + HRESULT, + "GetSimpleAudioVolume", + (["in"], POINTER(GUID), "AudioSessionGuid"), + (["in"], DWORD, "CrossProcessSession"), + (["out"], POINTER(POINTER(ISimpleAudioVolume)), "AudioVolume"), + ), + ) + + +class IAudioSessionNotification(IUnknown): + _iid_ = GUID("{8aad9bb7-39e1-4c62-a3ab-ff6e76dcf9c8}") + _methods_ = ( + # HRESULT OnSessionCreated( + # ['in'] IAudioSessionControl *NewSession + # ); + COMMETHOD( + [], + HRESULT, + "OnSessionCreated", + (["in"], POINTER(IAudioSessionControl), "NewSession"), + ), + ) + + +class IAudioVolumeDuckNotification(IUnknown): + _iid_ = GUID("{C3B284D4-6D39-4359-B3CF-B56DDB3BB39C}") + _methods_ = ( + # HRESULT OnVolumeDuckNotification( + # [in] LPCWSTR sessionID, + # [in] UINT32 countCommunicationSessions); + COMMETHOD( + [], + HRESULT, + "OnVolumeDuckNotification", + (["in"], LPCWSTR, "sessionID"), + (["in"], c_uint32, "countCommunicationSessions"), + ), + # HRESULT OnVolumeUnduckNotification( + # [in] LPCWSTR sessionID); + COMMETHOD( + [], + HRESULT, + "OnVolumeUnduckNotification", + (["in"], LPCWSTR, "sessionID"), + ), + ) + + +class IAudioSessionManager2(IAudioSessionManager): + _iid_ = GUID("{77aa99a0-1bd6-484f-8bc7-2c654c9a9b6f}") + _methods_ = ( + # HRESULT GetSessionEnumerator( + # [out] IAudioSessionEnumerator **SessionList); + COMMETHOD( + [], + HRESULT, + "GetSessionEnumerator", + (["out"], POINTER(POINTER(IAudioSessionEnumerator)), "SessionList"), + ), + # HRESULT RegisterSessionNotification( + # IAudioSessionNotification *SessionNotification); + COMMETHOD( + [], + HRESULT, + "RegisterSessionNotification", + (["in"], POINTER(IAudioSessionNotification), "SessionNotification"), + ), + # HRESULT UnregisterSessionNotification( + # IAudioSessionNotification *SessionNotification); + COMMETHOD( + [], + HRESULT, + "UnregisterSessionNotification", + (["in"], POINTER(IAudioSessionNotification), "SessionNotification"), + ), + # HRESULT RegisterDuckNotification( + # LPCWSTR SessionID, + # IAudioVolumeDuckNotification *duckNotification); + COMMETHOD( + [], + HRESULT, + "RegisterDuckNotification", + (["in"], LPCWSTR, "SessionID"), + (["in"], POINTER(IAudioVolumeDuckNotification), "duckNotification"), + ), + # HRESULT UnregisterDuckNotification( + # IAudioVolumeDuckNotification *duckNotification); + COMMETHOD( + [], + HRESULT, + "UnregisterDuckNotification", + (["in"], POINTER(IAudioVolumeDuckNotification), "duckNotification"), + ), + ) + + + diff --git a/source/comInterfaces/coreAudio/constants.py b/source/comInterfaces/coreAudio/constants.py new file mode 100644 index 00000000000..f57579f1ec9 --- /dev/null +++ b/source/comInterfaces/coreAudio/constants.py @@ -0,0 +1,60 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from enum import Enum, IntEnum + +from comtypes import GUID + +IID_Empty = GUID("{00000000-0000-0000-0000-000000000000}") + +CLSID_MMDeviceEnumerator = GUID("{BCDE0395-E52F-467C-8E3D-C4579291692E}") + + +class ERole(Enum): + eConsole = 0 + eMultimedia = 1 + eCommunications = 2 + ERole_enum_count = 3 + + +class EDataFlow(Enum): + eRender = 0 + eCapture = 1 + eAll = 2 + EDataFlow_enum_count = 3 + + +class DEVICE_STATE(Enum): + ACTIVE = 0x00000001 + DISABLED = 0x00000002 + NOTPRESENT = 0x00000004 + UNPLUGGED = 0x00000008 + MASK_ALL = 0x0000000F + + +class AudioDeviceState(Enum): + Active = 0x1 + Disabled = 0x2 + NotPresent = 0x4 + Unplugged = 0x8 + + +class STGM(Enum): + STGM_READ = 0x00000000 + + +class AUDCLNT_SHAREMODE(Enum): + AUDCLNT_SHAREMODE_SHARED = 0x00000001 + AUDCLNT_SHAREMODE_EXCLUSIVE = 0x00000002 + + +class AudioSessionState(IntEnum): + # IntEnum to make instances comparable. + Inactive = 0 + Active = 1 + Expired = 2 + + + + diff --git a/source/comInterfaces/coreAudio/endpointvolume/__init__.py b/source/comInterfaces/coreAudio/endpointvolume/__init__.py new file mode 100644 index 00000000000..9ac8f2b7bd1 --- /dev/null +++ b/source/comInterfaces/coreAudio/endpointvolume/__init__.py @@ -0,0 +1,184 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from ctypes import HRESULT, POINTER, c_float +from ctypes.wintypes import BOOL, DWORD, UINT + +from comtypes import COMMETHOD, GUID, IUnknown + +from .depend import PAUDIO_VOLUME_NOTIFICATION_DATA + + +class IAudioEndpointVolumeCallback(IUnknown): + _iid_ = GUID("{b1136c83-b6b5-4add-98a5-a2df8eedf6fa}") + _methods_ = ( + # HRESULT OnNotify( + # [in] PAUDIO_VOLUME_NOTIFICATION_DATA pNotify); + COMMETHOD( + [], + HRESULT, + "OnNotify", + (["in"], PAUDIO_VOLUME_NOTIFICATION_DATA, "pNotify"), + ), + ) + + +class IAudioEndpointVolume(IUnknown): + _iid_ = GUID("{5CDF2C82-841E-4546-9722-0CF74078229A}") + _methods_ = ( + # HRESULT RegisterControlChangeNotify( + # [in] IAudioEndpointVolumeCallback *pNotify); + COMMETHOD( + [], + HRESULT, + "RegisterControlChangeNotify", + (["in"], POINTER(IAudioEndpointVolumeCallback), "pNotify"), + ), + # HRESULT UnregisterControlChangeNotify( + # [in] IAudioEndpointVolumeCallback *pNotify); + COMMETHOD( + [], + HRESULT, + "UnregisterControlChangeNotify", + (["in"], POINTER(IAudioEndpointVolumeCallback), "pNotify"), + ), + # HRESULT GetChannelCount([out] UINT *pnChannelCount); + COMMETHOD( + [], HRESULT, "GetChannelCount", (["out"], POINTER(UINT), "pnChannelCount") + ), + # HRESULT SetMasterVolumeLevel( + # [in] float fLevelDB, [in] LPCGUID pguidEventContext); + COMMETHOD( + [], + HRESULT, + "SetMasterVolumeLevel", + (["in"], c_float, "fLevelDB"), + (["in"], POINTER(GUID), "pguidEventContext"), + ), + # HRESULT SetMasterVolumeLevelScalar( + # [in] float fLevel, [in] LPCGUID pguidEventContext); + COMMETHOD( + [], + HRESULT, + "SetMasterVolumeLevelScalar", + (["in"], c_float, "fLevel"), + (["in"], POINTER(GUID), "pguidEventContext"), + ), + # HRESULT GetMasterVolumeLevel([out] float *pfLevelDB); + COMMETHOD( + [], + HRESULT, + "GetMasterVolumeLevel", + (["out"], POINTER(c_float), "pfLevelDB"), + ), + # HRESULT GetMasterVolumeLevelScalar([out] float *pfLevel); + COMMETHOD( + [], + HRESULT, + "GetMasterVolumeLevelScalar", + (["out"], POINTER(c_float), "pfLevelDB"), + ), + # HRESULT SetChannelVolumeLevel( + # [in] UINT nChannel, + # [in] float fLevelDB, + # [in] LPCGUID pguidEventContext); + COMMETHOD( + [], + HRESULT, + "SetChannelVolumeLevel", + (["in"], UINT, "nChannel"), + (["in"], c_float, "fLevelDB"), + (["in"], POINTER(GUID), "pguidEventContext"), + ), + # HRESULT SetChannelVolumeLevelScalar( + # [in] UINT nChannel, + # [in] float fLevel, + # [in] LPCGUID pguidEventContext); + COMMETHOD( + [], + HRESULT, + "SetChannelVolumeLevelScalar", + (["in"], DWORD, "nChannel"), + (["in"], c_float, "fLevelDB"), + (["in"], POINTER(GUID), "pguidEventContext"), + ), + # HRESULT GetChannelVolumeLevel( + # [in] UINT nChannel, + # [out] float *pfLevelDB); + COMMETHOD( + [], + HRESULT, + "GetChannelVolumeLevel", + (["in"], UINT, "nChannel"), + (["out"], POINTER(c_float), "pfLevelDB"), + ), + # HRESULT GetChannelVolumeLevelScalar( + # [in] UINT nChannel, + # [out] float *pfLevel); + COMMETHOD( + [], + HRESULT, + "GetChannelVolumeLevelScalar", + (["in"], DWORD, "nChannel"), + (["out"], POINTER(c_float), "pfLevelDB"), + ), + # HRESULT SetMute([in] BOOL bMute, [in] LPCGUID pguidEventContext); + COMMETHOD( + [], + HRESULT, + "SetMute", + (["in"], BOOL, "bMute"), + (["in"], POINTER(GUID), "pguidEventContext"), + ), + # HRESULT GetMute([out] BOOL *pbMute); + COMMETHOD([], HRESULT, "GetMute", (["out"], POINTER(BOOL), "pbMute")), + # HRESULT GetVolumeStepInfo( + # [out] UINT *pnStep, + # [out] UINT *pnStepCount); + COMMETHOD( + [], + HRESULT, + "GetVolumeStepInfo", + (["out"], POINTER(DWORD), "pnStep"), + (["out"], POINTER(DWORD), "pnStepCount"), + ), + # HRESULT VolumeStepUp([in] LPCGUID pguidEventContext); + COMMETHOD( + [], HRESULT, "VolumeStepUp", (["in"], POINTER(GUID), "pguidEventContext") + ), + # HRESULT VolumeStepDown([in] LPCGUID pguidEventContext); + COMMETHOD( + [], HRESULT, "VolumeStepDown", (["in"], POINTER(GUID), "pguidEventContext") + ), + # HRESULT QueryHardwareSupport([out] DWORD *pdwHardwareSupportMask); + COMMETHOD( + [], + HRESULT, + "QueryHardwareSupport", + (["out"], POINTER(DWORD), "pdwHardwareSupportMask"), + ), + # HRESULT GetVolumeRange( + # [out] float *pfLevelMinDB, + # [out] float *pfLevelMaxDB, + # [out] float *pfVolumeIncrementDB); + COMMETHOD( + [], + HRESULT, + "GetVolumeRange", + (["out"], POINTER(c_float), "pfMin"), + (["out"], POINTER(c_float), "pfMax"), + (["out"], POINTER(c_float), "pfIncr"), + ), + ) + + +class IAudioMeterInformation(IUnknown): + _iid_ = GUID("{C02216F6-8C67-4B5B-9D00-D008E73E0064}") + _methods_ = ( + # HRESULT GetPeakValue([out] c_float *pfPeak); + COMMETHOD([], HRESULT, "GetPeakValue", (["out"], POINTER(c_float), "pfPeak")), + ) + + + diff --git a/source/comInterfaces/coreAudio/endpointvolume/depend.py b/source/comInterfaces/coreAudio/endpointvolume/depend.py new file mode 100644 index 00000000000..c05af63f04b --- /dev/null +++ b/source/comInterfaces/coreAudio/endpointvolume/depend.py @@ -0,0 +1,24 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from ctypes import POINTER, Structure, c_float +from ctypes.wintypes import BOOL, UINT + +from comtypes import GUID + + +class AUDIO_VOLUME_NOTIFICATION_DATA(Structure): + _fields_ = [ + ("guidEventContext", GUID), + ("bMuted", BOOL), + ("fMasterVolume", c_float), + ("nChannels", UINT), + ("afChannelVolumes", c_float * 8), + ] + + +PAUDIO_VOLUME_NOTIFICATION_DATA = POINTER(AUDIO_VOLUME_NOTIFICATION_DATA) + + + diff --git a/source/comInterfaces/coreAudio/mmdeviceapi/__init__.py b/source/comInterfaces/coreAudio/mmdeviceapi/__init__.py new file mode 100644 index 00000000000..484ac870eb9 --- /dev/null +++ b/source/comInterfaces/coreAudio/mmdeviceapi/__init__.py @@ -0,0 +1,188 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from ctypes import HRESULT, POINTER +from ctypes.wintypes import DWORD, LPCWSTR, LPWSTR, UINT + +from comtypes import COMMETHOD, GUID, IUnknown + +from .depend import PROPERTYKEY, IPropertyStore + + +class IMMDevice(IUnknown): + _iid_ = GUID("{D666063F-1587-4E43-81F1-B948E807363F}") + _methods_ = ( + # HRESULT Activate( + # [in] REFIID iid, + # [in] DWORD dwClsCtx, + # [in] PROPVARIANT *pActivationParams, + # [out] void **ppInterface); + COMMETHOD( + [], + HRESULT, + "Activate", + (["in"], POINTER(GUID), "iid"), + (["in"], DWORD, "dwClsCtx"), + (["in"], POINTER(DWORD), "pActivationParams"), + (["out"], POINTER(POINTER(IUnknown)), "ppInterface"), + ), + # HRESULT OpenPropertyStore( + # [in] DWORD stgmAccess, + # [out] IPropertyStore **ppProperties); + COMMETHOD( + [], + HRESULT, + "OpenPropertyStore", + (["in"], DWORD, "stgmAccess"), + (["out"], POINTER(POINTER(IPropertyStore)), "ppProperties"), + ), + # HRESULT GetId([out] LPWSTR *ppstrId); + COMMETHOD([], HRESULT, "GetId", (["out"], POINTER(LPWSTR), "ppstrId")), + # HRESULT GetState([out] DWORD *pdwState); + COMMETHOD([], HRESULT, "GetState", (["out"], POINTER(DWORD), "pdwState")), + ) + + +class IMMDeviceCollection(IUnknown): + _iid_ = GUID("{0BD7A1BE-7A1A-44DB-8397-CC5392387B5E}") + _methods_ = ( + # HRESULT GetCount([out] UINT *pcDevices); + COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(UINT), "pcDevices")), + # HRESULT Item([in] UINT nDevice, [out] IMMDevice **ppDevice); + COMMETHOD( + [], + HRESULT, + "Item", + (["in"], UINT, "nDevice"), + (["out"], POINTER(POINTER(IMMDevice)), "ppDevice"), + ), + ) + + +class IMMNotificationClient(IUnknown): + _case_insensitive_ = True + _iid_ = GUID("{7991EEC9-7E89-4D85-8390-6C703CEC60C0}") + _methods_ = ( + # HRESULT OnDeviceStateChanged( + # [in] LPCWSTR pwstrDeviceId, + # [in] DWORD dwNewState); + COMMETHOD( + [], + HRESULT, + "OnDeviceStateChanged", + (["in"], LPCWSTR, "pwstrDeviceId"), + (["in"], DWORD, "dwNewState"), + ), + # HRESULT OnDeviceAdded( + # [in] LPCWSTR pwstrDeviceId, + COMMETHOD( + [], + HRESULT, + "OnDeviceAdded", + (["in"], LPCWSTR, "pwstrDeviceId"), + ), + # HRESULT OnDeviceRemoved( + # [in] LPCWSTR pwstrDeviceId, + COMMETHOD( + [], + HRESULT, + "OnDeviceRemoved", + (["in"], LPCWSTR, "pwstrDeviceId"), + ), + # HRESULT OnDefaultDeviceChanged( + # [in] EDataFlow flow, + # [in] ERole role, + # [in] LPCWSTR pwstrDefaultDeviceId; + COMMETHOD( + [], + HRESULT, + "OnDefaultDeviceChanged", + (["in"], DWORD, "flow"), + (["in"], DWORD, "role"), + (["in"], LPCWSTR, "pwstrDefaultDeviceId"), + ), + # HRESULT OnPropertyValueChanged( + # [in] LPCWSTR pwstrDeviceId, + # [in] const PROPERTYKEY key); + COMMETHOD( + [], + HRESULT, + "OnPropertyValueChanged", + (["in"], LPCWSTR, "pwstrDeviceId"), + (["in"], PROPERTYKEY, "key"), + ), + ) + + +class IMMDeviceEnumerator(IUnknown): + _iid_ = GUID("{A95664D2-9614-4F35-A746-DE8DB63617E6}") + _methods_ = ( + # HRESULT EnumAudioEndpoints( + # [in] EDataFlow dataFlow, + # [in] DWORD dwStateMask, + # [out] IMMDeviceCollection **ppDevices); + COMMETHOD( + [], + HRESULT, + "EnumAudioEndpoints", + (["in"], DWORD, "dataFlow"), + (["in"], DWORD, "dwStateMask"), + (["out"], POINTER(POINTER(IMMDeviceCollection)), "ppDevices"), + ), + # HRESULT GetDefaultAudioEndpoint( + # [in] EDataFlow dataFlow, + # [in] ERole role, + # [out] IMMDevice **ppDevice); + COMMETHOD( + [], + HRESULT, + "GetDefaultAudioEndpoint", + (["in"], DWORD, "dataFlow"), + (["in"], DWORD, "role"), + (["out"], POINTER(POINTER(IMMDevice)), "ppDevices"), + ), + # HRESULT GetDevice( + # [in] LPCWSTR pwstrId, + # [out] IMMDevice **ppDevice); + COMMETHOD( + [], + HRESULT, + "GetDevice", + (["in"], LPCWSTR, "pwstrId"), + (["out"], POINTER(POINTER(IMMDevice)), "ppDevice"), + ), + # HRESULT RegisterEndpointNotificationCallback( + # [in] IMMNotificationClient *pClient); + COMMETHOD( + [], + HRESULT, + "RegisterEndpointNotificationCallback", + (["in"], POINTER(IMMNotificationClient), "pClient"), + ), + # HRESULT UnregisterEndpointNotificationCallback( + # [in] IMMNotificationClient *pClient); + COMMETHOD( + [], + HRESULT, + "UnregisterEndpointNotificationCallback", + (["in"], POINTER(IMMNotificationClient), "pClient"), + ), + ) + + +class IMMEndpoint(IUnknown): + _iid_ = GUID("{1BE09788-6894-4089-8586-9A2A6C265AC5}") + _methods_ = ( + # HRESULT GetDataFlow( + # [out] EDataFlow *pDataFlow); + COMMETHOD( + [], + HRESULT, + "GetDataFlow", + (["out"], POINTER(DWORD), "pDataFlow"), + ), + ) + + + diff --git a/source/comInterfaces/coreAudio/mmdeviceapi/depend/__init__.py b/source/comInterfaces/coreAudio/mmdeviceapi/depend/__init__.py new file mode 100644 index 00000000000..18594db9ac4 --- /dev/null +++ b/source/comInterfaces/coreAudio/mmdeviceapi/depend/__init__.py @@ -0,0 +1,54 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from ctypes import HRESULT, POINTER +from ctypes.wintypes import DWORD + +from comtypes import COMMETHOD, GUID, IUnknown + +from .structures import PROPERTYKEY, PROPVARIANT + + +class IPropertyStore(IUnknown): + _iid_ = GUID("{886d8eeb-8cf2-4446-8d02-cdba1dbdcf99}") + _methods_ = ( + # HRESULT GetCount([out] DWORD *cProps); + COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(DWORD), "cProps")), + # HRESULT GetAt( + # [in] DWORD iProp, + # [out] PROPERTYKEY *pkey); + COMMETHOD( + [], + HRESULT, + "GetAt", + (["in"], DWORD, "iProp"), + (["out"], POINTER(PROPERTYKEY), "pkey"), + ), + # HRESULT GetValue( + # [in] REFPROPERTYKEY key, + # [out] PROPVARIANT *pv); + COMMETHOD( + [], + HRESULT, + "GetValue", + (["in"], POINTER(PROPERTYKEY), "key"), + (["out"], POINTER(PROPVARIANT), "pv"), + ), + # HRESULT SetValue( + # [in] REFPROPERTYKEY key, + # [in] REFPROPVARIANT propvar + # ); + COMMETHOD( + [], + HRESULT, + "SetValue", + (["in"], POINTER(PROPERTYKEY), "key"), + (["in"], POINTER(PROPVARIANT), "propvar"), + ), + # HRESULT Commit(); + COMMETHOD([], HRESULT, "Commit"), + ) + + + diff --git a/source/comInterfaces/coreAudio/mmdeviceapi/depend/structures.py b/source/comInterfaces/coreAudio/mmdeviceapi/depend/structures.py new file mode 100644 index 00000000000..08016daf282 --- /dev/null +++ b/source/comInterfaces/coreAudio/mmdeviceapi/depend/structures.py @@ -0,0 +1,61 @@ +# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). +# Please note that it is distributed under MIT license: +# https://github.com/AndreMiras/pycaw#MIT-1-ov-file + +from ctypes import Structure, Union, byref, windll +from ctypes.wintypes import DWORD, LONG, LPWSTR, ULARGE_INTEGER, VARIANT_BOOL, WORD + +from comtypes import GUID +from comtypes.automation import VARTYPE, VT_BOOL, VT_CLSID, VT_LPWSTR, VT_UI4 + + +class PROPVARIANT_UNION(Union): + _fields_ = [ + ("lVal", LONG), + ("uhVal", ULARGE_INTEGER), + ("boolVal", VARIANT_BOOL), + ("pwszVal", LPWSTR), + ("puuid", GUID), + ] + + +class PROPVARIANT(Structure): + _fields_ = [ + ("vt", VARTYPE), + ("reserved1", WORD), + ("reserved2", WORD), + ("reserved3", WORD), + ("union", PROPVARIANT_UNION), + ] + + def GetValue(self): + vt = self.vt + if vt == VT_BOOL: + return self.union.boolVal != 0 + elif vt == VT_LPWSTR: + # return Marshal.PtrToStringUni(union.pwszVal) + return self.union.pwszVal + elif vt == VT_UI4: + return self.union.lVal + elif vt == VT_CLSID: + # TODO + # return (Guid)Marshal.PtrToStructure(union.puuid, typeof(Guid)) + return + else: + return "%s:?" % (vt) + + def clear(self): + windll.ole32.PropVariantClear(byref(self)) + + +class PROPERTYKEY(Structure): + _fields_ = [ + ("fmtid", GUID), + ("pid", DWORD), + ] + + def __str__(self): + return "%s %s" % (self.fmtid, self.pid) + + + diff --git a/source/config/configSpec.py b/source/config/configSpec.py index 87d44a30fe9..7749be6005e 100644 --- a/source/config/configSpec.py +++ b/source/config/configSpec.py @@ -54,6 +54,8 @@ WASAPI = featureFlag(optionsEnum="BoolFlag", behaviorOfDefault="enabled") soundVolumeFollowsVoice = boolean(default=false) soundVolume = integer(default=100, min=0, max=100) + soundSplitState = integer(default=0) + soundSplitToggleMode= integer(default=0) # Braille settings [braille] diff --git a/source/core.py b/source/core.py index 13f7df7f783..901109927ed 100644 --- a/source/core.py +++ b/source/core.py @@ -597,6 +597,9 @@ def main(): log.debug("Initializing tones") import tones tones.initialize() + log.debug("Initializing sound split") + import audio + audio.initialize() import speechDictHandler log.debug("Speech Dictionary processing") speechDictHandler.initialize() diff --git a/source/globalCommands.py b/source/globalCommands.py index 297ffdfcc2e..7398fb8e14d 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -113,6 +113,9 @@ #: Script category for document formatting commands. # Translators: The name of a category of NVDA commands. SCRCAT_DOCUMENTFORMATTING = _("Document formatting") +#: Script category for audio streaming commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_AUDIOSTREAMING = _("Audio streaming") class GlobalCommands(ScriptableObject): """Commands that are available at all times, regardless of the current focus. @@ -123,6 +126,7 @@ class GlobalCommands(ScriptableObject): # Translators: Describes the Cycle audio ducking mode command. "Cycles through audio ducking modes which determine when NVDA lowers the volume of other sounds" ), + category=SCRCAT_AUDIOSTREAMING, gesture="kb:NVDA+shift+d" ) def script_cycleAudioDuckingMode(self,gesture): @@ -4382,6 +4386,18 @@ def script_cycleParagraphStyle(self, gesture: "inputCore.InputGesture") -> None: config.conf["documentNavigation"]["paragraphStyle"] = newFlag.name ui.message(newFlag.displayString) + @script( + description=_( + # Translators: Describes a command. + "Cycles through sound split modes", + ), + category=SCRCAT_AUDIOSTREAMING, + gesture="kb:NVDA+alt+s", + ) + def script_cycleSoundSplit(self, gesture: "inputCore.InputGesture") -> None: + import audio + audio.toggleSoundSplitState() + #: The single global commands instance. #: @type: L{GlobalCommands} diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 8ab48695fc9..c9967ea083c 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -42,6 +42,7 @@ import globalVars from logHandler import log import nvwave +import audio import audioDucking import queueHandler import braille @@ -2710,6 +2711,28 @@ def makeSettings(self, settingsSizer: wx.BoxSizer) -> None: self._onSoundVolChange(None) + # Translators: This is a label for the sound split combo box in the Audio Settings dialog. + soundSplitLabelText = _("Sound split mode:") + self.soundSplitComboBox = sHelper.addLabeledControl( + soundSplitLabelText, + wx.Choice, + choices=[mode.displayString for mode in audio.SoundSplitState] + ) + self.bindHelpEvent("SelectSoundSplitMode", self.soundSplitComboBox) + index = config.conf["audio"]["soundSplitState"] + self.soundSplitComboBox.SetSelection(index) + + # Translators: This is a label for the sound split Toggle Mode combo box in the Audio Settings dialog. + soundSplitToggleModeLabelText = _("NVDA+Alt+S command behavior:") + self.soundSplitToggleModeComboBox = sHelper.addLabeledControl( + soundSplitToggleModeLabelText, + wx.Choice, + choices=[mode.displayString for mode in audio.SoundSplitToggleMode] + ) + self.bindHelpEvent("SelectSoundSplitToggleMode", self.soundSplitToggleModeComboBox) + index = config.conf["audio"]["soundSplitToggleMode"] + self.soundSplitToggleModeComboBox.SetSelection(index) + def onSave(self): if config.conf["speech"]["outputDevice"] != self.deviceList.GetStringSelection(): # Synthesizer must be reload if output device changes @@ -2726,6 +2749,12 @@ def onSave(self): config.conf["audio"]["soundVolumeFollowsVoice"] = self.soundVolFollowCheckBox.IsChecked() config.conf["audio"]["soundVolume"] = self.soundVolSlider.GetValue() + index = self.soundSplitComboBox.GetSelection() + config.conf["audio"]["soundSplitState"] = index + audio.setSoundSplitState(audio.SoundSplitState(index)) + index = self.soundSplitToggleModeComboBox.GetSelection() + config.conf["audio"]["soundSplitToggleMode"] = index + if audioDucking.isAudioDuckingSupported(): index = self.duckingList.GetSelection() config.conf["audio"]["audioDuckingMode"] = index @@ -2743,6 +2772,8 @@ def _onSoundVolChange(self, event: wx.Event) -> None: wasapi and not self.soundVolFollowCheckBox.IsChecked() ) + self.soundSplitComboBox.Enable(wasapi) + self.soundSplitToggleModeComboBox.Enable(wasapi) class AddonStorePanel(SettingsPanel): diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index 09874d70967..a9b8fcf142f 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -7,6 +7,7 @@ What's New in NVDA == New Features == - In Windows 11, NVDA will announce alerts from voice typing and suggested actions including the top suggestion when copying data such as phone numbers to the clipboard (Windows 11 2022 Update and later). (#16009, @josephsl) +- Sound split - toggled by NVDA+Alt+S - diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 4ca1497d1cf..915344bb6eb 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -850,7 +850,7 @@ Here is a list of available commands: - Article - Grouping - Tab -- +-2 Keep in mind that there are two commands for each type of element, for moving forward in the document and backward in the document, and you must assign gestures to both commands in order to be able to quickly navigate in both directions. For example, if you want to use the ``y`` / ``shift+y`` keys to quickly navigate through tabs, you would do the following: @@ -1892,6 +1892,17 @@ This slider allows you to set the volume of NVDA sounds and beeps. This setting only takes effect when "Volume of NVDA sounds follows voice volume" is disabled. This option is not available if you have started NVDA with [WASAPI disabled for audio output #WASAPI] in Advanced Settings. +==== Sound split====[SelectSoundSplitMode] +Key: ``NVDA+alt+s`` + +This option allows you to make NVDA sound output to be directed to either left or right channel and application volume to be directed to the other channel. This could be useful for headphones users. +- Disabled sound split: both NVDA and other applications output sounds to both left and right channels. +- NVDA on the left and applications on the right: NVDA will speak in the left channel, while other applications will play sounds in the right channel. +- NVDA on the right and applications on the left: NVDA will speak in the right channel, while other applications will play sounds in the left channel. +- + +This option is not available if you have started NVDA with [WASAPI disabled for audio output #WASAPI] in Advanced Settings. + +++ Vision +++[VisionSettings] The Vision category in the NVDA Settings dialog allows you to enable, disable and configure [visual aids #Vision]. From adf2a8ed588ab807cbee46b9bcf552d4f0cd5647 Mon Sep 17 00:00:00 2001 From: mltony Date: Mon, 22 Jan 2024 11:21:43 -0800 Subject: [PATCH 02/44] Addressing comments --- source/audio.py | 116 ++++--- .../comInterfaces/coreAudio.bak/__init__.py | 0 .../coreAudio.bak/audioclient/__init__.py | 155 --------- .../coreAudio.bak/audioclient/depend.py | 18 -- .../coreAudio.bak/audiopolicy/__init__.py | 299 ------------------ .../comInterfaces/coreAudio.bak/constants.py | 56 ---- .../coreAudio.bak/endpointvolume/__init__.py | 182 ----------- .../coreAudio.bak/endpointvolume/depend.py | 22 -- .../coreAudio.bak/mmdeviceapi/__init__.py | 186 ----------- .../mmdeviceapi/depend/__init__.py | 52 --- .../mmdeviceapi/depend/structures.py | 59 ---- source/config/configSpec.py | 3 +- source/core.py | 6 + source/globalCommands.py | 8 +- source/gui/settingsDialogs.py | 81 ++++- user_docs/en/changes.t2t | 2 +- user_docs/en/userGuide.t2t | 36 ++- 17 files changed, 168 insertions(+), 1113 deletions(-) delete mode 100644 source/comInterfaces/coreAudio.bak/__init__.py delete mode 100644 source/comInterfaces/coreAudio.bak/audioclient/__init__.py delete mode 100644 source/comInterfaces/coreAudio.bak/audioclient/depend.py delete mode 100644 source/comInterfaces/coreAudio.bak/audiopolicy/__init__.py delete mode 100644 source/comInterfaces/coreAudio.bak/constants.py delete mode 100644 source/comInterfaces/coreAudio.bak/endpointvolume/__init__.py delete mode 100644 source/comInterfaces/coreAudio.bak/endpointvolume/depend.py delete mode 100644 source/comInterfaces/coreAudio.bak/mmdeviceapi/__init__.py delete mode 100644 source/comInterfaces/coreAudio.bak/mmdeviceapi/depend/__init__.py delete mode 100644 source/comInterfaces/coreAudio.bak/mmdeviceapi/depend/structures.py diff --git a/source/audio.py b/source/audio.py index 6fb652edec5..0971d0a9868 100644 --- a/source/audio.py +++ b/source/audio.py @@ -16,86 +16,78 @@ import config from enum import IntEnum, unique import globalVars +import json from logHandler import log import nvwave -from typing import Tuple, Optional, Dict, List, Callable, NoReturn +from typing import Callable import ui from utils.displayString import DisplayStringIntEnum +import _ctypes -VolumeTupleT = Tuple[float, float] +VolumeTupleT = tuple[float, float] @unique class SoundSplitState(DisplayStringIntEnum): OFF = 0 - NVDA_LEFT = 1 - NVDA_RIGHT = 2 + NVDA_LEFT_APPS_RIGHT = 1 + NVDA_LEFT_APPS_BOTH = 2 + NVDA_RIGHT_APPS_LEFT = 3 + NVDA_RIGHT_APPS_BOTH = 4 + NVDA_BOTH_APPS_LEFT = 5 + NVDA_BOTH_APPS_RIGHT = 6 @property - def _displayStringLabels(self) -> Dict[IntEnum, str]: + def _displayStringLabels(self) -> dict[IntEnum, str]: return { # Translators: Sound split state SoundSplitState.OFF: _("Disabled sound split"), # Translators: Sound split state - SoundSplitState.NVDA_LEFT: _("NVDA on the left and applications on the right"), + SoundSplitState.NVDA_LEFT_APPS_RIGHT: _("NVDA on the left and applications on the right"), # Translators: Sound split state - SoundSplitState.NVDA_RIGHT: _("NVDA on the right and applications on the left"), + SoundSplitState.NVDA_LEFT_APPS_BOTH: _("NVDA on the left and applications in both channels"), + # Translators: Sound split state + SoundSplitState.NVDA_RIGHT_APPS_LEFT: _("NVDA on the right and applications on the left"), + # Translators: Sound split state + SoundSplitState.NVDA_RIGHT_APPS_BOTH: _("NVDA on the right and applications in both channels"), + # Translators: Sound split state + SoundSplitState.NVDA_BOTH_APPS_LEFT: _("NVDA in both channels and applications on the left"), + # Translators: Sound split state + SoundSplitState.NVDA_BOTH_APPS_RIGHT: _("NVDA in both channels and applications on the right"), } def getAppVolume(self) -> VolumeTupleT: - return { - SoundSplitState.OFF: (1.0, 1.0), - SoundSplitState.NVDA_LEFT: (0.0, 1.0), - SoundSplitState.NVDA_RIGHT: (1.0, 0.0), - }[self] - - def getNVDAVolume(self) -> VolumeTupleT: - return { - SoundSplitState.OFF: (1.0, 1.0), - SoundSplitState.NVDA_LEFT: (1.0, 0.0), - SoundSplitState.NVDA_RIGHT: (0.0, 1.0), - }[self] - - -@unique -class SoundSplitToggleMode(DisplayStringIntEnum): - OFF_LEFT_RIGHT = 0 - OFF_AND_NVDA_LEFT = 1 - OFF_AND_NVDA_RIGHT = 2 + if self == SoundSplitState.OFF or 'APPS_BOTH' in self.name: + return (1.0, 1.0) + elif 'APPS_LEFT' in self.name: + return (1.0, 0.0) + elif 'APPS_RIGHT' in self.name: + return (0.0, 1.0) + else: + raise RuntimeError - @property - def _displayStringLabels(self) -> Dict[IntEnum, str]: - return { - # Translators: Sound split toggle mode - SoundSplitToggleMode.OFF_AND_NVDA_LEFT: _("Cycles through off and NVDA on the left"), - # Translators: Sound split toggle mode - SoundSplitToggleMode.OFF_AND_NVDA_RIGHT: _("Cycles through off and NVDA on the right"), - # Translators: Sound split toggle mode - SoundSplitToggleMode.OFF_LEFT_RIGHT: _("Cycles through off, NVDA on the left and NVDA on the right"), - } - - def getPossibleStates(self) -> List[SoundSplitState]: - result = [SoundSplitState.OFF] - if 'LEFT' in self.name: - result.append(SoundSplitState.NVDA_LEFT) - if 'RIGHT' in self.name: - result.append(SoundSplitState.NVDA_RIGHT) - return result - - def getClosestState(self, state: SoundSplitState) -> SoundSplitState: - states = self.getPossibleStates() - if state in states: - return state - return states[-1] + def getNVDAVolume(self) -> VolumeTupleT: + if self == SoundSplitState.OFF or 'NVDA_BOTH' in self.name: + return (1.0, 1.0) + elif 'NVDA_LEFT' in self.name: + return (1.0, 0.0) + elif 'NVDA_RIGHT' in self.name: + return (0.0, 1.0) + else: + raise RuntimeError sessionManager: audiopolicy.IAudioSessionManager2 = None -activeCallback: Optional[comtypes.COMObject] = None +activeCallback: comtypes.COMObject | None = None def initialize() -> None: global sessionManager - sessionManager = getSessionManager() + try: + sessionManager = getSessionManager() + except _ctypes.COMError as e: + log.error("Could not initialize audio session manager! ", e) + return if sessionManager is None: log.error("Could not initialize audio session manager! ") return @@ -107,13 +99,15 @@ def initialize() -> None: @atexit.register def terminate(): + global activeCallback if nvwave.usingWasapiWavePlayer(): setSoundSplitState(SoundSplitState.OFF) if activeCallback is not None: unregisterCallback(activeCallback) + activeCallback = None -def getDefaultAudioDevice(kind: EDataFlow = EDataFlow.eRender) -> Optional[mmdeviceapi.IMMDevice]: +def getDefaultAudioDevice(kind: EDataFlow = EDataFlow.eRender) -> mmdeviceapi.IMMDevice | None: deviceEnumerator = comtypes.CoCreateInstance( CLSID_MMDeviceEnumerator, mmdeviceapi.IMMDeviceEnumerator, @@ -136,9 +130,9 @@ def getSessionManager() -> audiopolicy.IAudioSessionManager2: def applyToAllAudioSessions( - func: Callable[[audiopolicy.IAudioSessionControl2], NoReturn], + func: Callable[[audiopolicy.IAudioSessionControl2], None], applyToFuture: bool = True, -) -> Optional[comtypes.COMObject]: +) -> comtypes.COMObject | None: sessionEnumerator: audiopolicy.IAudioSessionEnumerator = sessionManager.GetSessionEnumerator() for i in range(sessionEnumerator.GetCount()): session: audiopolicy.IAudioSessionControl = sessionEnumerator.GetSession(i) @@ -168,7 +162,8 @@ def setSoundSplitState(state: SoundSplitState) -> None: unregisterCallback(activeCallback) activeCallback = None leftVolume, rightVolume = state.getAppVolume() - + leftNVDAVolume, rightNVDAVolume = state.getNVDAVolume() + def volumeSetter(session2: audiopolicy.IAudioSessionControl2) -> None: channelVolume: audioclient.IChannelAudioVolume = session2.QueryInterface(audioclient.IChannelAudioVolume) channelCount = channelVolume.GetChannelCount() @@ -181,8 +176,8 @@ def volumeSetter(session2: audiopolicy.IAudioSessionControl2) -> None: channelVolume.SetChannelVolume(0, leftVolume, None) channelVolume.SetChannelVolume(1, rightVolume, None) else: - channelVolume.SetChannelVolume(1, leftVolume, None) - channelVolume.SetChannelVolume(0, rightVolume, None) + channelVolume.SetChannelVolume(0, leftNVDAVolume, None) + channelVolume.SetChannelVolume(1, rightNVDAVolume, None) activeCallback = applyToAllAudioSessions(volumeSetter) @@ -196,15 +191,14 @@ def toggleSoundSplitState() -> None: ) ui.message(message) return - toggleMode = SoundSplitToggleMode(config.conf['audio']['soundSplitToggleMode']) state = SoundSplitState(config.conf['audio']['soundSplitState']) - allowedStates = toggleMode.getPossibleStates() + allowedStates: list[int] = json.loads(config.conf["audio"]["includedSoundSplitModes"]) try: i = allowedStates.index(state) except ValueError: i = -1 i = (i + 1) % len(allowedStates) - newState = allowedStates[i] + newState = SoundSplitState(allowedStates[i]) setSoundSplitState(newState) config.conf['audio']['soundSplitState'] = newState.value ui.message(newState.displayString) diff --git a/source/comInterfaces/coreAudio.bak/__init__.py b/source/comInterfaces/coreAudio.bak/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/source/comInterfaces/coreAudio.bak/audioclient/__init__.py b/source/comInterfaces/coreAudio.bak/audioclient/__init__.py deleted file mode 100644 index 5c13f49e4c7..00000000000 --- a/source/comInterfaces/coreAudio.bak/audioclient/__init__.py +++ /dev/null @@ -1,155 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from ctypes import HRESULT, POINTER, c_float -from ctypes import c_longlong as REFERENCE_TIME -from ctypes import c_uint32 as UINT32 -from ctypes.wintypes import BOOL, DWORD, HANDLE - -from comtypes import COMMETHOD, GUID, IUnknown - -from .depend import WAVEFORMATEX - - -class ISimpleAudioVolume(IUnknown): - _iid_ = GUID("{87CE5498-68D6-44E5-9215-6DA47EF883D8}") - _methods_ = ( - # HRESULT SetMasterVolume( - # [in] float fLevel, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "SetMasterVolume", - (["in"], c_float, "fLevel"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT GetMasterVolume([out] float *pfLevel); - COMMETHOD( - [], HRESULT, "GetMasterVolume", (["out"], POINTER(c_float), "pfLevel") - ), - # HRESULT SetMute( - # [in] BOOL bMute, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "SetMute", - (["in"], BOOL, "bMute"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT GetMute([out] BOOL *pbMute); - COMMETHOD([], HRESULT, "GetMute", (["out"], POINTER(BOOL), "pbMute")), - ) - - -class IAudioClient(IUnknown): - _iid_ = GUID("{1cb9ad4c-dbfa-4c32-b178-c2f568a703b2}") - _methods_ = ( - # HRESULT Initialize( - # [in] AUDCLNT_SHAREMODE ShareMode, - # [in] DWORD StreamFlags, - # [in] REFERENCE_TIME hnsBufferDuration, - # [in] REFERENCE_TIME hnsPeriodicity, - # [in] const WAVEFORMATEX *pFormat, - # [in] LPCGUID AudioSessionGuid); - COMMETHOD( - [], - HRESULT, - "Initialize", - (["in"], DWORD, "ShareMode"), - (["in"], DWORD, "StreamFlags"), - (["in"], REFERENCE_TIME, "hnsBufferDuration"), - (["in"], REFERENCE_TIME, "hnsPeriodicity"), - (["in"], POINTER(WAVEFORMATEX), "pFormat"), - (["in"], POINTER(GUID), "AudioSessionGuid"), - ), - # HRESULT GetBufferSize( - # [out] UINT32 *pNumBufferFrames); - COMMETHOD( - [], HRESULT, "GetBufferSize", (["out"], POINTER(UINT32), "pNumBufferFrames") - ), - # HRESULT GetStreamLatency( - # [out] REFERENCE_TIME *phnsLatency); - COMMETHOD( - [], - HRESULT, - "GetStreamLatency", - (["out"], POINTER(REFERENCE_TIME), "phnsLatency"), - ), - # HRESULT GetCurrentPadding( - # [out] UINT32 *pNumPaddingFrames); - COMMETHOD( - [], - HRESULT, - "GetCurrentPadding", - (["out"], POINTER(UINT32), "pNumPaddingFrames"), - ), - # HRESULT IsFormatSupported( - # [in] AUDCLNT_SHAREMODE ShareMode, - # [in] const WAVEFORMATEX *pFormat, - # [out,unique] WAVEFORMATEX **ppClosestMatch); - COMMETHOD( - [], - HRESULT, - "IsFormatSupported", - (["in"], DWORD, "ShareMode"), - (["in"], POINTER(WAVEFORMATEX), "pFormat"), - (["out"], POINTER(POINTER(WAVEFORMATEX)), "ppClosestMatch"), - ), - # HRESULT GetMixFormat( - # [out] WAVEFORMATEX **ppDeviceFormat - # ); - COMMETHOD( - [], - HRESULT, - "GetMixFormat", - (["out"], POINTER(POINTER(WAVEFORMATEX)), "ppDeviceFormat"), - ), - # HRESULT GetDevicePeriod( - # [out] REFERENCE_TIME *phnsDefaultDevicePeriod, - # [out] REFERENCE_TIME *phnsMinimumDevicePeriod); - COMMETHOD( - [], - HRESULT, - "GetDevicePeriod", - (["out"], POINTER(REFERENCE_TIME), "phnsDefaultDevicePeriod"), - (["out"], POINTER(REFERENCE_TIME), "phnsMinimumDevicePeriod"), - ), - # HRESULT Start(void); - COMMETHOD([], HRESULT, "Start"), - # HRESULT Stop(void); - COMMETHOD([], HRESULT, "Stop"), - # HRESULT Reset(void); - COMMETHOD([], HRESULT, "Reset"), - # HRESULT SetEventHandle([in] HANDLE eventHandle); - COMMETHOD( - [], - HRESULT, - "SetEventHandle", - (["in"], HANDLE, "eventHandle"), - ), - # HRESULT GetService( - # [in] REFIID riid, - # [out] void **ppv); - COMMETHOD( - [], - HRESULT, - "GetService", - (["in"], POINTER(GUID), "iid"), - (["out"], POINTER(POINTER(IUnknown)), "ppv"), - ), - ) - - -class IChannelAudioVolume (IUnknown): - _iid_ = GUID('{1c158861-b533-4b30-b1cf-e853e51c59b8}') - _methods_ = ( - COMMETHOD([], HRESULT, 'GetChannelCount', - (['out'], POINTER(UINT), 'pnChannelCount')), - COMMETHOD([], HRESULT, 'SetChannelVolume', - (['in'], UINT, 'dwIndex'), - (['in'], c_float, 'fLevel'), - (['in'], POINTER(GUID), 'EventContext')), - ) diff --git a/source/comInterfaces/coreAudio.bak/audioclient/depend.py b/source/comInterfaces/coreAudio.bak/audioclient/depend.py deleted file mode 100644 index 666ff3f961f..00000000000 --- a/source/comInterfaces/coreAudio.bak/audioclient/depend.py +++ /dev/null @@ -1,18 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from ctypes import Structure -from ctypes.wintypes import WORD - - -class WAVEFORMATEX(Structure): - _fields_ = [ - ("wFormatTag", WORD), - ("nChannels", WORD), - ("nSamplesPerSec", WORD), - ("nAvgBytesPerSec", WORD), - ("nBlockAlign", WORD), - ("wBitsPerSample", WORD), - ("cbSize", WORD), - ] diff --git a/source/comInterfaces/coreAudio.bak/audiopolicy/__init__.py b/source/comInterfaces/coreAudio.bak/audiopolicy/__init__.py deleted file mode 100644 index ac0a1a26a91..00000000000 --- a/source/comInterfaces/coreAudio.bak/audiopolicy/__init__.py +++ /dev/null @@ -1,299 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from ctypes import HRESULT, POINTER, c_float, c_uint32 -from ctypes.wintypes import BOOL, DWORD, INT, LPCWSTR, LPWSTR - -from comtypes import COMMETHOD, GUID, IUnknown - -from ..audioclient import ISimpleAudioVolume - - -class IAudioSessionEvents(IUnknown): - _iid_ = GUID("{073d618c-490a-4f9f-9d18-7bec6fc21121}") - _methods_ = ( - # HRESULT OnDisplayNameChanged( - # [in] LPCWSTR NewDisplayName, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "OnDisplayNameChanged", - (["in"], LPCWSTR, "NewDisplayName"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT OnIconPathChanged( - # [in] LPCWSTR NewIconPath, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "OnIconPathChanged", - (["in"], LPCWSTR, "NewIconPath"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT OnSimpleVolumeChanged( - # [in] float NewVolume, - # [in] BOOL NewMute, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "OnSimpleVolumeChanged", - (["in"], c_float, "NewVolume"), - (["in"], BOOL, "NewMute"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT OnChannelVolumeChanged( - # [in] DWORD ChannelCount, - # [in] float [] NewChannelVolumeArray, - # [in] DWORD ChangedChannel, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "OnChannelVolumeChanged", - (["in"], DWORD, "ChannelCount"), - (["in"], (c_float * 8), "NewChannelVolumeArray"), - (["in"], DWORD, "ChangedChannel"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT OnGroupingParamChanged( - # [in] LPCGUID NewGroupingParam, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "OnGroupingParamChanged", - (["in"], POINTER(GUID), "NewGroupingParam"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT OnStateChanged( - # AudioSessionState NewState); - COMMETHOD([], HRESULT, "OnStateChanged", (["in"], DWORD, "NewState")), - # HRESULT OnSessionDisconnected( - # [in] AudioSessionDisconnectReason DisconnectReason); - COMMETHOD( - [], HRESULT, "OnSessionDisconnected", (["in"], DWORD, "DisconnectReason") - ), - ) - - -class IAudioSessionControl(IUnknown): - _iid_ = GUID("{F4B1A599-7266-4319-A8CA-E70ACB11E8CD}") - _methods_ = ( - # HRESULT GetState ([out] AudioSessionState *pRetVal); - COMMETHOD([], HRESULT, "GetState", (["out"], POINTER(DWORD), "pRetVal")), - # HRESULT GetDisplayName([out] LPWSTR *pRetVal); - COMMETHOD([], HRESULT, "GetDisplayName", (["out"], POINTER(LPWSTR), "pRetVal")), - # HRESULT SetDisplayName( - # [in] LPCWSTR Value, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "SetDisplayName", - (["in"], LPCWSTR, "Value"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT GetIconPath([out] LPWSTR *pRetVal); - COMMETHOD([], HRESULT, "GetIconPath", (["out"], POINTER(LPWSTR), "pRetVal")), - # HRESULT SetIconPath( - # [in] LPCWSTR Value, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "SetIconPath", - (["in"], LPCWSTR, "Value"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT GetGroupingParam([out] GUID *pRetVal); - COMMETHOD([], HRESULT, "GetGroupingParam", (["out"], POINTER(GUID), "pRetVal")), - # HRESULT SetGroupingParam( - # [in] LPCGUID Grouping, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "SetGroupingParam", - (["in"], POINTER(GUID), "Grouping"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT RegisterAudioSessionNotification( - # [in] IAudioSessionEvents *NewNotifications); - COMMETHOD( - [], - HRESULT, - "RegisterAudioSessionNotification", - (["in"], POINTER(IAudioSessionEvents), "NewNotifications"), - ), - # HRESULT UnregisterAudioSessionNotification( - # [in] IAudioSessionEvents *NewNotifications); - COMMETHOD( - [], - HRESULT, - "UnregisterAudioSessionNotification", - (["in"], POINTER(IAudioSessionEvents), "NewNotifications"), - ), - ) - - -class IAudioSessionControl2(IAudioSessionControl): - _iid_ = GUID("{BFB7FF88-7239-4FC9-8FA2-07C950BE9C6D}") - _methods_ = ( - # HRESULT GetSessionIdentifier([out] LPWSTR *pRetVal); - COMMETHOD( - [], HRESULT, "GetSessionIdentifier", (["out"], POINTER(LPWSTR), "pRetVal") - ), - # HRESULT GetSessionInstanceIdentifier([out] LPWSTR *pRetVal); - COMMETHOD( - [], - HRESULT, - "GetSessionInstanceIdentifier", - (["out"], POINTER(LPWSTR), "pRetVal"), - ), - # HRESULT GetProcessId([out] DWORD *pRetVal); - COMMETHOD([], HRESULT, "GetProcessId", (["out"], POINTER(DWORD), "pRetVal")), - # HRESULT IsSystemSoundsSession(); - COMMETHOD([], HRESULT, "IsSystemSoundsSession"), - # HRESULT SetDuckingPreference([in] BOOL optOut); - COMMETHOD([], HRESULT, "SetDuckingPreferences", (["in"], BOOL, "optOut")), - ) - - -class IAudioSessionEnumerator(IUnknown): - _iid_ = GUID("{E2F5BB11-0570-40CA-ACDD-3AA01277DEE8}") - _methods_ = ( - # HRESULT GetCount([out] int *SessionCount); - COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(INT), "SessionCount")), - # HRESULT GetSession( - # [in] int SessionCount, - # [out] IAudioSessionControl **Session); - COMMETHOD( - [], - HRESULT, - "GetSession", - (["in"], INT, "SessionCount"), - (["out"], POINTER(POINTER(IAudioSessionControl)), "Session"), - ), - ) - - -class IAudioSessionManager(IUnknown): - _iid_ = GUID("{BFA971F1-4d5e-40bb-935e-967039bfbee4}") - _methods_ = ( - # HRESULT GetAudioSessionControl( - # [in] LPCGUID AudioSessionGuid, - # [in] DWORD StreamFlags, - # [out] IAudioSessionControl **SessionControl); - COMMETHOD( - [], - HRESULT, - "GetAudioSessionControl", - (["in"], POINTER(GUID), "AudioSessionGuid"), - (["in"], DWORD, "StreamFlags"), - (["out"], POINTER(POINTER(IAudioSessionControl)), "SessionControl"), - ), - # HRESULT GetSimpleAudioVolume( - # [in] LPCGUID AudioSessionGuid, - # [in] DWORD CrossProcessSession, - # [out] ISimpleAudioVolume **AudioVolume); - COMMETHOD( - [], - HRESULT, - "GetSimpleAudioVolume", - (["in"], POINTER(GUID), "AudioSessionGuid"), - (["in"], DWORD, "CrossProcessSession"), - (["out"], POINTER(POINTER(ISimpleAudioVolume)), "AudioVolume"), - ), - ) - - -class IAudioSessionNotification(IUnknown): - _iid_ = GUID("{8aad9bb7-39e1-4c62-a3ab-ff6e76dcf9c8}") - _methods_ = ( - # HRESULT OnSessionCreated( - # ['in'] IAudioSessionControl *NewSession - # ); - COMMETHOD( - [], - HRESULT, - "OnSessionCreated", - (["in"], POINTER(IAudioSessionControl), "NewSession"), - ), - ) - - -class IAudioVolumeDuckNotification(IUnknown): - _iid_ = GUID("{C3B284D4-6D39-4359-B3CF-B56DDB3BB39C}") - _methods_ = ( - # HRESULT OnVolumeDuckNotification( - # [in] LPCWSTR sessionID, - # [in] UINT32 countCommunicationSessions); - COMMETHOD( - [], - HRESULT, - "OnVolumeDuckNotification", - (["in"], LPCWSTR, "sessionID"), - (["in"], c_uint32, "countCommunicationSessions"), - ), - # HRESULT OnVolumeUnduckNotification( - # [in] LPCWSTR sessionID); - COMMETHOD( - [], - HRESULT, - "OnVolumeUnduckNotification", - (["in"], LPCWSTR, "sessionID"), - ), - ) - - -class IAudioSessionManager2(IAudioSessionManager): - _iid_ = GUID("{77aa99a0-1bd6-484f-8bc7-2c654c9a9b6f}") - _methods_ = ( - # HRESULT GetSessionEnumerator( - # [out] IAudioSessionEnumerator **SessionList); - COMMETHOD( - [], - HRESULT, - "GetSessionEnumerator", - (["out"], POINTER(POINTER(IAudioSessionEnumerator)), "SessionList"), - ), - # HRESULT RegisterSessionNotification( - # IAudioSessionNotification *SessionNotification); - COMMETHOD( - [], - HRESULT, - "RegisterSessionNotification", - (["in"], POINTER(IAudioSessionNotification), "SessionNotification"), - ), - # HRESULT UnregisterSessionNotification( - # IAudioSessionNotification *SessionNotification); - COMMETHOD( - [], - HRESULT, - "UnregisterSessionNotification", - (["in"], POINTER(IAudioSessionNotification), "SessionNotification"), - ), - # HRESULT RegisterDuckNotification( - # LPCWSTR SessionID, - # IAudioVolumeDuckNotification *duckNotification); - COMMETHOD( - [], - HRESULT, - "RegisterDuckNotification", - (["in"], LPCWSTR, "SessionID"), - (["in"], POINTER(IAudioVolumeDuckNotification), "duckNotification"), - ), - # HRESULT UnregisterDuckNotification( - # IAudioVolumeDuckNotification *duckNotification); - COMMETHOD( - [], - HRESULT, - "UnregisterDuckNotification", - (["in"], POINTER(IAudioVolumeDuckNotification), "duckNotification"), - ), - ) - diff --git a/source/comInterfaces/coreAudio.bak/constants.py b/source/comInterfaces/coreAudio.bak/constants.py deleted file mode 100644 index 9ea4fb91f58..00000000000 --- a/source/comInterfaces/coreAudio.bak/constants.py +++ /dev/null @@ -1,56 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from enum import Enum, IntEnum - -from comtypes import GUID - -IID_Empty = GUID("{00000000-0000-0000-0000-000000000000}") - -CLSID_MMDeviceEnumerator = GUID("{BCDE0395-E52F-467C-8E3D-C4579291692E}") - - -class ERole(Enum): - eConsole = 0 - eMultimedia = 1 - eCommunications = 2 - ERole_enum_count = 3 - - -class EDataFlow(Enum): - eRender = 0 - eCapture = 1 - eAll = 2 - EDataFlow_enum_count = 3 - - -class DEVICE_STATE(Enum): - ACTIVE = 0x00000001 - DISABLED = 0x00000002 - NOTPRESENT = 0x00000004 - UNPLUGGED = 0x00000008 - MASK_ALL = 0x0000000F - - -class AudioDeviceState(Enum): - Active = 0x1 - Disabled = 0x2 - NotPresent = 0x4 - Unplugged = 0x8 - - -class STGM(Enum): - STGM_READ = 0x00000000 - - -class AUDCLNT_SHAREMODE(Enum): - AUDCLNT_SHAREMODE_SHARED = 0x00000001 - AUDCLNT_SHAREMODE_EXCLUSIVE = 0x00000002 - - -class AudioSessionState(IntEnum): - # IntEnum to make instances comparable. - Inactive = 0 - Active = 1 - Expired = 2 diff --git a/source/comInterfaces/coreAudio.bak/endpointvolume/__init__.py b/source/comInterfaces/coreAudio.bak/endpointvolume/__init__.py deleted file mode 100644 index c8d9cc190d0..00000000000 --- a/source/comInterfaces/coreAudio.bak/endpointvolume/__init__.py +++ /dev/null @@ -1,182 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from ctypes import HRESULT, POINTER, c_float -from ctypes.wintypes import BOOL, DWORD, UINT - -from comtypes import COMMETHOD, GUID, IUnknown - -from .depend import PAUDIO_VOLUME_NOTIFICATION_DATA - - -class IAudioEndpointVolumeCallback(IUnknown): - _iid_ = GUID("{b1136c83-b6b5-4add-98a5-a2df8eedf6fa}") - _methods_ = ( - # HRESULT OnNotify( - # [in] PAUDIO_VOLUME_NOTIFICATION_DATA pNotify); - COMMETHOD( - [], - HRESULT, - "OnNotify", - (["in"], PAUDIO_VOLUME_NOTIFICATION_DATA, "pNotify"), - ), - ) - - -class IAudioEndpointVolume(IUnknown): - _iid_ = GUID("{5CDF2C82-841E-4546-9722-0CF74078229A}") - _methods_ = ( - # HRESULT RegisterControlChangeNotify( - # [in] IAudioEndpointVolumeCallback *pNotify); - COMMETHOD( - [], - HRESULT, - "RegisterControlChangeNotify", - (["in"], POINTER(IAudioEndpointVolumeCallback), "pNotify"), - ), - # HRESULT UnregisterControlChangeNotify( - # [in] IAudioEndpointVolumeCallback *pNotify); - COMMETHOD( - [], - HRESULT, - "UnregisterControlChangeNotify", - (["in"], POINTER(IAudioEndpointVolumeCallback), "pNotify"), - ), - # HRESULT GetChannelCount([out] UINT *pnChannelCount); - COMMETHOD( - [], HRESULT, "GetChannelCount", (["out"], POINTER(UINT), "pnChannelCount") - ), - # HRESULT SetMasterVolumeLevel( - # [in] float fLevelDB, [in] LPCGUID pguidEventContext); - COMMETHOD( - [], - HRESULT, - "SetMasterVolumeLevel", - (["in"], c_float, "fLevelDB"), - (["in"], POINTER(GUID), "pguidEventContext"), - ), - # HRESULT SetMasterVolumeLevelScalar( - # [in] float fLevel, [in] LPCGUID pguidEventContext); - COMMETHOD( - [], - HRESULT, - "SetMasterVolumeLevelScalar", - (["in"], c_float, "fLevel"), - (["in"], POINTER(GUID), "pguidEventContext"), - ), - # HRESULT GetMasterVolumeLevel([out] float *pfLevelDB); - COMMETHOD( - [], - HRESULT, - "GetMasterVolumeLevel", - (["out"], POINTER(c_float), "pfLevelDB"), - ), - # HRESULT GetMasterVolumeLevelScalar([out] float *pfLevel); - COMMETHOD( - [], - HRESULT, - "GetMasterVolumeLevelScalar", - (["out"], POINTER(c_float), "pfLevelDB"), - ), - # HRESULT SetChannelVolumeLevel( - # [in] UINT nChannel, - # [in] float fLevelDB, - # [in] LPCGUID pguidEventContext); - COMMETHOD( - [], - HRESULT, - "SetChannelVolumeLevel", - (["in"], UINT, "nChannel"), - (["in"], c_float, "fLevelDB"), - (["in"], POINTER(GUID), "pguidEventContext"), - ), - # HRESULT SetChannelVolumeLevelScalar( - # [in] UINT nChannel, - # [in] float fLevel, - # [in] LPCGUID pguidEventContext); - COMMETHOD( - [], - HRESULT, - "SetChannelVolumeLevelScalar", - (["in"], DWORD, "nChannel"), - (["in"], c_float, "fLevelDB"), - (["in"], POINTER(GUID), "pguidEventContext"), - ), - # HRESULT GetChannelVolumeLevel( - # [in] UINT nChannel, - # [out] float *pfLevelDB); - COMMETHOD( - [], - HRESULT, - "GetChannelVolumeLevel", - (["in"], UINT, "nChannel"), - (["out"], POINTER(c_float), "pfLevelDB"), - ), - # HRESULT GetChannelVolumeLevelScalar( - # [in] UINT nChannel, - # [out] float *pfLevel); - COMMETHOD( - [], - HRESULT, - "GetChannelVolumeLevelScalar", - (["in"], DWORD, "nChannel"), - (["out"], POINTER(c_float), "pfLevelDB"), - ), - # HRESULT SetMute([in] BOOL bMute, [in] LPCGUID pguidEventContext); - COMMETHOD( - [], - HRESULT, - "SetMute", - (["in"], BOOL, "bMute"), - (["in"], POINTER(GUID), "pguidEventContext"), - ), - # HRESULT GetMute([out] BOOL *pbMute); - COMMETHOD([], HRESULT, "GetMute", (["out"], POINTER(BOOL), "pbMute")), - # HRESULT GetVolumeStepInfo( - # [out] UINT *pnStep, - # [out] UINT *pnStepCount); - COMMETHOD( - [], - HRESULT, - "GetVolumeStepInfo", - (["out"], POINTER(DWORD), "pnStep"), - (["out"], POINTER(DWORD), "pnStepCount"), - ), - # HRESULT VolumeStepUp([in] LPCGUID pguidEventContext); - COMMETHOD( - [], HRESULT, "VolumeStepUp", (["in"], POINTER(GUID), "pguidEventContext") - ), - # HRESULT VolumeStepDown([in] LPCGUID pguidEventContext); - COMMETHOD( - [], HRESULT, "VolumeStepDown", (["in"], POINTER(GUID), "pguidEventContext") - ), - # HRESULT QueryHardwareSupport([out] DWORD *pdwHardwareSupportMask); - COMMETHOD( - [], - HRESULT, - "QueryHardwareSupport", - (["out"], POINTER(DWORD), "pdwHardwareSupportMask"), - ), - # HRESULT GetVolumeRange( - # [out] float *pfLevelMinDB, - # [out] float *pfLevelMaxDB, - # [out] float *pfVolumeIncrementDB); - COMMETHOD( - [], - HRESULT, - "GetVolumeRange", - (["out"], POINTER(c_float), "pfMin"), - (["out"], POINTER(c_float), "pfMax"), - (["out"], POINTER(c_float), "pfIncr"), - ), - ) - - -class IAudioMeterInformation(IUnknown): - _iid_ = GUID("{C02216F6-8C67-4B5B-9D00-D008E73E0064}") - _methods_ = ( - # HRESULT GetPeakValue([out] c_float *pfPeak); - COMMETHOD([], HRESULT, "GetPeakValue", (["out"], POINTER(c_float), "pfPeak")), - ) - diff --git a/source/comInterfaces/coreAudio.bak/endpointvolume/depend.py b/source/comInterfaces/coreAudio.bak/endpointvolume/depend.py deleted file mode 100644 index be6bada134d..00000000000 --- a/source/comInterfaces/coreAudio.bak/endpointvolume/depend.py +++ /dev/null @@ -1,22 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from ctypes import POINTER, Structure, c_float -from ctypes.wintypes import BOOL, UINT - -from comtypes import GUID - - -class AUDIO_VOLUME_NOTIFICATION_DATA(Structure): - _fields_ = [ - ("guidEventContext", GUID), - ("bMuted", BOOL), - ("fMasterVolume", c_float), - ("nChannels", UINT), - ("afChannelVolumes", c_float * 8), - ] - - -PAUDIO_VOLUME_NOTIFICATION_DATA = POINTER(AUDIO_VOLUME_NOTIFICATION_DATA) - diff --git a/source/comInterfaces/coreAudio.bak/mmdeviceapi/__init__.py b/source/comInterfaces/coreAudio.bak/mmdeviceapi/__init__.py deleted file mode 100644 index cb4d120fbf5..00000000000 --- a/source/comInterfaces/coreAudio.bak/mmdeviceapi/__init__.py +++ /dev/null @@ -1,186 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from ctypes import HRESULT, POINTER -from ctypes.wintypes import DWORD, LPCWSTR, LPWSTR, UINT - -from comtypes import COMMETHOD, GUID, IUnknown - -from .depend import PROPERTYKEY, IPropertyStore - - -class IMMDevice(IUnknown): - _iid_ = GUID("{D666063F-1587-4E43-81F1-B948E807363F}") - _methods_ = ( - # HRESULT Activate( - # [in] REFIID iid, - # [in] DWORD dwClsCtx, - # [in] PROPVARIANT *pActivationParams, - # [out] void **ppInterface); - COMMETHOD( - [], - HRESULT, - "Activate", - (["in"], POINTER(GUID), "iid"), - (["in"], DWORD, "dwClsCtx"), - (["in"], POINTER(DWORD), "pActivationParams"), - (["out"], POINTER(POINTER(IUnknown)), "ppInterface"), - ), - # HRESULT OpenPropertyStore( - # [in] DWORD stgmAccess, - # [out] IPropertyStore **ppProperties); - COMMETHOD( - [], - HRESULT, - "OpenPropertyStore", - (["in"], DWORD, "stgmAccess"), - (["out"], POINTER(POINTER(IPropertyStore)), "ppProperties"), - ), - # HRESULT GetId([out] LPWSTR *ppstrId); - COMMETHOD([], HRESULT, "GetId", (["out"], POINTER(LPWSTR), "ppstrId")), - # HRESULT GetState([out] DWORD *pdwState); - COMMETHOD([], HRESULT, "GetState", (["out"], POINTER(DWORD), "pdwState")), - ) - - -class IMMDeviceCollection(IUnknown): - _iid_ = GUID("{0BD7A1BE-7A1A-44DB-8397-CC5392387B5E}") - _methods_ = ( - # HRESULT GetCount([out] UINT *pcDevices); - COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(UINT), "pcDevices")), - # HRESULT Item([in] UINT nDevice, [out] IMMDevice **ppDevice); - COMMETHOD( - [], - HRESULT, - "Item", - (["in"], UINT, "nDevice"), - (["out"], POINTER(POINTER(IMMDevice)), "ppDevice"), - ), - ) - - -class IMMNotificationClient(IUnknown): - _case_insensitive_ = True - _iid_ = GUID("{7991EEC9-7E89-4D85-8390-6C703CEC60C0}") - _methods_ = ( - # HRESULT OnDeviceStateChanged( - # [in] LPCWSTR pwstrDeviceId, - # [in] DWORD dwNewState); - COMMETHOD( - [], - HRESULT, - "OnDeviceStateChanged", - (["in"], LPCWSTR, "pwstrDeviceId"), - (["in"], DWORD, "dwNewState"), - ), - # HRESULT OnDeviceAdded( - # [in] LPCWSTR pwstrDeviceId, - COMMETHOD( - [], - HRESULT, - "OnDeviceAdded", - (["in"], LPCWSTR, "pwstrDeviceId"), - ), - # HRESULT OnDeviceRemoved( - # [in] LPCWSTR pwstrDeviceId, - COMMETHOD( - [], - HRESULT, - "OnDeviceRemoved", - (["in"], LPCWSTR, "pwstrDeviceId"), - ), - # HRESULT OnDefaultDeviceChanged( - # [in] EDataFlow flow, - # [in] ERole role, - # [in] LPCWSTR pwstrDefaultDeviceId; - COMMETHOD( - [], - HRESULT, - "OnDefaultDeviceChanged", - (["in"], DWORD, "flow"), - (["in"], DWORD, "role"), - (["in"], LPCWSTR, "pwstrDefaultDeviceId"), - ), - # HRESULT OnPropertyValueChanged( - # [in] LPCWSTR pwstrDeviceId, - # [in] const PROPERTYKEY key); - COMMETHOD( - [], - HRESULT, - "OnPropertyValueChanged", - (["in"], LPCWSTR, "pwstrDeviceId"), - (["in"], PROPERTYKEY, "key"), - ), - ) - - -class IMMDeviceEnumerator(IUnknown): - _iid_ = GUID("{A95664D2-9614-4F35-A746-DE8DB63617E6}") - _methods_ = ( - # HRESULT EnumAudioEndpoints( - # [in] EDataFlow dataFlow, - # [in] DWORD dwStateMask, - # [out] IMMDeviceCollection **ppDevices); - COMMETHOD( - [], - HRESULT, - "EnumAudioEndpoints", - (["in"], DWORD, "dataFlow"), - (["in"], DWORD, "dwStateMask"), - (["out"], POINTER(POINTER(IMMDeviceCollection)), "ppDevices"), - ), - # HRESULT GetDefaultAudioEndpoint( - # [in] EDataFlow dataFlow, - # [in] ERole role, - # [out] IMMDevice **ppDevice); - COMMETHOD( - [], - HRESULT, - "GetDefaultAudioEndpoint", - (["in"], DWORD, "dataFlow"), - (["in"], DWORD, "role"), - (["out"], POINTER(POINTER(IMMDevice)), "ppDevices"), - ), - # HRESULT GetDevice( - # [in] LPCWSTR pwstrId, - # [out] IMMDevice **ppDevice); - COMMETHOD( - [], - HRESULT, - "GetDevice", - (["in"], LPCWSTR, "pwstrId"), - (["out"], POINTER(POINTER(IMMDevice)), "ppDevice"), - ), - # HRESULT RegisterEndpointNotificationCallback( - # [in] IMMNotificationClient *pClient); - COMMETHOD( - [], - HRESULT, - "RegisterEndpointNotificationCallback", - (["in"], POINTER(IMMNotificationClient), "pClient"), - ), - # HRESULT UnregisterEndpointNotificationCallback( - # [in] IMMNotificationClient *pClient); - COMMETHOD( - [], - HRESULT, - "UnregisterEndpointNotificationCallback", - (["in"], POINTER(IMMNotificationClient), "pClient"), - ), - ) - - -class IMMEndpoint(IUnknown): - _iid_ = GUID("{1BE09788-6894-4089-8586-9A2A6C265AC5}") - _methods_ = ( - # HRESULT GetDataFlow( - # [out] EDataFlow *pDataFlow); - COMMETHOD( - [], - HRESULT, - "GetDataFlow", - (["out"], POINTER(DWORD), "pDataFlow"), - ), - ) - diff --git a/source/comInterfaces/coreAudio.bak/mmdeviceapi/depend/__init__.py b/source/comInterfaces/coreAudio.bak/mmdeviceapi/depend/__init__.py deleted file mode 100644 index cb80b879332..00000000000 --- a/source/comInterfaces/coreAudio.bak/mmdeviceapi/depend/__init__.py +++ /dev/null @@ -1,52 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from ctypes import HRESULT, POINTER -from ctypes.wintypes import DWORD - -from comtypes import COMMETHOD, GUID, IUnknown - -from .structures import PROPERTYKEY, PROPVARIANT - - -class IPropertyStore(IUnknown): - _iid_ = GUID("{886d8eeb-8cf2-4446-8d02-cdba1dbdcf99}") - _methods_ = ( - # HRESULT GetCount([out] DWORD *cProps); - COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(DWORD), "cProps")), - # HRESULT GetAt( - # [in] DWORD iProp, - # [out] PROPERTYKEY *pkey); - COMMETHOD( - [], - HRESULT, - "GetAt", - (["in"], DWORD, "iProp"), - (["out"], POINTER(PROPERTYKEY), "pkey"), - ), - # HRESULT GetValue( - # [in] REFPROPERTYKEY key, - # [out] PROPVARIANT *pv); - COMMETHOD( - [], - HRESULT, - "GetValue", - (["in"], POINTER(PROPERTYKEY), "key"), - (["out"], POINTER(PROPVARIANT), "pv"), - ), - # HRESULT SetValue( - # [in] REFPROPERTYKEY key, - # [in] REFPROPVARIANT propvar - # ); - COMMETHOD( - [], - HRESULT, - "SetValue", - (["in"], POINTER(PROPERTYKEY), "key"), - (["in"], POINTER(PROPVARIANT), "propvar"), - ), - # HRESULT Commit(); - COMMETHOD([], HRESULT, "Commit"), - ) - diff --git a/source/comInterfaces/coreAudio.bak/mmdeviceapi/depend/structures.py b/source/comInterfaces/coreAudio.bak/mmdeviceapi/depend/structures.py deleted file mode 100644 index d902dc80d08..00000000000 --- a/source/comInterfaces/coreAudio.bak/mmdeviceapi/depend/structures.py +++ /dev/null @@ -1,59 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from ctypes import Structure, Union, byref, windll -from ctypes.wintypes import DWORD, LONG, LPWSTR, ULARGE_INTEGER, VARIANT_BOOL, WORD - -from comtypes import GUID -from comtypes.automation import VARTYPE, VT_BOOL, VT_CLSID, VT_LPWSTR, VT_UI4 - - -class PROPVARIANT_UNION(Union): - _fields_ = [ - ("lVal", LONG), - ("uhVal", ULARGE_INTEGER), - ("boolVal", VARIANT_BOOL), - ("pwszVal", LPWSTR), - ("puuid", GUID), - ] - - -class PROPVARIANT(Structure): - _fields_ = [ - ("vt", VARTYPE), - ("reserved1", WORD), - ("reserved2", WORD), - ("reserved3", WORD), - ("union", PROPVARIANT_UNION), - ] - - def GetValue(self): - vt = self.vt - if vt == VT_BOOL: - return self.union.boolVal != 0 - elif vt == VT_LPWSTR: - # return Marshal.PtrToStringUni(union.pwszVal) - return self.union.pwszVal - elif vt == VT_UI4: - return self.union.lVal - elif vt == VT_CLSID: - # TODO - # return (Guid)Marshal.PtrToStructure(union.puuid, typeof(Guid)) - return - else: - return "%s:?" % (vt) - - def clear(self): - windll.ole32.PropVariantClear(byref(self)) - - -class PROPERTYKEY(Structure): - _fields_ = [ - ("fmtid", GUID), - ("pid", DWORD), - ] - - def __str__(self): - return "%s %s" % (self.fmtid, self.pid) - diff --git a/source/config/configSpec.py b/source/config/configSpec.py index 7749be6005e..99235cdcd9e 100644 --- a/source/config/configSpec.py +++ b/source/config/configSpec.py @@ -55,7 +55,8 @@ soundVolumeFollowsVoice = boolean(default=false) soundVolume = integer(default=100, min=0, max=100) soundSplitState = integer(default=0) - soundSplitToggleMode= integer(default=0) + # This is JSON-encoded list of integers. + includedSoundSplitModes = string(default="[0,1,3]") # Braille settings [braille] diff --git a/source/core.py b/source/core.py index 901109927ed..d09b8eee07a 100644 --- a/source/core.py +++ b/source/core.py @@ -272,6 +272,7 @@ def resetConfiguration(factoryDefaults=False): import bdDetect import hwIo import tones + import audio log.debug("Terminating vision") vision.terminate() log.debug("Terminating braille") @@ -282,6 +283,8 @@ def resetConfiguration(factoryDefaults=False): speech.terminate() log.debug("terminating tones") tones.terminate() + log.debug("terminating sound split") + audio.terminate() log.debug("Terminating background braille display detection") bdDetect.terminate() log.debug("Terminating background i/o") @@ -309,6 +312,9 @@ def resetConfiguration(factoryDefaults=False): bdDetect.initialize() # Tones tones.initialize() + # Sound split + log.debug("initializing sound split") + audio.initialize() #Speech log.debug("initializing speech") speech.initialize() diff --git a/source/globalCommands.py b/source/globalCommands.py index 7398fb8e14d..99d76153a43 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -66,6 +66,7 @@ from base64 import b16encode import vision from utils.security import objectBelowLockScreenAndWindowsIsLocked +import audio #: Script category for text review commands. @@ -115,7 +116,7 @@ SCRCAT_DOCUMENTFORMATTING = _("Document formatting") #: Script category for audio streaming commands. # Translators: The name of a category of NVDA commands. -SCRCAT_AUDIOSTREAMING = _("Audio streaming") +SCRCAT_AUDIO = _("Audio") class GlobalCommands(ScriptableObject): """Commands that are available at all times, regardless of the current focus. @@ -126,7 +127,7 @@ class GlobalCommands(ScriptableObject): # Translators: Describes the Cycle audio ducking mode command. "Cycles through audio ducking modes which determine when NVDA lowers the volume of other sounds" ), - category=SCRCAT_AUDIOSTREAMING, + category=SCRCAT_AUDIO, gesture="kb:NVDA+shift+d" ) def script_cycleAudioDuckingMode(self,gesture): @@ -4391,11 +4392,10 @@ def script_cycleParagraphStyle(self, gesture: "inputCore.InputGesture") -> None: # Translators: Describes a command. "Cycles through sound split modes", ), - category=SCRCAT_AUDIOSTREAMING, + category=SCRCAT_AUDIO, gesture="kb:NVDA+alt+s", ) def script_cycleSoundSplit(self, gesture: "inputCore.InputGesture") -> None: - import audio audio.toggleSoundSplitState() diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index c9967ea083c..cbc062fad01 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -78,6 +78,7 @@ import time import keyLabels from .dpiScalingHelper import DpiScalingHelperMixinWithoutInit +import json #: The size that settings panel text descriptions should be wrapped at. # Ensure self.scaleSize is used to adjust for OS scaling adjustments. @@ -2709,8 +2710,6 @@ def makeSettings(self, settingsSizer: wx.BoxSizer) -> None: self.bindHelpEvent("SoundVolume", self.soundVolSlider) self.soundVolSlider.SetValue(config.conf["audio"]["soundVolume"]) - self._onSoundVolChange(None) - # Translators: This is a label for the sound split combo box in the Audio Settings dialog. soundSplitLabelText = _("Sound split mode:") self.soundSplitComboBox = sHelper.addLabeledControl( @@ -2722,16 +2721,51 @@ def makeSettings(self, settingsSizer: wx.BoxSizer) -> None: index = config.conf["audio"]["soundSplitState"] self.soundSplitComboBox.SetSelection(index) - # Translators: This is a label for the sound split Toggle Mode combo box in the Audio Settings dialog. - soundSplitToggleModeLabelText = _("NVDA+Alt+S command behavior:") - self.soundSplitToggleModeComboBox = sHelper.addLabeledControl( - soundSplitToggleModeLabelText, - wx.Choice, - choices=[mode.displayString for mode in audio.SoundSplitToggleMode] + self._appendSoundSplitModesList(sHelper) + + self._onSoundVolChange(None) + + def _appendSoundSplitModesList(self, settingsSizerHelper: guiHelper.BoxSizerHelper) -> None: + self._allSoundSplitModes = list(audio.SoundSplitState) + self.soundSplitModesList: nvdaControls.CustomCheckListBox = settingsSizerHelper.addLabeledControl( + # Translators: Label of the list where user can select sound split modes that will be available. + _("&Modes available in the Cycle sound split mode command:"), + nvdaControls.CustomCheckListBox, + choices=[mode.displayString for mode in self._allSoundSplitModes] ) - self.bindHelpEvent("SelectSoundSplitToggleMode", self.soundSplitToggleModeComboBox) - index = config.conf["audio"]["soundSplitToggleMode"] - self.soundSplitToggleModeComboBox.SetSelection(index) + self.bindHelpEvent("customizeSoundSplitModes", self.soundSplitModesList) + includedModes: list[int] = json.loads(config.conf["audio"]["includedSoundSplitModes"]) + self.soundSplitModesList.Checked = [ + mIndex for mIndex in range(len(self._allSoundSplitModes)) if mIndex in includedModes + ] + self.soundSplitModesList.Bind(wx.EVT_CHECKLISTBOX, self._onSoundSplitModesListChange) + self.soundSplitModesList.Select(0) + + def _onSoundSplitModesListChange(self, evt: wx.CommandEvent): + # continue event propagation to custom control event handler + # to guarantee user is notified about checkbox being checked or unchecked + evt.Skip() + if ( + evt.GetInt() == self._allSoundSplitModes.index(audio.SoundSplitState.OFF) + and not self.soundSplitModesList.IsChecked(evt.GetInt()) + ): + if gui.messageBox( + _( + # Translators: Warning shown when 'OFF' sound split mode is disabled in settings. + "You did not choose 'Off' as one of your sound split mode options. " + "Please note that this may result in no speech output at all " + "in case if one of your audio channels is malfunctioning. " + "Are you sure you want to continue?" + ), + # Translators: Title of the warning message. + _("Warning"), + wx.YES | wx.NO | wx.ICON_WARNING, + self, + ) == wx.NO: + self.soundSplitModesList.SetCheckedItems( + list(self.soundSplitModesList.GetCheckedItems()) + + [self._allSoundSplitModes.index(audio.SoundSplitState.OFF)] + ) def onSave(self): if config.conf["speech"]["outputDevice"] != self.deviceList.GetStringSelection(): @@ -2752,9 +2786,11 @@ def onSave(self): index = self.soundSplitComboBox.GetSelection() config.conf["audio"]["soundSplitState"] = index audio.setSoundSplitState(audio.SoundSplitState(index)) - index = self.soundSplitToggleModeComboBox.GetSelection() - config.conf["audio"]["soundSplitToggleMode"] = index - + config.conf["audio"]["includedSoundSplitModes"] = json.dumps([ + mIndex + for mIndex in range(len(self._allSoundSplitModes)) + if mIndex in self.soundSplitModesList.CheckedItems + ]) if audioDucking.isAudioDuckingSupported(): index = self.duckingList.GetSelection() config.conf["audio"]["audioDuckingMode"] = index @@ -2773,7 +2809,22 @@ def _onSoundVolChange(self, event: wx.Event) -> None: and not self.soundVolFollowCheckBox.IsChecked() ) self.soundSplitComboBox.Enable(wasapi) - self.soundSplitToggleModeComboBox.Enable(wasapi) + self.soundSplitModesList.Enable(wasapi) + + def isValid(self) -> bool: + enabledSoundSplitModes = self.soundSplitModesList.CheckedItems + if len(enabledSoundSplitModes) < 1: + log.debugWarning("No sound split modes enabled.") + gui.messageBox( + # Translators: Message shown when no sound split modes are enabled. + _("At least one sound split mode has to be checked."), + # Translators: The title of the message box + _("Error"), + wx.OK | wx.ICON_ERROR, + self, + ) + return False + return super().isValid() class AddonStorePanel(SettingsPanel): diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index a9b8fcf142f..8980f0953c6 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -7,7 +7,7 @@ What's New in NVDA == New Features == - In Windows 11, NVDA will announce alerts from voice typing and suggested actions including the top suggestion when copying data such as phone numbers to the clipboard (Windows 11 2022 Update and later). (#16009, @josephsl) -- Sound split - toggled by NVDA+Alt+S +- Sound split - toggled by NVDA+Alt+S (#12985, @mltony) - diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 915344bb6eb..1f6fc5bd5d7 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -546,6 +546,27 @@ A gesture allows cycling through the various speech modes: If you only need to switch between a limited subset of speech modes, see [Modes available in the Cycle speech mode command #SpeechModesDisabling] for a way to disable unwanted modes. +++ Sound split modes ++[SpeechModes] + +The sound split feature allows users to make use of their stereo output devices, such as headphones and speakers. Sound split makes it possible to have NVDA speech in one channel (e.g. left) and have all other applications play their sounds in the other channel (e.g. right). +By default sound split is disabled, which means that all applications including NVDA will play sounds in both left and right channels. + +A gesture allows cycling through the various sound split modes: +%kc:beginInclude +|| Name | Key | Description | +| Cycle Sound Split Mode | ``NVDA+alt+s`` | Cycles between speech modes. | +%kc:endInclude + +By default this command will cycle between the following modes: +- Disabled sound split: both NVDA and other applications output sounds to both left and right channels. +- NVDA on the left and applications on the right: NVDA will speak in the left channel, while other applications will play sounds in the right channel. +- NVDA on the right and applications on the left: NVDA will speak in the right channel, while other applications will play sounds in the left channel. +- + +There are more advanced sound split modes availabel in NVDA settings; for more information please see [Customizing Sound split modes #customizeSoundSplitModes] section. + +Please note, that sound split doesn't work as a mixer. For example, if an application is playing a stereo sound track while sound split is set to "NVDA on the left and applications on the right", then you will only hear the right channel of the sound track, while the left channel of the sound track will be muted. in order to allow NVDA to use the left channel. + + Navigating with NVDA +[NavigatingWithNVDA] NVDA allows you to explore and navigate the system in several ways, including both normal interaction and review. @@ -850,7 +871,7 @@ Here is a list of available commands: - Article - Grouping - Tab --2 +- Keep in mind that there are two commands for each type of element, for moving forward in the document and backward in the document, and you must assign gestures to both commands in order to be able to quickly navigate in both directions. For example, if you want to use the ``y`` / ``shift+y`` keys to quickly navigate through tabs, you would do the following: @@ -1895,7 +1916,7 @@ This option is not available if you have started NVDA with [WASAPI disabled for ==== Sound split====[SelectSoundSplitMode] Key: ``NVDA+alt+s`` -This option allows you to make NVDA sound output to be directed to either left or right channel and application volume to be directed to the other channel. This could be useful for headphones users. +This option allows you to make NVDA sound output to be directed to either left or right channel and application volume to be directed to the other channel. This could be useful for users of headphones or stereo speakers. - Disabled sound split: both NVDA and other applications output sounds to both left and right channels. - NVDA on the left and applications on the right: NVDA will speak in the left channel, while other applications will play sounds in the right channel. - NVDA on the right and applications on the left: NVDA will speak in the right channel, while other applications will play sounds in the left channel. @@ -1903,6 +1924,17 @@ This option allows you to make NVDA sound output to be directed to either left o This option is not available if you have started NVDA with [WASAPI disabled for audio output #WASAPI] in Advanced Settings. +==== Customizing Sound split modes====[customizeSoundSplitModes] +This checkable list allows selecting which sound split modes are included when cycling between them using ``NVDA+alt+s``. +Modes which are unchecked are excluded. +By default only three modes are included. +- Sound split disabled: both NVDA and applications play sounds in both left and right channels. +- NVDA on the left and all other applications on the right channel. +- NVDA on the right and all other applications on the left channel. +- + +Note that it is necessary to check at least one mode. + +++ Vision +++[VisionSettings] The Vision category in the NVDA Settings dialog allows you to enable, disable and configure [visual aids #Vision]. From a856a875ee2d9201ebce2decea8d98589d45cee6 Mon Sep 17 00:00:00 2001 From: mltony Date: Mon, 22 Jan 2024 16:37:21 -0800 Subject: [PATCH 03/44] Addressing comments by @@CyrilleB79 --- source/gui/settingsDialogs.py | 2 +- user_docs/en/userGuide.t2t | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index cbc062fad01..af91a58566d 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -2733,7 +2733,7 @@ def _appendSoundSplitModesList(self, settingsSizerHelper: guiHelper.BoxSizerHelp nvdaControls.CustomCheckListBox, choices=[mode.displayString for mode in self._allSoundSplitModes] ) - self.bindHelpEvent("customizeSoundSplitModes", self.soundSplitModesList) + self.bindHelpEvent("CustomizeSoundSplitModes", self.soundSplitModesList) includedModes: list[int] = json.loads(config.conf["audio"]["includedSoundSplitModes"]) self.soundSplitModesList.Checked = [ mIndex for mIndex in range(len(self._allSoundSplitModes)) if mIndex in includedModes diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 1f6fc5bd5d7..a7a16f421a9 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -563,7 +563,7 @@ By default this command will cycle between the following modes: - NVDA on the right and applications on the left: NVDA will speak in the right channel, while other applications will play sounds in the left channel. - -There are more advanced sound split modes availabel in NVDA settings; for more information please see [Customizing Sound split modes #customizeSoundSplitModes] section. +There are more advanced sound split modes availabel in NVDA settings; for more information please see [Customizing Sound split modes #CustomizeSoundSplitModes] section. Please note, that sound split doesn't work as a mixer. For example, if an application is playing a stereo sound track while sound split is set to "NVDA on the left and applications on the right", then you will only hear the right channel of the sound track, while the left channel of the sound track will be muted. in order to allow NVDA to use the left channel. @@ -1924,7 +1924,7 @@ This option allows you to make NVDA sound output to be directed to either left o This option is not available if you have started NVDA with [WASAPI disabled for audio output #WASAPI] in Advanced Settings. -==== Customizing Sound split modes====[customizeSoundSplitModes] +==== Customizing Sound split modes====[CustomizeSoundSplitModes] This checkable list allows selecting which sound split modes are included when cycling between them using ``NVDA+alt+s``. Modes which are unchecked are excluded. By default only three modes are included. From 8f8ce120e62707dc82e8a7d590249c2e835e57bd Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Tue, 23 Jan 2024 10:41:04 -0800 Subject: [PATCH 04/44] Update user_docs/en/userGuide.t2t MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Łukasz Golonka --- user_docs/en/userGuide.t2t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index a7a16f421a9..0af6895a880 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -563,7 +563,7 @@ By default this command will cycle between the following modes: - NVDA on the right and applications on the left: NVDA will speak in the right channel, while other applications will play sounds in the left channel. - -There are more advanced sound split modes availabel in NVDA settings; for more information please see [Customizing Sound split modes #CustomizeSoundSplitModes] section. +There are more advanced sound split modes available in NVDA settings; for more information please see [Customizing Sound split modes #CustomizeSoundSplitModes] section. Please note, that sound split doesn't work as a mixer. For example, if an application is playing a stereo sound track while sound split is set to "NVDA on the left and applications on the right", then you will only hear the right channel of the sound track, while the left channel of the sound track will be muted. in order to allow NVDA to use the left channel. From 501737fb7e96846d876d6e58cc7a8c08de8c795e Mon Sep 17 00:00:00 2001 From: mltony Date: Tue, 23 Jan 2024 14:10:59 -0800 Subject: [PATCH 05/44] According to @lukaszgo1 converting json-encoded list into native list in config --- source/audio.py | 5 ++--- source/config/configSpec.py | 3 +-- source/gui/settingsDialogs.py | 7 +++---- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/source/audio.py b/source/audio.py index 0971d0a9868..5d7dd64eb26 100644 --- a/source/audio.py +++ b/source/audio.py @@ -16,7 +16,6 @@ import config from enum import IntEnum, unique import globalVars -import json from logHandler import log import nvwave from typing import Callable @@ -163,7 +162,7 @@ def setSoundSplitState(state: SoundSplitState) -> None: activeCallback = None leftVolume, rightVolume = state.getAppVolume() leftNVDAVolume, rightNVDAVolume = state.getNVDAVolume() - + def volumeSetter(session2: audiopolicy.IAudioSessionControl2) -> None: channelVolume: audioclient.IChannelAudioVolume = session2.QueryInterface(audioclient.IChannelAudioVolume) channelCount = channelVolume.GetChannelCount() @@ -192,7 +191,7 @@ def toggleSoundSplitState() -> None: ui.message(message) return state = SoundSplitState(config.conf['audio']['soundSplitState']) - allowedStates: list[int] = json.loads(config.conf["audio"]["includedSoundSplitModes"]) + allowedStates: list[int] = config.conf["audio"]["includedSoundSplitModes"] try: i = allowedStates.index(state) except ValueError: diff --git a/source/config/configSpec.py b/source/config/configSpec.py index 99235cdcd9e..91a950bf029 100644 --- a/source/config/configSpec.py +++ b/source/config/configSpec.py @@ -55,8 +55,7 @@ soundVolumeFollowsVoice = boolean(default=false) soundVolume = integer(default=100, min=0, max=100) soundSplitState = integer(default=0) - # This is JSON-encoded list of integers. - includedSoundSplitModes = string(default="[0,1,3]") + includedSoundSplitModes =int_list(default=list(0, 1, 3)) # Braille settings [braille] diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index af91a58566d..e5c0907b2ae 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -78,7 +78,6 @@ import time import keyLabels from .dpiScalingHelper import DpiScalingHelperMixinWithoutInit -import json #: The size that settings panel text descriptions should be wrapped at. # Ensure self.scaleSize is used to adjust for OS scaling adjustments. @@ -2734,7 +2733,7 @@ def _appendSoundSplitModesList(self, settingsSizerHelper: guiHelper.BoxSizerHelp choices=[mode.displayString for mode in self._allSoundSplitModes] ) self.bindHelpEvent("CustomizeSoundSplitModes", self.soundSplitModesList) - includedModes: list[int] = json.loads(config.conf["audio"]["includedSoundSplitModes"]) + includedModes: list[int] = config.conf["audio"]["includedSoundSplitModes"] self.soundSplitModesList.Checked = [ mIndex for mIndex in range(len(self._allSoundSplitModes)) if mIndex in includedModes ] @@ -2786,11 +2785,11 @@ def onSave(self): index = self.soundSplitComboBox.GetSelection() config.conf["audio"]["soundSplitState"] = index audio.setSoundSplitState(audio.SoundSplitState(index)) - config.conf["audio"]["includedSoundSplitModes"] = json.dumps([ + config.conf["audio"]["includedSoundSplitModes"] = [ mIndex for mIndex in range(len(self._allSoundSplitModes)) if mIndex in self.soundSplitModesList.CheckedItems - ]) + ] if audioDucking.isAudioDuckingSupported(): index = self.duckingList.GetSelection() config.conf["audio"]["audioDuckingMode"] = index From bfef0fcc733f8d0d428b96b37b6f0b2a9969d60a Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Wed, 24 Jan 2024 10:46:50 -0800 Subject: [PATCH 06/44] Update source/audio.py Co-authored-by: Cyrille Bougot --- source/audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/audio.py b/source/audio.py index 5d7dd64eb26..0fcfcafd814 100644 --- a/source/audio.py +++ b/source/audio.py @@ -58,7 +58,7 @@ def _displayStringLabels(self) -> dict[IntEnum, str]: def getAppVolume(self) -> VolumeTupleT: if self == SoundSplitState.OFF or 'APPS_BOTH' in self.name: return (1.0, 1.0) - elif 'APPS_LEFT' in self.name: + elif self in [SoundSplitState.NVDA_RIGHT_APPS_LEFT, SoundSplitState.NVDA_BOTH_APPS_LEFT]: return (1.0, 0.0) elif 'APPS_RIGHT' in self.name: return (0.0, 1.0) From eff0c6d73b28f53c491be2721ad85fef8a5e9c3b Mon Sep 17 00:00:00 2001 From: mltony Date: Wed, 24 Jan 2024 11:33:24 -0800 Subject: [PATCH 07/44] Addressing comment by @LeonarddeR --- source/audio.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/source/audio.py b/source/audio.py index 0fcfcafd814..afc7c01548a 100644 --- a/source/audio.py +++ b/source/audio.py @@ -56,24 +56,26 @@ def _displayStringLabels(self) -> dict[IntEnum, str]: } def getAppVolume(self) -> VolumeTupleT: - if self == SoundSplitState.OFF or 'APPS_BOTH' in self.name: - return (1.0, 1.0) - elif self in [SoundSplitState.NVDA_RIGHT_APPS_LEFT, SoundSplitState.NVDA_BOTH_APPS_LEFT]: - return (1.0, 0.0) - elif 'APPS_RIGHT' in self.name: - return (0.0, 1.0) - else: - raise RuntimeError + match self: + case SoundSplitState.OFF | SoundSplitState.NVDA_LEFT_APPS_BOTH | SoundSplitState.NVDA_RIGHT_APPS_BOTH: + return (1.0, 1.0) + case SoundSplitState.NVDA_RIGHT_APPS_LEFT | SoundSplitState.NVDA_BOTH_APPS_LEFT: + return (1.0, 0.0) + case SoundSplitState.NVDA_LEFT_APPS_RIGHT | SoundSplitState.NVDA_BOTH_APPS_RIGHT: + return (0.0, 1.0) + case _: + raise RuntimeError def getNVDAVolume(self) -> VolumeTupleT: - if self == SoundSplitState.OFF or 'NVDA_BOTH' in self.name: - return (1.0, 1.0) - elif 'NVDA_LEFT' in self.name: - return (1.0, 0.0) - elif 'NVDA_RIGHT' in self.name: - return (0.0, 1.0) - else: - raise RuntimeError + match self: + case SoundSplitState.OFF | SoundSplitState.NVDA_BOTH_APPS_LEFT | SoundSplitState.NVDA_BOTH_APPS_RIGHT: + return (1.0, 1.0) + case SoundSplitState.NVDA_LEFT_APPS_RIGHT | SoundSplitState.NVDA_LEFT_APPS_BOTH: + return (1.0, 0.0) + case SoundSplitState.NVDA_RIGHT_APPS_LEFT | SoundSplitState.NVDA_RIGHT_APPS_BOTH: + return (0.0, 1.0) + case _: + raise RuntimeError sessionManager: audiopolicy.IAudioSessionManager2 = None From 78343951d23b6a48e406b536adca77c4eba06564 Mon Sep 17 00:00:00 2001 From: mltony Date: Thu, 25 Jan 2024 11:35:58 -0800 Subject: [PATCH 08/44] Addressing comments --- source/audio/__init__.py | 10 ++++++++++ source/{audio.py => audio/soundSplit.py} | 0 source/core.py | 6 +++--- 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 source/audio/__init__.py rename source/{audio.py => audio/soundSplit.py} (100%) diff --git a/source/audio/__init__.py b/source/audio/__init__.py new file mode 100644 index 00000000000..22bf3086ee4 --- /dev/null +++ b/source/audio/__init__.py @@ -0,0 +1,10 @@ +# A part of NonVisual Desktop Access (NVDA) +# Copyright (C) 2015-2021 NV Access Limited +# This file is covered by the GNU General Public License. +# See the file COPYING for more details. + +from .soundSplit import ( + SoundSplitState, + setSoundSplitState, + toggleSoundSplitState +) diff --git a/source/audio.py b/source/audio/soundSplit.py similarity index 100% rename from source/audio.py rename to source/audio/soundSplit.py diff --git a/source/core.py b/source/core.py index d09b8eee07a..92cc14399f8 100644 --- a/source/core.py +++ b/source/core.py @@ -284,7 +284,7 @@ def resetConfiguration(factoryDefaults=False): log.debug("terminating tones") tones.terminate() log.debug("terminating sound split") - audio.terminate() + audio.soundSplit.terminate() log.debug("Terminating background braille display detection") bdDetect.terminate() log.debug("Terminating background i/o") @@ -314,7 +314,7 @@ def resetConfiguration(factoryDefaults=False): tones.initialize() # Sound split log.debug("initializing sound split") - audio.initialize() + audio.soundSplit.initialize() #Speech log.debug("initializing speech") speech.initialize() @@ -605,7 +605,7 @@ def main(): tones.initialize() log.debug("Initializing sound split") import audio - audio.initialize() + audio.soundSplit.initialize() import speechDictHandler log.debug("Speech Dictionary processing") speechDictHandler.initialize() From 7873e960152c93b207860b14468cd6754af5b5be Mon Sep 17 00:00:00 2001 From: mltony Date: Thu, 25 Jan 2024 11:39:52 -0800 Subject: [PATCH 09/44] Addresing comment --- source/audio/soundSplit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/audio/soundSplit.py b/source/audio/soundSplit.py index afc7c01548a..6825c045f9b 100644 --- a/source/audio/soundSplit.py +++ b/source/audio/soundSplit.py @@ -64,7 +64,7 @@ def getAppVolume(self) -> VolumeTupleT: case SoundSplitState.NVDA_LEFT_APPS_RIGHT | SoundSplitState.NVDA_BOTH_APPS_RIGHT: return (0.0, 1.0) case _: - raise RuntimeError + raise RuntimeError(f"{self=}") def getNVDAVolume(self) -> VolumeTupleT: match self: @@ -75,7 +75,7 @@ def getNVDAVolume(self) -> VolumeTupleT: case SoundSplitState.NVDA_RIGHT_APPS_LEFT | SoundSplitState.NVDA_RIGHT_APPS_BOTH: return (0.0, 1.0) case _: - raise RuntimeError + raise RuntimeError(f"{self=}") sessionManager: audiopolicy.IAudioSessionManager2 = None From 1f6d4a48df08751a23ee41797f0046e5a7d99a97 Mon Sep 17 00:00:00 2001 From: mltony Date: Thu, 25 Jan 2024 11:50:42 -0800 Subject: [PATCH 10/44] lint --- source/audio/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/source/audio/__init__.py b/source/audio/__init__.py index 22bf3086ee4..c2d63e29aff 100644 --- a/source/audio/__init__.py +++ b/source/audio/__init__.py @@ -6,5 +6,11 @@ from .soundSplit import ( SoundSplitState, setSoundSplitState, - toggleSoundSplitState + toggleSoundSplitState, ) + +__all__ = [ + "SoundSplitState", + "setSoundSplitState", + "toggleSoundSplitState", +] From 4202ab6c8b548ff05591ee0525616d81623c56ed Mon Sep 17 00:00:00 2001 From: mltony Date: Thu, 25 Jan 2024 17:26:59 -0800 Subject: [PATCH 11/44] Addressing comments --- source/comInterfaces_sconscript | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/comInterfaces_sconscript b/source/comInterfaces_sconscript index 1e7069897e4..3da41330bf7 100755 --- a/source/comInterfaces_sconscript +++ b/source/comInterfaces_sconscript @@ -119,6 +119,8 @@ for k,v in COM_INTERFACES.items(): # except for things starting with __ (e.g. __init__.py), and files tracked by Git env.Clean( Dir('comInterfaces'), - Glob('comInterfaces/[!_]*', exclude=['comInterfaces/readme.md', 'comInterfaces/UIAutomationClient.py']) - + Glob('comInterfaces/_[!_]*', exclude='comInterfaces/_944DE083_8FB8_45CF_BCB7_C477ACB2F897_0_1_0.py') + Glob( + 'comInterfaces/[!_]*', + exclude=['comInterfaces/readme.md', 'comInterfaces/UIAutomationClient.py', r'comInterfaces/coreAudio\**'] + ) + Glob('comInterfaces/_[!_]*', exclude='comInterfaces/_944DE083_8FB8_45CF_BCB7_C477ACB2F897_0_1_0.py') ) From 68e5d8dd66824baf92c769e2d4044ad16a6d2745 Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Fri, 26 Jan 2024 11:07:59 -0800 Subject: [PATCH 12/44] Update source/audio/soundSplit.py Co-authored-by: WangFeng Huang <1398969445@qq.com> --- source/audio/soundSplit.py | 1 - 1 file changed, 1 deletion(-) diff --git a/source/audio/soundSplit.py b/source/audio/soundSplit.py index 6825c045f9b..77d6cd10c12 100644 --- a/source/audio/soundSplit.py +++ b/source/audio/soundSplit.py @@ -54,7 +54,6 @@ def _displayStringLabels(self) -> dict[IntEnum, str]: # Translators: Sound split state SoundSplitState.NVDA_BOTH_APPS_RIGHT: _("NVDA in both channels and applications on the right"), } - def getAppVolume(self) -> VolumeTupleT: match self: case SoundSplitState.OFF | SoundSplitState.NVDA_LEFT_APPS_BOTH | SoundSplitState.NVDA_RIGHT_APPS_BOTH: From 74f525ca549a1e591882bc0d6ea51429a8b28a5d Mon Sep 17 00:00:00 2001 From: mltony Date: Fri, 26 Jan 2024 11:12:19 -0800 Subject: [PATCH 13/44] Addressing comment --- source/config/configSpec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/config/configSpec.py b/source/config/configSpec.py index 91a950bf029..4209cc0230f 100644 --- a/source/config/configSpec.py +++ b/source/config/configSpec.py @@ -55,7 +55,7 @@ soundVolumeFollowsVoice = boolean(default=false) soundVolume = integer(default=100, min=0, max=100) soundSplitState = integer(default=0) - includedSoundSplitModes =int_list(default=list(0, 1, 3)) + includedSoundSplitModes =int_list(default=list(0, 1, 2)) # Braille settings [braille] From f2d57019b4964fa74ce0e4959461abac4942f357 Mon Sep 17 00:00:00 2001 From: mltony Date: Fri, 26 Jan 2024 12:04:53 -0800 Subject: [PATCH 14/44] lint --- source/audio/soundSplit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/audio/soundSplit.py b/source/audio/soundSplit.py index 77d6cd10c12..a729bb5502c 100644 --- a/source/audio/soundSplit.py +++ b/source/audio/soundSplit.py @@ -54,6 +54,7 @@ def _displayStringLabels(self) -> dict[IntEnum, str]: # Translators: Sound split state SoundSplitState.NVDA_BOTH_APPS_RIGHT: _("NVDA in both channels and applications on the right"), } + def getAppVolume(self) -> VolumeTupleT: match self: case SoundSplitState.OFF | SoundSplitState.NVDA_LEFT_APPS_BOTH | SoundSplitState.NVDA_RIGHT_APPS_BOTH: From 1d4ffb37333415e45869f11658e2517589ddae55 Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:40:40 -0800 Subject: [PATCH 15/44] Update source/audio/soundSplit.py Co-authored-by: Cyrille Bougot --- source/audio/soundSplit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/audio/soundSplit.py b/source/audio/soundSplit.py index a729bb5502c..3f3f6e452ef 100644 --- a/source/audio/soundSplit.py +++ b/source/audio/soundSplit.py @@ -187,8 +187,8 @@ def toggleSoundSplitState() -> None: if not nvwave.usingWasapiWavePlayer(): message = _( # Translators: error message when wasapi is turned off. - "Sound split is only available in wasapi mode. " - "Please enable wasapi on the Advanced panel in NVDA Settings." + "Sound split cannot be used. " + "Please enable WASAPI in the Advanced category in NVDA Settings to use it." ) ui.message(message) return From 62f94158ad2b295bebacfe3c2ce526a30095be6b Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:46:18 -0800 Subject: [PATCH 16/44] Update source/audio/soundSplit.py Co-authored-by: Cyrille Bougot --- source/audio/soundSplit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/audio/soundSplit.py b/source/audio/soundSplit.py index 3f3f6e452ef..11719980538 100644 --- a/source/audio/soundSplit.py +++ b/source/audio/soundSplit.py @@ -40,7 +40,7 @@ class SoundSplitState(DisplayStringIntEnum): def _displayStringLabels(self) -> dict[IntEnum, str]: return { # Translators: Sound split state - SoundSplitState.OFF: _("Disabled sound split"), + SoundSplitState.OFF: pgettext("SoundSplit", "Disabled"), # Translators: Sound split state SoundSplitState.NVDA_LEFT_APPS_RIGHT: _("NVDA on the left and applications on the right"), # Translators: Sound split state From 64aac396b295438c58ef64a961631bc7f9188ab5 Mon Sep 17 00:00:00 2001 From: mltony Date: Thu, 1 Feb 2024 10:46:55 -0800 Subject: [PATCH 17/44] Update doc --- user_docs/en/changes.t2t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index 61ee3e6744a..70ca1d0a772 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -7,7 +7,7 @@ What's New in NVDA == New Features == - In Windows 11, NVDA will announce alerts from voice typing and suggested actions including the top suggestion when copying data such as phone numbers to the clipboard (Windows 11 2022 Update and later). (#16009, @josephsl) -- Sound split - toggled by NVDA+Alt+S (#12985, @mltony) +- Sound split: allows to split NVDA sounds in one channel (e.g. left) while sounds from all other applications in the other channel (e.g. right) - toggled by NVDA+Alt+S (#12985, @mltony) - From 8bddb3bc0c17d17423bb1d16bc185577e15f6614 Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Wed, 7 Feb 2024 16:46:11 -0800 Subject: [PATCH 18/44] Update user_docs/en/changes.t2t Co-authored-by: Sean Budd --- user_docs/en/changes.t2t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index 70ca1d0a772..fed63faa85b 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -7,7 +7,7 @@ What's New in NVDA == New Features == - In Windows 11, NVDA will announce alerts from voice typing and suggested actions including the top suggestion when copying data such as phone numbers to the clipboard (Windows 11 2022 Update and later). (#16009, @josephsl) -- Sound split: allows to split NVDA sounds in one channel (e.g. left) while sounds from all other applications in the other channel (e.g. right) - toggled by NVDA+Alt+S (#12985, @mltony) +- Sound split: allows to split NVDA sounds in one channel (e.g. left) while sounds from all other applications in the other channel (e.g. right) - toggled by ``NVDA+alt+s`` (#12985, @mltony) - From 007e108de741ebd07b49ae6fd90fb44307dcfd70 Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Wed, 7 Feb 2024 16:46:55 -0800 Subject: [PATCH 19/44] Update source/audio/__init__.py Co-authored-by: Sean Budd --- source/audio/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/audio/__init__.py b/source/audio/__init__.py index c2d63e29aff..47d07112d94 100644 --- a/source/audio/__init__.py +++ b/source/audio/__init__.py @@ -1,5 +1,5 @@ # A part of NonVisual Desktop Access (NVDA) -# Copyright (C) 2015-2021 NV Access Limited +# Copyright (C) 2024 NV Access Limited # This file is covered by the GNU General Public License. # See the file COPYING for more details. From 6d6043764ed88bfdf9d8478a5c344be6837215fc Mon Sep 17 00:00:00 2001 From: mltony Date: Sat, 10 Feb 2024 17:53:03 -0800 Subject: [PATCH 20/44] Switching to pycaw --- requirements.txt | 3 + source/audio/soundSplit.py | 130 +++----- source/comInterfaces/coreAudio/__init__.py | 0 .../coreAudio/audioclient/__init__.py | 155 --------- .../coreAudio/audioclient/depend.py | 22 -- .../coreAudio/audiopolicy/__init__.py | 301 ------------------ source/comInterfaces/coreAudio/constants.py | 60 ---- .../coreAudio/endpointvolume/__init__.py | 184 ----------- .../coreAudio/endpointvolume/depend.py | 24 -- .../coreAudio/mmdeviceapi/__init__.py | 188 ----------- .../coreAudio/mmdeviceapi/depend/__init__.py | 54 ---- .../mmdeviceapi/depend/structures.py | 61 ---- 12 files changed, 45 insertions(+), 1137 deletions(-) delete mode 100644 source/comInterfaces/coreAudio/__init__.py delete mode 100644 source/comInterfaces/coreAudio/audioclient/__init__.py delete mode 100644 source/comInterfaces/coreAudio/audioclient/depend.py delete mode 100644 source/comInterfaces/coreAudio/audiopolicy/__init__.py delete mode 100644 source/comInterfaces/coreAudio/constants.py delete mode 100644 source/comInterfaces/coreAudio/endpointvolume/__init__.py delete mode 100644 source/comInterfaces/coreAudio/endpointvolume/depend.py delete mode 100644 source/comInterfaces/coreAudio/mmdeviceapi/__init__.py delete mode 100644 source/comInterfaces/coreAudio/mmdeviceapi/depend/__init__.py delete mode 100644 source/comInterfaces/coreAudio/mmdeviceapi/depend/structures.py diff --git a/requirements.txt b/requirements.txt index 809fb55cb22..925bc309f0b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,9 @@ fast_diff_match_patch==2.0.1 # typing_extensions are required for specifying default value for `TypeVar`, which is not yet possible with any released version of Python (see PEP 696) typing-extensions==4.9.0 +# pycaw is a Core Audio Windows Library used for sound split +git+https://github.com/AndreMiras/pycaw@ec2070d679d6fb268e99d2017127983bf9ab0d61#egg=pycaw + # Packaging NVDA git+https://github.com/py2exe/py2exe@4e7b2b2c60face592e67cb1bc935172a20fa371d#egg=py2exe diff --git a/source/audio/soundSplit.py b/source/audio/soundSplit.py index 11719980538..70ad344a35f 100644 --- a/source/audio/soundSplit.py +++ b/source/audio/soundSplit.py @@ -4,20 +4,14 @@ # See the file COPYING for more details. import atexit -from comInterfaces.coreAudio.constants import ( - CLSID_MMDeviceEnumerator, - EDataFlow, - ERole, -) -import comInterfaces.coreAudio.audioclient as audioclient -import comInterfaces.coreAudio.audiopolicy as audiopolicy -import comInterfaces.coreAudio.mmdeviceapi as mmdeviceapi -import comtypes import config from enum import IntEnum, unique import globalVars from logHandler import log import nvwave +from pycaw.api.audiopolicy import IAudioSessionManager2 +from pycaw.callbacks import AudioSessionNotification +from pycaw.utils import AudioSession, AudioUtilities from typing import Callable import ui from utils.displayString import DisplayStringIntEnum @@ -78,109 +72,69 @@ def getNVDAVolume(self) -> VolumeTupleT: raise RuntimeError(f"{self=}") -sessionManager: audiopolicy.IAudioSessionManager2 = None -activeCallback: comtypes.COMObject | None = None +audioSessionManager: IAudioSessionManager2|None = None +activeCallback: AudioSessionNotification = None def initialize() -> None: - global sessionManager - try: - sessionManager = getSessionManager() - except _ctypes.COMError as e: - log.error("Could not initialize audio session manager! ", e) - return - if sessionManager is None: - log.error("Could not initialize audio session manager! ") - return if nvwave.usingWasapiWavePlayer(): + global audioSessionManager + audioSessionManager = AudioUtilities.GetAudioSessionManager() state = SoundSplitState(config.conf['audio']['soundSplitState']) - global activeCallback - activeCallback = setSoundSplitState(state) + setSoundSplitState(state) @atexit.register def terminate(): - global activeCallback if nvwave.usingWasapiWavePlayer(): setSoundSplitState(SoundSplitState.OFF) - if activeCallback is not None: - unregisterCallback(activeCallback) - activeCallback = None - - -def getDefaultAudioDevice(kind: EDataFlow = EDataFlow.eRender) -> mmdeviceapi.IMMDevice | None: - deviceEnumerator = comtypes.CoCreateInstance( - CLSID_MMDeviceEnumerator, - mmdeviceapi.IMMDeviceEnumerator, - comtypes.CLSCTX_INPROC_SERVER, - ) - device = deviceEnumerator.GetDefaultAudioEndpoint( - kind.value, - ERole.eMultimedia.value, - ) - return device - - -def getSessionManager() -> audiopolicy.IAudioSessionManager2: - audioDevice = getDefaultAudioDevice() - if audioDevice is None: - raise RuntimeError("No default output audio device found!") - tmp = audioDevice.Activate(audiopolicy.IAudioSessionManager2._iid_, comtypes.CLSCTX_ALL, None) - sessionManager: audiopolicy.IAudioSessionManager2 = tmp.QueryInterface(audiopolicy.IAudioSessionManager2) - return sessionManager + unregisterCallback() def applyToAllAudioSessions( - func: Callable[[audiopolicy.IAudioSessionControl2], None], + callback: AudioSessionNotification, applyToFuture: bool = True, -) -> comtypes.COMObject | None: - sessionEnumerator: audiopolicy.IAudioSessionEnumerator = sessionManager.GetSessionEnumerator() - for i in range(sessionEnumerator.GetCount()): - session: audiopolicy.IAudioSessionControl = sessionEnumerator.GetSession(i) - session2: audiopolicy.IAudioSessionControl2 = session.QueryInterface(audiopolicy.IAudioSessionControl2) - func(session2) +) -> None: + unregisterCallback() if applyToFuture: - class AudioSessionNotification(comtypes.COMObject): - _com_interfaces_ = (audiopolicy.IAudioSessionNotification,) - - def OnSessionCreated(self, session: audiopolicy.IAudioSessionControl): - session2 = session.QueryInterface(audiopolicy.IAudioSessionControl2) - func(session2) - callback = AudioSessionNotification() - sessionManager.RegisterSessionNotification(callback) - return callback - else: - return None - - -def unregisterCallback(callback: comtypes.COMObject) -> None: - sessionManager .UnregisterSessionNotification(callback) + audioSessionManager.RegisterSessionNotification(callback) + # The following call is required to make callback to work: + audioSessionManager.GetSessionEnumerator() + global activeCallback + activeCallback = callback + sessions: list[AudioSession] = AudioUtilities.GetAllSessions() + for session in sessions: + callback.on_session_created(session) -def setSoundSplitState(state: SoundSplitState) -> None: +def unregisterCallback() -> None: global activeCallback if activeCallback is not None: - unregisterCallback(activeCallback) + audioSessionManager.UnregisterSessionNotification(activeCallback) activeCallback = None + + +def setSoundSplitState(state: SoundSplitState) -> None: leftVolume, rightVolume = state.getAppVolume() leftNVDAVolume, rightNVDAVolume = state.getNVDAVolume() - def volumeSetter(session2: audiopolicy.IAudioSessionControl2) -> None: - channelVolume: audioclient.IChannelAudioVolume = session2.QueryInterface(audioclient.IChannelAudioVolume) - channelCount = channelVolume.GetChannelCount() - if channelCount != 2: - pid = session2.GetProcessId() - log.warning(f"Audio session for pid {pid} has {channelCount} channels instead of 2 - cannot set volume!") - return - pid: int = session2.GetProcessId() - if pid != globalVars.appPid: - channelVolume.SetChannelVolume(0, leftVolume, None) - channelVolume.SetChannelVolume(1, rightVolume, None) - else: - channelVolume.SetChannelVolume(0, leftNVDAVolume, None) - channelVolume.SetChannelVolume(1, rightNVDAVolume, None) - - activeCallback = applyToAllAudioSessions(volumeSetter) + class VolumeSetter(AudioSessionNotification): + def on_session_created(self, new_session: AudioSession): + pid = new_session.ProcessId + channelVolume = new_session.channelAudioVolume() + channelCount = channelVolume.GetChannelCount() + if channelCount != 2: + log.warning(f"Audio session for pid {pid} has {channelCount} channels instead of 2 - cannot set volume!") + return + if pid != globalVars.appPid: + channelVolume.SetChannelVolume(0, leftVolume, None) + channelVolume.SetChannelVolume(1, rightVolume, None) + else: + channelVolume.SetChannelVolume(0, leftNVDAVolume, None) + channelVolume.SetChannelVolume(1, rightNVDAVolume, None) + + volumeSetter = VolumeSetter() + applyToAllAudioSessions(volumeSetter) def toggleSoundSplitState() -> None: diff --git a/source/comInterfaces/coreAudio/__init__.py b/source/comInterfaces/coreAudio/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/source/comInterfaces/coreAudio/audioclient/__init__.py b/source/comInterfaces/coreAudio/audioclient/__init__.py deleted file mode 100644 index f58a7da1e60..00000000000 --- a/source/comInterfaces/coreAudio/audioclient/__init__.py +++ /dev/null @@ -1,155 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from ctypes import HRESULT, POINTER, c_float -from ctypes import c_longlong as REFERENCE_TIME -from ctypes import c_uint32 as UINT32 -from ctypes.wintypes import BOOL, DWORD, HANDLE, UINT - -from comtypes import COMMETHOD, GUID, IUnknown - -from .depend import WAVEFORMATEX - - -class ISimpleAudioVolume(IUnknown): - _iid_ = GUID("{87CE5498-68D6-44E5-9215-6DA47EF883D8}") - _methods_ = ( - # HRESULT SetMasterVolume( - # [in] float fLevel, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "SetMasterVolume", - (["in"], c_float, "fLevel"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT GetMasterVolume([out] float *pfLevel); - COMMETHOD( - [], HRESULT, "GetMasterVolume", (["out"], POINTER(c_float), "pfLevel") - ), - # HRESULT SetMute( - # [in] BOOL bMute, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "SetMute", - (["in"], BOOL, "bMute"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT GetMute([out] BOOL *pbMute); - COMMETHOD([], HRESULT, "GetMute", (["out"], POINTER(BOOL), "pbMute")), - ) - - -class IAudioClient(IUnknown): - _iid_ = GUID("{1cb9ad4c-dbfa-4c32-b178-c2f568a703b2}") - _methods_ = ( - # HRESULT Initialize( - # [in] AUDCLNT_SHAREMODE ShareMode, - # [in] DWORD StreamFlags, - # [in] REFERENCE_TIME hnsBufferDuration, - # [in] REFERENCE_TIME hnsPeriodicity, - # [in] const WAVEFORMATEX *pFormat, - # [in] LPCGUID AudioSessionGuid); - COMMETHOD( - [], - HRESULT, - "Initialize", - (["in"], DWORD, "ShareMode"), - (["in"], DWORD, "StreamFlags"), - (["in"], REFERENCE_TIME, "hnsBufferDuration"), - (["in"], REFERENCE_TIME, "hnsPeriodicity"), - (["in"], POINTER(WAVEFORMATEX), "pFormat"), - (["in"], POINTER(GUID), "AudioSessionGuid"), - ), - # HRESULT GetBufferSize( - # [out] UINT32 *pNumBufferFrames); - COMMETHOD( - [], HRESULT, "GetBufferSize", (["out"], POINTER(UINT32), "pNumBufferFrames") - ), - # HRESULT GetStreamLatency( - # [out] REFERENCE_TIME *phnsLatency); - COMMETHOD( - [], - HRESULT, - "GetStreamLatency", - (["out"], POINTER(REFERENCE_TIME), "phnsLatency"), - ), - # HRESULT GetCurrentPadding( - # [out] UINT32 *pNumPaddingFrames); - COMMETHOD( - [], - HRESULT, - "GetCurrentPadding", - (["out"], POINTER(UINT32), "pNumPaddingFrames"), - ), - # HRESULT IsFormatSupported( - # [in] AUDCLNT_SHAREMODE ShareMode, - # [in] const WAVEFORMATEX *pFormat, - # [out,unique] WAVEFORMATEX **ppClosestMatch); - COMMETHOD( - [], - HRESULT, - "IsFormatSupported", - (["in"], DWORD, "ShareMode"), - (["in"], POINTER(WAVEFORMATEX), "pFormat"), - (["out"], POINTER(POINTER(WAVEFORMATEX)), "ppClosestMatch"), - ), - # HRESULT GetMixFormat( - # [out] WAVEFORMATEX **ppDeviceFormat - # ); - COMMETHOD( - [], - HRESULT, - "GetMixFormat", - (["out"], POINTER(POINTER(WAVEFORMATEX)), "ppDeviceFormat"), - ), - # HRESULT GetDevicePeriod( - # [out] REFERENCE_TIME *phnsDefaultDevicePeriod, - # [out] REFERENCE_TIME *phnsMinimumDevicePeriod); - COMMETHOD( - [], - HRESULT, - "GetDevicePeriod", - (["out"], POINTER(REFERENCE_TIME), "phnsDefaultDevicePeriod"), - (["out"], POINTER(REFERENCE_TIME), "phnsMinimumDevicePeriod"), - ), - # HRESULT Start(void); - COMMETHOD([], HRESULT, "Start"), - # HRESULT Stop(void); - COMMETHOD([], HRESULT, "Stop"), - # HRESULT Reset(void); - COMMETHOD([], HRESULT, "Reset"), - # HRESULT SetEventHandle([in] HANDLE eventHandle); - COMMETHOD( - [], - HRESULT, - "SetEventHandle", - (["in"], HANDLE, "eventHandle"), - ), - # HRESULT GetService( - # [in] REFIID riid, - # [out] void **ppv); - COMMETHOD( - [], - HRESULT, - "GetService", - (["in"], POINTER(GUID), "iid"), - (["out"], POINTER(POINTER(IUnknown)), "ppv"), - ), - ) - - -class IChannelAudioVolume (IUnknown): - _iid_ = GUID('{1c158861-b533-4b30-b1cf-e853e51c59b8}') - _methods_ = ( - COMMETHOD([], HRESULT, 'GetChannelCount', - (['out'], POINTER(UINT), 'pnChannelCount')), - COMMETHOD([], HRESULT, 'SetChannelVolume', - (['in'], UINT, 'dwIndex'), - (['in'], c_float, 'fLevel'), - (['in'], POINTER(GUID), 'EventContext')), - ) diff --git a/source/comInterfaces/coreAudio/audioclient/depend.py b/source/comInterfaces/coreAudio/audioclient/depend.py deleted file mode 100644 index 581b0279e4c..00000000000 --- a/source/comInterfaces/coreAudio/audioclient/depend.py +++ /dev/null @@ -1,22 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from ctypes import Structure -from ctypes.wintypes import WORD - - -class WAVEFORMATEX(Structure): - _fields_ = [ - ("wFormatTag", WORD), - ("nChannels", WORD), - ("nSamplesPerSec", WORD), - ("nAvgBytesPerSec", WORD), - ("nBlockAlign", WORD), - ("wBitsPerSample", WORD), - ("cbSize", WORD), - ] - - - - diff --git a/source/comInterfaces/coreAudio/audiopolicy/__init__.py b/source/comInterfaces/coreAudio/audiopolicy/__init__.py deleted file mode 100644 index 46493574219..00000000000 --- a/source/comInterfaces/coreAudio/audiopolicy/__init__.py +++ /dev/null @@ -1,301 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from ctypes import HRESULT, POINTER, c_float, c_uint32 -from ctypes.wintypes import BOOL, DWORD, INT, LPCWSTR, LPWSTR - -from comtypes import COMMETHOD, GUID, IUnknown - -from ..audioclient import ISimpleAudioVolume - - -class IAudioSessionEvents(IUnknown): - _iid_ = GUID("{073d618c-490a-4f9f-9d18-7bec6fc21121}") - _methods_ = ( - # HRESULT OnDisplayNameChanged( - # [in] LPCWSTR NewDisplayName, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "OnDisplayNameChanged", - (["in"], LPCWSTR, "NewDisplayName"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT OnIconPathChanged( - # [in] LPCWSTR NewIconPath, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "OnIconPathChanged", - (["in"], LPCWSTR, "NewIconPath"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT OnSimpleVolumeChanged( - # [in] float NewVolume, - # [in] BOOL NewMute, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "OnSimpleVolumeChanged", - (["in"], c_float, "NewVolume"), - (["in"], BOOL, "NewMute"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT OnChannelVolumeChanged( - # [in] DWORD ChannelCount, - # [in] float [] NewChannelVolumeArray, - # [in] DWORD ChangedChannel, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "OnChannelVolumeChanged", - (["in"], DWORD, "ChannelCount"), - (["in"], (c_float * 8), "NewChannelVolumeArray"), - (["in"], DWORD, "ChangedChannel"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT OnGroupingParamChanged( - # [in] LPCGUID NewGroupingParam, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "OnGroupingParamChanged", - (["in"], POINTER(GUID), "NewGroupingParam"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT OnStateChanged( - # AudioSessionState NewState); - COMMETHOD([], HRESULT, "OnStateChanged", (["in"], DWORD, "NewState")), - # HRESULT OnSessionDisconnected( - # [in] AudioSessionDisconnectReason DisconnectReason); - COMMETHOD( - [], HRESULT, "OnSessionDisconnected", (["in"], DWORD, "DisconnectReason") - ), - ) - - -class IAudioSessionControl(IUnknown): - _iid_ = GUID("{F4B1A599-7266-4319-A8CA-E70ACB11E8CD}") - _methods_ = ( - # HRESULT GetState ([out] AudioSessionState *pRetVal); - COMMETHOD([], HRESULT, "GetState", (["out"], POINTER(DWORD), "pRetVal")), - # HRESULT GetDisplayName([out] LPWSTR *pRetVal); - COMMETHOD([], HRESULT, "GetDisplayName", (["out"], POINTER(LPWSTR), "pRetVal")), - # HRESULT SetDisplayName( - # [in] LPCWSTR Value, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "SetDisplayName", - (["in"], LPCWSTR, "Value"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT GetIconPath([out] LPWSTR *pRetVal); - COMMETHOD([], HRESULT, "GetIconPath", (["out"], POINTER(LPWSTR), "pRetVal")), - # HRESULT SetIconPath( - # [in] LPCWSTR Value, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "SetIconPath", - (["in"], LPCWSTR, "Value"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT GetGroupingParam([out] GUID *pRetVal); - COMMETHOD([], HRESULT, "GetGroupingParam", (["out"], POINTER(GUID), "pRetVal")), - # HRESULT SetGroupingParam( - # [in] LPCGUID Grouping, - # [in] LPCGUID EventContext); - COMMETHOD( - [], - HRESULT, - "SetGroupingParam", - (["in"], POINTER(GUID), "Grouping"), - (["in"], POINTER(GUID), "EventContext"), - ), - # HRESULT RegisterAudioSessionNotification( - # [in] IAudioSessionEvents *NewNotifications); - COMMETHOD( - [], - HRESULT, - "RegisterAudioSessionNotification", - (["in"], POINTER(IAudioSessionEvents), "NewNotifications"), - ), - # HRESULT UnregisterAudioSessionNotification( - # [in] IAudioSessionEvents *NewNotifications); - COMMETHOD( - [], - HRESULT, - "UnregisterAudioSessionNotification", - (["in"], POINTER(IAudioSessionEvents), "NewNotifications"), - ), - ) - - -class IAudioSessionControl2(IAudioSessionControl): - _iid_ = GUID("{BFB7FF88-7239-4FC9-8FA2-07C950BE9C6D}") - _methods_ = ( - # HRESULT GetSessionIdentifier([out] LPWSTR *pRetVal); - COMMETHOD( - [], HRESULT, "GetSessionIdentifier", (["out"], POINTER(LPWSTR), "pRetVal") - ), - # HRESULT GetSessionInstanceIdentifier([out] LPWSTR *pRetVal); - COMMETHOD( - [], - HRESULT, - "GetSessionInstanceIdentifier", - (["out"], POINTER(LPWSTR), "pRetVal"), - ), - # HRESULT GetProcessId([out] DWORD *pRetVal); - COMMETHOD([], HRESULT, "GetProcessId", (["out"], POINTER(DWORD), "pRetVal")), - # HRESULT IsSystemSoundsSession(); - COMMETHOD([], HRESULT, "IsSystemSoundsSession"), - # HRESULT SetDuckingPreference([in] BOOL optOut); - COMMETHOD([], HRESULT, "SetDuckingPreferences", (["in"], BOOL, "optOut")), - ) - - -class IAudioSessionEnumerator(IUnknown): - _iid_ = GUID("{E2F5BB11-0570-40CA-ACDD-3AA01277DEE8}") - _methods_ = ( - # HRESULT GetCount([out] int *SessionCount); - COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(INT), "SessionCount")), - # HRESULT GetSession( - # [in] int SessionCount, - # [out] IAudioSessionControl **Session); - COMMETHOD( - [], - HRESULT, - "GetSession", - (["in"], INT, "SessionCount"), - (["out"], POINTER(POINTER(IAudioSessionControl)), "Session"), - ), - ) - - -class IAudioSessionManager(IUnknown): - _iid_ = GUID("{BFA971F1-4d5e-40bb-935e-967039bfbee4}") - _methods_ = ( - # HRESULT GetAudioSessionControl( - # [in] LPCGUID AudioSessionGuid, - # [in] DWORD StreamFlags, - # [out] IAudioSessionControl **SessionControl); - COMMETHOD( - [], - HRESULT, - "GetAudioSessionControl", - (["in"], POINTER(GUID), "AudioSessionGuid"), - (["in"], DWORD, "StreamFlags"), - (["out"], POINTER(POINTER(IAudioSessionControl)), "SessionControl"), - ), - # HRESULT GetSimpleAudioVolume( - # [in] LPCGUID AudioSessionGuid, - # [in] DWORD CrossProcessSession, - # [out] ISimpleAudioVolume **AudioVolume); - COMMETHOD( - [], - HRESULT, - "GetSimpleAudioVolume", - (["in"], POINTER(GUID), "AudioSessionGuid"), - (["in"], DWORD, "CrossProcessSession"), - (["out"], POINTER(POINTER(ISimpleAudioVolume)), "AudioVolume"), - ), - ) - - -class IAudioSessionNotification(IUnknown): - _iid_ = GUID("{8aad9bb7-39e1-4c62-a3ab-ff6e76dcf9c8}") - _methods_ = ( - # HRESULT OnSessionCreated( - # ['in'] IAudioSessionControl *NewSession - # ); - COMMETHOD( - [], - HRESULT, - "OnSessionCreated", - (["in"], POINTER(IAudioSessionControl), "NewSession"), - ), - ) - - -class IAudioVolumeDuckNotification(IUnknown): - _iid_ = GUID("{C3B284D4-6D39-4359-B3CF-B56DDB3BB39C}") - _methods_ = ( - # HRESULT OnVolumeDuckNotification( - # [in] LPCWSTR sessionID, - # [in] UINT32 countCommunicationSessions); - COMMETHOD( - [], - HRESULT, - "OnVolumeDuckNotification", - (["in"], LPCWSTR, "sessionID"), - (["in"], c_uint32, "countCommunicationSessions"), - ), - # HRESULT OnVolumeUnduckNotification( - # [in] LPCWSTR sessionID); - COMMETHOD( - [], - HRESULT, - "OnVolumeUnduckNotification", - (["in"], LPCWSTR, "sessionID"), - ), - ) - - -class IAudioSessionManager2(IAudioSessionManager): - _iid_ = GUID("{77aa99a0-1bd6-484f-8bc7-2c654c9a9b6f}") - _methods_ = ( - # HRESULT GetSessionEnumerator( - # [out] IAudioSessionEnumerator **SessionList); - COMMETHOD( - [], - HRESULT, - "GetSessionEnumerator", - (["out"], POINTER(POINTER(IAudioSessionEnumerator)), "SessionList"), - ), - # HRESULT RegisterSessionNotification( - # IAudioSessionNotification *SessionNotification); - COMMETHOD( - [], - HRESULT, - "RegisterSessionNotification", - (["in"], POINTER(IAudioSessionNotification), "SessionNotification"), - ), - # HRESULT UnregisterSessionNotification( - # IAudioSessionNotification *SessionNotification); - COMMETHOD( - [], - HRESULT, - "UnregisterSessionNotification", - (["in"], POINTER(IAudioSessionNotification), "SessionNotification"), - ), - # HRESULT RegisterDuckNotification( - # LPCWSTR SessionID, - # IAudioVolumeDuckNotification *duckNotification); - COMMETHOD( - [], - HRESULT, - "RegisterDuckNotification", - (["in"], LPCWSTR, "SessionID"), - (["in"], POINTER(IAudioVolumeDuckNotification), "duckNotification"), - ), - # HRESULT UnregisterDuckNotification( - # IAudioVolumeDuckNotification *duckNotification); - COMMETHOD( - [], - HRESULT, - "UnregisterDuckNotification", - (["in"], POINTER(IAudioVolumeDuckNotification), "duckNotification"), - ), - ) - - - diff --git a/source/comInterfaces/coreAudio/constants.py b/source/comInterfaces/coreAudio/constants.py deleted file mode 100644 index f57579f1ec9..00000000000 --- a/source/comInterfaces/coreAudio/constants.py +++ /dev/null @@ -1,60 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from enum import Enum, IntEnum - -from comtypes import GUID - -IID_Empty = GUID("{00000000-0000-0000-0000-000000000000}") - -CLSID_MMDeviceEnumerator = GUID("{BCDE0395-E52F-467C-8E3D-C4579291692E}") - - -class ERole(Enum): - eConsole = 0 - eMultimedia = 1 - eCommunications = 2 - ERole_enum_count = 3 - - -class EDataFlow(Enum): - eRender = 0 - eCapture = 1 - eAll = 2 - EDataFlow_enum_count = 3 - - -class DEVICE_STATE(Enum): - ACTIVE = 0x00000001 - DISABLED = 0x00000002 - NOTPRESENT = 0x00000004 - UNPLUGGED = 0x00000008 - MASK_ALL = 0x0000000F - - -class AudioDeviceState(Enum): - Active = 0x1 - Disabled = 0x2 - NotPresent = 0x4 - Unplugged = 0x8 - - -class STGM(Enum): - STGM_READ = 0x00000000 - - -class AUDCLNT_SHAREMODE(Enum): - AUDCLNT_SHAREMODE_SHARED = 0x00000001 - AUDCLNT_SHAREMODE_EXCLUSIVE = 0x00000002 - - -class AudioSessionState(IntEnum): - # IntEnum to make instances comparable. - Inactive = 0 - Active = 1 - Expired = 2 - - - - diff --git a/source/comInterfaces/coreAudio/endpointvolume/__init__.py b/source/comInterfaces/coreAudio/endpointvolume/__init__.py deleted file mode 100644 index 9ac8f2b7bd1..00000000000 --- a/source/comInterfaces/coreAudio/endpointvolume/__init__.py +++ /dev/null @@ -1,184 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from ctypes import HRESULT, POINTER, c_float -from ctypes.wintypes import BOOL, DWORD, UINT - -from comtypes import COMMETHOD, GUID, IUnknown - -from .depend import PAUDIO_VOLUME_NOTIFICATION_DATA - - -class IAudioEndpointVolumeCallback(IUnknown): - _iid_ = GUID("{b1136c83-b6b5-4add-98a5-a2df8eedf6fa}") - _methods_ = ( - # HRESULT OnNotify( - # [in] PAUDIO_VOLUME_NOTIFICATION_DATA pNotify); - COMMETHOD( - [], - HRESULT, - "OnNotify", - (["in"], PAUDIO_VOLUME_NOTIFICATION_DATA, "pNotify"), - ), - ) - - -class IAudioEndpointVolume(IUnknown): - _iid_ = GUID("{5CDF2C82-841E-4546-9722-0CF74078229A}") - _methods_ = ( - # HRESULT RegisterControlChangeNotify( - # [in] IAudioEndpointVolumeCallback *pNotify); - COMMETHOD( - [], - HRESULT, - "RegisterControlChangeNotify", - (["in"], POINTER(IAudioEndpointVolumeCallback), "pNotify"), - ), - # HRESULT UnregisterControlChangeNotify( - # [in] IAudioEndpointVolumeCallback *pNotify); - COMMETHOD( - [], - HRESULT, - "UnregisterControlChangeNotify", - (["in"], POINTER(IAudioEndpointVolumeCallback), "pNotify"), - ), - # HRESULT GetChannelCount([out] UINT *pnChannelCount); - COMMETHOD( - [], HRESULT, "GetChannelCount", (["out"], POINTER(UINT), "pnChannelCount") - ), - # HRESULT SetMasterVolumeLevel( - # [in] float fLevelDB, [in] LPCGUID pguidEventContext); - COMMETHOD( - [], - HRESULT, - "SetMasterVolumeLevel", - (["in"], c_float, "fLevelDB"), - (["in"], POINTER(GUID), "pguidEventContext"), - ), - # HRESULT SetMasterVolumeLevelScalar( - # [in] float fLevel, [in] LPCGUID pguidEventContext); - COMMETHOD( - [], - HRESULT, - "SetMasterVolumeLevelScalar", - (["in"], c_float, "fLevel"), - (["in"], POINTER(GUID), "pguidEventContext"), - ), - # HRESULT GetMasterVolumeLevel([out] float *pfLevelDB); - COMMETHOD( - [], - HRESULT, - "GetMasterVolumeLevel", - (["out"], POINTER(c_float), "pfLevelDB"), - ), - # HRESULT GetMasterVolumeLevelScalar([out] float *pfLevel); - COMMETHOD( - [], - HRESULT, - "GetMasterVolumeLevelScalar", - (["out"], POINTER(c_float), "pfLevelDB"), - ), - # HRESULT SetChannelVolumeLevel( - # [in] UINT nChannel, - # [in] float fLevelDB, - # [in] LPCGUID pguidEventContext); - COMMETHOD( - [], - HRESULT, - "SetChannelVolumeLevel", - (["in"], UINT, "nChannel"), - (["in"], c_float, "fLevelDB"), - (["in"], POINTER(GUID), "pguidEventContext"), - ), - # HRESULT SetChannelVolumeLevelScalar( - # [in] UINT nChannel, - # [in] float fLevel, - # [in] LPCGUID pguidEventContext); - COMMETHOD( - [], - HRESULT, - "SetChannelVolumeLevelScalar", - (["in"], DWORD, "nChannel"), - (["in"], c_float, "fLevelDB"), - (["in"], POINTER(GUID), "pguidEventContext"), - ), - # HRESULT GetChannelVolumeLevel( - # [in] UINT nChannel, - # [out] float *pfLevelDB); - COMMETHOD( - [], - HRESULT, - "GetChannelVolumeLevel", - (["in"], UINT, "nChannel"), - (["out"], POINTER(c_float), "pfLevelDB"), - ), - # HRESULT GetChannelVolumeLevelScalar( - # [in] UINT nChannel, - # [out] float *pfLevel); - COMMETHOD( - [], - HRESULT, - "GetChannelVolumeLevelScalar", - (["in"], DWORD, "nChannel"), - (["out"], POINTER(c_float), "pfLevelDB"), - ), - # HRESULT SetMute([in] BOOL bMute, [in] LPCGUID pguidEventContext); - COMMETHOD( - [], - HRESULT, - "SetMute", - (["in"], BOOL, "bMute"), - (["in"], POINTER(GUID), "pguidEventContext"), - ), - # HRESULT GetMute([out] BOOL *pbMute); - COMMETHOD([], HRESULT, "GetMute", (["out"], POINTER(BOOL), "pbMute")), - # HRESULT GetVolumeStepInfo( - # [out] UINT *pnStep, - # [out] UINT *pnStepCount); - COMMETHOD( - [], - HRESULT, - "GetVolumeStepInfo", - (["out"], POINTER(DWORD), "pnStep"), - (["out"], POINTER(DWORD), "pnStepCount"), - ), - # HRESULT VolumeStepUp([in] LPCGUID pguidEventContext); - COMMETHOD( - [], HRESULT, "VolumeStepUp", (["in"], POINTER(GUID), "pguidEventContext") - ), - # HRESULT VolumeStepDown([in] LPCGUID pguidEventContext); - COMMETHOD( - [], HRESULT, "VolumeStepDown", (["in"], POINTER(GUID), "pguidEventContext") - ), - # HRESULT QueryHardwareSupport([out] DWORD *pdwHardwareSupportMask); - COMMETHOD( - [], - HRESULT, - "QueryHardwareSupport", - (["out"], POINTER(DWORD), "pdwHardwareSupportMask"), - ), - # HRESULT GetVolumeRange( - # [out] float *pfLevelMinDB, - # [out] float *pfLevelMaxDB, - # [out] float *pfVolumeIncrementDB); - COMMETHOD( - [], - HRESULT, - "GetVolumeRange", - (["out"], POINTER(c_float), "pfMin"), - (["out"], POINTER(c_float), "pfMax"), - (["out"], POINTER(c_float), "pfIncr"), - ), - ) - - -class IAudioMeterInformation(IUnknown): - _iid_ = GUID("{C02216F6-8C67-4B5B-9D00-D008E73E0064}") - _methods_ = ( - # HRESULT GetPeakValue([out] c_float *pfPeak); - COMMETHOD([], HRESULT, "GetPeakValue", (["out"], POINTER(c_float), "pfPeak")), - ) - - - diff --git a/source/comInterfaces/coreAudio/endpointvolume/depend.py b/source/comInterfaces/coreAudio/endpointvolume/depend.py deleted file mode 100644 index c05af63f04b..00000000000 --- a/source/comInterfaces/coreAudio/endpointvolume/depend.py +++ /dev/null @@ -1,24 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from ctypes import POINTER, Structure, c_float -from ctypes.wintypes import BOOL, UINT - -from comtypes import GUID - - -class AUDIO_VOLUME_NOTIFICATION_DATA(Structure): - _fields_ = [ - ("guidEventContext", GUID), - ("bMuted", BOOL), - ("fMasterVolume", c_float), - ("nChannels", UINT), - ("afChannelVolumes", c_float * 8), - ] - - -PAUDIO_VOLUME_NOTIFICATION_DATA = POINTER(AUDIO_VOLUME_NOTIFICATION_DATA) - - - diff --git a/source/comInterfaces/coreAudio/mmdeviceapi/__init__.py b/source/comInterfaces/coreAudio/mmdeviceapi/__init__.py deleted file mode 100644 index 484ac870eb9..00000000000 --- a/source/comInterfaces/coreAudio/mmdeviceapi/__init__.py +++ /dev/null @@ -1,188 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from ctypes import HRESULT, POINTER -from ctypes.wintypes import DWORD, LPCWSTR, LPWSTR, UINT - -from comtypes import COMMETHOD, GUID, IUnknown - -from .depend import PROPERTYKEY, IPropertyStore - - -class IMMDevice(IUnknown): - _iid_ = GUID("{D666063F-1587-4E43-81F1-B948E807363F}") - _methods_ = ( - # HRESULT Activate( - # [in] REFIID iid, - # [in] DWORD dwClsCtx, - # [in] PROPVARIANT *pActivationParams, - # [out] void **ppInterface); - COMMETHOD( - [], - HRESULT, - "Activate", - (["in"], POINTER(GUID), "iid"), - (["in"], DWORD, "dwClsCtx"), - (["in"], POINTER(DWORD), "pActivationParams"), - (["out"], POINTER(POINTER(IUnknown)), "ppInterface"), - ), - # HRESULT OpenPropertyStore( - # [in] DWORD stgmAccess, - # [out] IPropertyStore **ppProperties); - COMMETHOD( - [], - HRESULT, - "OpenPropertyStore", - (["in"], DWORD, "stgmAccess"), - (["out"], POINTER(POINTER(IPropertyStore)), "ppProperties"), - ), - # HRESULT GetId([out] LPWSTR *ppstrId); - COMMETHOD([], HRESULT, "GetId", (["out"], POINTER(LPWSTR), "ppstrId")), - # HRESULT GetState([out] DWORD *pdwState); - COMMETHOD([], HRESULT, "GetState", (["out"], POINTER(DWORD), "pdwState")), - ) - - -class IMMDeviceCollection(IUnknown): - _iid_ = GUID("{0BD7A1BE-7A1A-44DB-8397-CC5392387B5E}") - _methods_ = ( - # HRESULT GetCount([out] UINT *pcDevices); - COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(UINT), "pcDevices")), - # HRESULT Item([in] UINT nDevice, [out] IMMDevice **ppDevice); - COMMETHOD( - [], - HRESULT, - "Item", - (["in"], UINT, "nDevice"), - (["out"], POINTER(POINTER(IMMDevice)), "ppDevice"), - ), - ) - - -class IMMNotificationClient(IUnknown): - _case_insensitive_ = True - _iid_ = GUID("{7991EEC9-7E89-4D85-8390-6C703CEC60C0}") - _methods_ = ( - # HRESULT OnDeviceStateChanged( - # [in] LPCWSTR pwstrDeviceId, - # [in] DWORD dwNewState); - COMMETHOD( - [], - HRESULT, - "OnDeviceStateChanged", - (["in"], LPCWSTR, "pwstrDeviceId"), - (["in"], DWORD, "dwNewState"), - ), - # HRESULT OnDeviceAdded( - # [in] LPCWSTR pwstrDeviceId, - COMMETHOD( - [], - HRESULT, - "OnDeviceAdded", - (["in"], LPCWSTR, "pwstrDeviceId"), - ), - # HRESULT OnDeviceRemoved( - # [in] LPCWSTR pwstrDeviceId, - COMMETHOD( - [], - HRESULT, - "OnDeviceRemoved", - (["in"], LPCWSTR, "pwstrDeviceId"), - ), - # HRESULT OnDefaultDeviceChanged( - # [in] EDataFlow flow, - # [in] ERole role, - # [in] LPCWSTR pwstrDefaultDeviceId; - COMMETHOD( - [], - HRESULT, - "OnDefaultDeviceChanged", - (["in"], DWORD, "flow"), - (["in"], DWORD, "role"), - (["in"], LPCWSTR, "pwstrDefaultDeviceId"), - ), - # HRESULT OnPropertyValueChanged( - # [in] LPCWSTR pwstrDeviceId, - # [in] const PROPERTYKEY key); - COMMETHOD( - [], - HRESULT, - "OnPropertyValueChanged", - (["in"], LPCWSTR, "pwstrDeviceId"), - (["in"], PROPERTYKEY, "key"), - ), - ) - - -class IMMDeviceEnumerator(IUnknown): - _iid_ = GUID("{A95664D2-9614-4F35-A746-DE8DB63617E6}") - _methods_ = ( - # HRESULT EnumAudioEndpoints( - # [in] EDataFlow dataFlow, - # [in] DWORD dwStateMask, - # [out] IMMDeviceCollection **ppDevices); - COMMETHOD( - [], - HRESULT, - "EnumAudioEndpoints", - (["in"], DWORD, "dataFlow"), - (["in"], DWORD, "dwStateMask"), - (["out"], POINTER(POINTER(IMMDeviceCollection)), "ppDevices"), - ), - # HRESULT GetDefaultAudioEndpoint( - # [in] EDataFlow dataFlow, - # [in] ERole role, - # [out] IMMDevice **ppDevice); - COMMETHOD( - [], - HRESULT, - "GetDefaultAudioEndpoint", - (["in"], DWORD, "dataFlow"), - (["in"], DWORD, "role"), - (["out"], POINTER(POINTER(IMMDevice)), "ppDevices"), - ), - # HRESULT GetDevice( - # [in] LPCWSTR pwstrId, - # [out] IMMDevice **ppDevice); - COMMETHOD( - [], - HRESULT, - "GetDevice", - (["in"], LPCWSTR, "pwstrId"), - (["out"], POINTER(POINTER(IMMDevice)), "ppDevice"), - ), - # HRESULT RegisterEndpointNotificationCallback( - # [in] IMMNotificationClient *pClient); - COMMETHOD( - [], - HRESULT, - "RegisterEndpointNotificationCallback", - (["in"], POINTER(IMMNotificationClient), "pClient"), - ), - # HRESULT UnregisterEndpointNotificationCallback( - # [in] IMMNotificationClient *pClient); - COMMETHOD( - [], - HRESULT, - "UnregisterEndpointNotificationCallback", - (["in"], POINTER(IMMNotificationClient), "pClient"), - ), - ) - - -class IMMEndpoint(IUnknown): - _iid_ = GUID("{1BE09788-6894-4089-8586-9A2A6C265AC5}") - _methods_ = ( - # HRESULT GetDataFlow( - # [out] EDataFlow *pDataFlow); - COMMETHOD( - [], - HRESULT, - "GetDataFlow", - (["out"], POINTER(DWORD), "pDataFlow"), - ), - ) - - - diff --git a/source/comInterfaces/coreAudio/mmdeviceapi/depend/__init__.py b/source/comInterfaces/coreAudio/mmdeviceapi/depend/__init__.py deleted file mode 100644 index 18594db9ac4..00000000000 --- a/source/comInterfaces/coreAudio/mmdeviceapi/depend/__init__.py +++ /dev/null @@ -1,54 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from ctypes import HRESULT, POINTER -from ctypes.wintypes import DWORD - -from comtypes import COMMETHOD, GUID, IUnknown - -from .structures import PROPERTYKEY, PROPVARIANT - - -class IPropertyStore(IUnknown): - _iid_ = GUID("{886d8eeb-8cf2-4446-8d02-cdba1dbdcf99}") - _methods_ = ( - # HRESULT GetCount([out] DWORD *cProps); - COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(DWORD), "cProps")), - # HRESULT GetAt( - # [in] DWORD iProp, - # [out] PROPERTYKEY *pkey); - COMMETHOD( - [], - HRESULT, - "GetAt", - (["in"], DWORD, "iProp"), - (["out"], POINTER(PROPERTYKEY), "pkey"), - ), - # HRESULT GetValue( - # [in] REFPROPERTYKEY key, - # [out] PROPVARIANT *pv); - COMMETHOD( - [], - HRESULT, - "GetValue", - (["in"], POINTER(PROPERTYKEY), "key"), - (["out"], POINTER(PROPVARIANT), "pv"), - ), - # HRESULT SetValue( - # [in] REFPROPERTYKEY key, - # [in] REFPROPVARIANT propvar - # ); - COMMETHOD( - [], - HRESULT, - "SetValue", - (["in"], POINTER(PROPERTYKEY), "key"), - (["in"], POINTER(PROPVARIANT), "propvar"), - ), - # HRESULT Commit(); - COMMETHOD([], HRESULT, "Commit"), - ) - - - diff --git a/source/comInterfaces/coreAudio/mmdeviceapi/depend/structures.py b/source/comInterfaces/coreAudio/mmdeviceapi/depend/structures.py deleted file mode 100644 index 08016daf282..00000000000 --- a/source/comInterfaces/coreAudio/mmdeviceapi/depend/structures.py +++ /dev/null @@ -1,61 +0,0 @@ -# This file is a part of pycaw library (https://github.com/AndreMiras/pycaw). -# Please note that it is distributed under MIT license: -# https://github.com/AndreMiras/pycaw#MIT-1-ov-file - -from ctypes import Structure, Union, byref, windll -from ctypes.wintypes import DWORD, LONG, LPWSTR, ULARGE_INTEGER, VARIANT_BOOL, WORD - -from comtypes import GUID -from comtypes.automation import VARTYPE, VT_BOOL, VT_CLSID, VT_LPWSTR, VT_UI4 - - -class PROPVARIANT_UNION(Union): - _fields_ = [ - ("lVal", LONG), - ("uhVal", ULARGE_INTEGER), - ("boolVal", VARIANT_BOOL), - ("pwszVal", LPWSTR), - ("puuid", GUID), - ] - - -class PROPVARIANT(Structure): - _fields_ = [ - ("vt", VARTYPE), - ("reserved1", WORD), - ("reserved2", WORD), - ("reserved3", WORD), - ("union", PROPVARIANT_UNION), - ] - - def GetValue(self): - vt = self.vt - if vt == VT_BOOL: - return self.union.boolVal != 0 - elif vt == VT_LPWSTR: - # return Marshal.PtrToStringUni(union.pwszVal) - return self.union.pwszVal - elif vt == VT_UI4: - return self.union.lVal - elif vt == VT_CLSID: - # TODO - # return (Guid)Marshal.PtrToStructure(union.puuid, typeof(Guid)) - return - else: - return "%s:?" % (vt) - - def clear(self): - windll.ole32.PropVariantClear(byref(self)) - - -class PROPERTYKEY(Structure): - _fields_ = [ - ("fmtid", GUID), - ("pid", DWORD), - ] - - def __str__(self): - return "%s %s" % (self.fmtid, self.pid) - - - From 14db88597fe3b0958c97e51de284d278bbaae1cf Mon Sep 17 00:00:00 2001 From: mltony Date: Sun, 11 Feb 2024 12:14:20 -0800 Subject: [PATCH 21/44] lint --- source/audio/soundSplit.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/source/audio/soundSplit.py b/source/audio/soundSplit.py index 70ad344a35f..f1f25ffaf16 100644 --- a/source/audio/soundSplit.py +++ b/source/audio/soundSplit.py @@ -12,10 +12,8 @@ from pycaw.api.audiopolicy import IAudioSessionManager2 from pycaw.callbacks import AudioSessionNotification from pycaw.utils import AudioSession, AudioUtilities -from typing import Callable import ui from utils.displayString import DisplayStringIntEnum -import _ctypes VolumeTupleT = tuple[float, float] @@ -72,7 +70,7 @@ def getNVDAVolume(self) -> VolumeTupleT: raise RuntimeError(f"{self=}") -audioSessionManager: IAudioSessionManager2|None = None +audioSessionManager: IAudioSessionManager2 | None = None activeCallback: AudioSessionNotification = None From 66753cb6f8a86c95b55307bc465066af79d37bf4 Mon Sep 17 00:00:00 2001 From: mltony Date: Sun, 11 Feb 2024 12:37:38 -0800 Subject: [PATCH 22/44] Revert sconscript --- source/comInterfaces_sconscript | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/source/comInterfaces_sconscript b/source/comInterfaces_sconscript index 3da41330bf7..1e7069897e4 100755 --- a/source/comInterfaces_sconscript +++ b/source/comInterfaces_sconscript @@ -119,8 +119,6 @@ for k,v in COM_INTERFACES.items(): # except for things starting with __ (e.g. __init__.py), and files tracked by Git env.Clean( Dir('comInterfaces'), - Glob( - 'comInterfaces/[!_]*', - exclude=['comInterfaces/readme.md', 'comInterfaces/UIAutomationClient.py', r'comInterfaces/coreAudio\**'] - ) + Glob('comInterfaces/_[!_]*', exclude='comInterfaces/_944DE083_8FB8_45CF_BCB7_C477ACB2F897_0_1_0.py') + Glob('comInterfaces/[!_]*', exclude=['comInterfaces/readme.md', 'comInterfaces/UIAutomationClient.py']) + + Glob('comInterfaces/_[!_]*', exclude='comInterfaces/_944DE083_8FB8_45CF_BCB7_C477ACB2F897_0_1_0.py') ) From 0fc96c7937f8c2cb631280f18a2345b9abbf95b7 Mon Sep 17 00:00:00 2001 From: mltony Date: Wed, 21 Feb 2024 10:56:09 -0800 Subject: [PATCH 23/44] Update pycaw version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 925bc309f0b..2b69c567331 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ fast_diff_match_patch==2.0.1 typing-extensions==4.9.0 # pycaw is a Core Audio Windows Library used for sound split -git+https://github.com/AndreMiras/pycaw@ec2070d679d6fb268e99d2017127983bf9ab0d61#egg=pycaw +pycaw==20240210 # Packaging NVDA git+https://github.com/py2exe/py2exe@4e7b2b2c60face592e67cb1bc935172a20fa371d#egg=py2exe From 5da261dcd33a41f375441d4a47e132ef4d6e8a47 Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:32:50 -0800 Subject: [PATCH 24/44] Update source/config/configSpec.py Co-authored-by: Sean Budd --- source/config/configSpec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/config/configSpec.py b/source/config/configSpec.py index 6694e65af42..f663131f0f0 100644 --- a/source/config/configSpec.py +++ b/source/config/configSpec.py @@ -58,7 +58,7 @@ keepAudioAwakeTimeSeconds = integer(default=30, min=0, max=3600) whiteNoiseVolume = integer(default=0, min=0, max=100) soundSplitState = integer(default=0) - includedSoundSplitModes =int_list(default=list(0, 1, 2)) + includedSoundSplitModes = int_list(default=list(0, 1, 2)) # Braille settings [braille] From 46717813d78d2e44733ec98eb8e610b75f5436f5 Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:33:17 -0800 Subject: [PATCH 25/44] Update source/audio/soundSplit.py Co-authored-by: Sean Budd --- source/audio/soundSplit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/audio/soundSplit.py b/source/audio/soundSplit.py index f1f25ffaf16..a8a3bb50c14 100644 --- a/source/audio/soundSplit.py +++ b/source/audio/soundSplit.py @@ -1,5 +1,5 @@ # A part of NonVisual Desktop Access (NVDA) -# Copyright (C) 2015-2021 NV Access Limited +# Copyright (C) 2024 NV Access Limited # This file is covered by the GNU General Public License. # See the file COPYING for more details. From 549dde3e56ff6cb5bcfbfae26cc7fc50fdd7a843 Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:34:37 -0800 Subject: [PATCH 26/44] Update source/audio/soundSplit.py Co-authored-by: Sean Budd --- source/audio/soundSplit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/audio/soundSplit.py b/source/audio/soundSplit.py index a8a3bb50c14..c423d539791 100644 --- a/source/audio/soundSplit.py +++ b/source/audio/soundSplit.py @@ -56,7 +56,7 @@ def getAppVolume(self) -> VolumeTupleT: case SoundSplitState.NVDA_LEFT_APPS_RIGHT | SoundSplitState.NVDA_BOTH_APPS_RIGHT: return (0.0, 1.0) case _: - raise RuntimeError(f"{self=}") + raise RuntimeError(f"Unexpected or unknown state {self=}") def getNVDAVolume(self) -> VolumeTupleT: match self: From ce52f0f7fba26cf7a4a93e8f339e5b8939e05a15 Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:35:01 -0800 Subject: [PATCH 27/44] Update source/audio/soundSplit.py Co-authored-by: Sean Budd --- source/audio/soundSplit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/audio/soundSplit.py b/source/audio/soundSplit.py index c423d539791..cd8f6416632 100644 --- a/source/audio/soundSplit.py +++ b/source/audio/soundSplit.py @@ -71,7 +71,7 @@ def getNVDAVolume(self) -> VolumeTupleT: audioSessionManager: IAudioSessionManager2 | None = None -activeCallback: AudioSessionNotification = None +activeCallback: AudioSessionNotification | None = None def initialize() -> None: From a9bfe6a542cd91315e39c22f74bafc6fce333624 Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:35:57 -0800 Subject: [PATCH 28/44] Update source/audio/soundSplit.py Co-authored-by: Sean Budd --- source/audio/soundSplit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/audio/soundSplit.py b/source/audio/soundSplit.py index cd8f6416632..c067ee5db3a 100644 --- a/source/audio/soundSplit.py +++ b/source/audio/soundSplit.py @@ -78,7 +78,7 @@ def initialize() -> None: if nvwave.usingWasapiWavePlayer(): global audioSessionManager audioSessionManager = AudioUtilities.GetAudioSessionManager() - state = SoundSplitState(config.conf['audio']['soundSplitState']) + state = SoundSplitState(config.conf["audio"]["soundSplitState"]) setSoundSplitState(state) From 01ce9708c0a034a407c566cc0ec9072a9808f107 Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:36:16 -0800 Subject: [PATCH 29/44] Update source/audio/soundSplit.py Co-authored-by: Sean Budd --- source/audio/soundSplit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/audio/soundSplit.py b/source/audio/soundSplit.py index c067ee5db3a..3f471c4768c 100644 --- a/source/audio/soundSplit.py +++ b/source/audio/soundSplit.py @@ -153,5 +153,5 @@ def toggleSoundSplitState() -> None: i = (i + 1) % len(allowedStates) newState = SoundSplitState(allowedStates[i]) setSoundSplitState(newState) - config.conf['audio']['soundSplitState'] = newState.value + config.conf["audio"]["soundSplitState"] = newState.value ui.message(newState.displayString) From 04eb90a46df6c83739dc9dae11a5f7036fcd36bb Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:36:51 -0800 Subject: [PATCH 30/44] 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 bc1c517a378..ba6551ea4a3 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -2752,7 +2752,7 @@ def _onSoundSplitModesListChange(self, evt: wx.CommandEvent): # Translators: Warning shown when 'OFF' sound split mode is disabled in settings. "You did not choose 'Off' as one of your sound split mode options. " "Please note that this may result in no speech output at all " - "in case if one of your audio channels is malfunctioning. " + "in case one of your audio channels is malfunctioning. " "Are you sure you want to continue?" ), # Translators: Title of the warning message. From 466075cde4fcda4a41a17f85cdd2596b22e550cf Mon Sep 17 00:00:00 2001 From: mltony Date: Wed, 21 Feb 2024 16:56:44 -0800 Subject: [PATCH 31/44] doc --- user_docs/en/userGuide.t2t | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 0aca9780cc5..8beab484a3a 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -546,26 +546,10 @@ A gesture allows cycling through the various speech modes: If you only need to switch between a limited subset of speech modes, see [Modes available in the Cycle speech mode command #SpeechModesDisabling] for a way to disable unwanted modes. -++ Sound split modes ++[SpeechModes] +++ Sound split modes ++[SoundSplitModes] The sound split feature allows users to make use of their stereo output devices, such as headphones and speakers. Sound split makes it possible to have NVDA speech in one channel (e.g. left) and have all other applications play their sounds in the other channel (e.g. right). -By default sound split is disabled, which means that all applications including NVDA will play sounds in both left and right channels. - -A gesture allows cycling through the various sound split modes: -%kc:beginInclude -|| Name | Key | Description | -| Cycle Sound Split Mode | ``NVDA+alt+s`` | Cycles between speech modes. | -%kc:endInclude - -By default this command will cycle between the following modes: -- Disabled sound split: both NVDA and other applications output sounds to both left and right channels. -- NVDA on the left and applications on the right: NVDA will speak in the left channel, while other applications will play sounds in the right channel. -- NVDA on the right and applications on the left: NVDA will speak in the right channel, while other applications will play sounds in the left channel. -- - -There are more advanced sound split modes available in NVDA settings; for more information please see [Customizing Sound split modes #CustomizeSoundSplitModes] section. - -Please note, that sound split doesn't work as a mixer. For example, if an application is playing a stereo sound track while sound split is set to "NVDA on the left and applications on the right", then you will only hear the right channel of the sound track, while the left channel of the sound track will be muted. in order to allow NVDA to use the left channel. +For more information, please see [Sound split settings#SelectSoundSplitMode] + Navigating with NVDA +[NavigatingWithNVDA] NVDA allows you to explore and navigate the system in several ways, including both normal interaction and review. @@ -1956,14 +1940,24 @@ This setting only takes effect when "Volume of NVDA sounds follows voice volume" This option is not available if you have started NVDA with [WASAPI disabled for audio output #WASAPI] in Advanced Settings. ==== Sound split====[SelectSoundSplitMode] -Key: ``NVDA+alt+s`` -This option allows you to make NVDA sound output to be directed to either left or right channel and application volume to be directed to the other channel. This could be useful for users of headphones or stereo speakers. +The sound split feature allows users to make use of their stereo output devices, such as headphones and speakers. Sound split makes it possible to have NVDA speech in one channel (e.g. left) and have all other applications play their sounds in the other channel (e.g. right). +By default sound split is disabled, which means that all applications including NVDA will play sounds in both left and right channels. +A gesture allows cycling through the various sound split modes: +%kc:beginInclude +|| Name | Key | Description | +| Cycle Sound Split Mode | ``NVDA+alt+s`` | Cycles between speech modes. | +%kc:endInclude + +By default this command will cycle between the following modes: - Disabled sound split: both NVDA and other applications output sounds to both left and right channels. - NVDA on the left and applications on the right: NVDA will speak in the left channel, while other applications will play sounds in the right channel. - NVDA on the right and applications on the left: NVDA will speak in the right channel, while other applications will play sounds in the left channel. - +There are more advanced sound split modes available in NVDA setting combo box. +Please note, that sound split doesn't work as a mixer. For example, if an application is playing a stereo sound track while sound split is set to "NVDA on the left and applications on the right", then you will only hear the right channel of the sound track, while the left channel of the sound track will be muted. + This option is not available if you have started NVDA with [WASAPI disabled for audio output #WASAPI] in Advanced Settings. ==== Customizing Sound split modes====[CustomizeSoundSplitModes] @@ -1976,6 +1970,7 @@ By default only three modes are included. - Note that it is necessary to check at least one mode. +This option is not available if you have started NVDA with [WASAPI disabled for audio output #WASAPI] in Advanced Settings. +++ Vision +++[VisionSettings] The Vision category in the NVDA Settings dialog allows you to enable, disable and configure [visual aids #Vision]. From cfe3015ca2ca21d308d72edd73ba328e21a8f686 Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:02:40 -0800 Subject: [PATCH 32/44] Update source/audio/soundSplit.py Co-authored-by: Sean Budd --- source/audio/soundSplit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/audio/soundSplit.py b/source/audio/soundSplit.py index 3f471c4768c..3295d3f1f55 100644 --- a/source/audio/soundSplit.py +++ b/source/audio/soundSplit.py @@ -144,7 +144,7 @@ def toggleSoundSplitState() -> None: ) ui.message(message) return - state = SoundSplitState(config.conf['audio']['soundSplitState']) + state = SoundSplitState(config.conf["audio"]["soundSplitState"]) allowedStates: list[int] = config.conf["audio"]["includedSoundSplitModes"] try: i = allowedStates.index(state) From b6adad12469b244a669d04b22b62a2e50012fb6a Mon Sep 17 00:00:00 2001 From: mltony Date: Thu, 22 Feb 2024 11:18:50 -0800 Subject: [PATCH 33/44] Addressing comments --- source/audio/soundSplit.py | 69 +++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/source/audio/soundSplit.py b/source/audio/soundSplit.py index 3295d3f1f55..41d2da882a4 100644 --- a/source/audio/soundSplit.py +++ b/source/audio/soundSplit.py @@ -14,6 +14,7 @@ from pycaw.utils import AudioSession, AudioUtilities import ui from utils.displayString import DisplayStringIntEnum +from dataclasses import dataclass VolumeTupleT = tuple[float, float] @@ -80,6 +81,8 @@ def initialize() -> None: audioSessionManager = AudioUtilities.GetAudioSessionManager() state = SoundSplitState(config.conf["audio"]["soundSplitState"]) setSoundSplitState(state) + else: + log.debug("Cannot initialize sound split as WASAPI is disabled") @atexit.register @@ -87,12 +90,21 @@ def terminate(): if nvwave.usingWasapiWavePlayer(): setSoundSplitState(SoundSplitState.OFF) unregisterCallback() + else: + log.debug("Skipping terminating sound split as wasapi is mode is not enabled.") def applyToAllAudioSessions( callback: AudioSessionNotification, applyToFuture: bool = True, ) -> None: + """ + Executes provided callback function on all active audio sessions. + Additionally, if applyToFuture is True, then it will register a notification with audio session manager, + which will execute the same callback for all future sessions as they are created. + That notification will be active until next invokation of this function, + or until unregisterCallback() is called. + """ unregisterCallback() if applyToFuture: audioSessionManager.RegisterSessionNotification(callback) @@ -112,27 +124,38 @@ def unregisterCallback() -> None: activeCallback = None -def setSoundSplitState(state: SoundSplitState) -> None: +@dataclass(unsafe_hash=True) +class VolumeSetter(AudioSessionNotification): + leftVolume: float + rightVolume: float + leftNVDAVolume: float + rightNVDAVolume: float + foundSessionWithNot2Channels: bool = False + + def on_session_created(self, new_session: AudioSession): + pid = new_session.ProcessId + channelVolume = new_session.channelAudioVolume() + channelCount = channelVolume.GetChannelCount() + if channelCount != 2: + log.warning(f"Audio session for pid {pid} has {channelCount} channels instead of 2 - cannot set volume!") + self.foundSessionWithNot2Channels = True + return + if pid != globalVars.appPid: + channelVolume.SetChannelVolume(0, self.leftVolume, None) + channelVolume.SetChannelVolume(1, self.rightVolume, None) + else: + channelVolume.SetChannelVolume(0, self.leftNVDAVolume, None) + channelVolume.SetChannelVolume(1, self.rightNVDAVolume, None) + + +def setSoundSplitState(state: SoundSplitState) -> dict: leftVolume, rightVolume = state.getAppVolume() leftNVDAVolume, rightNVDAVolume = state.getNVDAVolume() - - class VolumeSetter(AudioSessionNotification): - def on_session_created(self, new_session: AudioSession): - pid = new_session.ProcessId - channelVolume = new_session.channelAudioVolume() - channelCount = channelVolume.GetChannelCount() - if channelCount != 2: - log.warning(f"Audio session for pid {pid} has {channelCount} channels instead of 2 - cannot set volume!") - return - if pid != globalVars.appPid: - channelVolume.SetChannelVolume(0, leftVolume, None) - channelVolume.SetChannelVolume(1, rightVolume, None) - else: - channelVolume.SetChannelVolume(0, leftNVDAVolume, None) - channelVolume.SetChannelVolume(1, rightNVDAVolume, None) - - volumeSetter = VolumeSetter() + volumeSetter = VolumeSetter(leftVolume, rightVolume, leftNVDAVolume, rightNVDAVolume) applyToAllAudioSessions(volumeSetter) + return { + "foundSessionWithNot2Channels": volumeSetter.foundSessionWithNot2Channels, + } def toggleSoundSplitState() -> None: @@ -152,6 +175,14 @@ def toggleSoundSplitState() -> None: i = -1 i = (i + 1) % len(allowedStates) newState = SoundSplitState(allowedStates[i]) - setSoundSplitState(newState) + result = setSoundSplitState(newState) config.conf["audio"]["soundSplitState"] = newState.value ui.message(newState.displayString) + if result["foundSessionWithNot2Channels"]: + msg = _( + # Translators: warning message when sound split trigger wasn't successful due to one of audio sessions + # had number of channels other than 2 . + "Warning: couldn't set volumes for sound split: " + "one of audio sessions is either mono, or has more than 2 audio channels." + ) + ui.message(msg) From 26d319d37248d171ec3ab5f0c950c82a0de1d545 Mon Sep 17 00:00:00 2001 From: mltony Date: Thu, 22 Feb 2024 11:30:46 -0800 Subject: [PATCH 34/44] docs --- user_docs/en/userGuide.t2t | 5 ----- 1 file changed, 5 deletions(-) diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 458beba056b..72efc32d17c 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -553,11 +553,6 @@ A gesture allows cycling through the various speech modes: If you only need to switch between a limited subset of speech modes, see [Modes available in the Cycle speech mode command #SpeechModesDisabling] for a way to disable unwanted modes. -++ Sound split modes ++[SoundSplitModes] - -The sound split feature allows users to make use of their stereo output devices, such as headphones and speakers. Sound split makes it possible to have NVDA speech in one channel (e.g. left) and have all other applications play their sounds in the other channel (e.g. right). -For more information, please see [Sound split settings#SelectSoundSplitMode] - + Navigating with NVDA +[NavigatingWithNVDA] NVDA allows you to explore and navigate the system in several ways, including both normal interaction and review. From 520b4fc64c97ee0a36900d72633a74fb0767dfef Mon Sep 17 00:00:00 2001 From: Sean Budd Date: Wed, 28 Feb 2024 15:51:50 +1100 Subject: [PATCH 35/44] commit suggestion --- source/audio/soundSplit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/audio/soundSplit.py b/source/audio/soundSplit.py index 41d2da882a4..55f4cc84425 100644 --- a/source/audio/soundSplit.py +++ b/source/audio/soundSplit.py @@ -172,6 +172,7 @@ def toggleSoundSplitState() -> None: try: i = allowedStates.index(state) except ValueError: + # State not found, resetting to default (OFF) i = -1 i = (i + 1) % len(allowedStates) newState = SoundSplitState(allowedStates[i]) From 633b814d72dc7221b024df210a47ff08e7e341e2 Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:49:12 -0800 Subject: [PATCH 36/44] Update source/audio/soundSplit.py Co-authored-by: Sean Budd --- source/audio/soundSplit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/audio/soundSplit.py b/source/audio/soundSplit.py index 55f4cc84425..69119d13b13 100644 --- a/source/audio/soundSplit.py +++ b/source/audio/soundSplit.py @@ -68,7 +68,7 @@ def getNVDAVolume(self) -> VolumeTupleT: case SoundSplitState.NVDA_RIGHT_APPS_LEFT | SoundSplitState.NVDA_RIGHT_APPS_BOTH: return (0.0, 1.0) case _: - raise RuntimeError(f"{self=}") + raise RuntimeError(f"Unexpected or unknown state {self=}") audioSessionManager: IAudioSessionManager2 | None = None From 880544a272d53ddfbb9e041852af01fd9787e9ca Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:49:43 -0800 Subject: [PATCH 37/44] Update source/audio/soundSplit.py Co-authored-by: Sean Budd --- source/audio/soundSplit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/audio/soundSplit.py b/source/audio/soundSplit.py index 69119d13b13..5b2ff8e446d 100644 --- a/source/audio/soundSplit.py +++ b/source/audio/soundSplit.py @@ -91,7 +91,7 @@ def terminate(): setSoundSplitState(SoundSplitState.OFF) unregisterCallback() else: - log.debug("Skipping terminating sound split as wasapi is mode is not enabled.") + log.debug("Skipping terminating sound split as WASAPI is disabled.") def applyToAllAudioSessions( From 34cc3ab2ef9231e28159a4c06f897401fe6f70c5 Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:50:11 -0800 Subject: [PATCH 38/44] 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 8b21510e88b..84e7bdc9f72 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -2754,7 +2754,7 @@ def _appendSoundSplitModesList(self, settingsSizerHelper: guiHelper.BoxSizerHelp self._allSoundSplitModes = list(audio.SoundSplitState) self.soundSplitModesList: nvdaControls.CustomCheckListBox = settingsSizerHelper.addLabeledControl( # Translators: Label of the list where user can select sound split modes that will be available. - _("&Modes available in the Cycle sound split mode command:"), + _("&Modes available in the 'Cycle sound split mode' command:"), nvdaControls.CustomCheckListBox, choices=[mode.displayString for mode in self._allSoundSplitModes] ) From 161bc2a6a744d5daa96b9002a0363c0dc2b17981 Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:50:44 -0800 Subject: [PATCH 39/44] Update user_docs/en/userGuide.t2t Co-authored-by: Sean Budd --- user_docs/en/userGuide.t2t | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 72efc32d17c..a1a0699e4c4 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -1975,7 +1975,8 @@ By default this command will cycle between the following modes: - There are more advanced sound split modes available in NVDA setting combo box. -Please note, that sound split doesn't work as a mixer. For example, if an application is playing a stereo sound track while sound split is set to "NVDA on the left and applications on the right", then you will only hear the right channel of the sound track, while the left channel of the sound track will be muted. +Please note, that sound split doesn't work as a mixer. +For example, if an application is playing a stereo sound track while sound split is set to "NVDA on the left and applications on the right", then you will only hear the right channel of the sound track, while the left channel of the sound track will be muted. This option is not available if you have started NVDA with [WASAPI disabled for audio output #WASAPI] in Advanced Settings. From ef5c81cbd536451f8ba4f28ba82b17689f6a1f1c Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:51:13 -0800 Subject: [PATCH 40/44] Update user_docs/en/userGuide.t2t Co-authored-by: Sean Budd --- user_docs/en/userGuide.t2t | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index a1a0699e4c4..ba2658b2e1a 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -1960,7 +1960,8 @@ You can set the time to zero in order to disable this feature. ==== Sound split====[SelectSoundSplitMode] -The sound split feature allows users to make use of their stereo output devices, such as headphones and speakers. Sound split makes it possible to have NVDA speech in one channel (e.g. left) and have all other applications play their sounds in the other channel (e.g. right). +The sound split feature allows users to make use of their stereo output devices, such as headphones and speakers. +Sound split makes it possible to have NVDA speech in one channel (e.g. left) and have all other applications play their sounds in the other channel (e.g. right). By default sound split is disabled, which means that all applications including NVDA will play sounds in both left and right channels. A gesture allows cycling through the various sound split modes: %kc:beginInclude From fb9c5619e854ae825a621d9e4d3c38872a288ec7 Mon Sep 17 00:00:00 2001 From: mltony <34804124+mltony@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:51:55 -0800 Subject: [PATCH 41/44] Update user_docs/en/userGuide.t2t Co-authored-by: Sean Budd --- user_docs/en/userGuide.t2t | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index ba2658b2e1a..500b09f0bf3 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -1966,7 +1966,8 @@ By default sound split is disabled, which means that all applications including A gesture allows cycling through the various sound split modes: %kc:beginInclude || Name | Key | Description | -| Cycle Sound Split Mode | ``NVDA+alt+s`` | Cycles between speech modes. | +| Cycle Sound Split Mode | ``NVDA+alt+s`` | Cycles between sound split modes. | + %kc:endInclude By default this command will cycle between the following modes: From 26f75fd0257a13005ff15deba1327f376e261dea Mon Sep 17 00:00:00 2001 From: Sean Budd Date: Wed, 6 Mar 2024 15:35:50 +1100 Subject: [PATCH 42/44] Update source/gui/settingsDialogs.py --- 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 84e7bdc9f72..c05b3fe5e7a 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -2719,7 +2719,7 @@ def makeSettings(self, settingsSizer: wx.BoxSizer) -> None: self.soundVolSlider.SetValue(config.conf["audio"]["soundVolume"]) # Translators: This is a label for the sound split combo box in the Audio Settings dialog. - soundSplitLabelText = _("Sound split mode:") + soundSplitLabelText = _("&Sound split mode:") self.soundSplitComboBox = sHelper.addLabeledControl( soundSplitLabelText, wx.Choice, From e3227cac4fa68d2730cba215702ff28fa43b7ead Mon Sep 17 00:00:00 2001 From: Sean Budd Date: Wed, 6 Mar 2024 15:37:27 +1100 Subject: [PATCH 43/44] fixup changes --- user_docs/en/changes.t2t | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index 736aaf2e77c..b52cae2699c 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -25,7 +25,10 @@ What's New in NVDA - - Added support for the BrailleEdgeS2 braille device. (#16033) - NVDA will keep the audio device awake after speech stops, in order to prevent the start of the next speech being clipped with some audio devices such as Bluetooth headphones. (#14386, @jcsteh, @mltony) -- Sound split: allows to split NVDA sounds in one channel (e.g. left) while sounds from all other applications in the other channel (e.g. right) - toggled by ``NVDA+alt+s`` (#12985, @mltony) +- Sound split: (#12985, @mltony) + - Allows splitting NVDA sounds in one channel (e.g. left) while sounds from all other applications in the other channel (e.g. right). + - Toggled by ``NVDA+alt+s``. + - - From 9c109d3283e9a757118f5fe7aacd1c95dc292c6f Mon Sep 17 00:00:00 2001 From: Sean Budd Date: Wed, 6 Mar 2024 15:37:53 +1100 Subject: [PATCH 44/44] Update user_docs/en/changes.t2t --- user_docs/en/changes.t2t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index b52cae2699c..5ee9ee60ae1 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -25,7 +25,7 @@ What's New in NVDA - - Added support for the BrailleEdgeS2 braille device. (#16033) - NVDA will keep the audio device awake after speech stops, in order to prevent the start of the next speech being clipped with some audio devices such as Bluetooth headphones. (#14386, @jcsteh, @mltony) -- Sound split: (#12985, @mltony) +- Sound split: (#12985, @mltony) - Allows splitting NVDA sounds in one channel (e.g. left) while sounds from all other applications in the other channel (e.g. right). - Toggled by ``NVDA+alt+s``. -