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 25 static HRESULT __stdcall Volume_FindMixerControl(CSysTray * pSysTray) 26 { 27 MMRESULT result; 28 UINT mixerId = 0; 29 DWORD waveOutId = 0; 30 DWORD param2 = 0; 31 32 TRACE("Volume_FindDefaultMixerID\n"); 33 34 result = waveOutMessage((HWAVEOUT)UlongToHandle(WAVE_MAPPER), DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR)&waveOutId, (DWORD_PTR)¶m2); 35 if (result) 36 return E_FAIL; 37 38 if (waveOutId == (DWORD)-1) 39 { 40 TRACE("WARNING: waveOut has no default device, trying with first available device...\n", waveOutId); 41 42 mixerId = 0; 43 } 44 else 45 { 46 TRACE("waveOut default device is %d\n", waveOutId); 47 48 result = mixerGetID((HMIXEROBJ)UlongToHandle(waveOutId), &mixerId, MIXER_OBJECTF_WAVEOUT); 49 if (result) 50 return E_FAIL; 51 52 TRACE("mixerId for waveOut default device is %d\n", mixerId); 53 } 54 55 g_mixerId = mixerId; 56 return S_OK; 57 58 MIXERCAPS mixerCaps; 59 MIXERLINE mixerLine; 60 MIXERCONTROL mixerControl; 61 MIXERLINECONTROLS mixerLineControls; 62 63 g_mixerLineID = -1; 64 g_muteControlID = -1; 65 66 if (mixerGetDevCapsW(g_mixerId, &mixerCaps, sizeof(mixerCaps))) 67 return E_FAIL; 68 69 if (mixerCaps.cDestinations == 0) 70 return S_FALSE; 71 72 TRACE("mixerCaps.cDestinations %d\n", mixerCaps.cDestinations); 73 74 DWORD idx; 75 for (idx = 0; idx < mixerCaps.cDestinations; idx++) 76 { 77 mixerLine.cbStruct = sizeof(mixerLine); 78 mixerLine.dwDestination = idx; 79 if (!mixerGetLineInfoW((HMIXEROBJ)UlongToHandle(g_mixerId), &mixerLine, 0)) 80 { 81 if (mixerLine.dwComponentType >= MIXERLINE_COMPONENTTYPE_DST_SPEAKERS && 82 mixerLine.dwComponentType <= MIXERLINE_COMPONENTTYPE_DST_HEADPHONES) 83 break; 84 TRACE("Destination %d was not speakers or headphones.\n"); 85 } 86 } 87 88 if (idx >= mixerCaps.cDestinations) 89 return E_FAIL; 90 91 TRACE("Valid destination %d found.\n"); 92 93 g_mixerLineID = mixerLine.dwLineID; 94 95 mixerLineControls.cbStruct = sizeof(mixerLineControls); 96 mixerLineControls.dwLineID = mixerLine.dwLineID; 97 mixerLineControls.cControls = 1; 98 mixerLineControls.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE; 99 mixerLineControls.pamxctrl = &mixerControl; 100 mixerLineControls.cbmxctrl = sizeof(mixerControl); 101 102 if (mixerGetLineControlsW((HMIXEROBJ)UlongToHandle(g_mixerId), &mixerLineControls, MIXER_GETLINECONTROLSF_ONEBYTYPE)) 103 return E_FAIL; 104 105 TRACE("Found control id %d for mute: %d\n", mixerControl.dwControlID); 106 107 g_muteControlID = mixerControl.dwControlID; 108 109 return S_OK; 110 } 111 112 HRESULT Volume_IsMute() 113 { 114 #if 0 115 MIXERCONTROLDETAILS mixerControlDetails; 116 117 if (g_mixerId != (UINT)-1 && g_muteControlID != (DWORD)-1) 118 { 119 BOOL detailsResult = 0; 120 mixerControlDetails.cbStruct = sizeof(mixerControlDetails); 121 mixerControlDetails.hwndOwner = 0; 122 mixerControlDetails.dwControlID = g_muteControlID; 123 mixerControlDetails.cChannels = 1; 124 mixerControlDetails.paDetails = &detailsResult; 125 mixerControlDetails.cbDetails = sizeof(detailsResult); 126 if (mixerGetControlDetailsW((HMIXEROBJ) g_mixerId, &mixerControlDetails, 0)) 127 return E_FAIL; 128 129 TRACE("Obtained mute status %d\n", detailsResult); 130 131 g_IsMute = detailsResult != 0; 132 } 133 #endif 134 return S_OK; 135 } 136 137 HRESULT STDMETHODCALLTYPE Volume_Init(_In_ CSysTray * pSysTray) 138 { 139 HRESULT hr; 140 WCHAR strTooltip[128]; 141 142 TRACE("Volume_Init\n"); 143 144 if (!g_hMixer) 145 { 146 hr = Volume_FindMixerControl(pSysTray); 147 if (FAILED(hr)) 148 return hr; 149 150 g_mmDeviceChange = RegisterWindowMessageW(L"winmm_devicechange"); 151 } 152 153 g_hIconVolume = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_VOLUME)); 154 g_hIconMute = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_VOLMUTE)); 155 156 Volume_IsMute(); 157 158 HICON icon; 159 if (g_IsMute) 160 icon = g_hIconMute; 161 else 162 icon = g_hIconVolume; 163 164 LoadStringW(g_hInstance, IDS_VOL_VOLUME, strTooltip, _countof(strTooltip)); 165 return pSysTray->NotifyIcon(NIM_ADD, ID_ICON_VOLUME, icon, strTooltip); 166 } 167 168 HRESULT STDMETHODCALLTYPE Volume_Update(_In_ CSysTray * pSysTray) 169 { 170 BOOL PrevState; 171 172 TRACE("Volume_Update\n"); 173 174 PrevState = g_IsMute; 175 Volume_IsMute(); 176 177 if (PrevState != g_IsMute) 178 { 179 WCHAR strTooltip[128]; 180 HICON icon; 181 if (g_IsMute) 182 { 183 icon = g_hIconMute; 184 LoadStringW(g_hInstance, IDS_VOL_MUTED, strTooltip, _countof(strTooltip)); 185 } 186 else 187 { 188 icon = g_hIconVolume; 189 LoadStringW(g_hInstance, IDS_VOL_VOLUME, strTooltip, _countof(strTooltip)); 190 } 191 192 return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_VOLUME, icon, strTooltip); 193 } 194 else 195 { 196 return S_OK; 197 } 198 } 199 200 HRESULT STDMETHODCALLTYPE Volume_Shutdown(_In_ CSysTray * pSysTray) 201 { 202 TRACE("Volume_Shutdown\n"); 203 204 return pSysTray->NotifyIcon(NIM_DELETE, ID_ICON_VOLUME, NULL, NULL); 205 } 206 207 HRESULT Volume_OnDeviceChange(_In_ CSysTray * pSysTray, WPARAM wParam, LPARAM lParam) 208 { 209 return Volume_FindMixerControl(pSysTray); 210 } 211 212 static void _RunVolume(BOOL bTray) 213 { 214 ShellExecuteW(NULL, 215 NULL, 216 L"sndvol32.exe", 217 bTray ? L"/t" : NULL, 218 NULL, 219 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 SetMenuDefaultItem(hPopup, IDS_VOL_OPEN, FALSE); 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(FALSE); 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 == VOLUME_SERVICE_FLAG) 271 { 272 if (lParam) 273 { 274 pSysTray->EnableService(VOLUME_SERVICE_FLAG, TRUE); 275 return Volume_Init(pSysTray); 276 } 277 else 278 { 279 pSysTray->EnableService(VOLUME_SERVICE_FLAG, FALSE); 280 return Volume_Shutdown(pSysTray); 281 } 282 } 283 return S_FALSE; 284 285 case WM_USER + 221: 286 TRACE("Volume_Message: WM_USER+221\n"); 287 if (wParam == VOLUME_SERVICE_FLAG) 288 { 289 lResult = (LRESULT)pSysTray->IsServiceEnabled(VOLUME_SERVICE_FLAG); 290 return S_OK; 291 } 292 return S_FALSE; 293 294 case WM_TIMER: 295 if (wParam == VOLUME_TIMER_ID) 296 { 297 KillTimer(pSysTray->GetHWnd(), VOLUME_TIMER_ID); 298 _RunVolume(TRUE); 299 } 300 break; 301 302 case ID_ICON_VOLUME: 303 TRACE("Volume_Message uMsg=%d, w=%x, l=%x\n", uMsg, wParam, lParam); 304 305 Volume_Update(pSysTray); 306 307 switch (lParam) 308 { 309 case WM_LBUTTONDOWN: 310 SetTimer(pSysTray->GetHWnd(), VOLUME_TIMER_ID, GetDoubleClickTime(), NULL); 311 break; 312 313 case WM_LBUTTONUP: 314 break; 315 316 case WM_LBUTTONDBLCLK: 317 KillTimer(pSysTray->GetHWnd(), VOLUME_TIMER_ID); 318 _RunVolume(FALSE); 319 break; 320 321 case WM_RBUTTONDOWN: 322 break; 323 324 case WM_RBUTTONUP: 325 _ShowContextMenu(pSysTray); 326 break; 327 328 case WM_RBUTTONDBLCLK: 329 break; 330 331 case WM_MOUSEMOVE: 332 break; 333 } 334 return S_OK; 335 336 default: 337 TRACE("Volume_Message received for unknown ID %d, ignoring.\n"); 338 return S_FALSE; 339 } 340 341 return S_FALSE; 342 } 343