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