© 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.
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.
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.
// 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; }
[boot] ... SCRNSAVE.EXE=C:\WINDOWS\FLAME.SCRUnder '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.
// 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.
// 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;
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.
// 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); } }
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.
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.
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|
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.
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.
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.
11. Version differences between Win'95, Plus! and NT
A number of differences between versions have already been mentioned.
Here is a summary: