
/**
 * IdleTracker - a DLL that tracks the user's idle input time
 *               system-wide.
 *
 * Usage
 * =====
 * - call IdleTrackerInit() when you want to start monitoring.
 * - call IdleTrackerTerm() when you want to stop monitoring.
 * - to get the time past since last user input, do the following:
 *    GetTickCount() - IdleTrackerGetLastTickCount()
 *
 * Author: Sidney Chong
 * Date: 25/5/2000
 * Version: 1.0
 **/

// Modify the following defines if you have to target a platform prior to the ones specified below.
// Refer to MSDN for the latest info on corresponding values for different platforms.
#ifndef WINVER                  // Allow use of features specific to Windows 95 and Windows NT 4 or later.
#define WINVER 0x0400           // Change this to the appropriate value to target Windows 98 and Windows 2000 or later.
#endif

#ifndef _WIN32_WINNT            // Allow use of features specific to Windows NT 4 or later.
#define _WIN32_WINNT 0x0400		// Change this to the appropriate value to target Windows 98 and Windows 2000 or later.
#endif

#ifndef _WIN32_WINDOWS          // Allow use of features specific to Windows 98 or later.
#define _WIN32_WINDOWS 0x0400   // Change this to the appropriate value to target Windows Me or later.
#endif

#ifndef _WIN32_IE               // Allow use of features specific to IE 5.01 or later.
#define _WIN32_IE 0x0501        // Change this to the appropriate value to target IE 6.0 or later.
#endif

#ifdef _DEBUG
#define UNUSED(x)
#else
#define UNUSED(x) x
#endif

#include <windows.h>
#include <crtdbg.h>

/**
 * The following global data is only shared in this instance of the DLL
 **/ 
HINSTANCE g_hInstance = NULL;   // global instance handle
HMODULE   g_hUser32 = NULL;
HANDLE    g_hMemoryMappedData = NULL;
BOOL      g_bIsWindows2000Compatible = FALSE;
BOOL      g_bIsTerminalServicesEnabled = FALSE;

/**
 * The following global data is SHARED among all instances of the DLL
 * (processes) within a terminal services session.
 **/ 
#pragma data_seg(".IdleTrac")	// you must define as SHARED in .def
HHOOK 	g_hHkKeyboard = NULL;	// handle to the keyboard hook
HHOOK 	g_hHkMouse = NULL;	    // handle to the mouse hook
LONG	g_mouseLocX = -1;	    // x-location of mouse position
LONG	g_mouseLocY = -1;	    // y-location of mouse position
DWORD	g_dwLastTick = 0;       // tick time of last input event
#pragma data_seg()
#pragma comment(linker, "/section:.IdleTrac,rws")

/**
 * The following global data is SHARED among all instances of the DLL
 * (processes); i.e., these are system-wide globals.
 **/
struct SystemWideIdleData
{
	DWORD	dwLastTick;         // tick time of last input event
};

struct SystemWideIdleData* g_pSystemWideIdleData = NULL;

/**
 * Define stuff that only exists on Windows 2000 compatible machines
 **/
typedef struct tagLASTINPUTINFO {
    UINT cbSize;
    DWORD dwTime;
} LASTINPUTINFO, *PLASTINPUTINFO;

typedef BOOL (WINAPI *GETLASTINPUTINFO)(PLASTINPUTINFO);

GETLASTINPUTINFO g_fnGetLastInputInfo = NULL;

/**
 * Find out if we are on a Windows 2000 compatible system
 **/
BOOL IsWindows2000Compatible()
{
   OSVERSIONINFO osvi;
   ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
   osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

    if (! GetVersionEx ( (OSVERSIONINFO *) &osvi) ) 
        return FALSE;

    return (osvi.dwMajorVersion >= 5);
}

/**
 * This function compares the passed in "suite name" string
 * to the product suite information stored in the registry.
 * This only works on the Terminal Server 4.0 platform.
 **/
BOOL ValidateProductSuite (LPSTR SuiteName)
{
    BOOL rVal = FALSE;
    LONG Rslt;
    HKEY hKey = NULL;
    DWORD Type = 0;
    DWORD Size = 0;
    LPSTR ProductSuite = NULL;
    LPSTR p;

    Rslt = RegOpenKeyA(
        HKEY_LOCAL_MACHINE,
        "System\\CurrentControlSet\\Control\\ProductOptions",
        &hKey
        );

    if (Rslt != ERROR_SUCCESS)
        goto exit;

    Rslt = RegQueryValueExA( hKey, "ProductSuite", NULL, &Type, NULL, &Size );
    if (Rslt != ERROR_SUCCESS || !Size)
        goto exit;

    ProductSuite = (LPSTR) LocalAlloc( LPTR, Size );
    if (!ProductSuite)
        goto exit;

    Rslt = RegQueryValueExA( hKey, "ProductSuite", NULL, &Type,
        (LPBYTE) ProductSuite, &Size );
     if (Rslt != ERROR_SUCCESS || Type != REG_MULTI_SZ)
        goto exit;

    p = ProductSuite;
    while (*p)
    {
        if (lstrcmpA( p, SuiteName ) == 0)
        {
            rVal = TRUE;
            break;
        }
        p += (lstrlenA( p ) + 1);
    }

exit:
    if (ProductSuite)
        LocalFree( ProductSuite );

    if (hKey)
        RegCloseKey( hKey );

    return rVal;
}

/**
 * This function performs the basic check to see if
 * the platform on which it is running is Terminal
 * services enabled.  Note, this code is compatible on
 * all Win32 platforms.  For the Windows 2000 platform
 * we perform a "lazy" bind to the new product suite
 * APIs that were first introduced on that platform.
 **/
BOOL IsTerminalServicesEnabled()
{
    BOOL    bResult = FALSE;    // assume Terminal Services is not enabled

    DWORD   dwVersion;
    OSVERSIONINFOEXA osVersionInfo;
    DWORDLONG dwlConditionMask = 0;
    HMODULE hmodK32 = NULL;
    HMODULE hmodNtDll = NULL;
    typedef ULONGLONG (WINAPI *PFnVerSetConditionMask)(ULONGLONG,ULONG,UCHAR);
    typedef BOOL (WINAPI *PFnVerifyVersionInfoA)(POSVERSIONINFOEXA, DWORD, DWORDLONG);
    PFnVerSetConditionMask pfnVerSetConditionMask;
    PFnVerifyVersionInfoA pfnVerifyVersionInfoA;

    dwVersion = GetVersion();

    // are we running NT ?
    if (!(dwVersion & 0x80000000))
    {
        // Is it Windows 2000 (NT 5.0) or greater ?
        if (LOBYTE(LOWORD(dwVersion)) > 4)
        {
            // In Windows 2000 we need to use the Product Suite APIs
            // Don't static link because it won't load on non-Win2000 systems
            hmodNtDll = GetModuleHandle( "NTDLL.DLL" );
            if (hmodNtDll != NULL)
            {
                pfnVerSetConditionMask = (PFnVerSetConditionMask )GetProcAddress( hmodNtDll, "VerSetConditionMask");
                if (pfnVerSetConditionMask != NULL)
                {
                    dwlConditionMask = (*pfnVerSetConditionMask)( dwlConditionMask, VER_SUITENAME, VER_AND );
                    hmodK32 = GetModuleHandle( "KERNEL32.DLL" );
                    if (hmodK32 != NULL)
                    {
                        pfnVerifyVersionInfoA = (PFnVerifyVersionInfoA)GetProcAddress( hmodK32, "VerifyVersionInfoA") ;
                        if (pfnVerifyVersionInfoA != NULL)
                        {
                            ZeroMemory(&osVersionInfo, sizeof(osVersionInfo));
                            osVersionInfo.dwOSVersionInfoSize = sizeof(osVersionInfo);
                            osVersionInfo.wSuiteMask = VER_SUITE_TERMINAL | VER_SUITE_SINGLEUSERTS;
                            bResult = (*pfnVerifyVersionInfoA)(
                                              &osVersionInfo,
                                              VER_SUITENAME,
                                              dwlConditionMask);
                        }
                    }
                }
            }
        }
        else
        {
            // This is NT 4.0 or older
            bResult = ValidateProductSuite( "Terminal Server" );
        }
    }

    return bResult;
}

/**
 * Keyboard hook: record tick count
 **/
LRESULT CALLBACK KeyboardTracker(int code, WPARAM wParam, LPARAM lParam)
{
	if (code==HC_ACTION)
    {
		g_dwLastTick = GetTickCount();
	}
	return ::CallNextHookEx(g_hHkKeyboard, code, wParam, lParam);
}

/**
 * Mouse hook: record tick count
 **/
LRESULT CALLBACK MouseTracker(int code, WPARAM wParam, LPARAM lParam)
{
	if (code==HC_ACTION) 
    {
		MOUSEHOOKSTRUCT* pStruct = (MOUSEHOOKSTRUCT*)lParam;
		//we will assume that any mouse msg with the same locations as spurious
		if (pStruct->pt.x != g_mouseLocX || pStruct->pt.y != g_mouseLocY)
		{
			g_mouseLocX = pStruct->pt.x;
			g_mouseLocY = pStruct->pt.y;
			g_dwLastTick = GetTickCount();
		}
	}
	return ::CallNextHookEx(g_hHkMouse, code, wParam, lParam);
}

/**
 * Get tick count of last keyboard or mouse event
 **/
__declspec(dllexport) DWORD IdleTrackerGetIdleTickCount()
{
    DWORD dwCurrentTickCount = GetTickCount();
    DWORD dwLastTickCount = 0;

    if ( g_bIsWindows2000Compatible )
    {
        LASTINPUTINFO lii;
        ZeroMemory( &lii, sizeof(lii) );
        lii.cbSize = sizeof(lii);
        g_fnGetLastInputInfo( &lii );

        /**
         * If both values are greater than the system tick count then
         *   the system must have looped back to the begining.
         **/
        if ( ( dwCurrentTickCount < lii.dwTime ) &&
             ( dwCurrentTickCount < g_pSystemWideIdleData->dwLastTick ) )
        {
            lii.dwTime = dwCurrentTickCount;
            g_pSystemWideIdleData->dwLastTick = dwCurrentTickCount;
        }

        if ( lii.dwTime > g_pSystemWideIdleData->dwLastTick )
            g_pSystemWideIdleData->dwLastTick = lii.dwTime;

        dwLastTickCount = g_pSystemWideIdleData->dwLastTick;
    }
    else
    {
        dwLastTickCount = g_dwLastTick;
    }

	return (dwCurrentTickCount - dwLastTickCount);
}

/**
 * Initialize DLL: install kbd/mouse hooks.
 **/
__declspec(dllexport) BOOL IdleTrackerInit()
{
 	BOOL                bExists = FALSE;
	BOOL                bResult = FALSE;
 	SECURITY_ATTRIBUTES	sec_attr;
 	SECURITY_DESCRIPTOR sd;


    g_bIsWindows2000Compatible = IsWindows2000Compatible();
    g_bIsTerminalServicesEnabled = IsTerminalServicesEnabled();

        
    if ( !g_bIsWindows2000Compatible )
    {
        if ( NULL == g_hHkKeyboard )
        {
		    g_hHkKeyboard = SetWindowsHookEx( WH_KEYBOARD, KeyboardTracker, g_hInstance, 0 );
	    }
	    if ( NULL == g_hHkMouse )
        {
		    g_hHkMouse = SetWindowsHookEx( WH_MOUSE, MouseTracker, g_hInstance, 0 );
	    }

	    _ASSERT( g_hHkKeyboard );
	    _ASSERT( g_hHkMouse );
    }
    else
    {
        g_hUser32 = LoadLibrary("user32.dll");            
        if (g_hUser32)
            g_fnGetLastInputInfo = (GETLASTINPUTINFO)GetProcAddress(g_hUser32, "GetLastInputInfo");


 	    /*
 	    * Create a security descriptor that will allow
 	    * everyone full access.
 	    */
 	    InitializeSecurityDescriptor( &sd, SECURITY_DESCRIPTOR_REVISION );
 	    SetSecurityDescriptorDacl( &sd, TRUE, NULL, FALSE );

 	    sec_attr.nLength = sizeof(sec_attr);
 	    sec_attr.bInheritHandle = TRUE;
 	    sec_attr.lpSecurityDescriptor = &sd;

 	    /*
	    * Create a filemap object that is global for everyone,
 	    * including users logged in via terminal services.
 	    */
 	    if( g_bIsTerminalServicesEnabled )
 	    {
 		    g_hMemoryMappedData = 
                CreateFileMapping(
                    INVALID_HANDLE_VALUE,
 				    &sec_attr,
 				    PAGE_READWRITE,
 				    0,
 				    4096,
 				    "Global\\BoincIdleTracker"
                );
 	    }
 	    else
 	    {
 		    g_hMemoryMappedData = 
                CreateFileMapping(
                    INVALID_HANDLE_VALUE,
 				    &sec_attr,
 				    PAGE_READWRITE,
 				    0,
 				    4096,
 				    "BoincIdleTracker"
                );
 	    }
	    _ASSERT( g_hMemoryMappedData );

 	    if( NULL != g_hMemoryMappedData )
 	    {
 		    if( ERROR_ALREADY_EXISTS == GetLastError() )
 			    bExists = TRUE;

            g_pSystemWideIdleData = (struct SystemWideIdleData*) 
                MapViewOfFile(
                    g_hMemoryMappedData, 
                    FILE_MAP_ALL_ACCESS,
 				    0,
                    0,
                    0
                );

            _ASSERT( g_pSystemWideIdleData );
        }

 	    if( !bExists )
 	    {
 		    g_pSystemWideIdleData->dwLastTick = GetTickCount();
 	    }
    }


    if ( !g_bIsWindows2000Compatible )
    {
	    if ( !g_hHkKeyboard || !g_hHkMouse )
		    bResult = FALSE;
	    else
		    bResult = TRUE;
    }
    else
    {
        if ( !g_hUser32 || !g_fnGetLastInputInfo || !g_hMemoryMappedData || !g_pSystemWideIdleData )
		    bResult = FALSE;
	    else
		    bResult = TRUE;
    }

    return bResult;
}

/**
 * Terminate DLL: remove hooks.
 **/
__declspec(dllexport) void IdleTrackerTerm()
{
    if ( !g_bIsWindows2000Compatible )
    {
	    BOOL bResult;
	    if ( g_hHkKeyboard )
	    {
		    bResult = UnhookWindowsHookEx( g_hHkKeyboard );
		    _ASSERT( bResult );
		    g_hHkKeyboard = NULL;
	    }
	    if ( g_hHkMouse )
	    {
		    bResult = UnhookWindowsHookEx(g_hHkMouse);
		    _ASSERT( bResult );
		    g_hHkMouse = NULL;
	    }
    }
    else
    {
        if( NULL != g_pSystemWideIdleData )
 	    {
 		    UnmapViewOfFile(g_pSystemWideIdleData);
 		    CloseHandle(g_hMemoryMappedData);
 	    }

        if ( NULL != g_hUser32 )
            FreeLibrary(g_hUser32);
    }
}

/**
 * DLL's entry point
 **/
int WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID UNUSED(lpReserved))
{
    switch(dwReason)
    {
		case DLL_PROCESS_ATTACH:
			DisableThreadLibraryCalls(hInstance);
			g_hInstance = hInstance;
            break;
	}
	return TRUE;
}

const char *BOINC_RCSID_14d432d5b3 = "$Id: win_idle_tracker.cpp,v 1.10 2005/07/07 22:26:40 rwalton Exp $";
