xref: /reactos/dll/shellext/stobject/volume.cpp (revision bae2bac6)
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)&param2);
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