XScreenSaver is a collection of screen savers for X Window System. Each "module" is a standalone program that renders graphics to a window created by the XScreenSaver daemon.
- Any program can be a screensaver if it can draw on a window it didn't create
- You must use C (not C++) because other languages typically can't draw to external windows
- XScreenSaver provides utility functions to make development easier
- Start by modifying an existing simple screensaver (recommended: "Greynetic" or "Deluxe" for Xlib, "DangerBall" for OpenGL)
- Include "screenhack.h"
You need to define:
-
Two global variables:
yoursavername_defaults
- Default values for resourcesyoursavername_options
- Command-line options
-
Five functions:
yoursavername_init
- Creates and returns your state objectyoursavername_draw
- Draws a single frame (must be fast!)yoursavername_free
- Cleanup functionyoursavername_reshape
- Handles window resizingyoursavername_event
- Processes keyboard/mouse input
- Your
draw
function must run quickly (under 1/20 second) - Store ALL state in your state object, not in global variables
- Don't call XSync() or XFlush()
- Use provided color utilities, don't make assumptions about display depth
- Double-buffering to a Pixmap is better than erasing/redrawing
- Free all memory you allocate
If using OpenGL, you'll need to use the slightly different "xlockmore" API:
- Functions are named
init_hackname
instead ofhackname_init
- Must use ENTRYPOINT declaration before functions
- Requires using global variables for options and state objects (exception to the no-globals rule)
XScreenSaver runs on:
- X11 systems (Linux, Unix)
- macOS
- iOS and Android
If you follow the API correctly, your screensaver should work across platforms without modification.
- Test with
-pair
argument to catch global variable issues - For mobile compatibility, test with
-geometry 640x1136
and-geometry 640x960
- Use Valgrind or gcc's leak sanitizer to check for memory leaks
#include "screenhack.h"
struct state {
Display *dpy;
Window window;
GC gc;
int delay;
int width, height;
unsigned long fg, bg;
Colormap cmap;
int npixels;
unsigned long pixels[256]; /* For color allocation */
};
static void *
simpledemo_init (Display *dpy, Window window)
{
struct state *st = (struct state *) calloc (1, sizeof(*st));
XGCValues gcv;
XWindowAttributes xgwa;
st->dpy = dpy;
st->window = window;
XGetWindowAttributes (st->dpy, st->window, &xgwa);
st->width = xgwa.width;
st->height = xgwa.height;
st->cmap = xgwa.colormap;
/* Get resources */
st->delay = get_integer_resource (st->dpy, "delay", "Integer");
if (st->delay < 0) st->delay = 0;
st->fg = get_pixel_resource(st->dpy, st->cmap, "foreground", "Foreground");
st->bg = get_pixel_resource(st->dpy, st->cmap, "background", "Background");
/* Create GC for drawing */
gcv.foreground = st->fg;
st->gc = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
return st;
}
static unsigned long
simpledemo_draw (Display *dpy, Window window, void *closure)
{
struct state *st = (struct state *) closure;
int x, y, width, height;
XGCValues gcv;
XColor color;
/* Calculate random rectangle dimensions */
width = 10 + random() % (st->width / 2);
height = 10 + random() % (st->height / 2);
x = random() % (st->width - width);
y = random() % (st->height - height);
/* Choose a random color */
if (mono_p) {
/* In mono mode, just use foreground color */
gcv.foreground = st->fg;
} else {
/* In color mode, create a random color */
color.flags = DoRed | DoGreen | DoBlue;
color.red = random();
color.green = random();
color.blue = random();
if (XAllocColor(st->dpy, st->cmap, &color)) {
gcv.foreground = color.pixel;
if (st->npixels < 256)
st->pixels[st->npixels++] = color.pixel; /* Remember for later freeing */
} else {
/* If can't allocate, use foreground */
gcv.foreground = st->fg;
}
}
/* Set the new foreground color and draw */
XChangeGC(st->dpy, st->gc, GCForeground, &gcv);
XFillRectangle(st->dpy, st->window, st->gc, x, y, width, height);
return st->delay; /* Return milliseconds until next frame */
}
static void
simpledemo_reshape (Display *dpy, Window window, void *closure,
unsigned int width, unsigned int height)
{
struct state *st = (struct state *) closure;
st->width = width;
st->height = height;
}
static Bool
simpledemo_event (Display *dpy, Window window, void *closure, XEvent *event)
{
/* Return False to indicate we didn't handle the event */
return False;
}
static void
simpledemo_free (Display *dpy, Window window, void *closure)
{
struct state *st = (struct state *) closure;
if (st->npixels > 0)
XFreeColors(st->dpy, st->cmap, st->pixels, st->npixels, 0);
XFreeGC(st->dpy, st->gc);
free(st);
}
static const char *simpledemo_defaults[] = {
".background: black",
".foreground: white",
"*fpsSolid: true",
"*delay: 20000",
#ifdef HAVE_MOBILE
"*ignoreRotation: True",
#endif
0
};
static XrmOptionDescRec simpledemo_options[] = {
{ "-delay", ".delay", XrmoptionSepArg, 0 },
{ 0, 0, 0, 0 }
};
XSCREENSAVER_MODULE ("SimpleDemo", simpledemo)
-
Create and edit source file:
- Copy the boilerplate code to a new file (e.g.,
hacks/mynewsaver.c
) - Change all instances of "simpledemo" to your saver's name
- Update the copyright notice with your name/email
- Modify the state structure to include variables you need
- Implement your drawing code in the draw function
- Copy the boilerplate code to a new file (e.g.,
-
Update Makefile:
- Add your screensaver to
hacks/Makefile
:mynewsaver.o: mynewsaver.c $(HACK_INC) mynewsaver: mynewsaver.o $(HACK_OBJS) $(CC) $(LDFLAGS) -o $@ mynewsaver.o $(HACK_OBJS) $(LIBS)
- Add your saver to the
EXES
variable in the Makefile
- Add your screensaver to
-
Create XML configuration file:
- Create
hacks/config/mynewsaver.xml
(copy from another simple one) - Update the XML with your saver's name and options
- Update descriptions and parameters
- Create
-
Add to screensaver list:
- Add your saver to
driver/XScreenSaver.ad.in
- Add your saver to
-
Testing:
- Test standalone:
./mynewsaver -root
- Test with
-pair
to check for global variable problems - Test window resizing
- Test various display depths/color settings
- Test standalone:
-
Final checks:
- Ensure no global variables are used (except the required ones)
- Verify all memory is properly freed in the free function
- Check that drawing is reasonably efficient (completes in < 1/20 sec)
- Verify it works at different window sizes