现在的位置: 首页 > 综合 > 正文

How to write a 32bit screen saver

2013年07月20日 ⁄ 综合 ⁄ 共 47104字 ⁄ 字号 评论关闭

看到非常详细的一片资料文章,收藏。

© 1997-1999 Lucian Wischik.

This article describes the fundamental nuts and bolts of writing a saver. I have also written a second article with a more useful higher-level overview, and full source code for several example savers.

Overview

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

  • To avoid phosphor burn caused by static images left on the screen. (No longer relevant with today's monitors, but this point remains a matter of tradition and honour!)
  • To protect sensitive information left on the screen
  • To present a uniform professional corporate logo on unattended office-place computers
  • To advertise
  • To have fun.

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 minimal.zip, 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.

  • System idle behaviour:
    When the computer has been idle for a time, and as long as no computer-based-training (CBT) windows are present, the system sends a WM_SYSCOMMAND message to the current foreground window with argument SC_SCREENSAVE. If the active window is not a windows application, or if it gobbles up the message, then nothing further happens. But if the message gets passed on to DefWindowProc then the saver gets executed full-screen.
    DefWindowProc(WM_SYSCOMMAND,SC_SCREENSAVE) behaves as follows: If there is no currently selected saver in system.ini under the section [boot] with key SCRNSAVE.EXE=, then the message is ignored. If there is a saver listed there, then it is executed full-screen.
    If running NT, a separate saver desktop is first created with no windows on it. This desktop still has the user's background. The saver runs on this isolated desktop. This means that effects such as corrupting the currently visible windows will not work under NT. With Windows '95 and Plus!, the saver simply executes on the main desktop. In both cases, the saver is started with the argument /s at the IDLE_PRIORITY_CLASS.
  • Display Properties preview:
    Whenever the Display Properties control panel is displaying the screen saver tab, and the dialog gains focus, it runs the currently selected saver with argument /p ####, where #### is the decimal representation of the HWND of a parent window. This parent window is the colour of the desktop and it fills the little preview monitor on that dialog page. Your saver should create a child window of it, of the same size, and display itself in this child window.
    Under Windows '95, if the Display Properties control panel regains focus at any time, then the preview window is first destroyed before the current saver is started in preview mode all over again. If it was your saver that was running as the preview, it should respond to the destruction of its window by terminating. Under NT, this does not happen.
    Also, obviously, when a different saver is selected in the control panel, then the previous one that was running in the little preview monitor will be destroyed.
  • Configuration dialog:
    If the user clicks on the Settings button, then the preview window is destroyed (and so the instance of your saver that was running in preview mode should terminate yourself). Next, the current saver is executed with the argument /c. It should respond to this by executing its dialog as a child of the current foreground window. Once this dialog is finished, the preview inside the monitor will be set running all over again. (This is so that it has the opportunity to re-load any options that might have been changed). If you click on the Configure button, and drag the configuration dialog away, you'll see that the little monitor with preview in the Display Properties control panel is empty.
  • Change-password dialog:
    Under '95 and Plus!, if the user clicks on the 'Change Password' button in the Display Properties control panel, the preview window is first destroyed. Then the current saver is executed with argument /a #### where the #### is a decimal representation of the the HWND of the Display Properties dialog. The saver should respond to this by displaying the standard system Change-Password dialog, or by displaying its own, as a child of ####. Once the dialog has finished, the preview inside the monitor will be set running again.
    Under NT, passwords are managed by the system and the saver is not expected to handle this argument.
  • Full-screen preview:
    When the user clicks on the Preview button in the Display Properties dialog, the preview window is destroyed. Then the current saver is executed with argument /s on the current desktop. The saver should respond to this as before by running in full-screen mode. Note that, even under NT, in this case the saver is run on the normal desktop.
  • Test:
    When the user double-clicks on your saver's icon it gets executed in Test mode. This is exactly the same as the full-screen preview described immediately above, except that nothing is done to the preview in the Display Properties control panel if it should happen to be active.
  • Config:
    When the user right-clicks on your saver's icon it gets executed with no arguments. It should respond by displaying its configuration dialog with NULL as its parent window. (This is different from the Config button mentioned above, where the saver was started with argument /c and was expected to use GetForegroundWindow() as its parent).
  • Third party applications:
    There are several third party applications that launch the saver in preview mode with /p ####, or which launch the saver's configuration dialog with /c. Other applications start the saver running as a saver with the command SendMessage(GetForegroundWindow(),WM_SYSCOMMAND,SC_SCREENSAVE,0);. No special coding is needed to handle these situations.

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.

  • /c, /c ####, or no arguments at all - in response to any of these the saver should pop up its configuration dialog. If there are no arguments then NULL should be used as the parent: this will end up happening if the user clicks on the saver in the Explorer. With /c as an argument, the dialog should use GetForegroundWindow() as its parent. With /c #### the saver should treat #### as the decimal representation of an HWND, and use this as its parent.
  • /s - this indicates that the saver should run itself as a full-screen saver.
  • /p ####, or /l #### - here the saver should treat the #### as the decimal representation of an HWND, should pop up a child window of this HWND, and should run in preview mode inside that window.
  • /a #### - this command-line argument is only ever used in '95 and Plus! The saver should pop up a password-configuration dialog as a child of ####.

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;
HINSTANCE hInstance=NULL;
class TSaverSettings; TSaverSettings *ss=NULL;

int WINAPI WinMain(HINSTANCE h,HINSTANCE,LPSTR,int)
{ 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;}
  else
  { 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);
      ScrMode=smPreview;
    }
    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),
                                   hwnd,ConfigDialogProc);
  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
  { STARTUPINFO si; PROCESS_INFORMATION pi;
    ZeroMemory(&si,sizeof(si)); ZeroMemory(&pi,sizeof(pi));
    si.cb=sizeof(si);
    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,
                    CREATE_NEW_PROCESS_GROUP|CREATE_DEFAULT_ERROR_MODE,0,0,&si,&pi);
    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;
    }
    hwnd=FindWindow("Scrprev",NULL);
  }
  if (hwnd==NULL) {Debug("Unable to find Scrprev window"); return NULL;}
  ::SetForegroundWindow(hwnd);
  hwnd=GetWindow(hwnd,GW_CHILD);
  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.
  TSaverSettings();
  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
DLG_CONFIG DIALOG DISCARDABLE  0, 0, 186, 95
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Config dialog"
FONT 8, "MS Sans Serif"
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,129,7,50,14
    PUSHBUTTON      "Cancel",IDCANCEL,129,24,50,14
    CONTROL         "Flash screen",IDC_FLASH,"Button",BS_AUTOCHECKBOX | 
                    WS_TABSTOP,23,13,56,10
END
ConfigDialogProc
BOOL CALLBACK ConfigDialogProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ switch (msg)
  { case WM_INITDIALOG:
    { CheckDlgButton(hwnd,IDC_FLASH,ss->FlashScreen); return TRUE;
    }
    `:
    { int id=LOWORD(wParam);
      if (id==IDOK)
      { ss->FlashScreen=(IsDlgButtonChecked(hwnd,IDC_FLASH)==BST_CHECKED);
        ss->WriteConfigRegistry();
      }
      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 

#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;
  res=RegOpenKeyEx(HKEY_CURRENT_USER,REGSTR_PATH_CONFIG,0,KEY_ALL_ACCESS,&skey);
  if (res!=ERROR_SUCCESS) return;
  valsize=sizeof(val);
    res=RegQueryValueEx(skey,"Flash Screen",0,&valtype,(LPBYTE)&val,&valsize);
    if (res==ERROR_SUCCESS) FlashScreen=val;
  RegCloseKey(skey);
}  
void TSaverSettings::WriteConfigRegistry()
{ LONG res; HKEY skey; DWORD val, disp;
  res=RegCreateKeyEx(HKEY_CURRENT_USER,REGSTR_PATH_CONFIG,0,NULL,
                     REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&skey,&disp);
  if (res!=ERROR_SUCCESS) return;
  val=FlashScreen;
  RegSetValueEx(skey,"Flash Screen",0,REG_DWORD,(CONST BYTE*)&val,sizeof(val));
  RegCloseKey(skey);
}

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.

  • Password Delay - if the saver is closed within this many seconds after it started, then we might as well not bother checking for a password. You should pay attention to this value in you own code under Windows '95 and Plus!, when you figure out whether or not you should display a password dialog. You cannot do anything with this value under NT. Password Delay is a DWORD.
  • Mouse Threshold - if the saver moves this many pixels away from its starting location, then this is probably just a small 'mouse shudder' and it should not make the saver close itself. You should check it in your WM_MOUSEMOVE code under '95, Plus! and NT. It is up to you whether to treat it as a circle, a square or a diamond. The code below treats it as a diamond. Mouse Threshold is a DWORD.
  • Mute Sound - whether or not the saver should produce sounds. Mute Sound is a BOOL.
Code to read general registry settings
#include 

#define REGSTR_PATH_PLUSSCR (REGSTR_PATH_SETUP "//Screen Savers")

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;
  res=RegOpenKeyEx(HKEY_CURRENT_USER,REGSTR_PATH_PLUSSCR,0,KEY_ALL_ACCESS,&skey);
  if (res!=ERROR_SUCCESS) return;
  valsize=sizeof(val);
    res=RegQueryValueEx(skey,"Password Delay",0,&valtype,(LPBYTE)&val,&valsize);
    if (res==ERROR_SUCCESS) PasswordDelay=val;
  valsize=sizeof(val);
    res=RegQueryValueEx(skey,"Mouse Threshold",0,&valtype,(LPBYTE)&val,&valsize);
    if (res==ERROR_SUCCESS) MouseThreshold=val;
  valsize=sizeof(val);
    res=RegQueryValueEx(skey,"Mute Sound",0,&valtype,(LPBYTE)&val,&valsize);
    if (res==ERROR_SUCCESS) MuteSound=val;
  RegCloseKey(skey);
}

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.

  • Mouse Threshold Index - 0, 1, 2 or 3 for "High,0", "Normal, 200", "Low,400" or "Ignore mouse movement,999999" (The text is what should appear in your configuration dialog; the number is what should appear in the Mouse Threshold registry value). It is up to you how to behave if some fool stored an incorrect value in this key.
  • Password Delay Index: 0 or 1 for whether the configuration dialog should be displaying a value in "seconds" or "minutes". Note that, irrespective of the value of this field, the Password Delay registry value always stores its delay in seconds.
  • Mouse Corners - a four-character string corresponding to the four courners of the screen (top left, top right, bottom right, bottom left). Each character is one of '-': don't do anything fancy in this corner, 'Y': activate the saver immediately if the mouse is left in this corner, or 'N': don't let the saver run if the mouse is in this corner. These things only work if some Hot Corner Services are running. Hot corners are generally configured with a monitor, as in the dialog below. It is considered good form to grey out the hot-corner dialog if the services are not currently running. More details on hot corners can be found below, along with a code sample.

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 minimal.zip 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_THRESHOLDDESC 3631
#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_MONITORSCREEN 3642
#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_PASSWORDDELAY 3102
#define PLUSHELP_COPYRIGHT     3103 
#define PLUSHELP_PREVIEW       3104
#define PLUSHELP_MUTE          3105

// Relation between controls in dialog, and help topic
static DWORD GeneralHelpIds[] = {
  ID_DISMISSGROUP,   PLUSHELP_THRESHOLD,
  ID_THRESHOLDDESC,  PLUSHELP_THRESHOLD,
  ID_THRESHOLD,      PLUSHELP_THRESHOLD,
  ID_WAITDESC,       PLUSHELP_PASSWORDDELAY,
  ID_WAITTEXT,       PLUSHELP_PASSWORDDELAY,
  ID_WAITBUDDY,      PLUSHELP_PASSWORDDELAY,
  ID_WAITBOX,        PLUSHELP_PASSWORDDELAY,
  ID_WAITMOREDESC,   PLUSHELP_PASSWORDDELAY,
  ID_SAGEOK,         PLUSHELP_CORNERS,
  ID_SAGEBAD,        PLUSHELP_CORNERS,
  ID_MONITOR,        PLUSHELP_CORNERS,
  ID_MUTE,           PLUSHELP_MUTE,  0,0};


DLG_GENERAL DIALOG 0,0,237,220
STYLE DS_MODALFRAME|WS_POPUP|WS_VISIBLE|WS_CAPTION|WS_SYSMENU
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.",
         ID_SAGEOK,"STATIC",SS_LEFT|WS_CHILD|WS_VISIBLE|WS_GROUP,13,8,282,43
 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.",
         ID_SAGEBAD,"STATIC",SS_LEFT|WS_CHILD|WS_VISIBLE|WS_GROUP,13,13,282,43
 CONTROL "Options for dismissing the screen saver",
         ID_DISMISSGROUP,"BUTTON",BS_GROUPBOX|WS_CHILD|WS_VISIBLE,7,154,223,47
 CONTROL "&Mouse sensitivity",
         ID_THRESHOLDDESC,"STATIC",SS_LEFT|WS_CHILD|WS_VISIBLE|WS_GROUP,13,169,58,12
 CONTROL "",ID_THRESHOLD,"COMBOBOX",
         CBS_DROPDOWNLIST|WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_TABSTOP,74,167,148,72
 CONTROL "&Wait",
         ID_WAITDESC,"STATIC",SS_RIGHT|WS_CHILD|WS_VISIBLE|WS_GROUP,13,184,16,12
 CONTROL "",ID_WAITTEXT,"EDIT",
         ES_LEFT|WS_CHILD|WS_VISIBLE|WS_BORDER|WS_TABSTOP,32,184,25,12
 CONTROL "Generic1",ID_WAITBUDDY,
         "msctls_updown32",54|WS_CHILD|WS_VISIBLE,57,184,11,36
 CONTROL "",ID_WAITBOX,"COMBOBOX",
         CBS_DROPDOWNLIST|WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_TABSTOP,74,184,50,36
 CONTROL "before requiring a password",ID_WAITMOREDESC,"STATIC",
          SS_LEFT|WS_CHILD|WS_VISIBLE|WS_GROUP,130,185,95,11
 CONTROL "Always require password",ID_WAITSUMMARY,"STATIC",
          SS_LEFT|WS_CHILD|WS_VISIBLE,13,184,282,11
 CONTROL "Control corners",ID_MONITOR,MonitorClassName,
          MS_CORNERS|WS_CHILD|WS_VISIBLE,108,82,20,20
 CONTROL "Mute Sound",ID_MUTE,"button",
          BS_AUTOCHECKBOX|WS_CHILD|WS_VISIBLE|WS_TABSTOP,11,202,65,15
}
LANGUAGE LANG_NEUTRAL,SUBLANG_NEUTRAL

Dialog for configuring the general settingsDialog 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;}
  typedef VOID (WINAPI *PWDCHANGEPASSWORD)
      (LPCSTR lpcRegkeyname,HWND hwnd,UINT uiReserved1,UINT uiReserved2);
  PWDCHANGEPASSWORD PwdChangePassword=
      (PWDCHANGEPASSWORD)::GetProcAddress(hmpr,"PwdChangePasswordA");
  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 ####.

  • It should create a window as a child of ####, and of the same size, and it should run in this window.
  • Whenever this window is destroyed, the saver should terminate itself.
  • When the mouse cursor is inside the window, it should be a standard arrow cursor.

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

  • The saver might be started with argument /s as a preview (from clicking the Preview button in the Display Properties control panel, or by double-clicking on the saver icon in the Explorer). Or it may be started with this argument when it is expected to run as a proper saver.
  • Before the saver starts running it should call SystemParametersInfo(SPI_SETSCREENSAVERRUNNING,TRUE,..);. After it has finished it should call SystemParametersInfo(SPI_SETSCREENSAVERRUNNING,FALSE,..);. This disables such system keys as ctrl-alt-delete and alt-tab.
  • The saver should create a window that is WS_POPUP and WS_EX_TOPMOST, and is the size of the entire screen. While the saver is running, and as long as no dialogs are active, there should be no cursor. (When debugging you should run without WS_EX_TOPMOST, and you should make the window a little smaller. This will make it easier to debug.)
  • The saver should keep running until it receives messages that would imply termination. These are mouse movement (in response to which the saver should check if the mouse has moved beyond a certain threshold), mouse and key presses, and WM_ACTIVATEAPP and WM_ACTIVATE messages indicating that the saver window is being deactivated. The response to all of these should be for the saver to close itself. (Unless it is an interactive saver, in which case it will respond to mouse and keyboard messages as it sees fit).
  • Under '95 and Plus, it is the responsibility of the saver itself to pop up a password-verification dialog in response to any of the above. This dialog should be a child of the current saver window. Source code to execute the normal system verify-password dialog is given below; you can also use your own password verification technology. If the user failed the password check then the saver should continue as before.
  • Under NT, the system itself monitors for all the above messages. If it receives any then it sends a WM_CLOSE message to the saver. This is normally fine. But if you are writing an interactive saver, or if you want your saver to handle mouse-movement threshold itself, then you need a way to ignore all those WM_CLOSE messages sent by the system and to pay attention only to those ones which you generated yourself. This is typically done with a flag BOOL reallyclose which you set to TRUE just before you send a close message.
  • Under NT, if the saver gets a close message for whatever reason (either because of your own checks or because of a WM_CLOSE from the system), then the saver should terminate immediately without doing any password verification. If the saver had been running just as a preview then the user can get back to their work. If the saver had been running as a proper full-screen saver then the screen will go blank and the system itself will put up a password dialog. If the user enters the password correctly then the regular desktop is restored. If the user fails to enter the correct password and the password dialog is left idle for some minutes, then the saver gets executed with argument /s all over again.

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);
  GetCursorPos(&InitCursorPos);
}
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)
{ WNDCLASS wc;
  wc.style=CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc=SaverWindowProc;
  wc.cbClsExtra=0;
  wc.cbWndExtra=0;
  wc.hInstance=hInstance;
  wc.hIcon=NULL;
  wc.hCursor=NULL;
  wc.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
  wc.lpszMenuName=NULL;
  wc.lpszClassName="ScrClass";
  RegisterClass(&wc);
  if (ScrMode==smPreview)
  { RECT rc; GetWindowRect(hparwnd,&rc);
    int cx=rc.right-rc.left, cy=rc.bottom-rc.top;  
    hScrWindow=CreateWindowEx(0,"ScrClass","SaverPreview",WS_CHILD|WS_VISIBLE,
                              0,0,cx,cy,hparwnd,NULL,hInstance,NULL);
  }
  else
  { 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;}
    hScrWindow=CreateWindowEx(exstyle,"ScrClass","SaverWindow",style,
                              0,0,cx,cy,NULL,NULL,hInstance,NULL);
  }
  if (hScrWindow==NULL) return;
  UINT oldval;
  if (ScrMode==smSaver) SystemParametersInfo(SPI_SETSCREENSAVERRUNNING,1,&oldval,0);
  MSG msg;
  while (GetMessage(&msg,NULL,0,0))
  { TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  if (ScrMode==smSaver) SystemParametersInfo(SPI_SETSCREENSAVERRUNNING,0,&oldval,0);
  return;
}
SaverWindowProc
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");
      ss->hwnd=hwnd;
      GetCursorPos(&(ss->InitCursorPos)); ss->InitTime=GetTickCount();
      ss->idTimer=SetTimer(hwnd,0,100,NULL);
    } break;
    case WM_TIMER:
    { if (ss->FlashScreen)
      { HDC hdc=GetDC(hwnd); RECT rc; GetClientRect(hwnd, &rc); 
        FillRect(hdc,&rc,GetSysColorBrush((GetTickCount()>>8)%25));
        ReleaseDC(hwnd,hdc);
      }
    } break;
    case WM_ACTIVATE: case WM_ACTIVATEAPP: case WM_NCACTIVATE:
    { if (ScrMode==smSaver && !ss->IsDialogActive &&
          LOWORD(wParam)==WA_INACTIVE && !DEBUG)
      { Debug("WM_ACTIVATE: about to inactive window, so sending close");
        ss->CloseSaverWindow();
      }
    } break;
    case WM_SETCURSOR:
    { if (ScrMode==smSaver && !ss->IsDialogActive && !DEBUG)
      { Debug("WM_SETCURSOR: Saver is running at the moment: so no cursor");
        SetCursor(NULL);
      }
      else
      { Debug("WM_SETCURSOR: dialog up, or Preview or Debug mode: normal cursor");
        SetCursor(LoadCursor(NULL,IDC_ARROW));
      }
    } break;
    case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_RBUTTONDOWN: case WM_KEYDOWN:
    { if (ScrMode==smSaver && !ss->IsDialogActive)
      { Debug("WM_BUTTONDOWN: sending close");
        ss->CloseSaverWindow();
      }
    } break;
    case WM_MOUSEMOVE:
    { if (ScrMode==smSaver && !ss->IsDialogActive && !DEBUG)
      { POINT pt; GetCursorPos(&pt);
        int dx=pt.x-ss->InitCursorPos.x; if (dxInitCursorPos.y; if (dy(int)ss->MouseThreshold || dy>(int)ss->MouseThreshold)
        { Debug("WM_MOUSEMOVE: moved beyond threshold, sending close");
          ss->CloseSaverWindow();
        }
      }
    } break;
    case WM_SYSCOMMAND:
    { 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;
      Debug("POSTQUITMESSAGE from WM_DESTROY!!");
      PostQuitMessage(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().

VerifyPassword
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;}
  typedef BOOL (WINAPI *VERIFYSCREENSAVEPWD)(HWND hwnd);
  VERIFYSCREENSAVEPWD VerifyScreenSavePwd;
  VerifyScreenSavePwd=
      (VERIFYSCREENSAVEPWD)GetProcAddress(hpwdcpl,"VerifyScreenSavePwd");
  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
STRINGTABLE DISCARDABLE 
BEGIN
    1 "Minimal screen saver"
END
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
#define DEBUG FALSE

#if DEBUG
void Debug(char *c) {OutputDebugString(c); OutputDebugString("/n");}
#else
void Debug(char *) {}
#endif

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:

  • standard icon #1small standard icon #1 - Hippy age: strange visions of the future
  • standard icon #2small standard icon #2 - Industrial age: blocky machinery
  • standard icon #3small standard icon #3 - Information age: clean, simple and elegant

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, '98 and NT5 it is easy to tell whether a saver is currently running: use SPI_GETSCREENSAVERRUNNING and check the returned value. Under NT4 it doesn't work so we employ a workaround. See also MS KB article Q150785.

IsSaverRunning
BOOL IsSaverRunning()
{ BOOL srunning=FALSE;
  BOOL res=SystemParametersInfo(SPI_GETSCREENSAVERRUNNING,0,&srunning,0);
  if (res) {if (srunning==0) return FALSE; else return TRUE;}
  // That works fine under '95, '98 and NT5. But not older versions of NT.
  // Hence we need some magic.
  HDESK hDesk=OpenDesktop(TEXT("screen-saver"), 0, FALSE, MAXIMUM_ALLOWED);
  if (hDesk!=NULL) {CloseDesktop(hDesk); return TRUE;}
  if (GetLastError()==ERROR_ACCESS_DENIED) return TRUE;
  else return FALSE;
}
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");
STARTUPINFO si; PROCESS_INFORMATION pi;
ZeroMemory(&si,sizeof(si)); ZeroMemory(&pi,sizeof(pi));
si.cb=sizeof(si);
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_SETSCREENSAVERRUNNING,TRUE,..). After the saver has finished you re-enable them by calling SystemParametersInfo(SPI_SETSCREENSAVERRUNNING,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_SETSCREENSAVERRUNNING,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.

  • Test - the default, executes the saver with the /s argument. Under NT, because the saver gets executed directly rather than through DefWindowProc(WM_SYSCOMMAND,SC_SCREENSAVE,...), it runs on the normal desktop with no security features.
  • Configure - runs the saver with no arguments, in response to which it is expected to pop up its configuration dialog with NULL parent.
  • Install - Contrary to expectation, this command does not copy the saver into the system directory or anywhere useful. All it does is open up the Display Properties control panel with this saver selected. This is the command you would expect to use in your installation programs. However, if your users have installed the Win'98 preview on top of Windows '95, then this command gets broken and will no longer work. The damage persists even if they try to uninstall the Win'98 preview. Therefore you have to use a different strategy for your installation programs: see Installation below.
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 install.zip, 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()
{ typedef BOOL (WINAPI *SYSTEMAGENTDETECT)();
  HINSTANCE sagedll=LoadLibrary("Sage.dll");
  if (sagedll!=NULL)
  { SYSTEMAGENTDETECT detectproc=(SYSTEMAGENTDETECT)
        GetProcAddress(sagedll,"System_Agent_Detect");
    BOOL res=FALSE;
    if (detectproc!=NULL) res=detectproc();
    FreeLibrary(sagedll);
    if (res) return TRUE;
  }
  HINSTANCE hotsdll=LoadLibrary("ScrHots.dll");
  if (hotsdll!=NULL)
  { SYSTEMAGENTDETECT detectproc=(SYSTEMAGENTDETECT)
        GetProcAddress(hotsdll,"System_Agent_Detect");
    BOOL res=FALSE;
    if (detectproc!=NULL) res=detectproc();
    FreeLibrary(hotsdll);
    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()
{ typedef VOID (WINAPI *SCREENSAVERCHANGED)();
  HINSTANCE sagedll=LoadLibrary("Sage.DLL");
  if (sagedll!=NULL)
  { SCREENSAVERCHANGED changedproc=(SCREENSAVERCHANGED)
        GetProcAddress(sagedll,"Screen_Saver_Changed");
    if (changedproc!=NULL) changedproc();
    FreeLibrary(sagedll);
  }
  HINSTANCE hotsdll=LoadLibrary("ScrHots.dll");
  if (hotsdll!=NULL)
  { SCREENSAVERCHANGED changedproc=(SCREENSAVERCHANGED)
        GetProcAddress(hotsdll,"Screen_Saver_Changed");
    if (changedproc!=NULL) changedproc();
    FreeLibrary(hotsdll);
  }
}
Currently selected saver

The currently selected saver is stored in SYSTEM.INI in the [boot] section.

[boot]
...
SCRNSAVE.EXE=C:/WINDOWS/FLAME.SCR

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","",
                                  CurSav,MAX_PATH,"system.ini");
if (res==0 || strcmp(scrt,"")==0) {..} // Currently selected saver is 'none'

// To change the currently selected saver:
char ShortName[MAX_PATH];
GetShortPathName(CurSav,ShortName,MAX_PATH);
WritePrivateProfileString("boot","SCRNSAVE.EXE",ShortName,"system.ini");
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE,TRUE,NULL,TRUE);
// 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 install.zip. 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(savpath);
ExecutePreviewDialog(hwnd,savpath);


// SelectAsDefault: makes scr the currently selected saver

oid SelectAsDefault(char *scr)
{ char sscr[MAX_PATH];
  GetShortPathName(scr,sscr,MAX_PATH);
  WritePrivateProfileString("boot","SCRNSAVE.EXE",sscr,"system.ini");
  SystemParametersInfo(SPI_SETSCREENSAVEACTIVE,TRUE,NULL,TRUE);
  // 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_MODALFRAME|DS_3DLOOK|DS_SETFONT|DS_CENTER|WS_POPUP|
     WS_CAPTION|WS_SYSMENU|WS_VISIBLE,0,3,0,0,278,196},
     0,0,L"Preview",8,L"MS Sans Serif",
     {{BS_DEFPUSHBUTTON|WS_CHILD|WS_VISIBLE,0,113,175,50,14,IDOK},0xFFFF,0x0080,0,0,0},
     {{SS_BLACKRECT|WS_CHILD|WS_VISIBLE,0,7,7,264,152,3},0xFFFF,0x0082,0,0,0},
     {{SS_CENTER|WS_CHILD|WS_VISIBLE,0,7,162,264,8,2},0xFFFF,0x0082,0,0,0}};
  return DialogBoxIndirectParam(hInstance,(DLGTEMPLATE*)&dtp,hwnd,
                                ExecutePreviewDialogProc,(LONG)scr);
}
LRESULT CALLBACK BlackWindowProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ LONG oldproc=GetWindowLong(hwnd,GWL_USERDATA);
  if (msg==WM_DESTROY) SetWindowLong(hwnd,GWL_WNDPROC,oldproc);
  if (msg==WM_PAINT)
  { PAINTSTRUCT ps; BeginPaint(hwnd,&ps);
    FillRect(ps.hdc,&ps.rcPaint,(HBRUSH)GetStockObject(BLACK_BRUSH));
    EndPaint(hwnd,&ps);return 0;
  }
  return CallWindowProc((WNDPROC)oldproc,hwnd,msg,wParam,lParam);
}
BOOL CALLBACK ExecutePreviewDialogProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ switch (msg)
  { case WM_INITDIALOG:
    { SetDlgItemText(hwnd,IDOK,"OK");
      SetDlgItemText(hwnd,2,"Screen saver succesfully installed!");
      HWND hPrev=GetDlgItem(hwnd,3);
      LONG oldproc=GetWindowLong(hPrev,GWL_WNDPROC);
      SetWindowLong(hPrev,GWL_USERDATA,oldproc);
      SetWindowLong(hPrev,GWL_WNDPROC,(LONG)BlackWindowProc);
      STARTUPINFO si; PROCESS_INFORMATION pi;
      ZeroMemory(&si,sizeof(si)); ZeroMemory(&pi,sizeof(pi));
      si.cb=sizeof(si); char *scr=(char *)lParam;
      char c[MAX_PATH]; wsprintf(c,"/"%s/" /p %i",scr,(int)hPrev);
      CreateProcess(scr,c,NULL,NULL,TRUE,IDLE_PRIORITY_CLASS,NULL,NULL,&si,&pi);
      return TRUE;
    }
    case WM_COMMAND:
    { int id=LOWORD(wParam); switch (id)
      { case IDOK: case IDCANCEL:
        { HWND hPrev=GetDlgItem(hwnd,3); HWND hChild=GetWindow(hPrev,GW_CHILD);
          if (hChild!=NULL) SendMessage(hChild,WM_CLOSE,0,0);
          hChild=GetWindow(hPrev,GW_CHILD); if (hChild!=NULL) DestroyWindow(hChild);
          EndDialog(hwnd,id); return TRUE;
        }
      }
    }
  }
  return FALSE;
}

Knowledge base

This chapter lists a few common problems and suggests the typical solution.

How to debug a saver

Ninety percent of saver errors can be solved by keeping a log of every single message that gets sent to your saver window procedure.

DebugMessage code
#define DEBUG TRUE

#if DEBUG
void Debug(char *c) {OutputDebugString(c); OutputDebugString("/n");}
void DebugMessage(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ char c[100];
wsprintf(c,"%04lx: %s (msg=%lx,wParam=%lx,lParam=%lx)",
(DWORD)hwnd,MessageName(msg),(DWORD)msg,(DWORD)wParam,(DWORD)lParam);
Debug(c);
}
#else
void Debug(char *) {}
void DebugMessage(HWND,UINT,WPARAM,LPARAM) {}
#endif

// MessageName: this function returns the text name of the message.
// Full source code of the function can be found inside minimal.zip
// in the file 'minimal.cpp'.

char *MessageName(UINT msg)
{ switch (msg)
{ case 0x0001: return "WM_C

抱歉!评论已关闭.