/*******************************************************************************
*
*  (C) COPYRIGHT MICROSOFT CORP., 1997
*
*  TITLE:       BATMETER.C
*
*  VERSION:     2.0
*
*  AUTHOR:      ReedB
*
*  DATE:        17 Oct, 1996
*
*  DESCRIPTION:
*
*   Implements the battery meter of the PowerCfg or SysTray battery
*   meter windows. The battery meter has two display modes, single and
*   multi-battery. In single mode, a representation of the total of all battery
*   capacity in a system is displayed. In multi-battery mode, battery
*   information is displayed for each individual battery as well as the total.
*
*   The battery meter parent window receives notification from USER when
*   any battery status has changed through the WM_POWERBROADCAST,
*   PBT_APMPOWERSTATUSCHANGE message.
*
*   ??? We need to add perfmon support: Create and maintain keys/values
*   under HKEY_PERFORMANCE_DATA.
*
*******************************************************************************/

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>

#include <windows.h>
#include <commctrl.h>

#include <dbt.h>
#include <objbase.h>
#include <initguid.h>
#include <ntpoapi.h>
#include <poclass.h>

#include <setupapi.h>
#include <syssetup.h>
#include <setupbat.h>
#include <ccstock.h>

#include <help.h>

#include "powrprofp.h"
#include "batmeter.h"
#include "bmresid.h"
#include "..\powercfg\PwrMn_cs.h"
#include "shfusion.h"

#define ARRAYSIZE(x)    (sizeof(x)/sizeof(x[0]))

// Simulated battery only for debug build.
#ifndef DEBUG
#undef SIM_BATTERY
#endif


// Define some things for debug.h.  Required when you include ccstock.h in
// one and only one file.
#define SZ_DEBUGINI         "ccshell.ini"
#define SZ_DEBUGSECTION     "BATMETER"
#define SZ_MODULE           "BATMETER"
#define DECLARE_DEBUG

#include <debug.h>



/*******************************************************************************
*
*                     G L O B A L    D A T A
*
*******************************************************************************/

HINSTANCE   g_hInstance;        // Global instance handle of this DLL.
HWND        g_hwndParent;       // Parent of the battery meter.
HWND        g_hwndBatMeter;     // Battery meter.

// The following constant global array is used to walk through the
// control ID's in the battery metter dialog box. It makes getting
// a control ID from a battery number easy.

#define BAT_ICON      0
#define BAT_STATUS    1
#define BAT_REMAINING 2
#define BAT_NUM       3
#define BAT_LAST      BAT_NUM+1

UINT g_iMapBatNumToID [NUM_BAT+1][4]={
    {IDC_POWERSTATUSICON,  IDC_POWERSTATUSBAR, IDC_REMAINING, IDC_BATNUM0},
    {IDC_POWERSTATUSICON1, IDC_STATUS1, IDC_REMAINING1, IDC_BATNUM1},
    {IDC_POWERSTATUSICON2, IDC_STATUS2, IDC_REMAINING2, IDC_BATNUM2},
    {IDC_POWERSTATUSICON3, IDC_STATUS3, IDC_REMAINING3, IDC_BATNUM3},
    {IDC_POWERSTATUSICON4, IDC_STATUS4, IDC_REMAINING4, IDC_BATNUM4},
    {IDC_POWERSTATUSICON5, IDC_STATUS5, IDC_REMAINING5, IDC_BATNUM5},
    {IDC_POWERSTATUSICON6, IDC_STATUS6, IDC_REMAINING6, IDC_BATNUM6},
    {IDC_POWERSTATUSICON7, IDC_STATUS7, IDC_REMAINING7, IDC_BATNUM7},
    {IDC_POWERSTATUSICON8, IDC_STATUS8, IDC_REMAINING8, IDC_BATNUM8}
};

// Global battery state list. This list has the composite system battery state
// as it's always present head. individual battery devices are linked to this
// head. Use WalkBatteryState(ALL, ... to walk the entire list, including the
// head. Use WalkBatteryState(DEVICES, ... to walk just the device list. If a
// battery is in this list, it's displayable. g_uiBatCount is the count of
// battery devices in this list. The composite battery is not counted. The
// g_pbs array provides a handy UI battery number to pbs conversion. The
// following three variables are only changed during DeviceChanged.

BATTERY_STATE   g_bs;
UINT            g_uiBatCount;
PBATTERY_STATE  g_pbs[NUM_BAT+1];
LPTSTR          g_lpszDriverNames[NUM_BAT];
UINT            g_uiDriverCount;
BOOL            g_bShowingMulti;

// The following array provides context sensitive help associations between
// resource control identifiers and help resource string identifiers.

const DWORD g_ContextMenuHelpIDs[] =
{
    IDC_BATMETERGROUPBOX,       IDH_COMM_GROUPBOX,
    IDC_BATMETERGROUPBOX1,      IDH_COMM_GROUPBOX,
    IDC_POWERSTATUSICON,        NO_HELP,
    IDC_POWERSTATUSICON1,       IDH_BATMETER_CHARGING_ICON,
    IDC_POWERSTATUSICON2,       IDH_BATMETER_CHARGING_ICON,
    IDC_POWERSTATUSICON3,       IDH_BATMETER_CHARGING_ICON,
    IDC_POWERSTATUSICON4,       IDH_BATMETER_CHARGING_ICON,
    IDC_POWERSTATUSICON5,       IDH_BATMETER_CHARGING_ICON,
    IDC_POWERSTATUSICON6,       IDH_BATMETER_CHARGING_ICON,
    IDC_POWERSTATUSICON7,       IDH_BATMETER_CHARGING_ICON,
    IDC_POWERSTATUSICON8,       IDH_BATMETER_CHARGING_ICON,
    IDC_BATNUM1,                NO_HELP,
    IDC_BATNUM2,                NO_HELP,
    IDC_BATNUM3,                NO_HELP,
    IDC_BATNUM4,                NO_HELP,
    IDC_BATNUM5,                NO_HELP,
    IDC_BATNUM6,                NO_HELP,
    IDC_BATNUM7,                NO_HELP,
    IDC_BATNUM8,                NO_HELP,
    IDC_STATUS1,                NO_HELP,
    IDC_STATUS2,                NO_HELP,
    IDC_STATUS3,                NO_HELP,
    IDC_STATUS4,                NO_HELP,
    IDC_STATUS5,                NO_HELP,
    IDC_STATUS6,                NO_HELP,
    IDC_STATUS7,                NO_HELP,
    IDC_STATUS8,                NO_HELP,
    IDC_MOREINFO,               NO_HELP,
    IDC_CURRENTPOWERSOURCE,     IDH_BATMETER_CURPOWERSOURCE,
    IDC_BATTERYLEVEL,           IDH_BATMETER_CURPOWERSOURCE,
    IDC_TOTALBATPWRREMAINING,   IDH_BATMETER_TOTALBATPOWER,
    IDC_REMAINING,              IDH_BATMETER_TOTALBATPOWER,
    IDC_POWERSTATUSBAR,         IDH_BATMETER_TOTALBATPOWER,
    IDC_BARPERCENT,             IDH_BATMETER_TOTALBATPOWER,
    IDC_TOTALTIME,              IDH_BATMETER_TOTALTIME,
    IDC_TIMEREMAINING,          IDH_BATMETER_TOTALTIME,
    IDC_BATTERYNAME,            IDH_DETAILED_BATINFO_LABELS,
    IDC_DEVNAME,                IDH_DETAILED_BATINFO_LABELS,
    IDC_UNIQUEID,               IDH_DETAILED_BATINFO_LABELS,
    IDC_BATID,                  IDH_DETAILED_BATINFO_LABELS,
    IDC_MANUFACTURE,            IDH_DETAILED_BATINFO_LABELS,
    IDC_BATMANNAME,             IDH_DETAILED_BATINFO_LABELS,
    IDC_DATEMANUFACTURED,       IDH_DETAILED_BATINFO_LABELS,
    IDC_BATMANDATE,             IDH_DETAILED_BATINFO_LABELS,
    IDC_CHEMISTRY,              IDH_DETAILED_BATINFO_LABELS,
    IDC_CHEM,                   IDH_DETAILED_BATINFO_LABELS,
    IDC_POWERSTATE,             IDH_DETAILED_BATINFO_LABELS,
    IDC_STATE,                  IDH_DETAILED_BATINFO_LABELS,
    IDC_REFRESH,                IDH_DETAILED_BATINFO_REFRESH,
    0, 0
};

/*******************************************************************************
*
*               P U B L I C   E N T R Y   P O I N T S
*
*******************************************************************************/

/*******************************************************************************
*
*  DllInitialize
*
*  DESCRIPTION:
*
*  PARAMETERS:
*
*******************************************************************************/

BOOL DllInitialize(IN PVOID hmod, IN ULONG ulReason, IN PCONTEXT pctx OPTIONAL)
{
    UNREFERENCED_PARAMETER(pctx);

    switch (ulReason) {

        case DLL_PROCESS_ATTACH:
            g_hInstance = hmod;
            DisableThreadLibraryCalls(g_hInstance);
            SHFusionInitializeFromModuleID(hmod, 124);
            break;

        case DLL_PROCESS_DETACH:
            SHFusionUninitialize();
            break;
    }
    return TRUE;
}

/*******************************************************************************
*
*  PowerCapabilities
*
*  DESCRIPTION:
*   This public function is used to determine if the system has any power
*   management capabilities which require UI support. Return TRUE if power
*   management UI should be displayed.
*
*  PARAMETERS:
*
*******************************************************************************/

BOOL PowerCapabilities()
{
    SYSTEM_POWER_CAPABILITIES   spc;
    int   dummy;

#ifndef SIM_BATTERY
    if (GetPwrCapabilities(&spc)) {
        if ((spc.PowerButtonPresent) ||
            (spc.SleepButtonPresent) ||
            (spc.LidPresent) ||
            (spc.SystemS1) ||
            (spc.SystemS2) ||
            (spc.SystemS3) ||
            (spc.SystemS4) ||
            (spc.SystemS5) ||
            (spc.DiskSpinDown) ||
            (spc.SystemBatteriesPresent)) {
            return TRUE;
        }
        else {
            if (SystemParametersInfo(SPI_GETLOWPOWERACTIVE, 0, &dummy, 0)) {
                return TRUE;
            }
        }
    }
    return FALSE;
#else
    return TRUE;
#endif
}

/*******************************************************************************
*
*  BatMeterCapabilities
*
*  DESCRIPTION:
*   This public function is used to determine if the battery meter library
*   can run on the host machine. Return TRUE on success (battery meter can run).
*
*  PARAMETERS:
*   ppuiBatCount - Points to a pointer which will be filled in with a pointer
*                  to the global battery count.
*
*******************************************************************************/

BOOL BatMeterCapabilities(
    PUINT   *ppuiBatCount
)
{
#ifndef SIM_BATTERY
    SYSTEM_POWER_CAPABILITIES   spc;
#endif // SIM_BATTERY

    if (ppuiBatCount) {
        *ppuiBatCount = &g_uiBatCount;
    }
    g_uiBatCount = 0;

#ifndef SIM_BATTERY
    // Make sure we have batteries to query.
    if (GetPwrCapabilities(&spc)) {
        if (spc.SystemBatteriesPresent) {
            g_uiDriverCount = GetBatteryDriverNames(g_lpszDriverNames);
            if (g_uiDriverCount != 0) {
                g_uiBatCount = g_uiDriverCount;

                return TRUE;
            }
            else {
                DebugPrint( "BatMeterCapabilities, no battery drivers found.");
            }
        }
    }
    return FALSE;

#else // SIM_BATTERY
    g_uiBatCount = g_uiDriverCount = GetBatteryDriverNames(g_lpszDriverNames);
    return UpdateDriverList(g_lpszDriverNames, g_uiDriverCount);
#endif // SIM_BATTERY

}


/*******************************************************************************
*
*  CreateBatMeter
*
*  DESCRIPTION:
*   Create, fetch data for and draw the battery meter window. Returns a handle
*   to the newly created battery meter window on success, NULL on failure.
*
*  PARAMETERS:
*   hwndParent      - Parent of the battery meter dialog.
*   wndFrame        - Frame to locate the battery meter dialog.
*   bShowMulti      - Specifies the display mode (TRUE -> multiple battery).
*   pbsComposite    - Optional pointer to composite battery state.
*
*******************************************************************************/

HWND CreateBatMeter(
    HWND            hwndParent,
    HWND            hwndFrame,
    BOOL            bShowMulti,
    PBATTERY_STATE  pbsComposite
)
{
    INT iWidth, iHeight;
    RECT rFrame = {0};

    // Build the battery devices name list if hasn't already been built.
    if (!g_uiBatCount)
    {
        BatMeterCapabilities(NULL);
    }

    // Remember if we are showing details for each battery
    g_bShowingMulti = bShowMulti;

    // Make sure we have at least one battery.
    if (g_uiBatCount)
    {
        // Create the battery meter control.
        g_hwndParent = hwndParent;
        g_hwndBatMeter = CreateDialog(g_hInstance,
                                MAKEINTRESOURCE(IDD_BATMETER),
                                hwndParent,
                                BatMeterDlgProc);

        // Place the battery meter in the passed frame window.
        if ((g_hwndBatMeter) && (hwndFrame))
        {
            // Position the BatMeter dialog in the frame.
            if (!GetWindowRect(hwndFrame, &rFrame))
            {
                DebugPrint( "CreateBatMeter, GetWindowRect failed, hwndFrame: %08X", hwndFrame);
            }

            iWidth  = rFrame.right  - rFrame.left;
            iHeight = rFrame.bottom - rFrame.top;

            if (IsBiDiLocalizedSystemEx(NULL))
            {
                // Whistler #209400: On BIDI systems, ScreenToClient() wants the right
                // coord in the left location because everything is flipped.
                rFrame.left = rFrame.right;
            }

            if (!ScreenToClient(hwndParent, (LPPOINT)&rFrame))
            {
                DebugPrint( "CreateBatMeter, ScreenToClient failed");
            }

            if (!MoveWindow(g_hwndBatMeter,
                         rFrame.left,
                         rFrame.top,
                         iWidth,
                         iHeight,
                         FALSE))
            {
                DebugPrint( "CreateBatMeter, MoveWindow failed, %d, %d", rFrame.left, rFrame.top);
            }

            // Build the battery driver data list.
            if (!UpdateDriverList(g_lpszDriverNames, g_uiDriverCount))
            {
                return DestroyBatMeter(g_hwndBatMeter);
            }

            // Do the first update.
            UpdateBatMeter(g_hwndBatMeter, bShowMulti, TRUE, pbsComposite);
            ShowWindow(g_hwndBatMeter, SW_SHOWNOACTIVATE);
        }
    }

   return g_hwndBatMeter;
}

/*******************************************************************************
*
*  DestroyBatMeter
*
*  DESCRIPTION:
*
*******************************************************************************/

HWND DestroyBatMeter(HWND hWnd)
{
   SendMessage(hWnd, WM_DESTROYBATMETER, 0, 0);
   g_hwndBatMeter = NULL;
   return g_hwndBatMeter;
}

/*******************************************************************************
*
*  UpdateBatMeter
*
*  DESCRIPTION:
*   This function should be called when the battery meter parent window
*   receives a WM_POWERBROADCAST, PBT_APMPOWERSTATUSCHANGE message, it will
*   update the data in the global battery state list. If needed the display
*   will also be updated.
*
*  PARAMETERS:
*   HWND hwndBatMeter,          hWnd of the battery meter dialog
*   BOOL bShowMulti,            Specifies the display mode
*   BOOL bForceUpdate,          Forces a UI update
*   PBATTERY_STATE pbsComposite Optional pointer to composite battery state.
*
*******************************************************************************/

BOOL UpdateBatMeter(
    HWND            hWnd,
    BOOL            bShowMulti,
    BOOL            bForceUpdate,
    PBATTERY_STATE  pbsComposite
)
{
    BOOL bRet = FALSE;
    SYSTEM_POWER_STATUS sps;
    UINT uIconID;

    // Update the composite battery state.
    if (GetSystemPowerStatus(&sps) && hWnd) {
        if (sps.BatteryLifePercent > 100) {
            DebugPrint( "GetSystemPowerStatuse, set BatteryLifePercent: %d", sps.BatteryLifePercent);
        }

        // Fill in the composite battery state.
        SystemPowerStatusToBatteryState(&sps, &g_bs);

        // Update the information in the battery state list if we have a battery.
        if (g_hwndBatMeter) {

#ifndef SIM_BATTERY
           WalkBatteryState(DEVICES,
                            (WALKENUMPROC)UpdateBatInfoProc,
                            NULL,
                            (LPARAM)NULL,
                            (LPARAM)NULL);
#else
           WalkBatteryState(DEVICES,
                            (WALKENUMPROC)SimUpdateBatInfoProc,
                            NULL,
                            (LPARAM)NULL,
                            (LPARAM)NULL);
#endif

           // See if the current display mode matches the requested mode.
           if ((g_bShowingMulti != bShowMulti) || (bForceUpdate)) {
               g_bShowingMulti = SwitchDisplayMode(hWnd, bShowMulti);
               bForceUpdate  = TRUE;
           }

           if (g_bShowingMulti) {
               // Walk the bs list, and update all battery displays.
               WalkBatteryState(ALL,
                                (WALKENUMPROC)UpdateBatMeterProc,
                                hWnd,
                                (LPARAM)g_bShowingMulti,
                                (LPARAM)bForceUpdate);
           }
           else {
               // Display only the comosite battery information.
               UpdateBatMeterProc(&g_bs,
                                  hWnd,
                                  (LPARAM)g_bShowingMulti,
                                  (LPARAM)bForceUpdate);
           }
           bRet = TRUE;
        }
    }
    else {
        // Fill in default composite info.
        g_bs.ulPowerState     = BATTERY_POWER_ON_LINE;
        g_bs.ulBatLifePercent = (UINT) -1;
        g_bs.ulBatLifeTime    = (UINT) -1;

        uIconID = MapBatInfoToIconID(&g_bs);
        g_bs.hIconCache = GetBattIcon(hWnd, uIconID, g_bs.hIconCache, FALSE, 32);
        g_bs.hIconCache16 = GetBattIcon(hWnd, uIconID, g_bs.hIconCache16, FALSE, 16);
    }

    // If a pointer is provided, copy the composite battery state data.
    if (pbsComposite) {
        if (pbsComposite->ulSize == sizeof(BATTERY_STATE)) {
            memcpy(pbsComposite, &g_bs, sizeof(BATTERY_STATE));
        }
        else {
            DebugPrint( "UpdateBatMeter, passed BATTERY_STATE size is invalid");
        }
    }
    return bRet;
}

/*******************************************************************************
*
*                 P R I V A T E   F U N C T I O N S
*
*******************************************************************************/

/*******************************************************************************
*
*  LoadDynamicString
*
*  DESCRIPTION:
*     Wrapper for the FormatMessage function that loads a string from our
*     resource table into a dynamically allocated buffer, optionally filling
*     it with the variable arguments passed.
*
*  PARAMETERS:
*     uiStringID    - resource identifier of the string to use.
*     ...           - Optional parameters to use to format the string message.
*
*******************************************************************************/

LPTSTR CDECL LoadDynamicString(UINT uiStringID, ... )
{
    va_list Marker;
    TCHAR szBuf[256];
    LPTSTR lpsz;
    int   iLen;

    // va_start is a macro...it breaks when you use it as an assign...on ALPHA.
    va_start(Marker, uiStringID);

    iLen = LoadString(g_hInstance, uiStringID, szBuf, ARRAYSIZE(szBuf));

    if (iLen == 0) {
        DebugPrint( "LoadDynamicString: LoadString on: 0x%X failed", uiStringID);
        return NULL;
    }

    FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER,
                  (LPVOID) szBuf, 0, 0, (LPTSTR)&lpsz, 0, &Marker);

    return lpsz;
}

/*******************************************************************************
*
*  DisplayFreeStr
*
*  DESCRIPTION:
*
*  PARAMETERS:
*
*******************************************************************************/

LPTSTR DisplayFreeStr(HWND hWnd, UINT uID, LPTSTR  lpsz, BOOL bFree)
{
    if (lpsz) {
        SetDlgItemText(hWnd, uID, lpsz);
        ShowWindow(GetDlgItem(hWnd, uID), SW_SHOWNOACTIVATE);
        if (bFree) {
            LocalFree(lpsz);
            return NULL;
        }
    }
    else {
        ShowWindow(GetDlgItem(hWnd, uID), SW_HIDE);
    }
    return lpsz;
}

/*******************************************************************************
*
*  ShowHideItem
*  ShowItem
*  HideItem
*
*  DESCRIPTION:
*     Handy helpers to show or hide dialog items in the battery meter dialog.
*
*  PARAMETERS:
*     hWnd - Battery meter dialog handle.
*     uID  - Control ID of control to be shown or hidden.
*
*******************************************************************************/

BOOL ShowHideItem(HWND hWnd, UINT uID, BOOL bShow)
{
    ShowWindow(GetDlgItem(hWnd, uID), (bShow)  ? SW_SHOWNOACTIVATE : SW_HIDE);
    return bShow;
}

void ShowItem(HWND hWnd, UINT uID)
{
    ShowWindow(GetDlgItem(hWnd, uID), SW_SHOWNOACTIVATE);
}

void HideItem(HWND hWnd, UINT uID)
{
    ShowWindow(GetDlgItem(hWnd, uID), SW_HIDE);
}

/*******************************************************************************
*
*  SwitchDisplayMode
*
*  DESCRIPTION:
*   Return TRUE if display is switched to multi battery mode.
*
*  PARAMETERS:
*
*******************************************************************************/

BOOL SwitchDisplayMode(HWND hWnd, BOOL bShowMulti)
{
    ULONG i, j;

    // Override request if multi-battery display is not possible.
    if ((bShowMulti) && (!g_uiBatCount)) {
        bShowMulti = FALSE;
    }

    if (!g_uiBatCount) {

        //
        // Hide all info if no batteries are installed
        //
        HideItem(hWnd, IDC_POWERSTATUSBAR);
        HideItem(hWnd, IDC_BARPERCENT);
        HideItem(hWnd, IDC_MOREINFO);

    } else if (bShowMulti) {
        HideItem(hWnd, IDC_POWERSTATUSBAR);
        HideItem(hWnd, IDC_BARPERCENT);
        ShowItem(hWnd, IDC_MOREINFO);

        for (i = 1; i <= g_uiBatCount; i++) {
            for (j = 0; j < BAT_LAST; j++) {
                ShowItem(hWnd, g_iMapBatNumToID[i][0]);
            }
        }
    }
    else {
        for (i = 1; i <= g_uiBatCount; i++) {
            for (j = 0; j < BAT_LAST; j++) {
                HideItem(hWnd, g_iMapBatNumToID[i][j]);
            }
        }

        ShowItem(hWnd, IDC_POWERSTATUSBAR);
        ShowItem(hWnd, IDC_BARPERCENT);
        HideItem(hWnd, IDC_MOREINFO);
    }
    return bShowMulti;
}

/*******************************************************************************
*
*  CleanupBatteryData
*
*  DESCRIPTION:
*
*  PARAMETERS:
*
*******************************************************************************/

void CleanupBatteryData(void)
{
   g_hwndBatMeter = NULL;

   // Mark all batteries as missing.
   memset(&g_pbs, 0, sizeof(g_pbs));

   // Walk the bs list, remove all devices and cleanup.
   WalkBatteryState(DEVICES,
                    (WALKENUMPROC)RemoveMissingProc,
                    NULL,
                    (LPARAM)NULL,
                    (LPARAM)REMOVE_ALL);

   // Free any old driver names.
   FreeBatteryDriverNames(g_lpszDriverNames);
   g_uiBatCount = 0;
}

/*******************************************************************************
*
*  BatMeterDlgProc
*
*  DESCRIPTION:
*   DialogProc for the Battery Meter control. Provide support for more battery
*   info.
*
*  PARAMETERS:
*
*******************************************************************************/

LRESULT CALLBACK BatMeterDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
#ifdef WINNT
    UINT i, j;
    PBATTERY_STATE pbsTemp;
#endif // WINNT

    UINT uiBatNum;

   switch (uMsg) {
      case WM_COMMAND:
         if ((HIWORD(wParam) == STN_CLICKED) ||
             (HIWORD(wParam) == BN_CLICKED)) {
            switch (LOWORD(wParam)) {
               case IDC_POWERSTATUSICON1:
               case IDC_POWERSTATUSICON2:
               case IDC_POWERSTATUSICON3:
               case IDC_POWERSTATUSICON4:
               case IDC_POWERSTATUSICON5:
               case IDC_POWERSTATUSICON6:
               case IDC_POWERSTATUSICON7:
               case IDC_POWERSTATUSICON8:
                  uiBatNum = LOWORD(wParam) - IDC_POWERSTATUSICON1 + 1;
                  // Allow battery details only for present batteries.
                  if ((g_pbs[uiBatNum]) &&
                      (g_pbs[uiBatNum]->ulTag != BATTERY_TAG_INVALID)) {
                     DialogBoxParam(g_hInstance,
                                    MAKEINTRESOURCE(IDD_BATDETAIL),
                                    hWnd,
                                    BatDetailDlgProc,
                                    (LPARAM)g_pbs[uiBatNum]);
                  }
                  break;
            }
         }
         break;

      case WM_DESTROYBATMETER:
         CleanupBatteryData();
         EndDialog(hWnd, wParam);
         break;

      case WM_DESTROY:
         CleanupBatteryData();
         break;

      case WM_DEVICECHANGE:
#ifdef WINNT
         if ((wParam == DBT_DEVICEQUERYREMOVE) || (wParam == DBT_DEVICEREMOVECOMPLETE)) {
            if ( ((PDEV_BROADCAST_HANDLE)lParam)->dbch_devicetype == DBT_DEVTYP_HANDLE) {

               //
               // Find Device that got removed
               //
               pbsTemp = DEVICES;
               while (pbsTemp) {
                  if (pbsTemp->hDevNotify == ((PDEV_BROADCAST_HANDLE)lParam)->dbch_hdevnotify) {
                     break;
                  }
                  pbsTemp = pbsTemp->bsNext;
               }
               if (!pbsTemp) {
                  break;
               }

               //
               // Close the handle to this device and release cached data.
               //
               RemoveBatteryStateDevice (pbsTemp);
               g_uiDriverCount--;
               g_uiBatCount = g_uiDriverCount;

               // Clear and rebuild g_pbs, the handy batttery number to pbs array.
               memset(&g_pbs, 0, sizeof(g_pbs));
               pbsTemp = &g_bs;
               for (i = 0; i <= g_uiBatCount; i++) {
                  if (pbsTemp) {
                     g_pbs[i] = pbsTemp;
                     pbsTemp->ulBatNum = i;
                     pbsTemp = pbsTemp->bsNext;
                  }
               }

               // Refresh display
               for (i = 1; i <= NUM_BAT; i++) {
                  for (j = 0; j < BAT_LAST; j++) {
                     HideItem(g_hwndBatMeter, g_iMapBatNumToID[i][j]);
                  }
               }

               g_bShowingMulti = SwitchDisplayMode (g_hwndBatMeter, g_bShowingMulti);
               if (g_bShowingMulti) {
                  // Walk the bs list, and update all battery displays.
                  WalkBatteryState(DEVICES,
                                   (WALKENUMPROC)UpdateBatMeterProc,
                                   g_hwndBatMeter,
                                   (LPARAM)g_bShowingMulti,
                                   (LPARAM)TRUE);
               }
            }
         }
#else
         if (wParam == DBT_DEVICEQUERYREMOVE) {
            if (g_hwndBatMeter) {
               // Close all of the batteries.
               CleanupBatteryData();
            }
         }
#endif
         return TRUE;

      case WM_HELP:             // F1
         WinHelp(((LPHELPINFO)lParam)->hItemHandle, PWRMANHLP, HELP_WM_HELP, (ULONG_PTR)(LPTSTR)g_ContextMenuHelpIDs);
         return TRUE;

      case WM_CONTEXTMENU:      // right mouse click
         WinHelp((HWND)wParam, PWRMANHLP, HELP_CONTEXTMENU, (ULONG_PTR)(LPTSTR)g_ContextMenuHelpIDs);
         return TRUE;
   }
   return FALSE;
}

/*******************************************************************************
*
*  GetBattIcon
*
*  DESCRIPTION:
*
*  PARAMETERS:
*
*******************************************************************************/

HICON PASCAL GetBattIcon(
    HWND    hWnd,
    UINT    uIconID,
    HICON   hIconCache,
    BOOL    bWantBolt,
    UINT    uiRes)
{
    static HIMAGELIST hImgLst32, hImgLst16;
    HIMAGELIST hImgLst;
    int ImageIndex;

    // Destroy the old cached icon.
    if (hIconCache) {
        DestroyIcon(hIconCache);
    }

    // Don't put the charging bolt over the top of IDI_BATGONE.
    if (uIconID == IDI_BATGONE) {
        bWantBolt = FALSE;
    }

    // Use the transparency color must match that in the bit maps.
    if (!hImgLst32 || !hImgLst16) {
        hImgLst32 = ImageList_LoadImage(g_hInstance,
                                        MAKEINTRESOURCE(IDB_BATTS),
                                        32, 0, RGB(255, 0, 255), IMAGE_BITMAP, 0);
        hImgLst16 = ImageList_LoadImage(g_hInstance,
                                        MAKEINTRESOURCE(IDB_BATTS16),
                                        16, 0, RGB(255, 0, 255), IMAGE_BITMAP, 0);
        ImageList_SetOverlayImage(hImgLst32, IDI_CHARGE-FIRST_ICON_IMAGE, 1);
        ImageList_SetOverlayImage(hImgLst16, IDI_CHARGE-FIRST_ICON_IMAGE, 1);
    }

    if (uiRes == 32) {
        hImgLst = hImgLst32;
    }
    else {
        hImgLst = hImgLst16;
    }

    ImageIndex = uIconID - FIRST_ICON_IMAGE;

    if (bWantBolt) {
        return ImageList_GetIcon(hImgLst, ImageIndex, INDEXTOOVERLAYMASK(1));
    }
    else {
        return ImageList_GetIcon(hImgLst, ImageIndex, ILD_NORMAL);
    }
}

/*******************************************************************************
*
*  CheckUpdateBatteryState
*
*  DESCRIPTION:
*
*  PARAMETERS:
*
*******************************************************************************/

#define UPDATESTATUS_NOUPDATE        0
#define UPDATESTATUS_UPDATE          1
#define UPDATESTATUS_UPDATE_CHARGE   2

UINT CheckUpdateBatteryState(
    PBATTERY_STATE   pbs,
    BOOL             bForceUpdate
)
{
    UINT uiRetVal = UPDATESTATUS_NOUPDATE;

    // Check to see if anything in the battery status has changed
    // since last time.  If not then we have no work to do!

    if ((bForceUpdate) ||
        !((pbs->ulTag            == pbs->ulLastTag) &&
          (pbs->ulBatLifePercent == pbs->ulLastBatLifePercent) &&
          (pbs->ulBatLifeTime    == pbs->ulLastBatLifeTime) &&
          (pbs->ulPowerState     == pbs->ulLastPowerState))) {

        uiRetVal = UPDATESTATUS_UPDATE;

        //  Check for the special case where the charging state has changed.
        if ((pbs->ulPowerState     & BATTERY_CHARGING) !=
            (pbs->ulLastPowerState & BATTERY_CHARGING)) {
                uiRetVal |= UPDATESTATUS_UPDATE_CHARGE;
        }

        // Copy current battery state to last.
        pbs->ulLastTag            = pbs->ulTag;
        pbs->ulLastBatLifePercent = pbs->ulBatLifePercent;
        pbs->ulLastBatLifeTime    = pbs->ulBatLifeTime;
        pbs->ulLastPowerState     = pbs->ulPowerState;
    }
    return uiRetVal;
}

/*******************************************************************************
*
*  MapBatInfoToIconID
*
*  DESCRIPTION:
*    Map battery info to an Icon ID.
*
*  PARAMETERS:
*    ulBatNum - Zero implies composite system state
*
*******************************************************************************/

UINT MapBatInfoToIconID(PBATTERY_STATE pbs)
{
    UINT uIconID = IDI_BATDEAD;

    if (!pbs->ulBatNum) {
        if (pbs->ulPowerState & BATTERY_POWER_ON_LINE) {
            return IDI_PLUG;
        }
    }
    else {
        if (pbs->ulTag == BATTERY_TAG_INVALID) {
            return IDI_BATGONE;
        }
    }

    if  (pbs->ulPowerState & BATTERY_CRITICAL) {
        return IDI_BATDEAD;
    }

    if (pbs->ulBatLifePercent > 66) {
        uIconID = IDI_BATFULL;
    }
    else {
        if (pbs->ulBatLifePercent > 33) {
            uIconID = IDI_BATHALF;
        }
        else {
            if (pbs->ulBatLifePercent > 9) {
                uIconID = IDI_BATLOW;
            }
        }
    }

    return uIconID;
}

/*******************************************************************************
*
*  DisplayIcon
*
*  DESCRIPTION:
*
*  PARAMETERS:
*
*******************************************************************************/

void DisplayIcon(
    HWND            hWnd,
    UINT            uIconID,
    PBATTERY_STATE  pbs,
    ULONG           ulUpdateStatus
)
{
    BOOL    bBolt;
    UINT    uiMsg;

    // Only redraw the icon if it has changed OR
    // if it has gone from charging to not charging.
    if ((uIconID != pbs->uiIconIDcache) ||
        (ulUpdateStatus != UPDATESTATUS_NOUPDATE)) {

        pbs->uiIconIDcache = uIconID;
        bBolt = (pbs->ulPowerState & BATTERY_CHARGING);

        pbs->hIconCache   = GetBattIcon(hWnd, uIconID, pbs->hIconCache, bBolt, 32);
        pbs->hIconCache16 = GetBattIcon(hWnd, uIconID, pbs->hIconCache16, bBolt, 16);

        if (pbs->ulBatNum) {
            uiMsg = BM_SETIMAGE;
        }
        else {
            uiMsg = STM_SETIMAGE;
        }
        SendDlgItemMessage(hWnd, g_iMapBatNumToID[pbs->ulBatNum][BAT_ICON],
                           uiMsg, IMAGE_ICON, (LPARAM) pbs->hIconCache);
        ShowItem(hWnd, g_iMapBatNumToID[pbs->ulBatNum][BAT_ICON]);
    }
}

/*******************************************************************************
*
*  UpdateBatMeterProc
*
*  DESCRIPTION:
*    Updates the System and per battery UI elements if needed.
*
*  PARAMETERS:
*
*******************************************************************************/

BOOL UpdateBatMeterProc(
    PBATTERY_STATE pbs,
    HWND hWnd,
    LPARAM bShowMulti,
    LPARAM bForceUpdate
)
{
    UINT   uIconID, uiHour, uiMin;
    LPTSTR lpsz, lpszRemaining;
    ULONG  ulUpdateStatus;

    ulUpdateStatus = CheckUpdateBatteryState(pbs, (BOOL) bForceUpdate);

    // Make sure there is work to do.
    if (ulUpdateStatus == UPDATESTATUS_NOUPDATE) {
       return TRUE;
    }

    // Determine which icon to display.
    uIconID = MapBatInfoToIconID(pbs);
    DisplayIcon(hWnd, uIconID, pbs, ulUpdateStatus);

    // Are we looking for system power status ?
    if (!pbs->ulBatNum) {

        // Display the Current Power Source text
        lpsz = LoadDynamicString(((pbs->ulPowerState & BATTERY_POWER_ON_LINE) ?
                                   IDS_ACLINEONLINE : IDS_BATTERIES));
        DisplayFreeStr(hWnd, IDC_BATTERYLEVEL, lpsz, FREE_STR);

        if (pbs->ulBatLifePercent <= 100) {
            lpsz = LoadDynamicString(IDS_PERCENTREMAININGFORMAT,
                                        pbs->ulBatLifePercent);
        }
        else {
            lpsz = LoadDynamicString(IDS_UNKNOWN);
        }
        DisplayFreeStr(hWnd, IDC_REMAINING, lpsz, NO_FREE_STR);

        ShowHideItem(hWnd, IDC_CHARGING, pbs->ulPowerState & BATTERY_CHARGING);

        // Show and Update the PowerStatusBar only if in single battery mode and
        // there is al least one battery installed.
        if (!bShowMulti && g_uiBatCount) {
            SendDlgItemMessage(hWnd, IDC_POWERSTATUSBAR, PBM_SETPOS,
                               (WPARAM) pbs->ulBatLifePercent, 0);
            lpsz = DisplayFreeStr(hWnd, IDC_BARPERCENT, lpsz, FREE_STR);
        }

        if (lpsz) {
            LocalFree(lpsz);
        }

        if (pbs->ulBatLifeTime != (UINT) -1) {
            uiHour = pbs->ulBatLifeTime / 3600;
            uiMin  = (pbs->ulBatLifeTime % 3600) / 60;
            if (uiHour) {
                lpsz = LoadDynamicString(IDS_TIMEREMFORMATHOUR, uiHour, uiMin);
            }
            else {
                lpsz = LoadDynamicString(IDS_TIMEREMFORMATMIN, uiMin);
            }
            DisplayFreeStr(hWnd, IDC_TIMEREMAINING, lpsz, FREE_STR);
            ShowHideItem(hWnd, IDC_TOTALTIME, TRUE);
        }
        else {
            ShowHideItem(hWnd, IDC_TOTALTIME, FALSE);
            ShowHideItem(hWnd, IDC_TIMEREMAINING, FALSE);
        }
    }
    else {

        // Here when getting the power status of each individual battery
        // when in multi-battery display mode.
        lpsz = LoadDynamicString(IDS_BATNUM, pbs->ulBatNum);
        DisplayFreeStr(hWnd, g_iMapBatNumToID[pbs->ulBatNum][BAT_NUM],
                       lpsz, FREE_STR);

        if (pbs->ulTag != BATTERY_TAG_INVALID) {
            if (pbs->ulPowerState & BATTERY_CHARGING) {
                lpsz = LoadDynamicString(IDS_BATTCHARGING);
            }
            else {
                lpsz = NULL;
            }
            lpszRemaining  = LoadDynamicString(IDS_PERCENTREMAININGFORMAT,
                                               pbs->ulBatLifePercent);
        }
        else {
            lpsz = LoadDynamicString(IDS_NOT_PRESENT);
            lpszRemaining  = NULL;
        }
        DisplayFreeStr(hWnd, g_iMapBatNumToID[pbs->ulBatNum][BAT_STATUS],
                       lpsz, FREE_STR);

        DisplayFreeStr(hWnd, g_iMapBatNumToID[pbs->ulBatNum][BAT_REMAINING],
                       lpszRemaining, FREE_STR);
    }
    return TRUE;
}

/*******************************************************************************
*
*  FreeBatteryDriverNames
*
*  DESCRIPTION:
*
*  PARAMETERS:
*
*******************************************************************************/

VOID FreeBatteryDriverNames(LPTSTR *lpszDriverNames)
{
    UINT i;

    // Free any old driver names.
    for (i = 0; i < NUM_BAT; i++) {
        if (lpszDriverNames[i]) {
            LocalFree(lpszDriverNames[i]);
            lpszDriverNames[i] = NULL;
        }
    }
}

/*******************************************************************************
*
*  GetBatteryDriverNames
*
*  DESCRIPTION:
*
*  PARAMETERS:
*
*******************************************************************************/

UINT GetBatteryDriverNames(LPTSTR *lpszDriverNames)
{
    UINT                                uiDriverCount, uiIndex;
    DWORD                               dwReqSize;
    HDEVINFO                            hDevInfo;
    SP_INTERFACE_DEVICE_DATA            InterfaceDevData;
    PSP_INTERFACE_DEVICE_DETAIL_DATA    pFuncClassDevData;

    // Free any old driver names.
    FreeBatteryDriverNames(lpszDriverNames);
    uiDriverCount = 0;

#ifndef SIM_BATTERY
    // Use the SETUPAPI.DLL interface to get the
    // possible battery driver names.
    hDevInfo = SetupDiGetClassDevs((LPGUID)&GUID_DEVICE_BATTERY, NULL, NULL,
                                   DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);

    if (hDevInfo != INVALID_HANDLE_VALUE) {
        InterfaceDevData.cbSize = sizeof(SP_DEVINFO_DATA);

        uiIndex = 0;
        while (uiDriverCount < NUM_BAT) {
            if (SetupDiEnumInterfaceDevice(hDevInfo,
                                           0,
                                           (LPGUID)&GUID_DEVICE_BATTERY,
                                           uiIndex,
                                           &InterfaceDevData)) {

                // Get the required size of the function class device data.
                SetupDiGetInterfaceDeviceDetail(hDevInfo,
                                                &InterfaceDevData,
                                                NULL,
                                                0,
                                                &dwReqSize,
                                                NULL);

                pFuncClassDevData = LocalAlloc(0, dwReqSize);
                if (pFuncClassDevData != NULL) {
                    pFuncClassDevData->cbSize =
                        sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);

                    if (SetupDiGetInterfaceDeviceDetail(hDevInfo,
                                                        &InterfaceDevData,
                                                        pFuncClassDevData,
                                                        dwReqSize,
                                                        &dwReqSize,
                                                        NULL)) {

                        dwReqSize = (lstrlen(pFuncClassDevData->DevicePath) + 1) * sizeof(TCHAR);
                        lpszDriverNames[uiDriverCount] = LocalAlloc(0, dwReqSize);

                        if (lpszDriverNames[uiDriverCount]) {
                            lstrcpy(lpszDriverNames[uiDriverCount],
                                    pFuncClassDevData->DevicePath);
                            uiDriverCount++;
                        }
                    }
                    else {
                        DebugPrint("SetupDiGetInterfaceDeviceDetail, failed: %d", GetLastError());
                    }

                    LocalFree(pFuncClassDevData);
                }
            } else {
                if (ERROR_NO_MORE_ITEMS == GetLastError()) {
                    break;
                }
                else {
                    DebugPrint("SetupDiEnumInterfaceDevice, failed: %d", GetLastError());
                }
            }
            uiIndex++;
        }
        SetupDiDestroyDeviceInfoList(hDevInfo);
    }
    else {
        DebugPrint("SetupDiGetClassDevs on GUID_DEVICE_BATTERY, failed: %d", GetLastError());
    }
#else
   // Simulate batteries.
   {
      UINT i;
      static UINT uiState = 1;

      for (i = 0; i <= uiState; i++) {
         lpszDriverNames[i] = LocalAlloc(0, STRSIZE(TEXT("SIMULATED_BATTERY_0")));
         wsprintf(lpszDriverNames[i], TEXT("SIMULATED_BATTERY_%d"), i);
      }
      uiState++;
      uiDriverCount = uiState;
      if (uiState >= NUM_BAT) {
         uiState = 0;
      }
   }
#endif
    return uiDriverCount;
}

/*******************************************************************************
*
*  UpdateDriverList
*
*  DESCRIPTION:
*
*  PARAMETERS:
*
*******************************************************************************/

BOOL UpdateDriverList(
    LPTSTR *lpszDriverNames,
    UINT uiDriverCount
)
{
    UINT            i;
    PBATTERY_STATE  pbs;

    // Walk the bs list, and remove any devices which aren't in pszDeviceNames.
    WalkBatteryState(DEVICES,
                     (WALKENUMPROC)RemoveMissingProc,
                     NULL,
                     (LPARAM)g_lpszDriverNames,
                     (LPARAM)REMOVE_MISSING);

    // Scan the pszDeviceNames list and add any devices which aren't in bs.
    for (i = 0; i < uiDriverCount; i++) {

        if (WalkBatteryState(DEVICES,
                             (WALKENUMPROC)FindNameProc,
                             NULL,
                             (LPARAM)g_lpszDriverNames[i],
                             (LPARAM)NULL)) {

#ifndef SIM_BATTERY
            if (!AddBatteryStateDevice(g_lpszDriverNames[i], i + 1)) {
                // We weren't able get minimal info from driver, dec the
                // battery counts. g_uiBatCount should always be > 0.
                if (--g_uiDriverCount) {;
                    g_uiBatCount--;
                }
            }
#else
            SimAddBatteryStateDevice(g_lpszDriverNames[i], i + 1);
#endif
        }
    }

    // Clear and rebuild g_pbs, the handy batttery number to pbs array.
    memset(&g_pbs, 0, sizeof(g_pbs));
    pbs = &g_bs;
    for (i = 0; i <= g_uiBatCount; i++) {
        if (pbs) {
            g_pbs[i] = pbs;
            pbs = pbs->bsNext;
        }
    }
    return TRUE;
}