Click here to Skip to main content
15,867,704 members
Articles / Programming Languages / C#
Article

Controlling The Screen Saver With C#

Rate me:
Please Sign up or sign in to vote.
4.48/5 (26 votes)
8 Jan 2007CPOL5 min read 256.2K   8.2K   98   37
A C# class for monitoring, activating, deactivating, closing, and changing the timeout of the screen saver.

Display Properties Dialog

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
{
   // Signatures for unmanaged calls
   [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( );

   // Callbacks
   private delegate bool EnumDesktopWindowsProc( 
      IntPtr hDesktop, IntPtr lParam );

   // Constants
   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;


   // Returns TRUE if the screen saver is active 
   // (enabled, but not necessarily running).
   public static bool GetScreenSaverActive( )
   {
      bool isActive = false;

      SystemParametersInfo( SPI_GETSCREENSAVERACTIVE, 0, 
         ref isActive, 0 );
      return isActive;
   }

   // Pass in TRUE(1) to activate or FALSE(0) to deactivate
   // the screen saver.
   public static void SetScreenSaverActive( int Active )
   {
      int nullVar = 0;

      SystemParametersInfo( SPI_SETSCREENSAVERACTIVE, 
         Active, ref nullVar, SPIF_SENDWININICHANGE );
   }

   // Returns the screen saver timeout setting, in seconds
   public static Int32 GetScreenSaverTimeout( )
   {
      Int32 value = 0;

      SystemParametersInfo( SPI_GETSCREENSAVERTIMEOUT, 0, 
         ref value, 0 );
      return value;
   }

   // Pass in the number of seconds to set the screen saver
   // timeout value.
   public static void SetScreenSaverTimeout( Int32 Value )
   {
      int nullVar = 0;

      SystemParametersInfo( SPI_SETSCREENSAVERTIMEOUT, 
         Value, ref nullVar, SPIF_SENDWININICHANGE );
   }

   // Returns TRUE if the screen saver is actually running
   public static bool GetScreenSaverRunning( )
   {
      bool isRunning = false;

      SystemParametersInfo( SPI_GETSCREENSAVERRUNNING, 0, 
         ref isRunning, 0 );
      return isRunning;
   }

   // From Microsoft's Knowledge Base article #140723: 
   // http://support.microsoft.com/kb/140723
   // "How to force a screen saver to close once started 
   // in Windows NT, Windows 2000, and Windows Server 2003"

   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.

Screen Saver Test Dialog

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.

// Kill event timer handler
private void KillTimer_Elapsed( object state )
{
   // Toggle kill state to indicate activity
   killState = ( killState == 1 ) ? 0 : 1;
   this.SetText( killState.ToString( ) );

   // Stop the screen saver if it's active and running, 
   // otherwise reset the screen saver timer.
   // Apparently it's possible for GetScreenSaverRunning()
   // to return TRUE before the screen saver has time to 
   // actually become the foreground application. So...
   // Make sure we're not the foreground window to avoid 
   // killing ourself.

   if( ScreenSaver.GetScreenSaverActive( ) )
   {
      if( ScreenSaver.GetScreenSaverRunning( ) )
      {
         if( ScreenSaver.GetForegroundWindow() != hThisWnd)
            ScreenSaver.KillScreenSaver( );
      }
      else
      {
         // Reset the screen saver timer, so the screen 
         // saver doesn't turn on until after a full
         // timeout period. If killPeriod is less than 
         // ssTimeout the screen saver should never 
         // activate.
         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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
Kurt's programming career began in 1978, developing firmware for the Zilog Z80-A microprocessor on a Mostek development workstation. For 23 years, Kurt led the development of Human Machine Interface (HMI) programs used in factory automation, marketed and sold worldwide by CTC and Parker Hannifin as ScreenWare, Interact and InteractX. For the last 4 years, Kurt has been consulting on agile development practices and working as an independent software contractor focusing on graphical and user interface development.

Other passions include spending time with his wife Janice, daughters Erica and Laura, scuba diving, target shooting, guitar, travel, and digital photo imaging and restoration. Currently residing in southwestern Ohio.

Comments and Discussions

 
GeneralMy vote of 5 Pin
martinrj301-Dec-11 18:13
martinrj301-Dec-11 18:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.