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