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() 218 { 219 // FIXME: ensure we are loading the right one 220 ShellExecuteW(NULL, NULL, L"sndvol32.exe", NULL, NULL, SW_SHOWNORMAL); 221 } 222 223 static void _RunMMCpl() 224 { 225 ShellExecuteW(NULL, NULL, L"mmsys.cpl", NULL, NULL, SW_NORMAL); 226 } 227 228 static void _ShowContextMenu(CSysTray * pSysTray) 229 { 230 WCHAR strAdjust[128]; 231 WCHAR strOpen[128]; 232 LoadStringW(g_hInstance, IDS_VOL_OPEN, strOpen, _countof(strOpen)); 233 LoadStringW(g_hInstance, IDS_VOL_ADJUST, strAdjust, _countof(strAdjust)); 234 235 HMENU hPopup = CreatePopupMenu(); 236 AppendMenuW(hPopup, MF_STRING, IDS_VOL_OPEN, strOpen); 237 AppendMenuW(hPopup, MF_STRING, IDS_VOL_ADJUST, strAdjust); 238 239 DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN; 240 POINT pt; 241 SetForegroundWindow(pSysTray->GetHWnd()); 242 GetCursorPos(&pt); 243 244 DWORD id = TrackPopupMenuEx(hPopup, flags, 245 pt.x, pt.y, 246 pSysTray->GetHWnd(), NULL); 247 248 DestroyMenu(hPopup); 249 250 switch (id) 251 { 252 case IDS_VOL_OPEN: 253 _RunVolume(); 254 break; 255 case IDS_VOL_ADJUST: 256 _RunMMCpl(); 257 break; 258 } 259 } 260 261 HRESULT STDMETHODCALLTYPE Volume_Message(_In_ CSysTray * pSysTray, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT &lResult) 262 { 263 if (uMsg == g_mmDeviceChange) 264 return Volume_OnDeviceChange(pSysTray, wParam, lParam); 265 266 switch (uMsg) 267 { 268 case WM_USER + 220: 269 TRACE("Volume_Message: WM_USER+220\n"); 270 if (wParam == 4) 271 { 272 if (lParam == FALSE) 273 return Volume_Init(pSysTray); 274 else 275 return Volume_Shutdown(pSysTray); 276 } 277 return S_FALSE; 278 279 case WM_USER + 221: 280 TRACE("Volume_Message: WM_USER+221\n"); 281 if (wParam == 4) 282 { 283 lResult = (LRESULT)g_IsRunning; 284 return S_OK; 285 } 286 return S_FALSE; 287 288 case ID_ICON_VOLUME: 289 TRACE("Volume_Message uMsg=%d, w=%x, l=%x\n", uMsg, wParam, lParam); 290 291 Volume_Update(pSysTray); 292 293 switch (lParam) 294 { 295 case WM_LBUTTONDOWN: 296 break; 297 298 case WM_LBUTTONUP: 299 TRACE("TODO: display volume slider\n"); 300 break; 301 302 case WM_LBUTTONDBLCLK: 303 _RunVolume(); 304 break; 305 306 case WM_RBUTTONDOWN: 307 break; 308 309 case WM_RBUTTONUP: 310 _ShowContextMenu(pSysTray); 311 break; 312 313 case WM_RBUTTONDBLCLK: 314 break; 315 316 case WM_MOUSEMOVE: 317 break; 318 } 319 return S_OK; 320 321 default: 322 TRACE("Volume_Message received for unknown ID %d, ignoring.\n"); 323 return S_FALSE; 324 } 325 326 return S_FALSE; 327 } 328