I'm also providing an open-source example of a working screensaver using OpenGL and satisfying all my assumptions. It's not the coolest one you'll ever see, showing a simple green square moving in a circle on a black background, but it will provide an example and introduction for a few topics not quite covered here, such as the use of the registry for saving user preferences. Feel free to start with its code when creating your screensavers. Note that only the C/C++ code is given, not the resource file... you'll be better off creating your own project as outlined below anyway. Note that if you do this, you'll need to put a check box in your configuration dialog called IDC_TUMBLE; if you don't do this, or else comment out the logic related to IDC_TUMBLE, it won't compile.
See the example code
Download the executable example .scr file (zipped)
Screensaver executables (.scr files) can run in two modes, the configuration mode and the screensaver mode. The mode is set by a command-line argument of either -c (for configuration) or -s (for screensaver). If there is no argument, the configuration dialog box is run.
You can search for "Handling Screen Savers" in the MSDN to learn more about screensavers. Most of the time, developers use the pre-existing code in scnrnsave.h to get them up and running, and that is what we'll do here.
The rest of this paper focuses on how to make scrnsave.h work with your code, and how to set up OpenGL for use with your animations.
And as long as we're setting up the project, you will need to include the scrnsave.lib library (which contains scrnsave.h) and the libraries you'll need for OpenGL. In Visual Studio, choose Project-->Settings from the menu options. Under the General tab, make sure Not Using MFC is selected from the combo box. Then go to the Link tab, and in the Object/library modules edit field, add the following:
scrnsave.lib opengl32.lib glu32.lib
If you are planning to use any Windows common controls such as slider bars in your configuration dialog box, you'll also need comctl32.lib. You can add it now, or wait until you're actually working on the configuration dialog.
Similarly, if you want your program to be identified by an icon, that icon must be identified as ID_APP and have a value of 100. These constants are defined in scrnsave.h, so you will get errors about their redefinition if you don't define them in the same way.
#include <windows.h> #include <scrnsave.h> |
If you plan to have OpenGL functionality in the same file, don't forget to include GL/gl.h and GL/glu.h... you'll need them later.
The first function you need is ScreenSaverProc. This function deals with certain Windows messages, passing the rest back to the screensaver library, and will call your animation subroutines.
If you're somewhat familiar with Windows messaging, this example code should look straightforward. On WM_CREATE, we need to set up OpenGL and create a timer to run our animation. On WM_TIMER, we advance our animation by one time step. On WM_DESTROY, we clean up any messes and shut down OpenGL nicely (the InitGL and CloseGL functions are given in the next section).
//globals used by the function below to hold the screen size int Width; int Height; //define a Windows timer #define TIMER 1 // Screen Saver Procedure LRESULT WINAPI ScreenSaverProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { static HDC hDC; static HGLRC hRC; static RECT rect; switch ( message ) { case WM_CREATE: // get window dimensions GetClientRect( hWnd, &rect ); Width = rect.right; Height = rect.bottom; //get configuration from registry if applicable //set up OpenGL InitGL( hWnd, hDC, hRC ); //Initialize perspective, viewpoint, and //any objects you wish to animate //create a timer that ticks every 10 milliseconds SetTimer( hWnd, TIMER, 10, NULL ); return 0; case WM_DESTROY: KillTimer( hWnd, TIMER ); //delete any objects created during animation //and close down OpenGL nicely CloseGL( hWnd, hDC, hRC ); return 0; case WM_TIMER: //call some function to advance your animation return 0; } //let the screensaver library take care of any //other messages return DefScreenSaverProc( hWnd, message, wParam, lParam ); } |
If you don't want double buffering, you can look in the MSDN for the details of the PIXELFORMATDESCRIPTOR structure and change the flags to satisfy your requirements.
I should give a quick acknowledgement to Blaine Hodge here, whose open-source screensaver Gravity gave me these functions in almost this form (though without the double buffering).
static void InitGL(HWND hWnd, HDC & hDC, HGLRC & hRC) { PIXELFORMATDESCRIPTOR pfd; ZeroMemory( &pfd, sizeof pfd ); pfd.nSize = sizeof pfd; pfd.nVersion = 1; pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 24; hDC = GetDC( hWnd ); int i = ChoosePixelFormat( hDC, &pfd ); SetPixelFormat( hDC, i, &pfd ); hRC = wglCreateContext( hDC ); wglMakeCurrent( hDC, hRC ); } |
static void CloseGL(HWND hWnd, HDC hDC, HGLRC hRC) { wglMakeCurrent( NULL, NULL ); wglDeleteContext( hRC ); ReleaseDC( hWnd, hDC ); } |
BOOL WINAPI ScreenSaverConfigureDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { return FALSE; } |
Of course, that function doesn't do anything except satisfy the screensaver library. However, feel free to leave it in that state until you've done all your animation work and have the screensaver itself looking as it should at its default settings. You'll have a much better idea of what you'd like your users to be able to configure by then anyway.
When you've reached that point, use the resource editor to create a dialog box. It must have an identifier of DLG_SCRNSAVECONFIGURE and be defined by the constant 2003. As before, then, in its properties you can set the ID to DLG_SCRNSAVECONFIGURE=2003 and Studio Developer will do that invisible parsing for you. You can open the .rc file as text to make sure it worked.
This is not really the place to teach someone how to make a dialog box, but a few general notes are called for. First, keep it simple to start with. Add the fancy stuff later. If you think your dialog box should be coming up but nothing happens when you press Settings... or try to run it in configuration mode from your IDE, try this trick: in the dialog box's styles, go to the More Styles tab and choose No fail create. This will cause your box to come up without whatever offensive part was causing the error, which will let you know what part it was.
A real dialog box will have controls to set up and functionality to write to, and read from, the registry so that your user's preferred settings will persist from session to session; so the ScreenSaverConfigureDialog function can get pretty ungainly. Here's a somewhat more realistic, example showing the basic form you'll want to follow; my example code also shows the reading/writing of the registry information.
BOOL WINAPI ScreenSaverConfigureDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch ( message ) { case WM_INITDIALOG: //get configuration from the registry return TRUE; case WM_COMMAND: switch( LOWORD( wParam ) ) { //handle various cases sent when controls //are checked, unchecked, etc //when OK is pressed, write the configuration //to the registry and end the dialog box case IDOK: //write configuration EndDialog( hDlg, LOWORD( wParam ) == IDOK ); return TRUE; case IDCANCEL: EndDialog( hDlg, LOWORD( wParam ) == IDOK ); return TRUE; } } //end command switch return FALSE; } |
// needed for SCRNSAVE.LIB BOOL WINAPI RegisterDialogClasses(HANDLE hInst) { return TRUE; } |
The system adds these command-line arguments automatically; to include them when running from your IDE so that you can test and debug the animation functionality, you'll have to add one of them in yourself. In Microsoft Visual Studio's project settings, choose the Debug tab and make sure you're in the General category in the combo box. Then type "-s" or "-c" (-c gives you the default behavior anyway) into the Program Arguments edit field.
At some point, you'll want to be sure your screensaver works with the Windows system. I advise doing this as soon as you get the three skeleton functions in, before you've done more animation work than making the screen go black. You'll need to move the executable to where the system can find it automatically, usually the C:\Windows\System file. For this, I found it useful to create a little batch file called CopyScr.bat, containing a line like this:
copy c:\dev\myscr\Release\myscr.exe c:\Windows\System\myscr.scr |
Of course, I could have set my project settings so that myscr would have had an extension of .scr to begin with.
Here's a table showing some folders where your system will be able to find your screensaver:
Windows 95: | C:\Windows (?) |
Windows 98: | C:\Windows\System or C:\Windows |
Windows XP: | C:\Windows or C:\Windows\System32 |
Windows 2000: | C:\WINNT\System32 |
Good luck and have fun writing screensavers. Please let me know if you find errors in this document, or found any part of it especially misleading/unhelpful, so I can change it. I suppose you could even drop me a line if you liked it. I look forward to hearing from you.