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