/*******************************************************************************
*
*  (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;
}
