1

I need to get the system idle time when an event is triggered in my application. I used the GetLastInputInfo() function to get it, but it seems that after 5 minutes and a few seconds of inactivity, my idle counter is restarted back to zero, without me touching the mouse or keyboard. My screensaver is deactivated, and also all of the standby and sleep settings of the system and display!

What can be the cause?

I used this code to test the problem:

procedure TMsgForm.Timer1Timer(Sender: TObject);
var mins, secs, T0, T1, TD: Cardinal;
    LastInput: TLastInputInfo;
begin
  LastInput.cbSize:=SizeOf(TLastInputInfo);
  if not GetLastInputInfo(LastInput) then Caption := 'error'
  else begin
    T0 := GetTickCount;
    T1 := LastInput.dwTime;
    TD := T0 - T1;
    secs := TD div 1000;
    mins := secs div 60;
    Caption := 'T0='+IntToStr(T0)+'  T1='+IntToStr(T1)+'  TD='+IntToStr(TD)+'  secs='+IntToStr(secs)+'  mins='+IntToStr(mins);
  end;
end;

UPDATE:

I closed all programs and then waited again 5 minutes, and the same thing happens. I thought that some program I installed may be simulating a user input or something, and so resets the idle counter. But it doesn't. There must be something from the system that does it?

I also tried on another computer and it works fine. It is not reset after 5 minutes.


UPDATE

It is strange... hooking the keyboard and mouse, I don't receive anything at the moment the standard idle counter is reset. So, the only logical conclusion is that the counter is restarted from other sources.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Marus Gradinaru
  • 2,824
  • 1
  • 26
  • 55
  • The second paragraph of Remarks for [GetLastInputInfo function (winuser.h)](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getlastinputinfo) and using Cardinal means your `TD:= T0 - T1;` will set TD to zero whenever T1 is >= T0. As for what is waking the screen you can try [SleepStudy](https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/modern-standby-sleepstudy) which is part of PowerConfig in Windows 8.1 and latter. – Brian Jun 12 '23 at 12:35
  • As you can see, my code displays T0 and T1 separately, and from what I saw in those 5 minutes, T1 is never greater than T0. But after 5 min and 14 secs the T1 is equal to T0 and then remains the same event anoter 5 minutes are passed. – Marus Gradinaru Jun 12 '23 at 13:17

1 Answers1

0

I managed to get the real idle time by installing keyboard and mouse hooks, which has a flag that tells if the source of the trigger was real or not. It works, but its not a nice implementation. I can't believe that Microsoft hasn't thought about implementing a system idle timer for REAL events, like GetLastInputInfo(LastInput).

I don't know if there are other inputs sources other then keyboard and mouse... If anyone can help me improve this procedure, it is welcome.

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Timer1: TTimer;
    Panel2: TPanel;
    procedure Timer1Timer(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  TrueLastInput: DWORD;

implementation

{$R *.dfm}

const
  LLKHF_INJECTED = $00000010;
  LLKHF_LOWER_IL_INJECTED = $00000002;
  LLMHF_INJECTED = $00000001;
  LLMHF_LOWER_IL_INJECTED = $00000002;

type
  TLowLevelInputProc = function(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;

  PKBDLLHOOKSTRUCT = ^TKBDLLHOOKSTRUCT;
  TKBDLLHOOKSTRUCT = record
    vkCode: DWORD;
    scanCode: DWORD;
    flags: DWORD;
    time: DWORD;
    dwExtraInfo: ULONG_PTR;
  end;

  PMOUSEHOOKSTRUCT = ^TMOUSEHOOKSTRUCT;
  TMOUSEHOOKSTRUCT = record
    pt: TPoint;
    mouseData: DWORD;
    flags: DWORD;
    time: DWORD;
    dwExtraInfo: ULONG_PTR;
  end;

var
  KHook, MHook: HHOOK;

function KeyboardProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var Info: PKBDLLHOOKSTRUCT;
begin
  if nCode >= 0 then begin
    Info:= PKBDLLHOOKSTRUCT(lParam);
    if (Info.flags and (LLKHF_INJECTED or LLKHF_LOWER_IL_INJECTED)) = 0 then
      TrueLastInput:= Info.time;
  end;
  Result:= CallNextHookEx(KHook, nCode, wParam, lParam);
end;

function MouseProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var Info: PMOUSEHOOKSTRUCT;
begin
  if nCode >= 0 then begin
    Info:= PMOUSEHOOKSTRUCT(lParam);
    if (Info.flags and (LLMHF_INJECTED or LLMHF_LOWER_IL_INJECTED)) = 0 then
      TrueLastInput:= Info.time;
  end;
  Result:= CallNextHookEx(MHook, nCode, wParam, lParam);
end;

procedure InstallInputHooks;
var LLKeyboardProc, LLMouseProc: TLowLevelInputProc;
begin
  LLKeyboardProc:= @KeyboardProc;
  KHook:= SetWindowsHookEx(WH_KEYBOARD_LL, @LLKeyboardProc, HInstance, 0);
  LLMouseProc:= @MouseProc;
  MHook:= SetWindowsHookEx(WH_MOUSE_LL, @LLMouseProc, HInstance, 0);
end;

procedure UninstallInputHooks;
begin
  if KHook <> 0 then begin UnhookWindowsHookEx(KHook); KHook:= 0; end;
  if MHook <> 0 then begin UnhookWindowsHookEx(MHook); MHook:= 0; end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
 InstallInputHooks;
 TrueLastInput:= GetTickCount;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
 UninstallInputHooks;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var T0, TD, Tsecs, Tmins: DWORD;
begin
  T0:= GetTickCount;
  TD:= T0 - TrueLastInput;
  Tsecs:= TD div 1000;
  Tmins:= Tsecs div 60;
  panel2.caption := 'T0='+IntToStr(T0)+'  T1='+IntToStr(TrueLastInput)+'  TD='+IntToStr(TD)+'  secs='+IntToStr(Tsecs)+'  mins='+IntToStr(Tmins);
end;

end.
Marus Gradinaru
  • 2,824
  • 1
  • 26
  • 55
  • `GetLastInputInfo()` works just fine in general, as you already proved with your working computer. I've implemented idle timers with it that last far longer than 5 minutes. Whatever you are experiencing, it is something on your misbehaving computer only. – Remy Lebeau Jun 12 '23 at 22:30
  • Yes, but if it happened on my system, it can happen to anyone. A true Idle Timer must only take into account real input sources. It seems that `GetLastInputInfo' is also triggered by other unknown sources... – Marus Gradinaru Jun 13 '23 at 12:46