//*********************************************************************
//*                  Microsoft Windows                               **
//*            Copyright(c) Microsoft Corp., 1995                    **
//*********************************************************************

//
//      DIALMON.C - Window proc for dial monitor app
//

//      HISTORY:
//      
//      4/18/95         jeremys         Created.
//

#include "private.h"

#include <mluisupp.h>

#define TF_THISMODULE TF_DIALMON

//
// Registry keys we use to get autodial information
//
                                                                     
// Internet connection goes in remote access key
const TCHAR c_szRASKey[]    = TEXT("RemoteAccess");

// Key name
const TCHAR c_szProfile[]   = TEXT("InternetProfile");
const TCHAR c_szEnable[]    = TEXT("EnableUnattended");

// registry keys of interest
const TCHAR c_szRegPathInternetSettings[] =         REGSTR_PATH_INTERNET_SETTINGS;
static const TCHAR szRegValEnableAutoDisconnect[] = REGSTR_VAL_ENABLEAUTODISCONNECT; 
static const TCHAR szRegValDisconnectIdleTime[] =   REGSTR_VAL_DISCONNECTIDLETIME; 
static const TCHAR szRegValExitDisconnect[] =       REGSTR_VAL_ENABLEEXITDISCONNECT;
static const TCHAR szEllipsis[] =                   TEXT("...");
static const CHAR szDashes[] =                      "----";
static const TCHAR szAutodialMonitorClass[] =       REGSTR_VAL_AUTODIAL_MONITORCLASSNAME;
static const TCHAR c_szDialmonClass[] =             TEXT("MS_WebcheckMonitor");

// Dialmon globals
UINT_PTR    g_uDialmonSecTimerID = 0;  

CDialMon *  g_pDialMon = NULL;

// Function prototypes for dialog handling functions
INT_PTR CALLBACK DisconnectPromptDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam,
        LPARAM lParam);
BOOL DisconnectDlgInit(HWND hDlg,DISCONNECTDLGINFO * pDisconnectDlgInfo);
VOID DisconnectDlgCancel(HWND hDlg);
VOID DisconnectDlgTimerProc(HWND hDlg);
VOID DisconnectDlgDisableAutodisconnect(HWND hDlg);
VOID DisconnectDlgShowCountdown(HWND hDlg,DWORD dwSecsRemaining);
VOID EnableDisconnectDlgCtrls(HWND hDlg,BOOL fEnable);
BOOL CenterWindow (HWND hwndChild, HWND hwndParent);

////////////////////////////////////////////////////////////////////////
// RAS delay load helpers
//

typedef DWORD (WINAPI* _RASSETAUTODIALPARAM) (
    DWORD, LPVOID, DWORD
);

typedef DWORD (WINAPI* _RASENUMCONNECTIONSA) (
    LPRASCONNA, LPDWORD, LPDWORD
);

typedef DWORD (WINAPI* _RASENUMCONNECTIONSW) (
    LPRASCONNW, LPDWORD, LPDWORD
);

typedef DWORD (WINAPI* _RASHANGUP) (
    HRASCONN
);

typedef struct _tagAPIMAPENTRY {
    FARPROC* pfn;
    LPSTR pszProc;
} APIMAPENTRY;

static _RASSETAUTODIALPARAM     pfnRasSetAutodialParam = NULL;
static _RASENUMCONNECTIONSA     pfnRasEnumConnectionsA = NULL;
static _RASENUMCONNECTIONSW     pfnRasEnumConnectionsW = NULL;
static _RASHANGUP               pfnRasHangUp = NULL;

static HINSTANCE    g_hRasLib = NULL;
static long         g_lRasRefCnt = 0;

APIMAPENTRY rgRasApiMap[] = {
    { (FARPROC*) &pfnRasSetAutodialParam,       "RasSetAutodialParamA" },
    { (FARPROC*) &pfnRasEnumConnectionsA,       "RasEnumConnectionsA" },
    { (FARPROC*) &pfnRasEnumConnectionsW,       "RasEnumConnectionsW" },
    { (FARPROC*) &pfnRasHangUp,                 "RasHangUpA" },
    { NULL, NULL },
};

/////////////////////////////////////////////////////////////////////////////
//
// RasEnumHelp
//
// Abstract grusome details of getting a correct enumeration of connections 
// from RAS.  Works on all 9x and NT platforms correctly, maintaining unicode
// whenever possible.
//
/////////////////////////////////////////////////////////////////////////////

class RasEnumHelp
{
private:

    //
    // Possible ways we got info from RAS
    //
    typedef enum {
        ENUM_MULTIBYTE,             // Win9x
        ENUM_UNICODE,               // NT
    } ENUM_TYPE;

    //
    // How we got the info
    //
    ENUM_TYPE       _EnumType;     

    //
    // Any error we got during enumeration
    //
    DWORD           _dwLastError;

    //
    // Number of entries we got
    //
    DWORD           _dwEntries;

    //
    // Pointer to info retrieved from RAS
    //
    RASCONNW *      _rcList;

    //
    // Last entry returned as multibyte or unicode when conversion required
    //
    RASCONNW        _rcCurrentEntryW;


public:
    RasEnumHelp();
    ~RasEnumHelp();

    DWORD       GetError();
    DWORD       GetEntryCount();
    LPRASCONNW  GetEntryW(DWORD dwEntry);
};

RasEnumHelp::RasEnumHelp()
{
    DWORD           dwBufSize, dwStructSize;

    // init
    _dwEntries = 0;
    _dwLastError = 0;

    // figure out which kind of enumeration we're doing - start with multibyte
    _EnumType = ENUM_MULTIBYTE;
    dwStructSize = sizeof(RASCONNA);

    if (g_fIsWinNT)
    {
        _EnumType = ENUM_UNICODE;
        dwStructSize = sizeof(RASCONNW);
    }

    // allocate space for 16 entries
    dwBufSize = 16 * dwStructSize;
    _rcList = (LPRASCONNW)LocalAlloc(LMEM_FIXED, dwBufSize);
    if(_rcList)
    {
        do
        {
            // set up list
            _rcList[0].dwSize = dwStructSize;

            // call ras to enumerate
            _dwLastError = ERROR_UNKNOWN;
            if(ENUM_MULTIBYTE == _EnumType)
            {
                if(pfnRasEnumConnectionsA)
                {
                    _dwLastError = pfnRasEnumConnectionsA(
                                    (LPRASCONNA)_rcList,
                                    &dwBufSize,
                                    &_dwEntries
                                    );
                }
            }
            else
            {
                if(pfnRasEnumConnectionsW)
                {
                    _dwLastError = pfnRasEnumConnectionsW(
                                    _rcList,
                                    &dwBufSize,
                                    &_dwEntries
                                    );
                }
            }
       
            // reallocate buffer if necessary
            if(ERROR_BUFFER_TOO_SMALL == _dwLastError)
            {
                LocalFree(_rcList);
                _rcList = (LPRASCONNW)LocalAlloc(LMEM_FIXED, dwBufSize);
                if(NULL == _rcList)
                {
                    _dwLastError = ERROR_NOT_ENOUGH_MEMORY;
                    break;
                }
            }
            else
            {
                break;
            }

        } while(TRUE);
    }
    else
    {
        _dwLastError = ERROR_NOT_ENOUGH_MEMORY;
    }

    if(_rcList && (ERROR_SUCCESS != _dwLastError))
    {
        LocalFree(_rcList);
        _rcList = NULL;
        _dwEntries = 0;
    }

    return;
}

RasEnumHelp::~RasEnumHelp()
{
    if(_rcList)
    {
        LocalFree(_rcList);
    }
}

DWORD
RasEnumHelp::GetError()
{
    return _dwLastError;
}

DWORD
RasEnumHelp::GetEntryCount()
{
    return _dwEntries;
}

LPRASCONNW
RasEnumHelp::GetEntryW(DWORD dwEntryNum)
{
    LPRASCONNW  prc = NULL;

    if(dwEntryNum < _dwEntries)
    {
        _rcCurrentEntryW.hrasconn = _rcList[dwEntryNum].hrasconn;

        switch(_EnumType)
        {
        case ENUM_MULTIBYTE:
            {
                MultiByteToWideChar(CP_ACP, 0,
                                    ((LPRASCONNA)_rcList)[dwEntryNum].szEntryName,
                                    -1, _rcCurrentEntryW.szEntryName,
                                    ARRAYSIZE(_rcCurrentEntryW.szEntryName));
            }
            break;

        case ENUM_UNICODE:
            {
                StrCpyNW(_rcCurrentEntryW.szEntryName,
                         _rcList[dwEntryNum].szEntryName,
                         ARRAYSIZE(_rcCurrentEntryW.szEntryName));
            }   
            break;
        }

        prc = &_rcCurrentEntryW;
    }

    return prc;
}


//
// Functions we can call once ras is loaded
//

DWORD _RasSetAutodialParam(DWORD dwKey, LPVOID lpvValue, DWORD dwcbValue)
{
    if (pfnRasSetAutodialParam == NULL)
        return ERROR_UNKNOWN;

    return (*pfnRasSetAutodialParam)(dwKey, lpvValue, dwcbValue);
}

DWORD _RasEnumConnections(LPRASCONNW lpRasConn, LPDWORD lpdwSize, LPDWORD lpdwConn)
{
    RasEnumHelp     reh;
    DWORD           dwRet = reh.GetError();

    if (ERROR_SUCCESS == dwRet)
    {
        DWORD   cItems = reh.GetEntryCount();
        DWORD   cbNeeded = cItems * sizeof(RASCONNW);

        *lpdwConn = 0;

        if (*lpdwSize >= cbNeeded)
        {

            *lpdwConn = cItems;

            DWORD   dw;

            for (dw = 0; dw < cItems; dw++)
            {
                LPRASCONNW  prc = reh.GetEntryW(dw);

                ASSERT(prc != NULL);

                lpRasConn[dw].hrasconn = prc->hrasconn;
                StrCpyNW(lpRasConn[dw].szEntryName,
                         prc->szEntryName,
                         ARRAYSIZE(lpRasConn[dw].szEntryName));
            }
        }
        else
        {
            dwRet = ERROR_BUFFER_TOO_SMALL;
        }

        *lpdwSize = cbNeeded;
    }

    return dwRet;
}

DWORD _RasHangUp(HRASCONN hRasConn)
{
    if (pfnRasHangUp == NULL)
        return ERROR_UNKNOWN;

    return (*pfnRasHangUp)(hRasConn);
}

BOOL
LoadRasDll(void)
{
    if(NULL == g_hRasLib) {
        g_hRasLib = LoadLibrary(TEXT("RASAPI32.DLL"));

        if(NULL == g_hRasLib)
            return FALSE;

        int nIndex = 0;
        while (rgRasApiMap[nIndex].pszProc != NULL) {
            *rgRasApiMap[nIndex].pfn =
                    GetProcAddress(g_hRasLib, rgRasApiMap[nIndex].pszProc);
            // GetProcAddress will fail on Win95 for a couple of NT only apis.
            // ASSERT(*rgRasApiMap[nIndex].pfn != NULL);

            nIndex++;
        }
    }

    if(g_hRasLib) {
        return TRUE;
    }

    return FALSE;
}

void
UnloadRasDll(void)
{
    if(g_hRasLib) {
        FreeLibrary(g_hRasLib);
        g_hRasLib = NULL;
        int nIndex = 0;
        while (rgRasApiMap[nIndex].pszProc != NULL) {
            *rgRasApiMap[nIndex].pfn = NULL;
            nIndex++;
        }
    }
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
//
// CDialmonClients
// 
//  Class to maintain a list of application Windows using Dialmon.
//  Used for auto-timeout for all these applications.
//  This class supports 
//      adding hooks to incoming applications, 
//      removing hooks for exiting applications,
//      some aggregate operations on all clients.
// 
//  This is a singleton class, and needs to support only serialized access
//  because all access is thru Dialmon which has a serialized message queue.
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////   

class CDialmonClients
{
private:

    HWND pHwndArray[MAX_DIALMON_HANDLES];
    int cCount;
    static CDialmonClients* pSingleton;

public:

    CDialmonClients();
    
    ~CDialmonClients(){}

    static CDialmonClients* getSingleton()
    {
        if( NULL == pSingleton )
            return ( pSingleton = new CDialmonClients() );
        else
            return pSingleton;
    }

    static void Shutdown()
    {
        if( pSingleton )
            delete pSingleton;
    }
    
    void ClearAll(void);
    BOOL AddHook( HWND hWnd );
    BOOL RemoveHook( HWND hWnd );
    BOOL HasEntries() { return (cCount != 0); };

    friend void BroadcastCanHangup( int iTimeoutMins );
    friend void BroadcastHangingUp( void );
    friend void OnConfirmHangup( HWND hWnd );
    friend void OnDenyHangup( HWND hWnd, CDialMon* pDialMon );
    friend BOOL CanHangup( void ); //doesn't have to be a friend - just bunched it together for now.

    void DebugPrint( void );
};

CDialmonClients* CDialmonClients::pSingleton;

CDialmonClients::CDialmonClients():cCount(0)
{
    for( int i=0; i<MAX_DIALMON_HANDLES; i++ )
        pHwndArray[i] = NULL;
}
void CDialmonClients::ClearAll(void)
{
    for( int i=0; i<MAX_DIALMON_HANDLES; i++ )
        pHwndArray[i] = NULL;
}

// client app. passes a handle to its messaging window when it starts up.
BOOL CDialmonClients::AddHook( HWND hWnd )
{  
    DebugMsg( DM_TRACE, TEXT("\tCDIALMONCLIENTS: Add Hook\n") );

    if( cCount >= MAX_DIALMON_HANDLES )
        return false; /* BAD! */

    pHwndArray[cCount++] = hWnd;

#ifdef DEBUG
    DebugPrint();
#endif

    return true;
}

// client app. unhooks the handle from the CDialmonClients.

//IMPL: cCount always points to the next empty entry in the array.
// so when we delete a handle, we move all the entries beyond that
// handle up one place and decrement cCount.
BOOL CDialmonClients::RemoveHook( HWND hWnd )
{
    DebugMsg( DM_TRACE, TEXT("\tCDIALMONCLIENTS: Remove Hook\n") );

    boolean found = false;
    int i;
    for( i=0; i<cCount; i++ )
    {
        if( hWnd == pHwndArray[i] )
        {
            pHwndArray[i] = NULL;
            --cCount;
            found = true;
            break;
        }
    }
    // move everything beyong cCount up by 1
    // so that cCount represents next free index to 
    // insert into.
    if( found )
    {
        for( ; i<cCount; i++ )
            pHwndArray[i] = pHwndArray[i+1];
        pHwndArray[cCount] = NULL;
    }
    
#ifdef DEBUG    
    DebugPrint();
#endif

    return found;
}

void CDialmonClients::DebugPrint(void)
{
    DebugMsg( DM_TRACE, TEXT("\tCDIALMONCLIENTS: ClientList->\n") );

    for( int i=0; i<cCount; i++ )
    {
        if( pHwndArray[i] )
        {
            DebugMsg( DM_TRACE, TEXT("\t\t%d: %x\n"), i, pHwndArray[i] );
        }
    }
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
//
//             Start up and Shutdown CDialmonClients
//
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
CDialmonClients* g_pDialmonClients;

static void DialmonClientsInit(void)
{
    g_pDialmonClients = new CDialmonClients();
}

static void DialmonClientsShutdown(void)
{
    delete g_pDialmonClients;
}

//#define USE_CONFIRM_ARRAY 1

// We don't need to use the pConfirmHangupArray.
// The option is to simply wait for some preset time, and if no registered
// client refuses the hangup option, to go ahead and simply hangup.
// This array lets us hangup a little earlier just in case all clients reply
// immdly.
#ifdef USE_CONFIRM_ARRAY
    static BOOL pConfirmHangupArray[MAX_DIALMON_HANDLES];
#endif
static int cOutstandingReplies = 0;

// Broadcast to all registered client a query of can_hang_up?
void BroadcastCanHangup( int iTimeoutMins )
{
    DebugMsg( DM_TRACE, TEXT("\tCDIALMONCLIENTS: Broadcasting WM_CANHANGUP?") );
    int i;
    
    cOutstandingReplies=0;
    for( i=0; i<g_pDialmonClients->cCount; i++ )
    {
        if(PostMessage( g_pDialmonClients->pHwndArray[i], WM_CANHANGUP, 
                    0, (LPARAM)iTimeoutMins ))
            ++cOutstandingReplies;
    }

    // if we are using a confirm boolean array, then set ALL
    // entries to false.. this is to take care of any clients that come
    // in AFTER we broadcast can hangup and BEFORE all the existing clients
    // have finished confirming.
#ifdef USE_CONFIRM_ARRAY
    for( i=0; i<MAX_DIALMON_HANDLES; i++ )
    {
        pConfirmHangupArray[i] = false;
    }
#endif
}

// This is a broadcast AFTER hangingup to all registered clients.
void BroadcastHangingUp(void)
{

    DebugMsg( DM_TRACE, TEXT("\tCDIALMONCLIENTS: Broadcasting WM_HANGING_UP") );
    for( int i=0; i<g_pDialmonClients->cCount; i++ )
        PostMessage( g_pDialmonClients->pHwndArray[i], WM_HANGING_UP, 0,0 );
}


// Record that this particular client would like to hangup.
void OnConfirmHangup( HWND hWnd )
{
#ifdef USE_CONFIRM_ARRAY
    for( int i=0; i<g_pDialmonClients->cCount; i++ )
        if( hWnd == g_pDialmonClients->pHwndArray[i] )
        {
            pConfirmHangupArray[i] = true;
            break;
        }
#endif
    --cOutstandingReplies;
}

// Checks if all clients have replied positively.
// Returns false if any client has not yet replied.
BOOL CanHangup( void )
{
#ifdef USE_CONFIRM_ARRAY
    for( int i=0; i<g_pDialmonClients->cCount; i++ )
        if( false == pConfirmHangupArray[i] )
            return false;
    return true;
#else
    return (cOutstandingReplies==0);
#endif 
}

// take action to abort hangup, ( set CDialmon::_dwElapsedTicks to 0 ).
// The effect of this would be that after another timeout period, dialmon
// would again query all the clients.
// However, the client does not have to bring up a dialog again if the user
// indicates that he is not interested in the feature.. the client can
// negate the hangup without interrupting the user.
// ( see the auto-disconnect feature in dialmon ).
void OnDenyHangup( HWND hWnd, CDialMon* pDialMon )
{
#ifdef USE_CONFIRM_ARRAY
    for( int i=0; i<g_pDialmonClients->cCount; i++ )
        if( hWnd == g_pDialmonClients->pHwndArray[i] )
        {
            pConfirmHangupArray[i] = false;
            break;
        }
#endif
    pDialMon->ResetElapsedTicks();
    cOutstandingReplies=0;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
//
// Helper to tell dialmon something is going on
//
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
void IndicateDialmonActivity(void)
{
    static HWND hwndDialmon = NULL;
    HWND hwndMonitor;

    // this one is dynamic - have to find window every time
    hwndMonitor = FindWindow(szAutodialMonitorClass, NULL);
    if(hwndMonitor)
        PostMessage(hwndMonitor, WM_WINSOCK_ACTIVITY, 0, 0);

    // dialmon lives forever - find it once and we're set
    if(NULL == hwndDialmon)
        hwndDialmon = FindWindow(c_szDialmonClass, NULL);
    if(hwndDialmon)
        PostMessage(hwndDialmon, WM_WINSOCK_ACTIVITY, 0, 0);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
//
// Dialmon startup and shutdown
//
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
BOOL DialmonInit(void)
{
    g_pDialMon = new CDialMon;

    DialmonClientsInit();
    if(g_pDialMon)
        return TRUE;

    return FALSE;
}


void DialmonShutdown(void)
{
    DialmonClientsShutdown();
    SAFEDELETE(g_pDialMon);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
//
// Dialmon window functions
//
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

// this cwebcheck instance is in iwebck.cpp
extern CWebCheck *g_pwc;

#ifdef DEBUG_KV
    // DEBUG_KV is set to 1 if you need to test hangup logic 
    // without actually having a dailup connection.
    static bool kvhack = true;
#endif

LRESULT CALLBACK Dialmon_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    CDialMon *pDialMon = (CDialMon*) GetWindowLongPtr(hwnd, GWLP_USERDATA);

    switch (uMsg)
    {
        case WM_CREATE:
        {
            // snag our class pointer and save in window data
            CREATESTRUCT *pcs;
            pcs = (CREATESTRUCT *)lParam;
            SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) pcs->lpCreateParams);
            break;
        }

        // DialMon messages (starting at WM_USER+100)
        case WM_SET_CONNECTOID_NAME:
            if(pDialMon)
                pDialMon->OnSetConnectoid(wParam!=0);
            break;
        case WM_WINSOCK_ACTIVITY:
            if(pDialMon)
                pDialMon->OnActivity();
            break;
        case WM_IEXPLORER_EXITING:
            if(pDialMon)
                pDialMon->OnExplorerExit();
            break;
            
        case WM_DIALMON_HOOK:
            DebugMsg( DM_TRACE, TEXT("\tCDIALMONCLIENTS: WM_HOOK recd. from Window 0x%x"), lParam );

            BOOL fRetval;
            fRetval = g_pDialmonClients->AddHook( (HWND)lParam );
            ASSERT( fRetval == TRUE );
            
#ifdef DEBUG_KV
            if( kvhack == true )
            {
                pDialMon->kvStartMonitoring();
                kvhack = false;
            }
#endif
            break;

        case WM_DIALMON_UNHOOK:            
            DebugMsg( DM_TRACE, TEXT("\tCDIALMONCLIENTS: WM_UNHOOK recd. from Window 0x%x"), lParam );

            g_pDialmonClients->RemoveHook( (HWND)lParam );
            break;

        case WM_CONFIRM_HANGUP:           
            DebugMsg( DM_TRACE, TEXT("\tCDIALMONCLIENTS: WM_CONFIRM_HANGUP recd. from Window 0x%x"), lParam );

            OnConfirmHangup( (HWND)lParam );
            break;

        case WM_DENY_HANGUP:          
            DebugMsg( DM_TRACE, TEXT("\tCDIALMONCLIENTS: WM_DENY_HANGUP recd. from Window 0x%x"), lParam );

            OnDenyHangup( (HWND)lParam, pDialMon );
            break;
            
        case WM_TIMER:
            if(pDialMon)
                pDialMon->OnTimer(wParam);
            break;
        case WM_LOAD_SENSLCE:
            DBG("Dialmon_WndProc - got WM_LOAD_SENSLCE");
            if(g_pwc)
            {
                g_pwc->LoadExternals();
            }
            break;
        case WM_IS_SENSLCE_LOADED:
            if(g_pwc)
            {
                return g_pwc->AreExternalsLoaded();
            }
            else
            {
                return FALSE;
            }
            break;
        case WM_WININICHANGE:
            if (lParam && !StrCmpI((LPCTSTR)lParam, TEXT("policy")))
            {
                ProcessInfodeliveryPolicies();
            }
            // FEATURE: This should be done on Policy and another filter, not for
            // all changes.  (The other filter hasn't been defined yet.)

            //  TODO: handle this in the new architecture!
            //SetNotificationMgrRestrictions(NULL);
            break;

    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
//
//                   CDialMon class implementation
//
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

//
// Constructor / Destructor
//
CDialMon::CDialMon()
{
    WNDCLASS wc;

    // register dialmon window class
    memset(&wc, 0, sizeof(wc));
    wc.lpfnWndProc = Dialmon_WndProc;
    wc.hInstance = g_hInst;
    wc.lpszClassName = c_szDialmonClass;
    RegisterClass(&wc);

    // create dialmon window
    _hwndDialmon = CreateWindow(c_szDialmonClass,
                c_szDialmonClass,
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                NULL,
                NULL,
                g_hInst,
                (LPVOID)this);
}

CDialMon::~CDialMon()
{
    if(_hwndDialmon)
        DestroyWindow(_hwndDialmon);

    // unload ras if it's still around
    UnloadRasDll();
}


///////////////////////////////////////////////////////////////////////////
//
// Start/StopMonitoring
//
///////////////////////////////////////////////////////////////////////////

BOOL CDialMon::StartMonitoring(void)
{
    DBG("CDialMon::StartMonitoring");

    // read timeout settings from registry
    RefreshTimeoutSettings();

    // set a one-minute timer
    StopIdleTimer();
    if(!StartIdleTimer())
        return FALSE;

    _dwElapsedTicks = 0;
    
    return TRUE;
}

void CDialMon::StopMonitoring(void)
{
    DBG("CDialMon::StopMonitoring");

    // don't ever hang up now but keep an eye on ras connection
    _dwTimeoutMins = 0;
    _fDisconnectOnExit = FALSE;
}

///////////////////////////////////////////////////////////////////////////
//
// Start/StopIdleTimer, OnTimer
//
///////////////////////////////////////////////////////////////////////////

INT_PTR CDialMon::StartIdleTimer(void)
{
    if(0 == _uIdleTimerID)
        _uIdleTimerID = SetTimer(_hwndDialmon, TIMER_ID_DIALMON_IDLE, 30000, NULL);

    ASSERT(_uIdleTimerID);

    return _uIdleTimerID;
}

void CDialMon::StopIdleTimer(void)
{
    if(_uIdleTimerID) {
        KillTimer(_hwndDialmon, _uIdleTimerID);
        _uIdleTimerID = 0;
    }
}

void CDialMon::OnTimer(UINT_PTR uTimerID)
{
    DBG("CDialMon::OnMonitorTimer");

    // if we're on Millennium, just bail out.  The system handles idle disconnect.
    if(g_fIsMillennium)
    {
        return;
    }

    // if it's not our timer, ignore it
    if(uTimerID != _uIdleTimerID)
        return;

    // prevent re-entrancy of timer proc (we can stay in here indefinitely
    // since we may bring up a dialog box)
    if (_fInDisconnectFunction) {
        // disconnect dialog already launched, ignore timer ticks while
        // it's present
        return;
    }

    _fInDisconnectFunction = TRUE;
    CheckForDisconnect(TRUE);
    _fInDisconnectFunction = FALSE;

#ifdef DEBUG_KV
    /* Don't stop idle timer */
#else
    if(FALSE == _fConnected) {
        StopIdleTimer();
    }
#endif
}

///////////////////////////////////////////////////////////////////////////
//
// OnSetConnectoid/OnActivity/OnExplorerExit
//
///////////////////////////////////////////////////////////////////////////

void CDialMon::OnSetConnectoid(BOOL fNoTimeout)
{
    RASCONN RasCon[MAX_CONNECTION];
    DWORD   dwBytes, dwRes, dwConnections;

    // save no timeout setting
    _fNoTimeout = fNoTimeout;

    // Ask ras which connectoid is connected and watch that one
    LoadRasDll();
    RasCon[0].dwSize = sizeof(RasCon[0]);
    dwBytes = MAX_CONNECTION * sizeof(RasCon[0]);
    dwRes = _RasEnumConnections(RasCon, &dwBytes, &dwConnections);
    
    // No connections? bail.
    if(0 == dwConnections) {
        *_pszConnectoidName = TEXT('\0');
        _fConnected = FALSE;
        return;
    }

    // Monitor first connectoid
    StrCpyN(_pszConnectoidName, RasCon[0].szEntryName, ARRAYSIZE(_pszConnectoidName));

    // send ras connect notification if we weren't previously connected
    if(FALSE == _fConnected) {
        _fConnected = TRUE;
    }

    // start watching it
    StartMonitoring();
}

void CDialMon::OnActivity(void)
{
    DBG("CDialMon::OnActivity");

    // reset idle tick count
    _dwElapsedTicks = 0;

    // if the disconnect dialog is present and winsock activity
    // resumes, then dismiss the dialog
    if(_hDisconnectDlg) {
        SendMessage(_hDisconnectDlg, WM_QUIT_DISCONNECT_DLG, 0, 0);
        _hDisconnectDlg = NULL;
    }
}

void CDialMon::OnExplorerExit()
{
    DBG("CDialMon::OnIExplorerExit");

    if(FALSE == _fDisconnectOnExit && FALSE == _fNoTimeout) {
        // no exit disconnection so bail
        DBG("CDialMon::OnIExplorerExit - exit hangup not enabled");
        return;
    }

    // prevent re-entrancy of this function (we can stay in here indefinitely
    // since we may bring up a dialog box)
    if (_fInDisconnectFunction) {
        // some UI already launched
        return;
    }

    _fInDisconnectFunction = TRUE;
    CheckForDisconnect(FALSE);
    _fInDisconnectFunction = FALSE;

    if(FALSE == _fConnected) {
        StopIdleTimer();
    }
}


///////////////////////////////////////////////////////////////////////////
//
// RefreshTimeoutSettings
//
///////////////////////////////////////////////////////////////////////////

BOOL CDialMon::RefreshTimeoutSettings(void)
{
    HKEY    hKey;
    BOOL    fSuccess = FALSE;
    TCHAR   szKey[MAX_PATH];
    DWORD   dwRes, dwData, dwSize, dwDisp;

    // assume disconnect monitoring is off
    _dwTimeoutMins = 0;
    _fDisconnectOnExit = FALSE;

    // figure out appropriate key
    wnsprintf(szKey, ARRAYSIZE(szKey), TEXT("%s\\Profile\\%s"),
            REGSTR_PATH_REMOTEACCESS, _pszConnectoidName);

    // open a regstry key to the internet settings section
    dwRes = RegCreateKeyEx(HKEY_CURRENT_USER, szKey, 0, TEXT(""), 0,
            KEY_QUERY_VALUE | KEY_SET_VALUE, NULL, &hKey, &dwDisp);
    
    if(ERROR_SUCCESS == dwRes)
    {
        //
        // is autodisconnect enabled?
        //
        dwSize = sizeof(DWORD);
        if (RegQueryValueEx(hKey,szRegValEnableAutoDisconnect,NULL,NULL,
                (LPBYTE) &dwData,&dwSize) == ERROR_SUCCESS)
        {
            if(dwData)
            {
                // what's the timeout?
                dwSize = sizeof(DWORD);
                if (RegQueryValueEx(hKey,szRegValDisconnectIdleTime,NULL,NULL,
                        (LPBYTE) &dwData,&dwSize) == ERROR_SUCCESS && dwData)
                {
                    _dwTimeoutMins = dwData;
                    fSuccess = TRUE;
                }
            }

            // is disconnect on exit enabled?
            dwSize = sizeof(DWORD);
            if (RegQueryValueEx(hKey,szRegValExitDisconnect,NULL,NULL,
                    (LPBYTE) &dwData,&dwSize) == ERROR_SUCCESS && dwData)
            {
                _fDisconnectOnExit = TRUE;
                fSuccess = TRUE;
            }
        }
        else
        {
            //
            // couldn't find enable autodisconnect key.  Set all disconnect
            // settings to their defaults
            //

            // set class members to default values
            _dwTimeoutMins = 20;
            _fDisconnectOnExit = TRUE;
            fSuccess = TRUE;

            // enable idle disconnect and exit disconnect
            dwData = 1;
            RegSetValueEx(hKey, szRegValEnableAutoDisconnect, 0, REG_DWORD,
                    (LPBYTE)&dwData, sizeof(DWORD));
            RegSetValueEx(hKey, szRegValExitDisconnect, 0, REG_DWORD,
                    (LPBYTE)&dwData, sizeof(DWORD));

            // Save idle minutes
            RegSetValueEx(hKey, szRegValDisconnectIdleTime, 0, REG_DWORD,
                    (LPBYTE)&_dwTimeoutMins, sizeof(DWORD));
        }

        RegCloseKey(hKey);
    }

    return fSuccess;
}

///////////////////////////////////////////////////////////////////////////
//
// Disconnection handling
//
///////////////////////////////////////////////////////////////////////////

void CDialMon::CheckForDisconnect(BOOL fTimer)
{
    BOOL    fPromptForDisconnect = TRUE;       // assume we should prompt for disconnect
    BOOL    fDisconnectDisabled = FALSE;
    BOOL    fConnectoidAlive = FALSE;
    RASCONN RasCon[MAX_CONNECTION];
    DWORD   dwBytes, dwRes, dwConnections = 0, i;
    HRASCONN hConnection = NULL;

    // variables for auto-hangup for other client apps. (MARS)
    static int dwElapsedTicksSincePoll = 0;
    static BOOL fPolledForHangup = false; 
    BOOL fClientsOkHangup = false;
    #define MAX_MINS_CLIENT_RESPONSE    1
    
#ifdef DEBUG_KV
    // skip all the connection code..
    goto KVHACK;
#endif    

    // Verify we still have a connection
    RasCon[0].dwSize = sizeof(RasCon[0]);
    dwBytes = MAX_CONNECTION * sizeof(RasCon[0]);
    dwRes = _RasEnumConnections(RasCon, &dwBytes, &dwConnections);
    
    // If ras is connected at all, stay alive to monitor it
    if(0 == dwConnections)
        _fConnected = FALSE;

    // Find connectoid we're supposed to watch
    if(TEXT('\0') == *_pszConnectoidName) {
        DBG_WARN("DisconnectHandler: No designated connection to monitor");
        return;
    }
        
    for(i=0; i<dwConnections; i++) {
        if(!StrCmp(RasCon[i].szEntryName, _pszConnectoidName)) {
            fConnectoidAlive = TRUE;
            hConnection = RasCon[i].hrasconn;
        }
    }

    // if we're not connected to out monitor connectoid, ditch our hangup
    // dialog if we have one and bail out
    if(FALSE == fConnectoidAlive) {
        if(_hDisconnectDlg) {
            SendMessage(_hDisconnectDlg, WM_QUIT_DISCONNECT_DLG, 0, 0);
            _hDisconnectDlg = NULL;
        }

        // Also make sure that if we were waiting for client (MARS) reponses for auto-hangup,
        // we also clean up state information..
        if( fPolledForHangup )
        {
            dwElapsedTicksSincePoll = 0;
            fPolledForHangup = false;
        }
        return;
    }

#ifdef DEBUG_KV
    // label to jump to after skipping connection code..
    // also need to set _dwTimeoutMins since without connection
    KVHACK:_dwTimeoutMins = 2;
#endif

    // Check timeout if we got a timer tick
    if(fTimer) {
        // increment tick count
        _dwElapsedTicks ++;

        // Haven't exceeded idle threshold or not watching for idle
        if (0 == _dwTimeoutMins || _dwElapsedTicks < _dwTimeoutMins * 2)
            fPromptForDisconnect = FALSE;
    }

    // THIS is a good place to message out to other clients (ie Mars ) and
    // see if everybody wants to hang up ( this is the point where if
    // fPromptForDisconnect is true, then the earlier behavior would have
    // prompted for disconnect.

    // If this is a disconnect because of IExplorer exiting, we 
    // probably don't want to hangup if there are other clients using dialmon.
    if( !fTimer && g_pDialmonClients->HasEntries() )
        return;
        
    if( g_pDialmonClients->HasEntries() && fPromptForDisconnect )
    {
        if( fPolledForHangup )
        {  
            // WM_CANHANGUP messages have been sent
            // we can hangup if either all clients have replied yes,
            // or if there are no refusals so far, and time has run out.
            if( CanHangup() ||
                ( dwElapsedTicksSincePoll >= 2*MAX_MINS_CLIENT_RESPONSE ) )
            {
                //can hangup!
                dwElapsedTicksSincePoll = 0;
                fPolledForHangup = false;
                fClientsOkHangup = true;
            }
            else
            {
                dwElapsedTicksSincePoll++;
                //ensure that hangup doesn't occur on THIS particular timer message.
                fPromptForDisconnect = false;
            }        
        }
        else
        {
            // WM_CANHANGUP queries may be sent out now..
            BroadcastCanHangup( _dwTimeoutMins );
            dwElapsedTicksSincePoll = 0;
            fPolledForHangup = true;
            //ensure that hangup doesn't occur now
            fPromptForDisconnect = false;
        }
    }
    else if( fPolledForHangup )
    {
        // activity restarted while waiting for client responses.
        // do clean up of state information.
        dwElapsedTicksSincePoll = 0;
        fPolledForHangup = false;
    }
    
    if(FALSE == fPromptForDisconnect) {
        return;
    }

    // prompt user to see if they want to hang up
    if(fClientsOkHangup || PromptForDisconnect(fTimer, &fDisconnectDisabled)) {
        // hang it up
        ASSERT(hConnection);
        if(hConnection)
            _RasHangUp(hConnection);

         // broadcast WM_HANGING_UP to remaining clients - REQUIRED??
         if( g_pDialmonClients->HasEntries() ) 
            BroadcastHangingUp();
         
        _fConnected = FALSE;
    }

    if (fDisconnectDisabled) {
        StopMonitoring();
    }

    _dwElapsedTicks = 0;
}

BOOL CDialMon::PromptForDisconnect(BOOL fTimer, BOOL *pfDisconnectDisabled)
{
    ASSERT(_pszConnectoidName);
    ASSERT(pfDisconnectDisabled);

    // fill out struct to pass to dialog
    DISCONNECTDLGINFO DisconnectDlgInfo;
    memset(&DisconnectDlgInfo,0,sizeof(DisconnectDlgInfo));
    DisconnectDlgInfo.pszConnectoidName = _pszConnectoidName;
    DisconnectDlgInfo.fTimer = fTimer;
    DisconnectDlgInfo.dwTimeout = _dwTimeoutMins;
    DisconnectDlgInfo.pDialMon = this;

    // choose the appropriate dialog depending on if this a "timeout" dialog
    // or "app exiting" dialog
    UINT uDlgTemplateID = fTimer ? IDD_DISCONNECT_PROMPT:IDD_APP_EXIT_PROMPT;

    // run the dialog
    BOOL fRet = (BOOL)DialogBoxParam(MLGetHinst(),MAKEINTRESOURCE(uDlgTemplateID),
            NULL, DisconnectPromptDlgProc,(LPARAM) &DisconnectDlgInfo);

    // dialog box stores its window handle in our class so we can send
    // messages to it, clear the global handle now that it's dismissed
    _hDisconnectDlg = NULL;

    *pfDisconnectDisabled = FALSE;
    if (!fRet && DisconnectDlgInfo.fDisconnectDisabled) {
        *pfDisconnectDisabled=TRUE;

        // turn off reg keys for this connection
        TCHAR   szKey[128];
        DWORD   dwRes, dwValue = 0;
        HKEY    hKey;

        wnsprintf(szKey, ARRAYSIZE(szKey), TEXT("%s\\Profile\\%s"),
                REGSTR_PATH_REMOTEACCESS, _pszConnectoidName);
        dwRes = RegOpenKey(HKEY_CURRENT_USER, szKey, &hKey);
        if(ERROR_SUCCESS == dwRes) {

            // Turn off idle disconnect
            RegSetValueEx(hKey, szRegValEnableAutoDisconnect, 0, REG_DWORD,
                    (LPBYTE)&dwValue, sizeof(DWORD));

            // Turn off exit disconnect
            RegSetValueEx(hKey, szRegValExitDisconnect, 0, REG_DWORD,
                    (LPBYTE)&dwValue, sizeof(DWORD));

            RegCloseKey(hKey);
        }
    }   

    return fRet;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
//
//                  Disconnect dialog implementation
//
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

INT_PTR CALLBACK DisconnectPromptDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam,
        LPARAM lParam)
{
    switch (uMsg) {
    case WM_INITDIALOG:
        // lParam points to data struct, store a pointer to it in window data
        SetWindowLongPtr(hDlg, DWLP_USER,lParam);
        return DisconnectDlgInit(hDlg,(DISCONNECTDLGINFO *) lParam);
        break;
    case WM_COMMAND:
        switch (wParam) {
        case IDOK:
            EndDialog(hDlg,TRUE);
            break;
        case IDCANCEL:
            DisconnectDlgCancel(hDlg);
            EndDialog(hDlg,FALSE);
            break;
        case IDC_DISABLE_AUTODISCONNECT:
            DisconnectDlgDisableAutodisconnect(hDlg);
            break;
        }
        break;
    case WM_QUIT_DISCONNECT_DLG:
        // parent window wants to terminate us
        EndDialog(hDlg,FALSE);
        break;
    case WM_TIMER:
        DisconnectDlgTimerProc(hDlg);
        break;
    }

    return FALSE;
}

BOOL __cdecl _FormatMessage(LPCWSTR szTemplate, LPWSTR szBuf, UINT cchBuf, ...)
{
    BOOL fRet;
    va_list ArgList;
    va_start(ArgList, cchBuf);

    fRet = FormatMessageWrapW(FORMAT_MESSAGE_FROM_STRING, szTemplate, 0, 0, szBuf, cchBuf, &ArgList);

    va_end(ArgList);
    return fRet;
}

BOOL DisconnectDlgInit(HWND hDlg,DISCONNECTDLGINFO * pDisconnectDlgInfo)
{
    ASSERT(pDisconnectDlgInfo);
    if (!pDisconnectDlgInfo)
        return FALSE;

    // allocate buffers to build text for dialog
    BUFFER BufText(MAX_RES_LEN + MAX_CONNECTOID_DISPLAY_LEN + 1);
    BUFFER BufFmt(MAX_RES_LEN),BufConnectoidName(MAX_CONNECTOID_DISPLAY_LEN+4);
    ASSERT(BufText && BufFmt && BufConnectoidName);
    if (!BufText || !BufFmt || !BufConnectoidName)
        return FALSE;

    UINT uStringID;
    // choose the appropriate text string for dialog
    if (pDisconnectDlgInfo->fTimer) {
        uStringID = IDS_DISCONNECT_DLG_TEXT;
    } else {
        uStringID = IDS_APP_EXIT_TEXT;
    }

    // load the format string from resource
    MLLoadString(uStringID,BufFmt.QueryPtr(),BufFmt.QuerySize());

    // copy the connectoid name into buffer, and truncate it if it's really
    // long
    StrCpyN(BufConnectoidName.QueryPtr(),pDisconnectDlgInfo->pszConnectoidName,
              BufConnectoidName.QuerySize());
    if (lstrlen(pDisconnectDlgInfo->pszConnectoidName) > MAX_CONNECTOID_DISPLAY_LEN) {
        StrCpyN(((TCHAR *) BufConnectoidName.QueryPtr()) + MAX_CONNECTOID_DISPLAY_LEN,
                 szEllipsis, BufConnectoidName.QuerySize());
    }

    if (pDisconnectDlgInfo->fTimer)
    {
        _FormatMessage(BufFmt.QueryPtr(),
                       BufText.QueryPtr(),
                       BufText.QuerySize(),
                       BufConnectoidName.QueryPtr(),
                       pDisconnectDlgInfo->dwTimeout);
    }
    else
    {
        _FormatMessage(BufFmt.QueryPtr(),
                       BufText.QueryPtr(),
                       BufText.QuerySize(),
                       BufConnectoidName.QueryPtr());
    }

    // set text in dialog
    SetDlgItemText(hDlg,IDC_TX1,BufText.QueryPtr());

    // if this timeout dialog (which counts down), initialize countdown timer
    if (pDisconnectDlgInfo->fTimer) {
        pDisconnectDlgInfo->dwCountdownVal = DISCONNECT_DLG_COUNTDOWN;

        DisconnectDlgShowCountdown(hDlg,pDisconnectDlgInfo->dwCountdownVal);

        // set a one-second timer
        g_uDialmonSecTimerID = SetTimer(hDlg,TIMER_ID_DIALMON_SEC,1000,NULL);
        ASSERT(g_uDialmonSecTimerID);
        if (!g_uDialmonSecTimerID) {
            // it's very unlikely that setting the timer will fail... but if it
            // does, then we'll act just like a normal dialog and won't have
            // a countdown.  Hide the countdown-related windows...
            ShowWindow(GetDlgItem(hDlg,IDC_TX2),SW_HIDE);
            ShowWindow(GetDlgItem(hDlg,IDC_GRP),SW_HIDE);
            ShowWindow(GetDlgItem(hDlg,IDC_TIME_REMAINING),SW_HIDE);
            ShowWindow(GetDlgItem(hDlg,IDC_TX3),SW_HIDE);
        }

        // beep to alert user
        MessageBeep(MB_ICONEXCLAMATION);
    }

    // center this dialog on the screen
    CenterWindow(hDlg,GetDesktopWindow());

    // default: assume user does not disable auto disconnect, change
    // this later if they do (this field is output to dialog invoker)
    pDisconnectDlgInfo->fDisconnectDisabled = FALSE;

    // Save dialog handle so we can get quit messages
    pDisconnectDlgInfo->pDialMon->_hDisconnectDlg = hDlg;
 
    return TRUE;
}

VOID DisconnectDlgCancel(HWND hDlg)
{
    // get pointer to data struct out of window data
    DISCONNECTDLGINFO * pDisconnectDlgInfo = (DISCONNECTDLGINFO *)
                                             GetWindowLongPtr(hDlg, DWLP_USER);
    ASSERT(pDisconnectDlgInfo);

    // check to see if user checked 'disable autodisconnect' checkbox
    if(IsDlgButtonChecked(hDlg,IDC_DISABLE_AUTODISCONNECT))
    {
        // set the output field to indicate that user wanted to disable
        // auto disconnect
        pDisconnectDlgInfo->fDisconnectDisabled = TRUE;
    }       
}

VOID DisconnectDlgTimerProc(HWND hDlg)
{
    // ignore timer ticks (e.g. hold countdown) if "disable autodisconnect"
    // checkbox is checked
    if (IsDlgButtonChecked(hDlg,IDC_DISABLE_AUTODISCONNECT))
        return;

    // get pointer to data struct out of window data
    DISCONNECTDLGINFO * pDisconnectDlgInfo =
                (DISCONNECTDLGINFO *) GetWindowLongPtr(hDlg, DWLP_USER);
    ASSERT(pDisconnectDlgInfo);
    if (!pDisconnectDlgInfo)
        return;

    if (pDisconnectDlgInfo->dwCountdownVal) {
        // decrement countdown value
        pDisconnectDlgInfo->dwCountdownVal --;

        // update the dialog with the new value
        if (pDisconnectDlgInfo->dwCountdownVal) {
            DisconnectDlgShowCountdown(hDlg,pDisconnectDlgInfo->dwCountdownVal);
            return;
        }
    }

    // countdown has run out!

    // kill the timer
    KillTimer(hDlg,g_uDialmonSecTimerID);
    g_uDialmonSecTimerID = 0;

    // send a 'OK' message to the dialog to dismiss it
    SendMessage(hDlg,WM_COMMAND,IDOK,0);
}

VOID DisconnectDlgShowCountdown(HWND hDlg,DWORD dwSecsRemaining)
{
    // build a string showing the number of seconds left
    CHAR szSecs[10];
    if (dwSecsRemaining == (DWORD) -1) {
        lstrcpyA(szSecs, szDashes);
    } else {
        wnsprintfA(szSecs, ARRAYSIZE(szSecs), "%lu", dwSecsRemaining);
    }

    // set string in text control
    SetDlgItemTextA(hDlg, IDC_TIME_REMAINING, szSecs);
}

VOID DisconnectDlgDisableAutodisconnect(HWND hDlg)
{
    // get pointer to data struct out of window data
    DISCONNECTDLGINFO * pDisconnectDlgInfo = (DISCONNECTDLGINFO *)
            GetWindowLongPtr(hDlg, DWLP_USER);
    ASSERT(pDisconnectDlgInfo);

    // find out if disable autodisconnect checkbox is checked
    BOOL fDisabled = IsDlgButtonChecked(hDlg,IDC_DISABLE_AUTODISCONNECT);

    // enable or disable controls appropriately
    EnableDisconnectDlgCtrls(hDlg,!fDisabled);

    if (!fDisabled) {
        // reset timer if we're re-enabling autodisconnect
        pDisconnectDlgInfo->dwCountdownVal = DISCONNECT_DLG_COUNTDOWN;
        // show timer value
        DisconnectDlgShowCountdown(hDlg,pDisconnectDlgInfo->dwCountdownVal);
    } else {
        // show "--" in countdown value
        DisconnectDlgShowCountdown(hDlg,(DWORD) -1);
    }
}

VOID EnableDisconnectDlgCtrls(HWND hDlg,BOOL fEnable)
{
    EnableWindow(GetDlgItem(hDlg,IDC_TX1),fEnable);
    EnableWindow(GetDlgItem(hDlg,IDC_TX2),fEnable);
    EnableWindow(GetDlgItem(hDlg,IDC_TX3),fEnable);
    EnableWindow(GetDlgItem(hDlg,IDC_TIME_REMAINING),fEnable);
    EnableWindow(GetDlgItem(hDlg,IDOK),fEnable);
}

BOOL CenterWindow (HWND hwndChild, HWND hwndParent)
{
    RECT    rChild, rParent;
    int     wChild, hChild, wParent, hParent;
    int     wScreen, hScreen, xNew, yNew;
    HDC     hdc;

    // Get the Height and Width of the child window
    GetWindowRect (hwndChild, &rChild);
    wChild = rChild.right - rChild.left;
    hChild = rChild.bottom - rChild.top;

    // Get the Height and Width of the parent window
    GetWindowRect (hwndParent, &rParent);
    wParent = rParent.right - rParent.left;
    hParent = rParent.bottom - rParent.top;

    // Get the display limits
    hdc = GetDC (hwndChild);
    wScreen = GetDeviceCaps (hdc, HORZRES);
    hScreen = GetDeviceCaps (hdc, VERTRES);
    ReleaseDC (hwndChild, hdc);

    // Calculate new X position, then adjust for screen
    xNew = rParent.left + ((wParent - wChild) /2);
    if (xNew < 0) {
        xNew = 0;
    } else if ((xNew+wChild) > wScreen) {
        xNew = wScreen - wChild;
    }

    // Calculate new Y position, then adjust for screen
    yNew = rParent.top  + ((hParent - hChild) /2);
    if (yNew < 0) {
        yNew = 0;
    } else if ((yNew+hChild) > hScreen) {
        yNew = hScreen - hChild;
    }

    // Set it, and return
    return SetWindowPos (hwndChild, NULL, xNew, yNew, 0, 0,
            SWP_NOSIZE | SWP_NOZORDER);
}


///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
//
//                      BUFFER class implementation
//
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

BOOL BUFFER::Alloc( UINT cchBuffer )
{
    _lpBuffer = (LPTSTR)::MemAlloc(LPTR,cchBuffer*sizeof(TCHAR));
    if (_lpBuffer != NULL) {
        _cch = cchBuffer;
        return TRUE;
    }
    return FALSE;
}

BOOL BUFFER::Realloc( UINT cchNew )
{
    LPVOID lpNew = ::MemReAlloc((HLOCAL)_lpBuffer, cchNew*sizeof(TCHAR),
            LMEM_MOVEABLE | LMEM_ZEROINIT);
    if (lpNew == NULL)
        return FALSE;

    _lpBuffer = (LPTSTR)lpNew;
    _cch = cchNew;
    return TRUE;
}

BUFFER::BUFFER( UINT cchInitial /* =0 */ )
  : BUFFER_BASE(),
        _lpBuffer( NULL )
{
    if (cchInitial)
        Alloc( cchInitial );
}

BUFFER::~BUFFER()
{
    if (_lpBuffer != NULL) {
        MemFree((HLOCAL) _lpBuffer);
        _lpBuffer = NULL;
    }
}

BOOL BUFFER::Resize( UINT cchNew )
{
    BOOL fSuccess;

    if (QuerySize() == 0)
        fSuccess = Alloc( cchNew*sizeof(TCHAR) );
    else {
        fSuccess = Realloc( cchNew*sizeof(TCHAR) );
    }
    if (fSuccess)
        _cch = cchNew;
    return fSuccess;
}