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 <windows.h> 12 #include <setupapi.h> 13 #include <devguid.h> 14 #include <batclass.h> 15 16 #include "precomp.h" 17 #include "powrprof.h" 18 19 #include <mmsystem.h> 20 #include <mmddk.h> 21 #include <atlstr.h> 22 23 #define GBS_HASBATTERY 0x1 24 #define GBS_ONBATTERY 0x2 25 26 WINE_DEFAULT_DEBUG_CHANNEL(stobject); 27 28 int br_icons[5] = { IDI_BATTCAP0, IDI_BATTCAP1, IDI_BATTCAP2, IDI_BATTCAP3, IDI_BATTCAP4 }; // battery mode icons. 29 int bc_icons[5] = { IDI_BATTCHA0, IDI_BATTCHA1, IDI_BATTCHA2, IDI_BATTCHA3, IDI_BATTCHA4 }; // charging mode icons. 30 31 typedef struct _PWRSCHEMECONTEXT 32 { 33 HMENU hPopup; 34 UINT uiFirst; 35 UINT uiLast; 36 } PWRSCHEMECONTEXT, *PPWRSCHEMECONTEXT; 37 38 CString g_strTooltip; 39 static float g_batCap = 0; 40 static HICON g_hIconBattery = NULL; 41 static BOOL g_IsRunning = FALSE; 42 43 /*++ 44 * @name GetBatteryState 45 * 46 * Enumerates the available battery devices and provides the remaining capacity. 47 * 48 * @param cap 49 * If no error occurs, then this will contain average remaining capacity. 50 * @param dwResult 51 * Helps in making battery type checks. 52 * { 53 * Returned value includes GBS_HASBATTERY if the system has a non-UPS battery, 54 * and GBS_ONBATTERY if the system is running on a battery. 55 * dwResult & GBS_ONBATTERY means we have not yet found AC power. 56 * dwResult & GBS_HASBATTERY means we have found a non-UPS battery. 57 * } 58 * 59 * @return The error code. 60 * 61 *--*/ 62 static HRESULT GetBatteryState(float& cap, DWORD& dwResult) 63 { 64 cap = 0; 65 dwResult = GBS_ONBATTERY; 66 67 HDEVINFO hdev = SetupDiGetClassDevs(&GUID_DEVCLASS_BATTERY, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); 68 if (INVALID_HANDLE_VALUE == hdev) 69 return E_HANDLE; 70 71 // Limit search to 100 batteries max 72 for (int idev = 0, count = 0; idev < 100; idev++) 73 { 74 SP_DEVICE_INTERFACE_DATA did = { 0 }; 75 did.cbSize = sizeof(did); 76 77 if (SetupDiEnumDeviceInterfaces(hdev, 0, &GUID_DEVCLASS_BATTERY, idev, &did)) 78 { 79 DWORD cbRequired = 0; 80 81 SetupDiGetDeviceInterfaceDetail(hdev, &did, 0, 0, &cbRequired, 0); 82 if (ERROR_INSUFFICIENT_BUFFER == GetLastError()) 83 { 84 PSP_DEVICE_INTERFACE_DETAIL_DATA pdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)LocalAlloc(LPTR, cbRequired); 85 if (pdidd) 86 { 87 pdidd->cbSize = sizeof(*pdidd); 88 if (SetupDiGetDeviceInterfaceDetail(hdev, &did, pdidd, cbRequired, &cbRequired, 0)) 89 { 90 // Enumerated a battery. Ask it for information. 91 HANDLE hBattery = CreateFile(pdidd->DevicePath, GENERIC_READ | GENERIC_WRITE, 92 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 93 94 if (INVALID_HANDLE_VALUE != hBattery) 95 { 96 // Ask the battery for its tag. 97 BATTERY_QUERY_INFORMATION bqi = { 0 }; 98 99 DWORD dwWait = 0; 100 DWORD dwOut; 101 102 if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_TAG, &dwWait, sizeof(dwWait), &bqi.BatteryTag, 103 sizeof(bqi.BatteryTag), &dwOut, NULL) && bqi.BatteryTag) 104 { 105 // With the tag, you can query the battery info. 106 BATTERY_INFORMATION bi = { 0 }; 107 bqi.InformationLevel = BatteryInformation; 108 109 if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), &bi, 110 sizeof(bi), &dwOut, NULL)) 111 { 112 // Only non-UPS system batteries count 113 if (bi.Capabilities & BATTERY_SYSTEM_BATTERY) 114 { 115 if (!(bi.Capabilities & BATTERY_IS_SHORT_TERM)) 116 dwResult |= GBS_HASBATTERY; 117 118 // Query the battery status. 119 BATTERY_WAIT_STATUS bws = { 0 }; 120 bws.BatteryTag = bqi.BatteryTag; 121 122 BATTERY_STATUS bs; 123 if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_STATUS, &bws, sizeof(bws), 124 &bs, sizeof(bs), &dwOut, NULL)) 125 { 126 if (bs.PowerState & BATTERY_POWER_ON_LINE) 127 dwResult &= ~GBS_ONBATTERY; 128 129 // Take average of total capacity of batteries detected! 130 cap = cap*(count)+(float)bs.Capacity / bi.FullChargedCapacity * 100; 131 cap /= count + 1; 132 count++; 133 } 134 } 135 } 136 } 137 CloseHandle(hBattery); 138 } 139 } 140 LocalFree(pdidd); 141 } 142 } 143 } 144 else if (ERROR_NO_MORE_ITEMS == GetLastError()) 145 { 146 break; // Enumeration failed - perhaps we're out of items 147 } 148 } 149 SetupDiDestroyDeviceInfoList(hdev); 150 151 // Final cleanup: If we didn't find a battery, then presume that we 152 // are on AC power. 153 154 if (!(dwResult & GBS_HASBATTERY)) 155 dwResult &= ~GBS_ONBATTERY; 156 157 return S_OK; 158 } 159 160 /*++ 161 * @name Quantize 162 * 163 * This function quantizes the mentioned quantity to nearest level. 164 * 165 * @param p 166 * Should be a quantity in percentage. 167 * @param lvl 168 * Quantization level (this excludes base level 0, which will always be present), default is 10. 169 * 170 * @return Nearest quantized level, can be directly used as array index based on context. 171 * 172 *--*/ 173 static UINT Quantize(float p, UINT lvl = 10) 174 { 175 int i = 0; 176 float f, q = (float)100 / lvl, d = q / 2; 177 for (f = 0; f < p; f += q, i++); 178 179 if ((f - d) <= p) 180 return i; 181 else 182 return i - 1; 183 /* 184 @remarks This function uses centred/symmetric logic for quantization. 185 For the case of lvl = 4, You will get following integer levels if given (p) value falls in between the range partitions: 186 0 <= p < 12.5 : returns 0; (corresponding to 0% centre) 187 12.5 <= p < 37.5 : returns 1; (corresponding to 25% centre) 188 37.5 <= p < 62.5 : returns 2; (corresponding to 50% centre) 189 62.5 <= p < 87.5 : returns 3; (corresponding to 75% centre) 190 87.5 <= p <= 100 : returns 4; (corresponding to 100% centre) 191 */ 192 } 193 194 /*++ 195 * @name DynamicLoadIcon 196 * 197 * Returns the respective icon as per the current battery capacity. 198 * It also does the work of setting global parameters of battery capacity and tooltips. 199 * 200 * @param hinst 201 * A handle to a instance of the module. 202 * 203 * @return The handle to respective battery icon. 204 * 205 *--*/ 206 static HICON DynamicLoadIcon(HINSTANCE hinst) 207 { 208 HICON hBatIcon; 209 float cap = 0; 210 DWORD dw = 0; 211 UINT index = -1; 212 HRESULT hr = GetBatteryState(cap, dw); 213 214 if (!FAILED(hr) && (dw & GBS_HASBATTERY)) 215 { 216 index = Quantize(cap, 4); 217 g_batCap = cap; 218 } 219 else 220 { 221 g_batCap = 0; 222 hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(IDI_BATTCAP_ERR)); 223 g_strTooltip.LoadStringW(IDS_PWR_UNKNOWN_REMAINING); 224 return hBatIcon; 225 } 226 227 if (dw & GBS_ONBATTERY) 228 { 229 hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(br_icons[index])); 230 g_strTooltip.Format(IDS_PWR_PERCENT_REMAINING, cap); 231 } 232 else 233 { 234 hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(bc_icons[index])); 235 g_strTooltip.Format(IDS_PWR_CHARGING, cap); 236 } 237 238 return hBatIcon; 239 } 240 241 HRESULT STDMETHODCALLTYPE Power_Init(_In_ CSysTray * pSysTray) 242 { 243 TRACE("Power_Init\n"); 244 g_hIconBattery = DynamicLoadIcon(g_hInstance); 245 g_IsRunning = TRUE; 246 247 return pSysTray->NotifyIcon(NIM_ADD, ID_ICON_POWER, g_hIconBattery, g_strTooltip); 248 } 249 250 HRESULT STDMETHODCALLTYPE Power_Update(_In_ CSysTray * pSysTray) 251 { 252 TRACE("Power_Update\n"); 253 g_hIconBattery = DynamicLoadIcon(g_hInstance); 254 255 return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_POWER, g_hIconBattery, g_strTooltip); 256 } 257 258 HRESULT STDMETHODCALLTYPE Power_Shutdown(_In_ CSysTray * pSysTray) 259 { 260 TRACE("Power_Shutdown\n"); 261 g_IsRunning = FALSE; 262 263 return pSysTray->NotifyIcon(NIM_DELETE, ID_ICON_POWER, NULL, NULL); 264 } 265 266 static void _RunPower() 267 { 268 ShellExecuteW(NULL, NULL, L"powercfg.cpl", NULL, NULL, SW_SHOWNORMAL); 269 } 270 271 static void _ShowContextMenu(CSysTray * pSysTray) 272 { 273 CString strOpen((LPCSTR)IDS_PWR_PROPERTIES); 274 HMENU hPopup = CreatePopupMenu(); 275 AppendMenuW(hPopup, MF_STRING, IDS_PWR_PROPERTIES, strOpen); 276 277 SetForegroundWindow(pSysTray->GetHWnd()); 278 DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN; 279 POINT pt; 280 GetCursorPos(&pt); 281 282 DWORD id = TrackPopupMenuEx(hPopup, flags, 283 pt.x, pt.y, 284 pSysTray->GetHWnd(), NULL); 285 286 switch (id) 287 { 288 case IDS_PWR_PROPERTIES: 289 _RunPower(); 290 break; 291 } 292 DestroyMenu(hPopup); 293 } 294 295 static 296 BOOLEAN 297 CALLBACK 298 PowerSchemesEnumProc( 299 UINT uiIndex, 300 DWORD dwName, 301 LPWSTR sName, 302 DWORD dwDesc, 303 LPWSTR sDesc, 304 PPOWER_POLICY pp, 305 LPARAM lParam) 306 { 307 PPWRSCHEMECONTEXT PowerSchemeContext = (PPWRSCHEMECONTEXT)lParam; 308 309 if (AppendMenuW(PowerSchemeContext->hPopup, MF_STRING, uiIndex + 1, sName)) 310 { 311 if (PowerSchemeContext->uiFirst == 0) 312 PowerSchemeContext->uiFirst = uiIndex + 1; 313 314 PowerSchemeContext->uiLast = uiIndex + 1; 315 } 316 317 return TRUE; 318 } 319 320 static 321 VOID 322 ShowPowerSchemesPopupMenu( 323 CSysTray *pSysTray) 324 { 325 PWRSCHEMECONTEXT PowerSchemeContext = {NULL, 0, 0}; 326 UINT uiActiveScheme; 327 DWORD id; 328 POINT pt; 329 PowerSchemeContext.hPopup = CreatePopupMenu(); 330 EnumPwrSchemes(PowerSchemesEnumProc, (LPARAM)&PowerSchemeContext); 331 332 if (GetActivePwrScheme(&uiActiveScheme)) 333 { 334 CheckMenuRadioItem(PowerSchemeContext.hPopup, 335 PowerSchemeContext.uiFirst, 336 PowerSchemeContext.uiLast, 337 uiActiveScheme + 1, 338 MF_BYCOMMAND); 339 } 340 341 SetForegroundWindow(pSysTray->GetHWnd()); 342 GetCursorPos(&pt); 343 344 id = TrackPopupMenuEx(PowerSchemeContext.hPopup, 345 TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN, 346 pt.x, 347 pt.y, 348 pSysTray->GetHWnd(), 349 NULL); 350 351 DestroyMenu(PowerSchemeContext.hPopup); 352 353 if (id != 0) 354 SetActivePwrScheme(id - 1, NULL, NULL); 355 } 356 357 HRESULT STDMETHODCALLTYPE Power_Message(_In_ CSysTray * pSysTray, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT &lResult) 358 { 359 TRACE("Power_Message uMsg=%d, wParam=%x, lParam=%x\n", uMsg, wParam, lParam); 360 361 switch (uMsg) 362 { 363 case WM_USER + 220: 364 TRACE("Power_Message: WM_USER+220\n"); 365 if (wParam == 1) 366 { 367 if (lParam == FALSE) 368 return Power_Init(pSysTray); 369 else 370 return Power_Shutdown(pSysTray); 371 } 372 return S_FALSE; 373 374 case WM_USER + 221: 375 TRACE("Power_Message: WM_USER+221\n"); 376 if (wParam == 1) 377 { 378 lResult = (LRESULT)g_IsRunning; 379 return S_OK; 380 } 381 return S_FALSE; 382 383 case ID_ICON_POWER: 384 Power_Update(pSysTray); 385 386 switch (lParam) 387 { 388 case WM_LBUTTONDOWN: 389 break; 390 391 case WM_LBUTTONUP: 392 ShowPowerSchemesPopupMenu(pSysTray); 393 break; 394 395 case WM_LBUTTONDBLCLK: 396 _RunPower(); 397 break; 398 399 case WM_RBUTTONDOWN: 400 break; 401 402 case WM_RBUTTONUP: 403 _ShowContextMenu(pSysTray); 404 break; 405 406 case WM_RBUTTONDBLCLK: 407 break; 408 409 case WM_MOUSEMOVE: 410 break; 411 } 412 return S_OK; 413 414 default: 415 TRACE("Power_Message received for unknown ID %d, ignoring.\n"); 416 return S_FALSE; 417 } 418 419 return S_FALSE; 420 } 421