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 } 424