Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

HOWTO track a user's idle time

0.00/5 (No votes)
12 Nov 2001 17  
Track a user's idle time using global keyboard and mouse hooks.

Introduction

In general, when we talk about tracking a user's idle time, we are really after the time duration since the user last touched the mouse or keyboard of the system. Unfortunately, the Windows API does not provide us with an easy way of getting this value. However, we can roll our own using the Win32 hooks API.

The approach used here is really a simple one. We intercept the mouse and keyboard activities of the user by hooking into the OS's mouse and keyboard events using the API SetWindowsHookEx(). It is important to note that the hooks we are installing are system-wide. i.e. we receive notification even when our application does not have the focus. This is necessary since we are interested in system-wide user activities, not just in our own application. In these notifications (both keyboard and mouse), we update a common variable that stores the time when the event occurred. Therefore, to get the duration since the last user input, we simply compare the current time against this value.

The accompanying zip file contains the VC++ 6.0 project files and source code that implements this feature in a compact DLL. Also included are .lib and .dll files, which you may use directly in your applications.

The focus of this article is on how to track a user's input idle time using global hooks and how to use the accompanying DLL. If you want to find out more about the issues regarding the use and implementation of system-wide hooks and dlls, check out Joseph M. Newcomer's article.

DLL Usage

The DLL exports the following three functions:

BOOL IdleTrackerInit();    //start the monitoring process

void IdleTrackerTerm(); //stop the monitoring process

DWORD IdleTrackerGetLastTickCount(); //get the tick count of last user input

To start the monitoring process, call the function IdleTrackerInit(). The return value indicates if the mouse and keyboard hooks are installed successfully.

To stop the monitoring process, call the function IdleTrackerTerm(). This function will uninstall the mouse and keyboard hooks from the system.

To get the time duration since the last user input, just use the following piece of code. (Note that the times used are measured in milliseconds.)

UINT timeDuration = (UINT)(GetTickCount() - IdleTrackerGetLastTickCount());

And that is all to it!

DLL Innards Dissected

Data Variables

We maintain a set of variables in a shared data segment so that we have only one instance of each variable in all processes. The most important variable here is g_dwLastTick, which stores the time when the last user input event occurred. We are also storing the last known position of the mouse to filter off spurious mouse events. See Mouse Woes for more details.

#pragma data_seg(".IdleTracker")
HHOOK     g_hHkKeyboard = NULL;    // handle to the keyboard hook

HHOOK     g_hHkMouse = NULL;    // handle to the mouse hook

DWORD    g_dwLastTick = 0;    // tick time of last input event

LONG    g_mouseLocX = -1;    // x-location of mouse position

LONG    g_mouseLocY = -1;    // y-location of mouse position

#pragma data_seg()
#pragma comment(linker, "/section:.IdleTrac,rws")

DLL Initialization

The function IdleTrackerInit() simply initializes the variable g_dwLastTick to the current time and installs the global keyboard and mouse hooks to start the monitoring process.

__declspec(dllexport) BOOL IdleTrackerInit()
{
    if (g_hHkKeyboard == NULL) {
        g_hHkKeyboard = SetWindowsHookEx(WH_KEYBOARD, 
           KeyboardTracker, g_hInstance, 0);
    }
    if (g_hHkMouse == NULL) {
        g_hHkMouse = SetWindowsHookEx(WH_MOUSE, 
           MouseTracker, g_hInstance, 0);
    }

    _ASSERT(g_hHkKeyboard);
    _ASSERT(g_hHkMouse);

    g_dwLastTick = GetTickCount(); // init count


    if (!g_hHkKeyboard || !g_hHkMouse)
        return FALSE;
    else
        return TRUE;
}

DLL Termination

The function IdleTrackerTerm() does nothing more than just uninstalling the mouse and keyboard hooks to stop the monitoring process.

__declspec(dllexport) void IdleTrackerTerm()
{
    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;
    }
}

Callback Functions

In the mouse and keyboard callbacks, we update the global variable g_dwLastTick with the latest tick count. But notice that in the mouse hook MouseTracker(), we update the tick count only if the mouse location has changed since the last time this method was called. This is really a hack solution to a problem that occurs on some systems. See Mouse Woes for more details on this problem.

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

Mouse Woes

This DLL was used in an internet application that I developed some time back. It was used to trigger multimedia shows/movies whenever the user has been idle for X minutes. While beta-testing on some 30 odd PCs, we found a handful of them not kicking in after the stipulated X minutes. On further investigation, I found out that on these systems, the mouse callback mysteriously get triggered periodically even when the mouse was left untouched. It may have been triggered by the mouse (too sensitive? faulty?), the OS (9x and NT both had this problem) itself or some third-party software, I do not know.

In any case, it was unrealistic to expect my users to change their mouse, reinstall the OS, or uninstall the conflicting third-party software to fix this problem; It has to be fixed within my application. Hence I made the assumption that any subsequent mouse event that has the same location as the previous is spurious. Note that this assumption is rather overbearing as we are ignoring scenarios where the user is simply clicking the buttons on the mouse without moving it (should be rather seldom but nonetheless possible). Therefore, you will have to come up with your own fix if this assumption is not acceptable to you.

Conclusion

Well, that's it folks. Hope at least some of you out there will find this DLL useful. Please send feedback, bug reports or suggestions here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here