xref: /reactos/dll/cpl/mmsys/volume.c (revision 5bb277a5)
1 /*
2  * PROJECT:         ReactOS Multimedia Control Panel
3  * FILE:            dll/cpl/mmsys/volume.c
4  * PURPOSE:         ReactOS Multimedia Control Panel
5  * PROGRAMMER:      Thomas Weidenmueller <w3seek@reactos.com>
6  *                  Johannes Anderwald <janderwald@reactos.com>
7  *                  Dmitry Chapyshev <dmitry@reactos.org>
8  */
9 
10 #include "mmsys.h"
11 
12 #include <shellapi.h>
13 
14 #define VOLUME_MIN        0
15 #define VOLUME_MAX      500
16 #define VOLUME_TICFREQ   50
17 #define VOLUME_PAGESIZE 100
18 
19 typedef struct _IMGINFO
20 {
21     HBITMAP hBitmap;
22     INT cxSource;
23     INT cySource;
24 } IMGINFO, *PIMGINFO;
25 
26 
27 typedef struct _GLOBAL_DATA
28 {
29     HMIXER hMixer;
30     HICON hIconMuted;
31     HICON hIconUnMuted;
32     HICON hIconNoHW;
33 
34     LONG muteVal;
35     DWORD muteControlID;
36 
37     DWORD volumeControlID;
38     DWORD volumeMinimum;
39     DWORD volumeMaximum;
40     DWORD volumeValue;
41     DWORD volumeStep;
42 } GLOBAL_DATA, *PGLOBAL_DATA;
43 
44 
45 static VOID
46 InitImageInfo(PIMGINFO ImgInfo)
47 {
48     BITMAP bitmap;
49 
50     ZeroMemory(ImgInfo, sizeof(*ImgInfo));
51 
52     ImgInfo->hBitmap = LoadImage(hApplet,
53                                  MAKEINTRESOURCE(IDB_SPEAKIMG),
54                                  IMAGE_BITMAP,
55                                  0,
56                                  0,
57                                  LR_DEFAULTCOLOR);
58 
59     if (ImgInfo->hBitmap != NULL)
60     {
61         GetObject(ImgInfo->hBitmap, sizeof(BITMAP), &bitmap);
62 
63         ImgInfo->cxSource = bitmap.bmWidth;
64         ImgInfo->cySource = bitmap.bmHeight;
65     }
66 }
67 
68 
69 VOID
70 GetMuteControl(PGLOBAL_DATA pGlobalData)
71 {
72     MIXERLINE mxln;
73     MIXERCONTROL mxc;
74     MIXERLINECONTROLS mxlctrl;
75 
76     if (pGlobalData->hMixer == NULL)
77         return;
78 
79     mxln.cbStruct = sizeof(MIXERLINE);
80     mxln.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
81 
82     if (mixerGetLineInfo((HMIXEROBJ)pGlobalData->hMixer, &mxln, MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE)
83         != MMSYSERR_NOERROR) return;
84 
85     mxlctrl.cbStruct = sizeof(MIXERLINECONTROLS);
86     mxlctrl.dwLineID = mxln.dwLineID;
87     mxlctrl.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
88     mxlctrl.cControls = 1;
89     mxlctrl.cbmxctrl = sizeof(MIXERCONTROL);
90     mxlctrl.pamxctrl = &mxc;
91 
92     if (mixerGetLineControls((HMIXEROBJ)pGlobalData->hMixer, &mxlctrl, MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE)
93         != MMSYSERR_NOERROR) return;
94 
95     pGlobalData->muteControlID = mxc.dwControlID;
96 }
97 
98 
99 VOID
100 GetMuteState(PGLOBAL_DATA pGlobalData)
101 {
102     MIXERCONTROLDETAILS_BOOLEAN mxcdMute;
103     MIXERCONTROLDETAILS mxcd;
104 
105     if (pGlobalData->hMixer == NULL)
106         return;
107 
108     mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
109     mxcd.dwControlID = pGlobalData->muteControlID;
110     mxcd.cChannels = 1;
111     mxcd.cMultipleItems = 0;
112     mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
113     mxcd.paDetails = &mxcdMute;
114 
115     if (mixerGetControlDetails((HMIXEROBJ)pGlobalData->hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_GETCONTROLDETAILSF_VALUE)
116         != MMSYSERR_NOERROR)
117         return;
118 
119     pGlobalData->muteVal = mxcdMute.fValue;
120 }
121 
122 
123 VOID
124 SwitchMuteState(PGLOBAL_DATA pGlobalData)
125 {
126     MIXERCONTROLDETAILS_BOOLEAN mxcdMute;
127     MIXERCONTROLDETAILS mxcd;
128 
129     mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
130     mxcd.dwControlID = pGlobalData->muteControlID;
131     mxcd.cChannels = 1;
132     mxcd.cMultipleItems = 0;
133     mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
134     mxcd.paDetails = &mxcdMute;
135 
136     mxcdMute.fValue = !pGlobalData->muteVal;
137     if (mixerSetControlDetails((HMIXEROBJ)pGlobalData->hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE)
138         != MMSYSERR_NOERROR)
139         return;
140 
141     pGlobalData->muteVal = mxcdMute.fValue;
142 }
143 
144 
145 VOID
146 GetVolumeControl(PGLOBAL_DATA pGlobalData)
147 {
148     MIXERLINE mxln;
149     MIXERCONTROL mxc;
150     MIXERLINECONTROLS mxlc;
151 
152     if (pGlobalData->hMixer == NULL)
153         return;
154 
155     mxln.cbStruct = sizeof(MIXERLINE);
156     mxln.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
157     if (mixerGetLineInfo((HMIXEROBJ)pGlobalData->hMixer, &mxln, MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE)
158         != MMSYSERR_NOERROR)
159         return;
160 
161     mxlc.cbStruct = sizeof(MIXERLINECONTROLS);
162     mxlc.dwLineID = mxln.dwLineID;
163     mxlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
164     mxlc.cControls = 1;
165     mxlc.cbmxctrl = sizeof(MIXERCONTROL);
166     mxlc.pamxctrl = &mxc;
167     if (mixerGetLineControls((HMIXEROBJ)pGlobalData->hMixer, &mxlc, MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE)
168         != MMSYSERR_NOERROR)
169         return;
170 
171     pGlobalData->volumeMinimum = mxc.Bounds.dwMinimum;
172     pGlobalData->volumeMaximum = mxc.Bounds.dwMaximum;
173     pGlobalData->volumeControlID = mxc.dwControlID;
174     pGlobalData->volumeStep = (pGlobalData->volumeMaximum - pGlobalData->volumeMinimum) / (VOLUME_MAX - VOLUME_MIN);
175 }
176 
177 
178 VOID
179 GetVolumeValue(PGLOBAL_DATA pGlobalData)
180 {
181     MIXERCONTROLDETAILS_UNSIGNED mxcdVolume;
182     MIXERCONTROLDETAILS mxcd;
183 
184     if (pGlobalData->hMixer == NULL)
185         return;
186 
187     mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
188     mxcd.dwControlID = pGlobalData->volumeControlID;
189     mxcd.cChannels = 1;
190     mxcd.cMultipleItems = 0;
191     mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
192     mxcd.paDetails = &mxcdVolume;
193 
194     if (mixerGetControlDetails((HMIXEROBJ)pGlobalData->hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_GETCONTROLDETAILSF_VALUE)
195         != MMSYSERR_NOERROR)
196         return;
197 
198     pGlobalData->volumeValue = mxcdVolume.dwValue;
199 }
200 
201 
202 VOID
203 SetVolumeValue(PGLOBAL_DATA pGlobalData){
204     MIXERCONTROLDETAILS_UNSIGNED mxcdVolume;
205     MIXERCONTROLDETAILS mxcd;
206 
207     if (pGlobalData->hMixer == NULL)
208         return;
209 
210     mxcdVolume.dwValue = pGlobalData->volumeValue;
211     mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
212     mxcd.dwControlID = pGlobalData->volumeControlID;
213     mxcd.cChannels = 1;
214     mxcd.cMultipleItems = 0;
215     mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
216     mxcd.paDetails = &mxcdVolume;
217 
218     if (mixerSetControlDetails((HMIXEROBJ)pGlobalData->hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE)
219         != MMSYSERR_NOERROR)
220         return;
221 
222     pGlobalData->volumeValue = mxcdVolume.dwValue;
223 }
224 
225 
226 static
227 VOID
228 SetSystrayVolumeIconState(BOOL bEnabled)
229 {
230     HWND hwndTaskBar;
231 
232     hwndTaskBar = FindWindowW(L"SystemTray_Main", NULL);
233     if (hwndTaskBar == NULL)
234         return;
235 
236     SendMessageW(hwndTaskBar, WM_USER + 220, 4, bEnabled);
237 }
238 
239 static
240 BOOL
241 GetSystrayVolumeIconState(VOID)
242 {
243     HWND hwndTaskBar;
244 
245     hwndTaskBar = FindWindowW(L"SystemTray_Main", NULL);
246     if (hwndTaskBar == NULL)
247     {
248         return FALSE;
249     }
250 
251     return (BOOL)SendMessageW(hwndTaskBar, WM_USER + 221, 4, 0);
252 }
253 
254 VOID
255 InitVolumeControls(HWND hwndDlg, PGLOBAL_DATA pGlobalData)
256 {
257     UINT NumMixers;
258     MIXERCAPS mxc;
259     TCHAR szNoDevices[256];
260 
261     CheckDlgButton(hwndDlg,
262                    IDC_ICON_IN_TASKBAR,
263                    GetSystrayVolumeIconState() ? BST_CHECKED : BST_UNCHECKED);
264 
265     LoadString(hApplet, IDS_NO_DEVICES, szNoDevices, _countof(szNoDevices));
266 
267     NumMixers = mixerGetNumDevs();
268     if (!NumMixers)
269     {
270         EnableWindow(GetDlgItem(hwndDlg, IDC_VOLUME_TRACKBAR), FALSE);
271         EnableWindow(GetDlgItem(hwndDlg, IDC_MUTE_CHECKBOX),   FALSE);
272         EnableWindow(GetDlgItem(hwndDlg, IDC_ADVANCED_BTN),    FALSE);
273         EnableWindow(GetDlgItem(hwndDlg, IDC_SPEAKER_VOL_BTN), FALSE);
274         EnableWindow(GetDlgItem(hwndDlg, IDC_ADVANCED2_BTN),   FALSE);
275         SendDlgItemMessage(hwndDlg, IDC_MUTE_ICON, STM_SETIMAGE, IMAGE_ICON, (LPARAM)pGlobalData->hIconNoHW);
276         SetDlgItemText(hwndDlg, IDC_DEVICE_NAME, szNoDevices);
277         return;
278     }
279 
280     if (mixerOpen(&pGlobalData->hMixer, 0, PtrToUlong(hwndDlg), 0, MIXER_OBJECTF_MIXER | CALLBACK_WINDOW) != MMSYSERR_NOERROR)
281     {
282         MessageBox(hwndDlg, _T("Cannot open mixer"), NULL, MB_OK);
283         return;
284     }
285 
286     ZeroMemory(&mxc, sizeof(MIXERCAPS));
287     if (mixerGetDevCaps(PtrToUint(pGlobalData->hMixer), &mxc, sizeof(MIXERCAPS)) != MMSYSERR_NOERROR)
288     {
289         MessageBox(hwndDlg, _T("mixerGetDevCaps failed"), NULL, MB_OK);
290         return;
291     }
292 
293     GetMuteControl(pGlobalData);
294     GetMuteState(pGlobalData);
295     if (pGlobalData->muteVal)
296     {
297         SendDlgItemMessage(hwndDlg, IDC_MUTE_CHECKBOX, BM_SETCHECK, (WPARAM)BST_CHECKED, (LPARAM)0);
298         SendDlgItemMessage(hwndDlg, IDC_MUTE_ICON, STM_SETIMAGE, IMAGE_ICON, (LPARAM)pGlobalData->hIconMuted);
299     }
300     else
301     {
302         SendDlgItemMessage(hwndDlg, IDC_MUTE_CHECKBOX, BM_SETCHECK, (WPARAM)BST_UNCHECKED, (LPARAM)0);
303         SendDlgItemMessage(hwndDlg, IDC_MUTE_ICON, STM_SETIMAGE, IMAGE_ICON, (LPARAM)pGlobalData->hIconUnMuted);
304     }
305 
306     GetVolumeControl(pGlobalData);
307     GetVolumeValue(pGlobalData);
308 
309     SendDlgItemMessage(hwndDlg, IDC_DEVICE_NAME, WM_SETTEXT, 0, (LPARAM)mxc.szPname);
310     SendDlgItemMessage(hwndDlg, IDC_VOLUME_TRACKBAR, TBM_SETRANGE, (WPARAM)TRUE, (LPARAM)MAKELONG(VOLUME_MIN, VOLUME_MAX));
311     SendDlgItemMessage(hwndDlg, IDC_VOLUME_TRACKBAR, TBM_SETTICFREQ, VOLUME_TICFREQ, 0);
312     SendDlgItemMessage(hwndDlg, IDC_VOLUME_TRACKBAR, TBM_SETPAGESIZE, 0, VOLUME_PAGESIZE);
313     SendDlgItemMessage(hwndDlg, IDC_VOLUME_TRACKBAR, TBM_SETPOS, (WPARAM)TRUE, (LPARAM)(pGlobalData->volumeValue - pGlobalData->volumeMinimum) / pGlobalData->volumeStep);
314 }
315 
316 VOID
317 SaveData(HWND hwndDlg)
318 {
319     BOOL bShowIcon;
320 
321     bShowIcon = (IsDlgButtonChecked(hwndDlg, IDC_ICON_IN_TASKBAR) == BST_CHECKED);
322 
323     SetSystrayVolumeIconState(!bShowIcon);
324 }
325 
326 VOID
327 LaunchSoundControl(HWND hwndDlg)
328 {
329     if ((INT_PTR)ShellExecuteW(NULL, L"open", L"sndvol32.exe", NULL, NULL, SW_SHOWNORMAL) > 32)
330         return;
331     MessageBox(hwndDlg, _T("Cannot run sndvol32.exe"), NULL, MB_OK);
332 }
333 
334 /* Volume property page dialog callback */
335 //static INT_PTR CALLBACK
336 INT_PTR CALLBACK
337 VolumeDlgProc(HWND hwndDlg,
338               UINT uMsg,
339               WPARAM wParam,
340               LPARAM lParam)
341 {
342     static IMGINFO ImgInfo;
343     PGLOBAL_DATA pGlobalData;
344     UNREFERENCED_PARAMETER(lParam);
345     UNREFERENCED_PARAMETER(wParam);
346 
347     pGlobalData = (PGLOBAL_DATA)GetWindowLongPtr(hwndDlg, DWLP_USER);
348 
349     switch(uMsg)
350     {
351         case MM_MIXM_LINE_CHANGE:
352         {
353             GetMuteState(pGlobalData);
354             if (pGlobalData->muteVal)
355             {
356                 SendDlgItemMessage(hwndDlg, IDC_MUTE_CHECKBOX, BM_SETCHECK, (WPARAM)BST_CHECKED, (LPARAM)0);
357                 SendDlgItemMessage(hwndDlg, IDC_MUTE_ICON, STM_SETIMAGE, IMAGE_ICON, (LPARAM)pGlobalData->hIconMuted);
358             }
359             else
360             {
361                 SendDlgItemMessage(hwndDlg, IDC_MUTE_CHECKBOX, BM_SETCHECK, (WPARAM)BST_UNCHECKED, (LPARAM)0);
362                 SendDlgItemMessage(hwndDlg, IDC_MUTE_ICON, STM_SETIMAGE, IMAGE_ICON, (LPARAM)pGlobalData->hIconUnMuted);
363             }
364             break;
365         }
366         case MM_MIXM_CONTROL_CHANGE:
367         {
368             GetVolumeValue(pGlobalData);
369             SendDlgItemMessage(hwndDlg, IDC_VOLUME_TRACKBAR, TBM_SETPOS, (WPARAM)TRUE, (LPARAM)(pGlobalData->volumeValue - pGlobalData->volumeMinimum) / pGlobalData->volumeStep);
370             break;
371         }
372         case WM_INITDIALOG:
373         {
374             pGlobalData = (GLOBAL_DATA*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(GLOBAL_DATA));
375             SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)pGlobalData);
376 
377             pGlobalData->hIconUnMuted = LoadImage(hApplet, MAKEINTRESOURCE(IDI_CPLICON), IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR);
378             pGlobalData->hIconMuted = LoadImage(hApplet, MAKEINTRESOURCE(IDI_MUTED_ICON), IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR);
379             pGlobalData->hIconNoHW = LoadImage(hApplet, MAKEINTRESOURCE(IDI_NO_HW), IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR);
380 
381             InitImageInfo(&ImgInfo);
382             InitVolumeControls(hwndDlg, pGlobalData);
383             break;
384         }
385 
386         case WM_DRAWITEM:
387         {
388             LPDRAWITEMSTRUCT lpDrawItem;
389             lpDrawItem = (LPDRAWITEMSTRUCT) lParam;
390             if(lpDrawItem->CtlID == IDC_SPEAKIMG)
391             {
392                 HDC hdcMem;
393                 LONG left;
394 
395                 /* Position image in centre of dialog */
396                 left = (lpDrawItem->rcItem.right - ImgInfo.cxSource) / 2;
397 
398                 hdcMem = CreateCompatibleDC(lpDrawItem->hDC);
399                 if (hdcMem != NULL)
400                 {
401                     SelectObject(hdcMem, ImgInfo.hBitmap);
402                     BitBlt(lpDrawItem->hDC,
403                            left,
404                            lpDrawItem->rcItem.top,
405                            lpDrawItem->rcItem.right - lpDrawItem->rcItem.left,
406                            lpDrawItem->rcItem.bottom - lpDrawItem->rcItem.top,
407                            hdcMem,
408                            0,
409                            0,
410                            SRCCOPY);
411                     DeleteDC(hdcMem);
412                 }
413             }
414             break;
415         }
416 
417         case WM_COMMAND:
418         {
419             switch (LOWORD(wParam))
420             {
421                 case IDC_MUTE_CHECKBOX:
422                     if (HIWORD(wParam) == BN_CLICKED)
423                     {
424                         SwitchMuteState(pGlobalData);
425                         if (pGlobalData->muteVal)
426                         {
427                             SendDlgItemMessage(hwndDlg, IDC_MUTE_ICON, STM_SETIMAGE, IMAGE_ICON, (LPARAM)pGlobalData->hIconMuted);
428                         }
429                         else
430                         {
431                             SendDlgItemMessage(hwndDlg, IDC_MUTE_ICON, STM_SETIMAGE, IMAGE_ICON, (LPARAM)pGlobalData->hIconUnMuted);
432                         }
433 
434                         PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
435                     }
436                     break;
437 
438                 case IDC_ICON_IN_TASKBAR:
439                     if (HIWORD(wParam) == BN_CLICKED)
440                     {
441                         PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
442                     }
443                     break;
444 
445                 case IDC_ADVANCED_BTN:
446                     LaunchSoundControl(hwndDlg);
447                     break;
448             }
449             break;
450         }
451 
452         case WM_HSCROLL:
453         {
454             HWND hVolumeTrackbar = GetDlgItem(hwndDlg, IDC_VOLUME_TRACKBAR);
455             if (hVolumeTrackbar == (HWND)lParam)
456             {
457                 pGlobalData->volumeValue = ((DWORD)SendDlgItemMessage(hwndDlg, IDC_VOLUME_TRACKBAR, TBM_GETPOS, 0, 0) * pGlobalData->volumeStep) + pGlobalData->volumeMinimum;
458                 SetVolumeValue(pGlobalData);
459 
460                 if (LOWORD(wParam) == TB_ENDTRACK)
461                     PlaySound((LPCTSTR)SND_ALIAS_SYSTEMDEFAULT, NULL, SND_ALIAS_ID | SND_ASYNC);
462             }
463             break;
464         }
465 
466         case WM_DESTROY:
467             mixerClose(pGlobalData->hMixer);
468             DestroyIcon(pGlobalData->hIconMuted);
469             DestroyIcon(pGlobalData->hIconUnMuted);
470             DestroyIcon(pGlobalData->hIconNoHW);
471             HeapFree(GetProcessHeap(), 0, pGlobalData);
472             break;
473 
474         case WM_NOTIFY:
475             if (((LPNMHDR)lParam)->code == (UINT)PSN_APPLY)
476             {
477                 SaveData(hwndDlg);
478             }
479             return TRUE;
480     }
481 
482     return FALSE;
483 }
484