5

We use GetLastInputInfo and calculate the difference from the result of GetTickCount64 to determine idleness. However, the tick count returned by GetLastInputInfo is written into a the member dwTime of LASTINPUTINFO, which is a DWORD and therefore an unsigned 32 bit integer, with a maximum value of and its maximum value of 4294967295, which is equivalent to ~49 days. On machines with uptimes longer than that, this of course leads to the function reporting an incorrect value.

Apparently there is no function called GetLastInputInfo64, but maybe a function under a different name?

Mephane
  • 1,984
  • 11
  • 18
  • 2
    Use `GetTickCount` instead, because it will reset in sync with `dwTime`? And make sure you [handle the overflows properly](https://blogs.msdn.microsoft.com/oldnewthing/20050531-22/?p=35493/). – GSerg Sep 27 '18 at 06:55
  • 3
    To answer the question asked -- no, there is no 64bit version, or equivalent. – Remy Lebeau Sep 27 '18 at 06:57
  • 1
    In which parallel world you see the possibility of no input for ~50 years? – Ajay Sep 27 '18 at 10:11
  • 2
    @Ajay that is not the point; this is about the roll-over of the counter after 49 days of Windows uptime. GetLastInputInfo gives you the absolute tick count of when the last user input happened, not the time delta since then. This tick count rolls over after 49 days, which the tick count of GetTickCount64 does not. – Mephane Sep 28 '18 at 20:29

2 Answers2

3

There is a workaround that we use.

Only use the result of GetLastInputInfo() to detect if the dwTick has changed without caring how much it changed.

Do this periodically.

When the tick has changed (the value is not the same as it was the previous reading) start your own timer and use that to detect when the input has became idle for for instance 30 seconds.

You will never need to deal with wraparounds using this approach.

Anders Lindén
  • 6,839
  • 11
  • 56
  • 109
0

@Anders:

public class IdleDetection
{
    private struct LASTINPUTINFO
    {
        public uint cbSize;
        public uint dwTime;
    }

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

    private readonly Timer _idleTimer;
    private double _idleSeconds = 0;
    private uint _previousLastInput = 0;
    private DateTime _idleStart = DateTime.Now;
    private object _lock = new object();

    public IdleDetection()
    {
        _idleTimer = new Timer(TimerCallback, null, 1000, Timeout.Infinite);
    }

    public TimeSpan IdleTime
    {
        get {
            lock (_lock)
            {
                return TimeSpan.FromSeconds(_idleSeconds);
            }
        }
    }

    private void TimerCallback(object state)
    {
        lock (_lock)
        {
            var lastInput = GetLastInputInfoValue();

            if (lastInput == _previousLastInput)
            {
                _idleSeconds = (DateTime.Now - _idleStart).TotalSeconds;
            }
            else
            {
                _idleSeconds = 0;
                _idleStart = DateTime.Now;
            }

            _previousLastInput = lastInput;
        }
        _idleTimer.Change(1000, Timeout.Infinite);
    }

    private static uint GetLastInputInfoValue()
    {
        LASTINPUTINFO lastInPut = new LASTINPUTINFO();
        lastInPut.cbSize = (uint)Marshal.SizeOf(lastInPut);
        GetLastInputInfo(ref lastInPut);
        return lastInPut.dwTime;
    }
}