From 36ffee8ea38e8132f202d7fdd44392cb7a086b04 Mon Sep 17 00:00:00 2001 From: Ratin Gao Date: Mon, 9 Mar 2026 01:58:37 +0800 Subject: [PATCH] [WIN32SS:NTUSER] Implement `SharedUserData->LastSystemRITEventTickCount` (#8537) This field was introduced in NT5.1, records the tick count of the last user input (system-wide), updated at most once per minute (or once per second since NT6.0). Unlike `GetLastInputInfo` which is designed for providing session-specific user input information, `SharedUserData->LastSystemRITEventTickCount` provides system-wide time, used by: - [Task Schedule (TASK_EVENT_TRIGGER_ON_IDLE)](https://learn.microsoft.com/en-us/windows/win32/taskschd/i) - [Inactivity Monitoring](https://learn.microsoft.com/en-us/windows/win32/devnotes/inactivity-monitoring) - Maybe some places I don't know... See also: - [KUSER_SHARED_DATA - Geoff Chappell](https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm) - [TASK_TRIGGER_TYPE enumeration - Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/api/mstask/ne-mstask-task_trigger_type) - [Task Scheduler: idle conditions](https://learn.microsoft.com/en-us/windows/win32/taskschd/i) - [GetLastInputInfo function - Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getlastinputinfo) - [Inactivity Monitoring](https://learn.microsoft.com/en-us/windows/win32/devnotes/inactivity-monitoring) ## Proposed changes Before this PR, `SharedUserData->LastSystemRITEventTickCount` is never used and always 0, `gpsi->dwLastSystemRITEventTickCountUpdate` is never used too. After this PR, `SharedUserData->LastSystemRITEventTickCount` updates correctly by using `gpsi->dwLastSystemRITEventTickCountUpdate` to record previous update time, the behavior is the same as on Windows. --- modules/rostests/win32/user32/CMakeLists.txt | 1 + .../win32/user32/useridletime/CMakeLists.txt | 5 ++ .../win32/user32/useridletime/UserIdleTime.c | 47 +++++++++++++++++++ win32ss/user/ntuser/input.c | 18 ++++++- 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 modules/rostests/win32/user32/useridletime/CMakeLists.txt create mode 100644 modules/rostests/win32/user32/useridletime/UserIdleTime.c diff --git a/modules/rostests/win32/user32/CMakeLists.txt b/modules/rostests/win32/user32/CMakeLists.txt index 44e44fb78a9..757220d2aa3 100644 --- a/modules/rostests/win32/user32/CMakeLists.txt +++ b/modules/rostests/win32/user32/CMakeLists.txt @@ -3,4 +3,5 @@ add_subdirectory(messagebox) add_subdirectory(paintdesktop) add_subdirectory(psmtest) add_subdirectory(sysicon) +add_subdirectory(useridletime) add_subdirectory(winstation) diff --git a/modules/rostests/win32/user32/useridletime/CMakeLists.txt b/modules/rostests/win32/user32/useridletime/CMakeLists.txt new file mode 100644 index 00000000000..ddc55191048 --- /dev/null +++ b/modules/rostests/win32/user32/useridletime/CMakeLists.txt @@ -0,0 +1,5 @@ + +add_executable(useridletime UserIdleTime.c) +set_module_type(useridletime win32cui UNICODE) +add_importlibs(useridletime user32 msvcrt kernel32) +add_rostests_file(TARGET useridletime SUBDIR suppl) diff --git a/modules/rostests/win32/user32/useridletime/UserIdleTime.c b/modules/rostests/win32/user32/useridletime/UserIdleTime.c new file mode 100644 index 00000000000..4d9ef294242 --- /dev/null +++ b/modules/rostests/win32/user32/useridletime/UserIdleTime.c @@ -0,0 +1,47 @@ +/* + * PROJECT: ReactOS Tests + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Calculate and print user idle time once per second by using + * user32!GetLastInputInfo() API and SharedUserData->LastSystemRITEventTickCount + * field separately. The former updates in real-time, while the latter updates + * periodically (60s in NT5, 1s since NT6.0). Press Ctrl+C to exit this program. + * COPYRIGHT: Copyright 2026 Ratin Gao + */ + +#include + +#define WIN32_NO_STATUS +#include +#include +#include + +#define NTOS_MODE_USER +#include + +int +_cdecl +wmain( + _In_ int argc, + _In_reads_(argc) _Pre_z_ wchar_t** argv) +{ + DWORD dwTickCount, dwError; + LASTINPUTINFO lii = { sizeof(lii) }; + + puts("User idle time in second (LastSystemRITEventTickCount, GetLastInputInfo):"); + for (;;) + { + dwTickCount = GetTickCount(); + if (!GetLastInputInfo(&lii)) + { + dwError = GetLastError(); + printf("GetLastInputInfo failed with: 0x%08lX\n", dwError); + return dwError; + } + printf("\t%lu, %lu\n", + (dwTickCount - SharedUserData->LastSystemRITEventTickCount) / 1000UL, + (dwTickCount - lii.dwTime) / 1000UL); + Sleep(1000); + } + + return 0; +} diff --git a/win32ss/user/ntuser/input.c b/win32ss/user/ntuser/input.c index a866ac51bf9..959bb5ba8ec 100644 --- a/win32ss/user/ntuser/input.c +++ b/win32ss/user/ntuser/input.c @@ -10,6 +10,13 @@ #include DBG_DEFAULT_CHANNEL(UserInput); +/* LastRITEventTickCount update cycle has been adjusted from 60s to 1s since NT6 */ +#if (NTDDI_VERSION < NTDDI_VISTA) +#define LAST_RIT_EVENT_UPDATE_INTERVAL 60000UL +#else +#define LAST_RIT_EVENT_UPDATE_INTERVAL 1000UL +#endif + /* GLOBALS *******************************************************************/ PTHREADINFO ptiRawInput; @@ -34,7 +41,16 @@ IntLastInputTick(BOOL bUpdate) if (bUpdate) { LastInputTick = EngGetTickCount32(); - if (gpsi) gpsi->dwLastRITEventTickCount = LastInputTick; + if (gpsi) + { + gpsi->dwLastRITEventTickCount = LastInputTick; + if (gpsi->dwLastRITEventTickCount - gpsi->dwLastSystemRITEventTickCountUpdate > + LAST_RIT_EVENT_UPDATE_INTERVAL) + { + SharedUserData->LastSystemRITEventTickCount = LastInputTick; + gpsi->dwLastSystemRITEventTickCountUpdate = LastInputTick; + } + } } return LastInputTick; }