Merge PR #283 "[USBPORT] Transaction Translator (TT) support bringup"
[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 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(BOOL bTray)
218 {
219 ShellExecuteW(NULL, NULL, bTray ? L"sndvol32.exe /t" : L"sndvol32.exe", NULL, NULL, 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
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
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 == 4)
270 {
271 if (lParam == FALSE)
272 return Volume_Init(pSysTray);
273 else
274 return Volume_Shutdown(pSysTray);
275 }
276 return S_FALSE;
277
278 case WM_USER + 221:
279 TRACE("Volume_Message: WM_USER+221\n");
280 if (wParam == 4)
281 {
282 lResult = (LRESULT)g_IsRunning;
283 return S_OK;
284 }
285 return S_FALSE;
286
287 case WM_TIMER:
288 if (wParam == VOLUME_TIMER_ID)
289 {
290 KillTimer(pSysTray->GetHWnd(), VOLUME_TIMER_ID);
291 _RunVolume(TRUE);
292 }
293 break;
294
295 case ID_ICON_VOLUME:
296 TRACE("Volume_Message uMsg=%d, w=%x, l=%x\n", uMsg, wParam, lParam);
297
298 Volume_Update(pSysTray);
299
300 switch (lParam)
301 {
302 case WM_LBUTTONDOWN:
303 SetTimer(pSysTray->GetHWnd(), VOLUME_TIMER_ID, GetDoubleClickTime(), NULL);
304 break;
305
306 case WM_LBUTTONUP:
307 break;
308
309 case WM_LBUTTONDBLCLK:
310 KillTimer(pSysTray->GetHWnd(), VOLUME_TIMER_ID);
311 _RunVolume(FALSE);
312 break;
313
314 case WM_RBUTTONDOWN:
315 break;
316
317 case WM_RBUTTONUP:
318 _ShowContextMenu(pSysTray);
319 break;
320
321 case WM_RBUTTONDBLCLK:
322 break;
323
324 case WM_MOUSEMOVE:
325 break;
326 }
327 return S_OK;
328
329 default:
330 TRACE("Volume_Message received for unknown ID %d, ignoring.\n");
331 return S_FALSE;
332 }
333
334 return S_FALSE;
335 }