Skip to content

Instantly share code, notes, and snippets.

@shmup
Last active April 4, 2025 23:55
Show Gist options
  • Save shmup/8d0c3ae839ef0bf5ad7f2d79c942dde9 to your computer and use it in GitHub Desktop.
Save shmup/8d0c3ae839ef0bf5ad7f2d79c942dde9 to your computer and use it in GitHub Desktop.

XScreenSaver: Simplified Guide

What Is XScreenSaver?

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.

Basic Approach

  1. Any program can be a screensaver if it can draw on a window it didn't create
  2. You must use C (not C++) because other languages typically can't draw to external windows
  3. XScreenSaver provides utility functions to make development easier

How To Create Your Own Screensaver

Getting Started

  1. Start by modifying an existing simple screensaver (recommended: "Greynetic" or "Deluxe" for Xlib, "DangerBall" for OpenGL)
  2. Include "screenhack.h"

Core Components

You need to define:

  • Two global variables:

    • yoursavername_defaults - Default values for resources
    • yoursavername_options - Command-line options
  • Five functions:

    • yoursavername_init - Creates and returns your state object
    • yoursavername_draw - Draws a single frame (must be fast!)
    • yoursavername_free - Cleanup function
    • yoursavername_reshape - Handles window resizing
    • yoursavername_event - Processes keyboard/mouse input

Key Rules

  1. Your draw function must run quickly (under 1/20 second)
  2. Store ALL state in your state object, not in global variables
  3. Don't call XSync() or XFlush()
  4. Use provided color utilities, don't make assumptions about display depth
  5. Double-buffering to a Pixmap is better than erasing/redrawing
  6. Free all memory you allocate

OpenGL Screensavers

If using OpenGL, you'll need to use the slightly different "xlockmore" API:

  • Functions are named init_hackname instead of hackname_init
  • Must use ENTRYPOINT declaration before functions
  • Requires using global variables for options and state objects (exception to the no-globals rule)

Cross-Platform Considerations

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.

Testing Tips

  1. Test with -pair argument to catch global variable issues
  2. For mobile compatibility, test with -geometry 640x1136 and -geometry 640x960
  3. Use Valgrind or gcc's leak sanitizer to check for memory leaks

Simple XScreenSaver Boilerplate

#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)

Checklist for Implementing Your XScreenSaver Module

  1. 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
  2. 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
  3. 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
  4. Add to screensaver list:

    • Add your saver to driver/XScreenSaver.ad.in
  5. Testing:

    • Test standalone: ./mynewsaver -root
    • Test with -pair to check for global variable problems
    • Test window resizing
    • Test various display depths/color settings
  6. 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment