« Writing a Screen Saver Module

Posted by Brian Christensen on April 10, 2001 [Feedback (2) & TrackBack (0)]

// Introduction

Writing a screen saver module is surprisingly simple using the public Screen Saver API (take a look in /Developer/Documentation/ReleaseNotes/ScreenSaver/ScreenSaverTOC.html for a detailed API reference). Sub-classing ScreenSaverView provides us with an interface for animating the screen saver, drawing the preview view in the System Preferences pane and displaying a configuration sheet.

Screen Saver Preview
Figure 1: This is what the final result will look like.


// Let's Get Started

First we'll need to create a new project. Launch Project Builder and choose "New Project..." from the File menu.

New Cocoa Bundle
Figure 2: Create a new Cocoa Bundle project.

From the list of choices you're presented with at this point you'll want to choose "Cocoa Bundle". Proceed to name the project "ScreenSaverTutorial" and save it in your preferred location (Apple recommends that you save it in ~/Projects, but in the end it's up to you). Once you've completed these steps, the project window will open.


// Preparing The Project

Before we can start writing code, we need to change a couple of project settings. Click the "Targets" tab on the left-hand side of the project window.

Figure 3: Change the WRAPPER_EXTENSION from "bundle" to "saver".

On the right-hand pane you'll see a row of tabs. What interests us right now is the "Build Settings" tab, so click that and scroll down to the "Expert Build Settings" section. Here you'll see a list of settings and their corresponding values. Look for "WRAPPER_EXTENSION" and change its value from "bundle" to "saver".

Once you're finished with that step, click "Files" in the vertical tab. On the left you'll see a hierarchical list of files and folders. Expand the "Classes" folder, click on the file "main.c" and hit the backspace key on your keyboard. Choose "Delete" in the resulting confirmation sheet.

Add Framework
Figure 4: Add the "Screensaver.framework" to your project.

We still have one last step to complete before we can start coding -- adding the "Screensaver.framework" to the project. To do this, go into the "Project" menu and select "Add Frameworks...". Navigate your way to /System/Library/Frameworks/ and choose the "Screensaver.framework" file.

Congratulations, you're ready to start writing code! Before we go on, let's create the source files we need. Choose "New File..." from the File menu, select "Objective-C class" in the Cocoa category, name it "TutorialView.m" and add it to the project. You might want to drag the resulting files to the "Classes" folder if it didn't put them there itself.


// Let's Do It

So, you're ready to start coding now? Okay, click on "TutorialView.h" in the left-hand pane to edit the file. Since our TutorialView class needs to be a subclass of ScreenSaverView and not NSObject, we need to modify the code to look like this (the changes you need to make are shown in bold):

#import <Cocoa/Cocoa.h>
#import <ScreenSaver/ScreenSaver.h>
@interface TutorialView : ScreenSaverView
- (void)animateOneFrame;

Since we're going to need to override the animateOneFrame method to draw our screen saver later, we might as well add it to the header file while we're at it.

Now's where the fun begins! Click the "TutorialView.m" file to edit it. Add the following method to it:

- (void)animateOneFrame;
    NSBezierPath *path;
    NSRect rect;
    NSSize size;
    NSColor *color;

First of all, let's obtain the screen boundary size for later use. To do this, we use the [self bounds] method. This returns an NSRect and we can access its size property directly, due to the fact that it's actually a struct.

    size = [self bounds].size;

Since we want our shapes to have random sizes, we can use the specially supplied SSRandomFloatBetween() function for this. It is automatically seeded by the screen saver framework, so there's no need to deal with that. Take a look in /Developer/Documentation/ReleaseNotes/ScreenSaver/Functions/ScreenSaverFunctions.html for an overview of all the randomizer functions.

    // Calculate width and height
    rect.size = NSMakeSize( SSRandomFloatBetween( size.width/100, size.width/10 ),
                            SSRandomFloatBetween( size.height/100, size.height/10 ));

Now we want to calculate a random origin point for our shape. To do this, we use the handy SSRandomPointForSizeWithinRect() function, which will do all the work for us (many thanks to Mike Trent for pointing this out, along with several other improvements).

    // Calculate random origin point
    rect.origin = SSRandomPointForSizeWithinRect( rect.size, [self bounds] );


// Using NSBezierPath

An NSBezierPath object lets you draw paths consisting of straight and curved line segments. Together these can form shapes such as rects, ovals, arcs and glyphs. In the following code we'll only be using rects and ovals, however you can experiment using the other shapes if you'd like (take a look in /Developer/Documentation/Cocoa/TasksAndConcepts/ProgrammingTopics/DrawBasic/index.html for more information).

We randomly decide whether to instantiate our NSBezierPath object as a rectangle (bezierPathWithRect:) or an oval (bezierPathWithOvalInRect:).

    // Decide whether to draw a rect or oval
    if (SSRandomIntBetween( 0, 1 ) == 0) {      
        path = [NSBezierPath bezierPathWithRect:rect];
    } else {
        path = [NSBezierPath bezierPathWithOvalInRect:rect];

Naturally we want to use random colors for our shapes, too:

    // Calculate a random color
    color = [NSColor colorWithCalibratedRed:(SSRandomFloatBetween( 0.0, 255.0 ) / 255.0)
                        green:(SSRandomFloatBetween( 0.0, 255.0 ) / 255.0)
                        blue:(SSRandomFloatBetween( 0.0, 255.0 ) / 255.0)
                        alpha:(SSRandomFloatBetween( 0.0, 255.0 ) / 255.0)];
                        // Notice the alpha parameter, 
                        // which allows us to do transparencies.

Last but not least, use the set method to set the color and fill to draw our shape to the screen.

    [color set];
    // And finally draw it
    [path fill];

That's about it.


// Build & Test It

You're ready to test the screen saver now. Select "Build" from the Build menu. When it's finished, go into the Finder and open the "build" folder located inside of your "ScreenSaverTutorial" project folder. You should see a "ScreenSaverTutorial.saver" file in there. Drag it into ~/Library/Screen Savers. To test it, open the System Preferences application, click the "Screen Saver" icon and select "ScreenSaverTutorial" from the list.


// Things To Keep In Mind

There's one thing you should keep in mind when you release a screen saver you've written. Don't assume the user knows how to install it, so be sure to include a read me document explaining the installation process (which basically entails putting the .saver file in "/Library/Screen Savers" or "~/Library/Screen Savers"). I just did something you shouldn't do in your installation instructions: using paths in the form of "~/Library/Screen Savers" to describe the location to put the file in. A novice user most likely won't know what that means, so instead write "drag the file to the Screen Savers folder in your Libary folder (located in your Home folder)". This sounds more complicated, but in the end it'll probably make more sense to a novice.


// Conclusion

So, now you've written your first screen saver for Mac OS X. That wasn't so hard, was it? For those of you who are looking for something to do with this new knowledge you've acquired, there's a screen saver contest going on over at http://www.stepwise.com/Contests/2001-04/. The deadline is 7pm EST May 16th, 2001, so write an original screen saver and enter it in the contest! Most importantly, have fun while doing it.


Tsk, tsk! That should be:

- (void)animateOneFrame
NSBezierPath *path;
NSRect rect;
NSSize size;
NSColor *color;

*without* the semicolon. Not likely to confuse, but it you copy/paste the line, you'll have trouble. Obviously not a high-priority typo. :)

Posted by: Spike Anderson on April 10, 2003 10:28 AM

Actually Spike, it's correct that way. You can try it, it works.

Posted by: Brian Christensen on June 30, 2003 07:28 PM
Post a comment