Страницы

Поиск по вопросам

пятница, 3 января 2020 г.

Как определить бездействие пользователя в windows

#cpp #windows


Как на C++ определить используется ли компьютер в настоящее время?

Через GetLastInputInfo() получаю время последних действий пользователя и определяю
время бездействия. Этот вариант не работает, если пользователь смотрит видео.

Каким способом более точно определить отсутствие активности?
    


Ответы

Ответ 1



Вроде как это можно сделать посредством API Планировщика заданий, через интерфейс IIdleTrigger. см Task Idle Conditions.

Ответ 2



Некоторые приложения, особенно для просмотра видео и игр, устанавливают в системе флаг SPI_SCREENSAVERRUNNING. Делается это для того, чтобы система не включала скринсейвер и вообще считала, что она используется - даже при отсутствии пользовательского ввода. Получить значение этого флага можно с помощью функции SystemParametersInfo(). Также, можно "ловить" момент его изменения, обрабатывая сообщение WM_SETTINGCHANGE. К сожалению, не очень легко отличить момент выполнения такой программы от момента, когда система действительно простаивает со включенным скринсейвером. Я бы предложил такой вариант: получаем значение таймаута экранной заставки, флагом SPI_GETSCREENSAVETIMEOUT (возможно, понадобится ещё обрабатывать сообщение WM_SETTINGCHANGE, чтобы корректно обрабатывать изменение таймаута заставки) засекаем последний момент времени, когда был пользовательский ввод (GetLastInputInfo()) получаем значение флага SPI_GETSCREENSAVERRUNNING на основании текущего времени вычисляем количество секунд, прошедших с момента последнего пользовательского ввода определяем результат если прошедшее время меньше таймаута заставки, а флаг SPI_GETSCREENSAVERRUNNING установлен, то пользователь смотрит видео или во что-то играет (возможно, сейчас смотрит какой-то внутриигровой ролик) если прошедшее время меньше таймаута заставки, а флаг SPI_GETSCREENSAVERRUNNING не установлен, то вероятней всего, это момент простоя системы если прошедшее время больше таймаута заставки, то результат неопределённый: либо используем предыдущие результаты, либо интерпретируем согласно своим интересам :) И не забыть отдельно обрабатывать вариант, когда экранная заставка отключена в настройках (приложениям это не мешает установить флаг SPI_SCREENSAVERRUNNING)

Ответ 3



Вот вам пример с использованием Планировщика задач версии 2 (Windows Vista и выше). Так как нам придётся работать с COM, то необходимо сделать три вещи: В свойствах проекта перейти к параметру Configuration Properties -> Linker -> Input -> Additional Dependencies и обеспечить наличие там библиотек ole32.lib и uuid.lib, отделённых друг от друга и от имеющегося содержимого строки точками с запятой (;). Вот пример того, как вообще выглядит данный параметр: Добавить в код вспомогательную функцию, выполняющую безопасное приведение типов указателей и избегающую, в отличие от reinterpret_cast, неопределённого поведения (а именно нарушения strict aliaing rule — см. статью «Про C++ алиасинг, ловкие оптимизации и подлые баги»): #include #include #if __cplusplus < 201103L # define static_assert(cond, text) #endif template< class D, class S, class = std::enable_if::value && std::is_pointer::value>::type > static D convert(S in) { D out; static_assert(sizeof(in) == sizeof(out), "incompatible pointer types"); memcpy(&out, &in, sizeof(in)); return out; } В начале работы программы вызвать CoInitialize(), а в конце — CoUninitialize(). Теперь перейдём к непосредственно регистрации обработчика. Сразу отмечу два момента: Пользователь имеет доступ к оснастке управления Планировщиком, а потому может отключить наш обработчик. Не надо этому противодействовать — пусть владелец компьютера сам решает, что ему нужно, а что нет. В качестве обработчика задачи можно назначить только исполняемый файл. Поэтому нам потребуется написать небольшую утилиту, которая будет посылать уведомление всем запущенным экземплярам вашей программы. Это можно сделать, например, с помощью именованных оконных сообщений: #include int main() { // Вместо "mymsg" подставьте своё название, например, "имя-программы.on_idle" const UINT uMsgId = RegisterWindowMessage(TEXT("mymsg")); SendMessage(HWND_BROADCAST, uMsgId, 0, 0); return 0; } Ваша же программа, в свою очередь, должна будет отлавливать это сообщение: // Где-нибудь во время создания окна инициализируйте эту переменную вызовом // uMsgId = RegisterWindowMessage(TEXT("название-указанное-выше")); INT uIdleNotificationMsgId; // Обработчик оконных сообщений LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch(uMsg) { // Здесь вы обрабатываете все обычные оконные сообщения default: if(uMsg == uIdleNotificationMsgId) { // Обнаружено бездействие. Выполняете всё, что хотели return 0; } else return DefWindowProc(hwnd, uMsg, wParam, lParam); } } Теперь приступим непосредственно к регистрации обработчика: #include #include #include // ... ITaskScheduler *scheduler; const HRESULT lComStartupResult = CoCreateInstance( /* rclsid */ CLSID_CTaskScheduler, // Класс планировщика задач /* pUnkOuter */ NULL, /* dwClsContext */ CLSCTX_INPROC_SERVER, // Загружаем mstask.dll в наш процесс /* riid */ IID_ITaskScheduler, // Нас интересует конкретный интерфейс /* ppv */ convert(&scheduler) ); if(SUCCEEDED(lComStartupResult)) { // Создаём задачу, выполняемую при бездействии LPCWSTR pwszTaskName; ITask *task; const HRESULT lTaskAdditionResult = scheduler->NewWorkItem( /* pwszTaskName */ L"MyTask", // Подставьте сюда осмысленное имя задачи /* rclsid */ CLSID_CTask, /* riid */ IID_ITask, /* ppunk */ convert(&task) ); if(SUCCEEDED(lTaskAdditionResult)) { // Указываем, что задача должна исполняться сразу же при обнаружении простоя. // В каждой версии Windows свои критерии наступления этого события: // // * Windows Vista ждёт десять минут с момента прекращения пользования // клавиатурой и мышью. // * Windows 7 и выше ждёт уже четыре минуты. TASK_TRIGGER triggerInfo; memset(&triggerInfo, 0, sizeof(triggerInfo)); rTrigger.cbTriggerSize = sizeof(triggerInfo); rTrigger.TriggerType = TASK_EVENT_TRIGGER_ON_IDLE; // Создаём триггер с указанными параметрами WORD triggerId; // Параметр помечен как [out], поэтому инициализация не требуется ITaskTrigger *trigger; const HRESULT lTriggerAdditionResult = pITask->CreateTrigger( /* piNewTrigger */ &triggerId, /* ppTrigger */ &trigger ); if(SUCCEEDED(lTriggerAdditionResult)) { trigger->SetTrigger(&triggerInfo); // Указываем программу, которая будет запущена при бездействии. Она, в свою // очередь, должна будет послать вам уведомление (как именно - см. выше) // и завершиться trigger->SetProgram(L"путь до программы"); trigger->SetComment(L"описание задачи для администратора, что и зачем делает"); // Сохраняем задачу в Планировщик задач IPersistFile *saver; const HRESULT lSaverRetrieved = trigger->QueryInterface( /* riid */ IID_IPersistFile, /* ppvObject */ convert(&saver) ); if(SUCCEEDED(lSaverRetrieved)) { saver->Save(/* pszFileName */ NULL, /* fRemember */ FALSE); saver->Release(); } trigger->Release(); } else { // Ошибка при добавлении триггера } task->Release(); } else { // Ошибка при создании задачи } scheduler->Release();

Комментариев нет:

Отправить комментарий