04723a7be2636844be9707ab6f0276aa28b91a00
[reactos.git] / dll / shellext / stobject / volume.cpp
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, NULL, bTray ? L"sndvol32.exe /t" : L"sndvol32.exe", NULL, NULL, SW_SHOWNORMAL);
215 }
216
217 static void _RunMMCpl()
218 {
219 ShellExecuteW(NULL, NULL, L"mmsys.cpl", NULL, NULL, SW_NORMAL);
220 }
221
222 static void _ShowContextMenu(CSysTray * pSysTray)
223 {
224 WCHAR strAdjust[128];
225 WCHAR strOpen[128];
226 LoadStringW(g_hInstance, IDS_VOL_OPEN, strOpen, _countof(strOpen));
227 LoadStringW(g_hInstance, IDS_VOL_ADJUST, strAdjust, _countof(strAdjust));
228
229 HMENU hPopup = CreatePopupMenu();
230 AppendMenuW(hPopup, MF_STRING, IDS_VOL_OPEN, strOpen);
231 AppendMenuW(hPopup, MF_STRING, IDS_VOL_ADJUST, strAdjust);
232 SetMenuDefaultItem(hPopup, IDS_VOL_OPEN, FALSE);
233
234 DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN;
235 POINT pt;
236 SetForegroundWindow(pSysTray->GetHWnd());
237 GetCursorPos(&pt);
238
239 DWORD id = TrackPopupMenuEx(hPopup, flags,
240 pt.x, pt.y,
241 pSysTray->GetHWnd(), NULL);
242
243 DestroyMenu(hPopup);
244
245 switch (id)
246 {
247 case IDS_VOL_OPEN:
248 _RunVolume(FALSE);
249 break;
250 case IDS_VOL_ADJUST:
251 _RunMMCpl();
252 break;
253 }
254 }
255
256 HRESULT STDMETHODCALLTYPE Volume_Message(_In_ CSysTray * pSysTray, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT &lResult)
257 {
258 if (uMsg == g_mmDeviceChange)
259 return Volume_OnDeviceChange(pSysTray, wParam, lParam);
260
261 switch (uMsg)
262 {
263 case WM_USER + 220:
264 TRACE("Volume_Message: WM_USER+220\n");
265 if (wParam == VOLUME_SERVICE_FLAG)
266 {
267 if (lParam)
268 {
269 pSysTray->EnableService(VOLUME_SERVICE_FLAG, TRUE);
270 return Volume_Init(pSysTray);
271 }
272 else
273 {
274 pSysTray->EnableService(VOLUME_SERVICE_FLAG, FALSE);
275 return Volume_Shutdown(pSysTray);
276 }
277 }
278 return S_FALSE;
279
280 case WM_USER + 221:
281 TRACE("Volume_Message: WM_USER+221\n");
282 if (wParam == VOLUME_SERVICE_FLAG)
283 {
284 lResult = (LRESULT)pSysTray->IsServiceEnabled(VOLUME_SERVICE_FLAG);
285 return S_OK;
286 }
287 return S_FALSE;
288
289 case WM_TIMER:
290 if (wParam == VOLUME_TIMER_ID)
291 {
292 KillTimer(pSysTray->GetHWnd(), VOLUME_TIMER_ID);
293 _RunVolume(TRUE);
294 }
295 break;
296
297 case ID_ICON_VOLUME:
298 TRACE("Volume_Message uMsg=%d, w=%x, l=%x\n", uMsg, wParam, lParam);
299
300 Volume_Update(pSysTray);
301
302 switch (lParam)
303 {
304 case WM_LBUTTONDOWN:
305 SetTimer(pSysTray->GetHWnd(), VOLUME_TIMER_ID, GetDoubleClickTime(), NULL);
306 break;
307
308 case WM_LBUTTONUP:
309 break;
310
311 case WM_LBUTTONDBLCLK:
312 KillTimer(pSysTray->GetHWnd(), VOLUME_TIMER_ID);
313 _RunVolume(FALSE);
314 break;
315
316 case WM_RBUTTONDOWN:
317 break;
318
319 case WM_RBUTTONUP:
320 _ShowContextMenu(pSysTray);
321 break;
322
323 case WM_RBUTTONDBLCLK:
324 break;
325
326 case WM_MOUSEMOVE:
327 break;
328 }
329 return S_OK;
330
331 default:
332 TRACE("Volume_Message received for unknown ID %d, ignoring.\n");
333 return S_FALSE;
334 }
335
336 return S_FALSE;
337 }