[NTDLL] Allow shimdata to override the process manifest.
[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 #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 }