ScrPlus How to write a 32bit screen saver

[Scr] [Savers] [ScrPlus/BCB] [ScrPlus] [HowToScr] [Lucian]
© 1997-1998 Lucian Wischik Stop! You should not be reading this! This document describes all the system requirements expected of a screen saver, and all of the internal undocumented Windows API for accomplishing this. It would be far better for you simply to ignore the whole mess and go straight to using ScrPlus or ScrPlus/C++Builder, which do everything for you a lot more simply.

I have had two kind offers of translations of this document into French and Spanish. Hopefully I will be able to put these translations up soon.


Screen savers start when the mouse and keyboard have been left idle for some time. They have five main purposes:

Microsoft documentation on the technical programming of savers is inadequate. This guide was written to make up for the lack of official documentation. It tells you how to write a saver from scratch, using only the plain Windows API without any additional libraries.

What a saver is

A saver is a straightforward executable that has been renamed with the extension .scr, and which responds to particular command-line arguments in particular ways as detailed in the rest of this document. It must be marked as a win4 executable, otherwise some features such as the preview will be disabled. All modern compilers do this automatically through an option labelled 'target subsystem' or similar. Older compilers such as BC++4.5 do not and you will have to use some external utility such as w40 to mark it yourself.

Version differences between '95, Plus! and NT

Windows NT does all saver password management itself, and closes savers automatically in response to keyboard or mouse events. Interactive savers need special precautions to prevent NT from doing this. Under '95 and Plus! the saver must do all the password management itself. Plus! introduced some new saver features: less sensitivity to mouse movement while the saver is running; and hot corners which can start the saver immediately or prevent it from running at all. Special code is needed to handle these features; and you can use these features under '95 and NT as well as Plus! See also Plus! configuration, Portability between '95 and NT.

Creating a saver

This chapter describes the behaviour expected of a saver. The first section describes the various times at which a saver can be executed. Subsequent sections give more details of the expected behaviour, illustrated with code extracts from a minimal saver. This saver has a single button in its configuration dialog for whether or not to flash the screen. If the button is checked then the saver runs by flashing the screen through the different colours of the user's colour scheme. If the button is unchecked, then the screen stays black. You may prefer to download separately the file, which contains the source code.

How and when saver is executed

The following list gives all the situations in which a saver will be launched. Observe how, especially in the Display Properties control panel under Windows '95, the preview running inside the little preview monitor gets terminated and then restarted every single time the control panel regains focus. If you saver takes a long time to start up this will look ugly, and you should look for ways to speed up the execution of your saver. One important thing is to check that you saver does not require any DLLs to be rebased. (Read about rebasing in WIN32.HLP). If your saver has a slow animation of some sort, you might consider saving its current state in the registry so that, next time it is started, it can resume from where it left off.

Be especially careful about any activities which might change the focus. If your saver pops up a top-level window on startup, this will mess up the focus: the control panel will regain focus, and your saver will be started again, and it will pop up another top-level window, and so on. This makes it very difficult for you to debug your preview mode. For debugging of the preview window you can use a utility called ScrPrev which runs its own preview window and is a little less temperamental.

Command-line arguments

The behaviour expected of the saver depends on the command-line arguments it is given. The code snippet below shows how you might parse the command line. If the command line arguments are invalid, then the saver should terminate immediately without doing anything. When parsing command-line options, note that the letters may appear as lower-case or upper-case, and that there might be either a forward slash or a hyphen prefixing the letter, and that there may be either a space or a colon after the letter. Thus, you should respond to /p #### and -P:#### and all options in between.

WinMain code to parse command line

// First we define some global variables and types.
// TScrMode is a global variable storing the mode the saver should be running in.
// TSaverSettings is a class with settings of various sorts.
// ss is a global variable with these settings.
enum TScrMode {smNone,smConfig,smPassword,smPreview,smSaver};
TScrMode ScrMode=smNone;
class TSaverSettings; TSaverSettings *ss=NULL;

{ hInstance=h;
  char *c=GetCommandLine();
  if (*c=='\"') {c++; while (*c!=0 && *c!='\"') c++;}
  else {while( *c!=0 && *c!=' ') c++;}
  if (*c!=0) c++;
  while (*c==' ') c++;
  HWND hwnd=NULL;
  if (*c==0) {ScrMode=smConfig; hwnd=NULL;)
  { if (*c=='-' || *c=='/') c++;
    if (*c=='p' || *c=='P' || *c=='l' || *c=='L')
    { c++; while (*c==' ' || *c==':') c++;
      if ((strcmp(c,"scrprev")==0) || (strcmp(c,"ScrPrev")==0) ||
          (strcmp(c,"SCRPREV")==0)) hwnd=CheckForScrprev();
      else hwnd=(HWND)atoi(c);
    else if (*c=='s' || *c=='S')
    { ScrMode=smSaver;
    else if (*c=='c' || *c=='C')
    { c++; while (*c==' ' || *c==':') c++;
      if (*c==0) hwnd=GetForegroundWindow(); else hwnd=(HWND)atoi(c); ScrMode=smConfig;
    else if (*c=='a' || *c=='A')
    { c++; while (*c==' ' || *c==':') c++;
      hwnd=(HWND)atoi(c); ScrMode=smPassword;}
  // We create a global TSaverSettings here, for convenience.
  // It will get used by the config dialog and by the saver as it runs
  ss=new TSaverSettings(); ss->ReadGeneralRegistry(); ss->ReadConfigRegistry();
  if (ScrMode==smPassword) ChangePassword(hwnd);
  if (ScrMode==smConfig) DialogBox(hInstance,MAKEINTRESOURCE(DLG_CONFIG),
  if (ScrMode==smSaver || ScrMode==smPreview) DoSaver(hwnd);
  delete ss;
  return 0;

Code to find ScrPrev window

// ScrPrev is a freely available utility to make it easier to debug
// savers. Start the saver with argument /p scrprev and it will run its preview
// inside a ScrPrev window.
HWND CheckForScrprev()
{ HWND hwnd=FindWindow("Scrprev",NULL); // looks for the Scrprev class
  if (hwnd==NULL) // try to load it
    ZeroMemory(&si,sizeof(si)); ZeroMemory(&pi,sizeof(pi));
    si.lpReserved=NULL; si.lpTitle=NULL;
    si.dwFlags=0; si.cbReserved2=0; si.lpReserved2=0; si.lpDesktop=0;
    BOOL cres=CreateProcess(NULL,"Scrprev",0,0,FALSE,
    if (!cres) {Debug("Error creating scrprev process"); return NULL;}
    DWORD wres=WaitForInputIdle(pi.hProcess,2000);
    if (wres==WAIT_TIMEOUT)
    { Debug("Scrprev never becomes idle"); return NULL; 
    if (wres==0xFFFFFFFF)
    { Debug("ScrPrev, misc error after ScrPrev execution");return NULL;
  if (hwnd==NULL) {Debug("Unable to find Scrprev window"); return NULL;}
  if (hwnd==NULL) {Debug("Couldn't find Scrprev child"); return NULL;}
  return hwnd;

Definition of TSaverSettings class

class TSaverSettings
{ public:
  // Following are the general saver registry settings which we will read in
  DWORD PasswordDelay;   // in seconds
  DWORD MouseThreshold;  // in pixels
  BOOL  MuteSound;
  // Following are the configuration options particular to this saver
  BOOL  FlashScreen;
  // Following are variables which the saver uses while it runs
  HWND hwnd;             // Handle of the currently running saver window
  POINT InitCursorPos;   // Where the mouse started off
  DWORD InitTime;        // Time at which we started, in ms
  UINT  idTimer;         // a timer id, because this particular saver uses a timer
  BOOL  IsDialogActive;  // If dialog is active, we ignore certain messages
  BOOL  ReallyClose;     // for NT, so we know if a WM_CLOSE came from us or it.
  void ReadGeneralRegistry(); // General settings that apply to all savers
  void ReadConfigRegistry();  // Settings particular to this saver
  void WriteConfigRegistry();
  void CloseSaverWindow();    // A convenient way of closing the saver, if appropriate
  void StartDialog();         // We need special protection against dialogs when the
  void EndDialog();           // saver is running
TSaverSettings::TSaverSettings() {hwnd=NULL; ReallyClose=FALSE; idTimer=0;}

See also ReadConfigRegistry code, ReadGeneralRegistryCode.

Configuration dialog

The configuration dialog is just a dialog, like any other. Before it appears it sets up any user-configuration controls. If the user clicks OK, then it saves them. This particular example has only a single configuration option: a boolean value FlashScreen. In the code below we take advantage of the global variable ss, which points to an instance of the TSaverSettings class and which stores the configuration.

Resources for config dialog

CAPTION "Config dialog"
FONT 8, "MS Sans Serif"
    DEFPUSHBUTTON   "OK",IDOK,129,7,50,14
    PUSHBUTTON      "Cancel",IDCANCEL,129,24,50,14
    CONTROL         "Flash screen",IDC_FLASH,"Button",BS_AUTOCHECKBOX | 


BOOL CALLBACK ConfigDialogProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ switch (msg)
    { CheckDlgButton(hwnd,IDC_FLASH,ss->FlashScreen); return TRUE;
    { int id=LOWORD(wParam);
      if (id==IDOK)
      { ss->FlashScreen=(IsDlgButtonChecked(hwnd,IDC_FLASH)==BST_CHECKED);
      if (id==IDOK || id==IDCANCEL) EndDialog(hwnd,id);
    } break;
  return FALSE;

See also TSaverSettings code, Configuration registry code.

When and where to save configuration

The saver's configuration should be saved when the user clicks on OK in the configuration dialog. It should be saved in the registry in the standard location: HKEY_CURRENT_USER\Software\MyCompany\MyProduct\.

Code to read and write registry configuration

#include <regstr.h>

#define REGSTR_PATH_CONFIG  ("Software\\Lu\\Minimal Saver")

// This saver has a single user configuration option: FlashScreen
void TSaverSettings::ReadConfigRegistry()
{ FlashScreen=TRUE;
  LONG res; HKEY skey; DWORD valtype, valsize, val;
  if (res!=ERROR_SUCCESS) return;
    res=RegQueryValueEx(skey,"Flash Screen",0,&valtype,(LPBYTE)&val,&valsize);
    if (res==ERROR_SUCCESS) FlashScreen=val;
void TSaverSettings::WriteConfigRegistry()
{ LONG res; HKEY skey; DWORD val, disp;
  if (res!=ERROR_SUCCESS) return;
  RegSetValueEx(skey,"Flash Screen",0,REG_DWORD,(CONST BYTE*)&val,sizeof(val));

Whenever a user brings up the configuration dialog for a particular saver and clicks OK, then the changes to the configuration for that particular saver are written to the registry immediately. But the current choice of screen saver appears in the control panel itself and changes to it do not actually take effect, or get written to the registry, until the user clicks OK or Apply on the Desktop control panel itself! Likewise the password option.

So someone might select a saver and spend ages configuring it but then fail to close the control panel: and when they use hot corners to see the effect immediately Windows will not launch the saver they had so painstakingly configured, but instead will launch the previous saver! And then they go to the control panel and turn on password checking and click Preview and it doesn't ask for your password, but when you click Apply and then Preview it does! And then you turn off password checking and you click Preview but it still (under '95) asks you for a password, only you don't know what to do because you think that passwords are turned off and anyway you've forgotten it!

This might seem odd at first, but you might as well get used to it.

Plus! configuration

Windows Plus! introduced a couple of useful saver settings. They are stored in a single, central place in the registry, given by the path HKEY_CURRENT_USER\REGSTR_PATH_SETUP\Screen Savers. (REGSTR_PATH_SETUP is defined in regstr.h to be Software\Microsoft\Windows\CurrentVersion). This means that changes to any one saver will affect all savers. You don't have to implement any of these, but it would look better if you did.

Code to read general registry settings

#include <regstr.h>


void TSaverSettings::ReadGeneralRegistry()
{ PasswordDelay=15; MouseThreshold=50; IsDialogActive=FALSE;
  // default values in case they're not in registry
  LONG res; HKEY skey; DWORD valtype, valsize, val;
  if (res!=ERROR_SUCCESS) return;
    res=RegQueryValueEx(skey,"Password Delay",0,&valtype,(LPBYTE)&val,&valsize);
    if (res==ERROR_SUCCESS) PasswordDelay=val;
    res=RegQueryValueEx(skey,"Mouse Threshold",0,&valtype,(LPBYTE)&val,&valsize);
    if (res==ERROR_SUCCESS) MouseThreshold=val;
    res=RegQueryValueEx(skey,"Mute Sound",0,&valtype,(LPBYTE)&val,&valsize);
    if (res==ERROR_SUCCESS) MuteSound=val;

If all you want to do is read the values, then the above information is adequate. If additionally you want to write your configuration dialog to be able to change these values, or if you want to write a utility which can change them, then you need to worry about three extra configuration values. It is a real drag writing your own general configuration dialog. The author strongly advises you to use his ScrPlus library, which does it all automatically.

If you do offer a configuration dialog with the ability to change these settings, it is conventional to use a dialog box with two or more tabs. The first tab would have general settings, and subsequent tabs would have the settings for this particular saver. When the dialog is shown it would typically be started on its second page. An example resource file for the dialog is given below. (You'll have to implement it all, including the MonitorClass, yourself.) If you wish to provide context-help for the controls in the General tab, in response to WM_HELP and WM_CONTEXTHELP, then you can either use the help topics provided in the Plus!.hlp file (which comes with Windows Plus! only) or you can use the help file SCRPLUS.HLP which is available in the file along with source code for a minimal saver, and which is freely distributable. Or you can of course write your own help file.

Resource file for typical general config dialog

// Identifiers for the various controls
#define ID_DISMISSGROUP  3630
#define ID_THRESHOLD     3632
#define ID_WAITDESC      3633
#define ID_WAITTEXT      3634
#define ID_WAITBUDDY     3635
#define ID_WAITBOX       3636
#define ID_WAITMOREDESC  3637
#define ID_SAGEOK        3638
#define ID_SAGEBAD       3639
#define ID_MONITOR       3640
#define ID_MUTE          3641
#define ID_ACTIVECONFIG  3643
#define ID_ABOUT         3650

// Help topics in Plus!.hlp and SCRPLUS.HLP
#define PLUSHELP_CORNERS       3100
#define PLUSHELP_THRESHOLD     3101
#define PLUSHELP_COPYRIGHT     3103 
#define PLUSHELP_PREVIEW       3104
#define PLUSHELP_MUTE          3105

// Relation between controls in dialog, and help topic
static DWORD GeneralHelpIds[] = {
  ID_MUTE,           PLUSHELP_MUTE,  0,0};

CAPTION "General"
FONT 8,"MS Sans Serif"
 CONTROL "You can display the screen saver immediately or prevent it from\n"
         "appearing at all,by moving the mouse pointer to a corner on \n"
         "the screen. Click the corners you want to use.",
 CONTROL "The system agent must be active in order for you to display \n"
         "the screen saver immediately by moving the mouse \n"
         "pointer to a corner on the screen.",
 CONTROL "Options for dismissing the screen saver",
 CONTROL "&Mouse sensitivity",
 CONTROL "&Wait",
 CONTROL "before requiring a password",ID_WAITMOREDESC,"STATIC",
 CONTROL "Always require password",ID_WAITSUMMARY,"STATIC",
 CONTROL "Control corners",ID_MONITOR,MonitorClassName,
 CONTROL "Mute Sound",ID_MUTE,"button",

Dialog for configuring the general settings Dialog for configuring saver settings

Change-password dialog

This dialog gets called when the saver was started with the /a #### command line argument. This happens in Windows '95 and Plus! when the user clicks on the Change Password button in the Display Properties control panel. Under NT, the system manages all passwords itself and this argument will never be given.

ChangePassword code

void ChangePassword(HWND hwnd)
{ // This only ever gets called under '95, when started with the /a option.
  HINSTANCE hmpr=::LoadLibrary("MPR.DLL");
  if (hmpr==NULL) {Debug("MPR.DLL not found: cannot change password.");return;}
      (LPCSTR lpcRegkeyname,HWND hwnd,UINT uiReserved1,UINT uiReserved2);
  if (PwdChangePassword==NULL)
  { FreeLibrary(hmpr);
    Debug("PwdChangeProc not found: cannot change password");return;
  PwdChangePassword("SCRSAVE",hwnd,0,0); FreeLibrary(hmpr);

This function makes use of the PwdChangePassword function in MPR.DLL. If you wanted to use your own password configuration system under '95 or Plus!, you could simply write your own ChangePassword routine and your own password verification routine. The situation is more difficult under NT.

Running full-screen and preview

A preview window is invoked with the argument /p ####.

The behaviour of a full-screen window /s is more complex:

In the code below we use the same window procedure for both the full-screen and preview modes of the saver. The global variable ScrMode, set in WinMain, determines whether it should respond to things like mouse clicks.

Useful functions while running saver

// The function CloseSaverWindow uses ReallyClose, as part of a workaround to deal
// with the WM_CLOSE messages that get sent automatically under NT.
void TSaverSettings::CloseSaverWindow()
{ ReallyClose=TRUE; PostMessage(hwnd,WM_CLOSE,0,0);

// When a dialog is up, the IsDialogActive flag prevents things like
// mouse-movement and key presses from terminating the saver. When a dialog
// closes, the mouse origin is re-read for threshold-detection purposes.
void TSaverSettings::StartDialog()
{ IsDialogActive=TRUE; SendMessage(hwnd,WM_SETCURSOR,0,0);
void TSaverSettings::EndDialog()
{ IsDialogActive=FALSE; SendMessage(hwnd,WM_SETCURSOR,0,0);

DoSaver code

// We refer to a global value DEBUG which has have been #defined to
// either TRUE or FALSE. If TRUE then we run the saver in only a quarter of
// the screen without the WS_EX_TOPMOST flag: this makes it easier to switch
// back and forth between the debugger and the saver.

void DoSaver(HWND hparwnd)
  if (ScrMode==smPreview)
  { RECT rc; GetWindowRect(hparwnd,&rc);
    int cx=rc.right-rc.left,;  
  { int cx=GetSystemMetrics(SM_CXSCREEN), cy=GetSystemMetrics(SM_CYSCREEN);
    DWORD exstyle, style;
    if (DEBUG) { cx=cx/3; cy=cy/3; exstyle=0; style=WS_OVERLAPPEDWINDOW|WS_VISIBLE;}
    else {exstyle=WS_EX_TOPMOST; style=WS_POPUP|WS_VISIBLE;}
  if (hScrWindow==NULL) return;
  UINT oldval;
  if (ScrMode==smSaver) SystemParametersInfo(SPI_SCREENSAVERRUNNING,1,&oldval,0);
  MSG msg;
  while (GetMessage(&msg,NULL,0,0))
  { TranslateMessage(&msg);
  if (ScrMode==smSaver) SystemParametersInfo(SPI_SCREENSAVERRUNNING,0,&oldval,0);


LRESULT CALLBACK SaverWindowProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ switch (msg)
  { case WM_CREATE:
    { Debug("WM_CREATE... reading initial position and time, and starting timer");
      GetCursorPos(&(ss->InitCursorPos)); ss->InitTime=GetTickCount();
    } break;
    case WM_TIMER:
    { if (ss->FlashScreen)
      { HDC hdc=GetDC(hwnd); RECT rc; GetClientRect(hwnd, &rc); 
    } break;
    { if (ScrMode==smSaver && !ss->IsDialogActive &&
          LOWORD(wParam)==WA_INACTIVE && !DEBUG)
      { Debug("WM_ACTIVATE: about to inactive window, so sending close");
    } break;
    case WM_SETCURSOR:
    { if (ScrMode==smSaver && !ss->IsDialogActive && !DEBUG)
      { Debug("WM_SETCURSOR: Saver is running at the moment: so no cursor");
      { Debug("WM_SETCURSOR: dialog up, or Preview or Debug mode: normal cursor");
    } break;
    { if (ScrMode==smSaver && !ss->IsDialogActive)
      { Debug("WM_BUTTONDOWN: sending close");
    } break;
    case WM_MOUSEMOVE:
    { if (ScrMode==smSaver && !ss->IsDialogActive && !DEBUG)
      { POINT pt; GetCursorPos(&pt);
        int dx=pt.x-ss->InitCursorPos.x; if (dx<0) dx=-dx;
        int dy=pt.y-ss->InitCursorPos.y; if (dy<0) dy=-dy;
        if (dx>(int)ss->MouseThreshold || dy>(int)ss->MouseThreshold)
        { Debug("WM_MOUSEMOVE: moved beyond threshold, sending close");
    } break;
    { if (ScrMode==smSaver)
      { if (wParam==SC_SCREENSAVE)
        { Debug("WM_SYSCOMMAND: gobbling up SC_SCREENSAVE to stop new saver running.");
          return FALSE;
        if (wParam==SC_CLOSE && !DEBUG)
        { Debug("WM_SYSCOMMAND: gobbling up SC_CLOSE");
          return FALSE;
    } break;
    case (WM_CLOSE):
    { if (ScrMode==smSaver && ss->ReallyClose && !ss->IsDialogActive)
      { Debug("WM_CLOSE: maybe we need a password");
        BOOL CanClose=TRUE;
        if (GetTickCount()-ss->InitTime > 1000*ss->PasswordDelay)
        { ss->StartDialog(); CanClose=VerifyPassword(hwnd); ss->EndDialog();
        if (CanClose) {Debug("WM_CLOSE: doing a DestroyWindow"); DestroyWindow(hwnd);}
        else {Debug("WM_CLOSE: but failed password, so doing nothing");}
      if (ScrMode==smSaver) return FALSE;
      // return FALSE here so that DefWindowProc doesn't get called,
      // because it would just DestroyWindow itself
    } break;
    case WM_DESTROY:
    { if (ss->idTimer!=0) KillTimer(hwnd,ss->idTimer); ss->idTimer=0;
    } break;
  return DefWindowProc(hwnd,msg,wParam,lParam);

Verify Password dialog

Under NT, you do not have to worry about password verification. Under '95 and Plus! you must do password detection yourself. The routine VerifyPassword below is called in response to WM_CLOSE, after first checking that more than PasswordDelay seconds have elapsed. Before making the call to VerifyPassword we first did ss->StartDialog() and after we did ss->EndDialog().


BOOL VerifyPassword(HWND hwnd)
{ // Under NT, we return TRUE immediately. This lets the saver quit,
  // and the system manages passwords. Under '95, we call VerifyScreenSavePwd.
  // This checks the appropriate registry key and, if necessary,
  // pops up a verify dialog
  OSVERSIONINFO osv; osv.dwOSVersionInfoSize=sizeof(osv); GetVersionEx(&osv);
  if (osv.dwPlatformId==VER_PLATFORM_WIN32_NT) return TRUE;
  HINSTANCE hpwdcpl=::LoadLibrary("PASSWORD.CPL");
  if (hpwdcpl==NULL) {Debug("Unable to load PASSWORD.CPL. Aborting");return TRUE;}
  if (VerifyScreenSavePwd==NULL)
  { Debug("Unable to get VerifyPwProc address. Aborting");
    FreeLibrary(hpwdcpl);return TRUE;
  Debug("About to call VerifyPwProc");
  BOOL bres=VerifyScreenSavePwd(hwnd); FreeLibrary(hpwdcpl);
  return bres;

String resource

Under NT, if the saver has a string resource with ID 1, then this string is used as the description line for the saver in the control panel. If it does not have this string, or of the saver is running under '95 or Plus!, then the long filename of the saver is used to describe it. The name should have25 characters or fewer.

String resources

    1 "Minimal screen saver"

Final touches

The above code samples referred to a global constant DEBUG and to a function Debug("..."). I can guarantee that you will encounter strange bugs in your program, and that the only way you solve them is by liberal use of a Debug function. It is especially useful to record which windows-messages get sent to the saver window.

Debug code


void Debug(char *c) {OutputDebugString(c); OutputDebugString("\n");}
void Debug(char *) {}

Explorer uses the first icon resource in the saver as its icon. Either create your own icon from scratch, or base it upon one of the standard saver icons:

Next, compile the saver and rename it with the suffix .scr. Copy it into the windows directory: then it will appear in the Display Properties control panel. See also Installation of a saver.

Interaction between saver and system

This chapter has notes on preventing ctrl-alt-delete, on savers that change mode, on hot corners and on installing a saver. It includes complete source code for a self-extracting saver installer.

How to tell whether a saver is currently running

Under '95, it is easy to tell whether a saver is currently running: use SPI_SCREENSAVERRUNNING and check the returned value in oldval. Under NT it is more difficult because no value is returned in oldval. Maybe we could traverse the list of desktops to check whether a saver desktop was active. The following code is more ugly but simpler.


BOOL IsSaverRunning()
{ BOOL isNT;
  OSVERSIONINFO ovi; ovi.dwOSVersionInfoSize=sizeof(ovi);
  GetVersionEx(&ovi); isNT=(ovi.dwPlatformId==VER_PLATFORM_WIN32_NT);
  if (!isNT)
  { UINT dummy, srunning=0;
    BOOL res=SystemParametersInfo(SPI_SCREENSAVERRUNNING,0,&srunning,0);
    if (srunning==0) return FALSE; else return TRUE;
  // That works fine under '95. But NT simply doesn't return the old value to us.
  // Hence we need some magic.
  HWND hfw=GetForegroundWindow();
  // Sometimes hfw is null. I presume this is because a screensaver is running on another
  // desktop. It's sometimes also cause by the user having just exited a game like X-wing.
  // I don't know how to check about desktops. So, I'll assume it's because the screensaver
  // is running. This means that, immediately after you exit X-wing, you won't be able to
  // detect a saver. No great loss.
  if (hfw==NULL) return TRUE;
  LONG wl=GetWindowLong(hfw,GWL_STYLE);
  if ((wl&0xF0000000)!=WS_POPUP|WS_VISIBLE) return FALSE;
  RECT rc; GetWindowRect(hfw,&rc);
  if (rc.right-rc.left!=GetSystemMetrics(SM_CXSCREEN) ||!=GetSystemMetrics(SM_CYSCREEN)) return FALSE;
  return TRUE;

To launch the current saver

Use the following code should you wish to launch the currently selected saver.

Code to launch current saver

// Code to launch saver full-screen
if (IsScreensaverRunning()) return;
// We don't want to try and set it running again.
HWND hfw=GetForegroundWindow();
if (hfw==NULL) DefWindowProc(hwnd,WM_SYSCOMMAND,SC_SCREENSAVE,0);
else PostMessage(hfw,WM_SYSCOMMAND,SC_SCREENSAVE,0);

// Code to launch configuration dialog
char scr[MAX_PATH];
DWORD res=GetPrivateProfileString("boot","SCRNSAVE.EXE","",scr,MAX_PATH,"system.ini");
ZeroMemory(&si,sizeof(si)); ZeroMemory(&pi,sizeof(pi));
char c[MAX_PATH]; wsprintf(c,"\"%s\" /c",scr);
BOOL res=CreateProcess(scr,c,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi);
if (res) return IDOK; else return IDCANCEL;

Preventing ctrl-alt-delete

One of the jobs of a saver is to protect the computer from unauthorized access, in case it has been left alone for a time. It is obviously necessary to disable such system keys as ctrl-alt-delete and alt-tab and the Windows key.

Under Windows '95 and Plus!, you must disable these keys by calling SystemParametersInfo(SPI_SCREENSAVERRUNNING,TRUE,..). After the saver has finished you re-enable them by calling SystemParametersInfo(SPI_SCREENSAVERRUNNING,FALSE,..);. Under NT the system keys get disabled automatically, but it's a good idea to call SystemParametersInfo just in case it has other undocumented side-effects.

The following section points out a problem that may occur with ctrl-alt-delete if using DirectDraw, and gives a solution.

Savers running in different screen modes

Changing mode is a jolly useful thing to have in a saver. After all, we know that the saver is going to be running full-screen anyway and hence that we're not going to mess up appearance of the rest of the display. And if the saver runs at a lower screen resolution then animations can often be performed far quicker.

If you use ChangeDisplaySettings or pDirectDraw->SetDisplayMode then a couple of spurious WM_MOUSEMOVE messages get generated as the mouse settles into its new position. To complicate matters, these messages do not get sent during the mode-change call but after. There are a few possible solutions, all unpleasant: you could count down and ignore the first five WM_MOUSEMOVE messages; or you could ignore all such messages that occur within the five seconds after changing mode. It will help greatly if you use some Debug function to display a record of every single window message that gets sent to your window during the change-mode.

Under '95, dialogs such as the password dialog must be shown by the saver itself. If you are running at 320x200 or 320x240 then the GDI cannot draw dialogs onto the screen. Just before showing the dialog you will have to change into a more respectable screen mode, and just after you will have to restore the screen to what it was before. This often looks ugly. You might try to copy the screen contents as they were in the full-screen low-resolution mode and then use them as a bitmap background in the respectable mode.

If you use DirectDraw to change modes, the act of changing out of full-screen mode will re-enable the system keys. You will have to call SystemParametersInfo(SPI_SCREENSAVERRUNNING,TRUE,...) to disable them again.

Properties of savers in the Explorer

When you right-click on a saver in the Explorer, three options appear in the context menu.

Hot corners

Windows Plus! introduced hot corners: if you move your mouse into some corners then the currently selected saver will start immediately; in other corners, the saver is prevented from running. To get hot corners you need some external program running which provides Hot Corner Services. One such program comes with Plus!, in the file SAGE.DLL. If you want to distribute a saver to be used by people with Windows '95 and NT as well, and you want them to have hot corners, you will have to give them a third-party hot corner program. The author has written one such program, ScrHots, which works on all Win32 platforms and may be freely distributed by anyone. A copy of ScrHots is included in the file, which also includes source code for a self-extracting screen saver installer.

The best use for hot corners is in interactive savers, such as a puzzle saver or an arcade-game saver. The user might be bored for a few minutes waiting for a download to finish, or might be fiddling with the computer while making a telephone call. Imagine how easy it is for the user simply to move their mouse to the top left corner of the screen and have your program run immediately!

The section on installation below includes sample code for installing ScrHots. Here in this section we give code which works both for ScrHots and for SAGE.DLL to interact with the hot corner services.

Hot corner code

// CheckHots: this routine checks for Hot Corner services.
// It first tries with SAGE.DLL, which comes with Windows Plus!
// Failint this it tries with ScrHots, a third-party hot-corner
// service program written by the author that is freely
// distributable and works with NT and '95.
BOOL CheckHots()
  HINSTANCE sagedll=LoadLibrary("Sage.dll");
  if (sagedll!=NULL)
    BOOL res=FALSE;
    if (detectproc!=NULL) res=detectproc();
    if (res) return TRUE;
  HINSTANCE hotsdll=LoadLibrary("ScrHots.dll");
  if (hotsdll!=NULL)
    BOOL res=FALSE;
    if (detectproc!=NULL) res=detectproc();
    if (res) return TRUE;
  return FALSE;

// NotifyHots: if you make any changes to the hot corner
// information in the registry, you must call NotifyHots
// to inform the hot corner services of your change.
void __fastcall NotifyHots()
  HINSTANCE sagedll=LoadLibrary("Sage.DLL");
  if (sagedll!=NULL)
    if (changedproc!=NULL) changedproc();
  HINSTANCE hotsdll=LoadLibrary("ScrHots.dll");
  if (hotsdll!=NULL)
    if (changedproc!=NULL) changedproc();

Currently selected saver

The currently selected saver is stored in SYSTEM.INI in the [boot] section.
Under '95 and Plus!, this corresponds to an actual file in the Windows directory called SYSTEM.INI. Under NT the values are actually stored in the registry but you should still use Get/WritePrivateProfileString as these calls are automatically mapped to the registry. The filename must be a short filename.

To change the currently selected saver you must not only change the value mentioned above; but also cause a WM_WININICHANGED message to be sent. This will inform the rest of the setting that the value has changed. In particular, it means that the next time the Display Properties dialog appears, it will be correct.

Code for the currently selected saver

// To get the currently selected saver:
char CurSav[MAX_PATH];
DWORD res=GetPrivateProfileString("boot","SCRNSAVE.EXE","",
if (res==0 || strcmp(scrt,"")==0) {..} // Currently selected saver is 'none'

// To change the currently selected saver:
char ShortName[MAX_PATH];
// that sends a WM_WININICHANGE so that DisplayProperties dialog knows we've changed.
// It also enables screensaving.

Installation of a saver

It used to be that the correct way to install a saver was to copy it into the Windows directory and use the command ShellExecute(hwnd,"install","c:\windows\mysaver",NULL,NULL,SW_SHOWNORMAL); This would bring up the Display Properties dialog on the screen saver page, with your saver currently selected. But the Win'98 preview and IE4 for Win'95 introduced a faulty version of DESK.CPL which causes an address exception if you try to execute the above command. Source code for a function ExecutePreviewDialog is given below as a workaround.

Note that the windows directory is the corret place to install a saver. This is because GetWindowsDirectory() will always return a directory to which you have write-access. You should not install a saver into the system directory because on many installations (such as shared network installations) it is read-only.

It is possible to install a saver into a different directory. The Display Properties dialog actually creates its list of possible savers from three locations: the directory of the currently selected saver; the Windows directory; and the System directory. You might choose to install a couple of theme savers in a theme directory so that they are only visible when the user selects your theme.

The essence of a saver is that it should be easy and fun to use, and easy and fun to install. If at all possible you should have a single .scr file with no additional files. Even if you want to have additional bitmaps or JPEGs with your saver, these might as well be deployed as resources in the .scr file. It also makes it much easier for the user if you deploy your saver as a single self-extracting .exe file which copies the appropriate files into the appropriate places and installs the saver.

You can download the file This contains complete source code for a self-extracting saver installer, and you are free to modify and distribute it as you wish. It installs both ScrHots and your saver into the system, and pops up a preview dialog when installation is complete. The code is generic and can be used to install any saver merely by making a few changes to the resource file. You may use the code however you wish.

If you simply want an alternative to ShellExecute("install",..) rather than a full-blown self-extracting installer, you might use the following workaround. Rather than popping up the Display Properties proper, this code pops up its own dialog with a preview of the saver. This code is part of the above-mentioned installer.

Code for ExecutePreviewDialog

// Call the following two procedures in your installation routine.

// SelectAsDefault: makes scr the currently selected saver
void SelectAsDefault(char *scr)
{ char sscr[MAX_PATH];
  // that sends a WM_WININICHANGE so that DisplayProperties
  // dialog knows we've changed

// ExecutePreviewDialog: displays a dialog with a preview running inside it.
int ExecutePreviewDialog(HWND hwnd,char *scr)
{ typedef struct {DLGITEMTEMPLATE dli; WORD ch; WORD c; WORD t; WORD dummy;
                  WORD cd;} TDlgItem;
  typedef struct {DLGTEMPLATE dlt; WORD m; WORD c; WCHAR t[8]; WORD pt; WCHAR f[14];
                  TDlgItem ia; TDlgItem ib; TDlgItem ic;} TDlgData;
  TDlgData dtp={{DS_MODALFR