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)¶m2);
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