C Windows API 判断用户是否在一段时间内不活跃
C Windows API determine if user inactive for certain period
所以我创建了一个带有阻塞消息事件循环的基本程序(等待时几乎不使用 CPU)并等待用户更改前景 window,然后执行一些代码:
#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;
}
我还想合并检查用户是否在一定时间内处于非活动状态(没有 mouse/keyboard 输入),但要保持低资源使用率。我可以想到几种方法来解决这个问题:
让阻塞事件循环检查是否有鼠标或键盘输入将某种计时器重置回零,并在同一循环内检查鼠标输入是否导致前景 window 变化(如果鼠标点击事件和前景 window 变化之间存在延迟,这可能会导致问题(这意味着不会捕获前景 window 变化)。触发事件时用户输入计时器已完成指定的时间。
运行 鼠标和键盘 activity 事件计时器在单独的线程上或异步到前台 window 更改事件。当计时器完成时触发事件(运行 在单独的线程上或异步以确保不会错过前台 window 更改事件)。
在单独的线程上或异步地,每隔几秒检查一次 GetLastInputInfo() 函数以查看 inactivity 阈值时间是否已过。
可以这样调用:
LASTINPUTINFO li;
li.cbSize = sizeof(LASTINPUTINFO);
GetLastInputInfo(&li);
记住最低的资源使用率,什么方法最好在activity检查中实施mouse/keyboard,同时检查前景window变化。
您可以设置一个计时器(请参阅 SetTimer)以在任意超时到期时调用用户定义的回调。这使您可以跳出阻塞 GetMessage
循环。
回调可以检查最后一次输入的时间戳,并将其与当前时间戳进行比较。如果该时间间隔超过所需的不活动超时,它可以执行必要的步骤。否则它会在超时的剩余时间重新启动计时器。
以下代码说明了这一点:
#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);
}
}
整个逻辑包含在TimerProc
中。要触发不活动计时器,StartInactivityTimer
会启动一个立即到期的计时器。当 TimerProc
取得控制权时,它会进行所需的计算,然后重新启动计时器,或调用超时程序 TimeoutExpired
.
这个实现有两个优点:其一,整个定时器重启逻辑都在一个地方。更重要的是,不活动条件是在第一次调用时评估的。如果在不活动间隔内调用 StartInactivityTimer
时没有任何用户输入,它会立即执行 TimeoutExpired
.
另请注意,区间计算使用无符号整数运算,特别是减法。由于无符号整数 'underflow' 在 C 和 C++ 中都有明确定义,因此该解决方案不受 GetTickCount 的 return 值在大约 49.7 天后回绕到 0 的影响。
所以我创建了一个带有阻塞消息事件循环的基本程序(等待时几乎不使用 CPU)并等待用户更改前景 window,然后执行一些代码:
#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;
}
我还想合并检查用户是否在一定时间内处于非活动状态(没有 mouse/keyboard 输入),但要保持低资源使用率。我可以想到几种方法来解决这个问题:
让阻塞事件循环检查是否有鼠标或键盘输入将某种计时器重置回零,并在同一循环内检查鼠标输入是否导致前景 window 变化(如果鼠标点击事件和前景 window 变化之间存在延迟,这可能会导致问题(这意味着不会捕获前景 window 变化)。触发事件时用户输入计时器已完成指定的时间。
运行 鼠标和键盘 activity 事件计时器在单独的线程上或异步到前台 window 更改事件。当计时器完成时触发事件(运行 在单独的线程上或异步以确保不会错过前台 window 更改事件)。
在单独的线程上或异步地,每隔几秒检查一次 GetLastInputInfo() 函数以查看 inactivity 阈值时间是否已过。
可以这样调用:
LASTINPUTINFO li;
li.cbSize = sizeof(LASTINPUTINFO);
GetLastInputInfo(&li);
记住最低的资源使用率,什么方法最好在activity检查中实施mouse/keyboard,同时检查前景window变化。
您可以设置一个计时器(请参阅 SetTimer)以在任意超时到期时调用用户定义的回调。这使您可以跳出阻塞 GetMessage
循环。
回调可以检查最后一次输入的时间戳,并将其与当前时间戳进行比较。如果该时间间隔超过所需的不活动超时,它可以执行必要的步骤。否则它会在超时的剩余时间重新启动计时器。
以下代码说明了这一点:
#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);
}
}
整个逻辑包含在TimerProc
中。要触发不活动计时器,StartInactivityTimer
会启动一个立即到期的计时器。当 TimerProc
取得控制权时,它会进行所需的计算,然后重新启动计时器,或调用超时程序 TimeoutExpired
.
这个实现有两个优点:其一,整个定时器重启逻辑都在一个地方。更重要的是,不活动条件是在第一次调用时评估的。如果在不活动间隔内调用 StartInactivityTimer
时没有任何用户输入,它会立即执行 TimeoutExpired
.
另请注意,区间计算使用无符号整数运算,特别是减法。由于无符号整数 'underflow' 在 C 和 C++ 中都有明确定义,因此该解决方案不受 GetTickCount 的 return 值在大约 49.7 天后回绕到 0 的影响。