Dowload all the source, including the VC 6.0 project files and icons.

AtomicTime.cpp (version 2.2)

// AtomicTime.cpp  
// Version 2.1.0   February 4, 1997
// Version 2.2.0   January 1, 2002
// Freeware, 1997  Tom Wuttke       http://schmail.com/atomictime
//
// If you think this program is cool, or used it to learn winsock
// programming, let me know.  
//
// Compiled with VC++5.0 and 6.0.  Other compilers might 
// have problems with the LONGLONG (__int64) data type.  
//
// Files:
//  AtomicTime.cpp - source file
//  AtomicTime.rc - resource file
//  resource.h - resource include file
//  AtomTim1.ico - icon1
//  AtomTim2.ico - icon2
//  AtomTim3.ico - icon3
//  AtomicTime.dsp - VC++6.0 project 
//

// This part taken from http://www.nopcode.com/AggressiveOptimize.shtml
// Adding this stuff makes the release exe file smaller
#ifndef _DEBUG
// /Og (global optimizations), /Os (favor small code), /Oy (no frame pointers)
#pragma optimize("gsy",on)
#pragma comment(linker,"/RELEASE")
//#pragma comment(linker,"/merge:.rdata=.data")  // makes code larger in this case
#pragma comment(linker,"/merge:.text=.data")
#pragma comment(linker,"/merge:.reloc=.data")
#pragma comment(linker,"/ignore:4078")
#if _MSC_VER >= 1000
#pragma comment(linker,"/opt:nowin98")
#endif // _MSC_VER >= 1000

#endif // _DEBUG




#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <commctrl.h>
#include <shellapi.h>
#include <winsock.h>
#include "resource.h"

// Version for the registry upgrade thing
// (More version stuff is in the rc file)
const DWORD cVersion = 0x20003;

#ifdef _DEBUG
    #include <stdarg.h>
    #include <stdio.h>

    #define cWindowName "Atomic Time v2.2 debug"
#else
    #define cWindowName "Atomic Time v2.2"
#endif

#define cUninstallString "SOFTWARE\\Microsoft\\Windows\\Current" \
    "Version\\Uninstall\\AtomicTime"

const cMaxIter = 7; // Number of times to sample the remote server
const cUsedIter = 4; // Number of samples to actually use
const cMaxSockets = 8; // Max simultaneous socket connections


// Each server has its last used status, which is remembered between
// sessions
//
enum 
{
    eStatusNothing,
    eStatusNetError,
    eStatusNotResponding,
    eStatusSlowResponding,
    eStatusRefused,
    eStatusNetDown,
    eStatusNoDNSEntry,
    eStatusDisabled,
    eStatusInterrupted,
    eStatusThrownOut = 100,
    eStatusTest,
    eStatusUsed         // This is the good status
};


// The state of the server's socket connection
enum
{
    eProgressNot,
    eProgressWaiting,
    eProgressLookup,
    eProgressConnecting,
    eProgressReading
};

// Which of the three global messages to display
enum
{
    eMessageIdle,
    eMessageChecking,
    eMessageTesting
};


const int cMaxServers = 64;
typedef char ServerName[128];

// This is the registry entry for each server.  It is saved
// between sessions
struct ServerItem
{
    ServerName sName; // 127 character name of server
    int enabled,      // true if enabled (has icon)
        status;       // see enum above
    DWORD latency;    // last average latency from reading
    LONGLONG correction; // last average correction 
};

// This is the temporary info about a server.  It is NOT saved
// between sessions
struct ServerState
{
    bool dirty; // true if progress has changed since last display
    int progress, // see enum above
        iter; // which connection is in progress (0 <= iter <= cMaxIter)
    DWORD ip, // ip address of this server
        latency[cMaxIter]; // latencies for each connection
    LONGLONG correction[cMaxIter]; // corrections for each connection
    SOCKET s;  // socket handle during asynchronous calls
    HANDLE h;  // Asynchronous handle for DNS calls
    char hostentBuf[MAXGETHOSTSTRUCT]; // buffer for DNS request
    LONGLONG  timeoutTime;  // determines last socket activity, used for timing out
};



HINSTANCE g_hInstance;
HWND g_hDlg = 0,  // handle of main window
    g_hwndList;   // handle of server list view control


// All times are in milliseconds.  
// All dates are in milliseconds since 1900.
LONGLONG g_LocalTimeOffset = 0, // user's local time offset
    g_LastCorrectionTime = 0,  // date of last system time change
    g_LastAttemptTime = 0,  // date of last attempt to corrrect system time
    g_LastCorrectionValue = 0, // value of last system time change
    g_CorrectionInterval,  // how long to wait between auto-corrections
    g_timeoutTime = 0;  // determines last socket activity, used for timing out

char g_mode = 0; // command line argument, 's' or 'u'
int g_nSchedule = 0, // Slider value for scheduled correction, 0-9
    g_nRunningServerCount = 0, // How many servers are reqeusted
    g_nReallyRunningServerCount = 0, // How many socket connections are open
    g_nSortColumn = 1,  // Which column we are sorting the server list by
    g_nSortPolarity = -1;  // Are we sorting up or down?

bool g_bAutoQuit = false,  // quit after ten seconds of idle
    g_bDefaultServers = true, // use a default list of servers
    g_bTesting, // the server connections are testing, not correcting
    g_bFirstServerList = false, // the user is using the original servers
    g_bWinsock = false, // Is winsock loaded?
    g_bCorrectOnLaunch = false, // Should we just correct and not show UI?
    g_bProblem = false; // Was there a connection problem?


ServerItem g_ServerItems[cMaxServers];
ServerState g_ServerStates[cMaxServers];



void ServerStart(int i);
void ServerDNS(HANDLE h, WORD error);
void ServerGotIP(int i);
void ServerConnected(int i, int err);
void ServerRead(int i, int err);
void ServerTimeAdjust(int i);
void ServerStop(int i, int status);
bool DoSystemTimeCorrection();
void Keep4BestServers();
void ChangeMessage(int nMessageIndex);
void SetTrackCaption();
int CALLBACK ServerListCompareFunc(LPARAM lParam1, LPARAM lParam2,
    LPARAM);
void AbortAllServers(int status);
void Cleanup();




void Fatal(char*s)
{
    static bFatal = false;

    if (!bFatal)
    {
        bFatal = true;
        if (g_hDlg)
        {
            Cleanup();
        }
        MessageBox(g_hDlg, s, cWindowName, MB_ICONERROR | MB_SYSTEMMODAL);
        if (g_bWinsock)
        {
            WSACleanup();
        }
        ExitProcess(0);
    }
}

#ifdef _DEBUG
    #define dprintf(a) _dprintf a

    void _dprintf(const char* format, ...)
    {
        va_list args;
        char s[1000];
        DWORD temp;
        HANDLE h = CreateFile("AtomicTimeLog.txt", GENERIC_WRITE, 
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL |
            FILE_FLAG_WRITE_THROUGH, NULL);

        va_start(args, format);
        vsprintf(s, format, args);
        va_end(args);

        if (h != INVALID_HANDLE_VALUE)
        {
            SetFilePointer(h, 0, 0, FILE_END);
            WriteFile(h, s, lstrlen(s), &temp, NULL);
            CloseHandle(h);
        }
    }
#else
    #define dprintf(a) ((void)0)
#endif



// REGISTRY FUNCTIONS
// ---------------------------------------------------------------------------
// Add or remove ourselves to the registry's secret auto-start area
void SetRunOnStart(BOOL state)
{
    HKEY hkey;
    char s[MAX_PATH + 2];
    int len;

    GetModuleFileName(NULL, s, sizeof(s) - 2);
    lstrcat(s, " s");

    len = lstrlen(s) + 1;

    if (!RegOpenKey(HKEY_LOCAL_MACHINE,
        "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",
        &hkey))
    {
        if (state)
        {
            RegSetValueEx(hkey, "AtomicTime", 0, REG_SZ, (PBYTE)s, len);
        }
        else
        {
            RegDeleteValue(hkey, "AtomicTime");
        }
        RegCloseKey(hkey);
    }

    // Add the uninstall reg entry as long as we're here
    if (RegOpenKey(HKEY_LOCAL_MACHINE, cUninstallString, &hkey))
    {
        if (RegCreateKey(HKEY_LOCAL_MACHINE, cUninstallString, &hkey))
        {
            return;
        }
    }
    s[len - 2] = 'u';
    RegSetValueEx(hkey, "DisplayName", 0, REG_SZ, (PBYTE)"AtomicTime", 11);
    RegSetValueEx(hkey, "UninstallString", 0, REG_SZ, (PBYTE)s, len);
    RegCloseKey(hkey);
}

// Add or remove a registry key to our area
void SetReg(LPCTSTR value, void *data = NULL, DWORD size = 0)
{
    HKEY hKey;

    if (RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\AtomicTime", &hKey))
    {
        if (RegCreateKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\AtomicTime", &hKey))
        {
            Fatal("Registry Problem");
            return;
        }
    }
    if (!data || !size)
    {
        DWORD error = RegDeleteValue(hKey, value);

        if (error && error != ERROR_FILE_NOT_FOUND)
        {
            Fatal("Registry Problem");
            return;
        }
    }
    else if (RegSetValueEx(hKey, value, 0, (size == 4) ? REG_DWORD:REG_BINARY, 
        (PBYTE)data, size))
    {
        Fatal("Registry Problem");
        return;
    }
    RegCloseKey(hKey);
}

// Get a registry key from our area
// returns true on success.
bool GetReg(LPCTSTR value = NULL, void* data = NULL)
{
    HKEY hKey;
    bool result = false;
    DWORD size;

    if (!RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\AtomicTime", &hKey))
    {
        if (data && value) 
        {
            if (!RegQueryValueEx(hKey, value, 0, NULL, NULL, &size) &&
                !RegQueryValueEx(hKey, value, 0, NULL, (PBYTE)data, &size))
            {
                result = true;
            }
        }
        else
        {
            result = true;
        }
        RegCloseKey(hKey);
    }
    return result;
}






// TIME FUNCTIONS
// ---------------------------------------------------------------------------

LONGLONG GetMillisecondsSince1900()
{
    FILETIME ft;

    GetSystemTimeAsFileTime(&ft);
    return *((LONGLONG *)&ft) / 10000 - LONGLONG(9435484800000);
}



LONGLONG OffsetMillisecondsSince1900(LONGLONG correction)
{
    FILETIME ft;
    SYSTEMTIME st;
    LONGLONG time;

    SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
    
    time = GetMillisecondsSince1900() + correction;

    *((LONGLONG *)&ft) = 10000 * (time + LONGLONG(9435484800000));

    FileTimeToSystemTime(&ft, &st);
    if (!SetSystemTime(&st))
    {
        time = 0;
    }
    SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);

    if (!time && GetLastError() != ERROR_PRIVILEGE_NOT_HELD)
    {
        Fatal("Cannot set the system time");
    }

    return time;
}

void FormatMillisecondsSince1900(DWORDLONG time, char* s)
{
    FILETIME ft, ftLocal;
    SYSTEMTIME st;
    int hour;

    *((DWORDLONG *)&ft) = 10000 * (time + DWORDLONG(9435484800000));

    FileTimeToLocalFileTime(&ft, &ftLocal);

    FileTimeToSystemTime(&ftLocal, &st);
    hour = st.wHour;
    if (hour > 12)
    {
        hour -= 12;
    }
    else if (!hour)
    {
        hour = 12;
    }
    wsprintf(s, "%u:%02u:%02u%cm on %u/%u/%02u ",
        hour, st.wMinute, st.wSecond, (st.wHour >= 12) ? 'p':'a',
        st.wMonth, st.wDay, st.wYear % 100);
}

// Similar to GetTickCount and timeGetTime, but more accurate and faster
DWORD GetMilliseconds()
{
    static DWORDLONG perfScale = 0;
    DWORDLONG perf;

    if (!perfScale)
    {
        QueryPerformanceFrequency((LARGE_INTEGER*)&perfScale);
        perfScale = (DWORDLONG(1) << 32) * 1000 / perfScale;
    }
    QueryPerformanceCounter((LARGE_INTEGER*)&perf);
    return DWORD((perf * perfScale) >> 32);
}

void MillisecondsToText(LONGLONG ms, char* s)
{
    LONGLONG base, unityBase = 10;
    char s2[2];
    char cUnits;

    if (ms >= 0)
    {
        lstrcpy(s, "+");
    }
    else if (ms < 0)
    {
        ms = -ms;
        lstrcpy(s, "-");
    }

    if (ms < 1000 * 60 * 2)
    {
        cUnits = 's';
        ms /= 10;
        unityBase = 10;
    }
    else if (ms < 1000 * 60 * 60 * 2)
    {
        cUnits = 'm';
        ms /= 60;
        unityBase = 100;
    }
    else if (ms < 1000 * 60 * 60 * 24 * 2)
    {
        cUnits = 'h';
        ms /= 60 * 60;
        unityBase = 100;
    }
    else 
    {
        cUnits = 'd';
        ms /= 60 * 60 * 24;
        unityBase = 100;
    }
    ms += 5;
    ms /= 10;

    if (!ms)
    {
        *s = ' ';
    }
    base = unityBase * 10;
    while (base <= ms) base *= 10;

    s2[1] = 0;
    while (base > 1)
    {
        base /= 10;
        s2[0] = (char)(ms / base);
        ms -= s2[0] * base;
        s2[0] += '0';
        lstrcat(s, s2);
        if (base == unityBase)
        lstrcat(s, ".");
    }
    s2[0] = cUnits;
    lstrcat(s, s2);
}

LONGLONG TextToMilliseconds(char* s)
{
    LONGLONG result = 0;
    int bias = 1000, sign = 1;
    BOOL decFound = FALSE;
    char c;

    while ((c = *s++) != 0)
    {
        if (bias > 1 && c >= '0' && c <= '9')
        {
            result = result * 10 + (c - '0');
            if (decFound)
            {
                bias /= 10;
            }
        }
        else if (c == '.')
        {
            decFound = TRUE;
        }
        else if (c == '-')
        {
            sign = -sign;
        }
        else if (c == 'h' || c == 'H')
        {
            result *= 3600;
        }
        else if (c == 'm' || c == 'M')
        {
            result *= 60;
        }
        else if (c == 'd' || c == 'D')
        {
            result *= 3600 * 24;
        }
    }
    return result * (bias * sign);
}




// SERVER DISPLAY AND SAVE/LOAD FUNCTIONS
// ---------------------------------------------------------------------------

// Adds, removes, or updates a server's entry in the list view
//
// If bJustProgress is true, assume this is just an update of
//   the server's progress, and not a complete change
void DisplayServer(int i, bool bJustProgress = false)
{
    LV_ITEM lvi;
    LV_FINDINFO lfi;
    int iFound;

    lvi.iSubItem = 0; 
    lfi.flags = LVFI_PARAM;

    for (i = 0; i < cMaxServers; i++)
    {
        ServerItem& si = g_ServerItems[i];
        ServerState& ss = g_ServerStates[i];
        char sLatency[100], sCorrection[100];

        lfi.lParam = i;
        ss.dirty = false;

        if (bJustProgress && !si.sName[0])
        {
            continue;
        }

ReFind:
        iFound = ListView_FindItem(g_hwndList, -1, &lfi);

        if (si.sName[0])
        {
            if (!bJustProgress)
            {
                if (iFound < 0)
                {
                    iFound = ListView_GetItemCount(g_hwndList);
                    lvi.lParam = i;
                    lvi.iItem = iFound;
                    lvi.mask = LVIF_PARAM;
                    ListView_InsertItem(g_hwndList, &lvi);
                    goto ReFind;
                }

                lvi.iItem = iFound;
                lvi.mask = LVIF_IMAGE | LVIF_TEXT;
                lvi.pszText = si.sName;
                lvi.iImage = !si.enabled;
                ListView_SetItem(g_hwndList, &lvi);
            }
            sCorrection[0] = 0;

            if (ss.progress)
            {
                switch (ss.progress)
                {
                    case eProgressWaiting:
                        lstrcpy(sLatency, "Waiting...");
                        break;

                    case eProgressLookup:
                        lstrcpy(sLatency, "DNS Lookup...");
                        break;

                    case eProgressConnecting:
                        wsprintf(sLatency, "Connecting%d...", ss.iter + 1);
                        break;

                    case eProgressReading:
                        wsprintf(sLatency, "Reading%d...", ss.iter + 1);
                        break;
                }
            }
            else
            {
                switch (si.status)
                {
                    case eStatusNetError:
                        lstrcpy(sLatency, "Winsock Error");
                        break;

                    case eStatusNotResponding:
                        lstrcpy(sLatency, "No Response");
                        break;

                    case eStatusSlowResponding:
                        lstrcpy(sLatency, "Slow Response");
                        break;

                    case eStatusRefused:
                        lstrcpy(sLatency, "Refused");
                        break;

                    case eStatusNetDown:
                        lstrcpy(sLatency, "Network Error");
                        break;

                    case eStatusNoDNSEntry:
                        lstrcpy(sLatency, "No DNS entry");
                        break;

                    case eStatusDisabled:
                        lstrcpy(sLatency, "Disabled");
                        break;

                    case eStatusInterrupted:
                        lstrcpy(sLatency, "Interrupted");
                        break;

                    case eStatusUsed:
                    case eStatusThrownOut:
                    case eStatusTest:
                        wsprintf(sLatency, "%d", si.latency);
                        MillisecondsToText(si.correction, sCorrection);
                        if (si.status == eStatusThrownOut)
                        {
                            lstrcat(sCorrection, " (unused)");
                        }
                        if (si.status == eStatusTest)
                        {
                            lstrcat(sCorrection, " (test)");
                        }
                        break;

                    default:
                        sLatency[0] = 0;
                }
            }
            ListView_SetItemText(g_hwndList, iFound, 1, sLatency);
            ListView_SetItemText(g_hwndList, iFound, 2, sCorrection);
        }
        else if (iFound >= 0)
        {
            ListView_DeleteItem(g_hwndList, iFound);
        }
    }
}

void DisplayDirtyServers()
{
    int i;
    bool bAnyone = false;

    for (i = 0; i < cMaxServers; i++)
    {
        ServerState& ss = g_ServerStates[i];
        if (ss.dirty)
        {
            bAnyone = true;
            DisplayServer(i, true);
        }
    }
    if (g_nSortColumn && bAnyone)
    {
        ListView_SortItems(g_hwndList, ServerListCompareFunc, 0);
    }
}

void SaveServer(int i)
{
    char s[100];
    wsprintf(s, "Server%03d", i);

    if (g_ServerItems[i].sName[0])
    {
        SetReg(s, &g_ServerItems[i], sizeof(g_ServerItems[i]));
    }
    else
    {
        SetReg(s);
    }
}

void DisplayAndSaveServer(int i)
{
    DisplayServer(i);
    SaveServer(i);
}

int AddServer(const char* s)
{
    int i;

    if (s && *s)
    {
        for (i = 0; i < cMaxServers; i++)
        {
            ServerItem& si = g_ServerItems[i];

            if (!si.sName[0])
            {
                lstrcpyn(si.sName, s, sizeof(si.sName));
                si.status = eStatusNothing;
                si.enabled = TRUE;
                g_ServerStates[i].dirty = true;
                return i;
            }
        }
    }
    return -1;
}

void SetDefaultServers()
{
    int i;

    ZeroMemory(g_ServerItems, sizeof(g_ServerItems));

    AddServer("bitsy.mit.edu");
    AddServer("canon.inria.fr");
    AddServer("chronos.univ-rennes1.fr");
    AddServer("clock.llnl.gov");
    AddServer("lerc-dns.lerc.nasa.gov");
    AddServer("norad.arc.nasa.gov");
    AddServer("ntp1.sony.com");
    AddServer("ntps1-0.cs.tu-berlin.de");
    AddServer("ntps1-1.cs.tu-berlin.de");
    AddServer("ntps1-1.uni-erlangen.de");
    AddServer("otc1.psu.edu");
    AddServer("rackety.udel.edu");
    AddServer("tick.usno.navy.mil");
    AddServer("time-A.timefreq.bldrdoc.gov");
    AddServer("time.nist.gov");
    AddServer("time.service.uit.no");
    AddServer("tock.usno.navy.mil");
    AddServer("wwvb.erg.sri.com");
    AddServer("wwvb.isi.edu");
    AddServer("augean.eleceng.adelaide.edu.au");
    AddServer("biofiz.mf.uni-lj.si");
    AddServer("black-ice.cc.vt.edu");
    AddServer("chime1.surfnet.nl");
    AddServer("clock.psu.edu");
    AddServer("clock1.unc.edu");
    AddServer("clock-1.cs.cmu.edu");
    AddServer("clock-2.cs.cmu.edu");
    AddServer("delphi.cs.ucla.edu");
    AddServer("esavax.esa.lanl.gov");
    AddServer("fartein.ifi.uio.no");
    AddServer("gazette.bcm.tmc.edu");
    AddServer("gilbreth.ecn.purdue.edu");
    AddServer("harbor.ecn.purdue.edu");
    AddServer("jane.jpl.nasa.gov");
    AddServer("kuhub.cc.ukans.edu");
    AddServer("lane.cc.ukans.edu");
    AddServer("louie.udel.edu");
    AddServer("molecule.ecn.purdue.edu");
    AddServer("noc.near.net");
    AddServer("ns.unet.umn.edu");
    AddServer("nss.unet.umn.edu");
    AddServer("ntp.ctr.columbia.edu");
    AddServer("ntp0.pipex.net");
    AddServer("ntp0.strath.ac.uk");
    AddServer("ntp1.pipex.net");
    AddServer("ntp2.pipex.net");
    AddServer("ntp2.sura.net");
    AddServer("ntp-2.mcs.anl.gov");
    AddServer("ntp0.cornell.edu");
    AddServer("rolex.peachnet.edu");
    AddServer("salmon.maths.tcd.ie");
    AddServer("timelord.cs.uregina.ca");
    AddServer("ticktock.wang.com");
    AddServer("timex.cs.columbia.edu");
    AddServer("timex.peachnet.edu");
    AddServer("tmc.edu");
    AddServer("tycho.usno.navy.mil");
    AddServer("wuarchive.wustl.edu");

    g_bFirstServerList = true;

    for (i = 0; i < cMaxServers; i++)
    {
        DisplayAndSaveServer(i);
    }
}

void LoadServerList()
{
    int i;
    char s[100];

    if (g_bDefaultServers)
    {
        SetDefaultServers();
        g_bDefaultServers = false;
    }
    else
    {
        for (i = 0; i < cMaxServers; i++)
        {
            wsprintf(s, "Server%03d", i);

            if (!GetReg(s, &g_ServerItems[i]))
            {
                g_ServerItems[i].sName[0] = 0;
                g_ServerItems[i].status = eStatusNothing;
                g_ServerStates[i].dirty = true;
            }
            DisplayServer(i);
        }
    }
}



// ASYNCHRONOUS SOCKET/SERVER FUNCTIONS
// ---------------------------------------------------------------------------

void StartCorrect()
{
    int i;

    if (g_nRunningServerCount)
    {
        return;
    }
    g_bTesting = false;

    g_LastAttemptTime = GetMillisecondsSince1900();

    for (i = 0; i < cMaxServers; i++)
    {
        ServerItem& si = g_ServerItems[i];
        ServerState& ss = g_ServerStates[i];

        if (si.sName[0])
        {
            if (si.enabled)
            {
                ServerStart(i);
            }
            else
            {
                si.status = eStatusDisabled;
                SaveServer(i);
                ss.dirty = true;
            }
        }
    }
    DisplayDirtyServers();
}

void ServerReallyStart(int i)
{
    ServerItem& si = g_ServerItems[i];
    ServerState& ss = g_ServerStates[i];

    // Wait no more than 20 seconds for user to connect
    ss.timeoutTime = GetMillisecondsSince1900() + 20 * 1000;

    g_nReallyRunningServerCount++;
    ss.progress = eProgressLookup;
    ss.ip = ntohl(inet_addr(si.sName));
    if (ss.ip == INADDR_NONE)
    {
        ss.dirty = true;
        ss.h = WSAAsyncGetHostByName(g_hDlg, WM_USER + 1, 
            si.sName, ss.hostentBuf, sizeof(ss.hostentBuf));
        if (!ss.h)
        {
            ServerStop(i, eStatusNetError);
        }
    }
    else
    {
        ServerGotIP(i);
    }
}

void DealWithWaitingServers()
{
    int i;
    LONGLONG now = GetMillisecondsSince1900();

    for (i = 0; i < cMaxServers; i++)
    {
        ServerState& ss = g_ServerStates[i];

        if (ss.progress == eProgressWaiting && g_nReallyRunningServerCount < cMaxSockets)
        {
            ServerReallyStart(i);
        }

        if (now > g_timeoutTime && 
            ss.progress > eProgressWaiting && now > ss.timeoutTime)
        {
            ServerStop(i, eStatusSlowResponding);
        }
    }
}


void ServerStart(int i)
{
    ServerItem& si = g_ServerItems[i];
    ServerState& ss = g_ServerStates[i];

    // Global timeout of 120 seconds
    g_timeoutTime = GetMillisecondsSince1900() + 120 * 1000;

    ss.iter = 0;
    si.status = eStatusNothing;
    ss.progress = eProgressWaiting;
    ss.dirty = true;

    if (!g_nRunningServerCount++)
    {
        ChangeMessage(eMessageChecking);
        SetDlgItemText(g_hDlg, IDOK, g_bTesting ? "Stop Testing...":
            "Stop Correcting...");
    }

    DealWithWaitingServers();
}


void ServerDNS(HANDLE h, WORD error)
{
    int i;

    for (i = 0; i < cMaxServers; i++)
    {
        ServerState& ss = g_ServerStates[i];

        if (ss.progress > eProgressWaiting && ss.h == h)
        {
            ServerItem& si = g_ServerItems[i];

            ss.h = 0;
            switch (error)
            {
                case 0:
                    ss.ip = 
                        *(LPDWORD(((hostent*)ss.hostentBuf)->h_addr_list[0]));
                    ServerGotIP(i);
                    break;

                case WSAHOST_NOT_FOUND:
                case WSANO_DATA:
                    ServerStop(i, eStatusNoDNSEntry);
                    break;

                default:
                    ServerStop(i, eStatusNetDown);
            }
            return;
        }
    }
}

void ServerGotIP(int i)
{
    SOCKADDR_IN sinClient, sinServer;
    int err;

    ServerState& ss = g_ServerStates[i];
    ServerItem& si = g_ServerItems[i];

    sinServer.sin_family = AF_INET;
    sinServer.sin_port = htons(37);
    sinServer.sin_addr.S_un.S_addr = ss.ip;

    sinClient.sin_family = AF_INET;
    sinClient.sin_addr.S_un.S_addr = INADDR_ANY;
    sinClient.sin_port = 0;

    ss.s = socket(AF_INET, SOCK_STREAM, 0);
    if (ss.s == INVALID_SOCKET)
    {
        ServerStop(i, eStatusNetError);
        return;
    }
    err = bind(ss.s, (const struct sockaddr FAR *)&sinClient, 
        sizeof(SOCKADDR_IN));
    if (err)
    {
        ServerStop(i, eStatusNetError);
        return;
    }

    err = 1;
    err = setsockopt(ss.s, SOL_SOCKET, SO_DONTLINGER, (LPCSTR)&err, sizeof(err));
    if (err)
    {
        ServerStop(i, eStatusNetError);
        return;
    }

    err = WSAAsyncSelect(ss.s, g_hDlg, WM_USER + 2, FD_CONNECT | FD_READ);
    if (err)
    {
        ServerStop(i, eStatusNetError);
        return;
    }
    ss.progress = eProgressConnecting;
    ss.dirty = true;

    err = connect(ss.s, (const struct sockaddr FAR *)&sinServer, 
        sizeof(SOCKADDR_IN));
    if (err && WSAGetLastError() != WSAEWOULDBLOCK)
    {
        ServerStop(i, eStatusNetError);
        return;
    }

    // Wait no more than 20 seconds for connect operation
    ss.timeoutTime = GetMillisecondsSince1900() + 20 * 1000;
}

void ServerConnected(int i, int err)
{
    ServerState& ss = g_ServerStates[i];
    ServerItem& si = g_ServerItems[i];

    if (err)
    {
        ServerStop(i, (err == WSAECONNREFUSED) ? 
            eStatusRefused : eStatusNotResponding);
    }
    else
    {
        ss.latency[ss.iter] = GetMilliseconds();
        ss.progress = eProgressReading;
        ss.dirty = true;
    }

    // Cancel global timeout
    g_timeoutTime = 0;

    // Wait no more than 20 seconds for a read operation
    ss.timeoutTime = GetMillisecondsSince1900() + 20 * 1000;
}

void ServerRead(int i, int err)
{
    DWORD temp;
    LONGLONG correction;

    ServerState& ss = g_ServerStates[i];
    ServerItem& si = g_ServerItems[i];

    if (err)
    {
        ServerStop(i, eStatusNotResponding);
    }
    else
    {
        err = recv(ss.s, (char FAR *)&temp, 4, 0);
        if (err != 4)
        {
            ServerStop(i, eStatusNotResponding);
            return;
        }

        correction = ntohl(temp) * LONGLONG(1000);
        correction -= GetMillisecondsSince1900();

        ss.latency[ss.iter] = GetMilliseconds() - ss.latency[ss.iter];
        ss.correction[ss.iter] = 
            correction + 
            (ss.latency[ss.iter] >> 1) +
            g_LocalTimeOffset;

        ss.dirty = true;

        if (++ss.iter < cMaxIter)
        {
            WSAAsyncSelect(ss.s, g_hDlg, 0, 0);
            closesocket(ss.s);
            ss.s = 0;
            ServerGotIP(i);
        }
        else
        {
            ServerTimeAdjust(i);
        }
    }
}

void ServerTimeAdjust(int i)
{
    LONGLONG correction;
    DWORD latency;
    int m, n;

    ServerState& ss = g_ServerStates[i];
    ServerItem& si = g_ServerItems[i];

    for (m = 0; m < cMaxIter - 1; m++)
    {
        for (n = m + 1;  n < cMaxIter; n++)
        {
            if (ss.latency[n] < ss.latency[m])
            {
                latency = ss.latency[n];
                correction = ss.correction[n];

                ss.latency[n] = ss.latency[m];
                ss.correction[n] = ss.correction[m];

                ss.latency[m] = latency;
                ss.correction[m] = correction;
            }
        }
    }

    correction = 0;
    for (m = 0; m < cUsedIter; m++)
    {
        correction += ss.correction[m];
    }
    si.correction = correction / cUsedIter;

    latency = 0;
    for (m = 0; m < cMaxIter; m++)
    {
        latency += ss.latency[m];
    }
    si.latency = latency / cMaxIter;

    ServerStop(i, g_bTesting ? eStatusTest:eStatusUsed);
}

void ServerStop(int i, int status)
{
    ServerState& ss = g_ServerStates[i];

    if (ss.progress)
    {
        if (ss.progress > eProgressWaiting)
        {
            --g_nReallyRunningServerCount;

            if (ss.h)
            {
                WSACancelAsyncRequest(ss.h);
                ss.h = 0;
            }
            if (ss.s)
            {
                WSAAsyncSelect(ss.s, g_hDlg, 0, 0);
                closesocket(ss.s);
                ss.s = 0;
            }
        }
        ss.progress = eProgressNot;
        g_ServerItems[i].status = status;
        SaveServer(i);
        ss.dirty = true;

        if (!(--g_nRunningServerCount))
        {
            bool bTimeCorrected = false;

            if (!g_bTesting)
            {
                bTimeCorrected = DoSystemTimeCorrection();
            }

            ChangeMessage(eMessageIdle);
            SetDlgItemText(g_hDlg, IDOK, "Correct NOW!");
            DisplayDirtyServers();

            if (bTimeCorrected && g_bFirstServerList)
            {
                g_bAutoQuit = false;
                g_bFirstServerList = false;
                if (MessageBox(g_hDlg, "Now that you've successfully "
                    "corrected your clock using several time servers, "
                    "AtomicTime will remove all but the four fastest "
                    "so that your next correction will run faster "
                    "and be more reliable.\n\n"
                    "(Alternately, you can right click on the server "
                    "list and edit them yourself.)", "AtomicTime", 
                    MB_OKCANCEL | MB_ICONEXCLAMATION) == IDOK)
                {
                    Keep4BestServers();
                }
            }
        }
    }
}

bool DoSystemTimeCorrection()
{
    LONGLONG correction, correctionTime, worstCorrection, llabs;
    int i, iWorstCorrection, count;

ThrewAwayWorstServer:
    correction = 0;
    count = 0;
    for (i = 0; i < cMaxServers; i++)
    {
        ServerItem& si = g_ServerItems[i];

        if (si.sName[0] && si.status == eStatusUsed)
        {
            correction += si.correction;
            count++;
        }
    }
    if (!count)
    {
        g_bProblem = true;
        return false;
    }
    correction /= count;

    worstCorrection = 0;
    for (i = 0; i < cMaxServers; i++)
    {
        ServerItem& si = g_ServerItems[i];

        if (si.sName[0] && si.status == eStatusUsed)
        {
            llabs = si.correction - correction;
            if (llabs < 0)
            {
                llabs = -llabs;
            }
            if (llabs >= worstCorrection)
            {
                worstCorrection = llabs;
                iWorstCorrection = i;
            }
        }
    }

    if (worstCorrection >= 2000)
    {
        g_ServerItems[iWorstCorrection].status = eStatusThrownOut;
        g_ServerStates[iWorstCorrection].dirty = true;
        SaveServer(iWorstCorrection);

        goto ThrewAwayWorstServer;
    }
    correctionTime = OffsetMillisecondsSince1900(correction);

    g_LastCorrectionValue = correction;
    g_LastCorrectionTime = correctionTime;
    g_LastAttemptTime = g_LastCorrectionTime;

    SetReg("LastCorrectionTime", &g_LastCorrectionTime, 
        sizeof(g_LastCorrectionTime));
    SetReg("LastCorrectionValue", &g_LastCorrectionValue, 
        sizeof(g_LastCorrectionValue));
    if (g_bAutoQuit)
    {
        SetTimer(g_hDlg, 1, 10000, NULL);
    }
    SendMessage(HWND_TOPMOST, WM_TIMECHANGE, 0, 0);

    #ifdef _DEBUG
        {
            char s1[100], s2[100];

            MillisecondsToText(g_LastCorrectionValue, s1);
            FormatMillisecondsSince1900(g_LastCorrectionTime, s2);

            dprintf(("Corrected %s at %s\r\n", s1, s2));
        }
    #endif
    return true;
}

void Keep4BestServers()
{
    int i, count;

    count = 0;
    for (i = 0; i < cMaxServers; i++)
    {
        ServerItem& si = g_ServerItems[i];

        if (si.sName[0])
        {
            if (si.status != eStatusUsed && si.status != eStatusTest)
            {
                si.sName[0] = 0;
                DisplayAndSaveServer(i);
            }
            else
            {   
                count++;
            }
        }
    }
    while (count > 4)
    {
        DWORD worstLatency = 0;
        int iWorst;

        for (i = 0; i < cMaxServers; i++)
        {
            ServerItem& si = g_ServerItems[i];

            if (si.sName[0] && si.latency >= worstLatency)
            {
                worstLatency = si.latency;
                iWorst = i;
            }
        }

        ServerStop(iWorst, eStatusInterrupted);
        g_ServerItems[iWorst].sName[0] = 0;
        DisplayAndSaveServer(iWorst);
        count--;
    }
    ListView_SortItems(g_hwndList, ServerListCompareFunc, 0);
}

void AbortAllServers(int status)
{
    int i;

    for (i = 0; i < cMaxServers; i++)
    {
        ServerStop(i, status);
    }
}




// USER INTERFACE STUFF
// ---------------------------------------------------------------------------

void Init()
{
    NOTIFYICONDATA nid;
    char s[100];
    HICON hIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_ATOMICTIME2));
    LV_COLUMN lvc;
    RECT rect;
    HIMAGELIST himl;

    GetReg("LastCorrectionTime", &g_LastCorrectionTime);
    GetReg("LastCorrectionValue", &g_LastCorrectionValue);

    g_LastAttemptTime = g_LastCorrectionTime;

    nid.cbSize = sizeof(NOTIFYICONDATA);
    nid.hWnd = g_hDlg;
    nid.uID = 0;
    nid.uFlags = NIF_MESSAGE;
    nid.uCallbackMessage = WM_USER;
    Shell_NotifyIcon(NIM_ADD, &nid);
    ChangeMessage(eMessageIdle);

    SetClassLong(g_hDlg, GCL_HICON, (LONG)hIcon);

    // Setup Server List View
    himl = ImageList_Create(11, 11, ILC_MASK, 1, 1); 
    ImageList_AddIcon(himl, hIcon); 
    ListView_SetImageList(g_hwndList, himl, LVSIL_SMALL); 
    ListView_SetBkColor(g_hwndList, GetSysColor(COLOR_WINDOW));

    GetClientRect(g_hwndList, &rect);

    lvc.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH;
    lvc.fmt = LVCFMT_LEFT;
    lvc.cx = rect.right * 50 / 100;
    lvc.pszText = "Time Server";
    ListView_InsertColumn(g_hwndList, 1,    &lvc);

    lvc.cx = rect.right * 25 / 100;
    lvc.pszText = "Latency (ms)";
    ListView_InsertColumn(g_hwndList, 2,    &lvc);

    lvc.cx = rect.right * 25 / 100;
    lvc.pszText = "Correction";
    ListView_InsertColumn(g_hwndList, 3,    &lvc);

    ZeroMemory(g_ServerStates, sizeof(g_ServerStates));

    LoadServerList();
    ListView_SortItems(g_hwndList, ServerListCompareFunc, 0);

    // Set up Trackbar
    SendDlgItemMessage(g_hDlg, IDC_SLIDER_SCHEDULE, TBM_SETRANGE, TRUE, 
        MAKELONG(0, 9));
    GetReg("Schedule", &g_nSchedule);
    SendDlgItemMessage(g_hDlg, IDC_SLIDER_SCHEDULE, TBM_SETPOS, TRUE, 
        g_nSchedule); 
    SetTrackCaption();

    // Set the checkbox
    SendDlgItemMessage(g_hDlg, IDC_CORRECT_ON_LAUNCH, BM_SETCHECK, 
        g_bCorrectOnLaunch, 0);

    // Set up offset
    GetReg("Offset", &g_LocalTimeOffset);
    MillisecondsToText(g_LocalTimeOffset, s);
    SetDlgItemText(g_hDlg, IDC_EDIT_OFFSET, s);

    DeleteObject(hIcon); 

    if (!g_nRunningServerCount && 
        ( g_bCorrectOnLaunch || (g_mode && g_nSchedule == 1)))
    {
        g_bAutoQuit = (g_nSchedule <= 1) ? true:false;
        StartCorrect();
    }
}

void Cleanup()
{
    NOTIFYICONDATA nid;

    nid.cbSize = sizeof(NOTIFYICONDATA);
    nid.hWnd = g_hDlg;
    nid.uID = 0;
    Shell_NotifyIcon(NIM_DELETE, &nid);

    KillTimer(g_hDlg, 0);
    if (g_nRunningServerCount)
    {
        AbortAllServers(eStatusInterrupted);
    }
    KillTimer(g_hDlg, 1);
    KillTimer(g_hDlg, 2);
}


void ChangeMessage(int nMessageIndex)
{
    char s[200];
    NOTIFYICONDATA nid;

    KillTimer(g_hDlg, 2);

    switch (nMessageIndex)
    {
        case eMessageChecking:
            g_bProblem = false;
            SetTimer(g_hDlg, 2, 200, NULL);
            lstrcpy(s, "Checking time servers...");
            break;

        case eMessageTesting:
            g_bProblem = false;
            SetTimer(g_hDlg, 2, 200, NULL);
            lstrcpy(s, "Testing time servers...");
            break;

        default:
            *s = 0;

            if (g_LastCorrectionTime)
            {
                char s1[100], s2[100];

                MillisecondsToText(g_LastCorrectionValue, s1);
                FormatMillisecondsSince1900(g_LastCorrectionTime, s2);

                wsprintf(s, "Corrected %s at %s", s1, s2);
            }
    }

    SetDlgItemText(g_hDlg, IDC_TEXT_STATUS, s);
    if (g_bProblem)
    {
        lstrcpy(s, "Unsuccessful correction!  Double-click for details...");
    }
    nid.cbSize = sizeof(NOTIFYICONDATA);
    nid.hIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(
        g_bProblem ? IDI_ATOMICTIME3:IDI_ATOMICTIME1));
    nid.hWnd = g_hDlg;
    nid.uID = 0;
    nid.uFlags = NIF_TIP | NIF_ICON;
    lstrcpy(nid.szTip, s);
    Shell_NotifyIcon(NIM_MODIFY, &nid);
    DeleteObject(nid.hIcon); 
}

void SetTrackCaption()
{
    char* s;

    switch (g_nSchedule)
    {
        case 1: 
            s = "On Startup"; 
            g_CorrectionInterval = 0;
            break;

        case 2: 
#ifdef _DEBUG
            s = "Every 20 seconds"; 
            g_CorrectionInterval = LONGLONG(1000) * 20;
#else
            s = "Every 5 Minutes"; 
            g_CorrectionInterval = LONGLONG(1000) * 60 * 5;
#endif
            break;

        case 3: 
            s = "Every 15 Minutes"; 
            g_CorrectionInterval = LONGLONG(1000) * 60 * 15;
            break;

        case 4: 
            s = "Hourly"; 
            g_CorrectionInterval = LONGLONG(1000) * 3600;
            break;

        case 5: 
            s = "Every Six Hours"; 
            g_CorrectionInterval = LONGLONG(1000) * 3600 * 6;
            break;

        case 6: 
            s = "Daily"; 
            g_CorrectionInterval = LONGLONG(1000) * 3600 * 24;
            break;

        case 7: 
            s = "Every 3 Days"; 
            g_CorrectionInterval = LONGLONG(1000) * 3600 * 24 * 3;
            break;

        case 8: 
            s = "Weekly"; 
            g_CorrectionInterval = LONGLONG(1000) * 3600 * 24 * 7;
            break;

        case 9: 
            s = "Monthly"; 
            g_CorrectionInterval = LONGLONG(1000) * 3600 * 24 * 30;
            break;

        default: 
            g_CorrectionInterval = 0;
            s = "No Scheduled Interval"; break;
    }
    SetRunOnStart(g_nSchedule != 0);
    SetDlgItemText(g_hDlg, IDC_TEXT_SCHEDULE, s);
}

void RemoveSelectedServers()
{
    int i = ListView_GetSelectedCount(g_hwndList);

    if (i)
    {
        char s[200];
        wsprintf(s, "Are you sure you want to delete %d server(s)?", i);

        if (MessageBox(g_hDlg, s, "Warning!", MB_OKCANCEL | MB_ICONEXCLAMATION) 
            == IDOK)
        {
            while ((i = ListView_GetNextItem(g_hwndList, -1, 
                LVNI_ALL | LVNI_SELECTED)) >= 0)
            {
                LV_ITEM lvi;

                lvi.mask = LVIF_PARAM;
                lvi.iItem = i;

                ListView_GetItem(g_hwndList, &lvi);

                ServerStop(lvi.lParam, eStatusInterrupted);
                g_ServerItems[lvi.lParam].sName[0] = 0;
                DisplayAndSaveServer(lvi.lParam);
            }
        }
        ListView_SortItems(g_hwndList, ServerListCompareFunc, 0);
    }
}

void ToggleSelectedServers()
{
    int i = -1;

    while ((i = ListView_GetNextItem(g_hwndList, i, 
        LVNI_ALL | LVNI_SELECTED)) >= 0)
    {
        LV_ITEM lvi;

        lvi.mask = LVIF_PARAM;
        lvi.iItem = i;

        ListView_GetItem(g_hwndList, &lvi);

        ServerStop(lvi.lParam, eStatusInterrupted);
        g_ServerItems[lvi.lParam].enabled ^= 1;
        DisplayAndSaveServer(lvi.lParam);
    }
    ListView_SortItems(g_hwndList, ServerListCompareFunc, 0);
}

void TestSelectedServers()
{
    int i = ListView_GetSelectedCount(g_hwndList);

    if (i)
    {
        if (g_nRunningServerCount)
        {
            return;
        }
        g_bTesting = true;

        i = -1;
        while ((i = ListView_GetNextItem(g_hwndList, i, 
            LVNI_ALL | LVNI_SELECTED)) >= 0)
        {
            LV_ITEM lvi;

            lvi.mask = LVIF_PARAM;
            lvi.iItem = i;

            ListView_GetItem(g_hwndList, &lvi);

            ServerStart(lvi.lParam);
        }
    }
}


void ServerListPopup(BOOL useMousePos)
{
    POINT pt;
    HMENU hMenu = LoadMenu(g_hInstance,
        MAKEINTRESOURCE(IDM_SERVER_LIST)), 
        hSubMenu = GetSubMenu(hMenu, 0);
    int i, count, total;
    MENUITEMINFO mii;

    mii.cbSize = sizeof(MENUITEMINFO);
    mii.fMask = MIIM_STATE;
    mii.fState = MFS_GRAYED;

    if (useMousePos)
    {
        GetCursorPos(&pt);
    }
    else
    {
        pt.x = pt.y = 0;
        ClientToScreen(g_hwndList, &pt);
    }

    count = total = 0;
    for (i = 0; i < cMaxServers; i++)
    {
        ServerItem& si = g_ServerItems[i];

        if (si.sName[0])
        {
            total++;
            
            if (si.status != eStatusNothing)
            {
                count++;
            }
        }
    }
    if (count <= 4)
    {
        SetMenuItemInfo(hSubMenu, ID_KEEP4BEST, FALSE, &mii);
    }
    if (total == cMaxServers)
    {
        SetMenuItemInfo(hSubMenu, ID_ADD_SERVER, FALSE, &mii);
    }
    count = ListView_GetSelectedCount(g_hwndList);
    if (!count)
    {
        SetMenuItemInfo(hSubMenu, ID_DELETE_SERVER, FALSE, &mii);
        SetMenuItemInfo(hSubMenu, ID_TOGGLE_SERVER, FALSE, &mii);
        SetMenuItemInfo(hSubMenu, ID_CHANGE_SERVER, FALSE, &mii);
        SetMenuItemInfo(hSubMenu, ID_TEST_SERVER, FALSE, &mii);
    }
    TrackPopupMenu(hSubMenu, TPM_LEFTALIGN | TPM_LEFTBUTTON,
        pt.x, pt.y, 0, g_hDlg, NULL);
    DestroyMenu(hMenu);
}

// Need this for the sorting of the list view
//
// Column 0 - Name sorting
// Column 1 - Latency sorting
//            If correction in progress, sort on iteration
//            Then, sort on state
//            If states are equal, sort on name (forward alphabetical)
//            Finally, sort on latencies of servers than completed OK
// Column 2 - Correction sorting
//            Just like latency, but uses correction instead
//
int CALLBACK ServerListCompareFunc(LPARAM lParam1, LPARAM lParam2,
    LPARAM) 
{
    ServerItem& si1 = g_ServerItems[lParam1];
    ServerState& ss1 = g_ServerStates[lParam1];
    ServerItem& si2 = g_ServerItems[lParam2];
    ServerState& ss2 = g_ServerStates[lParam2];
    int status1 = si1.status, status2 = si2.status, result = 0;

    if (status1 >= eStatusThrownOut)
    {
        status1 = eStatusUsed;
    }
    if (ss1.progress && !status1)
    {
        status1 = ss1.iter - cMaxIter;
    }
    if (status2 >= eStatusThrownOut)
    {   
        status2 = eStatusUsed;
    }
    if (ss2.progress && !status2)
    {
        status2 = ss2.iter - cMaxIter;
    }
    if (g_nSortColumn)
    {
        if (si1.enabled != si2.enabled)
        {
            result = si2.enabled - si1.enabled;
        }
        else if (status1 < status2)
        {
            result = (g_nSortColumn == 1) ? g_nSortPolarity:1;
        }
        else if (status1 > status2)
        {
            result = (g_nSortColumn == 1) ? -g_nSortPolarity:-1;
        }
        else if (status1 == eStatusUsed)
        {
            if (g_nSortColumn == 1)
            {
                if (si1.latency < si2.latency)
                {
                    result = -g_nSortPolarity;
                }
                if (si1.latency > si2.latency)
                {
                    result = g_nSortPolarity;
                }
            }
            else
            {
                if (status2 == eStatusUsed)
                {
                    if (si1.correction < si2.correction)
                    {
                        result = -g_nSortPolarity;
                    }
                    if (si1.correction > si2.correction)
                    {
                        result =  g_nSortPolarity;
                    }
                }
            }
        }
        else
        {
            if (ss1.progress + ss1.iter * 10  < ss2.progress + ss2.iter * 10)
            {
                result = -g_nSortPolarity;
            }
        }
    }
    if (!result)
    {
        result = lstrcmp(si1.sName, si2.sName);
        if (!g_nSortColumn)
        {
            result *= g_nSortPolarity;
        }
    }
    return result;
} 

BOOL CALLBACK MyDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static BOOL theTracking = FALSE;
    WINDOWPLACEMENT wp;
    int i;
    
    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
            WINDOWPLACEMENT wp2;
            RECT& r = wp.rcNormalPosition, &r2 = wp2.rcNormalPosition;

            g_hDlg = hDlg;
            g_hwndList = GetDlgItem(hDlg, IDC_LIST_SERVERS);
            SetWindowText(hDlg, cWindowName);
            Init();

            if (!GetReg("WindowPlacement", &wp) ||
                r.left > GetSystemMetrics(SM_CXFULLSCREEN) - 20 ||
                r.right < 20 ||
                r.top > GetSystemMetrics(SM_CYFULLSCREEN) - 20 ||
                r.bottom < 20 ||
                wp.length != sizeof(wp))
            {
                wp.length = sizeof(wp);
                GetWindowPlacement(hDlg, &wp);
                OffsetRect(&r, 
                    (GetSystemMetrics(SM_CXFULLSCREEN) - 
                    (r.right - r.left)) >> 1,
                    (GetSystemMetrics(SM_CYFULLSCREEN) - 
                    (r.bottom - r.top)) >> 1);
            }
            else
            {
                wp2.length = sizeof(wp2);
                GetWindowPlacement(hDlg, &wp2);
                r.right = r.left + r2.right - r2.left;
                r.bottom = r.top + r2.bottom - r2.top;
            }
            wp.showCmd = (g_mode || g_bCorrectOnLaunch) ? SW_HIDE:SW_SHOW;
            SetTimer(g_hDlg, 0, 10000, NULL);
            SetWindowPlacement(hDlg, &wp);
            return TRUE;
        }
        case WM_SHOWWINDOW:
            KillTimer(g_hDlg, 0);
            SetTimer(g_hDlg, 0, wParam ? 100:10000, NULL);
            return TRUE;

        case WM_CLOSE:
            goto Close;

        case WM_MOVE:
            wp.length = sizeof(wp);
            GetWindowPlacement(hDlg, &wp);
            SetReg("WindowPlacement", &wp, sizeof(wp));
            return TRUE;

        case WM_TIMER:
            if (wParam == 0)
            {
                char s1[100], s2[100];
                LONGLONG now = GetMillisecondsSince1900();

                FormatMillisecondsSince1900(now, s1);

                GetDlgItemText(g_hDlg, IDC_TEXT_TIME, s2, sizeof(s2));
                if (lstrcmp(s1, s2))
                {
                    SetDlgItemText(g_hDlg, IDC_TEXT_TIME, s1);
                }

                if (g_CorrectionInterval)
                {
                    if (now >= g_CorrectionInterval + g_LastAttemptTime &&
                        !g_nRunningServerCount)
                    {
                        StartCorrect();
                    }
                }
            }
            else if (wParam == 1)
            {
                KillTimer(g_hDlg, 1);
                if (g_bAutoQuit)
                {
                    DestroyWindow(hDlg);
                }
            }
            else if (wParam == 2)           
            {
                NOTIFYICONDATA nid;
                static flipflop;

                flipflop = !flipflop;

                nid.cbSize = sizeof(NOTIFYICONDATA);
                nid.hIcon = LoadIcon(g_hInstance, 
                    MAKEINTRESOURCE(flipflop ? IDI_ATOMICTIME1:IDI_ATOMICTIME2));
                nid.hWnd = g_hDlg;
                nid.uID = 0;
                nid.uFlags = NIF_ICON;
                Shell_NotifyIcon(NIM_MODIFY, &nid);
                DeleteObject(nid.hIcon); 

                DealWithWaitingServers();
                DisplayDirtyServers();
            }
            return TRUE;

        case WM_ENDSESSION:
            DestroyWindow(hDlg);
            return TRUE;

        case WM_HSCROLL:
            g_nSchedule = SendDlgItemMessage(g_hDlg, IDC_SLIDER_SCHEDULE, 
                TBM_GETPOS, 0, 0);
            SetReg("Schedule", &g_nSchedule, sizeof(g_nSchedule));
            SetTrackCaption();
            return TRUE;

            
        case WM_USER: // Tray icon message
            if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN)
            {
                if (!theTracking)
                {
                    POINT pt;
                    HMENU hMenu = LoadMenu(g_hInstance,
                        MAKEINTRESOURCE(IDM_ATOMICTIME));
                    MENUITEMINFO mii;
                    char s[100];

                    g_bAutoQuit = false;
                    GetCursorPos(&pt);
                    SetForegroundWindow(hDlg);

                    theTracking = TRUE;
                    SetMenuDefaultItem(GetSubMenu(hMenu, 0), 0, TRUE);

                    s[0] = '&';
                    GetDlgItemText(g_hDlg, IDOK, s + 1, sizeof(s) - 1);
                    mii.cbSize = sizeof(mii);
                    mii.fMask = MIIM_TYPE;
                    mii.fType = MFT_STRING;
                    mii.dwTypeData = s;
                    SetMenuItemInfo(GetSubMenu(hMenu, 0), IDOK, FALSE, &mii);

                    TrackPopupMenu(GetSubMenu(hMenu, 0),
                        TPM_LEFTALIGN | TPM_LEFTBUTTON,
                        pt.x, pt.y, 0, g_hDlg, NULL);

                    DestroyMenu(hMenu);
                    theTracking = FALSE;
                }
            }
            else if (lParam == WM_LBUTTONDBLCLK || lParam == WM_RBUTTONDBLCLK)
            {
                SendMessage(hDlg, WM_COMMAND, ID_OPTIONS, 0);
            }
            break;

        case WM_USER + 1: // Asynch Winsock callback for DNS
            ServerDNS((HANDLE)wParam, WSAGETASYNCERROR(lParam));
            return TRUE;

        case WM_USER + 2: // Asynch Winsock callback for connect/read
            {
                for (i = 0; i < cMaxServers; i++)
                {
                    ServerState& ss = g_ServerStates[i];

                    if (ss.progress > eProgressWaiting && ss.s == (SOCKET)wParam)
                    {
                        switch (WSAGETSELECTEVENT(lParam))
                        {
                            case FD_CONNECT:
                                ServerConnected(i, WSAGETSELECTERROR(lParam));
                                break;

                            case FD_READ:
                                ServerRead(i, WSAGETSELECTERROR(lParam));
                                break;
                        }
                    }
                }
            }
            return TRUE;

        case WM_NOTIFY: // messages from server list view
            if (wParam == IDC_LIST_SERVERS)
            {
                NM_LISTVIEW* pnmv = (NM_LISTVIEW*)lParam;

                switch (pnmv->hdr.code)
                {
                    case LVN_ENDLABELEDIT: // someone entered a server name
                        {
                            LV_ITEM* plvi = &((LV_DISPINFO *)lParam)->item; 
                            ServerName sOldName;
                            LV_ITEM lvi;

                            if (!(plvi->mask & LVIF_TEXT))
                            {
                                return TRUE; // Not a text edit?
                            }

                            lvi.mask = LVIF_PARAM | LVIF_TEXT;
                            lvi.iItem = plvi->iItem;
                            lvi.cchTextMax = sizeof(sOldName);
                            lvi.pszText = sOldName;
                            ListView_GetItem(g_hwndList, &lvi);

                            // If this is NULL, they pressed escape
                            // instead of changing the name
                            if (plvi->pszText)
                            {
                                ServerStop(lvi.lParam, eStatusInterrupted);
                                lstrcpyn(g_ServerItems[lvi.lParam].sName,
                                    plvi->pszText, 
                                    sizeof(ServerName));
                                DisplayAndSaveServer(lvi.lParam);
                                ListView_SortItems(g_hwndList, 
                                    ServerListCompareFunc, 0);
                            }
                        }
                        return TRUE;

                    case LVN_KEYDOWN:
                        switch (((LV_KEYDOWN *)lParam)->wVKey)
                        {
                            case VK_APPS:
                            case VK_RETURN:
                                ServerListPopup(FALSE);
                                return TRUE;

                            case VK_DELETE:
                                RemoveSelectedServers();
                                return TRUE;
                        }
                        break;

                    case LVN_COLUMNCLICK: 
                        {
                            i = ((NM_LISTVIEW *)lParam)->iSubItem;

                            if (i != g_nSortColumn)
                            {
                                g_nSortColumn = i;
                                g_nSortPolarity = 1;
                            }
                            else
                            {
                                g_nSortPolarity *= -1;
                            }
                            ListView_SortItems(g_hwndList, 
                                ServerListCompareFunc, 0);
                        }
                        return TRUE;

                    case NM_RCLICK:
                        ServerListPopup(TRUE);
                        return TRUE;
                }
            }
            return FALSE;
    
        case WM_COMMAND:
            // We cannot be getting any real input during an edit
            // session of the server list.  However, the ID of the
            // edit box is often the same as IDOK.  This check will
            // prevent any confusion (although it is ugly)
            if (g_hwndList && ListView_GetEditControl(g_hwndList))
            {
                return FALSE;
            }
            switch(LOWORD(wParam))
            {
                case IDOK:
                    if (g_nRunningServerCount)
                    {
                        AbortAllServers(eStatusInterrupted);
                        return TRUE;
                    }
                    // Fall through to IDYES

                case IDYES:
                    if (!g_nRunningServerCount)
                    {
                        StartCorrect();
                    }
                    return TRUE;

                case IDCANCEL:
Close:
                    ShowWindow(hDlg, SW_HIDE);

                    if (g_CorrectionInterval)
                    {
                        // If there is a timed-correction waiting to happen,
                        // do not shut the app down, leave the tray icon alive
                        return TRUE;
                    }
                    // Otherwise, fall through and shut down

                case ID_QUIT: // Tray icon quit message
                    DestroyWindow(hDlg);
                    return TRUE;

                case ID_OPTIONS: // Tray icon wants the big window
                    g_bProblem = false;
                    if (!g_nRunningServerCount)
                    {
                        ChangeMessage(eMessageIdle);
                    }
                    ShowWindow(hDlg, SW_NORMAL);
                    return TRUE;

                case IDC_CORRECT_ON_LAUNCH: // Checkbox
                    g_bCorrectOnLaunch = SendDlgItemMessage(hDlg, 
                        IDC_CORRECT_ON_LAUNCH, BM_GETCHECK, 0, 0) ? true:false;
                    if (g_bCorrectOnLaunch)
                    {
                        if (MessageBox(g_hDlg, 
                            "This will prevent the options window from appearing "
                            "the next time you run AtomicTime, and instead, do a "
                            "silent time correction & quit.  It is intended for "
                            "people who want a command line utility.  You "
                            "may combine this with scheduled corrections as well.\n\n"
                            "One you enable this, the only way to get back to "
                            "the options window is to click on the AtomicTime "
                            "icon in the system tray during the ten seconds it "
                            "appears there.\n\n"
                            "Do you still want to use this option?",
                            "Note!", MB_YESNO | MB_ICONINFORMATION) == IDNO)
                        {
                            g_bCorrectOnLaunch = false;
                            SendDlgItemMessage(hDlg, 
                                IDC_CORRECT_ON_LAUNCH, BM_SETCHECK, 0, 0);
                            return TRUE;
                        }
                    }
                    SetReg("CorrectOnLaunch", &g_bCorrectOnLaunch,
                            sizeof(g_bCorrectOnLaunch));
                    return TRUE;

                case ID_DEFAULT_SERVERS:
                    if (MessageBox(g_hDlg, 
                        "Are you sure you want to reset the server list?", 
                        "Warning!", MB_OKCANCEL | MB_ICONEXCLAMATION) == IDOK)
                    {
                        AbortAllServers(eStatusInterrupted);
                        SetDefaultServers();
                        ListView_SortItems(g_hwndList, ServerListCompareFunc, 0);
                    }
                    return TRUE;

                case ID_DELETE_SERVER:
                    RemoveSelectedServers();
                    return TRUE;

                case ID_TEST_SERVER:
                    TestSelectedServers();
                    return TRUE;

                case ID_TOGGLE_SERVER:
                    ToggleSelectedServers();
                    return TRUE;

                case ID_KEEP4BEST:
                    Keep4BestServers();
                    return TRUE;

                case ID_CHANGE_SERVER:
                    {
                        i = ListView_GetNextItem(g_hwndList, -1, 
                            LVNI_ALL | LVNI_FOCUSED);
                        if (i >= 0)
                        {
                            ListView_EditLabel(g_hwndList, i);
                        }
                    }
                    return TRUE;

                case ID_ADD_SERVER:
                    {
                        i = AddServer("New Server");

                        if (i >= 0)
                        {
                            LV_FINDINFO lfi;
                            int iFound;

                            DisplayAndSaveServer(i);

                            lfi.flags = LVFI_PARAM;
                            lfi.lParam = i;

                            iFound = ListView_FindItem(g_hwndList, -1, &lfi);
                            if (iFound >= 0)
                            {
                                ListView_EditLabel(g_hwndList, iFound);
                            }
                        }
                    }
                    return TRUE;

                case IDC_EDIT_OFFSET:
                    if (HIWORD(wParam) == EN_KILLFOCUS)
                    {
                        char s[100];

                        GetDlgItemText(hDlg, IDC_EDIT_OFFSET, s, sizeof(s));
                        g_LocalTimeOffset = TextToMilliseconds(s);

                        SetReg("Offset", &g_LocalTimeOffset, 
                            sizeof(g_LocalTimeOffset));
                        MillisecondsToText(g_LocalTimeOffset, s);
                        SetDlgItemText(hDlg, IDC_EDIT_OFFSET, s);
                    }
                    return TRUE;
            }
            break;

        case WM_SYSCOLORCHANGE:
            ListView_SetBkColor(g_hwndList, GetSysColor(COLOR_WINDOW));
            break;

        case WM_DESTROY:
            Cleanup();
            g_hDlg = 0;
            PostQuitMessage(0);
            return TRUE;
    }
    return FALSE;
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR lpCmdLine, int)
{
    DWORD version;
    MSG msg;
    WSADATA WSAData;
    HWND hWnd;

    if (GetReg("Version", &version))
    {
        if (version != cVersion)
        {
            if (MessageBox(0, "Registry settings of other versions of AtomicTime "
                "will be erased if you run this version.", cWindowName, 
                MB_OKCANCEL | MB_ICONSTOP) == IDCANCEL)
            {
                return 0;
            }
            if (RegDeleteKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\AtomicTime"))
            {
                Fatal("Cannot erase old Registry key");
            }
        }
        else
        {
            g_bDefaultServers = false;
        }
    }

    GetReg("CorrectOnLaunch", &g_bCorrectOnLaunch);

    switch(lpCmdLine[0])
    {
        case 's': case 'u':
            g_mode = lpCmdLine[0];
            break;
    }

    if ((hWnd = FindWindow(NULL, cWindowName)) != NULL)
    {
        if (g_bCorrectOnLaunch)
        {
            SendMessage(hWnd, WM_COMMAND, IDYES, 0);
            return 0;
        }
        else if (g_mode == 'u')
        {
            SendMessage(hWnd, WM_COMMAND, ID_QUIT, 0);
        }
        else
        {
            ShowWindow(hWnd, SW_SHOW);
            SetForegroundWindow(hWnd);
            return 0;
        }
    }

    if (g_mode == 'u')
    {
        SetRunOnStart(false);
        RegDeleteKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\AtomicTime");
        RegDeleteKey(HKEY_LOCAL_MACHINE, cUninstallString);
        Fatal("Uninstall complete.  Now delete AtomicTime files.");
        return 0;
    }

    if (!OffsetMillisecondsSince1900(0))
    {
        if (g_mode == 's')
        {
            return 0;
        }
        Fatal("You do not have privileges to set the system time."
            "\n\nLog on as an Administrator instead.");
    }

    // Initialize Windows sockets version 1.01
    if (WSAStartup(0x101, &WSAData))
    {
        Fatal("Could not open windows sockets.");
    }
    g_bWinsock = true;

    g_hInstance = hInstance;
    InitCommonControls(); // Need this for the list view

    version = cVersion;
    SetReg("Version", &version, sizeof(version));

    if (!CreateDialog(hInstance, MAKEINTRESOURCE(IDD_ATOMICTIME),
        NULL, (DLGPROC)MyDialogProc))
    {
        Fatal("Cannot open window");
    }
    while (GetMessage(&msg, NULL, NULL, NULL)) 
    {
        if (!IsWindow(g_hDlg) || !IsDialogMessage(g_hDlg, &msg)) 
        { 
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        } 
    } 
    if (g_bWinsock)
    {
        WSACleanup(); 
    }
    return msg.wParam;
}