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 MIXERCONTROLDETAILS mixerControlDetails; 115 116 if (g_mixerId != (UINT)-1 && g_muteControlID != (DWORD)-1) 117 { 118 BOOL detailsResult = 0; 119 mixerControlDetails.cbStruct = sizeof(mixerControlDetails); 120 mixerControlDetails.hwndOwner = 0; 121 mixerControlDetails.dwControlID = g_muteControlID; 122 mixerControlDetails.cChannels = 1; 123 mixerControlDetails.paDetails = &detailsResult; 124 mixerControlDetails.cbDetails = sizeof(detailsResult); 125 if (mixerGetControlDetailsW((HMIXEROBJ)UlongToHandle(g_mixerId), &mixerControlDetails, 0)) 126 return E_FAIL; 127 128 TRACE("Obtained mute status %d\n", detailsResult); 129 130 g_IsMute = detailsResult != 0; 131 } 132 133 return S_OK; 134 } 135 136 HRESULT STDMETHODCALLTYPE Volume_Init(_In_ CSysTray * pSysTray) 137 { 138 HRESULT hr; 139 WCHAR strTooltip[128]; 140 141 TRACE("Volume_Init\n"); 142 143 if (!g_hMixer) 144 { 145 hr = Volume_FindMixerControl(pSysTray); 146 if (FAILED(hr)) 147 return hr; 148 149 g_mmDeviceChange = RegisterWindowMessageW(L"winmm_devicechange"); 150 } 151 152 g_hIconVolume = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_VOLUME)); 153 g_hIconMute = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_VOLMUTE)); 154 155 Volume_IsMute(); 156 157 HICON icon; 158 if (g_IsMute) 159 icon = g_hIconMute; 160 else 161 icon = g_hIconVolume; 162 163 LoadStringW(g_hInstance, IDS_VOL_VOLUME, strTooltip, _countof(strTooltip)); 164 return pSysTray->NotifyIcon(NIM_ADD, ID_ICON_VOLUME, icon, strTooltip); 165 } 166 167 HRESULT STDMETHODCALLTYPE Volume_Update(_In_ CSysTray * pSysTray) 168 { 169 BOOL PrevState; 170 171 TRACE("Volume_Update\n"); 172 173 PrevState = g_IsMute; 174 Volume_IsMute(); 175 176 if (PrevState != g_IsMute) 177 { 178 WCHAR strTooltip[128]; 179 HICON icon; 180 if (g_IsMute) 181 { 182 icon = g_hIconMute; 183 LoadStringW(g_hInstance, IDS_VOL_MUTED, strTooltip, _countof(strTooltip)); 184 } 185 else 186 { 187 icon = g_hIconVolume; 188 LoadStringW(g_hInstance, IDS_VOL_VOLUME, strTooltip, _countof(strTooltip)); 189 } 190 191 return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_VOLUME, icon, strTooltip); 192 } 193 else 194 { 195 return S_OK; 196 } 197 } 198 199 HRESULT STDMETHODCALLTYPE Volume_Shutdown(_In_ CSysTray * pSysTray) 200 { 201 TRACE("Volume_Shutdown\n"); 202 203 return pSysTray->NotifyIcon(NIM_DELETE, ID_ICON_VOLUME, NULL, NULL); 204 } 205 206 HRESULT Volume_OnDeviceChange(_In_ CSysTray * pSysTray, WPARAM wParam, LPARAM lParam) 207 { 208 return Volume_FindMixerControl(pSysTray); 209 } 210 211 static void _RunVolume(BOOL bTray) 212 { 213 ShellExecuteW(NULL, 214 NULL, 215 L"sndvol32.exe", 216 bTray ? L"/t" : NULL, 217 NULL, 218 SW_SHOWNORMAL); 219 } 220 221 static void _RunMMCpl() 222 { 223 ShellExecuteW(NULL, NULL, L"mmsys.cpl", NULL, NULL, SW_NORMAL); 224 } 225 226 static void _ShowContextMenu(CSysTray * pSysTray) 227 { 228 WCHAR strAdjust[128]; 229 WCHAR strOpen[128]; 230 LoadStringW(g_hInstance, IDS_VOL_OPEN, strOpen, _countof(strOpen)); 231 LoadStringW(g_hInstance, IDS_VOL_ADJUST, strAdjust, _countof(strAdjust)); 232 233 HMENU hPopup = CreatePopupMenu(); 234 AppendMenuW(hPopup, MF_STRING, IDS_VOL_OPEN, strOpen); 235 AppendMenuW(hPopup, MF_STRING, IDS_VOL_ADJUST, strAdjust); 236 SetMenuDefaultItem(hPopup, IDS_VOL_OPEN, FALSE); 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 == VOLUME_SERVICE_FLAG) 270 { 271 if (lParam) 272 { 273 pSysTray->EnableService(VOLUME_SERVICE_FLAG, TRUE); 274 return Volume_Init(pSysTray); 275 } 276 else 277 { 278 pSysTray->EnableService(VOLUME_SERVICE_FLAG, FALSE); 279 return Volume_Shutdown(pSysTray); 280 } 281 } 282 return S_FALSE; 283 284 case WM_USER + 221: 285 TRACE("Volume_Message: WM_USER+221\n"); 286 if (wParam == VOLUME_SERVICE_FLAG) 287 { 288 lResult = (LRESULT)pSysTray->IsServiceEnabled(VOLUME_SERVICE_FLAG); 289 return S_OK; 290 } 291 return S_FALSE; 292 293 case WM_TIMER: 294 if (wParam == VOLUME_TIMER_ID) 295 { 296 KillTimer(pSysTray->GetHWnd(), VOLUME_TIMER_ID); 297 _RunVolume(TRUE); 298 } 299 break; 300 301 case ID_ICON_VOLUME: 302 TRACE("Volume_Message uMsg=%d, w=%x, l=%x\n", uMsg, wParam, lParam); 303 304 Volume_Update(pSysTray); 305 306 switch (lParam) 307 { 308 case WM_LBUTTONDOWN: 309 SetTimer(pSysTray->GetHWnd(), VOLUME_TIMER_ID, GetDoubleClickTime(), NULL); 310 break; 311 312 case WM_LBUTTONUP: 313 break; 314 315 case WM_LBUTTONDBLCLK: 316 KillTimer(pSysTray->GetHWnd(), VOLUME_TIMER_ID); 317 _RunVolume(FALSE); 318 break; 319 320 case WM_RBUTTONDOWN: 321 break; 322 323 case WM_RBUTTONUP: 324 _ShowContextMenu(pSysTray); 325 break; 326 327 case WM_RBUTTONDBLCLK: 328 break; 329 330 case WM_MOUSEMOVE: 331 break; 332 } 333 return S_OK; 334 335 default: 336 TRACE("Volume_Message received for unknown ID %d, ignoring.\n"); 337 return S_FALSE; 338 } 339 340 return S_FALSE; 341 } 342