//----------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1997
//
//  File:       idle.cpp
//
//  Contents:   user idle detection
//
//  Classes:
//
//  Functions:
//
//  History:    05-14-1997  darrenmi (Darren Mitchell) Created
//
//----------------------------------------------------------------------------

#include "private.h"
#include "throttle.h"

typedef void (WINAPI* _IDLECALLBACK) (DWORD dwState);
typedef DWORD (WINAPI* _BEGINIDLEDETECTION) (_IDLECALLBACK, DWORD, DWORD);
typedef BOOL (WINAPI* _ENDIDLEDETECTION) (DWORD);

#define TF_THISMODULE TF_WEBCHECKCORE

HINSTANCE           g_hinstMSIDLE = NULL;
_BEGINIDLEDETECTION g_pfnBegin = NULL;
_ENDIDLEDETECTION   g_pfnEnd = NULL;

//
// extra stuff so we don't need msidle.dll on win95
//
BOOL    g_fWin95PerfWin = FALSE;            // using msidle.dll or not?
UINT_PTR g_uIdleTimer = 0;                  // timer handle if not
HANDLE  g_hSageVxd = INVALID_HANDLE_VALUE;  // vxd handle if not
DWORD   g_dwIdleMin = 3;                    // inactivity mins before idle
BOOL    g_fIdle = FALSE;                    // are we idle?
DWORD   g_dwIdleBeginTicks = 0;             // when did idle begin?

VOID CALLBACK OnIdleTimer(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);

//
// A little code copied from msidle.dll.  We use this on Win95 with sage.vxd
// so we don't have to load msidle.dll. 
//

//
// SetIdleTimer - decide how often to poll and set the timer appropriately
//
void SetIdleTimer(void)
{
    UINT uInterval = 1000 * 60;

    //
    // If we're idle and looking for busy, check every 2 seconds
    //
    if(g_fIdle) {
        uInterval = 1000 * 4;
    }

    //
    // kill off the old timer
    //
    if(g_uIdleTimer) {
        KillTimer(NULL, g_uIdleTimer);
    }

    //
    // Set the timer
    //
    TraceMsg(TF_THISMODULE,"SetIdleTimer uInterval=%d", uInterval);
    g_uIdleTimer = SetTimer(NULL, 0, uInterval, OnIdleTimer);
}
       
//
// OnIdleTimer - idle timer has gone off
//
VOID CALLBACK OnIdleTimer(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
    DWORD dwDiff, dwLastActivityTicks;

    //
    // get last activity ticks from sage
    //
    DeviceIoControl(g_hSageVxd, 2, &dwLastActivityTicks, sizeof(DWORD),
        NULL, 0, NULL, NULL);

    //
    // check to see if we've changed state
    //
    if(g_fIdle) {
        //
        // currently in idle state
        //
        if(dwLastActivityTicks != g_dwIdleBeginTicks) {
            // activity since we became idle - stop being idle!
            g_fIdle = FALSE;

            // set timer
            SetIdleTimer();

            // call back client
            CThrottler::OnIdleStateChange(STATE_USER_IDLE_END);
        }

    } else {
        //
        // currently not in idle state
        //
        dwDiff = GetTickCount() - dwLastActivityTicks;

        if(dwDiff > 1000 * 60 * g_dwIdleMin) {
            // Nothing's happened for our threshold time.  We're now idle.
            g_fIdle = TRUE;

            // save time we became idle
            g_dwIdleBeginTicks = dwLastActivityTicks;

            // set timer
            SetIdleTimer();

            // call back client
            CThrottler::OnIdleStateChange(STATE_USER_IDLE_BEGIN);
        }
    }
}

BOOL LoadSageVxd(void)
{
    int inpVXD[3];

    if(INVALID_HANDLE_VALUE != g_hSageVxd)
        return TRUE;

    g_hSageVxd = CreateFile(TEXT("\\\\.\\sage.vxd"), 0, 0, NULL, 0,
            FILE_FLAG_DELETE_ON_CLOSE, NULL);

    // can't open it?  can't use it
    if(INVALID_HANDLE_VALUE == g_hSageVxd)
        return FALSE;

    // start it monitoring
    inpVXD[0] = -1;         // no window - will query
    inpVXD[1] = 0;          // unused
    inpVXD[2] = 0;          // post delay - not used without a window

    DeviceIoControl(g_hSageVxd, 1, &inpVXD, sizeof(inpVXD), NULL, 0, NULL, NULL);

    return TRUE;
}

BOOL UnloadSageVxd(void)
{
    if(INVALID_HANDLE_VALUE != g_hSageVxd) {
        CloseHandle(g_hSageVxd);
        g_hSageVxd = INVALID_HANDLE_VALUE;
    }

    return TRUE;
}

void IdleBegin(HWND hwndParent)
{
    DWORD dwValue;

    // Override idle minutes with reg value if present
    if(ReadRegValue(HKEY_CURRENT_USER,
            c_szRegKey,
            TEXT("IdleMinutes"),
            &dwValue,
            sizeof(DWORD)) &&
        dwValue) {

        g_dwIdleMin = dwValue;
    }

    if(FALSE == g_fIsWinNT && LoadSageVxd()) {
        // using optimal win95 configuration
        g_fWin95PerfWin = TRUE;
        SetIdleTimer();
        return;
    }

    // Bail out if the DebuggerFriendly registry value is set on NT4.
    OSVERSIONINFOA vi;
    vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
    GetVersionExA(&vi);

    if(   vi.dwPlatformId == VER_PLATFORM_WIN32_NT
       && vi.dwMajorVersion == 4
       && ReadRegValue(HKEY_CURRENT_USER, c_szRegKey, TEXT("DebuggerFriendly"), &dwValue, sizeof(DWORD))
       && dwValue)
    {
        return;
    }


    // load msidle.dll
    g_hinstMSIDLE = LoadLibrary(TEXT("msidle.dll"));

    // get begin and end functions
    if(g_hinstMSIDLE) {
        g_pfnBegin = (_BEGINIDLEDETECTION)GetProcAddress(g_hinstMSIDLE, (LPSTR)3);
        g_pfnEnd = (_ENDIDLEDETECTION)GetProcAddress(g_hinstMSIDLE, (LPSTR)4);

        // call start monitoring
        if(g_pfnBegin)
            (g_pfnBegin)(CThrottler::OnIdleStateChange, g_dwIdleMin, 0);
    }
}

void IdleEnd(void)
{
    if(g_fWin95PerfWin) {
        // clean up timer
        KillTimer(NULL, g_uIdleTimer);
        UnloadSageVxd();
    } else {
        // clean up msidle.dll
        if(g_pfnEnd) {
            (g_pfnEnd)(0);
            FreeLibrary(g_hinstMSIDLE);
            g_hinstMSIDLE = NULL;
            g_pfnBegin = NULL;
            g_pfnEnd = NULL;
        }
    }
}
