22. 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, NT4 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 NT4, 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.
