xref: /reactos/dll/shellext/stobject/volume.cpp (revision b6a0ef10)
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 
Volume_FindMixerControl(CSysTray * pSysTray)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 
Volume_IsMute()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 
Volume_Init(_In_ CSysTray * pSysTray)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 
Volume_Update(_In_ CSysTray * pSysTray)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 
Volume_Shutdown(_In_ CSysTray * pSysTray)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 
Volume_OnDeviceChange(_In_ CSysTray * pSysTray,WPARAM wParam,LPARAM lParam)206 HRESULT Volume_OnDeviceChange(_In_ CSysTray * pSysTray, WPARAM wParam, LPARAM lParam)
207 {
208     return Volume_FindMixerControl(pSysTray);
209 }
210 
_RunVolume(BOOL bTray)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 
_RunMMCpl()221 static void _RunMMCpl()
222 {
223     ShellExecuteW(NULL, NULL, L"mmsys.cpl", NULL, NULL, SW_NORMAL);
224 }
225 
_ShowContextMenu(CSysTray * pSysTray)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 
Volume_Message(_In_ CSysTray * pSysTray,UINT uMsg,WPARAM wParam,LPARAM lParam,LRESULT & lResult)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