2017-03-23 19:51:53 +00:00
|
|
|
/*
|
|
|
|
* PROJECT: ReactOS system libraries
|
|
|
|
* LICENSE: GPL - See COPYING in the top level directory
|
|
|
|
* FILE: dll/shellext/stobject/power.cpp
|
|
|
|
* PURPOSE: Power notification icon handler
|
|
|
|
* PROGRAMMERS: Eric Kohl <eric.kohl@reactos.org>
|
2017-10-16 14:11:10 +00:00
|
|
|
Shriraj Sawant a.k.a SR13 <sr.official@hotmail.com>
|
2017-03-23 19:51:53 +00:00
|
|
|
* David Quintana <gigaherz@gmail.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "precomp.h"
|
|
|
|
|
2017-12-08 22:15:31 +00:00
|
|
|
#include <devguid.h>
|
|
|
|
#include <winioctl.h>
|
|
|
|
#include <powrprof.h>
|
|
|
|
#include <windows.h>
|
|
|
|
#include <batclass.h>
|
2017-10-16 14:11:10 +00:00
|
|
|
|
|
|
|
#define GBS_HASBATTERY 0x1
|
|
|
|
#define GBS_ONBATTERY 0x2
|
|
|
|
|
|
|
|
int br_icons[5] = { IDI_BATTCAP0, IDI_BATTCAP1, IDI_BATTCAP2, IDI_BATTCAP3, IDI_BATTCAP4 }; // battery mode icons.
|
|
|
|
int bc_icons[5] = { IDI_BATTCHA0, IDI_BATTCHA1, IDI_BATTCHA2, IDI_BATTCHA3, IDI_BATTCHA4 }; // charging mode icons.
|
|
|
|
|
2017-03-25 13:35:41 +00:00
|
|
|
typedef struct _PWRSCHEMECONTEXT
|
|
|
|
{
|
|
|
|
HMENU hPopup;
|
|
|
|
UINT uiFirst;
|
|
|
|
UINT uiLast;
|
|
|
|
} PWRSCHEMECONTEXT, *PPWRSCHEMECONTEXT;
|
|
|
|
|
2017-10-16 14:11:10 +00:00
|
|
|
CString g_strTooltip;
|
|
|
|
static float g_batCap = 0;
|
2017-08-18 14:49:11 +00:00
|
|
|
static HICON g_hIconBattery = NULL;
|
2017-03-23 19:51:53 +00:00
|
|
|
static BOOL g_IsRunning = FALSE;
|
|
|
|
|
2017-10-16 14:11:10 +00:00
|
|
|
/*++
|
|
|
|
* @name GetBatteryState
|
|
|
|
*
|
|
|
|
* Enumerates the available battery devices and provides the remaining capacity.
|
|
|
|
*
|
|
|
|
* @param cap
|
|
|
|
* If no error occurs, then this will contain average remaining capacity.
|
|
|
|
* @param dwResult
|
|
|
|
* Helps in making battery type checks.
|
|
|
|
* {
|
|
|
|
* Returned value includes GBS_HASBATTERY if the system has a non-UPS battery,
|
|
|
|
* and GBS_ONBATTERY if the system is running on a battery.
|
|
|
|
* dwResult & GBS_ONBATTERY means we have not yet found AC power.
|
|
|
|
* dwResult & GBS_HASBATTERY means we have found a non-UPS battery.
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* @return The error code.
|
|
|
|
*
|
|
|
|
*--*/
|
|
|
|
static HRESULT GetBatteryState(float& cap, DWORD& dwResult)
|
|
|
|
{
|
|
|
|
cap = 0;
|
|
|
|
dwResult = GBS_ONBATTERY;
|
2017-03-23 19:51:53 +00:00
|
|
|
|
2017-10-16 14:11:10 +00:00
|
|
|
HDEVINFO hdev = SetupDiGetClassDevs(&GUID_DEVCLASS_BATTERY, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
|
|
|
if (INVALID_HANDLE_VALUE == hdev)
|
|
|
|
return E_HANDLE;
|
|
|
|
|
|
|
|
// Limit search to 100 batteries max
|
|
|
|
for (int idev = 0, count = 0; idev < 100; idev++)
|
|
|
|
{
|
|
|
|
SP_DEVICE_INTERFACE_DATA did = { 0 };
|
|
|
|
did.cbSize = sizeof(did);
|
|
|
|
|
|
|
|
if (SetupDiEnumDeviceInterfaces(hdev, 0, &GUID_DEVCLASS_BATTERY, idev, &did))
|
|
|
|
{
|
|
|
|
DWORD cbRequired = 0;
|
|
|
|
|
|
|
|
SetupDiGetDeviceInterfaceDetail(hdev, &did, 0, 0, &cbRequired, 0);
|
|
|
|
if (ERROR_INSUFFICIENT_BUFFER == GetLastError())
|
|
|
|
{
|
|
|
|
PSP_DEVICE_INTERFACE_DETAIL_DATA pdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)LocalAlloc(LPTR, cbRequired);
|
|
|
|
if (pdidd)
|
|
|
|
{
|
|
|
|
pdidd->cbSize = sizeof(*pdidd);
|
|
|
|
if (SetupDiGetDeviceInterfaceDetail(hdev, &did, pdidd, cbRequired, &cbRequired, 0))
|
|
|
|
{
|
|
|
|
// Enumerated a battery. Ask it for information.
|
|
|
|
HANDLE hBattery = CreateFile(pdidd->DevicePath, GENERIC_READ | GENERIC_WRITE,
|
|
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
|
|
|
|
|
|
if (INVALID_HANDLE_VALUE != hBattery)
|
|
|
|
{
|
|
|
|
// Ask the battery for its tag.
|
|
|
|
BATTERY_QUERY_INFORMATION bqi = { 0 };
|
|
|
|
|
|
|
|
DWORD dwWait = 0;
|
|
|
|
DWORD dwOut;
|
|
|
|
|
|
|
|
if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_TAG, &dwWait, sizeof(dwWait), &bqi.BatteryTag,
|
|
|
|
sizeof(bqi.BatteryTag), &dwOut, NULL) && bqi.BatteryTag)
|
|
|
|
{
|
|
|
|
// With the tag, you can query the battery info.
|
|
|
|
BATTERY_INFORMATION bi = { 0 };
|
|
|
|
bqi.InformationLevel = BatteryInformation;
|
|
|
|
|
|
|
|
if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), &bi,
|
|
|
|
sizeof(bi), &dwOut, NULL))
|
|
|
|
{
|
|
|
|
// Only non-UPS system batteries count
|
|
|
|
if (bi.Capabilities & BATTERY_SYSTEM_BATTERY)
|
|
|
|
{
|
|
|
|
if (!(bi.Capabilities & BATTERY_IS_SHORT_TERM))
|
|
|
|
dwResult |= GBS_HASBATTERY;
|
|
|
|
|
|
|
|
// Query the battery status.
|
|
|
|
BATTERY_WAIT_STATUS bws = { 0 };
|
|
|
|
bws.BatteryTag = bqi.BatteryTag;
|
|
|
|
|
|
|
|
BATTERY_STATUS bs;
|
|
|
|
if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_STATUS, &bws, sizeof(bws),
|
|
|
|
&bs, sizeof(bs), &dwOut, NULL))
|
|
|
|
{
|
|
|
|
if (bs.PowerState & BATTERY_POWER_ON_LINE)
|
|
|
|
dwResult &= ~GBS_ONBATTERY;
|
|
|
|
|
|
|
|
// Take average of total capacity of batteries detected!
|
|
|
|
cap = cap*(count)+(float)bs.Capacity / bi.FullChargedCapacity * 100;
|
|
|
|
cap /= count + 1;
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
CloseHandle(hBattery);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
LocalFree(pdidd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (ERROR_NO_MORE_ITEMS == GetLastError())
|
|
|
|
{
|
|
|
|
break; // Enumeration failed - perhaps we're out of items
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SetupDiDestroyDeviceInfoList(hdev);
|
|
|
|
|
|
|
|
// Final cleanup: If we didn't find a battery, then presume that we
|
|
|
|
// are on AC power.
|
|
|
|
|
|
|
|
if (!(dwResult & GBS_HASBATTERY))
|
|
|
|
dwResult &= ~GBS_ONBATTERY;
|
|
|
|
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*++
|
|
|
|
* @name Quantize
|
|
|
|
*
|
|
|
|
* This function quantizes the mentioned quantity to nearest level.
|
|
|
|
*
|
|
|
|
* @param p
|
|
|
|
* Should be a quantity in percentage.
|
|
|
|
* @param lvl
|
|
|
|
* Quantization level (this excludes base level 0, which will always be present), default is 10.
|
|
|
|
*
|
|
|
|
* @return Nearest quantized level, can be directly used as array index based on context.
|
|
|
|
*
|
|
|
|
*--*/
|
|
|
|
static UINT Quantize(float p, UINT lvl = 10)
|
2017-03-23 19:51:53 +00:00
|
|
|
{
|
2017-10-16 14:11:10 +00:00
|
|
|
int i = 0;
|
|
|
|
float f, q = (float)100 / lvl, d = q / 2;
|
|
|
|
for (f = 0; f < p; f += q, i++);
|
|
|
|
|
|
|
|
if ((f - d) <= p)
|
|
|
|
return i;
|
|
|
|
else
|
|
|
|
return i - 1;
|
|
|
|
/*
|
|
|
|
@remarks This function uses centred/symmetric logic for quantization.
|
|
|
|
For the case of lvl = 4, You will get following integer levels if given (p) value falls in between the range partitions:
|
|
|
|
0 <= p < 12.5 : returns 0; (corresponding to 0% centre)
|
|
|
|
12.5 <= p < 37.5 : returns 1; (corresponding to 25% centre)
|
|
|
|
37.5 <= p < 62.5 : returns 2; (corresponding to 50% centre)
|
|
|
|
62.5 <= p < 87.5 : returns 3; (corresponding to 75% centre)
|
|
|
|
87.5 <= p <= 100 : returns 4; (corresponding to 100% centre)
|
|
|
|
*/
|
|
|
|
}
|
2017-03-23 19:51:53 +00:00
|
|
|
|
2017-10-16 14:11:10 +00:00
|
|
|
/*++
|
|
|
|
* @name DynamicLoadIcon
|
|
|
|
*
|
|
|
|
* Returns the respective icon as per the current battery capacity.
|
|
|
|
* It also does the work of setting global parameters of battery capacity and tooltips.
|
|
|
|
*
|
|
|
|
* @param hinst
|
|
|
|
* A handle to a instance of the module.
|
|
|
|
*
|
|
|
|
* @return The handle to respective battery icon.
|
|
|
|
*
|
|
|
|
*--*/
|
|
|
|
static HICON DynamicLoadIcon(HINSTANCE hinst)
|
|
|
|
{
|
|
|
|
HICON hBatIcon;
|
|
|
|
float cap = 0;
|
|
|
|
DWORD dw = 0;
|
|
|
|
UINT index = -1;
|
|
|
|
HRESULT hr = GetBatteryState(cap, dw);
|
|
|
|
|
|
|
|
if (!FAILED(hr) && (dw & GBS_HASBATTERY))
|
|
|
|
{
|
|
|
|
index = Quantize(cap, 4);
|
|
|
|
g_batCap = cap;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_batCap = 0;
|
|
|
|
hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(IDI_BATTCAP_ERR));
|
|
|
|
g_strTooltip.LoadStringW(IDS_PWR_UNKNOWN_REMAINING);
|
|
|
|
return hBatIcon;
|
|
|
|
}
|
2017-03-23 19:51:53 +00:00
|
|
|
|
2017-10-16 14:11:10 +00:00
|
|
|
if (dw & GBS_ONBATTERY)
|
|
|
|
{
|
|
|
|
hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(br_icons[index]));
|
|
|
|
g_strTooltip.Format(IDS_PWR_PERCENT_REMAINING, cap);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(bc_icons[index]));
|
|
|
|
g_strTooltip.Format(IDS_PWR_CHARGING, cap);
|
|
|
|
}
|
2017-03-23 19:51:53 +00:00
|
|
|
|
2017-10-16 14:11:10 +00:00
|
|
|
return hBatIcon;
|
|
|
|
}
|
2017-03-23 19:51:53 +00:00
|
|
|
|
2017-10-16 14:11:10 +00:00
|
|
|
HRESULT STDMETHODCALLTYPE Power_Init(_In_ CSysTray * pSysTray)
|
|
|
|
{
|
|
|
|
TRACE("Power_Init\n");
|
|
|
|
g_hIconBattery = DynamicLoadIcon(g_hInstance);
|
2017-03-23 19:51:53 +00:00
|
|
|
g_IsRunning = TRUE;
|
|
|
|
|
2017-10-16 14:11:10 +00:00
|
|
|
return pSysTray->NotifyIcon(NIM_ADD, ID_ICON_POWER, g_hIconBattery, g_strTooltip);
|
2017-03-23 19:51:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE Power_Update(_In_ CSysTray * pSysTray)
|
|
|
|
{
|
|
|
|
TRACE("Power_Update\n");
|
2017-10-16 14:11:10 +00:00
|
|
|
g_hIconBattery = DynamicLoadIcon(g_hInstance);
|
|
|
|
|
|
|
|
return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_POWER, g_hIconBattery, g_strTooltip);
|
2017-03-23 19:51:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE Power_Shutdown(_In_ CSysTray * pSysTray)
|
|
|
|
{
|
|
|
|
TRACE("Power_Shutdown\n");
|
|
|
|
g_IsRunning = FALSE;
|
|
|
|
|
|
|
|
return pSysTray->NotifyIcon(NIM_DELETE, ID_ICON_POWER, NULL, NULL);
|
|
|
|
}
|
|
|
|
|
2017-10-16 14:11:10 +00:00
|
|
|
static void _RunPower()
|
2017-03-23 19:51:53 +00:00
|
|
|
{
|
2017-10-16 14:11:10 +00:00
|
|
|
ShellExecuteW(NULL, NULL, L"powercfg.cpl", NULL, NULL, SW_SHOWNORMAL);
|
2017-03-23 19:51:53 +00:00
|
|
|
}
|
|
|
|
|
2017-10-16 14:11:10 +00:00
|
|
|
static void _ShowContextMenu(CSysTray * pSysTray)
|
2017-03-23 19:51:53 +00:00
|
|
|
{
|
2017-10-16 14:11:10 +00:00
|
|
|
CString strOpen((LPCSTR)IDS_PWR_PROPERTIES);
|
|
|
|
HMENU hPopup = CreatePopupMenu();
|
|
|
|
AppendMenuW(hPopup, MF_STRING, IDS_PWR_PROPERTIES, strOpen);
|
2017-03-23 19:51:53 +00:00
|
|
|
|
|
|
|
SetForegroundWindow(pSysTray->GetHWnd());
|
2017-10-16 14:11:10 +00:00
|
|
|
DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN;
|
|
|
|
POINT pt;
|
|
|
|
GetCursorPos(&pt);
|
2017-03-23 19:51:53 +00:00
|
|
|
|
2017-10-16 14:11:10 +00:00
|
|
|
DWORD id = TrackPopupMenuEx(hPopup, flags,
|
|
|
|
pt.x, pt.y,
|
|
|
|
pSysTray->GetHWnd(), NULL);
|
2017-03-23 19:51:53 +00:00
|
|
|
|
2017-10-16 14:11:10 +00:00
|
|
|
switch (id)
|
|
|
|
{
|
|
|
|
case IDS_PWR_PROPERTIES:
|
|
|
|
_RunPower();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
DestroyMenu(hPopup);
|
2017-03-23 19:51:53 +00:00
|
|
|
}
|
|
|
|
|
2017-03-25 13:35:41 +00:00
|
|
|
static
|
|
|
|
BOOLEAN
|
|
|
|
CALLBACK
|
|
|
|
PowerSchemesEnumProc(
|
|
|
|
UINT uiIndex,
|
|
|
|
DWORD dwName,
|
|
|
|
LPWSTR sName,
|
|
|
|
DWORD dwDesc,
|
|
|
|
LPWSTR sDesc,
|
|
|
|
PPOWER_POLICY pp,
|
|
|
|
LPARAM lParam)
|
|
|
|
{
|
|
|
|
PPWRSCHEMECONTEXT PowerSchemeContext = (PPWRSCHEMECONTEXT)lParam;
|
|
|
|
|
|
|
|
if (AppendMenuW(PowerSchemeContext->hPopup, MF_STRING, uiIndex + 1, sName))
|
|
|
|
{
|
|
|
|
if (PowerSchemeContext->uiFirst == 0)
|
|
|
|
PowerSchemeContext->uiFirst = uiIndex + 1;
|
|
|
|
|
|
|
|
PowerSchemeContext->uiLast = uiIndex + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static
|
|
|
|
VOID
|
|
|
|
ShowPowerSchemesPopupMenu(
|
2017-10-16 14:11:10 +00:00
|
|
|
CSysTray *pSysTray)
|
2017-03-25 13:35:41 +00:00
|
|
|
{
|
|
|
|
PWRSCHEMECONTEXT PowerSchemeContext = {NULL, 0, 0};
|
|
|
|
UINT uiActiveScheme;
|
2017-10-16 14:11:10 +00:00
|
|
|
DWORD id;
|
|
|
|
POINT pt;
|
2017-03-25 13:35:41 +00:00
|
|
|
PowerSchemeContext.hPopup = CreatePopupMenu();
|
|
|
|
EnumPwrSchemes(PowerSchemesEnumProc, (LPARAM)&PowerSchemeContext);
|
|
|
|
|
|
|
|
if (GetActivePwrScheme(&uiActiveScheme))
|
|
|
|
{
|
|
|
|
CheckMenuRadioItem(PowerSchemeContext.hPopup,
|
|
|
|
PowerSchemeContext.uiFirst,
|
|
|
|
PowerSchemeContext.uiLast,
|
|
|
|
uiActiveScheme + 1,
|
|
|
|
MF_BYCOMMAND);
|
|
|
|
}
|
|
|
|
|
2017-10-16 14:11:10 +00:00
|
|
|
SetForegroundWindow(pSysTray->GetHWnd());
|
|
|
|
GetCursorPos(&pt);
|
|
|
|
|
2017-03-25 13:35:41 +00:00
|
|
|
id = TrackPopupMenuEx(PowerSchemeContext.hPopup,
|
|
|
|
TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN,
|
2017-10-16 14:11:10 +00:00
|
|
|
pt.x,
|
|
|
|
pt.y,
|
|
|
|
pSysTray->GetHWnd(),
|
2017-03-25 13:35:41 +00:00
|
|
|
NULL);
|
|
|
|
|
|
|
|
DestroyMenu(PowerSchemeContext.hPopup);
|
|
|
|
|
|
|
|
if (id != 0)
|
|
|
|
SetActivePwrScheme(id - 1, NULL, NULL);
|
|
|
|
}
|
|
|
|
|
2017-03-23 19:51:53 +00:00
|
|
|
HRESULT STDMETHODCALLTYPE Power_Message(_In_ CSysTray * pSysTray, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT &lResult)
|
|
|
|
{
|
|
|
|
TRACE("Power_Message uMsg=%d, wParam=%x, lParam=%x\n", uMsg, wParam, lParam);
|
|
|
|
|
|
|
|
switch (uMsg)
|
|
|
|
{
|
|
|
|
case WM_USER + 220:
|
|
|
|
TRACE("Power_Message: WM_USER+220\n");
|
|
|
|
if (wParam == 1)
|
|
|
|
{
|
|
|
|
if (lParam == FALSE)
|
|
|
|
return Power_Init(pSysTray);
|
|
|
|
else
|
|
|
|
return Power_Shutdown(pSysTray);
|
|
|
|
}
|
|
|
|
return S_FALSE;
|
|
|
|
|
|
|
|
case WM_USER + 221:
|
|
|
|
TRACE("Power_Message: WM_USER+221\n");
|
|
|
|
if (wParam == 1)
|
|
|
|
{
|
|
|
|
lResult = (LRESULT)g_IsRunning;
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
return S_FALSE;
|
|
|
|
|
2018-02-28 19:29:29 +00:00
|
|
|
case WM_TIMER:
|
|
|
|
if (wParam == POWER_TIMER_ID)
|
|
|
|
{
|
|
|
|
KillTimer(pSysTray->GetHWnd(), POWER_TIMER_ID);
|
|
|
|
ShowPowerSchemesPopupMenu(pSysTray);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2017-03-23 19:51:53 +00:00
|
|
|
case ID_ICON_POWER:
|
|
|
|
Power_Update(pSysTray);
|
|
|
|
|
|
|
|
switch (lParam)
|
|
|
|
{
|
|
|
|
case WM_LBUTTONDOWN:
|
2018-03-06 19:41:28 +00:00
|
|
|
SetTimer(pSysTray->GetHWnd(), POWER_TIMER_ID, GetDoubleClickTime(), NULL);
|
2017-03-23 19:51:53 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_LBUTTONUP:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_LBUTTONDBLCLK:
|
2018-02-28 19:29:29 +00:00
|
|
|
KillTimer(pSysTray->GetHWnd(), POWER_TIMER_ID);
|
2017-10-16 14:11:10 +00:00
|
|
|
_RunPower();
|
2017-03-23 19:51:53 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_RBUTTONDOWN:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_RBUTTONUP:
|
2017-10-16 14:11:10 +00:00
|
|
|
_ShowContextMenu(pSysTray);
|
2017-03-25 13:35:41 +00:00
|
|
|
break;
|
2017-03-23 19:51:53 +00:00
|
|
|
|
|
|
|
case WM_RBUTTONDBLCLK:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_MOUSEMOVE:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return S_OK;
|
|
|
|
|
|
|
|
default:
|
|
|
|
TRACE("Power_Message received for unknown ID %d, ignoring.\n");
|
|
|
|
return S_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return S_FALSE;
|
|
|
|
}
|