Creating modules for Doodle's Screen Saver - Part 1

Introduction
The purpose of these articles about dssaver is to help people to create new screen saver modules for Doodle's Screen Saver. These articles are meant to be something like a tutorial.

If you want to follow the article, and compile the source code we'll put together here, you should have the source code of dssaver, downloadable from its homepage. You'll not need everything from that package, only the header file for saver modules, called SSModule.h. However, it's much easier if you take the source code of an already existing saver module, and modify that to fit your needs.

What is a saver module?
First things first, let's see what is a screen saver module!

When a user has dssaver installed, it's possible to extend that with new modules which will start when dssaver detects no user activity on the desktop for a given time. It means, that these modules are executable binary files, and they are called by the core of the screen saver, when something has to be done.

To have the greatest flexibility, these saver modules have to be DLLs, containing specially named functions exported by name.

Some of these functions are mandatory to be implemented, some are optional. If a DLL contains all the mandatory functions exported, then that DLL is treated as a screen saver module.

The screen saver module API
The screen saver module API is defined in the SSModule.h file. Currently, there are six mandatory functions, and two optional ones. One thing is common though, that all functions return an integer value telling the caller if the function call succeeded or not. These return values are defined in the header file, like SSMODULE_NOERROR, SSMODULE_ERROR_INVALIDPARAMETER, etc...

The mandatory functions
Let's start with the mandatory functions, and let's see them in the order they will be called by the screen saver core!

When the user goes to the screen saver settings page, the screen saver core creates a list of available saver modules, and shows that list to the user.

For this, it looks for all the DLL files at C:\OS2\APPS\SSAVER or whatever the boot drive is, and creates a list from the DLLs having all the mandatory saver functions exported.

Then, when the user selects one of the modules, the screen saver core has to show some information about the selected module. This is done by calling this function of the DLL: int SSModule_GetModuleDesc(SSModuleDesc_p pModuleDesc);

This function has to fill the pModuleDesc structure with information about the module. This structure is declared this way: typedef struct SSModuleDesc_s {  int iVersionMajor; int iVersionMinor; char achModuleName[SSMODULE_MAXNAMELEN]; char achModuleDesc[SSMODULE_MAXDESCLEN]; int bConfigurable; int bSupportsPasswordProtection; } SSModuleDesc_t, *SSModuleDesc_p; The saver module has to tell its version number in iVersionMajor and iVersionMinor. One thing to be noted about it is that the minor version number will be padded with a leading zero, if smaller than 10, so if your module version is e.g. v1.5, then you should set iVersionMajor to 1, and iVersionMinor to 50.

The achModuleName has to contain a zero terminated string, telling the short name of the module. This is the name which will be shown in the list of modules.

The achModuleDesc tells the long description of the module. It can contain line-breaks too, as it is the string which will be shown in a MLE at the settings page.

The next two fields are boolean fields, they have to contain 1 or 0 (as TRUE or FALSE). They are informational fields, telling the screen saver core if it should enable the "Configure" button for this module or not (bConfigurable field), and telling the user if the module supports password protection or not (bSupportsPasswordProtection field).

If the user selects this module from the list of available saver modules, the screen saver core shows this information to the user. When the user presses the "Configure" button there (provided that it was enabled, because the bConfigurable field was TRUE), the screens saver calls the next function of the module: int SSModule_Configure(HWND hwndOwner, char *pchHomeDirectory);

This function should either return SSMODULE_ERROR_NOTSUPPORTER, or it should enable the user to configure the saver module in some way, and return SSMODULE_NOERROR.

This configuration is usually done by a module-specific dialog window. If the module creates that window, it has to do it so that the owner of that window will be hwndOwner, so the screen saver settings dialog will be disabled while the user is configuring the saver module.

The pchHomeDirectory parameter contains a string, the full path (with a trailing backslash!) of the screen saver home directory, where the modules and config files are located. It is usually C:\OS2\APPS\SSAVER, but nothing tells that it can not be changed later.

This directory can be used to store the private configuration file of the module, but it's not necessary to use it, once can use the OS2.INI file too, to save the configuration of the module.

So, this function should load the current module configuration (from the given directory or from anywhere), show a dialog window, then save the new configuration.

When it's needed to start the screen saver module for some reason, the screen saver core calls the following function: int SSModule_StartSaving(HWND hwndParent, char *pchHomeDirectory, int bPreviewMode);

This function is used both for starting the saver module for the preview window, and for starting the module to do the real screen saving.

What this function do is completely left for the developer of the module. It all depends on what the module wants to do to save the screen.

Of course, most of the time it's that the module will create a new window, and do something nice and attractive in that window, but it can be that there is no need for such a window, because the module communicates with an external device to turn off the monitor, or to send an SMS to somebody that the user of the computer is in idle. :)

However, you have to note something. This function has to start the screen saving, and has to return then. It can not create a window and go to a message loop here. It means, that the best way is to start a new thread from this function, which will set up a new window and which will include the message loop for that window, and if that thread succeeds in creating the window, return with SSMODULE_NOERROR, otherwise return with an error code.

The third parameter of this function (bPreviewMode) tells if the saving should be started in a small window, as a preview window (bPreviewMode is TRUE in this case), or for real screen saving (bPreviewMode is FALSE in this case).

If the saver has to be started as a preview window, then the hwndParent contains a window handle, which has to be the parent of the saver preview window. This window is the white rectangle with the "No preview" text inside, when the preview is turned off at the settings page of the screen saver. The best way to do the preview window is to subclass this window for the time of previewing.

If the saver has to be started for real screen saving, then hwndParent contains HWND_DESKTOP, and a new window should be created. To make sure that no other windows will be able to draw on top of the saver window, this new window should be made system modal and always on top.

So, to summarize what to do in this function, you should: Once the screen saving is going on, and the user does some kind of activity, the screen saving should be turned off. To notify the saver module to stop the screen saving (or the preview window), the screen saver core calls the following function: int SSModule_StopSaving(void); This is the function which should undo everything that SSModule_StartSaving did.
 * Load the configuration of the module (from pchHomeDirectory or from INI files).
 * Start a new thread to do the saving. This new thread will either subclass hwndParent (when bPreviewMode is TRUE), or create a new system modal, always on top window (when bPreviewMode is FALSE).
 * Wait for that thread, to see if it could create the window.
 * Return success or failure

It usually means, that it should notify the new thread created by the other function to stop saving, and wait for it to be done. That other thread then can clean up everything it did, and terminate.

If the theme of the screen saver module enables, it can be done to have a short "lead-out" when it's asked to stop the saving, but make sure it won't make be too long, or the users will hate it otherwise. Also, it's good not to have these lead-outs when the saver runs in preview mode, because that would make switching from one module to another quite time consuming.

If the password protection is enabled in the screen saver core, then it will make sure that the user knows the right password before stopping the screen saver. So, it will ask password from the user, and compare it to the one set for screen saving. If they match, then the screen saver module will be stopped by calling SSModule_StopSaving, but if they don't match, it will be told to the user, and the saver module will continue running, it won't be stopped.

To make it possible to have a password asking window which fits into the look of the current screen saver, the asking of the password is done by the screen saver modules, when the screen saver core calls this function: int SSModule_AskUserForPassword(int iPwdBuffSize, char *pchPwdBuff); So, when this function of the saver module is called, the module should somehow get a password from the user.

Usually, it's done by showing a dialog window, an letting the user enter the password there, but it can be done in any other way, too. For example, when a running saver module gets this call, it could have an entry field flowing in from the left side to the middle of the screen, and waiting there for 10 seconds, to let the user enter the password. If the user doesn't enter it, or presses Esc, it could flow out to the right side, and this function should return SSMODULE_ERROR_USERPRESSEDCANCEL, indicating that no password has been entered. Otherwise, it should return the password in pchPwdBuff (where the buffer itself has a maximum size of iPwdBuffSize), and return SSMODULE_NOERROR.

One thing to note here is that you should always make timeout for these methods, so it won't happen that the password asking window comes up accidentally, and stays there forever, which would not really save the screen at the position of the password asking window.

If a module doesn't support password protection, it has to return SSMODULE_ERROR_NOTSUPPORTED. This will tell the screen saver core that there is no way to get a password from the user, so the core will simply not ask password, but stop the screen saver module, even if the password protection was turned on.

The password returned by this function will be compared by the screen saver core to the password set by the user. If the passwords match, the saver module will be stopped. But if the passwords do not match, the following function of the saver module will be called: int SSModule_ShowWrongPasswordNotification(void);

This should notify the user that the password he entered is wrong.

Again, this is usually done by a simple dialog window, but it can be done in any other way, too, possibly in more attractive ways, fitting into the look of the saver module.

The same applies here, which applied to the previous function: Have a timeout for this notification. Don't let this window kill the screen saving.

This function should return only when the password notification has been done.

These were the mandatory functions, required to be implemented by every saver module.

The optional functions
The following function are optional, they don't have to be implemented, but they help a lot if they are.

These functions have been implemented to support the DPMS states. When the screen saver core switches to a given DPMS state (e.g. Standby state), the monitor will go black, so there is no need for the saver to eat that much CPU it eats with updating the screen and calculating the new frames to be shown. For this reason, the following two functions of the saver module will be called by the screen saver core (if they exist, and exported by the current module), to temporary pause the saver module, and to resume its running later on. int SSModule_PauseSaving(void); This function should pause the CPU intensive tasks of the screen saver module. For example, it pauses the DIVE blitter thread in IFSIM module, so it will stop calculating the next frames, and it will not blit the frames to the screen any more. This, effectively, stops every CPU activity of the module. int SSModule_ResumeSaving(void); This function should undo everything done in SSModule_PauseSaving. It is called if SSModule_PauseSaving was called before, and the user presses a key or moves the mouse. In the IFSIM module, it re-enables the DIVE blitter thread, so that will continue calculating new frames and showing them on the screen.

Conclusion
We've went through the screen saver module API in this part of the article. We've seen that the saver modules have to be DLLs, and we've seen what functions it has to implement to become a valid screen saver module.

In the next part, we'll go through the steps of creating a simple saver module.