I maintain a small tool to track my own activities through the day. For this, I wanted a feature to automatically log when I was "idle". So I'd know how long "that meeting" actually took.

Here's how I did that.

The Code

[DllImport("user32.dll")]
static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);

[StructLayout(LayoutKind.Sequential)]
struct LASTINPUTINFO
{
   public static readonly int SizeOf = Marshal.SizeOf(typeof(LASTINPUTINFO));

   [MarshalAs(UnmanagedType.U4)]
   public UInt32 cbSize;
   [MarshalAs(UnmanagedType.U4)]
   public UInt32 dwTime;
}

private TimeSpan RetrieveIdleTime()
{
    LASTINPUTINFO lastInputInfo = new LASTINPUTINFO();
    lastInputInfo.cbSize = (uint)LASTINPUTINFO.SizeOf;
    GetLastInputInfo(ref lastInputInfo);
        
    int elapsedTicks = Environment.TickCount - (int)lastInputInfo.dwTime;

    if (elapsedTicks > 0) { return new TimeSpan(0, 0, 0, 0, elapsedTicks); }
    else { return new TimeSpan(0); }
}

Be careful with "long" uptimes larger than 24 days (see further).

Details

To detect if a user has been idle, you'll have to make a call to the native win 32 function called GetLastInputInfo.

It will give you the tick count at the last user input. Careful, that's not "the amount of ticks the user has been idle". You'll have to compare against the current tickcount to get to that value.

First thing to do is import the function:

[DllImport("user32.dll")]
static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);

The LASTINPUTINFO struct will have to be defined as well:

[StructLayout(LayoutKind.Sequential)]
struct LASTINPUTINFO
{
    public static readonly int SizeOf = Marshal.SizeOf(typeof(LASTINPUTINFO));

    [MarshalAs(UnmanagedType.U4)]
    public UInt32 cbSize;
    [MarshalAs(UnmanagedType.U4)]
    public UInt32 dwTime;
}

And then you'll need some boilerplate code to actually fill up that structure:

LASTINPUTINFO lastInputInfo = new LASTINPUTINFO();
lastInputInfo.cbSize = (uint)LASTINPUTINFO.SizeOf;
GetLastInputInfo(ref lastInputInfo);

int elapsedTicks = Environment.TickCount - (int)lastInputInfo.dwTime;
TimeSpan idleTime = new TimeSpan(0, 0, 0, 0, elapsedTicks);

Don't forget to manually set the cbSize value.

The definition of LASTINPUTINFO comes straight from "pinvoke.net", a great website. However, it's always good to know what you're doing.

The [StructLayout(LayoutKind.Sequential)] attribute says that the information must be laid out in memory just as it appears. This goes for the object both in managed and unmanaged memory. This is because the win32 native functions depend on the layout of the struct in memory, being native functions (please correct me if I'm wrong here).

Marshal.SizeOf returns the size of the unmanaged type in memory. We can then use that value to set the cbSize property.

Also, both cbSize and dwTime are strictly defined as unsigned, 32-bit integers. That's also what the MarshalAs(UnmanagedType.U4) attribute indicates.

Uptime Issue

One thing to think about: the "tickcount" in windows is the amount of milliseconds since the system started. We're dealing with an unsigned 32-bit integer, which means it can represent up to 50 days before it wraps around.

Normally you work around this by calling GetTickCount64, but there doesn't seem to be anything for GetLastInputInfo. In any case, if you're converting to a TimeSpan (which takes Ticks as a signed 32 bit integer), you'll have trouble after about 24 days.

That is to say, the above will fail if the system is up for a long time.

Does anyone know a way around this?