How to write a screen saverBy David Llewellyn-Jones. Published: 2nd Sep 2006, 16:53:44 | Permalink | Printable
Last night some C code saved my lifeIn this hands-on C programming tutorial, David Llewellyn-Jones produces an example screen saver to explain how RISC OS screen savers are written, and how they work.
Screen savers have always been a staple free software for RISC OS, with the likes of the venerable Out to Lunch, AlphaSave, and the now GPLed Delirium all acting as saviours of the CRT for many years. However, it was not until RISC OS 4 that graphical screen saver capabilities shipped as a standard part of the OS.
In spite of its existence in every new version of the OS, relatively few new screen savers have been written to take advantage of the feature. So we thought we'd take a look at how screen saver plugins can be written, in the hope that maybe we might see a few more in future.
In fact, writing a new screen saver for use on RISC OS 4 or 5 is not that hard if you already know how to write a standard WIMP desktop application. Unfortunately, we're not going to be able to start from the very beginning, and we'll have to assume that you already know how to do this. If not, then let us know and Drobe will cover it in a future tutorial.
For the purposes of this explanation we're going to be programming in C using GCC, with UnixLib, and OSLib. If you don't already have these you'll need to download copies if you want to compile the example files.
The most important thing to realise is that screen saver programs are just WIMP applications much like any other. In effect, when run, they create a large black window that covers the screen and in which they display their graphical shenanigans. There are just one or two additional points that are needed in order to create a screen saver that interacts seamlessly with the rest of the desktop:
- When the user moves the mouse or presses a key, RISC OS will send us a notification that it's time to stop. We need to watch out for this.
- When we create our black covering window, we need to give it suitable properties so that it stays right at the front of the window stack and doesn't get covered by anything else.
- We need to generally keep things tidy, for example by removing the mouse pointer.
- There are a few things that we need to take note of relating to system variables.
If we can keep track of all of these points, then we'll be away. To understand how these work in action, let's take a look at an example screen saver. If you download and look inside the example archive, you should see something along the following lines.
The example screen saver's files
The screen saver application itself is called !Bristle, and the program we compile will go inside this as the !RunImage file. Note that screen saver applications shouldn't be given !Sprite files, since that would mean we'd have to register their name, which would get tedious. For the same reason, we shouldn't use a
Bristle$Dir system variable as we would normally do with an application. Instead, there's a special system variable
BlankTask$Dir that's been set aside for the purpose and which we can use instead. Having all screen savers use the same variable shouldn't generally cause problems, since we'll never be using more than one at the same time.
The Build obey file can be used to compile the program using GCC, and it's recommended that you do this from a task window. If you open this file in a text editor you'll see that it basically executes the following command:
gcc -mthrowback -O1 -IOSLib: -Werror ScrSave.c Choices.c Bristle.c OSLib:OSLib32.o -o !Bristle.!RunImage
This tells GCC to compile and link the three source files ScrSave, Choices and Bristle, saving the resulting executable as !RunImage inside the !Bristle directory. The three source files for compilation can all be found inside the 'c' directory. The 'h' directory contains their respective header files as expected.
The example screen saver's source code files
In brief, the c.ScrSave source file contains the generic code structure of an application needed to create any screen saver. The c.Bristle file contains all of the code that is specific to this particular screen saver. Basically, that's the functions to draw the graphical ditty on the screen. Finally the c.Choices file simply contains some useful functions for reading and writing configuration files; we can pretty much ignore everything contained in this last file.
Almost all of the action takes place in the c.ScrSave file and so we're going to spend most of our time looking at this. So, let's start as we mean to go on, and see what we've got.
The structure is the same as any other wimp application. The
main function is entered on line 63, starting with a whole bunch of initialisation code (lines 64-119). Here we read in our messages file (lines 76-81), initialise the application (lines 83-85) and load in the window templates we're going to use (lines 87-95). If you look inside the Templates file using something like WinEd, you'll see that we actually only have one window, and not a very interesting one at that. This is the window that we're going to be using to cover the screen when the screen saver kicks into action.
The window template is loaded using the
LoadTemplate function (lines 328-357). It's worth taking a look at this, since here we'll see the first difference between a normal application and our screen saver. When we create the window, we change a couple of its properties in lines 349 and 350:
pcWindow->next = wimp_TOP;
pcWindow->flags |= wimp_WINDOW_FOREGROUND_WINDOW;
The first of these (line 349) sets the window to be at the top of the window stack so that it appears in the foreground (wimp_TOP is set to be -1). This is important so that the screen saver covers all of the desktop. The second (line 350) sets the foreground window flag (bit 23), which is new to RISC OS 4. This prevents any other window from appearing in front of our screen saver window - the only exceptions being other windows that also have this flag set, such as error boxes.
Keeping track of the user
Returning to the initialisation routine for our application, we also need to pick up some information that RISC OS provides to us as a command line argument. When the computer has been left idle for the chosen amount of time, RISC OS will fire up the application to start the screen saver, executing it with a value as the first command line argument. As with any C application, the value is passed to the
main function in the string
argv. Since we need this value, we read it in and convert it to a number (lines 97-106).
The number passed in represents a pollword and is crucial to the operation of the screen saver. A pollword is basically a pointer to a piece of memory which is passed to
Wimp_Poll. The Window Manager checks this memory location, and as soon as the value stored at this location becomes non-zero, it returns to the application with a Pollword non-zero reason code. In our case, this is used to tell us when the user has moved the mouse or pressed a key, without us having to worry about all of the details.
In short, we pass the value in as a parameter to
Wimp_Poll (line 125) and set up an event handler checking for Pollword non-zero events (line 172). As soon as we receive this event, we know that the user has moved the mouse or some other event has occurred that means it's time for us to quit. Note that in order for the WIMP to check for pollwords, we also need to set bit 22 of the
Wimp_Poll flags. We do this on line 116. We also set bit 23 (line 115). This sets the Pollword non-zero event to high priority, so that it's the first thing that we receive if we're going to. However, we only do this for the first poll loop, in case the user moves the mouse immediately after the screen saver has fired up. For subsequent cycles we turn this flag off (line 130).
Note that we also open our black covering window immediately when the application is started, by calling the
OpenWindowScreenFull function (line 109). Looking at the details of this function (lines 274-320), we see that it first reads in the size of the screen using a bunch of
OS_ReadModeVariable calls (lines 291-295 and 298-302) and then sets the size of the window to fill the screen using
Wimp_SetExtent (lines 305-309). If we'd got our act together earlier we could have set the size of the window when we loaded in the template, but this will work just as well for the time being.
The final task we need to do before going into the main polling loop is to set up any variables needed to actually display the screen saver animation. We do this on line 112, where we call the
InitAnimation function. Since this is going to be specific to the screen saver, the actual function definition has been annexed off into the c.Bristle source file. We'll look at it shortly.
The main polling loop is pretty mundane, since our screen saver isn't actually going to do a great deal. We only have handlers for a few basic poll events. As well as the standard collection and the Pollword non-zero handler already mentioned, we also make sure we pick up null polls, so that we can animate the display, along with Pointer Entering Window and Pointer Leaving Window events. The latter two are used to turn off and on the mouse pointer respectively (lines 263 and 254).
Consequently, when the main window gets put up over the rest of the desktop, the mouse will also be removed. It's worth bearing in mind, however, that the first we'll hear about the mouse leaving the window is most likely to be when the application quits. It's therefore also important that we turn the mouse pointer back on irrespective of when the program ends (line 207), otherwise we may lose the pointer for good.
The null poll is used to call the animation function AnimateScreenSaver for our screen saver (line 138). Again, this is going to be specific to our particular screen saver, so has been moved into the c.Bristle source file. We'll also look at this in more detail shortly.
That pretty much covers all of the generic functionality that we're going to need from our screen saver, with one exception. The screensaver specification requires that we set a system variable
ScrSaver$Running to the value 'Yes' when we run our application. We do this in the !Run file for the !Bristle application:
Set ScrSaver$Running Yes
However, when our application quits we also need to unset this variable again, so that RISC OS and other programs know there's no longer a screen saver running. We therefore do this in the final quit routine on lines 218-220 using a call to
Having got all of the generic work done, we now need to consider the specifics for our particular screen saver. Our saver will draw a grid of bristles on the screen that move around in a kind of synchronised pattern.
The floating bristle effect
To achieve this we basically need three things: we need to initialise the animation, we need to be able to update the animation over time, and we need to be able to draw the animation frames to the screen.
This can all be done in a relatively standard way. We've already seen the call to
InitAnimation to initialise the animation details. If we look at lines 309-378 of the c.Bristle source file we can see what this function actually does. Lines 320-348 load in various configuration parameters from any Choices file saved by the user - this section uses some of the functions from the c.Choices source file, but any means of reading in the data would do. Lines 360-377 set up the variables needed by the animation.
All of the animation drawing code is done by the
DrawScreenSaver function (lines 140-206). You can use pretty much any kind of drawing code you fancy, but it's worth noting that screen saver applications are not supposed to change the screen mode; they're supposed to work in whatever mode the user happened to be in when the screen saver fired up. As a result, it's important to use mode-independent drawing methods. To make sure of this we've used only the standard OS drawing routines, although there may be other more exciting mode-independent graphics routines that could be used, such as those of the SDL libraries.
The drawing routine might be called in a couple of situations, either as a result of a standard redraw by the WIMP, or as part of the animation process. It's worth taking the time to appreciate the subtle distinction between them. When the Window Manager asks us to redraw part of the window, it's likely to be as a result of the window being drawn for the first time, or some other window being dragged over the top of our window.
For a screen saver, this second situation is highly unlikely to occur, but we need to cater for it to some extent anyway. Whatever the Window Manager's reason for asking us to redraw the window, it'll be likely that when it does so, whatever was in our window probably got messed up, so we're going to have to redraw it in full.
In contrast, when the update is the result of animation, redrawing everything from scratch is likely going to result in a horrendous flickering effect. It would be much better if we could just redraw those parts that have changed. To cater for this, the
DrawScreenSaver function is passed an enum variable
eDrawType. This is set to
ANIMDRAW_CLEAR if the area needs redrawing from scratch, and to
ANIMDRAW_UPDATE if only the changes need to be drawn.
Looking carefully at our Bristle example, we can see that for a full redraw we first completely wipe the area with a big black rectangle (lines 156-162) before redrawing all of the bristles (lines 180-204). If we're only doing an update, we leave the background as it is, but before drawing each bristle in its new position we first draw a black bristle in the old position to remove it from the screen (lines 166-178). This way we can achieve relatively smooth animation without having to worry about things like using screen buffering.
The final routine that we need to consider is the
AnimateScreenSaver function. This is called periodically on null WIMP polls, so that we can update our animation. By default, this will be called every two centiseconds, or as close to this as possible, depending on how long our drawing takes and how much time other applications need. The
fChange variable passed in to the function tells us how long it's been since the previous call - so in an ideal situation it will always take the value two.
For our animation we therefore do various calculations to update the positions of all of the bristles (lines 224-294) and then call the
UpdateScreenSaver function (line 297) to redraw the screen, which goes on to call the
DrawScreenSaver function we discussed above. If we fail to redraw the screen here, we're not going to get any animation.
Finally, we take a copy of the current position of all of the bristles (lines 299-305), so that we know how to remove them from the screen the next time we update the display. Putting everything together, we should end up with an animated screen saver that works in tandem with the built-in RISC OS screen saver routines.
Hopefully the above will have explained all of the details needed for you to write your own screen saver. If you want to use the source files supplied as a base, then you should be able to get away with changing only the three functions
InitAnimation found in the c.Bristle source file. However, if you want to write your own from scratch, this should also have provided most of the information you need. For the final word on all things screen saver, the full functional specification is also available from the RISC OS Ltd website.
Once you've created your screen saver, give it a go by placing the finished application inside the screen saver resource directory, which is
!Boot.RO500Hook.Res.Configure.ScrSavers on RISC OS 5, then go to the Screen section of the RISC OS configuration utility to try it out.
The finished project
Download the example screen saver and its source code
Previous: WebWonder review
Next: German ROS show this month
DiscussionViewing threaded comments | View comments unthreaded, listed by date | Skip to the end
Please login before posting a comment. Use the form on the right to do so or create a free account.
Search the archives
Today's featured article
RISC OS 5 pictured running on ARM Cortex-A8 kit
Picture exclusive - This grainy photograph shows a port of RISC OS 5, sourced from the RISC OS Open project, running on a Beagleboard - a device powered by a 600MHz ARM Cortex-A8 processor with a built-in graphics chip. The port, developed by Jeffrey Lee with help from Uwe Kall and ROOL staff, is seen as a major breakthrough for the shared-source project as it proves the OS can be ported to new hardware without the need for a large team of engineers.
75 comments, latest by rjek on 30/4/09 3:15PM. Published: 25 Apr 2009
Omega sighted in Scotland
We went to Livingston. We saw the Omega. Were we impressed? Read on...
25 comments, latest by diomus on 13/9/02 6:36PM. Published: 7 Sep 2002
News and media:
RISCOS Ltd •
RISC OS Open •
MW Software •
Advantage Six •
CJE Micros •
Liquid Silicon •
Chris Why's Acorn/RISC OS collection •
The Register •
The Inquirer •
Apple Insider •
BBC News •
Sky News •
Google News •