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