How do I start, detect and stop screen savers?

Starting

The method for starting a screen saver is simple, but surprising. You post your own window a message! Post yourself the WM_SYSCOMMAND message with the SC_SCREENSAVE parameter :

// Uses MFC CWnd::PostMessage
PostMessage (WM_SYSCOMMAND, SC_SCREENSAVE);

Detecting and Stopping

Stopping a screen saver is somewhat more complex. The Microsoft-documented way of doing this is to look for the special screen-saver desktop, enumerate all windows on that desktop, and close them, as follows:

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;
}

However, I can't recommend this approach. I have found when using this code, Windows very occasionally seems to get confused and pass you back the normal desktop handle, in which case you end up trying to close all the normal application windows. Note : this bug has now FINALLY been acknowledged by Microsoft - see Knowledgebase article Q230117

You can avoid this problem by taking advantage of the fact that all screen savers built using the MS standard lib file will have the same window class name. So if you change the above callback function code to read as follows :

BOOL CALLBACK KillScreenSaverFunc (HWND hwnd, LPARAM lParam)
{
   char szClass [32];
   GetClassName (hwnd, szClass, sizeof(szClass)-1);

   if (strcmpi(szClass, "WindowsScreenSaverClass") == 0)
      PostMessage (hwnd, WM_CLOSE, 0, 0);
   return TRUE;
}

Then the code will only close the correct window when the OS returns the correct desktop handle, and won't do any damage on those rare occasions when the OS gives you the wrong handle.

Yet another alternative is now available, which depends upon new functionality in SystemParametersInfo :

BOOL bSaver;
if (::SystemParametersInfo (SPI_GETSCREENSAVERUNNING,0,&bSaver,0))
{
   if (bSaver)
   {
      ::PostMessage (::GetForegroundWindow(),
                     WM_CLOSE, 
                     0L, 0L);
   }
}

So you can try that one as well. However I have found that under certain circumstances, GetForegroundWindow can return NULL, so this method might fail as well <sigh>.

Windows 2000+

Just to add insult to injury, on Windows 2000+, screen savers execute on the users desktop in normal mode, and their own desktop in password-protected mode. The class of the default screen saver has also changed to "Default Screen Saver", would you believe. So you can use code like this

if (hwnd = ::FindWindow ("WindowsScreenSaverClass", NULL))
{
   ::PostMessage (hwnd, WM_CLOSE, 0, 0);
}
else
{
   if (hwnd = ::FindWindow ("Default Screen Saver", NULL))
   {
      ::PostMessage (hwnd, WM_CLOSE, 0, 0);
   }
}

but you still have to retain the desktop check as per older systems, enumerate and check for both class names again, in case they're password protected. Sheesh. This all got too much for me, and I changed our system to do its own inactivity detection using system wide mouse and keyboard hooks. More reliable, and it works even if the user disables screen savers.

Download