Click here to Skip to main content
Click here to Skip to main content
Go to top

How to check for user inactivity with and without platform invokes in C#

, 22 Dec 2004
Rate this:
Please Sign up or sign in to vote.
Within the last month, two fellow programmers asked how to implement a timeout after a certain interval of inactivity. This article features four and a half ways of doing this.

Introduction

From time to time, someone comes up with the question 'How do I implement a timeout like that Show me as "Away" when I'm inactive feature in Windows Messenger?' One might think of other use cases for a feature like this but either way, it is an interesting question. And so I tried to find out how to do it right. The result is this article and this small collection of classes that should provide the long desired feature out of the box.

The Basics

There are basically two different approaches. First, you use a timer with the appropriate interval and reset it every time an event occurs. However, how to get your hands on the events is a little bit tricky and again there are several possibilities. Therefore, I created an interface and an abstract base class in order to be able to easily implement different ways to process mouse and keyboard events later. Second, you could still use a timer but this time with a short interval and check for console input every time the timer raises its Elapsed event. It is because of this different use of the timer that the class that uses polling is not derived from the base class. It implements the IInactivityMonitor interface directly.

The interface

The interface IInactivityMonitor consists of one method, five properties and two events. The method public void Reset() is used to reset the internal timer I mentioned before as well as some internal status information. The properties should be almost self-explanatory. public bool Enabled allows to determine whether an instance should raise events or not. public double Interval specifies the interval to wait for interaction. The only reason why the property is declared as double is because that's the type used for the Timer.Interval property of the Timer class. The properties public bool MonitorMouseEvents and public bool MonitorKeyboardEvents allow specifying if the instance should only monitor mouse events, keyboard events, or both. It is also possible to set both to false which has the same effect than setting Enabled to false. The last property is public ISynchronizeInvoke SynchronizingObject which allows providing a control for synchronization. If this property is null, events are not synchronized. If a synchronization object is provided, the execution of event handlers will be marshaled to the thread that owns the object. And finally, there are two events. event ElapsedEventHandler Elapsed is raised after an interval of inactivity. In addition, there is event EventHandler Reactivated which is raised when the user continues to interact with the monitored UI element after Elapsed has already been raised.

Using the interface

As the interface looks a lot like the interface of a timer, the usage is almost similar. The code snippet below is just an example. Of course, the correct call of the constructor depends on the concrete implementation. Regarding my own base class, the default value for MonitorKeyboardEvents and MonitorMouseEvents is true and the default value for Enabled is false. So, like with most timer classes, nothing will happen until Enabled is set to true.

private IInactivityMonitor inactivityMonitor = null;
inactivityMonitor = new ControlMonitor(this);
inactivityMonitor.SynchronizingObject = this;
inactivityMonitor.MonitorKeyboardEvents = false;
inactivityMonitor.MonitorMouseEvents = true;
inactivityMonitor.Interval = 600000;
inactivityMonitor.Elapsed += new ElapsedEventHandler(TimeElapsed);
inactivityMonitor.Reactivated += new EventHandler(Reactivated);
inactivityMonitor.Enabled = true;

The base class

The abstract base class MonitorBase implements the whole timer handling so that derived classes can focus on handling mouse and keyboard events. In conclusion, creating an instance of the timer is all that the constructor of the base class does.

protected MonitorBase()
{
  monitorTimer = new Timer();
  monitorTimer.AutoReset = false;
  monitorTimer.Elapsed += new ElapsedEventHandler(TimerElapsed);
}

Because derived classes might handle a lot of references as well as unmanaged handles, the interface IDisposable is implemented. The base deconstructor takes care of all previously registered event handlers and disposes the timer object.

protected virtual void Dispose(bool disposing)
{
  if (!disposed)
  {
    disposed = true;
    if (disposing)
    {
      Delegate[] delegateBuffer = null;

      monitorTimer.Elapsed -= new ElapsedEventHandler(TimerElapsed);
      monitorTimer.Dispose();

      delegateBuffer = Elapsed.GetInvocationList();
      foreach (ElapsedEventHandler item in delegateBuffer)
        Elapsed -= item;
      Elapsed = null;

      delegateBuffer = Reactivated.GetInvocationList();
      foreach (EventHandler item in delegateBuffer)
        Reactivated -= item;
      Reactivated = null;
    }
  }
}

The implementation of the properties is not really interesting. TimeElapsed and ReactivatedRaised are implemented as protected read-only properties as they are only needed for internal use in the derived classes. The other properties are declared as public virtual to allow overriding.

In case the object is enabled, Reset() resets the timer object by setting its Interval property to its current value. I admit, it looks strange, but it does exactly what it's supposed to do.

public virtual void Reset()
{
  if (disposed)
    throw new ObjectDisposedException("Object has already been disposed");

  if (enabled)
  {
    monitorTimer.Interval = monitorTimer.Interval;
    timeElapsed = false;
    reactivated = false;
  }
}

As derived classes can not invoke events declared in the base class, there are two protected methods to encapsulate the events. This is also because of the need to perform some additional checks to prevent unintended raising of events. Why is that? Well, the System.Timers.Timer used in the base class is a multithreaded timer. Therefore, it is possible that a timer thread calls the event handler although Enabled has been set to false (fortunately, this behavior is well documented).

protected void OnElapsed(ElapsedEventArgs e)
{
  timeElapsed = true;
  if (Elapsed != null && enabled && (monitorKeyboard || monitorMouse))
    Elapsed(this, e);
}

1. Monitoring mouse and keyboard events solely with managed code

Now that we've got a good basis, we can move on to the first derived class: ControlMonitor. This class was the result of the desire to use 100% managed code, no platform invokes, and not even managed methods that might make the class too platform dependent. Let's see how it works and what you can do with it.

public ControlMonitor(Control target) : base()
{
  if (target == null)
    throw new ArgumentException("Parameter target must not be null");
  targetControl = target;
  ControlAdded(this, new ControlEventArgs(targetControl));
  if (MonitorKeyboardEvents)
    RegisterKeyboardEvents(targetControl);
  if (MonitorMouseEvents)
    RegisterMouseEvents(targetControl);
}

The class has only one constructor which expects a Control object as its parameter (thus the name ControlMonitor). It calls up to three registration methods which are all based on the same principle. They first register an event handler for the control and then try to recursively register all child controls as well. For example, let's take a look at RegisterKeyboardEvents():

private void RegisterKeyboardEvents(Control c)
{
  c.KeyDown += new KeyEventHandler(KeyboardEventOccured);
  c.KeyUp   += new KeyEventHandler(KeyboardEventOccured);
  foreach (Control item in c.Controls)
    RegisterKeyboardEvents(item);
}

First, it registers event handlers for the KeyDown and the KeyUp events. After that, it uses a foreach loop and calls itself for every control in the Control.Controls collection. This ensures that our monitor object will be notified by every object in the hierarchy below the object that was passed to the constructor. Now, the question is does this really solve my problem?

Advantages

First, this approach has one big advantage. It does not use any platform specific functions or defines. Although I have not tested it, I think this class might have a good change to run on Mono without any modifications. This, however, might be the only advantage.

Disadvantages

As shown above, this class relies entirely on the Control class. This means that as soon as you've got an application with more than one form (which is most likely to be the normal case), you need to create one monitor object for every form. Also, there is this problem with message boxes. As showing a message box only involves calling a static method that is executed synchronously, you have absolutely no clue what happens when a message box is displayed. A rather weak workaround would be to generally consider 'message box time' as inactivity (or activity for that matter).

2. Using Application.AddMessageFilter to intercept messages

The Application class provides a convenient encapsulation to make subclassing easier. The method AddMessageFilter() can be used to register any object with an IMessageFilter interface as a message filter. Therefore, the ApplicationMonitor class is not only derived from MonitorBase but also implements IMessageFilter. This allows it to register itself rather than use a helper object. As the filter will be installed for the message queue of the thread in which AddMessageFilter() was called, the constructor does not need any parameters.

public ApplicationMonitor() : base()
{
  Application.AddMessageFilter(this);
}

The method PreFilterMessage() which is the only method define in the IMessageFilter interface is where we now can check for mouse and keyboard activity. If an incoming message indicates user input, we call ResetBase() to reset the timer in the base class. Although we are still using only managed code, this implementation already includes platform specific information because it needs to react on Windows messages with certain IDs.

public bool PreFilterMessage(ref Message m)
{
  if (MonitorKeyboardEvents)
    if (m.Msg == (int)Win32Message.WM_KEYDOWN ||
      m.Msg == (int)Win32Message.WM_KEYUP)
      ResetBase();
  if (MonitorMouseEvents)
    if (m.Msg == (int)Win32Message.WM_LBUTTONDOWN ||
      m.Msg == (int)Win32Message.WM_LBUTTONUP     ||
      m.Msg == (int)Win32Message.WM_MBUTTONDOWN   ||
      m.Msg == (int)Win32Message.WM_MBUTTONUP     ||
      m.Msg == (int)Win32Message.WM_RBUTTONDOWN   ||
      m.Msg == (int)Win32Message.WM_RBUTTONUP     ||
      m.Msg == (int)Win32Message.WM_MOUSEMOVE     ||
      m.Msg == (int)Win32Message.WM_MOUSEWHEEL)
      ResetBase();
  return false;
}

As you can see, PreFilterMessage() checks for the same events as the ControlMonitor class. However, if you expect that you now have a fully working application-level solution, I'll have to disappoint you. I also assumed that PreFilterMessage() would be called whenever a new message is dispatched to the thread's message queue before any other processing occurs. But for some (as it seems undocumented) reasons, there are two exceptions. The first one are message boxes. The message filter will not be called while a message box is displayed. Second, when you show a modal form by calling Form.ShowDialog(), you won't receive any messages which are related to the forms in the background (although a background form can't obtain the focus, there are still messages dispatched, for example, when you move the mouse cursor over the form).

Advantages

This solution is still not perfect but already features some improvements. Besides the problem with message boxes and modal forms, this class is able to track every user input on a whole thread. In addition, the code is much shorter as it does not need to register any event handlers. And of course, it still does not need to call any unmanaged code.

Disadvantages

Its major drawback is obviously that the class is not able to handle all messages of the thread because it simply does not receive all of them. In comparison with the ControlMonitor class, we gave up a lot of the platform independence as the messages which are processed by the message filter are 100% Windows specific.

3. Monitoring mouse and keyboard events with hooks

After discussing two solutions without any calls of unmanaged code, we will now take a look at the Win32 API. If one considers the use of platform invokes, he or she will certainly sooner or later think about hooks. According to the MSDN Library, Hooks are 'points in the system message-handling mechanism, where an application can install a subroutine to monitor the message traffic in the system and process certain types of messages before they reach the target window procedure'. With the functions SetWindowsHookEx(), UnhookWindowsHookEx() and CallNextHookEx(), we've got everything we need to intercept mouse and keyboard events (there is also a detailed MSDN Magazine article about hooks and the .NET Framework, titled Windows Hooks in the .NET Framework).

Let's take a look at the constructor of the HookMonitor class:

public HookMonitor(bool global) : base()
{
  globalHooks = global;
  if (MonitorKeyboardEvents)
    RegisterKeyboardHook(globalHooks);
  if (MonitorMouseEvents)
    RegisterMouseHook(globalHooks);
}

This constructor does not seem to be too different from the one used in the ControlMonitor class. However, if you take a close look at the private methods RegisterKeyboardHook and RegisterMouseHook, you'll find out that they use the SetWindowsHookEx() function mentioned above. So the following line registers the hook for the mouse events:

mouseHookHandle = User32.SetWindowsHookEx(
                         (int)Win32HookCodesEnum.WH_MOUSE, mouseHandler,
                         (IntPtr)0, AppDomain.GetCurrentThreadId());

mouseHookHandle is the handle returned by SetWindowsHookEx(), or zero if the call failed. WH_MOUSE is the type of handle we want to install and mouseHandler a delegate (or function pointer from Windows' point of view) of the callback function. The last parameter is the ID of the current thread. This means, an application with more than one thread with its own message pump would have to install a hook in all these threads. The setup for the keyboard hook looks just the same. The only difference is that the first parameter is WH_KEYBOARD and that the keyboard hook also uses its own callback function. The callback functions itself are quite minimalist. As we just want to know if there is any activity on the thread (but we don't mind what kind of activity), the function checks if nCode is zero or larger. In that case, the message is supposed to be handled by the hook function, so it calls ResetBase(). Finally, it calls CallNextHookEx() to pass the message to the next function in the chain.

private int MouseHook(int nCode, IntPtr wParam, IntPtr lParam)
{
  if (nCode >= 0)
    ResetBase();
  return User32.CallNextHookEx(mouseHookHandle, nCode, wParam, lParam);
}

Advantages

In order to reach our goal with just a few lines of extra code, this approach is really great. Not only do we not have to worry about the controls in the application, we are actually independent from them and also receive every message which is dispatched to our thread. The thread in which the class is constructed needs a message pump and that's all.

Disadvantages

The drawbacks are almost all the advantages of the ControlMonitor class as this solution makes intense use of platform invokes. This means that even a port to 64-bit Windows might require some changes.

4. Monitoring activity with GetLastInputInfo()

Windows hooks allowed us to view all incoming messages of a thread no matter which window has the focus or whether it is a modal form or not. But what if we want to monitor the activity on the whole system? The easiest solution for this problem is a Win32 API function called GetLastInputInfo(). However, there is one major drawback. While the previous three approaches generally allow analyzing the user activity, this function only informs us about the point in time of the last user input. After calling it, we might know that something happened but we don't have a clue what. Although this is still enough to generally solve our problem, it renders the distinction between mouse and keyboard events useless. Therefore, the LastInputMonitor class will monitor any activity as long as at least one of the two properties MonitorMouseEvents and MonitorKeyboardEvents is set to true. As GetLastInputInfo() is a synchronous function, the LastInputMonitor class uses polling to periodically check for activity. This is the reason why it is not derived from the base class MonitorBase. However, it uses an instance of the same timer class with the default interval of 100 milliseconds.

public LastInputMonitor()
{
  lastInputBuffer.cbSize = (uint)Win32LastInputInfo.SizeOf;
  pollingTimer = new Timer();
  pollingTimer.AutoReset = true;
  pollingTimer.Elapsed += new ElapsedEventHandler(TimerElapsed);
}

Without the help of a base class, the LastInputMonitor has the whole logic for event evaluation and event raising built in. The core of the class is the method TimerElapsed() which handles the Elapsed event of the timer object.

private void TimerElapsed(object sender, ElapsedEventArgs e)
{
  if (pollingTimer.SynchronizingObject != null)
    if (pollingTimer.SynchronizingObject.InvokeRequired)
    {
      pollingTimer.SynchronizingObject.BeginInvoke(
        new ElapsedEventHandler(TimerElapsed),
        new object[] {sender, e});
      return;
    }
  if (User32.GetLastInputInfo(out lastInputBuffer))
  {
    if (lastInputBuffer.dwTime != lastTickCount)
    {
      if (timeElapsed && !reactivated)
      {
        OnReactivated(new EventArgs());
        Reset();
      }
      lastTickCount = lastInputBuffer.dwTime;
      lastInputDate = DateTime.Now;
    }
    else if (!timeElapsed && (monitorMouse || monitorKeyboard))
      if (DateTime.Now.Subtract(lastInputDate).TotalMilliseconds > interval)
        OnElapsed(e);
  }
}

After checking if cross-thread marshaling is needed, the method calls GetLastInputInfo(). If it detects the first activity after IInactivityMonitor.Elapsed has been raised, it raises the Reactivated event. Otherwise, it updates the tick count information and the timestamp lastInputDate. If there is no activity for the time span specified by interval, it raises the Elapsed event.

Advantages

This class allows detecting inactivity on a system-wide level. Our problem has been solved completely (at least if you don't need to only monitor mouse or keyboard events).

Disadvantages

Well, I already wrote it twice: you can't distinguish between mouse and keyboard events. However, I guess that it won't bother most of the people. Apart from that, this approach uses polling which I think should be avoided if possible though the documentation explicitly refers to the possibility to use GetLastInputInfo() for input idle detection. And finally, this function is only available on Windows 2000 and higher.

4 1/2. Monitoring global events

I promised to show four and a half ways of implementing a timeout class. The last approach solves all our problems but does not help us to distinguish between mouse and keyboard events. The usage of the WH_MOUSE and WH_KEYBOARD hooks solved the problem on the application level, but isn't there something like global hooks to intercept all messages on a system? The short answer is yes. However, global hooks in .NET are a problem because they usually use code injection, and injecting managed code in unmanaged processes is no good idea. The popular workaround is to use a native Win32 DLL to achieve this (for details, see Michael Kennedy's article: Global System Hooks in .NET). However, George Mamaladze found out that there are two low-level hooks that don't need code injection and therefore can be used in a managed environment (see Processing Global Mouse and Keyboard Hooks in C#). And this is also the reason why my last suggestion is only a half solution.

As you probably noticed, the constructor of the HookMonitor class requires a bool as its parameter. If it's false, the class installs thread hooks as described above. However, if it is true, RegisterMouseHook() and RegisterKeyboardHook() will install the global hooks WH_MOUSE_LL and WH_KEYBOARD_LL. This results in a different call of SetWindowsHookEx():

keyboardHookHandle = User32.SetWindowsHookEx(
  (int)Win32HookCodesEnum.WH_KEYBOARD_LL,
  keyboardHandler,
  Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]),
  (int)0);

The thread ID is no longer needed. Instead, the function expects a handle of the library which contains the callback function. In our case, this is the current assembly.

Conclusion

Although I have to admit that I don't like the idea very much, using hooks seems to be the best way to achieve a timeout feature (at least at the moment). When future releases of Windows and the .NET Framework allow a more convenient solution, it should be easy to derive a new class from MonitorBase and use that instead of the HookMonitor. I hope you'll find the classes provided with this article useful, and let me know if there are any bugs left or you've got some ideas how to improve the code.

Change history

  • 2004-12-22: Corrected bug in the ControlMonitor class (the Control.MouseWheel event was not handled), renamed the enumeration with the hook defines (non-functional change), and included classes and paragraphs to demonstrate the use of the IMessageFilter interface and the Win32 API function GetLastInputInfo().
  • 2004-12-19: Initial release.

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

Share

About the Author

Dennis C. Dietrich [MSFT]
Tester / Quality Assurance Microsoft Corporation
United States United States
No Biography provided

Comments and Discussions

 
GeneralUsing LastInputMonitor with WPF Pinmemberbrunzefb21-Oct-09 8:06 

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

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

| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 22 Dec 2004
Article Copyright 2004 by Dennis C. Dietrich [MSFT]
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid