windows-nt/Source/XPSP1/NT/shell/ext/systray/dll/power.c
2020-09-26 16:20:57 +08:00

787 lines
22 KiB
C

/*******************************************************************************
*
* (C) COPYRIGHT MICROSOFT CORP., 1993-1995
*
* TITLE: POWER.C
*
* VERSION: 2.0
*
* AUTHOR: TCS/RAL
*
* DATE: 08 Feb 1994
*
********************************************************************************
*
* CHANGE LOG:
*
* DATE REV DESCRIPTION
* ----------- --- -------------------------------------------------------------
* 08 Feb 1994 TCS Original implementation.
* 11 Nov 1994 RAL Converted from batmeter to systray
* 11 Aug 1995 JEM Split batmeter functions into power.c & minor enahncements
* 23 Oct 1995 Shawnb UNICODE Enabled
* 24 Jan 1997 Reedb ACPI power management, common battery meter code.
*
*******************************************************************************/
#include "stdafx.h"
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <initguid.h>
#include <ntpoapi.h>
#include <poclass.h>
#include "systray.h"
#include "batmeter.h"
#include "powrprof.h"
#include "powercfp.h"
#define UPDATE_REGISTRY TRUE
#define NO_REGISTRY_UPDATE FALSE
// Structure to manage the power profile enum proc parameters.
typedef struct _POWER_PROFILE_ENUM_PROC_PARAMS
{
UINT uiCurActiveIndex;
HMENU hMenu;
UINT uiCurActiveID;
} POWER_PROFILE_ENUM_PROC_PARAMS, *PPOWER_PROFILE_ENUM_PROC_PARAMS;
// G L O B A L D A T A -------------------------------------------------------
BOOL g_bPowerEnabled; // Tracks the power service state.
UINT g_uiPowerSchemeCount; // Number of power schemes, left context menu.
HMENU g_hMenu[2]; // Context menus.
// BatMeter creation parameters.
HWND g_hwndBatMeter;
BOOL g_bShowMulti;
HWND g_hwndBatMeterFrame;
GLOBAL_POWER_POLICY g_gpp;
// Context sensitive help must be added to the windows.hlp file,
// for now we will use this dummy array define. Remove when windows.hlp updated.
#define IDH_POWERCFG_ENABLEMULTI IDH_POWERCFG_POWERSTATUSBAR
const DWORD g_ContextMenuHelpIDs[] = {
IDC_POWERSTATUSGROUPBOX, IDH_COMM_GROUPBOX,
IDC_ENABLEMETER, IDH_POWERCFG_ENABLEMETER,
IDC_ENABLEMULTI, IDH_POWERCFG_ENABLEMULTI,
0, 0
};
// Used to track registration for WM_DEVICECHANGED message.
HDEVNOTIFY g_hDevNotify;
/*******************************************************************************
*
* RunningOffLine
*
* DESCRIPTION:
*
* PARAMETERS:
*
*******************************************************************************/
BOOLEAN RunningOffLine(void)
{
SYSTEM_POWER_STATUS sps;
BOOLEAN bRet = FALSE;
if (GetSystemPowerStatus(&sps)) {
if (sps.ACLineStatus == 0) {
bRet = TRUE;
}
}
return bRet;
}
/*----------------------------------------------------------------------------
* Power_OnCommand
*
* Process WM_COMMAND msgs for the battery meter dialog.
*
*----------------------------------------------------------------------------*/
void
Power_OnCommand(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
BOOL Checked;
DWORD dwMask;
UINT uiCommandID = GET_WM_COMMAND_ID(wParam, lParam);
switch (uiCommandID) {
case IDC_ENABLEMETER:
dwMask = EnableSysTrayBatteryMeter;
goto DoUpdateFlags;
case IDC_ENABLEMULTI:
dwMask = EnableMultiBatteryDisplay;
goto DoUpdateFlags;
DoUpdateFlags:
Checked = (IsDlgButtonChecked(hWnd, uiCommandID) == BST_CHECKED);
Update_PowerFlags(dwMask, Checked);
if (uiCommandID == IDC_ENABLEMETER) {
PowerCfg_Notify();
SysTray_EnableService(STSERVICE_POWER, g_gpp.user.GlobalFlags & EnableSysTrayBatteryMeter);
}
else {
g_bShowMulti = Checked;
Power_UpdateStatus(hWnd, NIM_MODIFY, TRUE);
}
break;
case IDCANCEL:
EndDialog(hWnd, wParam);
break;
default:
// Notify battery meter of enter key events.
if (HIWORD(wParam) == BN_CLICKED) {
SendMessage(g_hwndBatMeter, WM_COMMAND, wParam, lParam);
}
}
}
/*******************************************************************************
*
* Power_OnPowerBroadcast
*
* DESCRIPTION:
* Process WM_POWERBROADCAS message for the battery meter dialog.
*
* PARAMETERS:
*
*******************************************************************************/
void Power_OnPowerBroadcast(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
if (wParam == PBT_APMPOWERSTATUSCHANGE) {
// If the power icon is not showing (power service disabled) and
// we are running on batteries, enable the systray power service.
if (!g_bPowerEnabled && RunningOffLine()) {
PostMessage(hWnd, STWM_ENABLESERVICE, STSERVICE_POWER, TRUE);
} else
// If the power icon is showing (power service enabled) and
// we are not running on batteries, disable the systray power service.
if (g_bPowerEnabled && !RunningOffLine()) {
PostMessage(hWnd, STWM_ENABLESERVICE, STSERVICE_POWER, FALSE);
}
// Don't change the state of the power service, just update the icon.
Power_UpdateStatus(hWnd, NIM_MODIFY, FALSE);
}
}
/*******************************************************************************
*
* Power_OnDeviceChange
*
* DESCRIPTION:
* Process WM_DEVICECHANGE message for the battery meter dialog.
*
* PARAMETERS:
*
*******************************************************************************/
void Power_OnDeviceChange(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
//
// Only listen to the WM_DEVICECHANGE if it is for GUID_DEVICE_BATTERY and
// it is a DBT_DEVICEARRIVAL, DBT_DEVICEREMOVECOMPLETE, or DBT_DEVICEQUERYREMOVEFAILED.
//
if (((wParam == DBT_DEVICEARRIVAL) ||
(wParam == DBT_DEVICEREMOVECOMPLETE) ||
(wParam == DBT_DEVICEQUERYREMOVEFAILED)) &&
(lParam) &&
(((PDEV_BROADCAST_DEVICEINTERFACE)lParam)->dbcc_devicetype == DBT_DEVTYP_DEVICEINTERFACE) &&
(IsEqualGUID(&((PDEV_BROADCAST_DEVICEINTERFACE)lParam)->dbcc_classguid, &GUID_DEVICE_BATTERY))) {
// Make sure BatMeter has been initialized.
if (g_hwndBatMeterFrame) {
if (g_hwndBatMeter) {
g_hwndBatMeter = DestroyBatMeter(g_hwndBatMeter);
}
g_hwndBatMeter = CreateBatMeter(hWnd,
g_hwndBatMeterFrame,
g_bShowMulti,
NULL);
InvalidateRect(hWnd, NULL, TRUE);
}
}
}
/*******************************************************************************
*
* Power_OnActivate
*
* DESCRIPTION:
*
* PARAMETERS:
*
*******************************************************************************/
BOOLEAN Power_OnActivate(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
if (g_hwndBatMeter) {
SendMessage(g_hwndBatMeter, WM_ACTIVATE, wParam, lParam);
return TRUE;
}
return FALSE;
}
/*******************************************************************************
*
* PowerProfileEnumProc
*
* DESCRIPTION:
*
* PARAMETERS:
*
*******************************************************************************/
#define POWERMENU_SCHEME 300
BOOLEAN CALLBACK PowerProfileEnumProc(
UINT uiID,
DWORD dwNameSize,
LPTSTR lpszName,
DWORD dwDescSize,
LPTSTR lpszDesc,
PPOWER_POLICY ppp,
LPARAM lParam
)
{
PPOWER_PROFILE_ENUM_PROC_PARAMS pppepp;
MENUITEMINFO mii;
if ((pppepp = (PPOWER_PROFILE_ENUM_PROC_PARAMS) lParam) == NULL) {
return FALSE;
}
AppendMenu(pppepp->hMenu, MF_STRING,
POWERMENU_SCHEME + g_uiPowerSchemeCount, lpszName);
// Store the power scheme ID in the menu info.
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_DATA;
mii.dwItemData = uiID;
SetMenuItemInfo(pppepp->hMenu,
POWERMENU_SCHEME + g_uiPowerSchemeCount,
FALSE, &mii);
if (uiID == pppepp->uiCurActiveID) {
pppepp->uiCurActiveIndex = POWERMENU_SCHEME + g_uiPowerSchemeCount;
}
g_uiPowerSchemeCount++;
return TRUE;
}
/*----------------------------------------------------------------------------
* GetPowerMenu()
*
* Build a menu containing battery meter/power selections.
*
*----------------------------------------------------------------------------*/
#define POWERMENU_OPEN 100
#define POWERMENU_PROPERTIES 101
#define POWERMENU_ENABLEWARN 200
#define POWERMENU_SHOWTIME 201
#define POWERMENU_SHOWPERCENT 202
HMENU
GetPowerMenu(LONG l)
{
LPTSTR lpszMenu;
UINT uiCurActiveID;
POWER_PROFILE_ENUM_PROC_PARAMS ppepp;
if (l > 0)
{
// Right button menu -- can change, rebuild each time.
if (g_hMenu[0])
{
DestroyMenu(g_hMenu[0]);
}
g_hMenu[1] = CreatePopupMenu();
// Properties for Power, PowerCfg.
if ((lpszMenu = LoadDynamicString(IDS_PROPFORPOWER)) != NULL)
{
AppendMenu(g_hMenu[1], MF_STRING, POWERMENU_PROPERTIES, lpszMenu);
DeleteDynamicString(lpszMenu);
}
// If we have a battery meter, add it's menu item and set as default.
if (g_hwndBatMeter) {
if ((lpszMenu = LoadDynamicString(IDS_OPEN)) != NULL)
{
AppendMenu(g_hMenu[1], MF_STRING, POWERMENU_OPEN, lpszMenu);
DeleteDynamicString(lpszMenu);
}
// Open BatMeter is default (double click action)
SetMenuDefaultItem(g_hMenu[1], POWERMENU_OPEN, FALSE);
}
else {
// Use open PowerCfg as default (double click action)
SetMenuDefaultItem(g_hMenu[1], POWERMENU_PROPERTIES, FALSE);
}
}
// Left button menu -- can change, rebuild each time.
if (g_hMenu[0])
{
DestroyMenu(g_hMenu[0]);
}
g_hMenu[0] = CreatePopupMenu();
// Get the currently active power policies.
if (GetActivePwrScheme(&uiCurActiveID)) {
g_uiPowerSchemeCount = 0;
ppepp.hMenu = g_hMenu[0];
ppepp.uiCurActiveID = uiCurActiveID;
EnumPwrSchemes(PowerProfileEnumProc, (LPARAM)&ppepp);
// Check the currently active menu item.
CheckMenuRadioItem(g_hMenu[0],
POWERMENU_SCHEME,
POWERMENU_SCHEME + g_uiPowerSchemeCount - 1,
ppepp.uiCurActiveIndex,
MF_BYCOMMAND);
}
return g_hMenu[l];
}
/*----------------------------------------------------------------------------
* Power_Open
*
* Update and display the battery meter dialog
*
*----------------------------------------------------------------------------*/
void
Power_Open(HWND hWnd)
{
if (g_hwndBatMeter) {
SetFocus(GetDlgItem(hWnd, IDC_ENABLEMETER));
CheckDlgButton(hWnd, IDC_ENABLEMULTI,
(g_gpp.user.GlobalFlags & EnableMultiBatteryDisplay) ?
BST_CHECKED : BST_UNCHECKED);
CheckDlgButton(hWnd, IDC_ENABLEMETER,
(g_gpp.user.GlobalFlags & EnableSysTrayBatteryMeter) ?
BST_CHECKED : BST_UNCHECKED);
Power_UpdateStatus(hWnd, NIM_MODIFY, FALSE); // show current info
ShowWindow(hWnd, SW_SHOW);
SetForegroundWindow(hWnd);
}
else {
SysTray_RunProperties(IDS_RUNPOWERPROPERTIES);
}
}
/*----------------------------------------------------------------------------
* DoPowerMenu
*
* Create and process a right or left button menu.
*
*----------------------------------------------------------------------------*/
void
DoPowerMenu(HWND hwnd, UINT uMenuNum, UINT uButton)
{
POINT pt;
UINT iCmd;
MENUITEMINFO mii;
SetForegroundWindow(hwnd);
GetCursorPos(&pt);
iCmd = (UINT)TrackPopupMenu(GetPowerMenu(uMenuNum),
uButton | TPM_RETURNCMD | TPM_NONOTIFY,
pt.x, pt.y, 0, hwnd, NULL);
if (iCmd >= POWERMENU_SCHEME) {
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_DATA;
if (GetMenuItemInfo(g_hMenu[uMenuNum], iCmd, FALSE, &mii)) {
SetActivePwrScheme((UINT)mii.dwItemData, NULL, NULL);
PowerCfg_Notify();
}
}
else {
switch (iCmd) {
case POWERMENU_OPEN:
Power_Open(hwnd);
break;
case POWERMENU_PROPERTIES:
SysTray_RunProperties(IDS_RUNPOWERPROPERTIES);
break;
case 0:
// The user cancelled the menu without choosing.
SetIconFocus(hwnd, STWM_NOTIFYPOWER);
break;
}
}
}
/*----------------------------------------------------------------------------
* Power_Notify
*
* Handle a notification from the power tray icon.
*
*----------------------------------------------------------------------------*/
#define PN_TIMER_CLEAR 0
#define PN_TIMER_SET 1
#define PN_DBLCLK 2
UINT g_uiTimerSet = PN_TIMER_CLEAR;
LARGE_INTEGER g_liHoverUpdateTime = {0,0};
void Power_Notify(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
LARGE_INTEGER liPerformanceFrequency;
LARGE_INTEGER liPerformanceCount;
switch (lParam)
{
case WM_RBUTTONUP:
DoPowerMenu(hWnd, 1, TPM_RIGHTBUTTON); // right button menu
break;
case WM_LBUTTONUP:
// start timing for left button menu
if (g_uiTimerSet == PN_TIMER_CLEAR) {
SetTimer(hWnd, POWER_TIMER_ID, GetDoubleClickTime()+100, NULL);
g_uiTimerSet = PN_TIMER_SET;
}
break;
case WM_LBUTTONDBLCLK:
g_uiTimerSet = PN_DBLCLK;
Power_Open(hWnd); // show battery meter dialog
break;
case WM_MOUSEMOVE:
if (QueryPerformanceFrequency (&liPerformanceFrequency)) {
if (QueryPerformanceCounter (&liPerformanceCount)) {
// Update no more than once a second
if ((liPerformanceCount.QuadPart - g_liHoverUpdateTime.QuadPart) >
liPerformanceFrequency.QuadPart) {
g_liHoverUpdateTime = liPerformanceCount;
Power_UpdateStatus(hWnd, NIM_MODIFY, FALSE);
}
}
}
break;
}
}
/*-----------------------------------------------------------------------------
* Power_Timer
*
* Execute the left button menu on WM_LBUTTONDOWN time-out.
*
*----------------------------------------------------------------------------*/
void Power_Timer(HWND hwnd)
{
KillTimer(hwnd, POWER_TIMER_ID);
if (g_uiTimerSet != PN_DBLCLK) {
DoPowerMenu(hwnd, 0, TPM_LEFTBUTTON);
}
g_uiTimerSet = PN_TIMER_CLEAR;
}
/*----------------------------------------------------------------------------
* Update_PowerFlags
*
* Set power flags using powrprof.dll API's.
*
*----------------------------------------------------------------------------*/
void Update_PowerFlags(DWORD dwMask, BOOL bEnable)
{
if (bEnable) {
g_gpp.user.GlobalFlags |= dwMask;
}
else {
g_gpp.user.GlobalFlags &= ~dwMask;
}
WriteGlobalPwrPolicy(&g_gpp);
}
/*----------------------------------------------------------------------------
* Get_PowerFlags
*
* Get power flags using powrprof.dll API's.
*
*----------------------------------------------------------------------------*/
DWORD Get_PowerFlags(void)
{
ReadGlobalPwrPolicy(&g_gpp);
return g_gpp.user.GlobalFlags;
}
/*******************************************************************************
*
* BatteryMeterInit
*
* DESCRIPTION:
* NOTE: Can be called multiple times. Simply re-init.
*
* PARAMETERS:
* (returns), TRUE if the Battery Meter could be enabled
*
*******************************************************************************/
BOOL PASCAL BatteryMeterInit(HWND hWnd)
{
PUINT puiBatCount = NULL;
if (!BatMeterCapabilities(&puiBatCount)) {
return FALSE;
}
if (!g_hwndBatMeter) {
g_hwndBatMeterFrame = GetDlgItem(hWnd, IDC_STATIC_FRAME_BATMETER);
g_bShowMulti = g_gpp.user.GlobalFlags & EnableMultiBatteryDisplay;
g_hwndBatMeter = CreateBatMeter(hWnd,
g_hwndBatMeterFrame,
g_bShowMulti,
NULL);
}
return TRUE;
}
/*******************************************************************************
*
* Power_UpdateStatus
*
* DESCRIPTION:
*
* PARAMETERS:
*
*******************************************************************************/
VOID PASCAL Power_UpdateStatus(
HWND hWnd,
DWORD NotifyIconMessage,
BOOL bForceUpdate
)
{
static TCHAR szTipCache[64];
static HICON hIconCache;
TCHAR szTip[64];
LPTSTR lpsz;
BATTERY_STATE bs;
UINT uiHour, uiMin;
*szTip = 0;
bs.ulSize = sizeof(BATTERY_STATE);
UpdateBatMeter(g_hwndBatMeter,
g_bShowMulti,
bForceUpdate,
&bs);
// Build up a new tool tip.
if (g_hwndBatMeter &&
!(((bs.ulPowerState & BATTERY_POWER_ON_LINE) &&
!(bs.ulPowerState & BATTERY_CHARGING)))) {
if (bs.ulBatLifePercent <= 100) {
if (bs.ulBatLifeTime != (UINT) -1) {
uiHour = bs.ulBatLifeTime / 3600;
uiMin = (bs.ulBatLifeTime % 3600) / 60;
if (uiHour) {
lpsz = LoadDynamicString(IDS_TIMEREMFORMATHOUR,
uiHour, uiMin,
bs.ulBatLifePercent);
}
else {
lpsz = LoadDynamicString(IDS_TIMEREMFORMATMIN, uiMin,
bs.ulBatLifePercent);
}
if (lpsz) {
lstrcpy(szTip, lpsz);
LocalFree(lpsz);
if (bs.ulPowerState & BATTERY_CHARGING) {
if ((lpsz = LoadDynamicString(IDS_CHARGING)) != NULL) {
lstrcat(szTip, lpsz);
LocalFree(lpsz);
}
}
}
}
else {
if ((lpsz = LoadDynamicString(IDS_REMAINING,
bs.ulBatLifePercent)) != NULL) {
lstrcpy(szTip, lpsz);
LocalFree(lpsz);
if (bs.ulPowerState & BATTERY_CHARGING) {
if ((lpsz = LoadDynamicString(IDS_CHARGING)) != NULL) {
lstrcat(szTip, lpsz);
LocalFree(lpsz);
}
}
}
}
}
else {
lpsz = LoadDynamicString(IDS_UNKNOWN);
lstrcpy(szTip, lpsz);
LocalFree(lpsz);
}
}
else {
lpsz = LoadDynamicString(IDS_ACPOWER);
lstrcpy(szTip, lpsz);
LocalFree(lpsz);
}
if ((NotifyIconMessage == NIM_ADD) ||
(hIconCache != bs.hIconCache16) ||
(lstrcmp(szTip, szTipCache))) {
hIconCache = bs.hIconCache16;
lstrcpy(szTipCache, szTip);
SysTray_NotifyIcon(hWnd, STWM_NOTIFYPOWER, NotifyIconMessage,
hIconCache, szTipCache);
}
}
/*******************************************************************************
*
* RegisterForDeviceNotification
*
* DESCRIPTION:
* Do onetime registration for WM_DEVICECHANGED.
*
* PARAMETERS:
*
*******************************************************************************/
BOOL RegisterForDeviceNotification(HWND hWnd)
{
DEV_BROADCAST_DEVICEINTERFACE dbc;
memset(&dbc, 0, sizeof(DEV_BROADCAST_DEVICEINTERFACE));
dbc.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
dbc.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
dbc.dbcc_classguid = GUID_DEVICE_BATTERY;
g_hDevNotify = RegisterDeviceNotification(hWnd,
&dbc,
DEVICE_NOTIFY_WINDOW_HANDLE);
if (!g_hDevNotify) {
return FALSE;
}
return TRUE;
}
/*******************************************************************************
*
* Power_WmDestroy
*
* DESCRIPTION:
*
*
* PARAMETERS:
*
*******************************************************************************/
void Power_WmDestroy(HWND hWnd)
{
if (g_hDevNotify) {
UnregisterDeviceNotification(g_hDevNotify);
g_hDevNotify = NULL;
}
}
/*******************************************************************************
*
* Power_CheckEnable
*
* DESCRIPTION:
* Return TRUE if the power service icon was enabled.
* Can be called multiple times. Simply re-init.
*
* PARAMETERS:
* bSvcEnabled - Request to enable/disable power service on tray.
*
*******************************************************************************/
BOOL Power_CheckEnable(HWND hWnd, BOOL bSvcEnable)
{
static BOOL bRegisteredForDC = FALSE;
// Is there any reason to display the systray power icon?
if (!PowerCapabilities()) {
return FALSE;
}
// Do onetime registration for WM_DEVICECHANGED.
if (!bRegisteredForDC) {
bRegisteredForDC = RegisterForDeviceNotification(hWnd);
}
// Get current battery meter flags from the registry
Get_PowerFlags();
// Are we running on battery power or has the user set
// the systray power icon to always on? If so, force enable.
if ((g_gpp.user.GlobalFlags & EnableSysTrayBatteryMeter) ||
(RunningOffLine())) {
bSvcEnable = TRUE;
}
else {
bSvcEnable = FALSE;
}
// Set the power service state.
if (bSvcEnable) {
if (g_bPowerEnabled) {
Power_UpdateStatus(hWnd, NIM_MODIFY, FALSE);
}
else {
BatteryMeterInit(hWnd);
Power_UpdateStatus(hWnd, NIM_ADD, FALSE);
}
g_bPowerEnabled = TRUE;
}
else {
SysTray_NotifyIcon(hWnd, STWM_NOTIFYPOWER, NIM_DELETE, NULL, NULL);
g_bPowerEnabled = FALSE;
}
return g_bPowerEnabled;
}