问题
So I've created a basic program with a blocking message event loop (to use little to no CPU while waiting) and waits for a user to change the foreground window, then executes some code:
#include <Windows.h>
VOID ExitFunction()
{
// Do Something
}
BOOL WINAPI HandlerRoutine(DWORD dwCtrlType)
{
switch (dwCtrlType)
{
case CTRL_SHUTDOWN_EVENT:
ExitFunction();
return TRUE;
case CTRL_LOGOFF_EVENT:
ExitFunction();
return TRUE;
//default:
//We don't care about this event
//Default handler is used
}
return FALSE;
}
VOID CALLBACK WinEventProcCallback(HWINEVENTHOOK hWinEventHook, DWORD dwEvent, HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime)
{
if (dwEvent == EVENT_SYSTEM_FOREGROUND)
{
// Do Stuff
}
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
HWINEVENTHOOK WindowChangeEvent;
SetConsoleCtrlHandler(HandlerRoutine, TRUE);
WindowChangeEvent = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, NULL, WinEventProcCallback, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
while (GetMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
ExitFunction();
return 0;
}
I also want to incorporate checking if the user has been inactive for a certain amount of time (no mouse/keyboard input) but keep resource usage low. There are a couple of ways to approach this that I can think of:
Have the blocking event loop check if there has been mouse or keyboard input which resets some kind of timer back to zero and also checks within the same loop if the mouse input resulted in a foreground window change (which may cause issues if there is a delay between the mouse click event and the foreground window change (meaning the foreground window change won't be captured). Have an event triggered when the user input timer has completed the specified time.
Run the mouse & keyboard activity event timer on a separate thread or asynchronously to the foreground window change event. When the timer has completed fire off an event (run on separate thread or asynchronously to make sure a foreground window change event isn't missed).
On a separate thread or asynchronously, check every couple seconds the GetLastInputInfo() function to see if the inactivity threshold time has elapsed.
It can be called like so:
LASTINPUTINFO li;
li.cbSize = sizeof(LASTINPUTINFO);
GetLastInputInfo(&li);
Keeping in mind lowest resource usage, what way is best to implement the mouse/keyboard inactivity checking while also checking for foreground window changes.
回答1:
You can set up a timer (see SetTimer) to have a user-defined callback called when an arbitrary timeout expires. This allows you to break out of the blocking GetMessage
loop.
The callback can check the timestamp of the last input, and compare it to the current timestamp. If that time interval exceeds the desired inactivity timeout, it can perform the necessary steps. Otherwise it restarts the timer with the remainder of the timeout.
The following code illustrates this:
#include <Windows.h>
#include <iostream>
static const DWORD timeout_in_ms { 5 * 1000 };
void TimeoutExpired() { std::wcout << L"Timeout elapsed" << std::endl; }
void CALLBACK TimerProc(HWND, UINT, UINT_PTR id, DWORD current_time)
{
// Timers are periodic, but we want it to fire only once.
KillTimer(nullptr, id);
LASTINPUTINFO lii { sizeof(lii) };
GetLastInputInfo(&lii);
auto const time_since_input { current_time - lii.dwTime };
if (time_since_input < timeout_in_ms)
{
// User input was recorded inside the timeout interval -> restart timer.
auto const remaining_time { timeout_in_ms - time_since_input };
SetTimer(nullptr, 0, remaining_time, &TimerProc);
}
else
{
TimeoutExpired();
}
}
void StartInactivityTimer()
{
// Start a timer that expires immediately;
// the TimerProc will do the required adjustments and
// restart the timer if necessary.
SetTimer(nullptr, 0, 0, &TimerProc);
}
int wmain()
{
StartInactivityTimer();
MSG msg {};
while (GetMessageW(&msg, nullptr, 0, 0) > 0)
{
DispatchMessageW(&msg);
}
}
The entire logic is contained within TimerProc
. To trigger the inactivity timer, StartInactivityTimer
starts a timer that expires immediately. When TimerProc
takes control it does the required calculations, and either restarts the timer, or calls the timeout procedure, TimeoutExpired
.
This implementation has two advantages: For one, the entire timer restart logic is in a single place. More importantly, the inactivity condition is evaluated on first call. If StartInactivityTimer
is called without any user input in the inactivity interval, it instantly executes TimeoutExpired
.
Also note that the interval calculations use unsigned integer arithmetic, specifically subtraction. With unsigned integer 'underflow' being well defined in both C and C++, this solution is immune to GetTickCount's return value wrapping around to 0 after approximately 49.7 days.
来源:https://stackoverflow.com/questions/61398993/c-windows-api-determine-if-user-inactive-for-certain-period