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