Click here to Skip to main content
15,891,864 members
Articles / Desktop Programming / MFC
Article

HOWTO track a user's idle time

Rate me:
Please Sign up or sign in to vote.
4.70/5 (23 votes)
12 Nov 20014 min read 394.7K   4.1K   109   76
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


Written By
Web Developer
Singapore Singapore
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionRaising an event instead? Pin
GaryM12-Dec-01 5:11
GaryM12-Dec-01 5:11 
GeneralHook only when focused X| Pin
25-Nov-01 3:11
suss25-Nov-01 3:11 
GeneralRe: Hook only when focused X| Pin
Sidney Chong25-Nov-01 15:25
Sidney Chong25-Nov-01 15:25 
GeneralRe: Hook only when focused X| Pin
28-May-02 14:15
suss28-May-02 14:15 
GeneralNice article Pin
20-Nov-01 10:50
suss20-Nov-01 10:50 
GeneralRe: Nice article Pin
Sidney Chong20-Nov-01 21:08
Sidney Chong20-Nov-01 21:08 
Generalprovide application... Pin
Sungjun Park18-Nov-01 22:32
Sungjun Park18-Nov-01 22:32 
GeneralRe: provide application... Pin
Sidney Chong25-Nov-01 16:06
Sidney Chong25-Nov-01 16:06 
Its really not that difficult to build an application using DLLs. Here's a short HOWTO:

To compile your code in VC++
(1) In your header (or source, depending on where you call the DLL functions) file, include the file "IdleTracker.h". Remember to take into account the location of this file in your include statement. e.g. suppose you unpacked the DLL package into C:\IdleTracker, then your include statement should be #include "C:\IdleTracker\IdleTracker.h"

(2) In VC++, open the dialog under the menu "Project\Settings". Under the "Link" tab, select the "Input" option under the "Category" combo box. Then in the text box "Object/library modules", add the file "IdleTrac.lib". Also add the path to this file (ie "C:\IdleTracker\Release") in the text box "Additional library path"

(3) Now hit compile!

To run your application
Just make sure that the DLL "IdleTrac.dll" is in the same directory as your exe and you are done!
GeneralA less intrusive approach using DirectInput Pin
pepoff16-Nov-01 0:04
pepoff16-Nov-01 0:04 
GeneralRe: A less intrusive approach using DirectInput Pin
tomcat16-Nov-01 4:45
tomcat16-Nov-01 4:45 
GeneralRe: A less intrusive approach using DirectInput Pin
pepoff16-Nov-01 5:10
pepoff16-Nov-01 5:10 
GeneralRe: A less intrusive approach using DirectInput Pin
1-Jun-02 20:44
suss1-Jun-02 20:44 
GeneralSuicidal Pin
Fazlul Kabir13-Nov-01 11:22
Fazlul Kabir13-Nov-01 11:22 
GeneralRe: Suicidal Pin
Oscar Londono15-Nov-01 4:19
Oscar Londono15-Nov-01 4:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.