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