© 1996-1999 Lucian Wishcik
Introduction - Why use ScrPlus? - Compiling screensavers
Basics - Minimal screen saver - Using a timer to flash - Local data objects - Using ScrmTimer
Configuration dialogs - Overview of configuration - How ScrPlus helps - About dialog, and ScrPrefs - Modes of configuration dialog - Normal delayed dialogs - Immediate response dialogs - Preview dialogs - Secure screensavers
Programming hints - Bitmaps and sound - Using 8bpp DIBSections - Creating DirectDraw screensavers
Each chapter is independent of the others; but within each chapter, later parts of the chapter build upon earlier parts. It would probably be easiest to print out this tutorial and read through it in order, but it would also be possible to read the chapters in any order that you find appealing.
The same screensaver with ScrPlus takes 30 lines of code.
#define WIN32_LEAN_AND_MEAN #define STRICT #include#include "scrplus.h" LRESULT CALLBACK MySaverProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) { switch (msg) { case WM_ERASEBKGND: { HDC hdc=(HDC) wParam; RECT rc; GetClientRect(hwnd,&rc); FillRect(hdc,&rc,GetStockObject(BLACK_BRUSH); } break; } return DefScreenSaverProc(hwnd,msg,wParam,lParam); } int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE,LPSTR,int) { return ScrExecute(hInstance,MySaverProc); }
ScrExecute(hInstance,MySaverProc) is all you need put in your WinMain. It will figure out what to do--be it create a preview window, pop up a configuration dialog or run the screen saver full screen--and does it. We give it the address of the window procedure for our screen saver window. Note that it does not use DefWindowProc(..): rather, it uses DefScreenSaverProc(..). It responds to the WM_ERASEBKGND message because the default background brush is NULL.
And that's it! Your first complete screensaver! You could even install it into the Windows directory now if you wanted. If you did, you'd see it did all of the following:
LRESULT CALLBACK MySaverProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) { static int i; static UINT idTimer; switch (msg) { case WM_ERASEBKGND: .... case WM_CREATE: { idTimer=SetTimer(hwnd,1,200,NULL); i=0; } break; case WM_DESTROY: { if (idTimer) KillTimer(hwnd,uTimer); } break; case WM_TIMER: { HDC hdc=GetDC(hwnd); RECT rc; GetClientRect(hwnd,&rc); if (i++<=4) FillRect(hdc,&rc,GetStockObject(i)); else i=0; ReleaseDC(hwnd,hdc); } break; } return DefScreenSaverProc(hwnd,msg,wParam,lParam); }
There is nothing special about this: it is just a standard Windows programming technique. We create a timer in response to WM_CREATE, destroy it in response to WM_DESTROY, and repaint the window whenever the timer clicks.
If you copied this one into the windows directory and selected it from the control panel, you'd see that the animated preview window worked correctly. Wow!
class TMyData { public: TMyData(); int i; UINT idTimer; }; TMyData::TMyData() {i=0; idTimer=0;} LRESULT CALLBACK MySaverProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) { TMyData *mydata=(TMyData *)GetWindowLong(hwnd,GWL_USERDATA); if (mydata==NULL && msg!=WM_CREATE) return DefScreenSaverProc(hwnd,msg,wParam,lParam); switch (msg) { ... case WM_CREATE: { TMyData *mydata=new TMyData(); SetWindowLong(hwnd,GWL_USERDATA,(LONG)mydata); mydata->idTimer=SetTimer(hwnd,1,200,NULL); } break; case WM_DESTROY: { if (mydata->idTimer!=0) KillTimer(hwnd,mydata->idTimer); SetWindowLong(hwnd,GWL_USERDATA,0); delete mydata; } break; ... } return DefScreenSaverProc(hwnd,msg,wParam,lParam); }
There's nothing special about this. It's just good programming practice. A new class is created at window-start and deleted and window-end, and the window stores a pointer to it. This technique has been introduced mainly as a foundation for later, more advanced examples.
LRESULT CALLBACK MySaverProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) { switch (msg) { case SCRM_TIMER: { ... // just like WM_TIMER above return 15; } break; } return DefScreenSaverProc(hwnd,msg,wParam,lParam); }
All you have to do is this:
You have learnt how to create basic screensavers with local data objects and with SCRM_TIMERs. Later on in Programming hints we return to more advanced features in a screensaver window such as sound and bitmaps. But first, a look at the screensaver configuration and about dialogs.
The configuration property-sheet has an icon at its top left. You should supply this icon, with its identification number ID_APP. If you fail to supply an icon, the default ScrPlus icon will be used.
To add your own page, you must supply the dialog-proc for your options page as an argument to ScrExecute in your WinMain:
int WINAPI Winmain(HISNTANCE hInstance,HINSTANCE,LPSTR,int) { return ScrExecute(hInstance,MySaverProc,MyConfigProc);
This tutorial only shows how to add a single 'options' tab to the screensaver config dialog, and how to include your own about dialog that has no special behaviour apart from its OK and Cancel buttons. If you wish to do anything different such as adding more than one tab to the config dialog, replacing it totally so it doesn't even have the property sheet, or putting in an About dialog that has special behaviour, then you will have to use ScrExecuteEx. Read the ScrPlus Reference for more details.
ScrPlus introduces a new feature to screensavers called ActiveConfig. While the screensaver is running full-screen, the user is able to press the control key or click the right mouse button, and the user is able to alter the settings with the screensaver still running! This cuts away the tedious chore of opening up the Display Properties, clicking on 'Settings', changing them, clicking on 'OK' and finally clicking on 'Preview' to see how it looks. If you wish to use ActiveConfig, then you must enable it explicitly at the start of your program. About Dialog, and ScrPrefs has details on this.
The Registry is used both as a permament store of the settings, and sometimes (if it is needed) as a transfer buffer.
The general page contains stuff for corner hot-spots, mouse sensitivity, password delay, mute and to allow the user to enable or disable ActiveConfig. You can read the current settings at any time with the following code:
TScrGeneralSettings sgs; ScrGetGeneralSettings(&sgs); // Fields in the structure include mute, mousethreshold, // activeconfig, passworddelay: see scrplus.h for detailsIn addition, if something on the general page has changed (eg. if the user clicked on 'mute'), then a SCRM_CHANGE message gets sent to your screensaver informing it of the change. More on this below.
An About dialog box is standard on Plus screensaver, and can be reached via the system menu of the configuration dialog. ScrPlus will look in your screensaver for a dialog with resource id DLG_ABOUT. If it finds it, it will use this for the about dialog; if you have not included such a dialog, then it defaults to an 'about' dialog that describes ScrPlus itself.
SCRM_CHANGE is the instrument which enables the registry to be used as a transfer buffer. Suppose that your page in the configuration dialog has an HWND hOptionsPage. This means that the property sheet itself has HWND hPropertySheet=GetParent(hOptionsPage) and that the parent of the property sheet has HWND hChangeWindow=GetParent(hpropertySheet). It is this parent of the property sheet that is of interest.
#define SCRCHANGE_MYDATA 102 // following code would be inside your dialog procedure TScrChangeRouter cr; cr.hsrcwnd=hPropertySheet; cr.lParam=(LPARAM)mydata; cr.code=SCRCHANGE_MYDATA | SCRCHANGE_FROMAPPLY | SCRCHANGE_TOBOTH; HWND hChangeWindow=GetParent(GetParent(hOptionsPage)); SendMessage(hChangeWindow,SCRM_CHANGE,0,(LPARAM)&cr);
These notifications get sent as messages to MySaverProc:
UINT msg = SCRM_CHANGE WPARAM wParam = SCRCHANGE_MYDATA | SCRCHANGE_FROMxxx | SCRCHANGE_TOxxx LPARAM lParam = cr.lParamFor instance, your screensaver window might wish to handle the built-in code SCRCHANGE_GENERAL. Suppose that your screensaver had sound, and that whenever the user clicked on OK or Apply and had turned sound on or off, you wanted to be aware of this change. (Note: in the SCRCHANGE_GENERAL code, cr.lParam points to a TScrGeneralSettings structure.
case SCRM_CHANGE: { if (wParam & SCRCHANGE_GETCODE)==SCRCHANGE_GENERAL &&(wParam & SCRCHANGE_GETFROM)==SCRCHANGE_FROMAPPLY) { TScrGeneralSettings *sgs=(TScrGeneralSettings *)lParam; ShouldMakeSound=sgs->mute; } } break;
If you do not want any 'about' dialog at all, then you can disable it completely right at the start of your application, using ScrPrefs.
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE,LPSTR,int) { TScrPrefs *p=ScrCreatePrefs(); p->DisableAbout=true; p->AllowActiveConfig=true; p->AllowMute=false; ScrSetPrefs(p); return ScrExecute(hInstance,MySaverProc,MyConfigProc); }This example also illustrates how to use the ScrPrefs to enable ActiveConfig and to disable Mute. Normally, checkboxes for 'Mute' and 'ActiveConfig' appear in the general page of the screensaver config dialog. If either of AllowMute or AllowActiveConfig is set to false (to indicate that this is a feature that your screensaver simply does not support) then the check-boxes vanish.
Note that, if you do ScrCreatePrefs, then you should follow it with ScrSetPrefs. This is because the first call allocates a block of memory, and the second deallocates it. Failure to do this would result in a memory leak.
Note that the descriptions and code given below are just one way of working with dialogs and with ScrPlus. You are free to use any alternatives you feel appropriate.
There is an interesting thing to note. In these examples, the TMyData class will have several functions:
Note! If you have the old win32 headers, without goodies like property sheets, then it'll be kind of hard for you to program property sheets. If you're really desparate, then look at the 'flame' example which does a couple of #defines at the start to get the necessary constants even if it is using the old headers.
// This code illustrates a screensaver with an option for // initial blanking out of the screen #define ID_BLANK 110 #define SCRCHANGE_MYDATA 2 class TMyData { public: TMyData(); // bool blank; // bool GetControls(HWND); void SetControls(HWND); void ReadRegistry(); void WriteRegistry(); void NotifyChange(HWND); }; // Constructor: puts the default values into the object. // These will be the values used when the screensaver is first // run on a new computer // TMyData::TMyData() { blank=true; } // GetControls: reads the current state of the dialog into // the TMyData object. Returns 'true' if there have been // any changes. // bool TMyData::GetControls(HWND hdlg) { bool ischanged=false; bool newblank=IsDlgButtonChecked(hdlg,ID_BLANK); if (newblank!=blank) ischanged=true; blank=newblank; return ischanged; } // SetControls: writes the control values into the dialog // void TMyData::SetControls(HWND hdlg) { CheckDlgButton(hdlg,ID_BLANK,blank); } // ReadRegistry: Loads the configuration from the registry // void TMyData::ReadRegistry() { LONG res; HKEY skey; DWORD val,valsize,valtype; res=RegOpenKeyEx(HKEY_CURRENT_USER,"Software\\Lu\\TestSaver",0,KEY_ALL_ACCESS,&skey); if (res!=ERROR_SUCCESS) return; // 'blank' valsize=sizeof(val); res=RegQueryValueEx(skey,"Blank",0,&valtype,(LPBYTE)&val,&valsize); if (res==ERROR_SUCCESS) blank=val; RegCloseKey(skey); } // WriteRegistry: stores the configuration into the registry // void TMyData::WriteRegistry() { LONG res; HKEY skey; DOWRD val; DWORD disp; res=RegCreateKeyEx(HKEY_CURRENT_USER,"Software\\Lu\\TestSaver",0,NULL, REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&skey,&disp); // We use CreateKey not OpenKey, so the first time the saver is run if (res!=ERROR_SUCCESS) return; // 'blank' val=blank; RegSetValueEx(skey,"DoLines",0,REG_DWORD,(CONST BYTE*)&val,sizeof(val)); RegCloseKey(skey); } // NotifyChange: (only needed if you have enabled ActiveConfig) // If running as an active-config dialog, NotifyChange informs // the screensaver window that the settings have changed // void TMyData::NotifyChange(HWND hOptionsPage) { UINT code=SCRCHANGE_MYDATA | SCRCHANGE_FROMAPPLY | SCRCHANGE_TOFULL; HWND hPropertySheet=GetParent(hOptionsPage); HWND hChangeWindow =GetParent(hPropertySheet); TScrChangeRouter cr; cr.hsrcwnd=hPropertySheet; cr.lParam=(LPARAM)this; cr.code=code; SendMessage(hChangeWindow,SCRM_CHANGE,0,(LPARAM)&cr); } // MyConfigProc: dialog-procedure for the Options page. // BOOL WINAPI MyConfigProc(HWND hwnd,UINT msg,WPARAM,LPARAM lParam) { TMyData *mydata=(TMyData *)GetWindowLong(hwnd,DWL_USER); if (mydata==NULL && msg!=WM_INITDIALOG) return false; switch (msg) { case WM_INITDIALOG: { TMyData *mydata=new TMyData(); SetWindowLong(hwnd,DWL_USER,(LONG)mydata); mydata->ReadRegistry(); mydata->SetControls(hwnd); return true; } case WM_DESTROY: { delete mydata; SetWindowLong(hwnd,DWL_USER,0); return true; } case WM_NOTIFY: { LPNMHDR nmh=(LPNMHDR) lParam; UINT code=nmh->code; switch (code) { case PSN_APPLY: { mydata->GetControls(hwnd); mydata->WriteRegistry(); mydata->NotifyChange(hwnd); return true; } } } return false; } LRESULT CALLBACK MySaverProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) { TMyData *mydata=(TMyData *)GetWindowLong(hwnd,GWL_USERDATA); if (mydata==NULL && msg!=WM_CREATE) return DefScreenSaverProc(hwnd,msg,wParam,lParam); switch (msg) { case WM_CREATE: { TMyData *mydata=new TMyData(); mydata->ReadRegistry(); SetWindowLong(hwnd,GWL_USERDATA,(LONG)mydata); } break; case WM_ERASEBKGND: { if (blank) { HDC hdc=(HDC) wParam; RECT rc; GetClientRect(hwnd,&rc); FillRect(hdc,&rc,GetStockObject(BLACK_BRUSH); } } break; case WM_DESTORY: { delete mydata; SetWindowLong(hwnd,GWL_USERDATA,0); } break; case SCRM_TIMER: { // do some animation return 100; } // The following message SCRM_CHANGE is only important if // we have enabled ActiveConfig case SCRM_CHANGE: { if (wParam & SCRCHANGE_GETCODE)==SCRCHANGE_MYDATA) { delete mydata; mydata=new TMyData(); mydata->ReadRegistry(); SetWindowLong(hwnd,GWL_USERDATA,(long)mydata); InvalidateRect(hwnd,NULL,true); } } break; } return DefScreenSaverProc(hwnd,msg,wParam,lParam); } int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE,LPSTR,int) { TScrPrefs *p=ScrCreatePrefs(); p->AllowActiveConfig=true; ScrSetPrefs(p); return ScrExecute(hInstance,MySaverProc,MyConfigProc); } ----------- and also the dialog resource DLG_SCRNSAVECONFIGURE DIALOG 0, 0, 237, 220 STYLE DS_MODALFRAME | DS_3DLOOK | DS_CONTEXTHELP | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Options" FONT 8, "Helv" { CONTROL "&Blank screen at start", ID_BLANK, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 20, 183, 95, 12 }
Here is a summary of the important points:
// This code illustrates an immediate-mode screensaver // with a flag for the size of blobs on screen #define ID_BIGBLOBS 110 #define SCRCHANGE_MYDATA 2 class TMyData { public: TMyData(); // bool BigBlobs; bool markBigBlobs; // bool GetControls(HWND); void SetControls(HWND); void ReadRegistry(); void WriteRegistry(); void NotifyChange(HWND); void Mark(); bool Reset(); }; // Constructor: puts the default values into the object. // TMyData::TMyData() { BigBlobs=true; } // Mark: remembers the current values of the object // void TMyData::Mark() { markBigBlobs=BigBlobs; } // Reset: returns to the remembered values. Returns // true or false for whether or not that involves a change // bool TMyData::Reset() { bool ischanged=false; if (BigBlobs!=markBigBlobs) ischanged=true; BigBlobs=markBigBlobs; return ischanged; } // GetControls: reads the current state of the dialog // bool TMyData::GetControls(HWND hdlg) { bool ischanged=false; bool newBigBlobs=IsDlgButtonChecked(hdlg,ID_BIGBLOBS); if (newBigBlobs!=BigBlobs) ischanged=true; BigBlobs=newBigBlobs; return ischanged; } // SetControls: writes the control values into the dialog // void TMyData::SetControls(HWND hdlg) { CheckDlgButton(hdlg,ID_BIGBLOBS,BigBlobs); } // ReadRegistry: Loads the configuration from the registry // void TMyData::ReadRegistry() { LONG res; HKEY skey; DWORD val,valsize,valtype; res=RegOpenKeyEx(HKEY_CURRENT_USER,"Software\\Lu\\TestSaver",0,KEY_ALL_ACCESS,&skey); if (res!=ERROR_SUCCESS) return; // 'bigblobs' valsize=sizeof(val); res=RegQueryValueEx(skey,"BigBlobs",0,&valtype,(LPBYTE)&val,&valsize); if (res==ERROR_SUCCESS) BigBlobs=val; RegCloseKey(skey); } // WriteRegistry: stores the configuration into the registry // void TMyData::WriteRegistry() { LONG res; HKEY skey; DOWRD val; DWORD disp; res=RegCreateKeyEx(HKEY_CURRENT_USER,"Software\\Lu\\TestSaver",0,NULL, REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&skey,&disp); // We use CreateKey not OpenKey, so the first time the saver is run if (res!=ERROR_SUCCESS) return; // 'BigBlobs' val=BigBlobs; RegSetValueEx(skey,"DoLines",0,REG_DWORD,(CONST BYTE*)&val,sizeof(val)); RegCloseKey(skey); } // NotifyChange: inform the full saver that changes have been made // void TMyData::NotifyChange(HWND hOptionsPage) { UINT code=SCRCHANGE_MYDATA; HWND hPropertySheet=GetParent(hOptionsPage); HWND hChangeWindow =GetParent(hPropertySheet); TScrChangeRouter cr; cr.hsrcwnd=hPropertySheet; cr.lParam=(LPARAM)this; cr.code=code; SendMessage(hChangeWindow,SCRM_CHANGE,0,(LPARAM)&cr); } // MyConfigProc: dialog-procedure for the Options page. // BOOL WINAPI MyConfigProc(HWND hwnd,UINT msg,WPARAM,LPARAM lParam) { TMyData *mydata=(TMyData *)GetWindowLong(hwnd,DWL_USER); if (mydata==NULL && msg!=WM_INITDIALOG) return false; switch (msg) { case WM_INITDIALOG: { TMyData *mydata=new TMyData(); SetWindowLong(hwnd,DWL_USER,(LONG)mydata); mydata->ReadRegistry(); mydata->Mark(); mydata->SetControls(hwnd); return true; } case WM_DESTROY: { delete mydata; SetWindowLong(hwnd,DWL_USER,0); return true; } case WM_COMMAND: { HWND hctrlwnd=(HWND) lParam; if (hctrlwnd==NULL) return false; bool ischanged=mydata->GetControls(hwnd); if (ischanged) mydata->NotifyChange(hwnd); return true; } case WM_NOTIFY: { LPNMHDR nmh=(LPNMHDR) lParam; UINT code=nmh->code; switch (code) { case PSN_APPLY: { mydata->Mark(); mydata->WriteRegistry(); // We don't even have to NotifyChange return true; } case PSN_RESET: { // revert back to old values bool ischanged=mydata->Reset(); if (ischanged) mydata->NotifyChanges(hwnd); return true; } } } return false; } LRESULT CALLBACK MySaverProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) { TMyData *mydata=(TMyData *)GetWindowLong(hwnd,GWL_USERDATA); if (mydata==NULL && msg!=WM_CREATE) return DefScreenSaverProc(hwnd,msg,wParam,lParam); switch (msg) { case WM_CREATE: { TMyData *mydata=new TMyData(); mydata->ReadRegistry(); SetWindowLong(hwnd,GWL_USERDATA,(LONG)mydata); } break; case WM_ERASEBKGND: { HDC hdc=(HDC) wParam; RECT rc; GetClientRect(hwnd,&rc); FillRect(hdc,&rc,GetStockObject(BLACK_BRUSH); } break; case WM_DESTORY: { delete mydata; SetWindowLong(hwnd,GWL_USERDATA,0); } break; case SCRM_TIMER: { // do something with blobs RECT rc; GetWindowRect(hwnd,&rc); int width=rc.right-rc.left; int rad=random(width/20); int x=random(width), y=random(width); HDC hdc=GetDC(hwnd); LOGBRUSH lb; lb.lbStyle=BS_SOLID; lb.lbColor=random(4); lb.lbHatch=0; HGDIOBJ holdbrush=SelectObject(hdc,CreateBrushIndirect(&lb)); Ellipse(hdc,x-rad,y-rad,x+rad,y+rad); holdbrush=SelectObject(hdc,holdbrush); DeleteObject(holdbrush); return 15; } // This version of SCRM_CHANGE uses lParam instead of reading // the registry case SCRM_CHANGE: { if (wParam & SCRCHANGE_GETCODE)==SCRCHANGE_MYDATA) { TMyData *newdata=(TMyData *)lParam; mydata->bigblobs=newdata->bigblobs; } } break; } return DefScreenSaverProc(hwnd,msg,wParam,lParam); } int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE,LPSTR,int) { TScrPrefs *p=ScrCreatePrefs(); p->AllowActiveConfig=true; ScrSetPrefs(p); return ScrExecute(hInstance,MySaverProc,MyConfigProc); } ----------- and also the dialog resource DLG_SCRNSAVECONFIGURE DIALOG 0, 0, 237, 220 STYLE DS_MODALFRAME | DS_3DLOOK | DS_CONTEXTHELP | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Options" FONT 8, "Helv" { CONTROL "Use &big blobs", ID_BIGBLOBS, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 20, 183, 95, 12 }
Here is a summary of the important points:
// This code illustrates an preview-mode screensaver // with a flag for the size of blobs on screen #define ID_BIGBLOBS 110 #define SCRCHANGE_MYDATA 2 class TMyData { public: TMyData(); // bool BigBlobs; bool markBigBlobs; // bool GetControls(HWND); void SetControls(HWND); void ReadRegistry(); void WriteRegistry(); void NotifyChange(HWND,bool fromcontrol); void Mark(); bool Reset(); }; // Constructor: puts the default values into the object. // TMyData::TMyData() { BigBlobs=true; } // Mark: remembers the current values of the object // void TMyData::Mark() { markBigBlobs=BigBlobs; } // Reset: returns to the remembered values. Returns // true or false for whether or not that involves a change // bool TMyData::Reset() { bool ischanged=false; if (BigBlobs!=markBigBlobs) ischanged=true; BigBlobs=markBigBlobs; return ischanged; } // GetControls: reads the current state of the dialog // bool TMyData::GetControls(HWND hdlg) { bool ischanged=false; bool newBigBlobs=IsDlgButtonChecked(hdlg,ID_BIGBLOBS); if (newBigBlobs!=BigBlobs) ischanged=true; BigBlobs=newBigBlobs; return ischanged; } // SetControls: writes the control values into the dialog // void TMyData::SetControls(HWND hdlg) { CheckDlgButton(hdlg,ID_BIGBLOBS,BigBlobs); } // ReadRegistry: Loads the configuration from the registry // void TMyData::ReadRegistry() { LONG res; HKEY skey; DWORD val,valsize,valtype; res=RegOpenKeyEx(HKEY_CURRENT_USER,"Software\\Lu\\TestSaver",0,KEY_ALL_ACCESS,&skey); if (res!=ERROR_SUCCESS) return; // 'bigblobs' valsize=sizeof(val); res=RegQueryValueEx(skey,"BigBlobs",0,&valtype,(LPBYTE)&val,&valsize); if (res==ERROR_SUCCESS) BigBlobs=val; RegCloseKey(skey); } // WriteRegistry: stores the configuration into the registry // void TMyData::WriteRegistry() { LONG res; HKEY skey; DOWRD val; DWORD disp; res=RegCreateKeyEx(HKEY_CURRENT_USER,"Software\\Lu\\TestSaver",0,NULL, REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&skey,&disp); // We use CreateKey not OpenKey, so the first time the saver is run if (res!=ERROR_SUCCESS) return; // 'BigBlobs' val=BigBlobs; RegSetValueEx(skey,"DoLines",0,REG_DWORD,(CONST BYTE*)&val,sizeof(val)); RegCloseKey(skey); } // NotifyChange: inform the saver windows that changes have occured. // If the change was from a control, then we route the message only // to preview windows; if the change was from the Apply button, then // we route the message only to the full-screen window // void TMyData::NotifyChange(HWND hOptionsPage, bool fromcontrol) { UINT code; if (fromcontrol) code=SCRCHANGE_MYDATA | SCRCHANGE_FROMCONTROL | SCRCHANGE_TOPREVIEW; else code=SCRCHANGE_MYDATA | SCRCHANGE_FROMAPPLY | SCRCHANGE_TOFULL; HWND hPropertySheet=GetParent(hOptionsPage); HWND hChangeWindow =GetParent(hPropertySheet); TScrChangeRouter cr; cr.hsrcwnd=hPropertySheet; cr.lParam=(LPARAM)this; cr.code=code; SendMessage(hChangeWindow,SCRM_CHANGE,0,(LPARAM)&cr); } // MyConfigProc: dialog-procedure for the Options page. // BOOL WINAPI MyConfigProc(HWND hwnd,UINT msg,WPARAM,LPARAM lParam) { TMyData *mydata=(TMyData *)GetWindowLong(hwnd,DWL_USER); if (mydata==NULL && msg!=WM_INITDIALOG) return false; switch (msg) { case WM_INITDIALOG: { TMyData *mydata=new TMyData(); SetWindowLong(hwnd,DWL_USER,(LONG)mydata); mydata->ReadRegistry(); mydata->Mark(); mydata->SetControls(hwnd); ScrCreatePreview(GetDlgItem(hwnd,ID_MONITOR)); return true; } case WM_DESTROY: { delete mydata; SetWindowLong(hwnd,DWL_USER,0); return true; } case WM_COMMAND: { HWND hctrlwnd=(HWND) lParam; if (hctrlwnd==NULL) return false; bool ischanged=mydata->GetControls(hwnd); if (ischanged) mydata->NotifyChange(hwnd,true); return true; } case WM_NOTIFY: { LPNMHDR nmh=(LPNMHDR) lParam; UINT code=nmh->code; switch (code) { case PSN_APPLY: { mydata->Mark(); mydata->WriteRegistry(); mydata->NotifyChange(hwnd,false); return true; } case PSN_RESET: { // revert back to old values bool ischanged=mydata->Reset(); if (ischanged) mydata->NotifyChanges(hwnd,false); return true; } } } return false; } LRESULT CALLBACK MySaverProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) { TMyData *mydata=(TMyData *)GetWindowLong(hwnd,GWL_USERDATA); if (mydata==NULL && msg!=WM_CREATE) return DefScreenSaverProc(hwnd,msg,wParam,lParam); switch (msg) { case WM_CREATE: { TMyData *mydata=new TMyData(); mydata->ReadRegistry(); SetWindowLong(hwnd,GWL_USERDATA,(LONG)mydata); } break; case WM_ERASEBKGND: { HDC hdc=(HDC) wParam; RECT rc; GetClientRect(hwnd,&rc); FillRect(hdc,&rc,GetStockObject(BLACK_BRUSH); } break; case WM_DESTORY: { delete mydata; SetWindowLong(hwnd,GWL_USERDATA,0); } break; case SCRM_TIMER: { // do something with blobs RECT rc; GetWindowRect(hwnd,&rc); int width=rc.right-rc.left; int rad=random(width/20); int x=random(width), y=random(width); HDC hdc=GetDC(hwnd); LOGBRUSH lb; lb.lbStyle=BS_SOLID; lb.lbColor=random(4); lb.lbHatch=0; HGDIOBJ holdbrush=SelectObject(hdc,CreateBrushIndirect(&lb)); Ellipse(hdc,x-rad,y-rad,x+rad,y+rad); holdbrush=SelectObject(hdc,holdbrush); DeleteObject(holdbrush); return 15; } // This version of SCRM_CHANGE uses lParam instead of reading // the registry case SCRM_CHANGE: { if (wParam & SCRCHANGE_GETCODE)==SCRCHANGE_MYDATA) { TMyData *newdata=(TMyData *)lParam; mydata->bigblobs=newdata->bigblobs; } } break; } return DefScreenSaverProc(hwnd,msg,wParam,lParam); } int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE,LPSTR,int) { TScrPrefs *p=ScrCreatePrefs(); p->AllowActiveConfig=true; ScrSetPrefs(p); return ScrExecute(hInstance,MySaverProc,MyConfigProc); } ----------- and also the dialog resource DLG_SCRNSAVECONFIGURE DIALOG 0, 0, 237, 220 STYLE DS_MODALFRAME | DS_3DLOOK | DS_CONTEXTHELP | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Options" FONT 8, "Helv" { CONTROL "Use &big blobs", ID_BIGBLOBS, "button", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 20, 183, 95, 12 }
Here is a summary of the important points:
#include#include LRESULT CALLBACK MySaverProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) { switch (msg) { case WM_ERASEBKGND: { HDC hdc=(HDC) wParam; RECT rc; GetClientRect(hwnd,&rc); FillRect(hdc,&rc,GetStockObject(BLACK_BRUSH); } break; case SCRM_VERIFYPW: { // Put your own password verification box here return TRUE; // if the user got the password ok, or FALSE otherwise } } } BOOL CALLBACK MyChangePassword(HWND hwnd) { // pop up your change-password dialog box here, as a child of hwnd return TRUE; } int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE,LPSTR,int) { TScrPrefs *p=ScrCreatePrefs(); p->AlwaysRequirePassword=true; ScrSetPrefs(p); return ScrExecute(hInstance,MySaverProc,NULL,NULL,MyChangePassword); }
One thing used in this screensaver was the global preference flag 'AlwaysRequirePassword'. (There is an equivalent 'NeverRequirePassword). This means that as soon as the screensaver has been activated, to get out of it will require a password. Normal behaviour is for the user to specify a password delay, only after which time would the password be required. You might want to require a password always if you are writing a screensaver for security in a particularly critical situation. If you happened to want the screensaver configuration dialog to be accessible without requiring a password, then you can set the flag CheckPasswordForActiveConfig=false.
Also see 5.9 Preventing ctrl-alt-delete and alt-tab.
if (ShouldPlaySound) { PlaySound(MAKEINTRESOURCE(WAV_BOING),hInstance, SND_RESOURCE | SND_ASYNC | SND_NODEFAULT); } ------- and in your .rc file WAV_BOING WAVE "boing.wav"
If you are running on a true-colour display (16bpp or above), or if you have low-colour bitmaps (16 colours or below) then you don't need to bother about palettes. Generally you would load the bitmap resource or file right at the start, in response to WM_CREATE in your screensaver window, and destroy it at WM_DESTROY.
// Following code called in response to WM_CREATE. Use exactly the // same call with slightly different parameters to load a .bmp file. // hBitmap=(HBITMAP)LoadImage(hInstance,MAKEINTRESOURCE(BMP_BOING), IMAGE_BITMAP,0,0,0); BITMAP bmp; GetObject(hBitmap,sizeof(bmp),&bmp); Width=bmp.bmWidth; Height=bmp.bmHeight; // Following code called in response to WM_DESTROY. // DeleteObject(hBitmap); // Following code to draw the bitmap. We assume that 'hdc' is the // HWND of the screensaver window, obtained with hdc=GetDC(hwnd); // or by the WM_PAINT method. // hMemDC=CreateCompatibleDC(hdc); hOldBitmap=SelectObject(hMemDC,hBitmap); BitBlt(hdc,0,0,Width,Height,hMemDC,0,0,SRCCOPY); SelectObject(hMemDC,hOldBitmap); DeleteDC(hMemDC);If the cases above did not hold, and if you do have to worry about palettes (or if had an 8bpp bitmap and wanted to do palette animation) then you'd have to obtain the bitmap's palette and respond to WM_QUERYNEWPALETTE and WM_PALETTECHANGED as in the next section.
Displaying an 8bpp DIBSection is a seriously non-trivial task. You have to respond to WM_QUERYNEWPALETTE and WM_PALETTECHANGED; you have to select palettes here and there; if you want to redefine 254 colours (ie. all the available ones) then you have to respond to WM_ACTIVATEAPP by snitching the static colours from the system. And there's lots lots more.
To make this easier, 'scotch', 'static' and 'ghosts' use a module called 'Bits'. The following code would create an 8bpp DIBSection and have it displayed in the window.
TBits *bits=new TBits(); bits->DipPainter.PositionAtPoint(0,0); bits->EnableSubclass(); bits->Create(hwnd,false,1,200,150,8,true);and you don't have to do anything more! The Bits module also does a couple of other things, including support for DirectDraw. You could use Bits yourself, or look at it to see how it works. Here is another example of it: TBits *bits=new TBits(); TBits *loadbits=new TBits(); loadbits->CreateDibFromFile(NULL,"c:\\art\\lu.bmp"); bits->DibPainter.PositionAtPoint(0,0); bits->EnableSubclass(); for (int i=0; i<256; i++) { bits->LogPal.Entries[i].peRed=i; bits->LogPal.Entries[i].peGreen=i; bits->LogPal.Entries[i].peBlue=i; } bits->ChangePalette(); bits->CreateDibResample(hwnd,loadbits->surface[0],250,100,8,true); delete loadbits;
Two example screensavers that come with ScrPlus demonstrate how to do this. 'Static' draws a full-screen of static which it colour-cycles, and is relatively basic. 'Scotch' has transparent tarten grey diagonals sliding across the screen and demonstrates how to take a snapshot of the screen before you start, and to manipulate it. 'Ghosts' has static but loads in a sequence of bitmaps and fades them into the static.