[NETAPI32]
[reactos.git] / reactos / 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 <mmsystem.h>
12 #include <mmddk.h>
13
14 #define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
15 #define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
16
17 WINE_DEFAULT_DEBUG_CHANNEL(stobject);
18
19 HICON g_hIconVolume;
20 HICON g_hIconMute;
21
22 HMIXER g_hMixer;
23 UINT g_mixerId;
24 DWORD g_mixerLineID;
25 DWORD g_muteControlID;
26
27 UINT g_mmDeviceChange;
28
29 static BOOL g_IsMute = FALSE;
30 static BOOL g_IsRunning = FALSE;
31
32 static HRESULT __stdcall Volume_FindMixerControl(CSysTray * pSysTray)
33 {
34 MMRESULT result;
35 UINT mixerId = 0;
36 DWORD waveOutId = 0;
37 DWORD param2 = 0;
38
39 TRACE("Volume_FindDefaultMixerID\n");
40
41 result = waveOutMessage((HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR)&waveOutId, (DWORD_PTR)&param2);
42 if (result)
43 return E_FAIL;
44
45 if (waveOutId == (DWORD)-1)
46 {
47 TRACE("WARNING: waveOut has no default device, trying with first available device...\n", waveOutId);
48
49 mixerId = 0;
50 }
51 else
52 {
53 TRACE("waveOut default device is %d\n", waveOutId);
54
55 result = mixerGetID((HMIXEROBJ)waveOutId, &mixerId, MIXER_OBJECTF_WAVEOUT);
56 if (result)
57 return E_FAIL;
58
59 TRACE("mixerId for waveOut default device is %d\n", mixerId);
60 }
61
62 g_mixerId = mixerId;
63 return S_OK;
64
65 MIXERCAPS mixerCaps;
66 MIXERLINE mixerLine;
67 MIXERCONTROL mixerControl;
68 MIXERLINECONTROLS mixerLineControls;
69
70 g_mixerLineID = -1;
71 g_muteControlID = -1;
72
73 if (mixerGetDevCapsW(g_mixerId, &mixerCaps, sizeof(mixerCaps)))
74 return E_FAIL;
75
76 if (mixerCaps.cDestinations == 0)
77 return S_FALSE;
78
79 TRACE("mixerCaps.cDestinations %d\n", mixerCaps.cDestinations);
80
81 DWORD idx;
82 for (idx = 0; idx < mixerCaps.cDestinations; idx++)
83 {
84 mixerLine.cbStruct = sizeof(mixerLine);
85 mixerLine.dwDestination = idx;
86 if (!mixerGetLineInfoW((HMIXEROBJ)g_mixerId, &mixerLine, 0))
87 {
88 if (mixerLine.dwComponentType >= MIXERLINE_COMPONENTTYPE_DST_SPEAKERS &&
89 mixerLine.dwComponentType <= MIXERLINE_COMPONENTTYPE_DST_HEADPHONES)
90 break;
91 TRACE("Destination %d was not speakers or headphones.\n");
92 }
93 }
94
95 if (idx >= mixerCaps.cDestinations)
96 return E_FAIL;
97
98 TRACE("Valid destination %d found.\n");
99
100 g_mixerLineID = mixerLine.dwLineID;
101
102 mixerLineControls.cbStruct = sizeof(mixerLineControls);
103 mixerLineControls.dwLineID = mixerLine.dwLineID;
104 mixerLineControls.cControls = 1;
105 mixerLineControls.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
106 mixerLineControls.pamxctrl = &mixerControl;
107 mixerLineControls.cbmxctrl = sizeof(mixerControl);
108
109 if (mixerGetLineControlsW((HMIXEROBJ)g_mixerId, &mixerLineControls, MIXER_GETLINECONTROLSF_ONEBYTYPE))
110 return E_FAIL;
111
112 TRACE("Found control id %d for mute: %d\n", mixerControl.dwControlID);
113
114 g_muteControlID = mixerControl.dwControlID;
115
116 return S_OK;
117 }
118
119 HRESULT Volume_IsMute()
120 {
121 #if 0
122 MIXERCONTROLDETAILS mixerControlDetails;
123
124 if (g_mixerId != (UINT)-1 && g_muteControlID != (DWORD)-1)
125 {
126 BOOL detailsResult = 0;
127 mixerControlDetails.cbStruct = sizeof(mixerControlDetails);
128 mixerControlDetails.hwndOwner = 0;
129 mixerControlDetails.dwControlID = g_muteControlID;
130 mixerControlDetails.cChannels = 1;
131 mixerControlDetails.paDetails = &detailsResult;
132 mixerControlDetails.cbDetails = sizeof(detailsResult);
133 if (mixerGetControlDetailsW((HMIXEROBJ) g_mixerId, &mixerControlDetails, 0))
134 return E_FAIL;
135
136 TRACE("Obtained mute status %d\n", detailsResult);
137
138 g_IsMute = detailsResult != 0;
139 }
140 #endif
141 return S_OK;
142 }
143
144 HRESULT STDMETHODCALLTYPE Volume_Init(_In_ CSysTray * pSysTray)
145 {
146 HRESULT hr;
147 WCHAR strTooltip[128];
148
149 TRACE("Volume_Init\n");
150
151 if (!g_hMixer)
152 {
153 hr = Volume_FindMixerControl(pSysTray);
154 if (FAILED(hr))
155 return hr;
156
157 g_mmDeviceChange = RegisterWindowMessageW(L"winmm_devicechange");
158 }
159
160 g_hIconVolume = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_VOLUME));
161 g_hIconMute = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_VOLMUTE));
162
163 Volume_IsMute();
164
165 g_IsRunning = TRUE;
166
167 HICON icon;
168 if (g_IsMute)
169 icon = g_hIconMute;
170 else
171 icon = g_hIconVolume;
172
173 LoadStringW(g_hInstance, IDS_VOL_VOLUME, strTooltip, _countof(strTooltip));
174 return pSysTray->NotifyIcon(NIM_ADD, ID_ICON_VOLUME, icon, strTooltip);
175 }
176
177 HRESULT STDMETHODCALLTYPE Volume_Update(_In_ CSysTray * pSysTray)
178 {
179 BOOL PrevState;
180
181 TRACE("Volume_Update\n");
182
183 PrevState = g_IsMute;
184 Volume_IsMute();
185
186 if (PrevState != g_IsMute)
187 {
188 WCHAR strTooltip[128];
189 HICON icon;
190 if (g_IsMute)
191 {
192 icon = g_hIconMute;
193 LoadStringW(g_hInstance, IDS_VOL_MUTED, strTooltip, _countof(strTooltip));
194 }
195 else
196 {
197 icon = g_hIconVolume;
198 LoadStringW(g_hInstance, IDS_VOL_VOLUME, strTooltip, _countof(strTooltip));
199 }
200
201 return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_VOLUME, icon, strTooltip);
202 }
203 else
204 {
205 return S_OK;
206 }
207 }
208
209 HRESULT STDMETHODCALLTYPE Volume_Shutdown(_In_ CSysTray * pSysTray)
210 {
211 TRACE("Volume_Shutdown\n");
212
213 g_IsRunning = FALSE;
214
215 return pSysTray->NotifyIcon(NIM_DELETE, ID_ICON_VOLUME, NULL, NULL);
216 }
217
218 HRESULT Volume_OnDeviceChange(_In_ CSysTray * pSysTray, WPARAM wParam, LPARAM lParam)
219 {
220 return Volume_FindMixerControl(pSysTray);
221 }
222
223 static void _RunVolume()
224 {
225 // FIXME: ensure we are loading the right one
226 ShellExecuteW(NULL, NULL, L"sndvol32.exe", NULL, NULL, SW_SHOWNORMAL);
227 }
228
229 static void _RunMMCpl()
230 {
231 ShellExecuteW(NULL, NULL, L"mmsys.cpl", NULL, NULL, SW_NORMAL);
232 }
233
234 static void _ShowContextMenu(CSysTray * pSysTray)
235 {
236 WCHAR strAdjust[128];
237 WCHAR strOpen[128];
238 LoadStringW(g_hInstance, IDS_VOL_OPEN, strOpen, _countof(strOpen));
239 LoadStringW(g_hInstance, IDS_VOL_ADJUST, strAdjust, _countof(strAdjust));
240
241 HMENU hPopup = CreatePopupMenu();
242 AppendMenuW(hPopup, MF_STRING, IDS_VOL_OPEN, strOpen);
243 AppendMenuW(hPopup, MF_STRING, IDS_VOL_ADJUST, strAdjust);
244
245 DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN;
246 DWORD msgPos = GetMessagePos();
247
248 SetForegroundWindow(pSysTray->GetHWnd());
249 DWORD id = TrackPopupMenuEx(hPopup, flags,
250 GET_X_LPARAM(msgPos), GET_Y_LPARAM(msgPos),
251 pSysTray->GetHWnd(), NULL);
252
253 DestroyMenu(hPopup);
254
255 switch (id)
256 {
257 case IDS_VOL_OPEN:
258 _RunVolume();
259 break;
260 case IDS_VOL_ADJUST:
261 _RunMMCpl();
262 break;
263 }
264 }
265
266 HRESULT STDMETHODCALLTYPE Volume_Message(_In_ CSysTray * pSysTray, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT &lResult)
267 {
268 if (uMsg == g_mmDeviceChange)
269 return Volume_OnDeviceChange(pSysTray, wParam, lParam);
270
271 switch (uMsg)
272 {
273 case WM_USER + 220:
274 TRACE("Volume_Message: WM_USER+220\n");
275 if (wParam == 4)
276 {
277 if (lParam == FALSE)
278 return Volume_Init(pSysTray);
279 else
280 return Volume_Shutdown(pSysTray);
281 }
282 return S_FALSE;
283
284 case WM_USER + 221:
285 TRACE("Volume_Message: WM_USER+221\n");
286 if (wParam == 4)
287 {
288 lResult = (LRESULT)g_IsRunning;
289 return S_OK;
290 }
291 return S_FALSE;
292
293 case ID_ICON_VOLUME:
294 TRACE("Volume_Message uMsg=%d, w=%x, l=%x\n", uMsg, wParam, lParam);
295
296 Volume_Update(pSysTray);
297
298 switch (lParam)
299 {
300 case WM_LBUTTONDOWN:
301 break;
302
303 case WM_LBUTTONUP:
304 TRACE("TODO: display volume slider\n");
305 break;
306
307 case WM_LBUTTONDBLCLK:
308 _RunVolume();
309 break;
310
311 case WM_RBUTTONDOWN:
312 break;
313
314 case WM_RBUTTONUP:
315 _ShowContextMenu(pSysTray);
316 break;
317
318 case WM_RBUTTONDBLCLK:
319 break;
320
321 case WM_MOUSEMOVE:
322 break;
323 }
324 return S_OK;
325
326 default:
327 TRACE("Volume_Message received for unknown ID %d, ignoring.\n");
328 return S_FALSE;
329 }
330
331 return S_FALSE;
332 }