Global Input Tracking

I stumbled across this while attempting to find something for our Surface app, and while it didn’t work for us it would definitely be handy for any windows applications.

We were trying to find a easy way to keep track of the last input event, so that if no one touched the surface after a set amount of time, the application would go to a “screensaver/attract” mode. I found this code snippet, which works great against keyboard and mouse events:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static class User32Interop
{
 [DllImport("user32.dll", SetLastError = true)]
  static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);

  struct LASTINPUTINFO
  {
      public uint cbSize;
      public uint dwTime;
  }

  public static TimeSpan GetLastInput()
  {
      var plii = new LASTINPUTINFO();
      plii.cbSize = (uint)Marshal.SizeOf(plii);
      if (GetLastInputInfo(ref plii))
      {
          return TimeSpan.FromMilliseconds(Environment.TickCount - plii.dwTime);
      }
      else
      {
          throw new Win32Exception(Marshal.GetLastWin32Error());
      }
  }
}

Basically it hooks into Windows’ user32.dll and calls a method to get the last time a user event was fired. User events basically include any keyboard or mouse event you can think of. Using this code, I created a custom event with a timer that you can just hook into and execute whatever code you want in the event handler method (below is a random assortment of snippets from our app.xaml.cs file):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public partial class App: Application
{
        public delegate void ApplicationIdleHandler(object sender, EventArgs e);
        public event ApplicationIdleHandler ApplicationIdle;
        Timer checkIdleTimer;
        bool isIdle;
        //5 minutes
        TimeSpan idleThreshold = new TimeSpan(0, 5, 0);

        public App()
        {
            isIdle = false;
            checkIdleTimer = new Timer(new TimerCallback(checkIdleCallback), null, 0, 100);

            //this is how you'd subscribe to the event
            this.ApplicationIdle += new ApplicationIdleHandler(Application_Idle);
        }

        //excuse the boolean shorthand - damn wordpress
        private void checkIdleCallback(object sender)
        {
            if (!isIdle and User32Interop.GetLastInput() gt idleThreshold)
            {
                isIdle = true;
                //fire the event
                ApplicationIdle(this, new EventArgs());
            }
            else if (isIdle and User32Interop.GetLastInput() lt idleThreshold)
            {
                isIdle = false;
            }
        }

        //example event handler
        protected void Application_Idle(object sender, EventArgs e)
        {
            MessageBox.Show("Idle!");
        }
}

The only reason this didn’t work for us is the Contact events that the surface interaction is based on don’t actually go through user32.dll, they go through the separate SurfaceInput library.

Comments