Visual Basic Code of the Week (COTW)
http://www.codeoftheweek.com
Issue #103
Online Version at http://www.codeoftheweek.com/membersonly/bi/0103.html (paid subscribers only)
All content and source code is Copyright (c) 1999 by C&D Programming Corp. No part of this issue can be reprinted or distributed in any manner without express written permission of C&D Programming Corp.

Issue topic: Checking for keyboard/mouse inactivity - Part 2

Get paid to surf the web!

If you would like to get paid for surfing the web, jump to http://www.codeoftheweek.com/paidsurf.html

Six months of VB Training for only $49.99

Want to get up to speed on the latest Visual Basic programming? Includes Visual Basic 6 and Visual InterDev 6. Check out our training programs at http://www.codeoftheweek.com/vbtraining.html

Requirements

In this Issue

This issue expands on last weeks issue to further explain how the API calls perform their magic to monitor keyboard and mouse activity.

The only limitation we are aware of with this approach is that it can only monitor the keyboard and mouse activity within your application, not across all Windows applications.

A good use of this routine is to log a user out of an application after a specified period of inactivity.

If you have any questions about using this module, let us know at questions@codeoftheweek.com

Properties

Public Enum InactivityMode

This enumerator is used as a parameter to ActivityRef. Currently eMode can only be one of two values: imKeyboard or imMouse. imKeyboard will specify keyboard activity and imMouse will specify mouse activity.

Functions

Public Function ActivityRef(eMode As InactivityMode) As Date

Retrieves the last date/time activity was found for the device specified by eMode. Currently eMode can only be one of two values: imKeyboard or imMouse.

Public Function SetInactivityHook() As Boolean

Creates the hooks to monitor the keyboard and mouse activity. Returns True if successful and False if unsuccessful.

This routine calls several API calls, most importantly SetWindowsHookEx which allows your application to hook into the keyboard and mouse activity chain for monitoring. If you really wanted to you, could actually perform some processing of the activity (keystrokes would probably be more likely) other than just watching for those events to occur. When using this API call the keyword AddressOf is used to return the address of your callback routine for passing into SetWindowsHookEx. The callback routine must be in a standard module; it can not be in a form or a class module. For those of you unfamiliar with callbacks see the section below describing them.

It is critical that you release the hooks before your application ends (using ResetInactivityHook) or you will create some spectacular system crashes.

Methods

Public Sub SetParameters(o As cInactivtyWatch, lInactivityDelay As Long, lInactivityInterval As Long)

This provides the module with a way to know where to raise the event when inactivity occurs. Refer to last weeks issue for details on the cInactivityWatch class. This routine also saves the Delay amount and Interval amounts for later use (refer to last weeks issue for more details).

Public Sub ResetInactivityHook()

Releases the monitoring of the keyboard and mouse, which is the reverse of the process started with SetInactivityHook.

The API call UnhookWindowsHookEx is used to accomplish this task. It uses the handles received in SetInactivityHook to cancel the monitoring of the keyboard and mouse.

Returns

See above descriptions.

Notes

Some other interesting notes about this module:

About callbacks

For those of you that have not used callbacks before here is a brief description. When your application needs to watch for some event to occur there are primarily two ways to accomplish this.

The first is to create a loop in your code which repeatedly checks for some condition. This usually is not a desirable way to accomplish this tasks since it will eat up all the available CPU time while processing the code in your loop.

The second is to provide the address of a routine called a CALLBACK to the routine which will can determine the event you are watching for in an efficient manner (In our case it was the keyboard/mouse hook). When that routine gets the event it will make a call to your callback routine to let your code know that the event has occured. This is a very desirable way to perform this task because your code can continue on its way and it will be notified if and when the event you are watching for occurs.

The simplest form of a callback is probably the timer control in Visual Basic. When you put the timer control on your form the Timer event fires when appropriate. This is in essence a callback.

If anyone has a better description of callbacks, pass it along and we will pass it along to our readers. Send an email to callback@codeoftheweek.com

Sample Usage

The sample included at http://www.codeoftheweek.com/issues/issue102 shows how to use the above class. It provides a form that shows keyboard and mouse inactivity timers.

  ' See zip file at http://www.codeoftheweek.com/issues/issue102 for a detailed sample.

Source Code

To see an easy-to-use class wrapper around this module you can download the class module at http://www.codeoftheweek.com/issues/issue102

Create a new module and paste this source code into it. You should name this class module basInactivity. If you have any questions, email us at help@codeoftheweek.com

'----------------------------------------------------------------------
'
'   Module Name:    basInactivity
'   Written By:     C&D Programming Corp.
'   Create Date:    8/99
'   Copyright:      Copyright 1999 by C&D Programming Corp.  Source
'                   code may not be reproduced except for use in a
'                   compiled executable.  All rights reserved.  If
'                   you would like to reprint any or all of this
'                   code please email us at info@codeoftheweek.com
'----------------------------------------------------------------------
Option Explicit

' provide a link into the class to allow this module to raise an event when the
' inactivity time becomes true.
Private oInactivityWatch As cInactivtyWatch

' Windows hook stuff
Private Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long
Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Long) As Long
Private Declare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, ByVal nCode As Long, ByVal wParam As Integer, lParam As Any) As Long
Private Const WH_KEYBOARD = 2
Private Const WH_MOUSE = 7

' Timer API
Private Declare Function SetTimer Lib "user32" (ByVal hWnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Private Declare Function KillTimer Lib "user32" (ByVal hWnd As Long, ByVal nIDEvent As Long) As Long
'============================================================================

Private mlInactivityDelay As Long       ' in seconds
Private mlInactivityInterval As Long    ' how often to check for inactivity in seconds (we
                                        '   recommend this is 5 seconds or less)
Private mhHookMouse As Long             ' handle for mouse hook
Private mhHookKeyboard As Long          ' handle for keyboard hook
Private mlTimerID As Long               ' timer ID value
Private mbMouseActivity As Boolean      ' flag set by any mouse activity
Private mbKeyboardActivity As Boolean   ' flag set by any keyboard activity
Private dtMouseReference As Date        ' last time of mouse activity
Private dtKeyboardReference As Date     ' last time of keyboard activity

' some enumerators to make things easier.
Public Enum InactivityMode
    imKeyboard
    imMouse
End Enum

Public Function ActivityRef(eMode As InactivityMode) As Date
    If eMode = imKeyboard Then
        ActivityRef = dtKeyboardReference
    End If
    If eMode = imMouse Then
        ActivityRef = dtMouseReference
    End If
End Function

Public Sub SetParameters(o As cInactivtyWatch, lInactivityDelay As Long, lInactivityInterval As Long)
    Set oInactivityWatch = o
    mlInactivityDelay = lInactivityDelay
    mlInactivityInterval = lInactivityInterval
End Sub

Public Function SetInactivityHook() As Boolean
    On Error Goto Handler

    ' set mouse hook
    If mhHookMouse = 0& Then 'not already set
        mhHookMouse = SetWindowsHookEx(WH_MOUSE, AddressOf MouseProc, 0&, App.ThreadID)
    End If

    ' set keyboard hook
    If mhHookKeyboard = 0& Then 'not already set
        mhHookKeyboard = SetWindowsHookEx(WH_KEYBOARD, AddressOf KBProc, 0&, App.ThreadID)
    End If

    ' initialize some variables
    dtMouseReference = Now '
    dtKeyboardReference = Now
    mbKeyboardActivity = True 'initialize
    mbMouseActivity = True 'initialize
    If mlTimerID = 0& Then 'not already set
        ' take default of 5 seconds
        If mlInactivityInterval = 0 Then
            mlInactivityInterval = 5
        End If
        ' this is a built-in limit of the settimer function (actually it is probably
        ' 65535, but we figured 60 seconds would be adequate for this application)
        If mlInactivityInterval > 60 Then
            mlInactivityInterval = 60
        End If
        mlTimerID = SetTimer(0&, 0&, mlInactivityInterval * 1000, AddressOf TimerProc)
    End If

    SetInactivityHook = True
    Exit Function

Handler:
    SetInactivityHook = False
    Exit Function
End Function

Public Sub ResetInactivityHook()

    Dim lRet As Long

    If mlTimerID <> 0& Then
        KillTimer 0&, mlTimerID
        mlTimerID = 0&
    End If
    If mhHookMouse <> 0& Then 'not already unhooked
        lRet = UnhookWindowsHookEx(mhHookMouse)
        If lRet <> 0& Then mhHookMouse = 0&
    End If
    If mhHookKeyboard <> 0& Then 'not already unhooked
        lRet = UnhookWindowsHookEx(mhHookKeyboard)
        If lRet <> 0& Then mhHookKeyboard = 0&
    End If
End Sub

Private Function MouseProc(ByVal nCode As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    If nCode < 0 Then
        MouseProc = CallNextHookEx(mhHookMouse, nCode, wParam, lParam)
    End If
    mbMouseActivity = True
End Function

Private Function KBProc(ByVal nCode As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    If nCode < 0 Then
        KBProc = CallNextHookEx(mhHookKeyboard, nCode, wParam, lParam)
    End If
    mbKeyboardActivity = True
End Function

Private Function TimerProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal idEvent As Long, ByVal dwTime As Long) As Long

    ' if we had activity, update our time reference and continue.
    If mbKeyboardActivity Then
        dtKeyboardReference = Now 'reset reference
        mbKeyboardActivity = False
    End If
    If mbMouseActivity Then
        dtMouseReference = Now 'reset reference
        mbMouseActivity = False
    End If

    If Not mbMouseActivity And Not mbKeyboardActivity Then
        'check for no activity
        If (Abs(DateDiff("s", Now, dtMouseReference)) >= mlInactivityDelay) And _
            Abs(DateDiff("s", Now, dtKeyboardReference)) >= mlInactivityDelay Then
            oInactivityWatch.UserInactivity
            ' force counter to reset in case the user decides not to abort the
            ' exiting of this routine.  This should happen anyway since the user
            ' would have to hit a key or move the mouse to cancel the operation.
            mbKeyboardActivity = True
            mbMouseActivity = True
        End If
    End If
End Function

This document is available on the web

Paid subscribers can view this issue in HTML format. There is no additional source or information in the HTML formatted document. It just looks a little better since we have included some HTML formatting. Just point your browser to link at the top of this document.

Get paid to surf the web!

If you would like to get paid for surfing the web, jump to http://www.codeoftheweek.com/paidsurf.html

Other links

Contact Information

C&D Programming Corp.
PO Box 20128
Floral Park, NY 11002-0128
Phone or Fax: (212) 504-7945
Email: info@codeoftheweek.com
Web: http://www.codeoftheweek.com