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