xref: /reactos/dll/shellext/stobject/power.cpp (revision 28b277aa)
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 int br_icons[5] = { IDI_BATTCAP0, IDI_BATTCAP1, IDI_BATTCAP2, IDI_BATTCAP3, IDI_BATTCAP4 }; // battery mode icons.
20 int bc_icons[5] = { IDI_BATTCHA0, IDI_BATTCHA1, IDI_BATTCHA2, IDI_BATTCHA3, IDI_BATTCHA4 }; // charging mode icons.
21 
22 typedef struct _PWRSCHEMECONTEXT
23 {
24     HMENU hPopup;
25     UINT uiFirst;
26     UINT uiLast;
27 } PWRSCHEMECONTEXT, *PPWRSCHEMECONTEXT;
28 
29 CString  g_strTooltip;
30 static HICON g_hIconBattery = NULL;
31 
32 #define HOUR_IN_SECS    3600
33 #define MIN_IN_SECS     60
34 
35 /*++
36 * @name Quantize
37 *
38 * This function quantizes the mentioned quantity to nearest level.
39 *
40 * @param p
41 *        Should be a quantity in percentage.
42 *
43 * @return Nearest quantized level, can be directly used as array index based on context.
44 *
45  @remarks This function uses centred/symmetric logic for quantization.
46  For the case of lvl = 4, You will get following integer levels if given (p) value falls in between the range partitions:
47      0    <= p <  12.5 : returns 0; (corresponding to 0% centre)
48      12.5 <= p <  37.5 : returns 1; (corresponding to 25% centre)
49      37.5 <= p <  62.5 : returns 2; (corresponding to 50% centre)
50      62.5 <= p <  87.5 : returns 3; (corresponding to 75% centre)
51      87.5 <= p <= 100  : returns 4; (corresponding to 100% centre)
52  *--*/
Quantize(BYTE p)53 static UINT Quantize(BYTE p)
54 {
55     if (p <= 12)
56         return 0;
57     else if (p > 12 && p <= 37)
58         return 1;
59     else if (p > 37 && p <= 62)
60         return 2;
61     else if (p > 62 && p <= 87)
62         return 3;
63     else
64         return 4;
65 }
66 
67 /*++
68 * @name DynamicLoadIcon
69 *
70 * Returns the respective icon as per the current battery capacity.
71 * It also does the work of setting global parameters of battery capacity and tooltips.
72 *
73 * @param hinst
74 *        A handle to a instance of the module.
75 *
76 * @return The handle to respective battery icon.
77 *
78 *--*/
DynamicLoadIcon(HINSTANCE hinst)79 static HICON DynamicLoadIcon(HINSTANCE hinst)
80 {
81     SYSTEM_POWER_STATUS PowerStatus;
82     HICON hBatIcon;
83     UINT uiHour, uiMin;
84     UINT index = -1;
85 
86     if (!GetSystemPowerStatus(&PowerStatus) ||
87         PowerStatus.ACLineStatus == AC_LINE_UNKNOWN ||
88         PowerStatus.BatteryFlag == BATTERY_FLAG_UNKNOWN)
89     {
90         hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(IDI_BATTCAP_ERR));
91         g_strTooltip.LoadStringW(IDS_PWR_UNKNOWN_REMAINING);
92         return hBatIcon;
93     }
94 
95     if (((PowerStatus.BatteryFlag & BATTERY_FLAG_NO_BATTERY) == 0) &&
96         (PowerStatus.BatteryLifePercent == BATTERY_PERCENTAGE_UNKNOWN))
97     {
98         hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(IDI_BATTCAP_ERR));
99         g_strTooltip.LoadStringW(IDS_PWR_UNKNOWN_REMAINING);
100     }
101     else if (((PowerStatus.BatteryFlag & BATTERY_FLAG_NO_BATTERY) == 0) &&
102         ((PowerStatus.BatteryFlag & BATTERY_FLAG_CHARGING) == BATTERY_FLAG_CHARGING))
103     {
104         index = Quantize(PowerStatus.BatteryLifePercent);
105         hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(bc_icons[index]));
106         g_strTooltip.Format(IDS_PWR_CHARGING, PowerStatus.BatteryLifePercent);
107     }
108     else if (((PowerStatus.BatteryFlag & BATTERY_FLAG_NO_BATTERY) == 0) &&
109              ((PowerStatus.BatteryFlag & BATTERY_FLAG_CHARGING) == 0))
110     {
111         index = Quantize(PowerStatus.BatteryLifePercent);
112         hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(br_icons[index]));
113 
114         if (PowerStatus.BatteryLifeTime != BATTERY_UNKNOWN_TIME)
115         {
116             uiHour = PowerStatus.BatteryLifeTime / HOUR_IN_SECS;
117             uiMin = (PowerStatus.BatteryLifeTime % HOUR_IN_SECS) / MIN_IN_SECS;
118 
119             if (uiHour != 0)
120             {
121                 g_strTooltip.Format(IDS_PWR_HOURS_REMAINING, uiHour, uiMin, PowerStatus.BatteryLifePercent);
122             }
123             else
124             {
125                 g_strTooltip.Format(IDS_PWR_MINUTES_REMAINING, uiMin, PowerStatus.BatteryLifePercent);
126             }
127         }
128         else
129         {
130             g_strTooltip.Format(IDS_PWR_PERCENT_REMAINING, PowerStatus.BatteryLifePercent);
131         }
132     }
133     else
134     {
135         hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(IDI_POWER_AC));
136         g_strTooltip.LoadStringW(IDS_PWR_AC);
137     }
138 
139     return hBatIcon;
140 }
141 
Power_Init(_In_ CSysTray * pSysTray)142 HRESULT STDMETHODCALLTYPE Power_Init(_In_ CSysTray * pSysTray)
143 {
144     TRACE("Power_Init\n");
145     g_hIconBattery = DynamicLoadIcon(g_hInstance);
146 
147     return pSysTray->NotifyIcon(NIM_ADD, ID_ICON_POWER, g_hIconBattery, g_strTooltip);
148 }
149 
Power_Update(_In_ CSysTray * pSysTray)150 HRESULT STDMETHODCALLTYPE Power_Update(_In_ CSysTray * pSysTray)
151 {
152     TRACE("Power_Update\n");
153     g_hIconBattery = DynamicLoadIcon(g_hInstance);
154 
155     return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_POWER, g_hIconBattery, g_strTooltip);
156 }
157 
Power_Shutdown(_In_ CSysTray * pSysTray)158 HRESULT STDMETHODCALLTYPE Power_Shutdown(_In_ CSysTray * pSysTray)
159 {
160     TRACE("Power_Shutdown\n");
161 
162     return pSysTray->NotifyIcon(NIM_DELETE, ID_ICON_POWER, NULL, NULL);
163 }
164 
_RunPower()165 static void _RunPower()
166 {
167     ShellExecuteW(NULL, NULL, L"powercfg.cpl", NULL, NULL, SW_SHOWNORMAL);
168 }
169 
_ShowContextMenu(CSysTray * pSysTray)170 static void _ShowContextMenu(CSysTray * pSysTray)
171 {
172     CString strOpen((LPCSTR)IDS_PWR_PROPERTIES);
173     HMENU hPopup = CreatePopupMenu();
174     AppendMenuW(hPopup, MF_STRING, IDS_PWR_PROPERTIES, strOpen);
175     SetMenuDefaultItem(hPopup, IDS_PWR_PROPERTIES, FALSE);
176 
177     SetForegroundWindow(pSysTray->GetHWnd());
178     DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN;
179     POINT pt;
180     GetCursorPos(&pt);
181 
182     DWORD id = TrackPopupMenuEx(hPopup, flags,
183         pt.x, pt.y,
184         pSysTray->GetHWnd(), NULL);
185 
186     switch (id)
187     {
188         case IDS_PWR_PROPERTIES:
189             _RunPower();
190             break;
191     }
192     DestroyMenu(hPopup);
193 }
194 
195 static
196 BOOLEAN
197 CALLBACK
PowerSchemesEnumProc(UINT uiIndex,DWORD dwName,LPWSTR sName,DWORD dwDesc,LPWSTR sDesc,PPOWER_POLICY pp,LPARAM lParam)198 PowerSchemesEnumProc(
199     UINT uiIndex,
200     DWORD dwName,
201     LPWSTR sName,
202     DWORD dwDesc,
203     LPWSTR sDesc,
204     PPOWER_POLICY pp,
205     LPARAM lParam)
206 {
207     PPWRSCHEMECONTEXT PowerSchemeContext = (PPWRSCHEMECONTEXT)lParam;
208 
209     if (AppendMenuW(PowerSchemeContext->hPopup, MF_STRING, uiIndex + 1, sName))
210     {
211         if (PowerSchemeContext->uiFirst == 0)
212             PowerSchemeContext->uiFirst = uiIndex + 1;
213 
214         PowerSchemeContext->uiLast = uiIndex + 1;
215     }
216 
217     return TRUE;
218 }
219 
220 static
221 VOID
ShowPowerSchemesPopupMenu(CSysTray * pSysTray)222 ShowPowerSchemesPopupMenu(
223     CSysTray *pSysTray)
224 {
225     PWRSCHEMECONTEXT PowerSchemeContext = {NULL, 0, 0};
226     UINT uiActiveScheme;
227     DWORD id;
228     POINT pt;
229     PowerSchemeContext.hPopup = CreatePopupMenu();
230     EnumPwrSchemes(PowerSchemesEnumProc, (LPARAM)&PowerSchemeContext);
231 
232     if (GetActivePwrScheme(&uiActiveScheme))
233     {
234         CheckMenuRadioItem(PowerSchemeContext.hPopup,
235                            PowerSchemeContext.uiFirst,
236                            PowerSchemeContext.uiLast,
237                            uiActiveScheme + 1,
238                            MF_BYCOMMAND);
239     }
240 
241     SetForegroundWindow(pSysTray->GetHWnd());
242     GetCursorPos(&pt);
243 
244     id = TrackPopupMenuEx(PowerSchemeContext.hPopup,
245                           TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN,
246                           pt.x,
247                           pt.y,
248                           pSysTray->GetHWnd(),
249                           NULL);
250 
251     DestroyMenu(PowerSchemeContext.hPopup);
252 
253     if (id != 0)
254         SetActivePwrScheme(id - 1, NULL, NULL);
255 }
256 
Power_Message(_In_ CSysTray * pSysTray,UINT uMsg,WPARAM wParam,LPARAM lParam,LRESULT & lResult)257 HRESULT STDMETHODCALLTYPE Power_Message(_In_ CSysTray * pSysTray, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT &lResult)
258 {
259     TRACE("Power_Message uMsg=%d, wParam=%x, lParam=%x\n", uMsg, wParam, lParam);
260 
261     switch (uMsg)
262     {
263         case WM_USER + 220:
264             TRACE("Power_Message: WM_USER+220\n");
265             if (wParam == POWER_SERVICE_FLAG)
266             {
267                 if (lParam)
268                 {
269                     pSysTray->EnableService(POWER_SERVICE_FLAG, TRUE);
270                     return Power_Init(pSysTray);
271                 }
272                 else
273                 {
274                     pSysTray->EnableService(POWER_SERVICE_FLAG, FALSE);
275                     return Power_Shutdown(pSysTray);
276                 }
277             }
278             return S_FALSE;
279 
280         case WM_USER + 221:
281             TRACE("Power_Message: WM_USER+221\n");
282             if (wParam == POWER_SERVICE_FLAG)
283             {
284                 lResult = (LRESULT)pSysTray->IsServiceEnabled(POWER_SERVICE_FLAG);
285                 return S_OK;
286             }
287             return S_FALSE;
288 
289         case WM_TIMER:
290             if (wParam == POWER_TIMER_ID)
291             {
292                 KillTimer(pSysTray->GetHWnd(), POWER_TIMER_ID);
293                 ShowPowerSchemesPopupMenu(pSysTray);
294             }
295             break;
296 
297         case ID_ICON_POWER:
298             Power_Update(pSysTray);
299 
300             switch (lParam)
301             {
302                 case WM_LBUTTONDOWN:
303                     SetTimer(pSysTray->GetHWnd(), POWER_TIMER_ID, GetDoubleClickTime(), NULL);
304                     break;
305 
306                 case WM_LBUTTONUP:
307                     break;
308 
309                 case WM_LBUTTONDBLCLK:
310                     KillTimer(pSysTray->GetHWnd(), POWER_TIMER_ID);
311                     _RunPower();
312                     break;
313 
314                 case WM_RBUTTONDOWN:
315                     break;
316 
317                 case WM_RBUTTONUP:
318                     _ShowContextMenu(pSysTray);
319                     break;
320 
321                 case WM_RBUTTONDBLCLK:
322                     break;
323 
324                 case WM_MOUSEMOVE:
325                     break;
326             }
327             return S_OK;
328 
329         default:
330             TRACE("Power_Message received for unknown ID %d, ignoring.\n");
331             return S_FALSE;
332     }
333 
334     return S_FALSE;
335 }
336