Foxpert Software Development & Consulting

Menu

Whitepapers
Downloads
Knowlbits
Guineu

2009-03Mar-13

Detecting idle users

Updated 2009-04Apr-10

Many thanks to John L Veazey, Simon White, and Geof Shugar for pointing out that my original code turns the mouse pointer into an hour glass most of the time, and for mentioning some unneeded code lines. The updated version should not show this behaviour, at all. John also provided a different approach that I'll post shortly.

It's not too uncommon in business application development to face the requirement to log out users automatically after a period of time. Preventing access to the application by unauthorized users is frequently quoted as a reason for such a feature. This purpose, however, should better be handled on the corporate level. A policy that locks a computer after a certain period of time is more secure, more reliable and better way to prevent access.

Nonetheless, some users let applications run all night long. This is often facilitated by company policies that require the user to keep the computer running as backup and maintenance tasks are carried out during the night.

While an application that is connected to SQL server has little impact on SQL server's ability for maintenance issues, the same does not hold true for Visual FoxPro. Reindexing, packing, backing up, all requires exclusive access to the files.

Detecting activity used to be a tedious development issue. In most scenarios activity equals to moving the mouse and pressing keys. Up to VFP 8.0 the only solution was to insert code into the KeyPress and the MouseMove event of every single base class. Especially the KeyPress event does react differently when there's code in the event. Consequently, adding this feature at a later stage required a lot of testing. When third-party libraries or ActiveX controls had been used, this was even more difficult as they required special coding.

Visual FoxPro 9.0 introduced a great feature: BINDEVENT can bind to API messages. There are two ways to bind to messages. You can specify a single window and only receive messages that go to that specific window, or you can specify 0 as the window handle and automatically receive message for every FoxPro window.

The following program implements a timer that keeps track of the user activities. When the user has been idle for a specified number of minutes, the eventTimeout event is triggered. Here you can respond by logging the user out, shutting the application, or just giving a warning and increasing the timeout:

*============================================================
* Detects user activity and fires an event after the
* specified period of inactivity.
*============================================================
Define Class InactivityTimer as Timer

  *----------------------------------------------------------
  * API constants
  *----------------------------------------------------------
  #define WM_KEYUP                        0x0101
  #define WM_SYSKEYUP                     0x0105
  #define WM_MOUSEMOVE                    0x0200
  #define GWL_WNDPROC         (-4)

  *----------------------------------------------------------
  * internal properties
  *----------------------------------------------------------
  nTimeOutInMinutes = 0
  tLastActivity = {/:}
  nOldProc = 0

  *----------------------------------------------------------
  * Timer configuration
  *----------------------------------------------------------
  Interval = 30000
  Enabled = .T.

*------------------------------------------------------------
* Listen to API events when the form starts. You can pass
* the timeout as a parameter.
*------------------------------------------------------------
Procedure Init(tnTimeOutInMinutes)
    DECLARE integer GetWindowLong IN WIN32API ;
      integer hWnd, ;
      integer nIndex
    DECLARE integer CallWindowProc IN WIN32API ;
      integer lpPrevWndFunc, ;
      integer hWnd,integer Msg,;
      integer wParam,;
      integer lParam
    THIS.nOldProc=GetWindowLong(_VFP.HWnd,GWL_WNDPROC)
  If Vartype(m.tnTimeOutInMinutes) == "N"
    This.nTimeOutInMinutes = m.tnTimeOutInMinutes
  EndIf
  This.tLastActivity = Datetime()
  BindEvent(0,WM_KEYUP,This,"WndProc")
  BindEvent(0,WM_MOUSEMOVE,This,"WndProc")
EndProc

*------------------------------------------------------------
* Stop listening
*------------------------------------------------------------
Procedure Unload
  UnBindEvents(0,WM_KEYUP)
  UnBindEvents(0,WM_MOUSEMOVE)
EndProc

*------------------------------------------------------------
* Every event counts as activity
*------------------------------------------------------------
Procedure WndProc( ;
  hWnd as Long,Msg as Long,wParam as Long,lParam as Long )
  This.tLastActivity = Datetime()
_screen.Caption = Str(Val(_Screen.Caption)+1)
Return CallWindowProc(this.noldproc,hWnd,msg,wParam,lParam)

*------------------------------------------------------------
* Check last activity against time out
*------------------------------------------------------------
Procedure Timer
  Local ltFireEvent
  ltFireEvent = This.tLastActivity + 60*This.nTimeOutInMinutes
  If Datetime() > m.ltFireEvent
    This.eventTimeout()
  EndIf
EndProc

*------------------------------------------------------------
* Override this event or bind to it to respond to user
* inactivity. You can change the nTimeOutInMinutes to offer
* multiple stages of timeouts.
*------------------------------------------------------------
Procedure eventTimeout

EndDefine

To use the timer you can either create a sub class or bind to the eventTimeout using BINDEVENTS(). In any case, the instance must be available while the application is running, whether that's as a global variable, as a property in an application object, or by any other mean. For trying this class you can run the following sample code

Public goForm
goForm = CreateObject("InactivityDemo")

Define Class InactivityDemo as InactivityTimer
  Procedure Init
    DoDefault(1)
  Procedure eventTimeout
    MessageBox("Timeout!")
EndDefine

If you run this code and then stop working in Visual FoxPro for between one and one and a half minutes, you get a message box every 30 seconds until you stop the demo with CLEAR ALL.

There are a few situations where the inactivity timer is not updated. Long running statements like SELECT or COPY TO commands don't trigger any event handler. Moving the mouse only in the header area isn't captured by VFP. I also suspect that activity inside an ActiveX control is not reliable detected, either.

Previous KnowlBits

RSS

February 2011 (2)

December 2010 (1)

October 2009 (2)

September 2009 (1)

August 2009 (4)

July 2009 (2)

June 2009 (2)

May 2009 (1)

April 2009 (1)

March 2009 (1)

August 2008 (1)

July 2008 (2)

May 2008 (1)

April 2008 (2)

January 2008 (2)

December 2007 (2)

November 2007 (2)

October 2007 (1)

September 2007 (1)

August 2007 (5)

July 2007 (4)

May 2007 (6)

March 2007 (3)

February 2007 (7)

January 2007 (6)

November 2006 (1)

October 2006 (3)

September 2006 (10)

June 2006 (2)

May 2006 (6)

April 2006 (1)


Impressum Kontakt Contact