Scrplus General Information

© 1997-1999 Lucian Wischik. This document contains information about screen savers in general. It includes notes on the distribution of savers. You should read it through before releasing onto the world any saver that you have written.

Contents


1. Screen saver filenames. Must have suffix .SCR

Screen savers are regular executables with a set of particular behaviours and responses to command line arguments, that have been renamed to have the suffix .SCR.

Give your saver a long filename. This long filename will be used for its description in the control panel and should not be longer than twenty five characters. If you prefix the name with the letters 'ss', then these first two letters will not appear in the drop-down list.



2. Installation of a screen 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 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.

If you 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
void 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;
}



3. Properties of screen savers in the Explorer

There are three properties for screen savers, that come up in the list when you right click one in the explorer.



4. The currently selected screen 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.



5. How to start a screen 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;



6. Hot corners

Windows Plus! comes with the System Agent, which detects if the mouse is resting in a corner of the screen. Some corners are 'Saver Now' corners which start the saver immediately; some are 'Saver Never' corners which prevent the saver from being executed; and some are normal corners which have no effect.

If you intend to distribute your saver to users who do not have the Plus! pack, and you want them to have hot corners, you may distribute ScrHots (which comes in the ScrPlus/C++Builder package: read SCRB\BIN\SCRHOTS.TXT). ScrHots does the same sort of thing as the System Agent. It also installs itself as an icon in the system tray.

If you execute ScrHots with the -install flag, then it will pop up a message asking the user whether a link to it should be placed in their windows startup directory.

Savers generally provide the configuration dialog for altering hot corners themselves. (You can use the TScrMonitor component for this; or the TScrGeneral, which includes a TScrMonitor).

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 following code 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);
  }
}



7. How and when screen savers are 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.



8. Visual effects of lengthy startups

If your saver does a great deal of processing on startup, this may be very noticable in the control panel--especially when the panel regains focus and the desktop background colour is displayed in the preview until your saver has loaded. Also, if you do something like a StretchBlt from the desktop onto your little window it might be time-consuming and irritationg. Also, VCL is a large class library and it makes the application take a long time to load. Also, if you have any forms being auto-created then they take a while to load. Also, VCL is simply kind of sluggish at creating new forms. If this is a problem then you might consider creating the form dynamically at runtime, rather than using the standard VCL way of streaming a form in from a resource. (This involves constructing it using the second form of the constructor, passing a dummy integer argument as the second parameter).

If it will take a really long time to get going, then you might want to put something up there in the preview in the meantime. If speed is absolutely crucial to you, then you will have no choice but to abandon C++Builder and write your screen saver at a lower level. (ScrPlus exists in a 'raw API' form as well as the C++Builder form; but you will find that programming for the raw Windows API is much more involved than programming with C++Builder).

Also, some savers look really silly restarting every single time the control panel regains focus if, say, they have something that progresses such as an animation. If this is a problem with yours, then you should probably store where it last got to in the registry so that, when it is launched again, you can start from the right point.



9. Testing a saver's preview

You will probably want to test the preview function of your saver. Normally this would be terribly inconvenient: it would involve you compiling, renaming, copying to the windows directory and bringing up the control panel; and you wouldn't even be able to debug it.

To make testing easier, ScrPlus comes with the utility ScrPrev. To use it, run any saver you have created with ScrPlus with the argument /p scrprev. This will look for a ScrPrev window (and if one is not found, it will execute ScrPrev); and then it will run with its preview in there. You could put /p scrprev in the Run|Parameters... for convenience.

Note that, when running the preview from the control panel, message boxes are a REAL DISASTER. So is anything that puts up non-child windows or interferes with focus. Imagine: your preview puts up a message box; you close the box; the control panel regains focus; and it executes your preview a second time. You'll find yourself stuck in an unpleasant loop. This is why ScrPrev is so useful for testing previews.



10. When configuration is saved

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, Windows will not launch the saver they had so painstakingly configured but the previous one! 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 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.



11. Version differences between Win'95, Plus! and NT

A number of differences between versions have already been mentioned. Here is a summary: