xref: /reactos/dll/shellext/stobject/power.cpp (revision 80774a2f)
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 }
421