Introduction
Presented here is a C# class useful for monitoring and controlling the Windows screen saver.
The class provides methods for getting information about the screen saver, such as whether or
not it is active, it is running, and its time out setting. Additional methods are provided to
change these settings and to terminate the screen saver if it is running.
Background
Some things aren't always as straight forward as you might think going in. Such was the case
on a recent project where one of the requirements called for killing the screen saver on the occurrence
of a real time event. The project involved a radio dispatch console, and in this case the real time
event was a dispatch operator pressing the push-to-talk button on a radio handset. Also, the
screen saver time out needed to be reset if the screen saver wasn't actually running when the button
was pressed, similar to the action of a key press or mouse event.
The SystemParametersInfo API provided
by the user32.dll supports a set of functions for accessing screen saver settings, including getting
and setting activation, the time out value, and checking to see if the screen saver is running. However,
there doesn't seem to be a function for terminating the screen saver if it is running. The
SPI_SETSCREENSAVERRUNNING
function oddly doesn't do the job as one might think, which instead
is suggested as an obscure technique to disable task switching
under Windows 95 and 98. Ultimately, the solution was found in another
knowledge base article discussing user32.dll functions which can be used to find the running screen
saver application and then force it to close. The main task now was to implement a class to call these unmanaged
functions using C#.
Using the code
To use the code, just copy the class ScreenSaver
into your program and call the exposed methods, which for
the most part are just wrappers to the user32.dll functions:
bool GetScreenSaverActive( )
- returns TRUE if the screen saver is activate (but not necessarily running)
void SetScreenSaverActive( int Active )
- pass in 1 to activate, 0 to deactivate
int GetScreenSaverTimeout( )
- returns the current timeout setting
void SetScreenSaverTimeout( int Value )
- pass in the desired timeout value
bool GetScreenSaverRunning( )
- returns TRUE if the screen saver is running
void KillScreenSaver( )
- terminates the screen saver
IntPtr GetForegroundWindow( )
- returns the handle of the current foreground window
Here is the entire
ScreenSaver
class:
public static class ScreenSaver
{
[DllImport( "user32.dll", CharSet = CharSet.Auto )]
private static extern bool SystemParametersInfo(
int uAction, int uParam, ref int lpvParam,
int flags );
[DllImport( "user32.dll", CharSet = CharSet.Auto )]
private static extern bool SystemParametersInfo(
int uAction, int uParam, ref bool lpvParam,
int flags );
[DllImport( "user32.dll", CharSet = CharSet.Auto )]
private static extern int PostMessage( IntPtr hWnd,
int wMsg, int wParam, int lParam );
[DllImport( "user32.dll", CharSet = CharSet.Auto )]
private static extern IntPtr OpenDesktop(
string hDesktop, int Flags, bool Inherit,
uint DesiredAccess );
[DllImport( "user32.dll", CharSet = CharSet.Auto )]
private static extern bool CloseDesktop(
IntPtr hDesktop );
[DllImport( "user32.dll", CharSet = CharSet.Auto )]
private static extern bool EnumDesktopWindows(
IntPtr hDesktop, EnumDesktopWindowsProc callback,
IntPtr lParam );
[DllImport( "user32.dll", CharSet = CharSet.Auto )]
private static extern bool IsWindowVisible(
IntPtr hWnd );
[DllImport( "user32.dll", CharSet = CharSet.Auto )]
public static extern IntPtr GetForegroundWindow( );
private delegate bool EnumDesktopWindowsProc(
IntPtr hDesktop, IntPtr lParam );
private const int SPI_GETSCREENSAVERACTIVE = 16;
private const int SPI_SETSCREENSAVERACTIVE = 17;
private const int SPI_GETSCREENSAVERTIMEOUT = 14;
private const int SPI_SETSCREENSAVERTIMEOUT = 15;
private const int SPI_GETSCREENSAVERRUNNING = 114;
private const int SPIF_SENDWININICHANGE = 2;
private const uint DESKTOP_WRITEOBJECTS = 0x0080;
private const uint DESKTOP_READOBJECTS = 0x0001;
private const int WM_CLOSE = 16;
public static bool GetScreenSaverActive( )
{
bool isActive = false;
SystemParametersInfo( SPI_GETSCREENSAVERACTIVE, 0,
ref isActive, 0 );
return isActive;
}
public static void SetScreenSaverActive( int Active )
{
int nullVar = 0;
SystemParametersInfo( SPI_SETSCREENSAVERACTIVE,
Active, ref nullVar, SPIF_SENDWININICHANGE );
}
public static Int32 GetScreenSaverTimeout( )
{
Int32 value = 0;
SystemParametersInfo( SPI_GETSCREENSAVERTIMEOUT, 0,
ref value, 0 );
return value;
}
public static void SetScreenSaverTimeout( Int32 Value )
{
int nullVar = 0;
SystemParametersInfo( SPI_SETSCREENSAVERTIMEOUT,
Value, ref nullVar, SPIF_SENDWININICHANGE );
}
public static bool GetScreenSaverRunning( )
{
bool isRunning = false;
SystemParametersInfo( SPI_GETSCREENSAVERRUNNING, 0,
ref isRunning, 0 );
return isRunning;
}
public static void KillScreenSaver( )
{
IntPtr hDesktop = OpenDesktop( "Screen-saver", 0,
false,DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS);
if( hDesktop != IntPtr.Zero )
{
EnumDesktopWindows( hDesktop, new
EnumDesktopWindowsProc( KillScreenSaverFunc ),
IntPtr.Zero );
CloseDesktop( hDesktop );
}
else
{
PostMessage( GetForegroundWindow( ), WM_CLOSE,
0, 0 );
}
}
private static bool KillScreenSaverFunc( IntPtr hWnd,
IntPtr lParam )
{
if( IsWindowVisible( hWnd ) )
PostMessage( hWnd, WM_CLOSE, 0, 0 );
return true;
}
}
The more interesting code here is the KillScreenSaver( )
method. Beginning with Microsoft Windows NT, you
cannot simply make a call to close the foreground window as you could under previous releases of Windows.
Windows NT introduces the concept of separate desktops. Applications can run on one desktop, and screen savers can run on
another. For example, under Windows XP, if you check the "On resume, display Welcome screen" option in the Screen Saver tab
of Display Properties, the screen saver will be running on a desktop other than the one for your application. In
this case, you need to find the screen saver desktop and close its foreground window to terminate the screen saver. If this
option is not checked, the screen saver is running on the same desktop as your application, and may be killed merely by
closing the foreground window in that desktop.
Another thing worth pointing out is in the KillScreenSaverFunc( )
callback function. Notice the call to
IsWindowVisible( hWnd )
just before closing what presumably is the screen saver window. Apparently it's possible
for the screen saver to be "running", yet not actually be seen as the foreground application on the desktop. As we'll see in
our Test Program, we need to keep this in mind for when the screen saver is running on the same desktop as our
application. Otherwise our application could end up being the one killed by the call to
PostMessage( GetForegroundWindow( ), WM_CLOSE, 0, 0 )
, which might annoy the user.
The Test Program
The Screen Saver Test program demonstrates how to use the ScreenSaver
class. It can also serve as a
convenient utility to edit the screen saver settings as you're testing your own application. Since it can modify the
screen saver values normally set with the Display Properties dialog, it saves off these values so
they may be restored on exit or manually using the Restore button. The form is set to be "TopMost" to make it easily
accessible when being used with full screen apps.

To change the screen saver timeout, enter the number of seconds in the NumericUpDown
control on the right and click
the Write button. If the screen saver is "active", it should kick on that many seconds later. If any of the
settings are changed outside of the test program, for example by using the Display Properties dialog, click the Refresh
button to display the new values.
To facilitate testing the KillScreenSaver( )
method, the test program uses a periodic timer. The timeout
period can be set and the timer can be toggled using the Start/Stop Timer button. If the period is greater than the
screen saver timeout, a call to KillScreenSaver( )
will terminate the screen saver. If the period is less,
the screen saver timeout is reset by invoking SetScreenSaverActive( TRUE )
, preventing the screen
saver from running until after a full timeout has expired.
private void KillTimer_Elapsed( object state )
{
killState = ( killState == 1 ) ? 0 : 1;
this.SetText( killState.ToString( ) );
if( ScreenSaver.GetScreenSaverActive( ) )
{
if( ScreenSaver.GetScreenSaverRunning( ) )
{
if( ScreenSaver.GetForegroundWindow() != hThisWnd)
ScreenSaver.KillScreenSaver( );
}
else
{
ScreenSaver.SetScreenSaverActive( TRUE );
}
}
}
As pointed out earlier, it's possible for the SPI_GETSCREENSAVERRUNNING
call to return TRUE
before the screen saver actually becomes the foreground window. Perhaps this function should be renamed to SPI_GETSCREENSAVERSORTAKINDARUNNING
. Anyways, make sure the foreground window isn't your own app before invoking
KillScreenSaver( )
, otherwise it might get closed instead.