5853b2bdbf344e0fd5c85db6ee3be800aab5bb79
[reactos.git] / dll / shellext / stobject / power.cpp
1 /*
2 * PROJECT: ReactOS system libraries
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: dll/shellext/stobject/power.cpp
5 * PURPOSE: Power notification icon handler
6 * PROGRAMMERS: Eric Kohl <eric.kohl@reactos.org>
7 Shriraj Sawant a.k.a SR13 <sr.official@hotmail.com>
8 * David Quintana <gigaherz@gmail.com>
9 */
10
11 #include <Windows.h>
12 #include <SetupAPI.h>
13 #include <devguid.h>
14 #include <BatClass.h>
15
16 #include "precomp.h"
17 #include "powrprof.h"
18
19 #include <mmsystem.h>
20 #include <mmddk.h>
21 #include <atlstr.h>
22
23 #define GBS_HASBATTERY 0x1
24 #define GBS_ONBATTERY 0x2
25
26 WINE_DEFAULT_DEBUG_CHANNEL(stobject);
27
28 int br_icons[5] = { IDI_BATTCAP0, IDI_BATTCAP1, IDI_BATTCAP2, IDI_BATTCAP3, IDI_BATTCAP4 }; // battery mode icons.
29 int bc_icons[5] = { IDI_BATTCHA0, IDI_BATTCHA1, IDI_BATTCHA2, IDI_BATTCHA3, IDI_BATTCHA4 }; // charging mode icons.
30
31 typedef struct _PWRSCHEMECONTEXT
32 {
33 HMENU hPopup;
34 UINT uiFirst;
35 UINT uiLast;
36 } PWRSCHEMECONTEXT, *PPWRSCHEMECONTEXT;
37
38 CString g_strTooltip;
39 static float g_batCap = 0;
40 static HICON g_hIconBattery = NULL;
41 static BOOL g_IsRunning = FALSE;
42
43 /*++
44 * @name GetBatteryState
45 *
46 * Enumerates the available battery devices and provides the remaining capacity.
47 *
48 * @param cap
49 * If no error occurs, then this will contain average remaining capacity.
50 * @param dwResult
51 * Helps in making battery type checks.
52 * {
53 * Returned value includes GBS_HASBATTERY if the system has a non-UPS battery,
54 * and GBS_ONBATTERY if the system is running on a battery.
55 * dwResult & GBS_ONBATTERY means we have not yet found AC power.
56 * dwResult & GBS_HASBATTERY means we have found a non-UPS battery.
57 * }
58 *
59 * @return The error code.
60 *
61 *--*/
62 static HRESULT GetBatteryState(float& cap, DWORD& dwResult)
63 {
64 cap = 0;
65 dwResult = GBS_ONBATTERY;
66
67 HDEVINFO hdev = SetupDiGetClassDevs(&GUID_DEVCLASS_BATTERY, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
68 if (INVALID_HANDLE_VALUE == hdev)
69 return E_HANDLE;
70
71 // Limit search to 100 batteries max
72 for (int idev = 0, count = 0; idev < 100; idev++)
73 {
74 SP_DEVICE_INTERFACE_DATA did = { 0 };
75 did.cbSize = sizeof(did);
76
77 if (SetupDiEnumDeviceInterfaces(hdev, 0, &GUID_DEVCLASS_BATTERY, idev, &did))
78 {
79 DWORD cbRequired = 0;
80
81 SetupDiGetDeviceInterfaceDetail(hdev, &did, 0, 0, &cbRequired, 0);
82 if (ERROR_INSUFFICIENT_BUFFER == GetLastError())
83 {
84 PSP_DEVICE_INTERFACE_DETAIL_DATA pdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)LocalAlloc(LPTR, cbRequired);
85 if (pdidd)
86 {
87 pdidd->cbSize = sizeof(*pdidd);
88 if (SetupDiGetDeviceInterfaceDetail(hdev, &did, pdidd, cbRequired, &cbRequired, 0))
89 {
90 // Enumerated a battery. Ask it for information.
91 HANDLE hBattery = CreateFile(pdidd->DevicePath, GENERIC_READ | GENERIC_WRITE,
92 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
93
94 if (INVALID_HANDLE_VALUE != hBattery)
95 {
96 // Ask the battery for its tag.
97 BATTERY_QUERY_INFORMATION bqi = { 0 };
98
99 DWORD dwWait = 0;
100 DWORD dwOut;
101
102 if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_TAG, &dwWait, sizeof(dwWait), &bqi.BatteryTag,
103 sizeof(bqi.BatteryTag), &dwOut, NULL) && bqi.BatteryTag)
104 {
105 // With the tag, you can query the battery info.
106 BATTERY_INFORMATION bi = { 0 };
107 bqi.InformationLevel = BatteryInformation;
108
109 if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), &bi,
110 sizeof(bi), &dwOut, NULL))
111 {
112 // Only non-UPS system batteries count
113 if (bi.Capabilities & BATTERY_SYSTEM_BATTERY)
114 {
115 if (!(bi.Capabilities & BATTERY_IS_SHORT_TERM))
116 dwResult |= GBS_HASBATTERY;
117
118 // Query the battery status.
119 BATTERY_WAIT_STATUS bws = { 0 };
120 bws.BatteryTag = bqi.BatteryTag;
121
122 BATTERY_STATUS bs;
123 if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_STATUS, &bws, sizeof(bws),
124 &bs, sizeof(bs), &dwOut, NULL))
125 {
126 if (bs.PowerState & BATTERY_POWER_ON_LINE)
127 dwResult &= ~GBS_ONBATTERY;
128
129 // Take average of total capacity of batteries detected!
130 cap = cap*(count)+(float)bs.Capacity / bi.FullChargedCapacity * 100;
131 cap /= count + 1;
132 count++;
133 }
134 }
135 }
136 }
137 CloseHandle(hBattery);
138 }
139 }
140 LocalFree(pdidd);
141 }
142 }
143 }
144 else if (ERROR_NO_MORE_ITEMS == GetLastError())
145 {
146 break; // Enumeration failed - perhaps we're out of items
147 }
148 }
149 SetupDiDestroyDeviceInfoList(hdev);
150
151 // Final cleanup: If we didn't find a battery, then presume that we
152 // are on AC power.
153
154 if (!(dwResult & GBS_HASBATTERY))
155 dwResult &= ~GBS_ONBATTERY;
156
157 return S_OK;
158 }
159
160 /*++
161 * @name Quantize
162 *
163 * This function quantizes the mentioned quantity to nearest level.
164 *
165 * @param p
166 * Should be a quantity in percentage.
167 * @param lvl
168 * Quantization level (this excludes base level 0, which will always be present), default is 10.
169 *
170 * @return Nearest quantized level, can be directly used as array index based on context.
171 *
172 *--*/
173 static UINT Quantize(float p, UINT lvl = 10)
174 {
175 int i = 0;
176 float f, q = (float)100 / lvl, d = q / 2;
177 for (f = 0; f < p; f += q, i++);
178
179 if ((f - d) <= p)
180 return i;
181 else
182 return i - 1;
183 /*
184 @remarks This function uses centred/symmetric logic for quantization.
185 For the case of lvl = 4, You will get following integer levels if given (p) value falls in between the range partitions:
186 0 <= p < 12.5 : returns 0; (corresponding to 0% centre)
187 12.5 <= p < 37.5 : returns 1; (corresponding to 25% centre)
188 37.5 <= p < 62.5 : returns 2; (corresponding to 50% centre)
189 62.5 <= p < 87.5 : returns 3; (corresponding to 75% centre)
190 87.5 <= p <= 100 : returns 4; (corresponding to 100% centre)
191 */
192 }
193
194 /*++
195 * @name DynamicLoadIcon
196 *
197 * Returns the respective icon as per the current battery capacity.
198 * It also does the work of setting global parameters of battery capacity and tooltips.
199 *
200 * @param hinst
201 * A handle to a instance of the module.
202 *
203 * @return The handle to respective battery icon.
204 *
205 *--*/
206 static HICON DynamicLoadIcon(HINSTANCE hinst)
207 {
208 HICON hBatIcon;
209 float cap = 0;
210 DWORD dw = 0;
211 UINT index = -1;
212 HRESULT hr = GetBatteryState(cap, dw);
213
214 if (!FAILED(hr) && (dw & GBS_HASBATTERY))
215 {
216 index = Quantize(cap, 4);
217 g_batCap = cap;
218 }
219 else
220 {
221 g_batCap = 0;
222 hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(IDI_BATTCAP_ERR));
223 g_strTooltip.LoadStringW(IDS_PWR_UNKNOWN_REMAINING);
224 return hBatIcon;
225 }
226
227 if (dw & GBS_ONBATTERY)
228 {
229 hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(br_icons[index]));
230 g_strTooltip.Format(IDS_PWR_PERCENT_REMAINING, cap);
231 }
232 else
233 {
234 hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(bc_icons[index]));
235 g_strTooltip.Format(IDS_PWR_CHARGING, cap);
236 }
237
238 return hBatIcon;
239 }
240
241 HRESULT STDMETHODCALLTYPE Power_Init(_In_ CSysTray * pSysTray)
242 {
243 TRACE("Power_Init\n");
244 g_hIconBattery = DynamicLoadIcon(g_hInstance);
245 g_IsRunning = TRUE;
246
247 return pSysTray->NotifyIcon(NIM_ADD, ID_ICON_POWER, g_hIconBattery, g_strTooltip);
248 }
249
250 HRESULT STDMETHODCALLTYPE Power_Update(_In_ CSysTray * pSysTray)
251 {
252 TRACE("Power_Update\n");
253 g_hIconBattery = DynamicLoadIcon(g_hInstance);
254
255 return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_POWER, g_hIconBattery, g_strTooltip);
256 }
257
258 HRESULT STDMETHODCALLTYPE Power_Shutdown(_In_ CSysTray * pSysTray)
259 {
260 TRACE("Power_Shutdown\n");
261 g_IsRunning = FALSE;
262
263 return pSysTray->NotifyIcon(NIM_DELETE, ID_ICON_POWER, NULL, NULL);
264 }
265
266 static void _RunPower()
267 {
268 ShellExecuteW(NULL, NULL, L"powercfg.cpl", NULL, NULL, SW_SHOWNORMAL);
269 }
270
271 static void _ShowContextMenu(CSysTray * pSysTray)
272 {
273 CString strOpen((LPCSTR)IDS_PWR_PROPERTIES);
274 HMENU hPopup = CreatePopupMenu();
275 AppendMenuW(hPopup, MF_STRING, IDS_PWR_PROPERTIES, strOpen);
276
277 SetForegroundWindow(pSysTray->GetHWnd());
278 DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN;
279 POINT pt;
280 GetCursorPos(&pt);
281
282 DWORD id = TrackPopupMenuEx(hPopup, flags,
283 pt.x, pt.y,
284 pSysTray->GetHWnd(), NULL);
285
286 switch (id)
287 {
288 case IDS_PWR_PROPERTIES:
289 _RunPower();
290 break;
291 }
292 DestroyMenu(hPopup);
293 }
294
295 static
296 BOOLEAN
297 CALLBACK
298 PowerSchemesEnumProc(
299 UINT uiIndex,
300 DWORD dwName,
301 LPWSTR sName,
302 DWORD dwDesc,
303 LPWSTR sDesc,
304 PPOWER_POLICY pp,
305 LPARAM lParam)
306 {
307 PPWRSCHEMECONTEXT PowerSchemeContext = (PPWRSCHEMECONTEXT)lParam;
308
309 if (AppendMenuW(PowerSchemeContext->hPopup, MF_STRING, uiIndex + 1, sName))
310 {
311 if (PowerSchemeContext->uiFirst == 0)
312 PowerSchemeContext->uiFirst = uiIndex + 1;
313
314 PowerSchemeContext->uiLast = uiIndex + 1;
315 }
316
317 return TRUE;
318 }
319
320 static
321 VOID
322 ShowPowerSchemesPopupMenu(
323 CSysTray *pSysTray)
324 {
325 PWRSCHEMECONTEXT PowerSchemeContext = {NULL, 0, 0};
326 UINT uiActiveScheme;
327 DWORD id;
328 POINT pt;
329 PowerSchemeContext.hPopup = CreatePopupMenu();
330 EnumPwrSchemes(PowerSchemesEnumProc, (LPARAM)&PowerSchemeContext);
331
332 if (GetActivePwrScheme(&uiActiveScheme))
333 {
334 CheckMenuRadioItem(PowerSchemeContext.hPopup,
335 PowerSchemeContext.uiFirst,
336 PowerSchemeContext.uiLast,
337 uiActiveScheme + 1,
338 MF_BYCOMMAND);
339 }
340
341 SetForegroundWindow(pSysTray->GetHWnd());
342 GetCursorPos(&pt);
343
344 id = TrackPopupMenuEx(PowerSchemeContext.hPopup,
345 TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN,
346 pt.x,
347 pt.y,
348 pSysTray->GetHWnd(),
349 NULL);
350
351 DestroyMenu(PowerSchemeContext.hPopup);
352
353 if (id != 0)
354 SetActivePwrScheme(id - 1, NULL, NULL);
355 }
356
357 HRESULT STDMETHODCALLTYPE Power_Message(_In_ CSysTray * pSysTray, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT &lResult)
358 {
359 TRACE("Power_Message uMsg=%d, wParam=%x, lParam=%x\n", uMsg, wParam, lParam);
360
361 switch (uMsg)
362 {
363 case WM_USER + 220:
364 TRACE("Power_Message: WM_USER+220\n");
365 if (wParam == 1)
366 {
367 if (lParam == FALSE)
368 return Power_Init(pSysTray);
369 else
370 return Power_Shutdown(pSysTray);
371 }
372 return S_FALSE;
373
374 case WM_USER + 221:
375 TRACE("Power_Message: WM_USER+221\n");
376 if (wParam == 1)
377 {
378 lResult = (LRESULT)g_IsRunning;
379 return S_OK;
380 }
381 return S_FALSE;
382
383 case ID_ICON_POWER:
384 Power_Update(pSysTray);
385
386 switch (lParam)
387 {
388 case WM_LBUTTONDOWN:
389 break;
390
391 case WM_LBUTTONUP:
392 ShowPowerSchemesPopupMenu(pSysTray);
393 break;
394
395 case WM_LBUTTONDBLCLK:
396 _RunPower();
397 break;
398
399 case WM_RBUTTONDOWN:
400 break;
401
402 case WM_RBUTTONUP:
403 _ShowContextMenu(pSysTray);
404 break;
405
406 case WM_RBUTTONDBLCLK:
407 break;
408
409 case WM_MOUSEMOVE:
410 break;
411 }
412 return S_OK;
413
414 default:
415 TRACE("Power_Message received for unknown ID %d, ignoring.\n");
416 return S_FALSE;
417 }
418
419 return S_FALSE;
420 }