Click here to Skip to main content
15,860,972 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 390K   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

 
GeneralMy vote of 5 Pin
gndnet21-Oct-12 15:27
gndnet21-Oct-12 15:27 
QuestionLicense Question Pin
LuisVilla21-Feb-12 10:40
LuisVilla21-Feb-12 10:40 
QuestionDoes this code is free license ? can be integrated into commerical software? Pin
oldhawkcn24-Nov-10 1:04
oldhawkcn24-Nov-10 1:04 
GeneralIDLE TIME TRACKER Pin
ishan.bhatnagar25-Mar-10 4:08
ishan.bhatnagar25-Mar-10 4:08 
QuestionAutomatically release lock(hook) of mouse Pin
pushpraj_r11-May-08 19:53
pushpraj_r11-May-08 19:53 
Questiondebug assertion failed Pin
pushpraj_r28-Apr-08 23:09
pushpraj_r28-Apr-08 23:09 
GeneralExternal calling Pin
Alexander Gräf9-Sep-07 3:58
Alexander Gräf9-Sep-07 3:58 
GeneralA bug in the code Pin
Eli Golovinsky21-May-07 4:55
Eli Golovinsky21-May-07 4:55 
GeneralGetLastInputInfo API [modified] Pin
NadavK25-Jul-06 3:18
NadavK25-Jul-06 3:18 
General..but its only per session Pin
x-b15-Jul-08 22:25
x-b15-Jul-08 22:25 
QuestionHow to use in VFP ??? Pin
riphet28-Sep-05 8:17
riphet28-Sep-05 8:17 
AnswerRe: How to use in VFP ??? Pin
zyz9954626-Oct-06 20:25
zyz9954626-Oct-06 20:25 
GeneralMy problem solved.. Pin
edgarj25-Aug-05 12:04
edgarj25-Aug-05 12:04 
GeneralHook other application Pin
TonhTron24-Sep-04 7:21
TonhTron24-Sep-04 7:21 
GeneralLocked computer state prevents hooking!! Pin
caykahve9-Sep-04 0:08
caykahve9-Sep-04 0:08 
Generalsensor of the mouse Pin
dnqhung2-Jun-04 18:20
dnqhung2-Jun-04 18:20 
GeneralPerl Alternatives Pin
Anonymous23-Mar-04 21:54
Anonymous23-Mar-04 21:54 
GeneralTracker for single application Pin
HSriram2-Mar-04 17:53
HSriram2-Mar-04 17:53 
Generalincluding the dll Pin
RBlyth15-Feb-04 23:28
RBlyth15-Feb-04 23:28 
GeneralThe mouse thing... Pin
Lars-Inge Tønnessen16-Aug-03 10:09
Lars-Inge Tønnessen16-Aug-03 10:09 
Generaluse GetLastInputInfo() instead Pin
BovineOne9-Jul-03 8:39
BovineOne9-Jul-03 8:39 
GeneralRe: use GetLastInputInfo() instead Pin
Anonymous22-Mar-04 3:50
Anonymous22-Mar-04 3:50 
GeneralRe: use GetLastInputInfo() instead Pin
Anonymous4-Oct-04 7:04
Anonymous4-Oct-04 7:04 
GeneralFound problem & alternative Pin
sfwong4419-Jun-03 23:55
sfwong4419-Jun-03 23:55 
GeneralUsing dll in VB6 code Pin
mvording28-Apr-03 5:21
mvording28-Apr-03 5:21 

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.