Reactos
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
19int br_icons[5] = { IDI_BATTCAP0, IDI_BATTCAP1, IDI_BATTCAP2, IDI_BATTCAP3, IDI_BATTCAP4 }; // battery mode icons.
20int bc_icons[5] = { IDI_BATTCHA0, IDI_BATTCHA1, IDI_BATTCHA2, IDI_BATTCHA3, IDI_BATTCHA4 }; // charging mode icons.
21
22typedef struct _PWRSCHEMECONTEXT
23{
24 HMENU hPopup;
25 UINT uiFirst;
26 UINT uiLast;
27} PWRSCHEMECONTEXT, *PPWRSCHEMECONTEXT;
28
29CString g_strTooltip;
30static 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 *--*/
53static 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*--*/
79static 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.ACLineStatus == AC_LINE_ONLINE &&
109 PowerStatus.BatteryLifePercent == 100)
110 {
111 index = Quantize(PowerStatus.BatteryLifePercent);
112 hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(bc_icons[index]));
113 g_strTooltip.LoadStringW(IDS_PWR_FULLY_CHARGED);
114 }
115 else if (((PowerStatus.BatteryFlag & BATTERY_FLAG_NO_BATTERY) == 0) &&
116 ((PowerStatus.BatteryFlag & BATTERY_FLAG_CHARGING) == 0))
117 {
118 index = Quantize(PowerStatus.BatteryLifePercent);
119 hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(br_icons[index]));
120
121 if (PowerStatus.BatteryLifeTime != BATTERY_UNKNOWN_TIME)
122 {
123 uiHour = PowerStatus.BatteryLifeTime / HOUR_IN_SECS;
124 uiMin = (PowerStatus.BatteryLifeTime % HOUR_IN_SECS) / MIN_IN_SECS;
125
126 if (uiHour != 0)
127 {
128 g_strTooltip.Format(IDS_PWR_HOURS_REMAINING, uiHour, uiMin, PowerStatus.BatteryLifePercent);
129 }
130 else
131 {
132 g_strTooltip.Format(IDS_PWR_MINUTES_REMAINING, uiMin, PowerStatus.BatteryLifePercent);
133 }
134 }
135 else
136 {
137 g_strTooltip.Format(IDS_PWR_PERCENT_REMAINING, PowerStatus.BatteryLifePercent);
138 }
139 }
140 else
141 {
142 hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(IDI_POWER_AC));
143 g_strTooltip.LoadStringW(IDS_PWR_AC);
144 }
145
146 return hBatIcon;
147}
148
149HRESULT STDMETHODCALLTYPE Power_Init(_In_ CSysTray * pSysTray)
150{
151 TRACE("Power_Init\n");
152 g_hIconBattery = DynamicLoadIcon(g_hInstance);
153
154 return pSysTray->NotifyIcon(NIM_ADD, ID_ICON_POWER, g_hIconBattery, g_strTooltip);
155}
156
157HRESULT STDMETHODCALLTYPE Power_Update(_In_ CSysTray * pSysTray)
158{
159 TRACE("Power_Update\n");
160 g_hIconBattery = DynamicLoadIcon(g_hInstance);
161
162 return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_POWER, g_hIconBattery, g_strTooltip);
163}
164
165HRESULT STDMETHODCALLTYPE Power_Shutdown(_In_ CSysTray * pSysTray)
166{
167 TRACE("Power_Shutdown\n");
168
169 return pSysTray->NotifyIcon(NIM_DELETE, ID_ICON_POWER, NULL, NULL);
170}
171
172static void _RunPower()
173{
174 CSysTray::RunDll("powercfg.cpl", "");
175}
176
177static void _ShowContextMenu(CSysTray * pSysTray)
178{
179 CString strOpen((LPCSTR)IDS_PWR_PROPERTIES);
180 HMENU hPopup = CreatePopupMenu();
181 AppendMenuW(hPopup, MF_STRING, IDS_PWR_PROPERTIES, strOpen);
182 SetMenuDefaultItem(hPopup, IDS_PWR_PROPERTIES, FALSE);
183
184 SetForegroundWindow(pSysTray->GetHWnd());
185 DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN;
186 POINT pt;
187 GetCursorPos(&pt);
188
189 DWORD id = TrackPopupMenuEx(hPopup, flags,
190 pt.x, pt.y,
191 pSysTray->GetHWnd(), NULL);
192
193 switch (id)
194 {
195 case IDS_PWR_PROPERTIES:
196 _RunPower();
197 break;
198 }
199 DestroyMenu(hPopup);
200}
201
202static
203BOOLEAN
204CALLBACK
205PowerSchemesEnumProc(
206 UINT uiIndex,
207 DWORD dwName,
208 LPWSTR sName,
209 DWORD dwDesc,
210 LPWSTR sDesc,
211 PPOWER_POLICY pp,
212 LPARAM lParam)
213{
214 PPWRSCHEMECONTEXT PowerSchemeContext = (PPWRSCHEMECONTEXT)lParam;
215
216 if (AppendMenuW(PowerSchemeContext->hPopup, MF_STRING, uiIndex + 1, sName))
217 {
218 if (PowerSchemeContext->uiFirst == 0)
219 PowerSchemeContext->uiFirst = uiIndex + 1;
220
221 PowerSchemeContext->uiLast = uiIndex + 1;
222 }
223
224 return TRUE;
225}
226
227static
228VOID
229ShowPowerSchemesPopupMenu(
230 CSysTray *pSysTray)
231{
232 PWRSCHEMECONTEXT PowerSchemeContext = {NULL, 0, 0};
233 UINT uiActiveScheme;
234 DWORD id;
235 POINT pt;
236 PowerSchemeContext.hPopup = CreatePopupMenu();
237 EnumPwrSchemes(PowerSchemesEnumProc, (LPARAM)&PowerSchemeContext);
238
239 if (GetActivePwrScheme(&uiActiveScheme))
240 {
241 CheckMenuRadioItem(PowerSchemeContext.hPopup,
242 PowerSchemeContext.uiFirst,
243 PowerSchemeContext.uiLast,
244 uiActiveScheme + 1,
245 MF_BYCOMMAND);
246 }
247
248 SetForegroundWindow(pSysTray->GetHWnd());
249 GetCursorPos(&pt);
250
251 id = TrackPopupMenuEx(PowerSchemeContext.hPopup,
252 TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN,
253 pt.x,
254 pt.y,
255 pSysTray->GetHWnd(),
256 NULL);
257
258 DestroyMenu(PowerSchemeContext.hPopup);
259
260 if (id != 0)
261 SetActivePwrScheme(id - 1, NULL, NULL);
262}
263
264HRESULT STDMETHODCALLTYPE Power_Message(_In_ CSysTray * pSysTray, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT &lResult)
265{
266 TRACE("Power_Message uMsg=%d, wParam=%x, lParam=%x\n", uMsg, wParam, lParam);
267
268 switch (uMsg)
269 {
270 case WM_USER + 220:
271 TRACE("Power_Message: WM_USER+220\n");
272 if (wParam == POWER_SERVICE_FLAG)
273 {
274 if (lParam)
275 {
276 pSysTray->EnableService(POWER_SERVICE_FLAG, TRUE);
277 return Power_Init(pSysTray);
278 }
279 else
280 {
281 pSysTray->EnableService(POWER_SERVICE_FLAG, FALSE);
282 return Power_Shutdown(pSysTray);
283 }
284 }
285 return S_FALSE;
286
287 case WM_USER + 221:
288 TRACE("Power_Message: WM_USER+221\n");
289 if (wParam == POWER_SERVICE_FLAG)
290 {
291 lResult = (LRESULT)pSysTray->IsServiceEnabled(POWER_SERVICE_FLAG);
292 return S_OK;
293 }
294 return S_FALSE;
295
296 case WM_TIMER:
297 if (wParam == POWER_TIMER_ID)
298 {
299 KillTimer(pSysTray->GetHWnd(), POWER_TIMER_ID);
300 ShowPowerSchemesPopupMenu(pSysTray);
301 }
302 break;
303
304 case ID_ICON_POWER:
305 Power_Update(pSysTray);
306
307 switch (lParam)
308 {
309 case WM_LBUTTONDOWN:
310 SetTimer(pSysTray->GetHWnd(), POWER_TIMER_ID, GetDoubleClickTime(), NULL);
311 break;
312
313 case WM_LBUTTONUP:
314 break;
315
316 case WM_LBUTTONDBLCLK:
317 KillTimer(pSysTray->GetHWnd(), POWER_TIMER_ID);
318 _RunPower();
319 break;
320
321 case WM_RBUTTONDOWN:
322 break;
323
324 case WM_RBUTTONUP:
325 _ShowContextMenu(pSysTray);
326 break;
327
328 case WM_RBUTTONDBLCLK:
329 break;
330
331 case WM_MOUSEMOVE:
332 break;
333 }
334 return S_OK;
335
336 default:
337 TRACE("Power_Message received for unknown ID %d, ignoring.\n");
338 return S_FALSE;
339 }
340
341 return S_FALSE;
342}