10

I'm writing code that need to run only when there is no human activity on the PC, like when the screensaver is running. Any suggestions on how to do this in c++ under windows?

@talnicolas, simply to use unused resources, how many times people leave the computer on but they are in another place?

chacham15
  • 13,719
  • 26
  • 104
  • 207
user1143336
  • 259
  • 1
  • 4
  • 11
  • 8
    @talnicolas: Because that's part of the quesition. – Niklas B. Jan 11 '12 at 14:08
  • 1
    rename your .exe to .scr and get the user to install it as their screen saver :) – matiu Jan 11 '12 at 14:08
  • 5
    You don't need to reinvent the wheel. Just create a scheduled task and under Conditions, specify "Start the task only if the computer is idle for [N] minutes." – Raymond Chen Jan 11 '12 at 14:10
  • 2
    @talnicolas It's a frequent requirement for distributed processing: you only accept new jobs from the central dispatcher if no one is actively using the machine. – James Kanze Jan 11 '12 at 14:11
  • this might get you a start http://stackoverflow.com/questions/2057492/how-to-determine-if-a-screensaver-is-running-in-java – solarc Jan 11 '12 at 14:11
  • Im not sure but this might be sollution... check for the no of process running when system is idle . and now put the condition that if no of process is equal to no of process found when system idle write rest of the code in that if block – Imposter Jan 11 '12 at 14:12
  • 2
    Define "no human activity". On my machine, if I weren't responding here, there would be "no human activity" until the build (running on a remote machine) finishes, which could be a couple of minutes. – James Kanze Jan 11 '12 at 14:17
  • @JamesKanze: So it would be a good time to run a background worker during that period. – Niklas B. Jan 11 '12 at 14:33
  • 4
    Might one valid option be to simply run the program and set the process priority class to `IDLE_PRIORITY_CLASS`? This would make 100% CPU available to your process unless someone else needs CPU time, in which case it will take zero CPU. No polling necessary, no obscure "so and so many seconds no input" heuristic, it happens at scheduler level as CPU time becomes available, on a scheduler quantum scale. – Damon Jan 11 '12 at 14:45

6 Answers6

14

You can use GetLastInputInfo to check how long the user has been idle (not moved around the mouse or typed something on the keyboard) and SystemParametersInfo to check if a screensaver is active.

Example

#define WINDOWS_LEAN_AND_MEAN
#include <windows.h>
#include <iostream>

// do something after 10 minutes of user inactivity
static const unsigned int idle_milliseconds = 60*10*1000;
// wait at least an hour between two runs
static const unsigned int interval = 60*60*1000;

int main() {
    LASTINPUTINFO last_input;
    BOOL screensaver_active;

    // main loop to check if user has been idle long enough
    for (;;) {
        if ( !GetLastInputInfo(&last_input)
          || !SystemParametersInfo(SPI_GETSCREENSAVERACTIVE, 0,  
                                   &screensaver_active, 0))
        {
            std::cerr << "WinAPI failed!" << std::endl;
            return ERROR_FAILURE;
        }

        if (last_input.dwTime < idle_milliseconds && !screensaver_active) {
            // user hasn't been idle for long enough
            // AND no screensaver is running
            Sleep(1000);
            continue;
        }

        // user has been idle at least 10 minutes
        do_something();
        // done. Wait before doing the next loop.
        Sleep(interval);
    }
}

Note that I wrote that code on a Linux machine, so I couldn't test it.

Niklas B.
  • 92,950
  • 18
  • 194
  • 224
  • For me the SPI_GETSCREENSAVERRUNNING detected windows screensaver, SPI_GETSCREENSAVERACTIVE only returns if windows has screensaver enabled, see https://msdn.microsoft.com/en-us/library/windows/desktop/ms724947(v=vs.85).aspx – krizajb Apr 28 '16 at 15:36
  • 1
    One more note about the code, in my case I had to set `cbSize` of `LASTINPUTINFO` in your case: `last_input.cbSize = sizeof(last_input);` – krizajb Apr 29 '16 at 16:35
  • Why would I compare a constant interval to the tick count when the last input event was received? – Paul B. Sep 10 '19 at 10:17
8

This is just a minor update to Niklas B. answer which is tested on Windows 8.1 and Windows 10.

Description of changes is as follows:

  • LASTINPUTINFO has cbSize initialized

  • instead of checking SPI_GETSCREENSAVERACTIVE, SPI_GETSCREENSAVERRUNNING is checked

  • idle_time is calculated and compared with idle_milliseconds

  • user is notified only on change, check is executed every ~500ms

  • since windows can trigger event id 4803 immediately after 4802 one can determine that screensaver is still on with user idle_time, see the problem I had

Additionally this only works if screensaver timer is smaller then screen power off timer!

#define start

#include <windows.h>
#include <iostream>

// do something after 10 minutes of user inactivity
static const unsigned int idle_milliseconds = 60*10*1000;

int main() {
    LASTINPUTINFO last_input;
    // without setting cbSize GetLastError() returns the parameter is incorrect
    last_input.cbSize = sizeof(last_input);  
    BOOL screensaver_running;

    DWORD idle_time;
    bool screensaverOn = false;

    // main loop to check if user has been idle long enough
    for (;;) {
        if ( !GetLastInputInfo( &last_input )
          || !SystemParametersInfo( SPI_GETSCREENSAVERRUNNING, 0,  
                               &screensaver_running, 0 ) )
        {
            std::cerr << "WinAPI failed!" << std::endl;
            return ERROR_FAILURE;
        }

        // calculate idle time
        idle_time = GetTickCount() - last_input.dwTime;

        if ( idle_time > this->user_idle_time && TRUE == screensaver_running )
        {
            if ( !screensaverOn )
            {
                // screensaver is running
                screensaverOn = true;
                notify( true );
            }
        }
        else if ( idle_time < this->user_idle_time && screensaverOn )
        {
            // screensaver is not running
            screensaverOn = false;
            notify( false );
        }
        // wait 500ms before next loop
        Sleep( 500 );
    }
}
krizajb
  • 1,715
  • 3
  • 30
  • 43
3

Use SystemParametersInfo(...)

   private const int SPI_GETSCREENSAVERACTIVE = 16;
   SystemParametersInfo( SPI_GETSCREENSAVERACTIVE, 0, 
         ref isActive, 0 );
      return isActive;

Reference: http://www.codeproject.com/KB/cs/ScreenSaverControl.aspx

Kristofer
  • 3,201
  • 23
  • 28
1

Maybe there is confusion for some people here with plii.dwTime

plii.dwTime gives the timestamp date of last input, it doesn't give time beteween now and the last input

for this you have to sub it to GetTickCount() :

LASTINPUTINFO plii;
plii.cbSize = sizeof(LASTINPUTINFO);
plii.dwTime = 0;

if (GetLastInputInfo(&plii) != 0)
{
    cout << "Last activity  : " <<  GetTickCount() - plii.dwTime << " (ms)" << endl;
}
else {
    cout << "GetLastInputInfo ERROR" << endl;
}
loki
  • 11
  • 1
0

The inability to detect user inputs in a service is enforced by design.

That means that a service has, among other things, no direct way of knowing when the last user input occurred, and is the reason why GetLastInputInfo returns nothing useful when called from within a service.

Apparently the idea was to make life more difficult for rabid hackers trying to write key loggers and other nasty pieces of code. Of course you can easily bypass that limitation by having a user process feed that information to your service.
I don't suppose this trivial workaround is out of reach of your average rabid hacker, I rather picture the guys relishing in such pretty pointless exercises in code convolution.

If your service performs user-requested computations, you will most likely need a user process to interact with it anyway. But if you want to poll whatever user activity indication, you'll have to leave this user process running. A user demon kept alive to feed an insignificant bit of information to your real demon. Talk about an efficient use of resources...

As a side note, monitoring screen saver activations is pretty unreliable. The screen could be set to shut off before any saver shows up. The PC could also go to sleep before your code get a chance to run. So you'll have to be more careful about what conditions you check.

Alternatively, you might want to look at the performance counters.
They can give you a truckload of informations on CPU load, disk usage, etc.
You could detect a period of low CPU and/or disk activity relatively easily by monitoring these.

The interface is a terrible mess though, and apparently there are yet other security hurdles to jump. I'm not quite sure which service accounts - if any - can access them or if they can do so by default on a typical Windows installation.

You could also activate your code when nobody is logged in. Apparently there is no obvious way of detecting this, but it is doable from within a service.

kuroi neko
  • 8,479
  • 1
  • 19
  • 43
0

check at this answer that looks similar.

However you need to determine exactly was "inactivity" means. That modifies the scope of your question.

Also check this code

hdesk = OpenDesktop(TEXT("Screen-saver"),
                    0,
                    FALSE,
                    DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS);
if (hdesk)
{
   EnumDesktopWindows (hdesk, (WNDENUMPROC)KillScreenSaverFunc, 0);
   CloseDesktop (hdesk);
}

// ----------------------------------------------------------------

BOOL CALLBACK KillScreenSaverFunc (HWND hwnd, LPARAM lParam)
{
   PostMessage(hwnd, WM_CLOSE, 0, 0);
   return TRUE;
}

from here.

Community
  • 1
  • 1
cateof
  • 6,608
  • 25
  • 79
  • 153