I solved the problem using a few different techniques rolled up to give me a pretty good solution. I use GetLastInput to work out when the system was last touched This is well documented elsewhere, but here's my method:
public static class User32Interop
{
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 System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}
[DllImport("user32.dll", SetLastError = true)]
static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
struct LASTINPUTINFO {
public uint cbSize;
public uint dwTime;
}
}
This only tells me when the system has been idle, not the application. If the user clicks into Word and works there for an hour, I still want a timeout. To handle this case, I simply remember when my application loses focus by overriding the OnDeactivated and OnActivated methods on the application object:
override protected void OnDeactivated(EventArgs e)
{
this._lostFocusTime = DateTime.Now;
base.OnDeactivated(e);
}
protected override void OnActivated(EventArgs e)
{
this._lostFocusTime = null;
base.OnActivated(e);
}
My IsIdle routine was added to the application object. It handles the global case where the app has focus but nothing happened (IsMachineIdle) and the specific case where the application lost focus while the user is doing other stuff (isAppIdle ):
public bool IsIdle
{
get
{
TimeSpan activityThreshold = TimeSpan.FromMinutes(1);
TimeSpan machineIdle = Support.User32Interop.GetLastInput();
TimeSpan? appIdle = this._lostFocusTime == null ? null : (TimeSpan?)DateTime.Now.Subtract(_lostFocusTime.Value);
bool isMachineIdle = machineIdle > activityThreshold ;
bool isAppIdle = appIdle != null && appIdle > activityThreshold ;
return isMachineIdle || isAppIdle;
}
}
The last thing I did was create a timer loop that polled this flag event few seconds.
This seems to work fine.