1 /* 2 * PROJECT: ReactOS system libraries 3 * LICENSE: GPL - See COPYING in the top level directory 4 * FILE: dll/shellext/stobject/volume.cpp 5 * PURPOSE: Volume notification icon handler 6 * PROGRAMMERS: David Quintana <gigaherz@gmail.com> 7 */ 8 9 #include "precomp.h" 10 11 #include <mmddk.h> 12 13 HICON g_hIconVolume; 14 HICON g_hIconMute; 15 16 HMIXER g_hMixer; 17 UINT g_mixerId; 18 DWORD g_mixerLineID; 19 DWORD g_muteControlID; 20 21 UINT g_mmDeviceChange; 22 23 static BOOL g_IsMute = FALSE; 24 static BOOL g_IsRunning = FALSE; 25 26 static HRESULT __stdcall Volume_FindMixerControl(CSysTray * pSysTray) 27 { 28 MMRESULT result; 29 UINT mixerId = 0; 30 DWORD waveOutId = 0; 31 DWORD param2 = 0; 32 33 TRACE("Volume_FindDefaultMixerID\n"); 34 35 result = waveOutMessage((HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR)&waveOutId, (DWORD_PTR)¶m2); 36 if (result) 37 return E_FAIL; 38 39 if (waveOutId == (DWORD)-1) 40 { 41 TRACE("WARNING: waveOut has no default device, trying with first available device...\n", waveOutId); 42 43 mixerId = 0; 44 } 45 else 46 { 47 TRACE("waveOut default device is %d\n", waveOutId); 48 49 result = mixerGetID((HMIXEROBJ)waveOutId, &mixerId, MIXER_OBJECTF_WAVEOUT); 50 if (result) 51 return E_FAIL; 52 53 TRACE("mixerId for waveOut default device is %d\n", mixerId); 54 } 55 56 g_mixerId = mixerId; 57 return S_OK; 58 59 MIXERCAPS mixerCaps; 60 MIXERLINE mixerLine; 61 MIXERCONTROL mixerControl; 62 MIXERLINECONTROLS mixerLineControls; 63 64 g_mixerLineID = -1; 65 g_muteControlID = -1; 66 67 if (mixerGetDevCapsW(g_mixerId, &mixerCaps, sizeof(mixerCaps))) 68 return E_FAIL; 69 70 if (mixerCaps.cDestinations == 0) 71 return S_FALSE; 72 73 TRACE("mixerCaps.cDestinations %d\n", mixerCaps.cDestinations); 74 75 DWORD idx; 76 for (idx = 0; idx < mixerCaps.cDestinations; idx++) 77 { 78 mixerLine.cbStruct = sizeof(mixerLine); 79 mixerLine.dwDestination = idx; 80 if (!mixerGetLineInfoW((HMIXEROBJ)g_mixerId, &mixerLine, 0)) 81 { 82 if (mixerLine.dwComponentType >= MIXERLINE_COMPONENTTYPE_DST_SPEAKERS && 83 mixerLine.dwComponentType <= MIXERLINE_COMPONENTTYPE_DST_HEADPHONES) 84 break; 85 TRACE("Destination %d was not speakers or headphones.\n"); 86 } 87 } 88 89 if (idx >= mixerCaps.cDestinations) 90 return E_FAIL; 91 92 TRACE("Valid destination %d found.\n"); 93 94 g_mixerLineID = mixerLine.dwLineID; 95 96 mixerLineControls.cbStruct = sizeof(mixerLineControls); 97 mixerLineControls.dwLineID = mixerLine.dwLineID; 98 mixerLineControls.cControls = 1; 99 mixerLineControls.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE; 100 mixerLineControls.pamxctrl = &mixerControl; 101 mixerLineControls.cbmxctrl = sizeof(mixerControl); 102 103 if (mixerGetLineControlsW((HMIXEROBJ)g_mixerId, &mixerLineControls, MIXER_GETLINECONTROLSF_ONEBYTYPE)) 104 return E_FAIL; 105 106 TRACE("Found control id %d for mute: %d\n", mixerControl.dwControlID); 107 108 g_muteControlID = mixerControl.dwControlID; 109 110 return S_OK; 111 } 112 113 HRESULT Volume_IsMute() 114 { 115 #if 0 116 MIXERCONTROLDETAILS mixerControlDetails; 117 118 if (g_mixerId != (UINT)-1 && g_muteControlID != (DWORD)-1) 119 { 120 BOOL detailsResult = 0; 121 mixerControlDetails.cbStruct = sizeof(mixerControlDetails); 122 mixerControlDetails.hwndOwner = 0; 123 mixerControlDetails.dwControlID = g_muteControlID; 124 mixerControlDetails.cChannels = 1; 125 mixerControlDetails.paDetails = &detailsResult; 126 mixerControlDetails.cbDetails = sizeof(detailsResult); 127 if (mixerGetControlDetailsW((HMIXEROBJ) g_mixerId, &mixerControlDetails, 0)) 128 return E_FAIL; 129 130 TRACE("Obtained mute status %d\n", detailsResult); 131 132 g_IsMute = detailsResult != 0; 133 } 134 #endif 135 return S_OK; 136 } 137 138 HRESULT STDMETHODCALLTYPE Volume_Init(_In_ CSysTray * pSysTray) 139 { 140 HRESULT hr; 141 WCHAR strTooltip[128]; 142 143 TRACE("Volume_Init\n"); 144 145 if (!g_hMixer) 146 { 147 hr = Volume_FindMixerControl(pSysTray); 148 if (FAILED(hr)) 149 return hr; 150 151 g_mmDeviceChange = RegisterWindowMessageW(L"winmm_devicechange"); 152 } 153 154 g_hIconVolume = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_VOLUME)); 155 g_hIconMute = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_VOLMUTE)); 156 157 Volume_IsMute(); 158 159 g_IsRunning = TRUE; 160 161 HICON icon; 162 if (g_IsMute) 163 icon = g_hIconMute; 164 else 165 icon = g_hIconVolume; 166 167 LoadStringW(g_hInstance, IDS_VOL_VOLUME, strTooltip, _countof(strTooltip)); 168 return pSysTray->NotifyIcon(NIM_ADD, ID_ICON_VOLUME, icon, strTooltip); 169 } 170 171 HRESULT STDMETHODCALLTYPE Volume_Update(_In_ CSysTray * pSysTray) 172 { 173 BOOL PrevState; 174 175 TRACE("Volume_Update\n"); 176 177 PrevState = g_IsMute; 178 Volume_IsMute(); 179 180 if (PrevState != g_IsMute) 181 { 182 WCHAR strTooltip[128]; 183 HICON icon; 184 if (g_IsMute) 185 { 186 icon = g_hIconMute; 187 LoadStringW(g_hInstance, IDS_VOL_MUTED, strTooltip, _countof(strTooltip)); 188 } 189 else 190 { 191 icon = g_hIconVolume; 192 LoadStringW(g_hInstance, IDS_VOL_VOLUME, strTooltip, _countof(strTooltip)); 193 } 194 195 return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_VOLUME, icon, strTooltip); 196 } 197 else 198 { 199 return S_OK; 200 } 201 } 202 203 HRESULT STDMETHODCALLTYPE Volume_Shutdown(_In_ CSysTray * pSysTray) 204 { 205 TRACE("Volume_Shutdown\n"); 206 207 g_IsRunning = FALSE; 208 209 return pSysTray->NotifyIcon(NIM_DELETE, ID_ICON_VOLUME, NULL, NULL); 210 } 211 212 HRESULT Volume_OnDeviceChange(_In_ CSysTray * pSysTray, WPARAM wParam, LPARAM lParam) 213 { 214 return Volume_FindMixerControl(pSysTray); 215 } 216 217 static void _RunVolume(BOOL bTray) 218 { 219 ShellExecuteW(NULL, NULL, bTray ? L"sndvol32.exe /t" : L"sndvol32.exe", NULL, NULL, SW_SHOWNORMAL); 220 } 221 222 static void _RunMMCpl() 223 { 224 ShellExecuteW(NULL, NULL, L"mmsys.cpl", NULL, NULL, SW_NORMAL); 225 } 226 227 static void _ShowContextMenu(CSysTray * pSysTray) 228 { 229 WCHAR strAdjust[128]; 230 WCHAR strOpen[128]; 231 LoadStringW(g_hInstance, IDS_VOL_OPEN, strOpen, _countof(strOpen)); 232 LoadStringW(g_hInstance, IDS_VOL_ADJUST, strAdjust, _countof(strAdjust)); 233 234 HMENU hPopup = CreatePopupMenu(); 235 AppendMenuW(hPopup, MF_STRING, IDS_VOL_OPEN, strOpen); 236 AppendMenuW(hPopup, MF_STRING, IDS_VOL_ADJUST, strAdjust); 237 238 DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN; 239 POINT pt; 240 SetForegroundWindow(pSysTray->GetHWnd()); 241 GetCursorPos(&pt); 242 243 DWORD id = TrackPopupMenuEx(hPopup, flags, 244 pt.x, pt.y, 245 pSysTray->GetHWnd(), NULL); 246 247 DestroyMenu(hPopup); 248 249 switch (id) 250 { 251 case IDS_VOL_OPEN: 252 _RunVolume(FALSE); 253 break; 254 case IDS_VOL_ADJUST: 255 _RunMMCpl(); 256 break; 257 } 258 } 259 260 HRESULT STDMETHODCALLTYPE Volume_Message(_In_ CSysTray * pSysTray, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT &lResult) 261 { 262 if (uMsg == g_mmDeviceChange) 263 return Volume_OnDeviceChange(pSysTray, wParam, lParam); 264 265 switch (uMsg) 266 { 267 case WM_USER + 220: 268 TRACE("Volume_Message: WM_USER+220\n"); 269 if (wParam == 4) 270 { 271 if (lParam == FALSE) 272 return Volume_Init(pSysTray); 273 else 274 return Volume_Shutdown(pSysTray); 275 } 276 return S_FALSE; 277 278 case WM_USER + 221: 279 TRACE("Volume_Message: WM_USER+221\n"); 280 if (wParam == 4) 281 { 282 lResult = (LRESULT)g_IsRunning; 283 return S_OK; 284 } 285 return S_FALSE; 286 287 case WM_TIMER: 288 if (wParam == VOLUME_TIMER_ID) 289 { 290 KillTimer(pSysTray->GetHWnd(), VOLUME_TIMER_ID); 291 _RunVolume(TRUE); 292 } 293 break; 294 295 case ID_ICON_VOLUME: 296 TRACE("Volume_Message uMsg=%d, w=%x, l=%x\n", uMsg, wParam, lParam); 297 298 Volume_Update(pSysTray); 299 300 switch (lParam) 301 { 302 case WM_LBUTTONDOWN: 303 SetTimer(pSysTray->GetHWnd(), VOLUME_TIMER_ID, GetDoubleClickTime(), NULL); 304 break; 305 306 case WM_LBUTTONUP: 307 break; 308 309 case WM_LBUTTONDBLCLK: 310 KillTimer(pSysTray->GetHWnd(), VOLUME_TIMER_ID); 311 _RunVolume(FALSE); 312 break; 313 314 case WM_RBUTTONDOWN: 315 break; 316 317 case WM_RBUTTONUP: 318 _ShowContextMenu(pSysTray); 319 break; 320 321 case WM_RBUTTONDBLCLK: 322 break; 323 324 case WM_MOUSEMOVE: 325 break; 326 } 327 return S_OK; 328 329 default: 330 TRACE("Volume_Message received for unknown ID %d, ignoring.\n"); 331 return S_FALSE; 332 } 333 334 return S_FALSE; 335 } 336