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