reactos/dll/shellext/stobject/power.cpp
George Bișoc 07a76b3dec
[STOBJECT] Do not report the remaining battery capacity if it is unknown
Support for system batteries in ReactOS is really minimal to the point of non-existing. We are detecting the presence of any upcoming battery but since there's lacking in critical code that deals with communication
between PO and the battery class driver as the battery systray icon uses GetSystemPowerStatus to gather battery info which in turn inquires the power manager via NtPowerInformation(SystemBatteryState), we have
to report to the user that the remaining capacity is unknown rather than returning a pseudo capacity value.

Technically this so called "pesudo" value is just a construct denoted as BATTERY_PERCENTAGE_UNKNOWN. Not reporting the actual remaining capacity makes sense, as there could be a scenario where the battery may not
properly report its real datum, therefore it's best to be honest to the user what's really going on.

CORE-19452
CORE-18969
2024-08-13 14:17:04 +02:00

314 lines
9 KiB
C++

/*
* 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>
Shriraj Sawant a.k.a SR13 <sr.official@hotmail.com>
* David Quintana <gigaherz@gmail.com>
*/
#include "precomp.h"
#include <devguid.h>
#include <winioctl.h>
#include <powrprof.h>
#include <windows.h>
#include <batclass.h>
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.
typedef struct _PWRSCHEMECONTEXT
{
HMENU hPopup;
UINT uiFirst;
UINT uiLast;
} PWRSCHEMECONTEXT, *PPWRSCHEMECONTEXT;
CString g_strTooltip;
static HICON g_hIconBattery = NULL;
/*++
* @name Quantize
*
* This function quantizes the mentioned quantity to nearest level.
*
* @param p
* Should be a quantity in percentage.
*
* @return Nearest quantized level, can be directly used as array index based on context.
*
@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)
*--*/
static UINT Quantize(BYTE p)
{
if (p <= 12)
return 0;
else if (p > 12 && p <= 37)
return 1;
else if (p > 37 && p <= 62)
return 2;
else if (p > 62 && p <= 87)
return 3;
else
return 4;
}
/*++
* @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)
{
SYSTEM_POWER_STATUS PowerStatus;
HICON hBatIcon;
UINT index = -1;
if (!GetSystemPowerStatus(&PowerStatus) ||
PowerStatus.ACLineStatus == AC_LINE_UNKNOWN ||
PowerStatus.BatteryFlag == BATTERY_FLAG_UNKNOWN)
{
hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(IDI_BATTCAP_ERR));
g_strTooltip.LoadStringW(IDS_PWR_UNKNOWN_REMAINING);
return hBatIcon;
}
if (((PowerStatus.BatteryFlag & BATTERY_FLAG_NO_BATTERY) == 0) &&
(PowerStatus.BatteryLifePercent == BATTERY_PERCENTAGE_UNKNOWN))
{
hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(IDI_BATTCAP_ERR));
g_strTooltip.LoadStringW(IDS_PWR_UNKNOWN_REMAINING);
}
else if (((PowerStatus.BatteryFlag & BATTERY_FLAG_NO_BATTERY) == 0) &&
((PowerStatus.BatteryFlag & BATTERY_FLAG_CHARGING) == BATTERY_FLAG_CHARGING))
{
index = Quantize(PowerStatus.BatteryLifePercent);
hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(bc_icons[index]));
g_strTooltip.Format(IDS_PWR_CHARGING, PowerStatus.BatteryLifePercent);
}
else if (((PowerStatus.BatteryFlag & BATTERY_FLAG_NO_BATTERY) == 0) &&
((PowerStatus.BatteryFlag & BATTERY_FLAG_CHARGING) == 0))
{
index = Quantize(PowerStatus.BatteryLifePercent);
hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(br_icons[index]));
g_strTooltip.Format(IDS_PWR_PERCENT_REMAINING, PowerStatus.BatteryLifePercent);
}
else
{
hBatIcon = LoadIcon(hinst, MAKEINTRESOURCE(IDI_POWER_AC));
g_strTooltip.LoadStringW(IDS_PWR_AC);
}
return hBatIcon;
}
HRESULT STDMETHODCALLTYPE Power_Init(_In_ CSysTray * pSysTray)
{
TRACE("Power_Init\n");
g_hIconBattery = DynamicLoadIcon(g_hInstance);
return pSysTray->NotifyIcon(NIM_ADD, ID_ICON_POWER, g_hIconBattery, g_strTooltip);
}
HRESULT STDMETHODCALLTYPE Power_Update(_In_ CSysTray * pSysTray)
{
TRACE("Power_Update\n");
g_hIconBattery = DynamicLoadIcon(g_hInstance);
return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_POWER, g_hIconBattery, g_strTooltip);
}
HRESULT STDMETHODCALLTYPE Power_Shutdown(_In_ CSysTray * pSysTray)
{
TRACE("Power_Shutdown\n");
return pSysTray->NotifyIcon(NIM_DELETE, ID_ICON_POWER, NULL, NULL);
}
static void _RunPower()
{
ShellExecuteW(NULL, NULL, L"powercfg.cpl", NULL, NULL, SW_SHOWNORMAL);
}
static void _ShowContextMenu(CSysTray * pSysTray)
{
CString strOpen((LPCSTR)IDS_PWR_PROPERTIES);
HMENU hPopup = CreatePopupMenu();
AppendMenuW(hPopup, MF_STRING, IDS_PWR_PROPERTIES, strOpen);
SetMenuDefaultItem(hPopup, IDS_PWR_PROPERTIES, FALSE);
SetForegroundWindow(pSysTray->GetHWnd());
DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN;
POINT pt;
GetCursorPos(&pt);
DWORD id = TrackPopupMenuEx(hPopup, flags,
pt.x, pt.y,
pSysTray->GetHWnd(), NULL);
switch (id)
{
case IDS_PWR_PROPERTIES:
_RunPower();
break;
}
DestroyMenu(hPopup);
}
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(
CSysTray *pSysTray)
{
PWRSCHEMECONTEXT PowerSchemeContext = {NULL, 0, 0};
UINT uiActiveScheme;
DWORD id;
POINT pt;
PowerSchemeContext.hPopup = CreatePopupMenu();
EnumPwrSchemes(PowerSchemesEnumProc, (LPARAM)&PowerSchemeContext);
if (GetActivePwrScheme(&uiActiveScheme))
{
CheckMenuRadioItem(PowerSchemeContext.hPopup,
PowerSchemeContext.uiFirst,
PowerSchemeContext.uiLast,
uiActiveScheme + 1,
MF_BYCOMMAND);
}
SetForegroundWindow(pSysTray->GetHWnd());
GetCursorPos(&pt);
id = TrackPopupMenuEx(PowerSchemeContext.hPopup,
TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN,
pt.x,
pt.y,
pSysTray->GetHWnd(),
NULL);
DestroyMenu(PowerSchemeContext.hPopup);
if (id != 0)
SetActivePwrScheme(id - 1, NULL, NULL);
}
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 == POWER_SERVICE_FLAG)
{
if (lParam)
{
pSysTray->EnableService(POWER_SERVICE_FLAG, TRUE);
return Power_Init(pSysTray);
}
else
{
pSysTray->EnableService(POWER_SERVICE_FLAG, FALSE);
return Power_Shutdown(pSysTray);
}
}
return S_FALSE;
case WM_USER + 221:
TRACE("Power_Message: WM_USER+221\n");
if (wParam == POWER_SERVICE_FLAG)
{
lResult = (LRESULT)pSysTray->IsServiceEnabled(POWER_SERVICE_FLAG);
return S_OK;
}
return S_FALSE;
case WM_TIMER:
if (wParam == POWER_TIMER_ID)
{
KillTimer(pSysTray->GetHWnd(), POWER_TIMER_ID);
ShowPowerSchemesPopupMenu(pSysTray);
}
break;
case ID_ICON_POWER:
Power_Update(pSysTray);
switch (lParam)
{
case WM_LBUTTONDOWN:
SetTimer(pSysTray->GetHWnd(), POWER_TIMER_ID, GetDoubleClickTime(), NULL);
break;
case WM_LBUTTONUP:
break;
case WM_LBUTTONDBLCLK:
KillTimer(pSysTray->GetHWnd(), POWER_TIMER_ID);
_RunPower();
break;
case WM_RBUTTONDOWN:
break;
case WM_RBUTTONUP:
_ShowContextMenu(pSysTray);
break;
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;
}