15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту


Как долго простаивает операционная система.

Автор: Paul DiLascia

Скачать исходник к статье - 21 Кб (Компилятор: Visual C++ 6.0)

Вы задавались когда-нибудь вопросом, как можно определить сколько времени бездействует операционная система ? Или, например, как аська определяет, что пользователя нет за компьютером ? Проще говоря, как определить, сколько времени не трогали мышку или не стучали по клавиатуре ?

В Windows 2000 для этих целей предусмотрена системная функция GetLastInputInfo, которая заполняет структуру LASTINPUTINFO нужной нам информацией:

 LASTINPUTINFO lpi;
 lpi.cbSize = sizeof(lpi);
 GetLastInputInfo(&lpi);

После вызова GetLastInputInfo, lpi.dwTime содержит количество миллисекунд, прошедших с момента прекращения каких либо действий пользователя. С одной стороны, это то, что нам нужно, но с другой - эта функция доступна только в Windows 2000 - но не в Windows 9x и не в Windows NT 4.0. Надо бы подумать, что в таком случае можно предпринять.

Наверное, проще всего будет написать свою версию GetLastInputInfo, а именно поставить глобальную ловушку (systemwide hook). Ловушка, это callback-функция, которую Windows вызывает каждый раз, когда случается что-нибудь интересное, например, когда пользователь что-нибудь напечатает на клавиатуре. Универсальной ловушки не существует, однако ловушкой на клавиатуру и ловушкой на мышь мы спокойно сможем перехватить весь пользовательский ввод. Единственное, что может раздражать в таком случае, это то, что ловушки должны жить в DLL.

Итак, для этой цели я написал простенькую DLL, которую назвал IdleUI и которая имеет всего три функции:

 BOOL  IdleUIInit()
 void  IdleUITerm();
 DWORD IdleUIGetLastInputTime();

Как Вы наверное уже догадались, IdleUIInit служит для инициализации, а IdleUITerm для завершения. Их можно спокойно вызывать из приложения MFC в функциях InitInstance и ExitInstance соответственно. Как только проинициализируется IdleUI, можно вызвать третью функцию IdleUIGetLastInputTime, чтобы получить количество тиков, прошедших с момента последнего пользовательского ввода, как это делает GetLastInputInfo.

Чтобы протестировать свою DLL, я написал программку TestIdleUI. Она вызывает IdleUIInit и IdleUITerm как описано выше и при этом отображает количество секунд бездействия клавиатуры и мышки.

 void CMainFrame::OnPaint()
 {
   CPaintDC dc(this);
   CString s;
   DWORD nsec = (GetTickCount()-
     IdleUIGetLastInputTime())/1000;
   s.Format(
     "No mouse or keyboard input for %d seconds.",nsec);
   CRect rc;
   GetClientRect(&rc);
   dc.DrawText(s, &rc,   
     DT_CENTER|DT_VCENTER|DT_SINGLELINE);
 }

Как это выглядит, можно посмотреть на рисунке 1. Для обеспечения непрерывности процесса отсчёта, в TestIdleUI используется односекундный таймер, который обновляет клиентскую область каждую секунду.

 void CMainFrame::OnTimer(UINT)
 {
   Invalidate();
   UpdateWindow();
 }

Что может быть проще? Запускаем TestIdleUI и наблюдаем по секундам, как Ваш компьютер бездействует. Достаточно сдвинуть мышку и щёлкнуть по клавиатуре, как счётчик сбросится в ноль.


Рисунок 1 TestIdleUI

Как работает IdleUI? Когда Вы вызваете IdleUIInit, то она инсталлирует две ловушки: одну для мышки, а вторую для клавиатуры.

 HHOOK g_hHookKbd;
 HHOOK g_hHookMouse;
 g_hHookKbd = SetWindowsHookEx(WH_KEYBOARD,
                               MyKbdHook, 
                               hInst, 0);
 g_hHookMouse = SetWindowsHookEx(WH_MOUSE,
                                 MyMouseHook, 
                                 hInst, 0);

Теперь если пользователь двигает мышку или нажимает клавишу на клавиатуре, Windows вызывает одну из этих ловушек и соответствующую ей функцию, которая записывает время:

 LRESULT CALLBACK MyMouseHook(int code, 
                              WPARAM wp, 
                              LPARAM lp)
 {
   if (code==HC_ACTION) {
     // уведомляем счётчик тиков
     g_dwLastInputTick = GetTickCount();
   }
   return ::CallNextHookEx(g_hHookMouse,
                           code, wp, lp);
 }

Аналогичная операция делается для MyKbdHook. IdleUIGetLastInputTime возвращает g_dwLastInputTick, а IdleUITerm деинсталлирует обе ловушки.

Есть только одна тонкость. Обычно, когда Вы создаёте DLL, то компилятор помечает статические данные как нерасшаренные (nonshared), что означает, что каждый процесс, который вызывает DLL получает свою собственную копию данных - в данном случае g_hHookKbd, g_hHookMouse, и g_dwLastInputTick. Но нам-то нужен один и только один экземпляр этих данных. Поэтому необходимо сделать данные доступными. Для этого необходимо поместить их в специальный сегмент, который помен как доступный (shared). В коде это выглялит так:

 #pragma data_seg (".IdleUI") // или любое другое имя
 HHOOK g_hHookKbd = NULL;
 HHOOK g_hHookMouse = NULL;
 DWORD g_dwLastInputTick = 0;
 #pragma data_seg ()

Этот код указывает компилятору, что три переменные необходимо поместить в сегмент данных с именем .IdleUI. Затем необходимо добавить следующую строчку в IdleUI.def, сделав тем самым сегмент расшаренным (доступным):

 // в IdleUI.def
 SECTIONS .IdleUI READ WRITE SHARED

Вот собственно и всё. Удачи!