How to get the true idle time of the system?

291 views Asked by At

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.

1

There are 1 answers

2
Marus Gradinaru On

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.