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