Skip to main content

Introducing JDesktop Integration Components, Part 2

November 1, 2004

{cs.r.title}












Contents
What is the SaverBeans SDK?
A Simple Example
A Custom Screensaver
Adapting
an Existing Program
Further
Work
Conclusion
Resources

"Write once, run anywhere." This has been the mantra of Java developers
since day one. The philosophy has given us great flexibility and power, but
always with trade-offs. Certain types of programming have effectively been
off-limits to Java developers because of the speed requirements or access
to native services. Now, finally, things are looking better. JIT virtual
machines and Moore's Law have taken care of the speed improvements and the
new JDesktop Integration Components finally let Java developers write programs that tie closely
to the native operating system. Part one of this series
covered the desktop, file, browser, and system tray components. In this
article, we will explore an entirely new area, the SaverBeans SDK, which lets us write screen savers entirely in Java. We will go
from a simple example to adapting a particle simulator with user-supplied
configuration.

What is the SaverBeans SDK?

A screensaver seems like a simple thing: just fill the screen with a
pretty animation and you're done. Sadly, nothing in the world of computers
is that simple. A screensaver must be packaged in a certain way for the
host operating system to recognize it and launch it at the correct time. And,
of course, every operating system has its own way of doing this. Since JDIC
was already doing such a great job of unifying disparate platform-specific
APIs, it was only logical to tackle this one as well. The SaverBeans SDK is
an incubator project that provides a cross-platform API for accessing the
screen, along with a packager tool to produce platform-specific executables.
SaverBeans doesn't come with any screensavers, but there is a secondary project where various
java.net citizens have posted their own creations.

In this article, you will learn how to build a simple countdown screensaver;
what to download, how to create your classes, and how to package
it all up. You will also see how to take an existing program and convert it
into a screensaver, including user configuration.

I should mention at this point that SaverBeans supports Windows, Linux,
and Solaris, but not the Mac. The SaverBeans API uses native code
underneath to do its magic, and no one has written a hook into the OS X
screensaver API yet. If you know a little bit about native OS X programming,
then please join the JDIC project
and help out.

A Simple Example

Now that we have all of that out of the way, let's go make some
screensavers. For the first one I've chosen something simple: a Christmas
countdown timer. Every time you activate the screensaver, your computer
will display the time left until Christmas morning, with the millisecond
accuracy that anxious eight-year-olds demand.

First of all, you will need to download the SDK from the
SaverBeans project page
.
This .zip file includes both the SDK itself and a sample screensaver you can
compile and run. The native libraries have already been compiled so you
won't need any native tools or C compilers to get started. You will need Ant and a recent JDK, though: 1.4 for
Windows and 1.5 for Linux (due to some animation problems with 1.4 for
Linux). Unzip this file and you will have a directory containing the main .jars,
plus a second .zip file, saverbeans-startup.zip, which contains the sample
screensaver.

I will only cover the Windows instructions here, but the documentation
contains similar instructions for Linux. The only major difference is where you
put the resulting screensaver executable.

Unzip the file and go into saverbeans-startup, and then copy the
build.properties.sample file to build.properties. Edit
the properties file and change the saverbeans.path property to
point to where you have the saverbeans-ant.jar file (usually
..). Now type ant dist, which compiles the code,
moves your program resources to platform directories (currently one each
for Windows, Solaris, and Linux), then builds a custom executable (a
.scr file, under Windows) that launches the screensaver class
files. Copy the contents of dist/bouncingline-win32 to your
C:/windows/system32 directory. Now head to the display
properties control panel, and select the bouncingline screensaver. If all
goes well, you will see a small preview in the mini-window of a bouncing
line, as seen in Figure 1.

Figure 1. Bouncinglines preview

Figure 1. Bouncinglines preview

A Custom Screensaver

Now let's make our own! Here's the beginning:

package org.joshy.screensavers;

import org.jdesktop.jdic.screensaver.*;
import org.joda.time.*;
import java.awt.*;
import java.io.*;

public class XmasCountdown extends SimpleScreensaver {
    DateTime xmas;
    Duration old_dur;
    boolean first;
   
    public void init() {
        xmas = new DateTime(2004,12,25,5,40,0,0);
        DateTime now = new DateTime();
        old_dur = new Duration(DurationType.getDayHourType(),
                               now,xmas);
        first = true;
    }

The SaverBeans API is quite simple. Just subclass the
SimpleScreensaver class in the
org.jdesktop.jdic.screensaver package, and then override two
methods. In the first method, init(), I have used a special
DateTime class from the Joda project. Joda is a set of date and time utilities that replace the
default Date and Calendar classes in Java. I chose to use Joda because it
makes calculating the distance between two dates much easier than the standard
Calendar class. In the init method, I
created a timestamp for Christmas at 5:40 in the morning, which is about
the time I used to get up as a child. I've also stored the initial distance
between now and Christmas in a Duration object, old_dur. The Boolean,
first, tells the paint method that it has just been initialized
so that it knows to redraw the entire screen. The paint method is below:

public void paint(Graphics g) {
    DateTime now = new DateTime();
    Duration dur =
        new Duration(DurationType.getDayHourType(),
                     now,xmas);
   
    // get the screen size and clear the screen
    Component c = getContext().getComponent();
    int w = c.getWidth();
    int h = c.getHeight();
   
    ((Graphics2D)g).setRenderingHint(
        RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    Font font = new Font("SansSerif",Font.PLAIN,50);
    g.setFont(font);
   
    // draw the header text
    g.setColor(Color.green);
    if(first) { g.drawString("Countdown to Christmas",10,50); }
               
    drawText(g,"Days: ", dur.getDays(),
        old_dur.getDays(),150);
    drawText(g,"Hours: ", dur.getHours(),
        old_dur.getHours(),200);
    drawText(g,"Minutes: ", dur.getMinutes(),
        old_dur.getMinutes(),250);
    drawText(g,"Seconds: ", dur.getSeconds(),
        old_dur.getSeconds(),300);
    drawText(g,"MilliSeconds: ", dur.getMillis(),
        old_dur.getMillis(),350);
   
    old_dur = dur;
    first = false;
}

I'll go through this step by step. The first two lines calculate the
distance between the current time and Christmas. Next, it pulls out a
reference to the drawing component via the context and calculates the width
and height of the screen. After that it turns on anti-aliasing, sets up a
large font, and then draws the title "Countdown to Christmas" in green.
The title is only drawn on the first pass so that we don't have to erase
and redraw it each time. The rest of the paint method draws each part of
the time until Christmas on its own line in red using the
drawText method. Finally we update the old_dur and first
variables for the next time paint() is called.

To get this class running as a screensaver, we have to tell Ant about
our new class and .jar. Start by modifying the screensaver and
screensaver.class properties at the top of the build.xml file.

If you need extra .jars for your screensaver, like mine does, then things
get a bit tricker. Due to a limitation in the launching system, a SaverBeans
screensaver cannot specify a complete classpath. Instead, it hands Java the
name of a .jar from which to load the main class. I modified the manifest of my
main .jar to automatically include the support .jars, like this:

Manifest-Version: 1.0
Main-Class: org.joshy.screensaver.XmasCountdown
Class-Path: joda-time-0.95.jar

To get this manifest into the .jar, I modified the jar task in the dist
target like this:

<jar destfile="${build}/jar/${screensaver}.jar"
        manifest="src/conf/MANIFEST.MF">
    <fileset dir="${build}/share" />
</jar>
;

There is only one last thing to do. Each screensaver has a config file,
stored in the src/conf directory. I copied the default one into
xmascountdown.xml and changed the .jars and classes to match the new
screensaver.

<?xml version="1.0" encoding="UTF-8"?>

<screensaver name="xmascount"
    _label="Countdown to Christmas">
   
    <command arg="-root"/>
    <command arg="-jar xmascount.jar"/>
    <command arg="-cp joda-time-0.95.jar;xmascount.jar"/>
    <command
    arg="-class org.joshy.screensavers.XmasCountdown"/>
    <file id="jdkhome" _label="Java Home (blank=auto)"
    arg="-jdkhome %" />

    <_description> This screensaver counts down,
    with great precision, the amount of time left until
    the magic day, defined as just a hair after
    midnight Christmas Eve. </_description>

</screensaver>

Some of the elements may look a little strange, like the
_description, for example. That is because this is actually
the configuration file for XScreenSaver, the
gold standard of Unix screensaver programs. Under Linux
and Solaris, this file is used directly to launch the program and create a
settings screen. Since Windows just uses .scr executables, SaverBeans
will parse this and generate a settings screen internally. Though this part
is a bit complicated, for the most part, you can set it up once and
forget about it.

Now run ant clean dist again and your new screensaver will be placed in
the dist/xmascount-win32 directory. Copy the contents
(xmascountdown.scr and the three .jar files) to
C:/windows/system32 and your screensaver is ready to go. It
should look something like Figure 2.

Figure 2. Countdown To Christmas Screensaver

Figure 2. Countdown To Christmas Screensaver








Adapting an Existing Program

Well, that wasn't so bad, and once you do it the first time it becomes
pretty easy to manage it all with the Ant file. Now let's try something
more complicated: turning an existing application into a screensaver.

In a previous article,
"
Getting Started with Scripting,"
I built a
simple particle simulator. It used a mixture of JavaScript and Java code to
create colorful displays on the screen. What better way to show off those
displays then to create a screensaver for them?

The SimEditor class is essentially a shell containing a SimView to
display the graphics, a JavascriptProvider to create the code buffers that
draw each frame, and a SimRunner to control the animation in a thread.
Turning this into a screensaver will be a simple exercise in refactoring.

To ensure our changes have as little impact on the codebase as possible,
I'm going to try to keep all of the new code in a single class,
SimScreenSaver. As with the Christmas counter, we need to make it a
SimpleSaver subclass. Since we won't have an onscreen editor to type in the
JavaScript, SimScreenSaver will have to be a JavascriptProvider, too. So
here's what we have so far:

public class SimScreenSaver 
    extends SimpleScreensaver
    implements JavascriptProvider {
   
    SimRunner runner;
    SimFrame frame;
    public String init, update, draw;
    SimView view;   

    public void init() {
       
        SimContext ctx = new SimContext();
        ctx.setBlur(true);
        ctx.setRadialStretch(true);
        ctx.setMaxParticles(20);
       
        load("circle.xml");

       
        runner = new SimRunner();
        runner.setContext(ctx);
        context.setRunner(runner);
       
        // setup the javascript provider frame and runner
        frame = new JavascriptFrame(this);
        runner.setFrame(frame);
        runner.setup();
        runner.initParticles();
       
        // prepare the view
        view = new SimView();
        int w = getContext().getComponent().getWidth();
        int h = getContext().getComponent().getHeight();
        view.setSize(w,h);
        view.setContext(ctx);
    }
   
    public String getInit() {
        return init;
    }
    public String getUpdate() {
        return update;
    }
    public String getDraw() {
        return draw;
    }
}

At the top there is a reference to each piece we will need: a SimRunner,
a SimFrame, and a SimView. In the init method, we create a SimContext to tie
each component together, and to hold the display settings (like the maximum
number of particles).

The load method (the implementation of which is not shown above) loads an XML file
containing each snippet of Java code into the init,
update, and draw code buffers. It loads the XML
from the classpath so the user doesn't have to worry about about where to
install the XML files.

At then end of the init method, you can see that we call getContext() and
then getComponent(). This is to get a reference to the actual drawing
canvas that fills the screen when the saver is activated. Using the size of
this canvas, we can set the size of the view. It is important not to hard
code the screen dimensions, because the end user's computer might be running
at a different resolution or the saver might be in preview mode (which is often
quite tiny).

After the init method is the implementation of
JavascriptProvider that returns the code buffers for the
init, update, and draw phases of the simulator.

Now that the screensaver is set up, we have to actually draw it. Normally,
we would call runner.start() to kick off the animation, but we
don't want to do that here. The SaverBeans framework has its own animation
thread that will call the SimScreenSaver's paint method at the
right time, so we don't need to use the SimRunner's threading. However, we
do want to reuse its loop method that launches the Java interpreter and
paints the screen. The only problem is that if we just called
runner.loop(), it would block forever. The method
loop is synchronized because it was originally intended for
use inside the runner's animation thread. To get around, this I have created
a second loop method called loopUnSynchronized() that does the same thing,
but without the synchronized keyword and the wait() call. This way, we can
use whichever form of looping is appropriate for the situation.

After the runner has looped, we just want to paint the view. The view is
a JPanel, but it hasn't been added to a JFrame and been realized on screen.
However, since we are running inside of the SaverBeans framework, we don't
need all of its Swing code. Instead, we just reuse its paintComponent()
method to draw the particles onscreen. Here is the new SimScreenSaver paint
method:

    public void paint(Graphics g) {
        runner.loopUnsynchronized();
        view.paintComponent(g);
    }

With this class, we can build a new screensaver like we did before and
drop the SimSaver.scr file into the system directory. The
running screensaver is shown in Figure 3.



Figure 3. SimScreenSaver running Circles

The simulator works. There's just one problem though. Using SimEditor,
the user could choose which sim to run and turn on the blurring, but these
values are hard coded in SimScreenSaver. It would be nice to provide a
control panel for the user to change these settings. Fortunately, the
SaverBeans framework provides a way to do this as well. You may recall that
the configuration XML is actually an XScreenSaver definition file. In addition to
holding startup parameters, the file can also define user-selectable
settings. The framework will automatically create a settings panel for the
user and then pass the selected settings to the screensaver. Under Windows,
this is done with a special Java application. Under Linux, the settings
screen is created by XScreenSaver itself.

Here is our new config file:

<?xml version="1.0" encoding="UTF-8"?>
<screensaver name="SimSaver"
  _label="Javascript Particle Simulator Screensaver">
  <command arg="-root"/> 
  ...
 
  <!-- start settings definitions -->
  <select id="blur">
    <option id="on" _label="Blur On"
    arg-set="-blur on" />
    <option id="off" _label="Blur Off"
    arg-set="-blur off" />
  </select>

  <select id="radial">
    <option id="on" _label="Radial Stretch On"
    arg-set="-radial on" />
    <option id="off" _label="Radial Stretch Off"
    arg-set="-radial off" />
  </select>

  <select id="simfile">
    <option id="circle.xml" _label="Simple Circle"
    arg-set="-simfile circle.xml" />
    <option id="swirl.xml"   _label="Eccentric Circle"
    arg-set="-simfile swirl.xml" />
    <option id="falling.xml" _label="Falling Lines"
    arg-set="-simfile falling.xml" />
    <option id="volcano.xml" _label="Volcano"
    arg-set="-simfile volcano.xml" />
    <option id="raindrops.xml" _label="Rain Drops"
    arg-set="-simfile raindrops.xml" />
  </select>
 
</screensaver>

The config file above defines three select values: blur, radial, and
simfile, which will be displayed as three drop-down menus to the user. The
id of the select is the key we can use later to retrieve the
selected value. Each select has several options whose ids define the
possible values. The _label attribute is the text of the
option that will be displayed to the user. The arg-set defines
the options that will be passed to XScreenSaver under Linux, so those
should match the select and option ids. For example, the first select is
blur and its first option is on, so the
arg-set for the first option would be -blur on. With these
settings in the config file, we will get a settings panel, the same as when the
user selects settings in the screensaver control panel. The
settings panel is shown in Figure 4.

Figure 4. SimSaver settings screen

Figure 4. SimSaver settings screen

Back in our SimScreenSaver class, we need to retrieve the current
settings, providing defaults if the settings are missing (i.e., if the user
didn't select anything). The modified init() method looks like this:

public void init() {
    ScreensaverSettings settings = getContext().getSettings();
   
    SimContext ctx = new SimContext();
   
    // setup blur
    String blur = settings.getProperty("blur");
    if(blur == null || blur.equals("on")) {
        ctx.setBlur(true);
    } else {
        ctx.setBlur(false);
    }
   
    // setup radial stretch
    String radial = settings.getProperty("radial");
    if(radial == null || radial.equals("off")) {
        ctx.setRadialStretch(false);
    } else {
        ctx.setRadialStretch(true);
    }
   
    ctx.setMaxParticles(20);
   
    // setup and load the sim file
    String file = settings.getProperty("simfile");
    if(file == null) {
        file = "circle.xml";
    }
    load(file);
    ... rest of init method
}

First we have to retrieve the ScreensaverSettings object
from the context with getContext().getSettings(). Next we test the value of
each property (checking for null, of course) and set the appropriate value
in the SimContext, ctx. With that, users can change the screensaver to
their hearts' content.

In addition to the sims from the original
scripting article,
I have included a new sim called Rain Drops. It draws ever-expanding circles that combine
with the blur effect to look like Figure 5:

Figure 5. Raindrops screensaver

Figure 5. Raindrops screensaver

Further Work

If you are interested in playing with the
simulator more, you can find the entire source on
my web site here.
The SaverBeans framework is here and you can find
tons of screensavers built by other developers. I recommend browsing the
screensavers forum
to find tips and tricks on
building your own screensavers.

Conclusion

As part of the JDIC project, SaverBeans provides a framework for
building a type of Java application never before possible, and, as always,
making it cross-platform. In the few months since its release, JDIC has
proven that there is pent-up demand for doing interesting things with
desktop Java. As it matures, I hope JDIC will continue to grow and provide us
with the kind of robust feature set we expect from Java.

Resources

Josh Marinacci first tried Java in 1995 at the request of his favorite TA and has never looked back.
Related Topics >> GUI   |