1+ /*
2+ * PROJECT: ReactOS Explorer
3+ * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
4+ * PURPOSE: Backend class for media buttons
5+ * COPYRIGHT: Copyright 2026 Vitaly Orekhov <vkvo2000@vivaldi.net>
6+ */
7+
8+ #include " precomp.h"
9+ #include " mediabtns.h"
10+
11+ CMultimediaBackend::CMultimediaBackend ()
12+ {
13+ m_isWinMMAvailable = false ;
14+
15+ m_hWinMM = LoadLibraryW (L" winmm.dll" );
16+ if (!m_hWinMM)
17+ return ;
18+
19+ if (!WINMM_PROC (mixerOpen, pmxOpen))
20+ return ;
21+
22+ if (!WINMM_PROC (mixerClose, pmxClose))
23+ return ;
24+
25+ if (!WINMM_PROC (mixerSetControlDetails, pmxControlDetails))
26+ return ;
27+
28+ if (!WINMM_PROC (mixerGetControlDetailsW, pmxControlDetails))
29+ return ;
30+
31+ if (!WINMM_PROC (mixerGetLineInfoW, pmxLine))
32+ return ;
33+
34+ if (!WINMM_PROC (mixerGetLineControlsW, pmxLineControls))
35+ return ;
36+
37+ if (!WINMM_PROC (waveOutGetErrorTextW, pwoGetErrText))
38+ return ;
39+ };
40+
41+ CMultimediaBackend::~CMultimediaBackend ()
42+ {
43+ if (m_isWinMMAvailable)
44+ mixerClose (m_hMixer);
45+
46+ CloseHandle (m_hWinMM);
47+ }
48+
49+ void
50+ CMultimediaBackend::GetMasterMixer ()
51+ {
52+ if (m_hMixer != NULL )
53+ mixerClose (m_hMixer);
54+
55+ WCHAR pszWinMMErrText[MAXERRORLENGTH] = {0 };
56+
57+ MMRESULT mmres = mixerOpen (&m_hMixer, 0 , NULL , 0 , MIXER_OBJECTF_MIXER);
58+ if (mmres != MMSYSERR_NOERROR)
59+ {
60+ waveOutGetErrorTextW (mmres, pszWinMMErrText, _countof (pszWinMMErrText));
61+ ERR (" GetMasterMixer: mixerOpen failed: %lu (%S)\n " , mmres, pszWinMMErrText);
62+ return ;
63+ }
64+
65+ MIXERLINEW mxln;
66+ mxln.cbStruct = sizeof (mxln);
67+ mxln.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
68+
69+ mmres = mixerGetLineInfoW ((HMIXEROBJ)m_hMixer, &mxln, MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE);
70+ if (mmres != MMSYSERR_NOERROR)
71+ {
72+ waveOutGetErrorTextW (mmres, pszWinMMErrText, _countof (pszWinMMErrText));
73+ ERR (" GetMasterMixer: mixerGetLineInfoW failed: %lu (%S)\n " , mmres, pszWinMMErrText);
74+ return ;
75+ }
76+
77+ m_dwMasterChannels = mxln.cChannels ;
78+
79+ MIXERLINECONTROLS mxlctrl;
80+ MIXERCONTROLW mxc;
81+ mxlctrl.cbStruct = sizeof (mxlctrl);
82+ mxlctrl.dwLineID = mxln.dwLineID ;
83+ mxlctrl.dwControlID = MIXERCONTROL_CONTROLTYPE_MUTE;
84+ mxlctrl.cControls = 1 ;
85+ mxlctrl.cbmxctrl = sizeof (mxc);
86+ mxlctrl.pamxctrl = &mxc;
87+
88+ mmres = mixerGetLineControlsW ((HMIXEROBJ)m_hMixer, &mxlctrl, MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE);
89+ if (mmres != MMSYSERR_NOERROR)
90+ {
91+ waveOutGetErrorTextW (mmres, pszWinMMErrText, _countof (pszWinMMErrText));
92+ ERR (" GetMasterMixer: mixerGetLineControlsW failed retrieving Master Mute control: %lu (%S)\n " , mmres, pszWinMMErrText);
93+ return ;
94+ }
95+
96+ m_dwMasterMuteControlID = mxc.dwControlID ;
97+
98+ mxlctrl.dwControlID = MIXERCONTROL_CONTROLTYPE_VOLUME;
99+
100+ mmres = mixerGetLineControlsW ((HMIXEROBJ)m_hMixer, &mxlctrl, MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE);
101+ if (mmres != MMSYSERR_NOERROR)
102+ {
103+ waveOutGetErrorTextW (mmres, pszWinMMErrText, _countof (pszWinMMErrText));
104+ ERR (" GetMasterMixer: mixerGetLineControlsW failed retrieving Master Volume control: %lu (%S)\n " , mmres, pszWinMMErrText);
105+ return ;
106+ }
107+
108+ m_dwMasterVolumeID = mxc.dwControlID ;
109+ m_dwMasterRanges[0 ] = mxc.Bounds .dwMinimum ;
110+ m_dwMasterRanges[1 ] = mxc.Bounds .dwMaximum ;
111+ m_dwVolumeStep = (m_dwMasterRanges[1 ] - m_dwMasterRanges[0 ]) / VOLUME_STEPS_COUNT;
112+
113+ TRACE (" GetMasterMixer: Master controls configured successfully:\n "
114+ " Master Mute dwControlID: %lu; Master Volume dwControlID %lu\n "
115+ " Master Volume channels: %lu; Master Volume value range: %lu-%lu\n "
116+ " Master Volume update step: %lu\n " ,
117+ m_dwMasterMuteControlID, m_dwMasterVolumeID,
118+ m_dwMasterChannels, m_dwMasterRanges[0 ], m_dwMasterRanges[1 ],
119+ m_dwVolumeStep);
120+
121+ m_isWinMMAvailable = true ;
122+ }
123+
124+ bool
125+ CMultimediaBackend::Mute ()
126+ {
127+ if (!m_isWinMMAvailable)
128+ {
129+ WARN (" CMultimediaBackend: Mixer is not available; cannot (un)mute sound. Check the prior class constructor calls\n " );
130+ return false ;
131+ }
132+
133+ MIXERCONTROLDETAILS mxcd;
134+ MIXERCONTROLDETAILS_BOOLEAN mxcdMute;
135+
136+ mxcd.cbStruct = sizeof (mxcd);
137+ mxcd.dwControlID = m_dwMasterMuteControlID;
138+ mxcd.cChannels = 1 ;
139+ mxcd.cMultipleItems = 0 ;
140+ mxcd.cbDetails = sizeof (mxcdMute);
141+ mxcd.paDetails = &mxcdMute;
142+
143+ mixerGetControlDetailsW ((HMIXEROBJ)m_hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_GETCONTROLDETAILSF_VALUE);
144+
145+ mxcdMute.fValue = !mxcdMute.fValue ;
146+
147+ mixerSetControlDetails ((HMIXEROBJ)m_hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE);
148+
149+ return true ;
150+ }
151+
152+ bool
153+ CMultimediaBackend::AdjustVolume (_In_ UINT direction, _In_ HANDLE hProcessHeap)
154+ {
155+ if (!m_isWinMMAvailable)
156+ {
157+ WARN (" CMultimediaBackend: Mixer is not available; cannot %s volume. Check the prior class constructor calls\n " ,
158+ direction == APPCOMMAND_VOLUME_DOWN ? " decrease" : " increase" );
159+ return FALSE ;
160+ }
161+
162+ MIXERCONTROLDETAILS mxcd;
163+ mxcd.cbStruct = sizeof (mxcd);
164+ mxcd.cMultipleItems = 0 ;
165+
166+ /* If sound is muted and we want to increase volume, we are unmuting it. */
167+ if (direction == APPCOMMAND_VOLUME_UP)
168+ {
169+ MIXERCONTROLDETAILS_BOOLEAN mxcdMute;
170+
171+ mxcd.cChannels = 1 ;
172+ mxcd.dwControlID = m_dwMasterMuteControlID;
173+ mxcd.cbDetails = sizeof (mxcdMute);
174+ mxcd.paDetails = &mxcdMute;
175+ mxcdMute.fValue = false ;
176+
177+ mixerSetControlDetails ((HMIXEROBJ)m_hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE);
178+ }
179+
180+ PMIXERCONTROLDETAILS_UNSIGNED mxcdVolume =
181+ (PMIXERCONTROLDETAILS_UNSIGNED)HeapAlloc (hProcessHeap,
182+ 0 ,
183+ m_dwMasterChannels * sizeof (*mxcdVolume));
184+
185+ if (mxcdVolume == NULL )
186+ {
187+ ERR (" CMultimediaBackend: HeapAlloc failed, requested %lu bytes\n " , m_dwMasterChannels * sizeof (*mxcdVolume));
188+ return FALSE ;
189+ }
190+
191+ mxcd.cChannels = m_dwMasterChannels;
192+ mxcd.dwControlID = m_dwMasterVolumeID;
193+ mxcd.cbDetails = sizeof (*mxcdVolume);
194+ mxcd.paDetails = mxcdVolume;
195+
196+ mixerGetControlDetailsW ((HMIXEROBJ)m_hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_GETCONTROLDETAILSF_VALUE);
197+
198+ /* TODO: implement restoring balance between channels when all channels were put to 0 */
199+ for (DWORD i = 0 ; i < m_dwMasterChannels; ++i)
200+ {
201+ if (direction == APPCOMMAND_VOLUME_DOWN)
202+ {
203+ /* Protecting from underflow when decreasing volume */
204+ if (mxcdVolume[i].dwValue < m_dwVolumeStep)
205+ mxcdVolume[i].dwValue = 0 ;
206+ else
207+ mxcdVolume[i].dwValue -= m_dwVolumeStep;
208+ }
209+ else
210+ {
211+ /* Protecting from overflow when increasing volume */
212+ if (m_dwMasterRanges[1 ] - mxcdVolume[i].dwValue < m_dwVolumeStep)
213+ mxcdVolume[i].dwValue = m_dwMasterRanges[1 ];
214+ else
215+ mxcdVolume[i].dwValue += m_dwVolumeStep;
216+ }
217+ }
218+
219+ mixerSetControlDetails ((HMIXEROBJ)m_hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE);
220+ HeapFree (hProcessHeap, 0 , mxcdVolume);
221+ return TRUE ;
222+ }
0 commit comments