Adding files related to the `screenhack` API for X11 hacks.
authorAaron Taylor <ataylor@subgeniuskitty.com>
Wed, 10 Mar 2021 09:38:09 +0000 (01:38 -0800)
committerAaron Taylor <ataylor@subgeniuskitty.com>
Wed, 10 Mar 2021 09:38:09 +0000 (01:38 -0800)
All files imported without modification from `xscreensaver-5.45.tar.gz`.
See file headers for license details.

57 files changed:
screenhack/aligned_malloc.c [new file with mode: 0644]
screenhack/aligned_malloc.h [new file with mode: 0644]
screenhack/alpha.c [new file with mode: 0644]
screenhack/alpha.h [new file with mode: 0644]
screenhack/colors.c [new file with mode: 0644]
screenhack/colors.h [new file with mode: 0644]
screenhack/erase.c [new file with mode: 0644]
screenhack/erase.h [new file with mode: 0644]
screenhack/fade.c [new file with mode: 0644]
screenhack/fade.h [new file with mode: 0644]
screenhack/font-retry.c [new file with mode: 0644]
screenhack/font-retry.h [new file with mode: 0644]
screenhack/fps.c [new file with mode: 0644]
screenhack/fps.h [new file with mode: 0644]
screenhack/fpsI.h [new file with mode: 0644]
screenhack/grabscreen.c [new file with mode: 0644]
screenhack/grabscreen.h [new file with mode: 0644]
screenhack/hsv.c [new file with mode: 0644]
screenhack/hsv.h [new file with mode: 0644]
screenhack/minixpm.c [new file with mode: 0644]
screenhack/minixpm.h [new file with mode: 0644]
screenhack/overlay.c [new file with mode: 0644]
screenhack/pow2.c [new file with mode: 0644]
screenhack/pow2.h [new file with mode: 0644]
screenhack/queue.h [new file with mode: 0644]
screenhack/resources.c [new file with mode: 0644]
screenhack/resources.h [new file with mode: 0644]
screenhack/screenhack.c [new file with mode: 0644]
screenhack/screenhack.h [new file with mode: 0644]
screenhack/screenhackI.h [new file with mode: 0644]
screenhack/spline.c [new file with mode: 0644]
screenhack/spline.h [new file with mode: 0644]
screenhack/textclient.c [new file with mode: 0644]
screenhack/textclient.h [new file with mode: 0644]
screenhack/thread_util.c [new file with mode: 0644]
screenhack/thread_util.h [new file with mode: 0644]
screenhack/usleep.c [new file with mode: 0644]
screenhack/usleep.h [new file with mode: 0644]
screenhack/utf8wc.c [new file with mode: 0644]
screenhack/utf8wc.h [new file with mode: 0644]
screenhack/utils.h [new file with mode: 0644]
screenhack/version.h [new file with mode: 0644]
screenhack/visual.c [new file with mode: 0644]
screenhack/visual.h [new file with mode: 0644]
screenhack/vroot.h [new file with mode: 0644]
screenhack/xdbe.c [new file with mode: 0644]
screenhack/xdbe.h [new file with mode: 0644]
screenhack/xft.c [new file with mode: 0644]
screenhack/xft.h [new file with mode: 0644]
screenhack/ximage-loader.c [new file with mode: 0644]
screenhack/ximage-loader.h [new file with mode: 0644]
screenhack/xmu.c [new file with mode: 0644]
screenhack/xmu.h [new file with mode: 0644]
screenhack/xshm.c [new file with mode: 0644]
screenhack/xshm.h [new file with mode: 0644]
screenhack/yarandom.c [new file with mode: 0644]
screenhack/yarandom.h [new file with mode: 0644]

diff --git a/screenhack/aligned_malloc.c b/screenhack/aligned_malloc.c
new file mode 100644 (file)
index 0000000..b345ad1
--- /dev/null
@@ -0,0 +1,48 @@
+/* -*- mode: c; tab-width: 4; fill-column: 128 -*- */
+/* vi: set ts=4 tw=128: */
+
+/*
+aligned_malloc.c, Copyright (c) 2014 Dave Odell <dmo2118@gmail.com>
+
+Permission to use, copy, modify, distribute, and sell this software and its
+documentation for any purpose is hereby granted without fee, provided that
+the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation.  No representations are made about the suitability of this
+software for any purpose.  It is provided "as is" without express or
+implied warranty.
+*/
+
+#include "aligned_malloc.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+
+#include <assert.h>
+#include <errno.h>
+
+/* aligned_alloc() (C11) or posix_memalign() (POSIX) are other possibilities
+   for aligned_malloc().
+ */
+
+int aligned_malloc(void **ptr, unsigned alignment, size_t size)
+{
+       void *block_start;
+       ptrdiff_t align1 = alignment - 1;
+
+       assert(alignment && !(alignment & (alignment - 1))); /* alignment must be a power of two. */
+
+       size += sizeof(void *) + align1;
+       block_start = malloc(size);
+       if(!block_start)
+               return ENOMEM;
+       *ptr = (void *)(((ptrdiff_t)block_start + sizeof(void *) + align1) & ~align1);
+       ((void **)(*ptr))[-1] = block_start;
+       return 0;
+}
+
+void aligned_free(void *ptr)
+{
+       if(ptr)
+               free(((void **)(ptr))[-1]);
+}
diff --git a/screenhack/aligned_malloc.h b/screenhack/aligned_malloc.h
new file mode 100644 (file)
index 0000000..b3f43c9
--- /dev/null
@@ -0,0 +1,26 @@
+/* -*- mode: c; tab-width: 4; fill-column: 128 -*- */
+/* vi: set ts=4 tw=128: */
+
+/*
+aligned_malloc.h, Copyright (c) 2014 Dave Odell <dmo2118@gmail.com>
+
+Permission to use, copy, modify, distribute, and sell this software and its
+documentation for any purpose is hereby granted without fee, provided that
+the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation.  No representations are made about the suitability of this
+software for any purpose.  It is provided "as is" without express or
+implied warranty.
+*/
+
+#ifndef __ALIGNED_MALLOC_H__
+#define __ALIGNED_MALLOC_H__
+
+#include <stdlib.h>
+
+ /* This can't simply be named posix_memalign, since the real thing uses
+    free(), but this one can't. */
+ int aligned_malloc(void **ptr, unsigned alignment, size_t size);
+ void aligned_free(void *);
+
+#endif /* __ALIGNED_MALLOC_H__ */
diff --git a/screenhack/alpha.c b/screenhack/alpha.c
new file mode 100644 (file)
index 0000000..5b18e85
--- /dev/null
@@ -0,0 +1,215 @@
+/* xscreensaver, Copyright (c) 1992, 1995, 1996, 1997, 2006
+ *  Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/* Beauty is only skin deep, unless you've got an alpha channel. */
+
+
+#include "utils.h"
+#include "alpha.h"
+#include "visual.h"
+#include "hsv.h"
+#include "yarandom.h"
+#include "resources.h"
+
+#include <X11/Xutil.h>
+
+#ifndef countof
+# define countof(x) (sizeof(*(x))/sizeof((x)))
+#endif
+
+
+/* I don't believe this fucking language doesn't have builtin exponentiation.
+   I further can't believe that the fucking ^ character means fucking XOR!! */
+static int 
+i_exp (int i, int j)
+{
+  int k = 1;
+  while (j--) k *= i;
+  return k;
+}
+
+
+static void
+merge_colors (int argc, XColor **argv, XColor *into_color, int mask,
+             Bool additive_p)
+{
+  int j;
+  *into_color = *argv [0];
+  into_color->pixel |= mask;
+
+  for (j = 1; j < argc; j++)
+    {
+# define SHORT_INC(x,y) (x = ((((x)+(y)) > 0xFFFF) ? 0xFFFF : ((x)+(y))))
+# define SHORT_DEC(x,y) (x = ((((x)-(y)) < 0)      ? 0      : ((x)-(y))))
+      if (additive_p)
+       {
+         SHORT_INC (into_color->red,   argv[j]->red);
+         SHORT_INC (into_color->green, argv[j]->green);
+         SHORT_INC (into_color->blue,  argv[j]->blue);
+       }
+      else
+       {
+         SHORT_DEC (into_color->red,   argv[j]->red);
+         SHORT_DEC (into_color->green, argv[j]->green);
+         SHORT_DEC (into_color->blue,  argv[j]->blue);
+       }
+# undef SHORT_INC
+# undef SHORT_DEC
+    }
+}
+
+static void
+permute_colors (XColor *pcolors, XColor *colors,
+               int count,
+               unsigned long *plane_masks,
+               Bool additive_p)
+{
+  int out = 0;
+  int max = i_exp (2, count);
+  if (count > 31) abort ();
+  for (out = 1; out < max; out++)
+    {
+      XColor *argv [32];
+      int this_mask = 0;
+      int argc = 0;
+      int bit;
+      for (bit = 0; bit < 32; bit++)
+       if (out & (1<<bit))
+         {
+           argv [argc++] = &pcolors [bit];
+           this_mask |= plane_masks [bit];
+         }
+      merge_colors (argc, argv, &colors [out-1], this_mask, additive_p);
+    }
+}
+
+
+static int
+allocate_color_planes (Display *dpy, Colormap cmap,
+                      int nplanes, unsigned long *plane_masks,
+                      unsigned long *base_pixel_ret)
+{
+  while (nplanes > 1 &&
+        !XAllocColorCells (dpy, cmap, False, plane_masks, nplanes,
+                           base_pixel_ret, 1))
+    nplanes--;
+
+  return nplanes;
+}
+                      
+
+static void
+initialize_transparency_colormap (Display *dpy, Colormap cmap,
+                                 int nplanes,
+                                 unsigned long base_pixel,
+                                 unsigned long *plane_masks,
+                                 XColor *colors,
+                                 Bool additive_p)
+{
+  int i;
+  int total_colors = i_exp (2, nplanes);
+  XColor *all_colors = (XColor *) calloc (total_colors, sizeof (XColor));
+
+  for (i = 0; i < nplanes; i++)
+    colors[i].pixel = base_pixel | plane_masks [i];
+  permute_colors (colors, all_colors, nplanes, plane_masks, additive_p);
+
+  /* clone the default background of the window into our "base" pixel */
+  all_colors [total_colors - 1].pixel =
+    get_pixel_resource (dpy, cmap, "background", "Background");
+  XQueryColor (dpy, cmap, &all_colors [total_colors - 1]);
+  all_colors [total_colors - 1].pixel = base_pixel;
+
+  for (i = 0; i < total_colors; i++)
+    all_colors[i].flags = DoRed|DoGreen|DoBlue;
+  XStoreColors (dpy, cmap, all_colors, total_colors);
+  XFree ((XPointer) all_colors);
+}
+
+
+Bool
+allocate_alpha_colors (Screen *screen, Visual *visual, Colormap cmap,
+                      int *nplanesP, Bool additive_p,
+                      unsigned long **plane_masks,
+                      unsigned long *base_pixelP)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  XColor *colors;
+  int nplanes = *nplanesP;
+  int i;
+
+  if (!has_writable_cells (screen, visual))
+    cmap = 0;
+
+  if (!cmap)            /* A TrueColor visual, or similar. */
+    {
+      int depth = visual_depth (screen, visual);
+      unsigned long masks;
+      XVisualInfo vi_in, *vi_out;
+
+      /* Find out which bits the R, G, and B components actually occupy
+         on this visual. */
+      vi_in.screen = screen_number (screen);
+      vi_in.visualid = XVisualIDFromVisual (visual);
+      vi_out = XGetVisualInfo (dpy, VisualScreenMask|VisualIDMask,
+                               &vi_in, &i);
+      if (! vi_out) abort ();
+      masks = vi_out[0].red_mask | vi_out[0].green_mask | vi_out[0].blue_mask;
+      XFree ((char *) vi_out);
+
+      if (nplanes > depth)
+        nplanes = depth;
+      *nplanesP = nplanes;
+      *base_pixelP = 0;
+      *plane_masks = (unsigned long *) calloc(sizeof(unsigned long), nplanes);
+
+      /* Pick the planar values randomly, but constrain them to fall within
+         the bit positions of the R, G, and B fields. */
+      for (i = 0; i < nplanes; i++)
+        (*plane_masks)[i] = random() & masks;
+
+    }
+  else                  /* A PseudoColor visual, or similar. */
+    {
+      if (nplanes > 31) nplanes = 31;
+      *plane_masks = (unsigned long *) malloc(sizeof(unsigned long) * nplanes);
+
+      nplanes = allocate_color_planes (dpy, cmap, nplanes, *plane_masks,
+                                  base_pixelP);
+      *nplanesP = nplanes;
+
+      if (nplanes <= 1)
+        {
+          free(*plane_masks);
+          *plane_masks = 0;
+          return False;
+        }
+
+      colors = (XColor *) calloc (nplanes, sizeof (XColor));
+      for (i = 0; i < nplanes; i++)
+        {
+          /* pick the base colors. If we are in subtractive mode, pick higher
+             intensities. */
+          hsv_to_rgb (random () % 360,
+                      frand (1.0),
+                      frand (0.5) + (additive_p ? 0.2 : 0.5),
+                      &colors[i].red,
+                      &colors[i].green,
+                      &colors[i].blue);
+        }
+      initialize_transparency_colormap (dpy, cmap, nplanes,
+                                        *base_pixelP, *plane_masks, colors,
+                                        additive_p);
+      XFree ((XPointer) colors);
+    }
+  return True;
+}
diff --git a/screenhack/alpha.h b/screenhack/alpha.h
new file mode 100644 (file)
index 0000000..4ff9903
--- /dev/null
@@ -0,0 +1,22 @@
+/* xscreensaver, Copyright (c) 1992, 1995, 1996, 1997
+ *  Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifndef __XSCREENSAVER_ALPHA_H__
+#define __XSCREENSAVER_ALPHA_H__
+
+extern Bool allocate_alpha_colors (Screen *screen, Visual *visual,
+                                   Colormap cmap,
+                                   int *nplanesP, Bool additive_p,
+                                   unsigned long **plane_masks,
+                                   unsigned long *base_pixelP);
+
+#endif /* __XSCREENSAVER_ALPHA_H__ */
diff --git a/screenhack/colors.c b/screenhack/colors.c
new file mode 100644 (file)
index 0000000..01f8fc6
--- /dev/null
@@ -0,0 +1,747 @@
+/* xscreensaver, Copyright (c) 1997-2018 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/* This file contains some utility routines for randomly picking the colors
+   to hack the screen with.
+ */
+
+#include "utils.h"
+#include "hsv.h"
+#include "yarandom.h"
+#include "visual.h"
+#include "colors.h"
+
+extern char *progname;
+
+void
+free_colors (Screen *screen, Colormap cmap, XColor *colors, int ncolors)
+{
+  Display *dpy = screen ? DisplayOfScreen (screen) : 0;
+  int i;
+  if (ncolors > 0)
+    {
+      unsigned long *pixels = (unsigned long *)
+       malloc(sizeof(*pixels) * ncolors);
+      for (i = 0; i < ncolors; i++)
+       pixels[i] = colors[i].pixel;
+      XFreeColors (dpy, cmap, pixels, ncolors, 0L);
+      free(pixels);
+    }
+}
+
+
+void
+allocate_writable_colors (Screen *screen, Colormap cmap,
+                         unsigned long *pixels, int *ncolorsP)
+{
+  Display *dpy = screen ? DisplayOfScreen (screen) : 0;
+  int desired = *ncolorsP;
+  int got = 0;
+  int requested = desired;
+  unsigned long *new_pixels = pixels;
+
+  *ncolorsP = 0;
+  while (got < desired
+        && requested > 0)
+    {
+      if (desired - got < requested)
+       requested = desired - got;
+
+      if (XAllocColorCells (dpy, cmap, False, 0, 0, new_pixels, requested))
+       {
+         /* Got all the pixels we asked for. */
+         new_pixels += requested;
+         got += requested;
+       }
+      else
+       {
+         /* We didn't get all/any of the pixels we asked for.  This time, ask
+            for half as many.  (If we do get all that we ask for, we ask for
+            the same number again next time, so we only do O(log(n)) server
+            roundtrips.)
+         */
+         requested = requested / 2;
+       }
+    }
+  *ncolorsP += got;
+}
+
+
+static void
+complain (int wanted_colors, int got_colors,
+         Bool wanted_writable, Bool got_writable)
+{
+  if (got_colors > wanted_colors - 10)
+    /* don't bother complaining if we're within ten pixels. */
+    return;
+
+  if (wanted_writable && !got_writable)
+    fprintf (stderr,
+             "%s: wanted %d writable colors; got %d read-only colors.\n",
+             progname, wanted_colors, got_colors);
+  else
+    fprintf (stderr, "%s: wanted %d%s colors; got %d.\n",
+             progname, wanted_colors, (got_writable ? " writable" : ""),
+             got_colors);
+}
+
+
+
+void
+make_color_ramp (Screen *screen, Visual *visual, Colormap cmap,
+                int h1, double s1, double v1,   /* 0-360, 0-1.0, 0-1.0 */
+                int h2, double s2, double v2,   /* 0-360, 0-1.0, 0-1.0 */
+                XColor *colors, int *ncolorsP,
+                Bool closed_p,
+                Bool allocate_p,
+                Bool *writable_pP)
+{
+  Display *dpy = screen ? DisplayOfScreen (screen) : 0;
+  Bool verbose_p = True;  /* argh. */
+  int i;
+  int total_ncolors = *ncolorsP;
+  int ncolors, wanted;
+  Bool wanted_writable = (allocate_p && writable_pP && *writable_pP);
+  double dh, ds, dv;           /* deltas */
+
+  wanted = total_ncolors;
+  if (closed_p)
+    wanted = (wanted / 2) + 1;
+
+  /* If this visual doesn't support writable cells, don't bother trying.
+   */
+  if (wanted_writable && !has_writable_cells(screen, visual))
+    *writable_pP = False;
+
+ AGAIN:
+  ncolors = total_ncolors;
+
+  memset (colors, 0, (*ncolorsP) * sizeof(*colors));
+
+  if (closed_p)
+    ncolors = (ncolors / 2) + 1;
+
+  /* Note: unlike other routines in this module, this function assumes that
+     if h1 and h2 are more than 180 degrees apart, then the desired direction
+     is always from h1 to h2 (rather than the shorter path.)  make_uniform
+     depends on this.
+   */
+  dh = ((double)h2 - (double)h1) / ncolors;
+  ds = (s2 - s1) / ncolors;
+  dv = (v2 - v1) / ncolors;
+
+  for (i = 0; i < ncolors; i++)
+    {
+      colors[i].flags = DoRed|DoGreen|DoBlue;
+      hsv_to_rgb ((int) (h1 + (i*dh)), (s1 + (i*ds)), (v1 + (i*dv)),
+                 &colors[i].red, &colors[i].green, &colors[i].blue);
+    }
+
+  if (closed_p)
+    for (i = ncolors; i < *ncolorsP; i++)
+      colors[i] = colors[(*ncolorsP)-i];
+
+  if (!allocate_p)
+    return;
+
+  if (writable_pP && *writable_pP)
+    {
+      unsigned long *pixels = (unsigned long *)
+       malloc(sizeof(*pixels) * ((*ncolorsP) + 1));
+
+      /* allocate_writable_colors() won't do here, because we need exactly this
+        number of cells, or the color sequence we've chosen won't fit. */
+      if (! XAllocColorCells(dpy, cmap, False, 0, 0, pixels, *ncolorsP))
+       {
+         free(pixels);
+         goto FAIL;
+       }
+
+      for (i = 0; i < *ncolorsP; i++)
+       colors[i].pixel = pixels[i];
+      free (pixels);
+
+      XStoreColors (dpy, cmap, colors, *ncolorsP);
+    }
+  else
+    {
+      for (i = 0; i < *ncolorsP; i++)
+       {
+         XColor color;
+         color = colors[i];
+         if (XAllocColor (dpy, cmap, &color))
+           {
+             colors[i].pixel = color.pixel;
+           }
+         else
+           {
+             free_colors (screen, cmap, colors, i);
+             goto FAIL;
+           }
+       }
+    }
+
+  goto WARN;
+
+ FAIL:
+  /* we weren't able to allocate all the colors we wanted;
+     decrease the requested number and try again.
+   */
+  total_ncolors = (total_ncolors > 170 ? total_ncolors - 20 :
+                   total_ncolors > 100 ? total_ncolors - 10 :
+                   total_ncolors >  75 ? total_ncolors -  5 :
+                   total_ncolors >  25 ? total_ncolors -  3 :
+                   total_ncolors >  10 ? total_ncolors -  2 :
+                   total_ncolors >   2 ? total_ncolors -  1 :
+                   0);
+  *ncolorsP = total_ncolors;
+  ncolors = total_ncolors;
+  if (total_ncolors > 0)
+    goto AGAIN;
+
+ WARN:
+  
+  if (verbose_p &&
+      /* don't warn if we got 0 writable colors -- probably TrueColor. */
+      (ncolors != 0 || !wanted_writable))
+    complain (wanted, ncolors, wanted_writable, 
+              (wanted_writable && writable_pP && *writable_pP));
+}
+
+
+#define MAXPOINTS 50   /* yeah, so I'm lazy */
+
+
+static void
+make_color_path (Screen *screen, Visual *visual, Colormap cmap,
+                int npoints, int *h, double *s, double *v,
+                XColor *colors, int *ncolorsP,
+                Bool allocate_p,
+                Bool *writable_pP)
+{
+  Display *dpy = screen ? DisplayOfScreen (screen) : 0;
+  int i, j, k;
+  int total_ncolors = *ncolorsP;
+
+  int ncolors[MAXPOINTS];  /* number of pixels per edge */
+  double dh[MAXPOINTS];    /* distance between pixels, per edge (0 - 360.0) */
+  double ds[MAXPOINTS];    /* distance between pixels, per edge (0 - 1.0) */
+  double dv[MAXPOINTS];    /* distance between pixels, per edge (0 - 1.0) */
+
+  if (npoints == 0)
+    {
+      *ncolorsP = 0;
+      return;
+    }
+  else if (npoints == 2)       /* using make_color_ramp() will be faster */
+    {
+      make_color_ramp (screen, visual, cmap,
+                      h[0], s[0], v[0], h[1], s[1], v[1],
+                      colors, ncolorsP,
+                      True,  /* closed_p */
+                      allocate_p, writable_pP);
+      return;
+    }
+  else if (npoints >= MAXPOINTS)
+    {
+      npoints = MAXPOINTS-1;
+    }
+
+ AGAIN:
+
+  {
+    double DH[MAXPOINTS];      /* Distance between H values in the shortest
+                                  direction around the circle, that is, the
+                                  distance between 10 and 350 is 20.
+                                  (Range is 0 - 360.0.)
+                               */
+    double edge[MAXPOINTS];    /* lengths of edges in unit HSV space. */
+    double ratio[MAXPOINTS];   /* proportions of the edges (total 1.0) */
+    double circum = 0;
+    double one_point_oh = 0;   /* (debug) */
+
+    for (i = 0; i < npoints; i++)
+      {
+       int j = (i+1) % npoints;
+       double d = ((double) (h[i] - h[j])) / 360;
+       if (d < 0) d = -d;
+       if (d > 0.5) d = 0.5 - (d - 0.5);
+       DH[i] = d;
+      }
+
+    for (i = 0; i < npoints; i++)
+      {
+       int j = (i+1) % npoints;
+       edge[i] = sqrt((DH[i] * DH[j]) +
+                      ((s[j] - s[i]) * (s[j] - s[i])) +
+                      ((v[j] - v[i]) * (v[j] - v[i])));
+       circum += edge[i];
+      }
+
+#ifdef DEBUG
+    fprintf(stderr, "\ncolors:");
+    for (i=0; i < npoints; i++)
+      fprintf(stderr, " (%d, %.3f, %.3f)", h[i], s[i], v[i]);
+    fprintf(stderr, "\nlengths:");
+    for (i=0; i < npoints; i++)
+      fprintf(stderr, " %.3f", edge[i]);
+#endif /* DEBUG */
+
+    if (circum < 0.0001)
+      goto FAIL;
+
+    for (i = 0; i < npoints; i++)
+      {
+       ratio[i] = edge[i] / circum;
+       one_point_oh += ratio[i];
+      }
+
+#ifdef DEBUG
+    fprintf(stderr, "\nratios:");
+    for (i=0; i < npoints; i++)
+      fprintf(stderr, " %.3f", ratio[i]);
+#endif /* DEBUG */
+
+    if (one_point_oh < 0.99999 || one_point_oh > 1.00001)
+      abort();
+
+    /* space the colors evenly along the circumference -- that means that the
+       number of pixels on a edge is proportional to the length of that edge
+       (relative to the lengths of the other edges.)
+     */
+    for (i = 0; i < npoints; i++)
+      ncolors[i] = total_ncolors * ratio[i];
+
+
+#ifdef DEBUG
+    fprintf(stderr, "\npixels:");
+    for (i=0; i < npoints; i++)
+      fprintf(stderr, " %d", ncolors[i]);
+    fprintf(stderr, "  (%d)\n", total_ncolors);
+#endif /* DEBUG */
+
+    for (i = 0; i < npoints; i++)
+      {
+       int j = (i+1) % npoints;
+
+       if (ncolors[i] > 0)
+         {
+           dh[i] = 360 * (DH[i] / ncolors[i]);
+           ds[i] = (s[j] - s[i]) / ncolors[i];
+           dv[i] = (v[j] - v[i]) / ncolors[i];
+         }
+      }
+  }
+
+  memset (colors, 0, (*ncolorsP) * sizeof(*colors));
+
+  k = 0;
+  for (i = 0; i < npoints; i++)
+    {
+      int distance = h[(i+1) % npoints] - h[i];
+      int direction = (distance >= 0 ? -1 : 1);
+
+      if (distance <= 180 && distance >= -180)
+        direction = -direction;
+
+#ifdef DEBUG
+      fprintf (stderr, "point %d: %3d %.2f %.2f\n",
+              i, h[i], s[i], v[i]);
+      fprintf(stderr, "  h[i]=%d  dh[i]=%.2f  ncolors[i]=%d\n",
+             h[i], dh[i], ncolors[i]);
+#endif /* DEBUG */
+      for (j = 0; j < ncolors[i]; j++, k++)
+       {
+         double hh = (h[i] + (j * dh[i] * direction));
+         if (hh < 0) hh += 360;
+         else if (hh > 360) hh -= 0;
+         colors[k].flags = DoRed|DoGreen|DoBlue;
+         hsv_to_rgb ((int)
+                     hh,
+                     (s[i] + (j * ds[i])),
+                     (v[i] + (j * dv[i])),
+                     &colors[k].red, &colors[k].green, &colors[k].blue);
+#ifdef DEBUG
+         fprintf (stderr, "point %d+%d: %.2f %.2f %.2f  %04X %04X %04X\n",
+                  i, j,
+                  hh,
+                  (s[i] + (j * ds[i])),
+                  (v[i] + (j * dv[i])),
+                  colors[k].red, colors[k].green, colors[k].blue);
+#endif /* DEBUG */
+       }
+    }
+
+  /* Floating-point round-off can make us decide to use fewer colors. */
+  if (k < *ncolorsP)
+    {
+      /* We used to just return the smaller set of colors, but that meant
+         that after re-generating the color map repeatedly, the number of
+         colors in use would tend toward 0, which not only looked bad but
+         also often caused crashes. So instead, just duplicate the last
+         color to pad things out. */
+# if 0
+      *ncolorsP = k;
+      if (k <= 0)
+       return;
+# else
+      if (k <= 0)
+       return;
+      for (i = k; i < *ncolorsP; i++)
+        /* #### Should duplicate the allocation of the color cell here
+           to avoid a double-color-free on PseudoColor, but it's 2018
+           and I don't care, */
+        colors[i] = colors[i-1];
+# endif
+    }
+
+  if (!allocate_p)
+    return;
+
+  if (writable_pP && *writable_pP)
+    {
+      unsigned long *pixels = (unsigned long *)
+       malloc(sizeof(*pixels) * ((*ncolorsP) + 1));
+
+      /* allocate_writable_colors() won't do here, because we need exactly this
+        number of cells, or the color sequence we've chosen won't fit. */
+      if (! XAllocColorCells(dpy, cmap, False, 0, 0, pixels, *ncolorsP))
+       {
+         free(pixels);
+         goto FAIL;
+       }
+
+      for (i = 0; i < *ncolorsP; i++)
+       colors[i].pixel = pixels[i];
+      free (pixels);
+
+      XStoreColors (dpy, cmap, colors, *ncolorsP);
+    }
+  else
+    {
+      for (i = 0; i < *ncolorsP; i++)
+       {
+         XColor color;
+         color = colors[i];
+         if (XAllocColor (dpy, cmap, &color))
+           {
+             colors[i].pixel = color.pixel;
+           }
+         else
+           {
+             free_colors (screen, cmap, colors, i);
+             goto FAIL;
+           }
+       }
+    }
+
+  return;
+
+ FAIL:
+  /* we weren't able to allocate all the colors we wanted;
+     decrease the requested number and try again.
+   */
+  total_ncolors = (total_ncolors > 170 ? total_ncolors - 20 :
+                  total_ncolors > 100 ? total_ncolors - 10 :
+                  total_ncolors >  75 ? total_ncolors -  5 :
+                  total_ncolors >  25 ? total_ncolors -  3 :
+                  total_ncolors >  10 ? total_ncolors -  2 :
+                  total_ncolors >   2 ? total_ncolors -  1 :
+                  0);
+  *ncolorsP = total_ncolors;
+  if (total_ncolors > 0)
+    goto AGAIN;
+}
+
+
+void
+make_color_loop (Screen *screen, Visual *visual, Colormap cmap,
+                int h0, double s0, double v0,   /* 0-360, 0-1.0, 0-1.0 */
+                int h1, double s1, double v1,   /* 0-360, 0-1.0, 0-1.0 */
+                int h2, double s2, double v2,   /* 0-360, 0-1.0, 0-1.0 */
+                XColor *colors, int *ncolorsP,
+                Bool allocate_p,
+                Bool *writable_pP)
+{
+  Bool wanted_writable = (allocate_p && writable_pP && *writable_pP);
+
+  int h[3];
+  double s[3], v[3];
+  h[0] = h0; h[1] = h1; h[2] = h2;
+  s[0] = s0; s[1] = s1; s[2] = s2;
+  v[0] = v0; v[1] = v1; v[2] = v2;
+
+  /* If this visual doesn't support writable cells, don't bother trying.
+   */
+  if (wanted_writable && !has_writable_cells(screen, visual))
+    *writable_pP = False;
+
+  make_color_path (screen, visual, cmap,
+                   3, h, s, v,
+                   colors, ncolorsP,
+                   allocate_p, writable_pP);
+}
+
+
+void
+make_smooth_colormap (Screen *screen, Visual *visual, Colormap cmap,
+                     XColor *colors, int *ncolorsP,
+                     Bool allocate_p,
+                     Bool *writable_pP,
+                     Bool verbose_p)
+{
+  int npoints;
+  int ncolors = *ncolorsP;
+  Bool wanted_writable = (allocate_p && writable_pP && *writable_pP);
+  int i;
+  int h[MAXPOINTS];
+  double s[MAXPOINTS];
+  double v[MAXPOINTS];
+  double total_s = 0;
+  double total_v = 0;
+  int loop = 0;
+
+  if (*ncolorsP <= 0) return;
+
+  {
+    int n = random() % 20;
+    if      (n <= 5)  npoints = 2;     /* 30% of the time */
+    else if (n <= 15) npoints = 3;     /* 50% of the time */
+    else if (n <= 18) npoints = 4;     /* 15% of the time */
+    else             npoints = 5;      /*  5% of the time */
+  }
+
+ REPICK_ALL_COLORS:
+  for (i = 0; i < npoints; i++)
+    {
+    REPICK_THIS_COLOR:
+      if (++loop > 10000) abort();
+      h[i] = random() % 360;
+      s[i] = frand(1.0);
+      v[i] = frand(0.8) + 0.2;
+
+      /* Make sure that no two adjascent colors are *too* close together.
+        If they are, try again.
+       */
+      if (i > 0)
+       {
+         int j = (i+1 == npoints) ? 0 : (i-1);
+         double hi = ((double) h[i]) / 360;
+         double hj = ((double) h[j]) / 360;
+         double dh = hj - hi;
+         double distance;
+         if (dh < 0) dh = -dh;
+         if (dh > 0.5) dh = 0.5 - (dh - 0.5);
+         distance = sqrt ((dh * dh) +
+                          ((s[j] - s[i]) * (s[j] - s[i])) +
+                          ((v[j] - v[i]) * (v[j] - v[i])));
+         if (distance < 0.2)
+           goto REPICK_THIS_COLOR;
+       }
+      total_s += s[i];
+      total_v += v[i];
+    }
+
+  /* If the average saturation or intensity are too low, repick the colors,
+     so that we don't end up with a black-and-white or too-dark map.
+   */
+  if (total_s / npoints < 0.2)
+    goto REPICK_ALL_COLORS;
+  if (total_v / npoints < 0.3)
+    goto REPICK_ALL_COLORS;
+
+  /* If this visual doesn't support writable cells, don't bother trying.
+   */
+  if (wanted_writable && !has_writable_cells(screen, visual))
+    *writable_pP = False;
+
+ RETRY_NON_WRITABLE:
+  make_color_path (screen, visual, cmap, npoints, h, s, v, colors, &ncolors,
+                  allocate_p, writable_pP);
+
+  /* If we tried for writable cells and got none, try for non-writable. */
+  if (allocate_p && *ncolorsP == 0 && writable_pP && *writable_pP)
+    {
+      *writable_pP = False;
+      goto RETRY_NON_WRITABLE;
+    }
+
+  if (verbose_p)
+    complain(*ncolorsP, ncolors, wanted_writable,
+            wanted_writable && *writable_pP);
+
+  *ncolorsP = ncolors;
+}
+
+
+void
+make_uniform_colormap (Screen *screen, Visual *visual, Colormap cmap,
+                      XColor *colors, int *ncolorsP,
+                      Bool allocate_p,
+                      Bool *writable_pP,
+                      Bool verbose_p)
+{
+  int ncolors = *ncolorsP;
+  Bool wanted_writable = (allocate_p && writable_pP && *writable_pP);
+
+  double S = ((double) (random() % 34) + 66) / 100.0;  /* range 66%-100% */
+  double V = ((double) (random() % 34) + 66) / 100.0;  /* range 66%-100% */
+
+  if (*ncolorsP <= 0) return;
+
+  /* If this visual doesn't support writable cells, don't bother trying. */
+  if (wanted_writable && !has_writable_cells(screen, visual))
+    *writable_pP = False;
+
+ RETRY_NON_WRITABLE:
+  make_color_ramp(screen, visual, cmap,
+                 0,   S, V,
+                 359, S, V,
+                 colors, &ncolors,
+                 False, allocate_p, writable_pP);
+
+  /* If we tried for writable cells and got none, try for non-writable. */
+  if (allocate_p && *ncolorsP == 0 && writable_pP && *writable_pP)
+    {
+      ncolors = *ncolorsP;
+      *writable_pP = False;
+      goto RETRY_NON_WRITABLE;
+    }
+
+  if (verbose_p)
+    complain(*ncolorsP, ncolors, wanted_writable,
+            wanted_writable && *writable_pP);
+
+  *ncolorsP = ncolors;
+}
+
+
+void
+make_random_colormap (Screen *screen, Visual *visual, Colormap cmap,
+                     XColor *colors, int *ncolorsP,
+                     Bool bright_p,
+                     Bool allocate_p,
+                     Bool *writable_pP,
+                     Bool verbose_p)
+{
+  Display *dpy = screen ? DisplayOfScreen (screen) : 0;
+  Bool wanted_writable = (allocate_p && writable_pP && *writable_pP);
+  int ncolors = *ncolorsP;
+  int i;
+
+  if (*ncolorsP <= 0) return;
+
+  /* If this visual doesn't support writable cells, don't bother trying. */
+  if (wanted_writable && !has_writable_cells(screen, visual))
+    *writable_pP = False;
+
+ RETRY_ALL:
+  for (i = 0; i < ncolors; i++)
+    {
+      colors[i].flags = DoRed|DoGreen|DoBlue;
+      if (bright_p)
+       {
+         int H = random() % 360;                          /* range 0-360    */
+         double S = ((double) (random()%70) + 30)/100.0;  /* range 30%-100% */
+         double V = ((double) (random()%34) + 66)/100.0;  /* range 66%-100% */
+         hsv_to_rgb (H, S, V,
+                     &colors[i].red, &colors[i].green, &colors[i].blue);
+       }
+      else
+       {
+         colors[i].red   = random() % 0xFFFF;
+         colors[i].green = random() % 0xFFFF;
+         colors[i].blue  = random() % 0xFFFF;
+       }
+    }
+
+  /* If there are a small number of colors, make sure at least the first
+     two contrast well.
+  */
+  if (!bright_p && ncolors <= 4)
+    {
+      int h0, h1;
+      double s0, s1, v0, v1;
+      rgb_to_hsv (colors[0].red, colors[0].green, colors[0].blue, &h0,&s0,&v0);
+      rgb_to_hsv (colors[1].red, colors[1].green, colors[1].blue, &h1,&s1,&v1);
+      if (fabs (v1-v0) < 0.5)
+        goto RETRY_ALL;
+    }
+
+  if (!allocate_p)
+    return;
+
+ RETRY_NON_WRITABLE:
+  if (writable_pP && *writable_pP)
+    {
+      unsigned long *pixels = (unsigned long *)
+       malloc(sizeof(*pixels) * (ncolors + 1));
+
+      allocate_writable_colors (screen, cmap, pixels, &ncolors);
+      if (ncolors > 0)
+       for (i = 0; i < ncolors; i++)
+         colors[i].pixel = pixels[i];
+      free (pixels);
+      if (ncolors > 0)
+       XStoreColors (dpy, cmap, colors, ncolors);
+    }
+  else
+    {
+      for (i = 0; i < ncolors; i++)
+       {
+         XColor color;
+         color = colors[i];
+         if (!XAllocColor (dpy, cmap, &color))
+           break;
+         colors[i].pixel = color.pixel;
+       }
+      ncolors = i;
+    }
+
+  /* If we tried for writable cells and got none, try for non-writable. */
+  if (allocate_p && ncolors == 0 && writable_pP && *writable_pP)
+    {
+      ncolors = *ncolorsP;
+      *writable_pP = False;
+      goto RETRY_NON_WRITABLE;
+    }
+
+  if (verbose_p)
+    complain(*ncolorsP, ncolors, wanted_writable,
+            wanted_writable && *writable_pP);
+
+  *ncolorsP = ncolors;
+}
+
+
+void
+rotate_colors (Screen *screen, Colormap cmap,
+              XColor *colors, int ncolors, int distance)
+{
+  Display *dpy = screen ? DisplayOfScreen (screen) : 0;
+  int i;
+  XColor *colors2;
+  if (ncolors < 2) return;
+  colors2 = (XColor *) malloc(sizeof(*colors2) * ncolors);
+  distance = distance % ncolors;
+  for (i = 0; i < ncolors; i++)
+    {
+      int j = i - distance;
+      if (j >= ncolors) j -= ncolors;
+      if (j < 0) j += ncolors;
+      colors2[i] = colors[j];
+      colors2[i].pixel = colors[i].pixel;
+    }
+  XStoreColors (dpy, cmap, colors2, ncolors);
+  XFlush(dpy);
+  memcpy(colors, colors2, sizeof(*colors) * ncolors);
+  free(colors2);
+}
diff --git a/screenhack/colors.h b/screenhack/colors.h
new file mode 100644 (file)
index 0000000..526a38a
--- /dev/null
@@ -0,0 +1,147 @@
+/* xscreensaver, Copyright (c) 1992-2013 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifndef __COLORS_H__
+#define __COLORS_H__
+
+/* Like XFreeColors, but works on `XColor *' instead of `unsigned long *'
+ */
+extern void free_colors (Screen *, Colormap, XColor *, int ncolors);
+
+
+/* Allocates writable, non-contiguous color cells.  The number requested is
+   passed in *ncolorsP, and the number actually allocated is returned there.
+   (Unlike XAllocColorCells(), this will allocate as many as it can, instead
+   of failing if they can't all be allocated.)
+ */
+extern void allocate_writable_colors (Screen *, Colormap,
+                                     unsigned long *pixels, int *ncolorsP);
+
+
+/* Generates a sequence of colors evenly spaced between the given pair
+   of HSV coordinates.
+
+   If closed_p is true, the colors will go from the first point to the
+   second then back to the first.
+
+   If allocate_p is true, the colors will be allocated from the map;
+   if enough colors can't be allocated, we will try for less, and the
+   result will be returned to ncolorsP.
+
+   If writable_p is true, writable color cells will be allocated;
+   otherwise, read-only cells will be allocated.
+
+   If allocate_p is false, screen and cmap are unused (OpenGL usage).
+ */
+extern void make_color_ramp (Screen *, Visual *, Colormap,
+                            int h1, double s1, double v1,
+                            int h2, double s2, double v2,
+                            XColor *colors, int *ncolorsP,
+                            Bool closed_p,
+                            Bool allocate_p,
+                            Bool *writable_pP);
+
+/* Generates a sequence of colors evenly spaced around the triangle
+   indicated by the thee HSV coordinates.
+
+   If allocate_p is true, the colors will be allocated from the map;
+   if enough colors can't be allocated, we will try for less, and the
+   result will be returned to ncolorsP.
+
+   If writable_p is true, writable color cells will be allocated;
+   otherwise, read-only cells will be allocated.
+
+   If allocate_p is false, screen, visual and cmap are unused (OpenGL usage).
+ */
+extern void make_color_loop (Screen *, Visual *, Colormap,
+                            int h1, double s1, double v1,
+                            int h2, double s2, double v2,
+                            int h3, double s3, double v3,
+                            XColor *colors, int *ncolorsP,
+                            Bool allocate_p,
+                            Bool *writable_pP);
+
+
+/* Allocates a hopefully-interesting colormap, which will be a closed loop
+   without any sudden transitions.
+
+   If allocate_p is true, the colors will be allocated from the map;
+   if enough colors can't be allocated, we will try for less, and the
+   result will be returned to ncolorsP.  An error message will be
+   printed on stderr (if verbose_p).
+
+   If *writable_pP is true, writable color cells will be allocated;
+   otherwise, read-only cells will be allocated.  If no writable cells
+   cannot be allocated, we will try to allocate unwritable cells
+   instead, and print a message on stderr to that effect (if verbose_p).
+
+   If allocate_p is false, screen, visual and cmap are unused (OpenGL usage).
+ */
+extern void make_smooth_colormap (Screen *, Visual *, Colormap,
+                                 XColor *colors, int *ncolorsP,
+                                 Bool allocate_p,
+                                 Bool *writable_pP,
+                                 Bool verbose_p);
+
+/* Allocates a uniform colormap which touches each hue of the spectrum,
+   evenly spaced.  The saturation and intensity are chosen randomly, but
+   will be high enough to be visible.
+
+   If allocate_p is true, the colors will be allocated from the map;
+   if enough colors can't be allocated, we will try for less, and the
+   result will be returned to ncolorsP.  An error message will be
+   printed on stderr (if verbose_p).
+
+   If *writable_pP is true, writable color cells will be allocated;
+   otherwise, read-only cells will be allocated.  If no writable cells
+   cannot be allocated, we will try to allocate unwritable cells
+   instead, and print a message on stderr to that effect (if verbose_p).
+
+   If allocate_p is false, screen, visual and cmap are unused (OpenGL usage).
+ */
+extern void make_uniform_colormap (Screen *, Visual *, Colormap,
+                                  XColor *colors, int *ncolorsP,
+                                  Bool allocate_p,
+                                  Bool *writable_pP,
+                                  Bool verbose_p);
+
+/* Allocates a random colormap (the colors are unrelated to one another.)
+   If `bright_p' is false, the colors will be completely random; if it is
+   true, all of the colors will be bright enough to see on a black background.
+
+   If allocate_p is true, the colors will be allocated from the map;
+   if enough colors can't be allocated, we will try for less, and the
+   result will be returned to ncolorsP.  An error message will be
+   printed on stderr (if verbose_p).
+
+   If *writable_pP is true, writable color cells will be allocated;
+   otherwise, read-only cells will be allocated.  If no writable cells
+   cannot be allocated, we will try to allocate unwritable cells
+   instead, and print a message on stderr to that effect (if verbose_p).
+
+   If allocate_p is false, screen, visual and cmap are unused (OpenGL usage).
+ */
+extern void make_random_colormap (Screen *, Visual *, Colormap,
+                                 XColor *colors, int *ncolorsP,
+                                 Bool bright_p,
+                                 Bool allocate_p,
+                                 Bool *writable_pP,
+                                 Bool verbose_p);
+
+
+/* Assuming that the array of colors indicates the current state of a set
+   of writable color cells, this rotates the contents of the array by
+   `distance' steps, moving the colors of cell N to cell (N - distance).
+ */
+extern void rotate_colors (Screen *, Colormap,
+                          XColor *, int ncolors, int distance);
+
+#endif /* __COLORS_H__ */
diff --git a/screenhack/erase.c b/screenhack/erase.c
new file mode 100644 (file)
index 0000000..7e1cbc9
--- /dev/null
@@ -0,0 +1,811 @@
+/* erase.c: Erase the screen in various more or less interesting ways.
+ * Copyright (c) 1997-2008 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ *
+ * Portions (c) 1997 by Johannes Keukelaar <johannes@nada.kth.se>:
+ *   Permission to use in any way granted. Provided "as is" without expressed
+ *   or implied warranty. NO WARRANTY, NO EXPRESSION OF SUITABILITY FOR ANY
+ *   PURPOSE. (I.e.: Use in any way, but at your own risk!)
+ */
+
+#include "utils.h"
+#include "yarandom.h"
+#include "usleep.h"
+#include "resources.h"
+#include "erase.h"
+#include <sys/time.h> /* for gettimeofday() */
+
+extern char *progname;
+
+#undef countof
+#define countof(x) (sizeof(x)/sizeof(*(x)))
+
+typedef void (*Eraser) (eraser_state *);
+
+struct eraser_state {
+  Display *dpy;
+  Window window;
+  GC fg_gc, bg_gc;
+  int width, height;
+  Eraser fn;
+
+  double start_time, stop_time;
+  double ratio, prev_ratio;
+
+  /* data for random_lines, venetian, random_squares */
+  Bool horiz_p;
+  Bool flip_p;
+  int nlines, *lines;
+
+  /* data for triple_wipe, quad_wipe */
+  Bool flip_x, flip_y;
+
+  /* data for circle_wipe, three_circle_wipe */
+  int start;
+
+  /* data for random_squares */
+  int cols;
+
+  /* data for fizzle */
+  unsigned short *fizzle_rnd;
+
+};
+
+
+static double
+double_time (void)
+{
+  struct timeval now;
+# ifdef GETTIMEOFDAY_TWO_ARGS
+  struct timezone tzp;
+  gettimeofday(&now, &tzp);
+# else
+  gettimeofday(&now);
+# endif
+
+  return (now.tv_sec + ((double) now.tv_usec * 0.000001));
+}
+
+
+static void
+random_lines (eraser_state *st)
+{
+  int i;
+
+  if (! st->lines)     /* first time */
+    {
+      st->horiz_p = (random() & 1);
+      st->nlines = (st->horiz_p ? st->height : st->width);
+      st->lines = (int *) calloc (st->nlines, sizeof(*st->lines));
+
+      for (i = 0; i < st->nlines; i++)  /* every line */
+        st->lines[i] = i;
+
+      for (i = 0; i < st->nlines; i++)  /* shuffle */
+        {
+          int t, r;
+          t = st->lines[i];
+          r = random() % st->nlines;
+          st->lines[i] = st->lines[r];
+          st->lines[r] = t;
+        }
+    }
+
+  for (i = st->nlines * st->prev_ratio;
+       i < st->nlines * st->ratio;
+       i++)
+    {
+      if (st->horiz_p)
+        XDrawLine (st->dpy, st->window, st->bg_gc, 
+                   0, st->lines[i], st->width, st->lines[i]);
+      else
+        XDrawLine (st->dpy, st->window, st->bg_gc, 
+                   st->lines[i], 0, st->lines[i], st->height);
+    }
+
+  if (st->ratio >= 1.0)
+    {
+      free (st->lines);
+      st->lines = 0;
+    }
+}
+
+
+static void
+venetian (eraser_state *st)
+{
+  int i;
+  if (st->ratio == 0.0)
+    {
+      int j = 0;
+      st->horiz_p = (random() & 1);
+      st->flip_p = (random() & 1);
+      st->nlines = (st->horiz_p ? st->height : st->width);
+      st->lines = (int *) calloc (st->nlines, sizeof(*st->lines));
+
+      for (i = 0; i < st->nlines * 2; i++)
+        {
+          int line = ((i / 16) * 16) - ((i % 16) * 15);
+          if (line >= 0 && line < st->nlines)
+            st->lines[j++] = (st->flip_p ? st->nlines - line : line);
+        }
+    }
+
+  
+  for (i = st->nlines * st->prev_ratio;
+       i < st->nlines * st->ratio;
+       i++)
+    {
+      if (st->horiz_p)
+        XDrawLine (st->dpy, st->window, st->bg_gc, 
+                   0, st->lines[i], st->width, st->lines[i]);
+      else
+        XDrawLine (st->dpy, st->window, st->bg_gc, 
+                   st->lines[i], 0, st->lines[i], st->height);
+    }
+
+  if (st->ratio >= 1.0)
+    {
+      free (st->lines);
+      st->lines = 0;
+    }
+}
+
+
+static void
+triple_wipe (eraser_state *st)
+{
+  int i;
+  if (st->ratio == 0.0)
+    {
+      st->flip_x = random() & 1;
+      st->flip_y = random() & 1;
+      st->nlines = st->width + (st->height / 2);
+      st->lines = (int *) calloc (st->nlines, sizeof(*st->lines));
+
+      for (i = 0; i < st->width / 2; i++)
+        st->lines[i] = i * 2 + st->height;
+      for (i = 0; i < st->height / 2; i++)
+        st->lines[i + st->width / 2] = i*2;
+      for (i = 0; i < st->width / 2; i++)
+        st->lines[i + st->width / 2 + st->height / 2] = 
+          st->width - i * 2 - (st->width % 2 ? 0 : 1) + st->height;
+    }
+
+  for (i = st->nlines * st->prev_ratio;
+       i < st->nlines * st->ratio;
+       i++)
+    {
+      int x, y, x2, y2;
+
+      if (st->lines[i] < st->height)
+        x = 0, y = st->lines[i], x2 = st->width, y2 = y;
+      else
+        x = st->lines[i]-st->height, y = 0, x2 = x, y2 = st->height;
+
+      if (st->flip_x)
+        x = st->width - x, x2 = st->width - x2;
+      if (st->flip_y)
+        y = st->height - y, y2 = st->height - y2;
+
+      XDrawLine (st->dpy, st->window, st->bg_gc, x, y, x2, y2);
+    }
+
+  if (st->ratio >= 1.0)
+    {
+      free (st->lines);
+      st->lines = 0;
+    }
+}
+
+
+static void
+quad_wipe (eraser_state *st)
+{
+  int i;
+  if (st->ratio == 0.0)
+    {
+      st->flip_x = random() & 1;
+      st->flip_y = random() & 1;
+      st->nlines = st->width + st->height;
+      st->lines = (int *) calloc (st->nlines, sizeof(*st->lines));
+
+      for (i = 0; i < st->nlines/4; i++)
+        {
+          st->lines[i*4]   = i*2;
+          st->lines[i*4+1] = st->height - i*2 - (st->height % 2 ? 0 : 1);
+          st->lines[i*4+2] = st->height + i*2;
+          st->lines[i*4+3] = st->height + st->width - i*2
+            - (st->width % 2 ? 0 : 1);
+        }
+    }
+
+  for (i = st->nlines * st->prev_ratio;
+       i < st->nlines * st->ratio;
+       i++)
+    {
+      int x, y, x2, y2;
+      if (st->lines[i] < st->height)
+        x = 0, y = st->lines[i], x2 = st->width, y2 = y;
+      else
+        x = st->lines[i] - st->height, y = 0, x2 = x, y2 = st->height;
+
+      if (st->flip_x)
+        x = st->width-x, x2 = st->width-x2;
+      if (st->flip_y)
+        y = st->height-y, y2 = st->height-y2;
+
+      XDrawLine (st->dpy, st->window, st->bg_gc, x, y, x2, y2);
+    }
+
+  if (st->ratio >= 1.0)
+    {
+      free (st->lines);
+      st->lines = 0;
+    }
+}
+
+
+static void
+circle_wipe (eraser_state *st)
+{
+  int rad = (st->width > st->height ? st->width : st->height);
+  int max = 360 * 64;
+  int th, oth;
+
+  if (st->ratio == 0.0)
+    {
+      st->flip_p = random() & 1;
+      st->start = random() % max;
+    }
+
+  th  = max * st->ratio;
+  oth = max * st->prev_ratio;
+  if (st->flip_p)
+    {
+      th  = max - th;
+      oth = max - oth;
+    }
+  XFillArc (st->dpy, st->window, st->bg_gc,
+            (st->width  / 2) - rad,
+            (st->height / 2) - rad, 
+            rad*2, rad*2,
+            (st->start + oth) % max,
+            th-oth);
+}
+
+
+static void
+three_circle_wipe (eraser_state *st)
+{
+  int rad = (st->width > st->height ? st->width : st->height);
+  int max = 360 * 64;
+  int th, oth;
+  int i;
+
+  if (st->ratio == 0.0)
+    st->start = random() % max;
+
+  th  = max/6 * st->ratio;
+  oth = max/6 * st->prev_ratio;
+
+  for (i = 0; i < 3; i++)
+    {
+      int off = i * max / 3;
+      XFillArc (st->dpy, st->window, st->bg_gc,
+                (st->width  / 2) - rad,
+                (st->height / 2) - rad, 
+                rad*2, rad*2,
+                (st->start + off + oth) % max,
+                th-oth);
+      XFillArc (st->dpy, st->window, st->bg_gc,
+                (st->width  / 2) - rad,
+                (st->height / 2) - rad, 
+                rad*2, rad*2,
+                (st->start + off - oth) % max,
+                oth-th);
+    }
+}
+
+
+static void
+squaretate (eraser_state *st)
+{
+  int max = ((st->width > st->height ? st->width : st->height) * 2);
+  XPoint points [3];
+  int i = max * st->ratio;
+
+  if (st->ratio == 0.0)
+    st->flip_p = random() & 1;
+    
+# define DRAW()                                                \
+  if (st->flip_p) {                                    \
+    points[0].x = st->width - points[0].x;             \
+    points[1].x = st->width - points[1].x;             \
+    points[2].x = st->width - points[2].x;             \
+  }                                                    \
+  XFillPolygon (st->dpy, st->window, st->bg_gc,                \
+               points, 3, Convex, CoordModeOrigin)
+
+  points[0].x = 0;
+  points[0].y = 0;
+  points[1].x = st->width;
+  points[1].y = 0;
+  points[2].x = 0;
+  points[2].y = points[0].y + ((i * st->height) / max);
+  DRAW();
+
+  points[0].x = 0;
+  points[0].y = 0;
+  points[1].x = 0;
+  points[1].y = st->height;
+  points[2].x = ((i * st->width) / max);
+  points[2].y = st->height;
+  DRAW();
+
+  points[0].x = st->width;
+  points[0].y = st->height;
+  points[1].x = 0;
+  points[1].y = st->height;
+  points[2].x = st->width;
+  points[2].y = st->height - ((i * st->height) / max);
+  DRAW();
+
+  points[0].x = st->width;
+  points[0].y = st->height;
+  points[1].x = st->width;
+  points[1].y = 0;
+  points[2].x = st->width - ((i * st->width) / max);
+  points[2].y = 0;
+  DRAW();
+# undef DRAW
+}
+
+
+static void
+fizzle (eraser_state *st)
+{
+  const double overshoot = 1.0625;
+
+  unsigned int x, y, i;
+  const unsigned int size = 256;
+  unsigned short *rnd;
+  XPoint *points;
+  unsigned int npoints =
+    (unsigned int)(size * size * st->ratio * overshoot) -
+    (unsigned int)(size * size * st->prev_ratio * overshoot);
+
+  if (st->ratio >= 1.0)
+    {
+      free (st->fizzle_rnd);
+      st->fizzle_rnd = NULL;
+      return;
+    }
+
+  if (! st->fizzle_rnd)
+    {
+      unsigned int chunks =
+        ((st->width + size - 1) / size) * ((st->height + size - 1) / size);
+      unsigned int i;
+
+      st->fizzle_rnd =
+        (unsigned short *) calloc (sizeof(unsigned short), chunks);
+
+      if (! st->fizzle_rnd)
+        return;
+
+      for (i = 0; i != chunks; i++)
+        st->fizzle_rnd[i] = NRAND(0x10000) | 1; /* Seed can't be 0. */
+    }
+
+  points = (XPoint *) malloc ((npoints + 1) * sizeof(*points));
+  if (! points) return;
+
+  rnd = st->fizzle_rnd;
+
+  for (y = 0; y < st->height; y += 256)
+    {
+      for (x = 0; x < st->width; x += 256)
+        {
+          unsigned int need0 = 0;
+          unsigned short r = *rnd;
+          for (i = 0; i != npoints; i++)
+            {
+              points[i].x = r % size + x;
+              points[i].y = (r >> 8) % size + y;
+
+              /* Xorshift. This has a period of 2^16, which exactly matches
+                 the number of pixels in each 256x256 chunk.
+
+                 Other shift constants are possible, but it's hard to say
+                 which constants are best: a 2^16 period PRNG will never score
+                 well on BigCrush.
+               */
+              r = (r ^ (r <<  3)) & 0xffff;
+              r =  r ^ (r >>  5);
+              r = (r ^ (r << 11)) & 0xffff;
+              need0 |= (r == 0x8080); /* Can be anything, really. */
+            }
+
+          if (need0)
+            {
+              points[npoints].x = x;
+              points[npoints].y = y;
+            }
+
+          XDrawPoints (st->dpy, st->window, st->bg_gc,
+                       points, npoints + need0, CoordModeOrigin);
+          *rnd = r;
+          rnd++;
+        }
+    }
+  free (points);
+}
+
+
+static void
+spiral (eraser_state *st)
+{
+  int max_radius = (st->width > st->height ? st->width : st->height) * 0.7;
+  int loops = 10;
+  float max_th = M_PI * 2 * loops;
+  int i;
+  int steps = 360 * loops / 4;
+  float off;
+
+  if (st->ratio == 0.0)
+    {
+      st->flip_p = random() & 1;
+      st->start = random() % 360;
+    }
+
+  off = st->start * M_PI / 180;
+
+  for (i = steps * st->prev_ratio;
+       i < steps * st->ratio;
+       i++)
+    {
+      float th1 = i     * max_th / steps;
+      float th2 = (i+1) * max_th / steps;
+      int   r1  = i     * max_radius / steps;
+      int   r2  = (i+1) * max_radius / steps;
+      XPoint points[3];
+
+      if (st->flip_p)
+        {
+          th1 = max_th - th1;
+          th2 = max_th - th2;
+        }
+
+      points[0].x = st->width  / 2;
+      points[0].y = st->height / 2;
+      points[1].x = points[0].x + r1 * cos (off + th1);
+      points[1].y = points[0].y + r1 * sin (off + th1);
+      points[2].x = points[0].x + r2 * cos (off + th2);
+      points[2].y = points[0].y + r2 * sin (off + th2);
+/*  XFillRectangle(st->dpy, st->window, st->fg_gc,0,0,st->width, st->height);*/
+      XFillPolygon (st->dpy, st->window, st->bg_gc,
+                    points, 3, Convex, CoordModeOrigin);
+    }
+}
+
+
+static void
+random_squares (eraser_state *st)
+{
+  int i, size, rows;
+
+  if (st->ratio == 0.0)
+    {
+      st->cols = 10 + random() % 30;
+      size = st->width / st->cols;
+      rows = (size ? (st->height / size) : 0) + 1;
+      st->nlines = st->cols * rows;
+      st->lines = (int *) calloc (st->nlines, sizeof(*st->lines));
+
+      for (i = 0; i < st->nlines; i++)  /* every square */
+        st->lines[i] = i;
+
+      for (i = 0; i < st->nlines; i++)  /* shuffle */
+        {
+          int t, r;
+          t = st->lines[i];
+          r = random() % st->nlines;
+          st->lines[i] = st->lines[r];
+          st->lines[r] = t;
+        }
+    }
+
+  size = st->width / st->cols;
+  rows = (size ? (st->height / size) : 0) + 1;
+
+  for (i = st->nlines * st->prev_ratio;
+       i < st->nlines * st->ratio;
+       i++)
+    {
+      int x = st->lines[i] % st->cols;
+      int y = st->lines[i] / st->cols;
+      XFillRectangle (st->dpy, st->window, st->bg_gc,
+                      st->width  * x / st->cols,
+                      st->height * y / rows,
+                      size+1, size+1);
+    }
+
+  if (st->ratio >= 1.0)
+    {
+      free (st->lines);
+      st->lines = 0;
+    }
+}
+
+
+/* I first saw something like this, albeit in reverse, in an early Tetris
+   implementation for the Mac.
+    -- Torbjörn Andersson <torbjorn@dev.eurotime.se>
+ */
+static void
+slide_lines (eraser_state *st)
+{
+  int max = st->width * 1.1;
+  int nlines = 40;
+  int h = st->height / nlines;
+  int y, step;
+  int tick = 0;
+
+  if (h < 10)
+    h = 10;
+
+  step = (max * st->ratio) - (max * st->prev_ratio);
+  if (step <= 0)
+    step = 1;
+
+  for (y = 0; y < st->height; y += h)
+    {
+      if (st->width <= step)
+        ;
+      else if (tick & 1)
+        {
+          XCopyArea (st->dpy, st->window, st->window, st->fg_gc,
+                     0, y, st->width-step, h, step, y);
+          XFillRectangle (st->dpy, st->window, st->bg_gc, 
+                          0, y, step, h);
+        }
+      else
+        {
+          XCopyArea (st->dpy, st->window, st->window, st->fg_gc,
+                     step, y, st->width-step, h, 0, y);
+          XFillRectangle (st->dpy, st->window, st->bg_gc, 
+                          st->width-step, y, step, h);
+        }
+
+      tick++;
+    }
+}
+
+
+/* from Frederick Roeber <roeber@xigo.com> */
+static void
+losira (eraser_state *st)
+{
+  double mode1 = 0.55;
+  double mode2 = mode1 + 0.30;
+  double mode3 = 1.0;
+  int radius = 10;
+
+  if (st->ratio < mode1)               /* squeeze from the sides */
+    {
+      double ratio = st->ratio / mode1;
+      double prev_ratio = st->prev_ratio / mode1;
+      int max = st->width / 2;
+      int step = (max * ratio) - (max * prev_ratio);
+
+      if (step <= 0)
+        step = 1;
+
+      /* pull from left */
+      XCopyArea (st->dpy, st->window, st->window, st->fg_gc,
+                 0, 0, max - step, st->height, step, 0);
+      XFillRectangle (st->dpy, st->window, st->bg_gc, 
+                      0, 0, max * ratio, st->height);
+
+      /* pull from right */
+      XCopyArea (st->dpy, st->window, st->window, st->fg_gc,
+                 max+step, 0, max - step, st->height, max, 0);
+      XFillRectangle (st->dpy, st->window, st->bg_gc, 
+                      max + max*(1-ratio), 0, max, st->height);
+
+      /* expand white from center */
+      XFillRectangle (st->dpy, st->window, st->fg_gc,
+                      max - (radius * ratio), 0,
+                      radius * ratio * 2, st->height);
+    }
+  else if (st->ratio < mode2)          /* squeeze from the top/bottom */
+    {
+      double ratio = (st->ratio - mode1) / (mode2 - mode1);
+      int max = st->height / 2;
+
+      /* fill middle */
+      XFillRectangle (st->dpy, st->window, st->fg_gc,
+                      st->width/2 - radius,
+                      max * ratio,
+                      radius*2, st->height * (1 - ratio));
+
+      /* fill left and right */
+      XFillRectangle (st->dpy, st->window, st->bg_gc, 
+                      0, 0, st->width/2 - radius, st->height);
+      XFillRectangle (st->dpy, st->window, st->bg_gc, 
+                      st->width/2 + radius, 0, st->width/2, st->height);
+
+      /* fill top and bottom */
+      XFillRectangle (st->dpy, st->window, st->bg_gc,
+                      0, 0, st->width, max * ratio);
+      XFillRectangle (st->dpy, st->window, st->bg_gc,
+                      0, st->height - (max * ratio),
+                      st->width, max);
+
+      /* cap top */
+      XFillArc (st->dpy, st->window, st->fg_gc,
+                st->width/2 - radius,
+                max * ratio - radius,
+                radius*2, radius*2,
+                0, 180*64);
+
+      /* cap bottom */
+      XFillArc (st->dpy, st->window, st->fg_gc,
+                st->width/2 - radius,
+                st->height - (max * ratio + radius),
+                radius*2, radius*2,
+                180*64, 180*64);
+    }
+  else if (st->ratio < mode3)          /* starburst */
+    {
+      double ratio = (st->ratio - mode2) / (mode3 - mode2);
+      double r2 = ratio * radius * 4;
+      XArc arc[9];
+      int i;
+      int angle = 360*64/countof(arc);
+
+      for (i = 0; i < countof(arc); i++)
+        {
+          double th;
+          arc[i].angle1 = angle * i;
+          arc[i].angle2 = angle;
+          arc[i].width  = radius*2 * (1 + ratio);
+          arc[i].height = radius*2 * (1 + ratio);
+          arc[i].x = st->width  / 2 - radius;
+          arc[i].y = st->height / 2 - radius;
+
+          th = ((arc[i].angle1 + (arc[i].angle2 / 2)) / 64.0 / 180 * M_PI);
+
+          arc[i].x += r2 * cos (th);
+          arc[i].y -= r2 * sin (th);
+        }
+
+      XFillRectangle (st->dpy, st->window, st->bg_gc, 
+                      0, 0, st->width, st->height);
+      XFillArcs (st->dpy, st->window, st->fg_gc, arc, countof(arc));
+    }
+}
+
+
+static Eraser erasers[] = {
+  random_lines,
+  venetian,
+  triple_wipe,
+  quad_wipe,
+  circle_wipe,
+  three_circle_wipe,
+  squaretate,
+  fizzle,
+  spiral,
+  random_squares,
+  slide_lines,
+  losira,
+};
+
+
+static eraser_state *
+eraser_init (Display *dpy, Window window)
+{
+  eraser_state *st = (eraser_state *) calloc (1, sizeof(*st));
+  XWindowAttributes xgwa;
+  XGCValues gcv;
+  unsigned long fg, bg;
+  double duration;
+  int which;
+  char *s;
+
+  st->dpy = dpy;
+  st->window = window;
+
+  XGetWindowAttributes (dpy, window, &xgwa);
+  st->width = xgwa.width;
+  st->height = xgwa.height;
+
+  bg = get_pixel_resource (dpy, xgwa.colormap, "background", "Background");
+  fg = get_pixel_resource (dpy, xgwa.colormap, "foreground", "Foreground");
+
+  gcv.foreground = fg;
+  gcv.background = bg;
+  st->fg_gc = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);
+  gcv.foreground = bg;
+  gcv.background = fg;
+  st->bg_gc = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);
+
+# ifdef HAVE_COCOA
+  /* Pretty much all of these leave turds if AA is on. */
+  jwxyz_XSetAntiAliasing (st->dpy, st->fg_gc, False);
+  jwxyz_XSetAntiAliasing (st->dpy, st->bg_gc, False);
+# endif
+
+  s = get_string_resource (dpy, "eraseMode", "Integer");
+  if (!s || !*s)
+    which = -1;
+  else
+    which = get_integer_resource(dpy, "eraseMode", "Integer");
+  if (s) free (s);
+
+  if (which < 0 || which >= countof(erasers))
+    which = random() % countof(erasers);
+  st->fn = erasers[which];
+
+  duration = get_float_resource (dpy, "eraseSeconds", "Float");
+  if (duration < 0.1 || duration > 10)
+    duration = 1;
+
+  st->start_time = double_time();
+  st->stop_time = st->start_time + duration;
+
+  XSync (st->dpy, False);
+
+  return st;
+}
+
+
+void
+eraser_free (eraser_state *st)
+{
+  XClearWindow (st->dpy, st->window); /* Final draw is black-on-black. */
+  st->ratio = 1.0;
+  st->fn (st); /* Free any memory. May also draw, but that doesn't matter. */
+  XFreeGC (st->dpy, st->fg_gc);
+  XFreeGC (st->dpy, st->bg_gc);
+  free (st);
+}
+
+eraser_state *
+erase_window (Display *dpy, Window window, eraser_state *st)
+{
+  double now, duration;
+  Bool first_p = False;
+  if (! st)
+    {
+      first_p = True;
+      st = eraser_init (dpy, window);
+    }
+
+  now = (first_p ? st->start_time : double_time());
+  duration = st->stop_time - st->start_time;
+
+  st->prev_ratio = st->ratio;
+  st->ratio = (now - st->start_time) / duration;
+
+  if (st->ratio < 1.0)
+    {
+      st->fn (st);
+      XSync (st->dpy, False);
+    }
+  else
+    {
+      eraser_free (st);
+      st = 0;
+    }
+  return st;
+}
diff --git a/screenhack/erase.h b/screenhack/erase.h
new file mode 100644 (file)
index 0000000..1e720f9
--- /dev/null
@@ -0,0 +1,21 @@
+/* erase.c: Erase the screen in various more or less interesting ways.
+ * Copyright (c) 1997-2001, 2006 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifndef __XSCREENSAVER_ERASE_H__
+#define __XSCREENSAVER_ERASE_H__
+
+typedef struct eraser_state eraser_state;
+
+extern void eraser_free (eraser_state *st);
+extern eraser_state *erase_window (Display *, Window, eraser_state *);
+
+#endif /* __XSCREENSAVER_ERASE_H__ */
diff --git a/screenhack/fade.c b/screenhack/fade.c
new file mode 100644 (file)
index 0000000..7a2ce2b
--- /dev/null
@@ -0,0 +1,962 @@
+/* xscreensaver, Copyright (c) 1992-2011 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#include "utils.h"
+
+#include <sys/time.h> /* for gettimeofday() */
+
+#ifdef VMS
+# include "vms-gtod.h"
+#endif /* VMS */
+
+#include "visual.h"
+#include "usleep.h"
+#include "fade.h"
+
+Colormap
+copy_colormap (Screen *screen, Visual *visual,
+              Colormap cmap, Colormap into_cmap)
+{
+  int i;
+  Display *dpy = DisplayOfScreen (screen);
+  Window window = RootWindowOfScreen (screen);
+  int ncolors = CellsOfScreen (screen);
+  XColor *colors = 0;
+
+  /* If this is a colormap on a mono visual, or one with insanely many
+     color cells, bug out. */
+  if (ncolors <= 2 || ncolors > 4096)
+    return 0;
+  /* If this is a non-writable visual, bug out. */
+  if (!has_writable_cells (screen, visual))
+    return 0;
+
+  if (! into_cmap)
+    into_cmap = XCreateColormap (dpy, window, visual, AllocAll);
+  if (! cmap)
+    cmap = DefaultColormapOfScreen (screen);
+
+  colors = (XColor *) calloc(sizeof(XColor), ncolors);
+  for (i = 0; i < ncolors; i++)
+    colors [i].pixel = i;
+  XQueryColors (dpy, cmap, colors, ncolors);
+  XStoreColors (dpy, into_cmap, colors, ncolors);
+  free (colors);
+  return into_cmap;
+}
+
+
+void
+blacken_colormap (Screen *screen, Colormap cmap)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  int ncolors = CellsOfScreen (screen);
+  XColor *colors;
+  int i;
+  if (ncolors > 4096)
+    return;
+  colors = (XColor *) calloc(sizeof(XColor), ncolors);
+  for (i = 0; i < ncolors; i++)
+    colors[i].pixel = i;
+  XStoreColors (dpy, cmap, colors, ncolors);
+  free (colors);
+}
+
+
+
+static void fade_screens_1 (Display *dpy, Colormap *cmaps,
+                           Window *black_windows, int nwindows,
+                            int seconds, int ticks,
+                           Bool out_p, Bool clear_windows);
+
+#ifdef HAVE_SGI_VC_EXTENSION
+static int sgi_gamma_fade (Display *dpy,
+                          Window *black_windows, int nwindows,
+                           int seconds, int ticks,
+                          Bool out_p, Bool clear_windows);
+#endif /* HAVE_SGI_VC_EXTENSION */
+
+#ifdef HAVE_XF86VMODE_GAMMA
+static int xf86_gamma_fade (Display *dpy,
+                            Window *black_windows, int nwindows,
+                            int seconds, int ticks,
+                            Bool out_p, Bool clear_windows);
+#endif /* HAVE_XF86VMODE_GAMMA */
+
+
+void
+fade_screens (Display *dpy, Colormap *cmaps,
+              Window *black_windows, int nwindows,
+             int seconds, int ticks,
+             Bool out_p, Bool clear_windows)
+{
+  int oseconds = seconds;
+  Bool was_in_p = !out_p;
+
+  /* When we're asked to fade in, first fade out, then fade in.
+     That way all the transitions are smooth -- from what's on the
+     screen, to black, to the desktop.
+   */
+  if (was_in_p)
+    {
+      clear_windows = True;
+      out_p = True;
+      seconds /= 3;
+      if (seconds == 0)
+       seconds = 1;
+    }
+
+ AGAIN:
+
+/* #### printf("\n\nfade_screens %d %d %d\n", seconds, ticks, out_p); */
+
+#ifdef HAVE_SGI_VC_EXTENSION
+  /* First try to do it by fading the gamma in an SGI-specific way... */
+  if (0 == sgi_gamma_fade(dpy, black_windows, nwindows,
+                          seconds, ticks, out_p,
+                         clear_windows))
+    ;
+  else
+#endif /* HAVE_SGI_VC_EXTENSION */
+
+#ifdef HAVE_XF86VMODE_GAMMA
+  /* Then try to do it by fading the gamma in an XFree86-specific way... */
+  if (0 == xf86_gamma_fade(dpy, black_windows, nwindows,
+                           seconds, ticks, out_p,
+                           clear_windows))
+    ;
+  else
+#endif /* HAVE_XF86VMODE_GAMMA */
+
+    /* Else, do it the old-fashioned way, which (somewhat) loses if
+       there are TrueColor windows visible. */
+    fade_screens_1 (dpy, cmaps, black_windows, nwindows,
+                    seconds, ticks,
+                   out_p, clear_windows);
+
+  /* If we were supposed to be fading in, do so now (we just faded out,
+     so now fade back in.)
+   */
+  if (was_in_p)
+    {
+      was_in_p = False;
+      out_p = False;
+      seconds = oseconds * 2 / 3;
+      if (seconds == 0)
+       seconds = 1;
+      goto AGAIN;
+    }
+}
+
+
+static void
+sleep_from (struct timeval *now, struct timeval *then, long usecs_per_step)
+{
+  /* If several seconds have passed, the machine must have been asleep
+     or thrashing or something.  Don't sleep in that case, to avoid
+     overflowing and sleeping for an unconscionably long time.  This
+     function should only be sleeping for very short periods.
+   */
+  if (now->tv_sec - then->tv_sec < 5)
+    {
+      long diff = (((now->tv_sec - then->tv_sec) * 1000000) +
+                   now->tv_usec - then->tv_usec);
+      if (usecs_per_step > diff)
+        usleep (usecs_per_step - diff);
+    }
+
+  then->tv_sec  = now->tv_sec;
+  then->tv_usec = now->tv_usec;
+}
+
+
+
+/* The business with `cmaps_per_screen' is to fake out the SGI 8-bit video
+   hardware, which is capable of installing multiple (4) colormaps
+   simultaneously.  We have to install multiple copies of the same set of
+   colors in order to fill up all the available slots in the hardware color
+   lookup table, so we install an extra N colormaps per screen to make sure
+   that all screens really go black.
+
+   I'm told that this trick also works with XInside's AcceleratedX when using
+   the Matrox Millennium card (which also allows multiple PseudoColor and
+   TrueColor visuals to co-exist and display properly at the same time.)  
+
+   This trick works ok on the 24-bit Indy video hardware, but doesn't work at
+   all on the O2 24-bit hardware.  I guess the higher-end hardware is too
+   "good" for this to work (dammit.)  So... I figured out the "right" way to
+   do this on SGIs, which is to ramp the monitor's gamma down to 0.  That's
+   what is implemented in sgi_gamma_fade(), so we use that if we can.
+ */
+static void
+fade_screens_1 (Display *dpy, Colormap *cmaps,
+                Window *black_windows, int nwindows,
+               int seconds, int ticks,
+               Bool out_p, Bool clear_windows)
+{
+  int i, j, k;
+  int steps = seconds * ticks;
+  long usecs_per_step = (long)(seconds * 1000000) / (long)steps;
+  XEvent dummy_event;
+  int cmaps_per_screen = 5;
+  int nscreens = ScreenCount(dpy);
+  int ncmaps = nscreens * cmaps_per_screen;
+  Colormap *fade_cmaps = 0;
+  Bool installed = False;
+  int total_ncolors;
+  XColor *orig_colors, *current_colors, *screen_colors, *orig_screen_colors;
+  struct timeval then, now;
+#ifdef GETTIMEOFDAY_TWO_ARGS
+  struct timezone tzp;
+#endif
+
+  total_ncolors = 0;
+  for (i = 0; i < nscreens; i++)
+    total_ncolors += CellsOfScreen (ScreenOfDisplay(dpy, i));
+
+  orig_colors    = (XColor *) calloc(sizeof(XColor), total_ncolors);
+  current_colors = (XColor *) calloc(sizeof(XColor), total_ncolors);
+
+  /* Get the contents of the colormap we are fading from or to. */
+  screen_colors = orig_colors;
+  for (i = 0; i < nscreens; i++)
+    {
+      int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, i));
+      Colormap cmap = (cmaps ? cmaps[i] : 0);
+      if (!cmap) cmap = DefaultColormap(dpy, i);
+
+      for (j = 0; j < ncolors; j++)
+       screen_colors[j].pixel = j;
+      XQueryColors (dpy, cmap, screen_colors, ncolors);
+
+      screen_colors += ncolors;
+    }
+
+  memcpy (current_colors, orig_colors, total_ncolors * sizeof (XColor));
+
+
+  /* Make the writable colormaps (we keep these around and reuse them.) */
+  if (!fade_cmaps)
+    {
+      fade_cmaps = (Colormap *) calloc(sizeof(Colormap), ncmaps);
+      for (i = 0; i < nscreens; i++)
+       {
+         Visual *v = DefaultVisual(dpy, i);
+         Screen *s = ScreenOfDisplay(dpy, i);
+         if (has_writable_cells (s, v))
+           for (j = 0; j < cmaps_per_screen; j++)
+             fade_cmaps[(i * cmaps_per_screen) + j] =
+               XCreateColormap (dpy, RootWindowOfScreen (s), v, AllocAll);
+       }
+    }
+
+#ifdef GETTIMEOFDAY_TWO_ARGS
+  gettimeofday(&then, &tzp);
+#else
+  gettimeofday(&then);
+#endif
+
+  /* Iterate by steps of the animation... */
+  for (i = (out_p ? steps : 0);
+       (out_p ? i > 0 : i < steps);
+       (out_p ? i-- : i++))
+    {
+
+      /* For each screen, compute the current value of each color...
+       */
+      orig_screen_colors = orig_colors;
+      screen_colors = current_colors;
+      for (j = 0; j < nscreens; j++)
+       {
+         int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, j));
+         for (k = 0; k < ncolors; k++)
+           {
+             /* This doesn't take into account the relative luminance of the
+                RGB components (0.299, 0.587, and 0.114 at gamma 2.2) but
+                the difference is imperceptible for this application... */
+             screen_colors[k].red   = orig_screen_colors[k].red   * i / steps;
+             screen_colors[k].green = orig_screen_colors[k].green * i / steps;
+             screen_colors[k].blue  = orig_screen_colors[k].blue  * i / steps;
+           }
+         screen_colors      += ncolors;
+         orig_screen_colors += ncolors;
+       }
+
+      /* Put the colors into the maps...
+       */
+      screen_colors = current_colors;
+      for (j = 0; j < nscreens; j++)
+       {
+         int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, j));
+         for (k = 0; k < cmaps_per_screen; k++)
+           {
+             Colormap c = fade_cmaps[j * cmaps_per_screen + k];
+             if (c)
+               XStoreColors (dpy, c, screen_colors, ncolors);
+           }
+         screen_colors += ncolors;
+       }
+
+      /* Put the maps on the screens, and then take the windows off the screen.
+        (only need to do this the first time through the loop.)
+       */
+      if (!installed)
+       {
+         for (j = 0; j < ncmaps; j++)
+           if (fade_cmaps[j])
+             XInstallColormap (dpy, fade_cmaps[j]);
+         installed = True;
+
+         if (black_windows && !out_p)
+           for (j = 0; j < nwindows; j++)
+             if (black_windows[j])
+               {
+                 XUnmapWindow (dpy, black_windows[j]);
+                 XClearWindow (dpy, black_windows[j]);
+               }
+       }
+
+      XSync (dpy, False);
+
+      /* If there is user activity, bug out.  (Bug out on keypresses or
+        mouse presses, but not motion, and not release events.  Bugging
+        out on motion made the unfade hack be totally useless, I think.)
+
+        We put the event back so that the calling code can notice it too.
+        It would be better to not remove it at all, but that's harder
+        because Xlib has such a non-design for this kind of crap, and
+        in this application it doesn't matter if the events end up out
+        of order, so in the grand unix tradition we say "fuck it" and
+        do something that mostly works for the time being.
+       */
+      if (XCheckMaskEvent (dpy, (KeyPressMask|ButtonPressMask), &dummy_event))
+       {
+         XPutBackEvent (dpy, &dummy_event);
+         goto DONE;
+       }
+
+#ifdef GETTIMEOFDAY_TWO_ARGS
+      gettimeofday(&now, &tzp);
+#else
+      gettimeofday(&now);
+#endif
+
+      /* If we haven't already used up our alotted time, sleep to avoid
+        changing the colormap too fast. */
+      sleep_from (&now, &then, usecs_per_step);
+    }
+
+ DONE:
+
+  if (orig_colors)    free (orig_colors);
+  if (current_colors) free (current_colors);
+
+  /* If we've been given windows to raise after blackout, raise them before
+     releasing the colormaps.
+   */
+  if (out_p && black_windows)
+    {
+      for (i = 0; i < nwindows; i++)
+       {
+         if (clear_windows)
+           XClearWindow (dpy, black_windows[i]);
+         XMapRaised (dpy, black_windows[i]);
+       }
+      XSync(dpy, False);
+    }
+
+  /* Now put the target maps back.
+     If we're fading out, use the given cmap (or the default cmap, if none.)
+     If we're fading in, always use the default cmap.
+   */
+  for (i = 0; i < nscreens; i++)
+    {
+      Colormap cmap = (cmaps ? cmaps[i] : 0);
+      if (!cmap || !out_p)
+       cmap = DefaultColormap(dpy, i);
+      XInstallColormap (dpy, cmap);
+    }
+
+  /* The fade (in or out) is complete, so we don't need the black maps on
+     stage any more.
+   */
+  for (i = 0; i < ncmaps; i++)
+    if (fade_cmaps[i])
+      {
+       XUninstallColormap(dpy, fade_cmaps[i]);
+       XFreeColormap(dpy, fade_cmaps[i]);
+       fade_cmaps[i] = 0;
+      }
+  free(fade_cmaps);
+  fade_cmaps = 0;
+}
+
+
+\f
+/* SGI Gamma fading */
+
+#ifdef HAVE_SGI_VC_EXTENSION
+
+# include <X11/extensions/XSGIvc.h>
+
+struct screen_sgi_gamma_info {
+  int gamma_map;  /* ??? always using 0 */
+  int nred, ngreen, nblue;
+  unsigned short *red1, *green1, *blue1;
+  unsigned short *red2, *green2, *blue2;
+  int gamma_size;
+  int gamma_precision;
+  Bool alpha_p;
+};
+
+
+static void sgi_whack_gamma(Display *dpy, int screen,
+                            struct screen_sgi_gamma_info *info, float ratio);
+
+static int
+sgi_gamma_fade (Display *dpy,
+               Window *black_windows, int nwindows,
+                int seconds, int ticks,
+               Bool out_p, Bool clear_windows)
+{
+  int steps = seconds * ticks;
+  long usecs_per_step = (long)(seconds * 1000000) / (long)steps;
+  XEvent dummy_event;
+  int nscreens = ScreenCount(dpy);
+  struct timeval then, now;
+#ifdef GETTIMEOFDAY_TWO_ARGS
+  struct timezone tzp;
+#endif
+  int i, screen;
+  int status = -1;
+  struct screen_sgi_gamma_info *info = (struct screen_sgi_gamma_info *)
+    calloc(nscreens, sizeof(*info));
+
+  /* Get the current gamma maps for all screens.
+     Bug out and return -1 if we can't get them for some screen.
+   */
+  for (screen = 0; screen < nscreens; screen++)
+    {
+      if (!XSGIvcQueryGammaMap(dpy, screen, info[screen].gamma_map,
+                              &info[screen].gamma_size,
+                              &info[screen].gamma_precision,
+                              &info[screen].alpha_p))
+       goto FAIL;
+
+      if (!XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
+                                 XSGIVC_COMPONENT_RED,
+                                 &info[screen].nred, &info[screen].red1))
+       goto FAIL;
+      if (! XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
+                                  XSGIVC_COMPONENT_GREEN,
+                                  &info[screen].ngreen, &info[screen].green1))
+       goto FAIL;
+      if (!XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
+                                 XSGIVC_COMPONENT_BLUE,
+                                 &info[screen].nblue, &info[screen].blue1))
+       goto FAIL;
+
+      if (info[screen].gamma_precision == 8)    /* Scale it up to 16 bits. */
+       {
+         int j;
+         for(j = 0; j < info[screen].nred; j++)
+           info[screen].red1[j]   =
+             ((info[screen].red1[j]   << 8) | info[screen].red1[j]);
+         for(j = 0; j < info[screen].ngreen; j++)
+           info[screen].green1[j] =
+             ((info[screen].green1[j] << 8) | info[screen].green1[j]);
+         for(j = 0; j < info[screen].nblue; j++)
+           info[screen].blue1[j]  =
+             ((info[screen].blue1[j]  << 8) | info[screen].blue1[j]);
+       }
+
+      info[screen].red2   = (unsigned short *)
+       malloc(sizeof(*info[screen].red2)   * (info[screen].nred+1));
+      info[screen].green2 = (unsigned short *)
+       malloc(sizeof(*info[screen].green2) * (info[screen].ngreen+1));
+      info[screen].blue2  = (unsigned short *)
+       malloc(sizeof(*info[screen].blue2)  * (info[screen].nblue+1));
+    }
+
+#ifdef GETTIMEOFDAY_TWO_ARGS
+  gettimeofday(&then, &tzp);
+#else
+  gettimeofday(&then);
+#endif
+
+  /* If we're fading in (from black), then first crank the gamma all the
+     way down to 0, then take the windows off the screen.
+   */
+  if (!out_p)
+    {
+      for (screen = 0; screen < nscreens; screen++)
+       sgi_whack_gamma(dpy, screen, &info[screen], 0.0);
+      
+      for (screen = 0; screen < nwindows; screen++)
+       if (black_windows && black_windows[screen])
+         {
+           XUnmapWindow (dpy, black_windows[screen]);
+           XClearWindow (dpy, black_windows[screen]);
+           XSync(dpy, False);
+         }
+    }
+
+  /* Iterate by steps of the animation... */
+  for (i = (out_p ? steps : 0);
+       (out_p ? i > 0 : i < steps);
+       (out_p ? i-- : i++))
+    {
+      for (screen = 0; screen < nscreens; screen++)
+       {
+         sgi_whack_gamma(dpy, screen, &info[screen],
+                          (((float)i) / ((float)steps)));
+
+         /* If there is user activity, bug out.  (Bug out on keypresses or
+            mouse presses, but not motion, and not release events.  Bugging
+            out on motion made the unfade hack be totally useless, I think.)
+
+            We put the event back so that the calling code can notice it too.
+            It would be better to not remove it at all, but that's harder
+            because Xlib has such a non-design for this kind of crap, and
+            in this application it doesn't matter if the events end up out
+            of order, so in the grand unix tradition we say "fuck it" and
+            do something that mostly works for the time being.
+          */
+         if (XCheckMaskEvent (dpy, (KeyPressMask|ButtonPressMask),
+                              &dummy_event))
+           {
+             XPutBackEvent (dpy, &dummy_event);
+             goto DONE;
+           }
+
+#ifdef GETTIMEOFDAY_TWO_ARGS
+         gettimeofday(&now, &tzp);
+#else
+         gettimeofday(&now);
+#endif
+
+         /* If we haven't already used up our alotted time, sleep to avoid
+            changing the colormap too fast. */
+          sleep_from (&now, &then, usecs_per_step);
+       }
+    }
+  
+
+ DONE:
+
+  if (out_p && black_windows)
+    {
+      for (screen = 0; screen < nwindows; screen++)
+       {
+         if (clear_windows)
+           XClearWindow (dpy, black_windows[screen]);
+         XMapRaised (dpy, black_windows[screen]);
+       }
+      XSync(dpy, False);
+    }
+
+  /* I can't explain this; without this delay, we get a flicker.
+     I suppose there's some lossage with stale bits being in the
+     hardware frame buffer or something, and this delay gives it
+     time to flush out.  This sucks! */
+  usleep(100000);  /* 1/10th second */
+
+  for (screen = 0; screen < nscreens; screen++)
+    sgi_whack_gamma(dpy, screen, &info[screen], 1.0);
+  XSync(dpy, False);
+
+  status = 0;
+
+ FAIL:
+  for (screen = 0; screen < nscreens; screen++)
+    {
+      if (info[screen].red1)   free (info[screen].red1);
+      if (info[screen].green1) free (info[screen].green1);
+      if (info[screen].blue1)  free (info[screen].blue1);
+      if (info[screen].red2)   free (info[screen].red2);
+      if (info[screen].green2) free (info[screen].green2);
+      if (info[screen].blue2)  free (info[screen].blue2);
+    }
+  free(info);
+
+  return status;
+}
+
+static void
+sgi_whack_gamma(Display *dpy, int screen, struct screen_sgi_gamma_info *info,
+                float ratio)
+{
+  int k;
+
+  if (ratio < 0) ratio = 0;
+  if (ratio > 1) ratio = 1;
+  for (k = 0; k < info->gamma_size; k++)
+    {
+      info->red2[k]   = info->red1[k]   * ratio;
+      info->green2[k] = info->green1[k] * ratio;
+      info->blue2[k]  = info->blue1[k]  * ratio;
+    }
+
+  XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->nred,
+                          XSGIVC_MComponentRed, info->red2);
+  XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->ngreen,
+                          XSGIVC_MComponentGreen, info->green2);
+  XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->nblue,
+                          XSGIVC_MComponentBlue, info->blue2);
+  XSync(dpy, False);
+}
+
+#endif /* HAVE_SGI_VC_EXTENSION */
+
+
+\f
+/* XFree86 4.x+ Gamma fading */
+
+#ifdef HAVE_XF86VMODE_GAMMA
+
+#include <X11/extensions/xf86vmode.h>
+
+typedef struct {
+  XF86VidModeGamma vmg;
+  int size;
+  unsigned short *r, *g, *b;
+} xf86_gamma_info;
+
+static int xf86_check_gamma_extension (Display *dpy);
+static Bool xf86_whack_gamma (Display *dpy, int screen,
+                              xf86_gamma_info *ginfo, float ratio);
+
+static int
+xf86_gamma_fade (Display *dpy,
+                 Window *black_windows, int nwindows,
+                 int seconds, int ticks,
+                 Bool out_p, Bool clear_windows)
+{
+  int steps = seconds * ticks;
+  long usecs_per_step = (long)(seconds * 1000000) / (long)steps;
+  XEvent dummy_event;
+  int nscreens = ScreenCount(dpy);
+  struct timeval then, now;
+#ifdef GETTIMEOFDAY_TWO_ARGS
+  struct timezone tzp;
+#endif
+  int i, screen;
+  int status = -1;
+  xf86_gamma_info *info = 0;
+
+  static int ext_ok = -1;
+
+  /* Only probe the extension once: the answer isn't going to change. */
+  if (ext_ok == -1)
+    ext_ok = xf86_check_gamma_extension (dpy);
+
+  /* If this server doesn't have the gamma extension, bug out. */
+  if (ext_ok == 0)
+    goto FAIL;
+
+# ifndef HAVE_XF86VMODE_GAMMA_RAMP
+  if (ext_ok == 2) ext_ok = 1;  /* server is newer than client! */
+# endif
+
+  info = (xf86_gamma_info *) calloc(nscreens, sizeof(*info));
+
+  /* Get the current gamma maps for all screens.
+     Bug out and return -1 if we can't get them for some screen.
+   */
+  for (screen = 0; screen < nscreens; screen++)
+    {
+      if (ext_ok == 1)  /* only have gamma parameter, not ramps. */
+        {
+          if (!XF86VidModeGetGamma(dpy, screen, &info[screen].vmg))
+            goto FAIL;
+        }
+# ifdef HAVE_XF86VMODE_GAMMA_RAMP
+      else if (ext_ok == 2)  /* have ramps */
+        {
+          if (!XF86VidModeGetGammaRampSize(dpy, screen, &info[screen].size))
+            goto FAIL;
+          if (info[screen].size <= 0)
+            goto FAIL;
+
+          info[screen].r = (unsigned short *)
+            calloc(info[screen].size, sizeof(unsigned short));
+          info[screen].g = (unsigned short *)
+            calloc(info[screen].size, sizeof(unsigned short));
+          info[screen].b = (unsigned short *)
+            calloc(info[screen].size, sizeof(unsigned short));
+
+          if (!(info[screen].r && info[screen].g && info[screen].b))
+            goto FAIL;
+
+          if (!XF86VidModeGetGammaRamp(dpy, screen, info[screen].size,
+                                       info[screen].r,
+                                       info[screen].g,
+                                       info[screen].b))
+            goto FAIL;
+        }
+# endif /* HAVE_XF86VMODE_GAMMA_RAMP */
+      else
+        abort();
+    }
+
+#ifdef GETTIMEOFDAY_TWO_ARGS
+  gettimeofday(&then, &tzp);
+#else
+  gettimeofday(&then);
+#endif
+
+  /* If we're fading in (from black), then first crank the gamma all the
+     way down to 0, then take the windows off the screen.
+   */
+  if (!out_p)
+    {
+      for (screen = 0; screen < nscreens; screen++)
+       xf86_whack_gamma(dpy, screen, &info[screen], 0.0);
+      for (screen = 0; screen < nwindows; screen++)
+       if (black_windows && black_windows[screen])
+         {
+           XUnmapWindow (dpy, black_windows[screen]);
+           XClearWindow (dpy, black_windows[screen]);
+           XSync(dpy, False);
+         }
+    }
+
+  /* Iterate by steps of the animation... */
+  for (i = (out_p ? steps : 0);
+       (out_p ? i > 0 : i < steps);
+       (out_p ? i-- : i++))
+    {
+      for (screen = 0; screen < nscreens; screen++)
+       {
+         xf86_whack_gamma(dpy, screen, &info[screen],
+                           (((float)i) / ((float)steps)));
+
+         /* If there is user activity, bug out.  (Bug out on keypresses or
+            mouse presses, but not motion, and not release events.  Bugging
+            out on motion made the unfade hack be totally useless, I think.)
+
+            We put the event back so that the calling code can notice it too.
+            It would be better to not remove it at all, but that's harder
+            because Xlib has such a non-design for this kind of crap, and
+            in this application it doesn't matter if the events end up out
+            of order, so in the grand unix tradition we say "fuck it" and
+            do something that mostly works for the time being.
+          */
+         if (XCheckMaskEvent (dpy, (KeyPressMask|ButtonPressMask),
+                              &dummy_event))
+           {
+             XPutBackEvent (dpy, &dummy_event);
+             goto DONE;
+           }
+
+#ifdef GETTIMEOFDAY_TWO_ARGS
+         gettimeofday(&now, &tzp);
+#else
+         gettimeofday(&now);
+#endif
+
+         /* If we haven't already used up our alotted time, sleep to avoid
+            changing the colormap too fast. */
+          sleep_from (&now, &then, usecs_per_step);
+       }
+    }
+  
+
+ DONE:
+
+  if (out_p && black_windows)
+    {
+      for (screen = 0; screen < nwindows; screen++)
+       {
+         if (clear_windows)
+           XClearWindow (dpy, black_windows[screen]);
+         XMapRaised (dpy, black_windows[screen]);
+       }
+      XSync(dpy, False);
+    }
+
+  /* I can't explain this; without this delay, we get a flicker.
+     I suppose there's some lossage with stale bits being in the
+     hardware frame buffer or something, and this delay gives it
+     time to flush out.  This sucks! */
+  usleep(100000);  /* 1/10th second */
+
+  for (screen = 0; screen < nscreens; screen++)
+    xf86_whack_gamma(dpy, screen, &info[screen], 1.0);
+  XSync(dpy, False);
+
+  status = 0;
+
+ FAIL:
+  if (info)
+    {
+      for (screen = 0; screen < nscreens; screen++)
+        {
+          if (info[screen].r) free(info[screen].r);
+          if (info[screen].g) free(info[screen].g);
+          if (info[screen].b) free(info[screen].b);
+        }
+      free(info);
+    }
+
+  return status;
+}
+
+
+/* This bullshit is needed because the VidMode extension doesn't work
+   on remote displays -- but if the remote display has the extension
+   at all, XF86VidModeQueryExtension returns true, and then
+   XF86VidModeQueryVersion dies with an X error.  Thank you XFree,
+   may I have another.
+ */
+
+static Bool error_handler_hit_p = False;
+
+static int
+ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
+{
+  error_handler_hit_p = True;
+  return 0;
+}
+
+static Bool
+safe_XF86VidModeQueryVersion (Display *dpy, int *majP, int *minP)
+{
+  Bool result;
+  XErrorHandler old_handler;
+  XSync (dpy, False);
+  error_handler_hit_p = False;
+  old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+
+  result = XF86VidModeQueryVersion (dpy, majP, minP);
+
+  XSync (dpy, False);
+  XSetErrorHandler (old_handler);
+  XSync (dpy, False);
+
+  return (error_handler_hit_p
+          ? False
+          : result);
+}
+
+
+
+/* VidModeExtension version 2.0 or better is needed to do gamma.
+   2.0 added gamma values; 2.1 added gamma ramps.
+ */
+# define XF86_VIDMODE_GAMMA_MIN_MAJOR 2
+# define XF86_VIDMODE_GAMMA_MIN_MINOR 0
+# define XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR 2
+# define XF86_VIDMODE_GAMMA_RAMP_MIN_MINOR 1
+
+
+
+/* Returns 0 if gamma fading not available; 1 if only gamma value setting
+   is available; 2 if gamma ramps are available.
+ */
+static int
+xf86_check_gamma_extension (Display *dpy)
+{
+  int event, error, major, minor;
+
+  if (!XF86VidModeQueryExtension (dpy, &event, &error))
+    return 0;  /* display doesn't have the extension. */
+
+  if (!safe_XF86VidModeQueryVersion (dpy, &major, &minor))
+    return 0;  /* unable to get version number? */
+
+  if (major < XF86_VIDMODE_GAMMA_MIN_MAJOR || 
+      (major == XF86_VIDMODE_GAMMA_MIN_MAJOR &&
+       minor < XF86_VIDMODE_GAMMA_MIN_MINOR))
+    return 0;  /* extension is too old for gamma. */
+
+  if (major < XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR || 
+      (major == XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR &&
+       minor < XF86_VIDMODE_GAMMA_RAMP_MIN_MINOR))
+    return 1;  /* extension is too old for gamma ramps. */
+
+  /* Copacetic */
+  return 2;
+}
+
+
+/* XFree doesn't let you set gamma to a value smaller than this.
+   Apparently they didn't anticipate the trick I'm doing here...
+ */
+#define XF86_MIN_GAMMA  0.1
+
+
+static Bool
+xf86_whack_gamma(Display *dpy, int screen, xf86_gamma_info *info,
+                 float ratio)
+{
+  Bool status;
+
+  XErrorHandler old_handler;
+  XSync (dpy, False);
+  error_handler_hit_p = False;
+  old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+
+  if (ratio < 0) ratio = 0;
+  if (ratio > 1) ratio = 1;
+
+  if (info->size == 0)    /* we only have a gamma number, not a ramp. */
+    {
+      XF86VidModeGamma g2;
+
+      g2.red   = info->vmg.red   * ratio;
+      g2.green = info->vmg.green * ratio;
+      g2.blue  = info->vmg.blue  * ratio;
+
+# ifdef XF86_MIN_GAMMA
+      if (g2.red   < XF86_MIN_GAMMA) g2.red   = XF86_MIN_GAMMA;
+      if (g2.green < XF86_MIN_GAMMA) g2.green = XF86_MIN_GAMMA;
+      if (g2.blue  < XF86_MIN_GAMMA) g2.blue  = XF86_MIN_GAMMA;
+# endif
+
+      status = XF86VidModeSetGamma (dpy, screen, &g2);
+    }
+  else
+    {
+# ifdef HAVE_XF86VMODE_GAMMA_RAMP
+
+      unsigned short *r, *g, *b;
+      int i;
+      r = (unsigned short *) malloc(info->size * sizeof(unsigned short));
+      g = (unsigned short *) malloc(info->size * sizeof(unsigned short));
+      b = (unsigned short *) malloc(info->size * sizeof(unsigned short));
+
+      for (i = 0; i < info->size; i++)
+        {
+          r[i] = info->r[i] * ratio;
+          g[i] = info->g[i] * ratio;
+          b[i] = info->b[i] * ratio;
+        }
+
+      status = XF86VidModeSetGammaRamp(dpy, screen, info->size, r, g, b);
+
+      free (r);
+      free (g);
+      free (b);
+
+# else  /* !HAVE_XF86VMODE_GAMMA_RAMP */
+      abort();
+# endif /* !HAVE_XF86VMODE_GAMMA_RAMP */
+    }
+
+  XSync (dpy, False);
+  XSetErrorHandler (old_handler);
+  XSync (dpy, False);
+
+  return status;
+}
+
+#endif /* HAVE_XF86VMODE_GAMMA */
diff --git a/screenhack/fade.h b/screenhack/fade.h
new file mode 100644 (file)
index 0000000..bedae93
--- /dev/null
@@ -0,0 +1,21 @@
+/* xscreensaver, Copyright (c) 1992-1997, 2003 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifndef __FADE_H__
+#define __FADE_H__
+
+extern Colormap copy_colormap (Screen *, Visual *, Colormap from, Colormap to);
+extern void blacken_colormap (Screen *, Colormap cmap);
+extern void fade_screens (Display *dpy,
+                         Colormap *cmaps, Window *black_windows, int nwindows,
+                         int seconds, int ticks,
+                         Bool out_p, Bool clear_windows);
+#endif /* __FADE_H__ */
diff --git a/screenhack/font-retry.c b/screenhack/font-retry.c
new file mode 100644 (file)
index 0000000..d726378
--- /dev/null
@@ -0,0 +1,226 @@
+/* xscreensaver, Copyright (c) 2018-2020 by Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/* Like XLoadQueryFont, but if it fails, it tries some heuristics to
+   load something close.
+ */
+
+#define _GNU_SOURCE
+
+#include "utils.h"
+#include "visual.h"
+#include "xft.h"
+#include "font-retry.h"
+
+extern const char *progname;
+
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+#undef DEBUG
+
+static void *
+load_font_retry_1 (Display *dpy, int screen, const char *xlfd, Bool xft_p)
+{
+
+# ifdef USE_XFT
+#  define LOADFONT(F) (xft_p \
+                      ? (void *) XftFontOpenXlfd (dpy, screen, (F)) \
+                      : (void *) XLoadQueryFont (dpy, (F)))
+# else
+#  define LOADFONT(F) ((void *) XLoadQueryFont (dpy, (F)))
+# endif
+
+  void *f = xlfd ? LOADFONT(xlfd) : 0;
+
+# ifndef USE_XFT
+  if (xft_p) abort();
+# endif
+
+# ifdef HAVE_JWXYZ
+  return f;
+# else /* !HAVE_JWXYZ */
+  if (! xlfd) xlfd = "<null>";
+  if (f)
+    {
+# ifdef DEBUG
+      fprintf (stderr, "%s: loaded %s\n", progname, xlfd);
+# endif
+      return f;
+    }
+  else
+    {
+      Bool bold_p   = (!!strcasestr (xlfd, "-bold-") ||
+                       !!strcasestr (xlfd, "-ocr"));
+      Bool italic_p = (!!strcasestr (xlfd, "-i-") ||
+                       !!strcasestr (xlfd, "-o-"));
+      Bool fixed_p  = (!!strcasestr (xlfd, "courier") ||
+                       !!strcasestr (xlfd, "-ocr") ||
+                       !!strcasestr (xlfd, "-m-") ||
+                       !!strcasestr (xlfd, "-c-"));
+      int size = 0;
+
+# ifdef DEBUG
+      fprintf (stderr, "%s: failed %s\n", progname, xlfd);
+# endif
+
+      if (!strcmp (xlfd, "vga"))  /* BSOD uses this: it has no XLFD name. */
+        fixed_p = True, size = 120;
+
+      /* Look for the first number in the string like "-180-" */
+      if (! size)
+        {
+          const char *s;
+          for (s = xlfd; *s; s++)
+            if (s[0] == '-' && s[1] >= '0' && s[1] <= '9')
+              {
+                int i = s[1] - '0';
+                const char *s2 = s+2;
+                while (*s2 >= '0' && *s2 <= '9')
+                  {
+                    i = i * 10 + *s2 - '0';
+                    s2++;
+                  }
+                if (*s2 != '-') continue;          /* Number ends with dash */
+                if (i < 60 || i >= 2000) continue; /* In range 6pt - 200pt */
+                if (i % 10) continue;              /* Multiple of 10 */
+
+                size = i;
+                break;
+              }
+        }
+
+      if (! size)
+        {
+          fprintf (stderr, "%s: unloadable, unparsable font: \"%s\"\n",
+                   progname, xlfd);
+          xlfd = "fixed";
+          return LOADFONT(xlfd);
+        }
+      else
+        {
+          const char *variable[] = {
+            "helvetica",
+            "arial",
+            "bitstream vera sans",  /* sometimes foundry is in family */
+            "vera sans",            /* sometimes not? */
+            "gill sans",
+            "times",
+            "times new roman",
+            "new century schoolbook",
+            "utopia",
+            "palatino",
+            "lucida",
+            "clearlyu",
+            "bitstream charter",    /* sometimes foundry is in family */
+            "charter",              /* sometimes not? */
+            "utopia",
+            "luxi sans",
+            "latin modern roman",
+
+            /* Don't use a wildcard family. If none of the above worked, then
+               then almost none of the X11 fonts are installed, and it's not
+               unlikely that "-*-*-medium-r-*-*-*-140-*-*-*-10646-1" will
+               match an Arabic or or Japanese font that contains no Latin
+               glyphs at all, even in a Latin locale. So in that case, just
+               let "helvetica" fall back to "fixed".
+             */
+            /* "*" */
+          };
+          const char *fixed[] = {
+            "courier",
+            "courier new",
+            "courier 10 pitch",
+            "lucidatypewriter",
+            "american typewriter",
+            "luxi mono",
+            "fixed",
+            "ocr a std",
+            /* As above, but "can't happen" because we already tried fixed? */
+            /* "*" */
+          };
+          const char *charsets[] = { "iso10646-1", "iso8859-1", "*-*" };
+          const char *weights[]  = { "bold", "medium" };
+          const char *slants[]   = { "o", "i", "r" };
+          const char *spacings[] = { "m", "c", "p" };
+          int a, b, c, d, e, g;
+          char buf[1024];
+
+          for (a = 0; a < countof(charsets); a++)
+            for (b = (bold_p ? 0 : 1); b < countof(weights); b++)
+              for (c = (italic_p ? 0 : 2); c < countof(slants); c++)
+                for (d = 0;
+                     d < (fixed_p ? countof(fixed) : countof(variable));
+                     d++)
+                  for (g = size; g >= 60; g -= 10)
+                    for (e = (fixed_p ? 0 : 2); e < countof(spacings); e++)
+                      {
+                        sprintf (buf,
+                                 "-%s-%s-%s-%s-%s-%s-%s-%d-%s-%s-%s-%s-%s",
+                                 "*",                  /* foundry */
+                                 (fixed_p ? fixed[d] : variable[d]),
+                                 weights[b],
+                                 slants[c],
+                                 "*",                  /* set width */
+                                 "*",                  /* add style */
+                                 "*",                  /* pixel size */
+                                 g,                    /* point size */
+                                 "*",                  /* x resolution */
+                                 "*",                  /* y resolution */
+                                 spacings[e],
+                                 "*",                  /* average width */
+                                 charsets[a]);
+# ifdef DEBUG
+                        fprintf(stderr, "%s: trying %s\n", progname, buf);
+# endif
+                        f = LOADFONT(buf);
+                        if (f)
+                          {
+# ifdef DEBUG
+                            fprintf (stderr,
+                                     "%s: substituted \"%s\" for \"%s\"\n",
+                                     progname, buf, xlfd);
+# endif
+                            return f;
+                          }
+                      }
+
+          fprintf (stderr, "%s: unable to find any alternatives to \"%s\"\n",
+                   progname, xlfd);
+          xlfd = "fixed";
+          return LOADFONT(xlfd);
+        }
+    }
+# endif /* !HAVE_JWXYZ */
+}
+
+XFontStruct *
+load_font_retry (Display *dpy, const char *xlfd)
+{
+  return (XFontStruct *) load_font_retry_1 (dpy, 0, xlfd, 0);
+}
+
+#ifdef USE_XFT
+XftFont *
+load_xft_font_retry (Display *dpy, int screen, const char *xlfd)
+{
+  return (XftFont *) load_font_retry_1 (dpy, screen, xlfd, 1);
+}
+
+#elif defined(HAVE_JWXYZ)
+
+XftFont *
+load_xft_font_retry (Display *dpy, int screen, const char *xlfd)
+{
+  return XftFontOpenXlfd (dpy, screen, xlfd);
+}
+
+#endif /* !HAVE_JWXYZ */
diff --git a/screenhack/font-retry.h b/screenhack/font-retry.h
new file mode 100644 (file)
index 0000000..83ba0e0
--- /dev/null
@@ -0,0 +1,24 @@
+/* xscreensaver, Copyright (c) 2018 by Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifndef __FONT_RETRY_H__
+#define __FONT_RETRY_H__
+
+/* Like XLoadQueryFont, but if it fails, it tries some heuristics to
+   load something close.
+ */
+extern XFontStruct *load_font_retry (Display *, const char *xlfd);
+
+# ifdef __XSCREENSAVER_XFT_H__  /* if xft.h has been included */
+extern XftFont *load_xft_font_retry (Display *, int screen, const char *xlfd);
+# endif
+
+#endif /* __FONT_RETRY_H__ */
diff --git a/screenhack/fps.c b/screenhack/fps.c
new file mode 100644 (file)
index 0000000..176084a
--- /dev/null
@@ -0,0 +1,295 @@
+/* fps, Copyright (c) 2001-2019 Jamie Zawinski <jwz@jwz.org>
+ * Draw a frames-per-second display (Xlib and OpenGL).
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <time.h>
+#include "screenhackI.h"
+#include "fpsI.h"
+
+fps_state *
+fps_init (Display *dpy, Window window)
+{
+  fps_state *st;
+  const char *font;
+  XFontStruct *f;
+  Bool top_p;
+  XWindowAttributes xgwa;
+
+  if (! get_boolean_resource (dpy, "doFPS", "DoFPS"))
+    return 0;
+
+  if (!strcasecmp (progname, "BSOD")) return 0;  /* Never worked right */
+
+  top_p = get_boolean_resource (dpy, "fpsTop", "FPSTop");
+
+  st = (fps_state *) calloc (1, sizeof(*st));
+
+  st->dpy = dpy;
+  st->window = window;
+  st->clear_p = get_boolean_resource (dpy, "fpsSolid", "FPSSolid");
+
+  font = get_string_resource (dpy, "fpsFont", "Font");
+
+  if (!font)
+    font = "-*-courier-bold-r-normal-*-*-180-*-*-*-*-*-*"; /* also texfont.c */
+  f = load_font_retry (dpy, font);
+  if (!f) abort();
+
+  {
+    XGCValues gcv;
+    XGetWindowAttributes (dpy, window, &xgwa);
+    gcv.font = f->fid;
+    gcv.foreground = 
+      get_pixel_resource (st->dpy, xgwa.colormap, "foreground", "Foreground");
+    st->draw_gc = XCreateGC (dpy, window, GCFont|GCForeground, &gcv);
+    gcv.foreground =
+      get_pixel_resource (st->dpy, xgwa.colormap, "background", "Background");
+    st->erase_gc = XCreateGC (dpy, window, GCFont|GCForeground, &gcv);
+  }
+
+  st->font = f;
+  st->x = 10;
+  st->y = 10;
+  if (top_p)
+    st->y = - (st->font->ascent + st->font->descent + 10);
+
+# ifdef HAVE_IPHONE
+  /* Don't hide the FPS display under the iPhone X bezel.
+     #### This is the worst of all possible ways to do this!  But how else?
+   */
+  if (xgwa.width == 2436 || xgwa.height == 2436)
+    {
+      st->x += 48;
+      st->y += 48 * (top_p ? -1 : 1);
+    }
+# endif
+
+  strcpy (st->string, "FPS: ... ");
+
+  return st;
+}
+
+void
+fps_free (fps_state *st)
+{
+  if (st->draw_gc)  XFreeGC (st->dpy, st->draw_gc);
+  if (st->erase_gc) XFreeGC (st->dpy, st->erase_gc);
+  if (st->font) XFreeFont (st->dpy, st->font);
+  free (st);
+}
+
+
+void
+fps_slept (fps_state *st, unsigned long usecs)
+{
+  st->slept += usecs;
+}
+
+
+double
+fps_compute (fps_state *st, unsigned long polys, double depth)
+{
+  if (! st) return 0;  /* too early? */
+
+  /* Every N frames (where N is approximately one second's worth of frames)
+     check the wall clock.  We do this because checking the wall clock is
+     a slow operation.
+   */
+  if (st->frame_count++ >= st->last_ifps)
+    {
+# ifdef GETTIMEOFDAY_TWO_ARGS
+      struct timezone tzp;
+      gettimeofday(&st->this_frame_end, &tzp);
+# else
+      gettimeofday(&st->this_frame_end);
+# endif
+
+      if (st->prev_frame_end.tv_sec == 0)
+        st->prev_frame_end = st->this_frame_end;
+    }
+
+  /* If we've probed the wall-clock time, regenerate the string.
+   */
+  if (st->this_frame_end.tv_sec != st->prev_frame_end.tv_sec)
+    {
+      double uprev_frame_end = (st->prev_frame_end.tv_sec +
+                                ((double) st->prev_frame_end.tv_usec
+                                 * 0.000001));
+      double uthis_frame_end = (st->this_frame_end.tv_sec +
+                                ((double) st->this_frame_end.tv_usec
+                                 * 0.000001));
+      double fps = st->frame_count / (uthis_frame_end - uprev_frame_end);
+      double idle = (((double) st->slept * 0.000001) /
+                     (uthis_frame_end - uprev_frame_end));
+      double load = 100 * (1 - idle);
+
+      if (load < 0) load = 0;  /* well that's obviously nonsense... */
+
+      st->prev_frame_end = st->this_frame_end;
+      st->frame_count = 0;
+      st->slept       = 0;
+      st->last_ifps   = fps;
+      st->last_fps    = fps;
+
+      sprintf (st->string, (polys 
+                            ? "FPS:   %.1f \nLoad:  %.1f%% "
+                            : "FPS:  %.1f \nLoad: %.1f%% "),
+               fps, load);
+
+      if (polys > 0)
+        {
+          const char *s = "";
+# if 0
+          if      (polys >= (1024 * 1024)) polys >>= 20, s = "M";
+          else if (polys >= 2048)          polys >>= 10, s = "K";
+# endif
+
+          strcat (st->string, "\nPolys: ");
+          if (polys >= 1000000)
+            sprintf (st->string + strlen(st->string), "%lu,%03lu,%03lu%s ",
+                     (polys / 1000000), ((polys / 1000) % 1000),
+                     (polys % 1000), s);
+          else if (polys >= 1000)
+            sprintf (st->string + strlen(st->string), "%lu,%03lu%s ",
+                     (polys / 1000), (polys % 1000), s);
+          else
+            sprintf (st->string + strlen(st->string), "%lu%s ", polys, s);
+        }
+
+      if (depth >= 0.0)
+        {
+          const char *s = "";
+          unsigned long ldepth = depth;
+# if 0
+          if (depth >= (1024 * 1024 * 1024))
+            ldepth = depth / (1024 * 1024 * 1024), s = "G";
+          else if (depth >= (1024 * 1024)) ldepth >>= 20, s = "M";
+          else if (depth >= 2048)          ldepth >>= 10, s = "K";
+# endif
+          strcat (st->string, "\nDepth: ");
+          if (ldepth >= 1000000000)
+            sprintf (st->string + strlen(st->string),
+                     "%lu,%03lu,%03lu,%03lu%s ",
+                     (ldepth / 1000000000),
+                     ((ldepth / 1000000) % 1000),
+                     ((ldepth / 1000) % 1000),
+                     (ldepth % 1000), s);
+          else if (ldepth >= 1000000)
+            sprintf (st->string + strlen(st->string), "%lu,%03lu,%03lu%s ",
+                     (ldepth / 1000000), ((ldepth / 1000) % 1000),
+                     (ldepth % 1000), s);
+          else if (ldepth >= 1000)
+            sprintf (st->string + strlen(st->string), "%lu,%03lu%s ",
+                     (ldepth / 1000), (ldepth % 1000), s);
+          else if (*s)
+            sprintf (st->string + strlen(st->string), "%lu%s ",
+                     ldepth, s);
+          else
+            {
+              int L;
+              sprintf (st->string + strlen(st->string), "%.1f", depth);
+              L = strlen (st->string);
+              /* Remove trailing ".0" in case depth is not a fraction. */
+              if (st->string[L-2] == '.' && st->string[L-1] == '0')
+                st->string[L-2] = 0;
+            }
+        }
+    }
+
+  return st->last_fps;
+}
+
+
+
+/* Width (and optionally height) of the string in pixels.
+ */
+static int
+string_width (XFontStruct *f, const char *c, int *height_ret)
+{
+  int x = 0;
+  int max_w = 0;
+  int h = f->ascent + f->descent;
+  while (*c)
+    {
+      int cc = *((unsigned char *) c);
+      if (*c == '\n')
+        {
+          if (x > max_w) max_w = x;
+          x = 0;
+          h += f->ascent + f->descent;
+        }
+      else
+        x += (f->per_char
+              ? f->per_char[cc-f->min_char_or_byte2].width
+              : f->min_bounds.rbearing);
+      c++;
+    }
+  if (x > max_w) max_w = x;
+  if (height_ret) *height_ret = h;
+
+  return max_w;
+}
+
+
+/* This function is used only in Xlib mode.  For GL mode, see glx/fps-gl.c.
+ */
+void
+fps_draw (fps_state *st)
+{
+  XWindowAttributes xgwa;
+  const char *string = st->string;
+  const char *s;
+  int x = st->x;
+  int y = st->y;
+  int lines = 1;
+  int lh = st->font->ascent + st->font->descent;
+
+  XGetWindowAttributes (st->dpy, st->window, &xgwa);
+
+  for (s = string; *s; s++) 
+    if (*s == '\n') lines++;
+
+  if (y < 0)
+    y = -y + (lines-1) * lh;
+  else
+    y = xgwa.height - y;
+
+  y -= lh * (lines-1) + st->font->descent;
+
+  /* clear the background */
+  if (st->clear_p)
+    {
+      int w, h;
+      w = string_width (st->font, string, &h);
+      XFillRectangle (st->dpy, st->window, st->erase_gc,
+                      x - st->font->descent,
+                      y - lh,
+                      w + 2*st->font->descent,
+                      h + 2*st->font->descent);
+    }
+
+  /* draw the text */
+  while (lines)
+    {
+      s = strchr (string, '\n');
+      if (! s) s = string + strlen(string);
+      XDrawString (st->dpy, st->window, st->draw_gc,
+                   x, y, string, (int) (s - string));
+      string = s;
+      string++;
+      lines--;
+      y += lh;
+    }
+}
diff --git a/screenhack/fps.h b/screenhack/fps.h
new file mode 100644 (file)
index 0000000..d3832c1
--- /dev/null
@@ -0,0 +1,35 @@
+/* fps, Copyright (c) 2001-2011 Jamie Zawinski <jwz@jwz.org>
+ * Draw a frames-per-second display (Xlib and OpenGL).
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifndef __XSCREENSAVER_FPS_H__
+# define __XSCREENSAVER_FPS_H__
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+typedef struct fps_state fps_state;
+
+extern fps_state *fps_init (Display *, Window);
+extern void fps_free (fps_state *);
+extern void fps_slept (fps_state *, unsigned long usecs);
+extern double fps_compute (fps_state *, unsigned long polys, double depth);
+extern void fps_draw (fps_state *);
+
+/* Doesn't really belong here, but close enough. */
+#ifdef HAVE_MOBILE
+  extern double current_device_rotation (void);
+#else
+# define current_device_rotation() (0)
+#endif
+
+#endif /* __XSCREENSAVER_FPS_H__ */
diff --git a/screenhack/fpsI.h b/screenhack/fpsI.h
new file mode 100644 (file)
index 0000000..552de9c
--- /dev/null
@@ -0,0 +1,40 @@
+/* fps, Copyright (c) 2001-2014 Jamie Zawinski <jwz@jwz.org>
+ * Draw a frames-per-second display (Xlib and OpenGL).
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifndef __XSCREENSAVER_FPSI_H__
+# define __XSCREENSAVER_FPSI_H__
+
+#include "fps.h"
+#undef HAVE_GLBITMAP
+
+
+struct fps_state {
+  Display *dpy;
+  Window window;
+  int x, y;
+  XFontStruct *font;
+  Bool clear_p;
+  char string[1024];
+
+  /* for glx/fps-gl.c */
+  void *gl_fps_data;
+
+  GC draw_gc, erase_gc;
+
+  int last_ifps;
+  double last_fps;
+  int frame_count;
+  unsigned long slept;
+  struct timeval prev_frame_end, this_frame_end;
+};
+
+#endif /* __XSCREENSAVER_FPSI_H__ */
diff --git a/screenhack/grabscreen.c b/screenhack/grabscreen.c
new file mode 100644 (file)
index 0000000..77fe3c9
--- /dev/null
@@ -0,0 +1,937 @@
+/* xscreensaver, Copyright (c) 1992-2016 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/* This file contains code for grabbing an image of the screen to hack its
+   bits.  This is a little tricky, since doing this involves the need to tell
+   the difference between drawing on the actual root window, and on the fake
+   root window used by the screensaver, since at this level the illusion 
+   breaks down...
+
+   The hacks themselves use utils/grabclient.c to invoke the
+   "xscreensaver-getimage" program as a sub-process.
+
+   On "real" X11 systems:
+
+       "driver/xscreensaver-getimage" runs the code in this file to grab
+       the X11 root window image as a Pixmap.
+
+   On MacOS systems running X11, which nobody does any more:
+
+       "driver/xscreensaver-getimage" runs the Perl script
+       "driver/xscreensaver-getimage-desktop", which in turn runs the MacOS
+       program "/usr/sbin/screencapture" to get the Mac desktop image as a
+       PNG file.
+
+   On MacOS systems running the native Cocoa build, or on iOS or Android
+   systems:
+
+       "driver/xscreensaver-getimage" is not used.  Instead, each saver's
+       "utils/grabclient.c" links against "OSX/grabclient-osx.m",
+       "OSX/grabclient-ios.m" or "jwxyz/jwxyz-android.c" to grab
+       screenshots directly without invoking a sub-process to do it.
+
+   See the comment at the top of utils/grabclient.c for a more detailed
+   explanation.
+ */
+
+#include "utils.h"
+#include "yarandom.h"
+
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+
+#ifdef HAVE_XMU
+# ifndef VMS
+#  include <X11/Xmu/WinUtil.h>
+# else  /* VMS */
+#  include <Xmu/WinUtil.h>
+# endif /* VMS */
+#endif
+
+#include "usleep.h"
+#include "colors.h"
+#include "grabscreen.h"
+#include "visual.h"
+#include "resources.h"
+
+#include "vroot.h"
+#undef RootWindowOfScreen
+#undef RootWindow
+#undef DefaultRootWindow
+
+
+#ifdef HAVE_READ_DISPLAY_EXTENSION
+# include <X11/extensions/readdisplay.h>
+  static Bool read_display (Screen *, Window, Pixmap, Bool);
+#endif /* HAVE_READ_DISPLAY_EXTENSION */
+
+
+static void copy_default_colormap_contents (Screen *, Colormap, Visual *);
+
+#ifdef HAVE_READ_DISPLAY_EXTENSION
+static void allocate_cubic_colormap (Screen *, Window, Visual *);
+void remap_image (Screen *, Window, Colormap, XImage *);
+#endif
+
+
+static Bool
+MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
+{
+  return (event->xany.type == MapNotify &&
+         event->xvisibility.window == (Window) window);
+}
+
+extern char *progname;
+Bool grab_verbose_p = False;
+
+void
+grabscreen_verbose(void)
+{
+  grab_verbose_p = True;
+}
+
+
+static void
+raise_window(Display *dpy, Window window, Bool dont_wait)
+{
+  if (grab_verbose_p)
+    fprintf(stderr, "%s: raising window 0x%08lX (%s)\n",
+            progname, (unsigned long) window,
+            (dont_wait ? "not waiting" : "waiting"));
+
+  if (! dont_wait)
+    {
+      XWindowAttributes xgwa;
+      XSizeHints hints;
+      long supplied = 0;
+      memset(&hints, 0, sizeof(hints));
+      XGetWMNormalHints(dpy, window, &hints, &supplied);
+      XGetWindowAttributes (dpy, window, &xgwa);
+      hints.x = xgwa.x;
+      hints.y = xgwa.y;
+      hints.width = xgwa.width;
+      hints.height = xgwa.height;
+      hints.flags |= (PPosition|USPosition|PSize|USSize);
+      XSetWMNormalHints(dpy, window, &hints);
+
+      XSelectInput (dpy, window, (xgwa.your_event_mask | StructureNotifyMask));
+    }
+
+  XMapRaised(dpy, window);
+
+  if (! dont_wait)
+    {
+      XEvent event;
+      XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
+      XSync (dpy, True);
+    }
+}
+
+
+static Bool
+xscreensaver_window_p (Display *dpy, Window window)
+{
+  Atom type;
+  int format;
+  unsigned long nitems, bytesafter;
+  unsigned char *version;
+  if (XGetWindowProperty (dpy, window,
+                         XInternAtom (dpy, "_SCREENSAVER_VERSION", False),
+                         0, 1, False, XA_STRING,
+                         &type, &format, &nitems, &bytesafter,
+                         &version)
+      == Success
+      && type != None)
+    return True;
+  return False;
+}
+
+
+
+/* Whether the given window is:
+   - the real root window;
+   - a direct child of the root window;
+   - a direct child of the window manager's decorations.
+ */
+Bool
+top_level_window_p (Screen *screen, Window window)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  Window root, parent, *kids;
+  unsigned int nkids;
+
+  if (!XQueryTree (dpy, window, &root, &parent, &kids, &nkids))
+    return False;
+
+  if (window == root)
+    return True;
+
+  /* If our direct parent is the real root window, then yes. */
+  if (parent == root)
+    return True;
+  else
+    {
+      Atom type = None;
+      int format;
+      unsigned long nitems, bytesafter;
+      unsigned char *data;
+
+      /* If our direct parent has the WM_STATE property, then it is a
+         window manager decoration -- yes.
+      */
+      if (XGetWindowProperty (dpy, window,
+                              XInternAtom (dpy, "WM_STATE", True),
+                              0, 0, False, AnyPropertyType,
+                              &type, &format, &nitems, &bytesafter,
+                              (unsigned char **) &data)
+          == Success
+          && type != None)
+        return True;
+    }
+
+  /* Else, no.  We're deep in a tree somewhere.
+   */
+  return False;
+}
+
+
+static Bool error_handler_hit_p = False;
+static XErrorHandler old_ehandler = 0;
+static int
+BadWindow_ehandler (Display *dpy, XErrorEvent *error)
+{
+  error_handler_hit_p = True;
+  if (error->error_code == BadWindow || error->error_code == BadDrawable)
+    return 0;
+  else if (!old_ehandler)
+    {
+      abort();
+      return 0;
+    }
+  else
+    return (*old_ehandler) (dpy, error);
+}
+
+
+/* XCopyArea seems not to work right on SGI O2s if you draw in SubwindowMode
+   on a window whose depth is not the maximal depth of the screen?  Or
+   something.  Anyway, things don't work unless we: use SubwindowMode for
+   the real root window (or a legitimate virtual root window); but do not
+   use SubwindowMode for the xscreensaver window.  I make no attempt to
+   explain.
+ */
+Bool
+use_subwindow_mode_p(Screen *screen, Window window)
+{
+  if (window != VirtualRootWindowOfScreen(screen))
+    return False;
+  else if (xscreensaver_window_p(DisplayOfScreen(screen), window))
+    return False;
+  else
+    return True;
+}
+
+
+/* Install the colormaps of all visible windows, deepest first.
+   This should leave the colormaps of the topmost windows installed
+   (if only N colormaps can be installed at a time, then only the
+   topmost N windows will be shown in the right colors.)
+ */
+static void
+install_screen_colormaps (Screen *screen)
+{
+  unsigned int i;
+  Display *dpy = DisplayOfScreen (screen);
+  Window real_root;
+  Window parent, *kids = 0;
+  unsigned int nkids = 0;
+
+  XSync (dpy, False);
+  old_ehandler = XSetErrorHandler (BadWindow_ehandler);
+  error_handler_hit_p = False;
+
+  real_root = XRootWindowOfScreen (screen);  /* not vroot */
+  if (XQueryTree (dpy, real_root, &real_root, &parent, &kids, &nkids))
+    for (i = 0; i < nkids; i++)
+      {
+       XWindowAttributes xgwa;
+       Window client;
+#ifdef HAVE_XMU
+       /* #### need to put XmuClientWindow() in xmu.c, sigh... */
+       if (! (client = XmuClientWindow (dpy, kids[i])))
+#endif
+         client = kids[i];
+       xgwa.colormap = 0;
+       XGetWindowAttributes (dpy, client, &xgwa);
+       if (xgwa.colormap && xgwa.map_state == IsViewable)
+         XInstallColormap (dpy, xgwa.colormap);
+      }
+  XInstallColormap (dpy, DefaultColormapOfScreen (screen));
+  XSync (dpy, False);
+  XSetErrorHandler (old_ehandler);
+  XSync (dpy, False);
+
+  if (kids)
+    XFree ((char *) kids);
+}
+
+
+void
+grab_screen_image_internal (Screen *screen, Window window)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  XWindowAttributes xgwa;
+  Window real_root;
+  Bool root_p;
+  Bool saver_p;
+  Bool grab_mouse_p = False;
+  int unmap_time = 0;
+
+  real_root = XRootWindowOfScreen (screen);  /* not vroot */
+  root_p = (window == real_root);
+  saver_p = xscreensaver_window_p (dpy, window);
+
+  XGetWindowAttributes (dpy, window, &xgwa);
+  screen = xgwa.screen;
+
+  if (saver_p)
+    /* I think this is redundant, but just to be safe... */
+    root_p = False;
+
+  if (saver_p)
+    /* The only time grabbing the mouse is important is if this program
+       is being run while the saver is locking the screen. */
+    grab_mouse_p = True;
+
+  if (!root_p)
+    {
+      double unmap = 0;
+      if (saver_p)
+       {
+         unmap = get_float_resource(dpy, "grabRootDelay", "Seconds");
+         if (unmap <= 0.00001 || unmap > 20) unmap = 2.5;
+       }
+      else
+       {
+         unmap = get_float_resource(dpy, "grabWindowDelay", "Seconds");
+         if (unmap <= 0.00001 || unmap > 20) unmap = 0.66;
+       }
+      unmap_time = unmap * 100000;
+    }
+
+  if (grab_verbose_p)
+    {
+      fprintf(stderr,
+              "\n%s: window 0x%08lX root: %d saver: %d grab: %d wait: %.1f\n",
+              progname, (unsigned long) window,
+              root_p, saver_p, grab_mouse_p, ((double)unmap_time)/1000000.0);
+
+      fprintf(stderr, "%s: ", progname);
+      describe_visual(stderr, screen, xgwa.visual, False);
+      fprintf (stderr, "\n");
+    }
+
+
+  if (!root_p && !top_level_window_p (screen, window))
+    {
+      if (grab_verbose_p)
+        fprintf (stderr, "%s: not a top-level window: 0x%08lX: not grabbing\n",
+                 progname, (unsigned long) window);
+      return;
+    }
+
+
+  if (!root_p)
+    XSetWindowBackgroundPixmap (dpy, window, None);
+
+  if (grab_mouse_p)
+    {
+      /* prevent random viewer of the screen saver (locker) from messing
+        with windows.   We don't check whether it succeeded, because what
+        are our options, really... */
+      XGrabPointer (dpy, real_root, True, ButtonPressMask|ButtonReleaseMask,
+                   GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
+      XGrabKeyboard (dpy, real_root, True, GrabModeSync, GrabModeAsync,
+                    CurrentTime);
+    }
+
+  if (unmap_time > 0)
+    {
+      XUnmapWindow (dpy, window);
+      install_screen_colormaps (screen);
+      XSync (dpy, True);
+      usleep(unmap_time); /* wait for everyone to swap in and handle exposes */
+    }
+
+  if (!root_p)
+    {
+#ifdef HAVE_READ_DISPLAY_EXTENSION
+      if (! read_display(screen, window, 0, saver_p))
+#endif /* HAVE_READ_DISPLAY_EXTENSION */
+       {
+#ifdef HAVE_READ_DISPLAY_EXTENSION
+          if (grab_verbose_p)
+            fprintf(stderr, "%s: read_display() failed\n", progname);
+#endif /* HAVE_READ_DISPLAY_EXTENSION */
+
+         copy_default_colormap_contents (screen, xgwa.colormap, xgwa.visual);
+         raise_window(dpy, window, saver_p);
+
+          /* Generally it's bad news to call XInstallColormap() explicitly,
+             but this file does a lot of sleazy stuff already...  This is to
+             make sure that the window's colormap is installed, even in the
+             case where the window is OverrideRedirect. */
+          if (xgwa.colormap) XInstallColormap (dpy, xgwa.colormap);
+          XSync (dpy, False);
+       }
+    }
+  else  /* root_p */
+    {
+      Pixmap pixmap;
+      pixmap = XCreatePixmap(dpy, window, xgwa.width, xgwa.height, xgwa.depth);
+
+#ifdef HAVE_READ_DISPLAY_EXTENSION
+      if (! read_display(screen, window, pixmap, True))
+#endif
+       {
+         Window real_root = XRootWindowOfScreen (screen); /* not vroot */
+         XGCValues gcv;
+         GC gc;
+
+#ifdef HAVE_READ_DISPLAY_EXTENSION
+          if  (grab_verbose_p)
+            fprintf(stderr, "%s: read_display() failed\n", progname);
+#endif /* HAVE_READ_DISPLAY_EXTENSION */
+
+         copy_default_colormap_contents (screen, xgwa.colormap, xgwa.visual);
+
+         gcv.function = GXcopy;
+         gcv.subwindow_mode = IncludeInferiors;
+         gc = XCreateGC (dpy, window, GCFunction | GCSubwindowMode, &gcv);
+         XCopyArea (dpy, real_root, pixmap, gc,
+                    xgwa.x, xgwa.y, xgwa.width, xgwa.height, 0, 0);
+         XFreeGC (dpy, gc);
+       }
+      XSetWindowBackgroundPixmap (dpy, window, pixmap);
+      XFreePixmap (dpy, pixmap);
+    }
+
+  if (grab_verbose_p)
+    fprintf (stderr, "%s: grabbed %d bit screen image to %swindow.\n",
+             progname, xgwa.depth,
+             (root_p ? "real root " : ""));
+
+  if (grab_mouse_p)
+    {
+      XUngrabPointer (dpy, CurrentTime);
+      XUngrabKeyboard (dpy, CurrentTime);
+    }
+
+  XSync (dpy, True);
+}
+
+
+/* When we are grabbing and manipulating a screen image, it's important that
+   we use the same colormap it originally had.  So, if the screensaver was
+   started with -install, we need to copy the contents of the default colormap
+   into the screensaver's colormap.
+ */
+static void
+copy_default_colormap_contents (Screen *screen,
+                               Colormap to_cmap,
+                               Visual *to_visual)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  Visual *from_visual = DefaultVisualOfScreen (screen);
+  Colormap from_cmap = XDefaultColormapOfScreen (screen);
+
+  XColor *old_colors, *new_colors;
+  unsigned long *pixels;
+  XVisualInfo vi_in, *vi_out;
+  int out_count;
+  int from_cells, to_cells, max_cells, got_cells;
+  int i;
+
+  if (from_cmap == to_cmap)
+    return;
+
+  vi_in.screen = XScreenNumberOfScreen (screen);
+  vi_in.visualid = XVisualIDFromVisual (from_visual);
+  vi_out = XGetVisualInfo (dpy, VisualScreenMask|VisualIDMask,
+                          &vi_in, &out_count);
+  if (! vi_out) abort ();
+  from_cells = vi_out [0].colormap_size;
+  XFree ((char *) vi_out);
+
+  vi_in.screen = XScreenNumberOfScreen (screen);
+  vi_in.visualid = XVisualIDFromVisual (to_visual);
+  vi_out = XGetVisualInfo (dpy, VisualScreenMask|VisualIDMask,
+                          &vi_in, &out_count);
+  if (! vi_out) abort ();
+  to_cells = vi_out [0].colormap_size;
+  XFree ((char *) vi_out);
+
+  max_cells = (from_cells > to_cells ? to_cells : from_cells);
+
+  old_colors = (XColor *) calloc (sizeof (XColor), max_cells);
+  new_colors = (XColor *) calloc (sizeof (XColor), max_cells);
+  pixels = (unsigned long *) calloc (sizeof (unsigned long), max_cells);
+  for (i = 0; i < max_cells; i++)
+    old_colors[i].pixel = i;
+  XQueryColors (dpy, from_cmap, old_colors, max_cells);
+
+  got_cells = max_cells;
+  allocate_writable_colors (screen, to_cmap, pixels, &got_cells);
+
+  if (grab_verbose_p && got_cells != max_cells)
+    fprintf(stderr, "%s: got only %d of %d cells\n", progname,
+           got_cells, max_cells);
+
+  if (got_cells <= 0)                                   /* we're screwed */
+    ;
+  else if (got_cells == max_cells &&                    /* we're golden */
+          from_cells == to_cells)
+    XStoreColors (dpy, to_cmap, old_colors, got_cells);
+  else                                                  /* try to cope... */
+    {
+      for (i = 0; i < got_cells; i++)
+       {
+         XColor *c = old_colors + i;
+         int j;
+         for (j = 0; j < got_cells; j++)
+           if (pixels[j] == c->pixel)
+             {
+               /* only store this color value if this is one of the pixels
+                  we were able to allocate. */
+               XStoreColors (dpy, to_cmap, c, 1);
+               break;
+             }
+       }
+    }
+
+
+  if (grab_verbose_p)
+    fprintf(stderr, "%s: installing copy of default colormap\n", progname);
+
+  free (old_colors);
+  free (new_colors);
+  free (pixels);
+}
+
+
+\f
+/* The SGI ReadDisplay extension.
+   This extension lets you get back a 24-bit image of the screen, taking into
+   account the colors with which all windows are *currently* displayed, even
+   if those windows have different visuals.  Without this extension, presence
+   of windows with different visuals or colormaps will result in technicolor
+   when one tries to grab the screen image.
+ */
+
+#ifdef HAVE_READ_DISPLAY_EXTENSION
+
+static Bool
+read_display (Screen *screen, Window window, Pixmap into_pixmap,
+             Bool dont_wait)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  XWindowAttributes xgwa;
+  int rd_event_base = 0;
+  int rd_error_base = 0;
+  unsigned long hints = 0;
+  XImage *image = 0;
+  XGCValues gcv;
+  int class;
+  GC gc;
+  Bool remap_p = False;
+
+  /* Check to see if the server supports the extension, and bug out if not.
+   */
+  if (! XReadDisplayQueryExtension (dpy, &rd_event_base, &rd_error_base))
+    {
+      if (grab_verbose_p)
+        fprintf(stderr, "%s: no XReadDisplay extension\n", progname);
+      return False;
+    }
+
+  /* If this isn't a visual we know how to handle, bug out.  We handle:
+      = TrueColor in depths 8, 12, 15, 16, and 32;
+      = PseudoColor and DirectColor in depths 8 and 12.
+   */
+  XGetWindowAttributes(dpy, window, &xgwa);
+  class = visual_class (screen, xgwa.visual);
+  if (class == TrueColor)
+    {
+      if (xgwa.depth != 8  && xgwa.depth != 12 && xgwa.depth != 15 &&
+          xgwa.depth != 16 && xgwa.depth != 24 && xgwa.depth != 32)
+        {
+          if (grab_verbose_p)
+            fprintf(stderr, "%s: TrueColor depth %d unsupported\n",
+                    progname, xgwa.depth);
+          return False;
+        }
+    }
+  else if (class == PseudoColor || class == DirectColor)
+    {
+      if (xgwa.depth != 8 && xgwa.depth != 12)
+        {
+          if (grab_verbose_p)
+            fprintf(stderr, "%s: Pseudo/DirectColor depth %d unsupported\n",
+                    progname, xgwa.depth);
+          return False;
+        }
+      else
+       /* Allocate a TrueColor-like spread of colors for the image. */
+       remap_p = True;
+    }
+
+
+  /* Try and read the screen.
+   */
+  hints = (XRD_TRANSPARENT | XRD_READ_POINTER);
+  image = XReadDisplay (dpy, window, xgwa.x, xgwa.y, xgwa.width, xgwa.height,
+                       hints, &hints);
+  if (!image)
+    {
+      if (grab_verbose_p)
+        fprintf(stderr, "%s: XReadDisplay() failed\n", progname);
+      return False;
+    }
+  if (!image->data)
+    {
+      if (grab_verbose_p)
+        fprintf(stderr, "%s: XReadDisplay() returned no data\n", progname);
+      XDestroyImage(image);
+      return False;
+    }
+
+  /* XReadDisplay tends to LIE about the depth of the image it read.
+     It is returning an XImage which has `depth' and `bits_per_pixel'
+     confused!
+
+     That is, on a 24-bit display, where all visuals claim depth 24, and
+     where XGetImage would return an XImage with depth 24, and where
+     XPutImage will get a BadMatch with images that are not depth 24,
+     XReadDisplay is returning images with depth 32!  Fuckwits!
+
+     So if the visual is of depth 24, but the image came back as depth 32,
+     hack it to be 24 lest we get a BadMatch from XPutImage.
+
+     I wonder what happens on an 8-bit SGI...  Probably it still returns
+     an image claiming depth 32?  Certainly it can't be 8.  So, let's just
+     smash it to 32...
+   */
+  if (image->depth == 32 /* && xgwa.depth == 24 */ )
+    image->depth = 24;
+
+  /* If the visual of the window/pixmap into which we're going to draw is
+     less deep than the screen itself, then we need to convert the grabbed bits
+     to match the depth by clipping off the less significant bit-planes of each
+     color component.
+   */
+  if (image->depth > xgwa.depth)
+    {
+      int x, y;
+      /* We use the same image->data in both images -- that's ok, because
+        since we're reading from B and writing to A, and B uses more bytes
+        per pixel than A, the write pointer won't overrun the read pointer.
+       */
+      XImage *image2 = XCreateImage (dpy, xgwa.visual, xgwa.depth,
+                                    ZPixmap, 0, image->data,
+                                    xgwa.width, xgwa.height,
+                                    8, 0);
+      if (!image2)
+        {
+          if (grab_verbose_p)
+            fprintf(stderr, "%s: out of memory?\n", progname);
+          return False;
+        }
+
+      if (grab_verbose_p)
+        fprintf(stderr, "%s: converting from depth %d to depth %d\n",
+                progname, image->depth, xgwa.depth);
+
+      for (y = 0; y < image->height; y++)
+       for (x = 0; x < image->width; x++)
+         {
+           /* #### really these shift values should be determined from the
+              mask values -- but that's a pain in the ass, and anyway,
+              this is an SGI-specific extension so hardcoding assumptions
+              about the SGI server's behavior isn't *too* heinous... */
+           unsigned long pixel = XGetPixel(image, x, y);
+           unsigned int r = (pixel & image->red_mask);
+           unsigned int g = (pixel & image->green_mask) >> 8;
+           unsigned int b = (pixel & image->blue_mask) >> 16;
+
+           if (xgwa.depth == 8)
+             pixel = ((r >> 5) | ((g >> 5) << 3) | ((b >> 6) << 6));
+           else if (xgwa.depth == 12)
+             pixel = ((r >> 4) | ((g >> 4) << 4) | ((b >> 4) << 8));
+           else if (xgwa.depth == 16 || xgwa.depth == 15)
+              /* Gah! I don't understand why these are in the other order. */
+             pixel = (((r >> 3) << 10) | ((g >> 3) << 5) | ((b >> 3)));
+           else
+             abort();
+
+           XPutPixel(image2, x, y, pixel);
+         }
+      image->data = 0;
+      XDestroyImage(image);
+      image = image2;
+    }
+
+  if (remap_p)
+    {
+      allocate_cubic_colormap (screen, window, xgwa.visual);
+      remap_image (screen, window, xgwa.colormap, image);
+    }
+
+  /* Now actually put the bits into the window or pixmap -- note the design
+     bogosity of this extension, where we've been forced to take 24 bit data
+     from the server to the client, and then push it back from the client to
+     the server, *without alteration*.  We should have just been able to tell
+     the server, "put a screen image in this drawable", instead of having to
+     go through the intermediate step of converting it to an Image.  Geez.
+     (Assuming that the window is of screen depth; we happen to handle less
+     deep windows, but that's beside the point.)
+   */
+  gcv.function = GXcopy;
+  gc = XCreateGC (dpy, window, GCFunction, &gcv);
+
+  if (into_pixmap)
+    {
+      gcv.function = GXcopy;
+      gc = XCreateGC (dpy, into_pixmap, GCFunction, &gcv);
+      XPutImage (dpy, into_pixmap, gc, image, 0, 0, 0, 0,
+                xgwa.width, xgwa.height);
+    }
+  else
+    {
+      gcv.function = GXcopy;
+      gc = XCreateGC (dpy, window, GCFunction, &gcv);
+
+      /* Ok, now we'll be needing that window on the screen... */
+      raise_window(dpy, window, dont_wait);
+
+      /* Plop down the bits... */
+      XPutImage (dpy, window, gc, image, 0, 0, 0, 0, xgwa.width, xgwa.height);
+    }
+  XFreeGC (dpy, gc);
+
+  if (image->data)
+    {
+      free(image->data);
+      image->data = 0;
+    }
+  XDestroyImage(image);
+
+  return True;
+}
+#endif /* HAVE_READ_DISPLAY_EXTENSION */
+
+
+#ifdef HAVE_READ_DISPLAY_EXTENSION
+
+/* Makes and installs a colormap that makes a PseudoColor or DirectColor
+   visual behave like a TrueColor visual of the same depth.
+
+   #### Duplicated in driver/xscreensaver-getimage.c
+ */
+static void
+allocate_cubic_colormap (Screen *screen, Window window, Visual *visual)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  XWindowAttributes xgwa;
+  Colormap cmap;
+  int nr, ng, nb, cells;
+  int r, g, b;
+  int depth;
+  XColor colors[4097];
+  int i;
+
+  XGetWindowAttributes (dpy, window, &xgwa);
+  cmap = xgwa.colormap;
+  depth = visual_depth (screen, visual);
+
+  switch (depth)
+    {
+    case 8:  nr = 3; ng = 3; nb = 2; cells = 256;  break;
+    case 12: nr = 4; ng = 4; nb = 4; cells = 4096; break;
+    default: abort(); break;
+    }
+
+  memset(colors, 0, sizeof(colors));
+  for (r = 0; r < (1 << nr); r++)
+    for (g = 0; g < (1 << ng); g++)
+      for (b = 0; b < (1 << nb); b++)
+       {
+         i = (r | (g << nr) | (b << (nr + ng)));
+         colors[i].pixel = i;
+          colors[i].flags = DoRed|DoGreen|DoBlue;
+         if (depth == 8)
+           {
+             colors[i].red   = ((r << 13) | (r << 10) | (r << 7) |
+                                (r <<  4) | (r <<  1));
+             colors[i].green = ((g << 13) | (g << 10) | (g << 7) |
+                                (g <<  4) | (g <<  1));
+             colors[i].blue  = ((b << 14) | (b << 12) | (b << 10) |
+                                (b <<  8) | (b <<  6) | (b <<  4) |
+                                (b <<  2) | b);
+           }
+         else
+           {
+             colors[i].red   = (r << 12) | (r << 8) | (r << 4) | r;
+             colors[i].green = (g << 12) | (g << 8) | (g << 4) | g;
+             colors[i].blue  = (b << 12) | (b << 8) | (b << 4) | b;
+           }
+       }
+
+  {
+    int j;
+    int allocated = 0;
+    int interleave = cells / 8;  /* skip around, rather than allocating in
+                                    order, so that we get better coverage if
+                                    we can't allocated all of them. */
+    for (j = 0; j < interleave; j++)
+      for (i = 0; i < cells; i += interleave)
+        if (XAllocColor (dpy, cmap, &colors[i + j]))
+          allocated++;
+
+    if (grab_verbose_p)
+      fprintf (stderr, "%s: allocated %d of %d colors for cubic map\n",
+               progname, allocated, cells);
+  }
+}
+
+/* Find the pixel index that is closest to the given color
+   (using linear distance in RGB space -- which is far from the best way.)
+
+   #### Duplicated in driver/xscreensaver-getimage.c
+ */
+static unsigned long
+find_closest_pixel (XColor *colors, int ncolors,
+                    unsigned long r, unsigned long g, unsigned long b)
+{
+  unsigned long distance = ~0;
+  int i, found = 0;
+
+  if (ncolors == 0)
+    abort();
+  for (i = 0; i < ncolors; i++)
+    {
+      unsigned long d;
+      int rd, gd, bd;
+
+      rd = r - colors[i].red;
+      gd = g - colors[i].green;
+      bd = b - colors[i].blue;
+      if (rd < 0) rd = -rd;
+      if (gd < 0) gd = -gd;
+      if (bd < 0) bd = -bd;
+      d = (rd << 1) + (gd << 2) + bd;
+      
+      if (d < distance)
+       {
+         distance = d;
+         found = i;
+          if (distance == 0)
+              break;
+       }
+    }
+
+  return found;
+}
+
+
+/* Given an XImage with 8-bit or 12-bit RGB data, convert it to be 
+   displayable with the given X colormap.  The farther from a perfect
+   color cube the contents of the colormap are, the lossier the 
+   transformation will be.  No dithering is done.
+
+   #### Duplicated in driver/xscreensaver-getimage.c
+ */
+void
+remap_image (Screen *screen, Window window, Colormap cmap, XImage *image)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  unsigned long map[4097];
+  int x, y, i;
+  int cells;
+  XColor colors[4097];
+
+  if (image->depth == 8)
+    cells = 256;
+  else if (image->depth == 12)
+    cells = 4096;
+  else
+    abort();
+
+  memset(map,    -1, sizeof(*map));
+  memset(colors, -1, sizeof(*colors));
+
+  for (i = 0; i < cells; i++)
+    colors[i].pixel = i;
+  XQueryColors (dpy, cmap, colors, cells);
+
+  if (grab_verbose_p)
+    fprintf(stderr, "%s: building table for %d bit image\n",
+            progname, image->depth);
+
+  for (i = 0; i < cells; i++)
+    {
+      unsigned short r, g, b;
+
+      if (cells == 256)
+        {
+          /* "RRR GGG BB" In an 8 bit map.  Convert that to
+             "RRR RRR RR" "GGG GGG GG" "BB BB BB BB" to give
+             an even spread. */
+          r = (i & 0x07);
+          g = (i & 0x38) >> 3;
+          b = (i & 0xC0) >> 6;
+
+          r = ((r << 13) | (r << 10) | (r << 7) | (r <<  4) | (r <<  1));
+          g = ((g << 13) | (g << 10) | (g << 7) | (g <<  4) | (g <<  1));
+          b = ((b << 14) | (b << 12) | (b << 10) | (b <<  8) |
+               (b <<  6) | (b <<  4) | (b <<  2) | b);
+        }
+      else
+        {
+          /* "RRRR GGGG BBBB" In a 12 bit map.  Convert that to
+             "RRRR RRRR" "GGGG GGGG" "BBBB BBBB" to give an even
+             spread. */
+          r = (i & 0x00F);
+          g = (i & 0x0F0) >> 4;
+          b = (i & 0xF00) >> 8;
+
+          r = (r << 12) | (r << 8) | (r << 4) | r;
+          g = (g << 12) | (g << 8) | (g << 4) | g;
+          b = (b << 12) | (b << 8) | (b << 4) | b;
+        }
+
+      map[i] = find_closest_pixel (colors, cells, r, g, b);
+    }
+
+  if (grab_verbose_p)
+    fprintf(stderr, "%s: remapping colors in %d bit image\n",
+            progname, image->depth);
+
+  for (y = 0; y < image->height; y++)
+    for (x = 0; x < image->width; x++)
+      {
+        unsigned long pixel = XGetPixel(image, x, y);
+        if (pixel >= cells) abort();
+        XPutPixel(image, x, y, map[pixel]);
+      }
+}
+
+
+#endif /* HAVE_READ_DISPLAY_EXTENSION */
diff --git a/screenhack/grabscreen.h b/screenhack/grabscreen.h
new file mode 100644 (file)
index 0000000..a17e671
--- /dev/null
@@ -0,0 +1,109 @@
+/* xscreensaver, Copyright (c) 1992-2014 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifndef __GRABSCREEN_H__
+#define __GRABSCREEN_H__
+
+/* This will write an image onto the given Drawable.
+   The Drawable (arg 3) may be a Window or a Pixmap.
+
+   The Window must be the top-level window.  The image *may or may not*
+   be written to the window, though it will definitely be written to
+   the drawable.  It's fine for args 2 and 3 to be the same window, or
+   for arg 2 to be a Window, and arg 3 to be a Pixmap.
+
+   The loaded image might be from a file, or from a screen shot of the
+   desktop, or from the system's video input, depending on user
+   preferences.
+
+   When the callback is called, the image data will have been loaded
+   into the given drawable.  Copy `name' if you want to keep it.
+
+   If it is from a file, then the `filename' argument will be the name
+   of the file.  It may be NULL.  If you want to keep this string, copy it.
+
+   The size and position of the image is in the `geometry' arg.
+   The image will generally have been scaled up to fit the window, but
+   if a loaded file had a different aspect ratio than the window, it
+   will have been centered, and the returned coords will describe that.
+
+   Many colors may be allocated from the window's colormap.
+ */
+extern void load_image_async (Screen *, Window, Drawable,
+                              void (*callback) (Screen *, Window,
+                                                Drawable,
+                                                const char *name,
+                                                XRectangle *geometry,
+                                                void *closure),
+                              void *closure);
+
+/* A utility wrapper around load_image_async() that is simpler if you
+   are only loading a single image at a time: just keep calling it
+   periodically until it returns NULL.  When it does, the image has
+   been loaded.
+ */
+typedef struct async_load_state async_load_state;
+extern async_load_state *load_image_async_simple (async_load_state *,
+                                                  Screen *,
+                                                  Window top_level,
+                                                  Drawable target, 
+                                                  char **filename_ret,
+                                                  XRectangle *geometry_ret);
+
+
+/* Whether one should use GCSubwindowMode when drawing on this window
+   (assuming a screen image has been grabbed onto it.)  Yes, this is a
+   total kludge. */
+extern Bool use_subwindow_mode_p(Screen *screen, Window window);
+
+/* Whether the given window is:
+   - the real root window;
+   - the virtual root window;
+   - a direct child of the root window;
+   - a direct child of the window manager's decorations.
+ */
+extern Bool top_level_window_p(Screen *screen, Window window);
+
+
+/* Don't call this: this is for the "xscreensaver-getimage" program only. */
+extern void grab_screen_image_internal (Screen *, Window);
+
+/* Don't use these: this is how "xscreensaver-getimage" and "grabclient.c"
+   pass the file name around. */
+#define XA_XSCREENSAVER_IMAGE_FILENAME "_SCREENSAVER_IMAGE_FILENAME"
+#define XA_XSCREENSAVER_IMAGE_GEOMETRY "_SCREENSAVER_IMAGE_GEOMETRY"
+
+/* For debugging: turn on verbosity. */
+extern void grabscreen_verbose (void);
+
+#ifdef HAVE_JWXYZ
+/* Don't use these: internal interface of grabclient.c. */
+extern Bool osx_grab_desktop_image (Screen *, Window, Drawable,
+                                    XRectangle *geom_ret);
+extern Bool osx_load_image_file (Screen *, Window, Drawable,
+                                 const char *filename, XRectangle *geom_ret);
+#endif /* HAVE_JWXYZ */
+
+#ifdef HAVE_IPHONE
+extern void ios_load_random_image (void (*callback) (void *uiimage,
+                                                     const char *filename,
+                                                     int w, int h,
+                                                     void *closure),
+                                   void *closure,
+                                   int width, int height);
+#endif /* HAVE_IPHONE */
+
+#ifdef HAVE_ANDROID
+char *jwxyz_draw_random_image (Display *dpy,  /* utils/grabclient.c */
+                               Drawable drawable, GC gc);
+#endif
+
+#endif /* __GRABSCREEN_H__ */
diff --git a/screenhack/hsv.c b/screenhack/hsv.c
new file mode 100644 (file)
index 0000000..cf1cc8d
--- /dev/null
@@ -0,0 +1,81 @@
+/* xscreensaver, Copyright (c) 1992, 1997 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/* This file contains some utility routines for randomly picking the colors
+   to hack the screen with.
+ */
+
+#include "utils.h"
+#include "hsv.h"
+
+void
+hsv_to_rgb (int h, double s, double v,
+           unsigned short *r, unsigned short *g, unsigned short *b)
+{
+  double H, S, V, R, G, B;
+  double p1, p2, p3;
+  double f;
+  int i;
+
+  if (s < 0) s = 0;
+  if (v < 0) v = 0;
+  if (s > 1) s = 1;
+  if (v > 1) v = 1;
+
+  S = s; V = v;
+  H = (h % 360) / 60.0;
+  i = H;
+  f = H - i;
+  p1 = V * (1 - S);
+  p2 = V * (1 - (S * f));
+  p3 = V * (1 - (S * (1 - f)));
+  if     (i == 0) { R = V;  G = p3; B = p1; }
+  else if (i == 1) { R = p2; G = V;  B = p1; }
+  else if (i == 2) { R = p1; G = V;  B = p3; }
+  else if (i == 3) { R = p1; G = p2; B = V;  }
+  else if (i == 4) { R = p3; G = p1; B = V;  }
+  else            { R = V;  G = p1; B = p2; }
+  *r = R * 65535;
+  *g = G * 65535;
+  *b = B * 65535;
+}
+
+void
+rgb_to_hsv (unsigned short r, unsigned short g, unsigned short b,
+           int *h, double *s, double *v)
+{
+  double R, G, B, H, S, V;
+  double cmax, cmin;
+  double cmm;
+  int imax;
+  R = ((double) r) / 65535.0;
+  G = ((double) g) / 65535.0;
+  B = ((double) b) / 65535.0;
+  cmax = R; cmin = G; imax = 1;
+  if  ( cmax < G ) { cmax = G; cmin = R; imax = 2; }
+  if  ( cmax < B ) { cmax = B; imax = 3; }
+  if  ( cmin > B ) { cmin = B; }
+  cmm = cmax - cmin;
+  V = cmax;
+  if (cmm == 0)
+    S = H = 0;
+  else
+    {
+      S = cmm / cmax;
+      if       (imax == 1)    H =       (G - B) / cmm;
+      else  if (imax == 2)    H = 2.0 + (B - R) / cmm;
+      else /*if (imax == 3)*/ H = 4.0 + (R - G) / cmm;
+      if (H < 0) H += 6.0;
+    }
+  *h = (H * 60.0);
+  *s = S;
+  *v = V;
+}
diff --git a/screenhack/hsv.h b/screenhack/hsv.h
new file mode 100644 (file)
index 0000000..e0fdfb0
--- /dev/null
@@ -0,0 +1,27 @@
+/* xscreensaver, Copyright (c) 1992, 1997 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifndef __HSV_H__
+#define __HSV_H__
+
+/* Converts between RGB and HSV color spaces.
+   R, G, and B are in the range 0 - 65535;
+   H is in the range 0 - 360;
+   S and V are in the range 0.0 - 1.0.
+ */
+extern void hsv_to_rgb (int h, double s, double v,
+                       unsigned short *r,
+                       unsigned short *g,
+                       unsigned short *b);
+extern void rgb_to_hsv (unsigned short r, unsigned short g, unsigned short b,
+                       int *h, double *s, double *v);
+
+#endif /* __HSV_H__ */
diff --git a/screenhack/minixpm.c b/screenhack/minixpm.c
new file mode 100644 (file)
index 0000000..997e628
--- /dev/null
@@ -0,0 +1,251 @@
+/* xscreensaver, Copyright (c) 2001-2014 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/* I implemented this subset of libXPM here because I don't want the
+   xscreensaver daemon to depend on libXPM for two reasons: first,
+   because I want the logo to show up even if libXPM is not installed
+   on the system; and second, I don't want to have to security-audit
+   libXPM.  The fewer libraries that are linked into the xscreensaver
+   daemon, the more likely to be secure it is.
+
+   Also, the Cocoa port uses this code since libXPM isn't available
+   by default on MacOS.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifdef HAVE_JWXYZ
+# include "jwxyz.h"
+#else  /* real Xlib */
+# include <X11/Xlib.h>
+# include <X11/Xutil.h>
+#endif /* !HAVE_JWXYZ */
+
+#include "minixpm.h"
+
+extern const char *progname;
+
+static Bool
+bigendian (void)
+{
+  union { int i; char c[sizeof(int)]; } u;
+  u.i = 1;
+  return !u.c[0];
+}
+
+static const char hex[128] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                              0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
+                              0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
+                              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                              0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
+                              0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+XImage *
+minixpm_to_ximage (Display *dpy, Visual *visual, Colormap colormap, int depth,
+                   unsigned long transparent_color,
+                   const char * const * data,
+                   int *width_ret, int *height_ret,
+                   unsigned long **pixels_ret, int *npixels_ret,
+                   unsigned char **mask_ret)
+{
+  int w, w8, h, ncolors, nbytes;
+  char c;
+  int x, y, i, pixel_count;
+  struct {
+    char byte;
+    int cr; int cg; int cb;
+    int mr; int mg; int mb;
+  } cmap[256];
+  unsigned char rmap[256];
+
+  unsigned long *pixels;
+  XImage *ximage = 0;
+  
+  memset (cmap, 0, sizeof(cmap)); /* avoid warnings */
+
+  if (4 != sscanf ((const char *) *data,
+                   "%d %d %d %d %c", &w, &h, &ncolors, &nbytes, &c)) {
+    fprintf (stderr, "%s: unparsable XPM header\n", progname);
+    abort();
+  }
+
+  if (ncolors < 1 || ncolors > 255) {
+    fprintf (stderr, "%s: XPM: ncolors is %d\n", progname, ncolors);
+    abort();
+  }
+  if (nbytes != 1) {
+    fprintf (stderr, "%s: %d-byte XPM files not supported\n",
+             progname, nbytes);
+    abort();
+  }
+  data++;
+
+  for (i = 0; i < ncolors; i++)
+    {
+      const char *line = *data;
+      cmap[i].byte = *line++;
+      while (*line)
+        {
+          int r, g, b;
+          char which;
+          while (*line == ' ' || *line == '\t')
+            line++;
+          which = *line;
+          if (!which) continue;  /* whitespace at end of line */
+          line++;
+          if (which != 'c' && which != 'm') {
+            fprintf (stderr, "%s: unknown XPM pixel type '%c' in \"%s\"\n",
+                     progname, which, *data);
+            abort();
+          }
+          while (*line == ' ' || *line == '\t')
+            line++;
+          if (!strncasecmp(line, "None", 4))
+            {
+              r = g = b = -1;
+              line += 4;
+            }
+          else if (!strncasecmp(line, "white", 5))
+            {
+              r = g = b = 255;
+              line += 5;
+            }
+          else if (!strncasecmp(line, "black", 5))
+            {
+              r = g = b = 0;
+              line += 5;
+            }
+          else
+            {
+              if (*line != '#') {
+                fprintf (stderr, "%s: unparsable XPM color spec: \"%s\"\n",
+                         progname, line);
+                abort();
+              }
+              if (*line == '#')
+                line++;
+              r = (hex[(int) line[0]] << 4) | hex[(int) line[1]]; line += 2;
+              g = (hex[(int) line[0]] << 4) | hex[(int) line[1]]; line += 2;
+              b = (hex[(int) line[0]] << 4) | hex[(int) line[1]]; line += 2;
+            }
+
+          if (which == 'c')
+            {
+              cmap[i].cr = r;
+              cmap[i].cg = g;
+              cmap[i].cb = b;
+            }
+          else
+            {
+              cmap[i].mr = r;
+              cmap[i].mg = g;
+              cmap[i].mb = b;
+            }
+        }
+
+      data++;
+    }
+
+  if (depth == 1) transparent_color = 1;
+
+  pixels = (unsigned long *) calloc (ncolors+1, sizeof(*pixels));
+  pixel_count = 0;
+  for (i = 0; i < ncolors; i++)
+    {
+      if (cmap[i].cr == -1) /* transparent */
+        {
+          rmap[(int) cmap[i].byte] = 255;
+        }
+      else
+        {
+          XColor color;
+          color.flags = DoRed|DoGreen|DoBlue;
+          color.red   = (cmap[i].cr << 8) | cmap[i].cr;
+          color.green = (cmap[i].cg << 8) | cmap[i].cg;
+          color.blue  = (cmap[i].cb << 8) | cmap[i].cb;
+          if (depth == 1 ||
+              !XAllocColor (dpy, colormap, &color))
+            {
+              color.red   = (cmap[i].mr << 8) | cmap[i].mr;
+              color.green = (cmap[i].mg << 8) | cmap[i].mg;
+              color.blue  = (cmap[i].mb << 8) | cmap[i].mb;
+              if (!XAllocColor (dpy, colormap, &color)) {
+                fprintf (stderr, "%s: unable to allocate XPM color\n",
+                         progname);
+                abort();
+              }
+            }
+          pixels[pixel_count] = color.pixel;
+          rmap[(int) cmap[i].byte] = pixel_count;
+          pixel_count++;
+        }
+    }
+
+  ximage = XCreateImage (dpy, visual, depth,
+                         (depth == 1 ? XYBitmap : ZPixmap),
+                         0, 0, w, h, 8, 0);
+  if (! ximage)
+    {
+      if (pixels) free (pixels);
+      return 0;
+    }
+
+  ximage->bitmap_bit_order =
+    ximage->byte_order =
+    (bigendian() ? MSBFirst : LSBFirst);
+
+  ximage->data = (char *) calloc (ximage->height, ximage->bytes_per_line);
+  if (!ximage->data)
+    {
+      XDestroyImage (ximage);
+      if (pixels) free (pixels);
+      return 0;
+    }
+
+  w8 = (w + 7) / 8;
+  if (mask_ret)
+    {
+      int s = (w8 * h) + 1;
+      *mask_ret = (unsigned char *) malloc (s);
+      if (!*mask_ret)
+        mask_ret = 0;
+      else
+        memset (*mask_ret, 255, s);
+    }
+
+  for (y = 0; y < h; y++)
+    {
+      const char *line = *data++;
+      for (x = 0; x < w; x++)
+        {
+          int p = rmap[(int) *line];
+          line++;
+          XPutPixel (ximage, x, y, (p == 255 ? transparent_color : pixels[p]));
+
+          if (p == 255 && mask_ret)
+            (*mask_ret)[(y * w8) + (x >> 3)] &= (~(1 << (x & 7)));
+        }
+    }
+
+  *width_ret = w;
+  *height_ret = h;
+  *pixels_ret = pixels;
+  *npixels_ret = pixel_count;
+  return ximage;
+}
diff --git a/screenhack/minixpm.h b/screenhack/minixpm.h
new file mode 100644 (file)
index 0000000..f027894
--- /dev/null
@@ -0,0 +1,28 @@
+/* xscreensaver, Copyright (c) 2001-2006 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifndef __MINIXPM_H__
+#define __MINIXPM_H__
+
+
+/* A dead simple XPM parser that knows how to make XImage structures.
+   Only handles single-byte color XPMs.
+ */
+
+extern XImage * minixpm_to_ximage (Display *, Visual *, Colormap, int depth,
+                                   unsigned long transparent_color,
+                                   const char * const * data,
+                                   int *width_ret, int *height_ret,
+                                   unsigned long **pixels_ret, 
+                                   int *npixels_ret,
+                                   unsigned char **mask_ret);
+
+#endif /* __MINIXPM_H__ */
diff --git a/screenhack/overlay.c b/screenhack/overlay.c
new file mode 100644 (file)
index 0000000..0f42dcd
--- /dev/null
@@ -0,0 +1,158 @@
+/* xscreensaver, Copyright (c) 1997, 2001, 2004 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/* If the server's root window contains a SERVER_OVERLAY_VISUALS property,
+   then that identifies the visuals which correspond to the video hardware's
+   overlay planes.  Windows created in these kinds of visuals have the
+   property that one or more particular pixel values is transparent.
+
+   Vendor support:
+
+    SGI:  - Supported on all systems since IRIX 4.0.
+          - Their version of the Motif toolkit renders menus into the overlay
+            visual, so that they don't cause exposure events on occluded
+            windows.
+          - 2 bit overlay plane with Indigo Entry graphics (e.g., Indy).
+          - 8 bit overlay on O2 with Indigo 24 graphics or better (e.g., O2).
+
+    HP:   - Supported on workstations with CRX24 and CRX48Z graphics hardware.
+          - 8 bit or 24 bit overlay plane.
+          - The default visual is the overlay visual, with a transparent pixel.
+            That way, all Xlib programs draw into the overlay plane, and no
+            Xlib program causes exposures on occluded OpenGL windows.
+
+    IBM:  - Supported on some graphics hardware (which?)
+
+    DEC:  - Supported on some graphics hardware (which?)
+
+    SUN:  - Some systems apparently implement it VERRRRRRY SLOWLY, so drawing
+            into the overlay plane is a performance killer (about as bad as
+            using the SHAPE extension.)
+
+
+   On my Indy, there are two transparent visuals, one of which is at layer 1,
+   and one of which is at layer 2.  This is apparently the ordering in which
+   they are overlayed (1 being topmost.)  The other difference between them
+   is that the topmost one only has 2 planes, while the next one has 8.
+
+   This code selects the topmost one, regardless of depth.  Maybe that's not
+   the right thing.  Well, in XScreenSaver, we only need to allocate two
+   colors from it (it's only used to display the stderr output, so that the
+   text can overlay the graphics without being obliterated by it.)
+
+   Documentation, such as it is, on SERVER_OVERLAY_VISUALS found on the web:
+
+     http://www.hp.com/xwindow/sharedInfo/Whitepapers/Visuals/server_overlay_visuals.html
+    http://www.xig.com/Pages/Ed-Overlays.html
+ */
+
+
+#include "utils.h"
+
+#include <X11/Xutil.h>
+#include <X11/Xproto.h>
+
+#include "visual.h"
+
+
+struct overlay_data
+{
+  CARD32 visual_id;
+  CARD32 transparency; /* 0: none; 1: pixel; 2: mask
+                           ("mask" means "any pixel with these bits set
+                           is a transparent pixel")
+                         */
+  CARD32 value;                /* the transparent pixel or mask */
+  CARD32 layer;                /* -1: underlay; 0: normal; 1: popup; 2: overlay */
+};
+
+static int
+get_overlay_prop (Screen *screen, struct overlay_data **data_ret)
+{
+  int result;
+  Atom actual_type;
+  int actual_format;
+  unsigned long nitems, bytes_after;
+  unsigned char *data = 0;
+  Display *dpy = DisplayOfScreen(screen);
+  Window window = RootWindowOfScreen(screen);
+  Atom XA_SERVER_OVERLAY_VISUALS =
+    XInternAtom (dpy, "SERVER_OVERLAY_VISUALS", False);
+
+  *data_ret = 0;
+  result = XGetWindowProperty (dpy, window, XA_SERVER_OVERLAY_VISUALS,
+                              0, (65536 / sizeof (long)), False, 
+                              XA_SERVER_OVERLAY_VISUALS,
+                              &actual_type, &actual_format,
+                              &nitems, &bytes_after,
+                              &data);
+  if (result != Success ||
+      actual_type != XA_SERVER_OVERLAY_VISUALS ||
+      actual_format != 32 ||
+      nitems < 1)
+    {
+      if (data) XFree(data);
+      return 0;
+    }
+  else
+    {
+      struct overlay_data *d = (struct overlay_data *) data;
+      *data_ret = d;
+      return nitems / (sizeof(*d) / sizeof(CARD32));
+    }
+}
+
+
+Visual *
+get_overlay_visual (Screen *screen, unsigned long *transparent_pixel_ret)
+{
+  struct overlay_data *data = 0;
+  int n_visuals = get_overlay_prop (screen, &data);
+  Visual *visual = 0;
+  int depth = 0;
+  unsigned long pixel = 0;
+  unsigned int layer = 0;
+  int i;
+
+  if (data)
+    for (i = 0; i < n_visuals; i++)
+
+      /* Only accept ones that have a transparent pixel or mask. */
+      if (data[i].transparency == 1 ||
+          data[i].transparency == 2)
+       {
+         XVisualInfo vi_in, *vi_out;
+         int out_count;
+         vi_in.visualid = data[i].visual_id;
+         vi_out = XGetVisualInfo (DisplayOfScreen(screen), VisualIDMask,
+                                  &vi_in, &out_count);
+         if (vi_out)
+           {
+             /* Prefer the one at the topmost layer; after that, prefer
+                the one with the greatest depth (most colors.) */
+             if (layer < data[i].layer ||
+                 (layer == data[i].layer &&
+                  depth < vi_out[0].depth))
+               {
+                 visual = vi_out[0].visual;
+                 depth  = vi_out[0].depth;
+                 layer  = data[i].layer;
+                 pixel  = data[i].value;
+               }
+             XFree(vi_out);
+           }
+       }
+
+  if (data) XFree(data);
+  if (visual && transparent_pixel_ret)
+    *transparent_pixel_ret = pixel;
+  return visual;
+}
diff --git a/screenhack/pow2.c b/screenhack/pow2.c
new file mode 100644 (file)
index 0000000..9cce3d8
--- /dev/null
@@ -0,0 +1,51 @@
+/* pow2, Copyright (c) 2016 Dave Odell <dmo2118@gmail.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or
+ * implied warranty.
+ */
+
+#include "pow2.h"
+
+#include <limits.h>
+
+int
+i_log2 (size_t x)
+{
+  /* -1 works best for to_pow2. */
+  if (!x)
+    return -1;
+
+  /* GCC 3.4 also has this. */
+# if defined __GNUC__ && __GNUC__ >= 4 || defined __clang__
+  return sizeof(long) * CHAR_BIT - __builtin_clzl(x) - 1;
+# else
+  {
+    unsigned bits = sizeof(x) * CHAR_BIT;
+    size_t mask = (size_t)-1;
+    unsigned result = bits - 1;
+
+    while (bits) {
+      if (!(x & mask)) {
+        result -= bits;
+        x <<= bits;
+      }
+
+      bits >>= 1;
+      mask <<= bits;
+    }
+
+    return result;
+  }
+# endif
+}
+
+size_t
+to_pow2 (size_t x)
+{
+  return !x ? 1 : 1 << (i_log2(x - 1) + 1);
+}
diff --git a/screenhack/pow2.h b/screenhack/pow2.h
new file mode 100644 (file)
index 0000000..3927e84
--- /dev/null
@@ -0,0 +1,20 @@
+/* pow2, Copyright (c) 2016 Dave Odell <dmo2118@gmail.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or
+ * implied warranty.
+ */
+
+#ifndef POW2_H
+#define POW2_H
+
+#include <stdlib.h>
+
+extern int i_log2(size_t x);
+extern size_t to_pow2 (size_t x); /* return the next larger power of 2. */
+
+#endif /* POW2_H */
diff --git a/screenhack/queue.h b/screenhack/queue.h
new file mode 100644 (file)
index 0000000..94bbf23
--- /dev/null
@@ -0,0 +1,638 @@
+/*     $NetBSD: queue.h,v 1.68 2014/11/19 08:10:01 uebayasi Exp $      */
+
+/*
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)queue.h     8.5 (Berkeley) 8/20/94
+ */
+
+#ifndef        _QUEUE_H_
+#define        _QUEUE_H_
+
+/*
+ * This file defines five types of data structures: singly-linked lists,
+ * lists, simple queues, tail queues, and circular queues.
+ *
+ * A singly-linked list is headed by a single forward pointer. The
+ * elements are singly linked for minimum space and pointer manipulation
+ * overhead at the expense of O(n) removal for arbitrary elements. New
+ * elements can be added to the list after an existing element or at the
+ * head of the list.  Elements being removed from the head of the list
+ * should use the explicit macro for this purpose for optimum
+ * efficiency. A singly-linked list may only be traversed in the forward
+ * direction.  Singly-linked lists are ideal for applications with large
+ * datasets and few or no removals or for implementing a LIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A simple queue is headed by a pair of pointers, one the head of the
+ * list and the other to the tail of the list. The elements are singly
+ * linked to save space, so elements can only be removed from the
+ * head of the list. New elements can be added to the list after
+ * an existing element, at the head of the list, or at the end of the
+ * list. A simple queue may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * A circle queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the list.
+ * A circle queue may be traversed in either direction, but has a more
+ * complex end of list detection.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ */
+
+/*
+ * Singly-linked List definitions.
+ */
+#define        SLIST_HEAD(name, type)                                          \
+struct name {                                                          \
+       struct type *slh_first; /* first element */                     \
+}
+
+#define        SLIST_HEAD_INITIALIZER(head)                                    \
+       { NULL }
+
+#define        SLIST_ENTRY(type)                                               \
+struct {                                                               \
+       struct type *sle_next;  /* next element */                      \
+}
+
+/*
+ * Singly-linked List access methods.
+ */
+#define        SLIST_FIRST(head)       ((head)->slh_first)
+#define        SLIST_END(head)         NULL
+#define        SLIST_EMPTY(head)       ((head)->slh_first == NULL)
+#define        SLIST_NEXT(elm, field)  ((elm)->field.sle_next)
+
+#define        SLIST_FOREACH(var, head, field)                                 \
+       for((var) = (head)->slh_first;                                  \
+           (var) != SLIST_END(head);                                   \
+           (var) = (var)->field.sle_next)
+
+#define        SLIST_FOREACH_SAFE(var, head, field, tvar)                      \
+       for ((var) = SLIST_FIRST((head));                               \
+           (var) != SLIST_END(head) &&                                 \
+           ((tvar) = SLIST_NEXT((var), field), 1);                     \
+           (var) = (tvar))
+
+/*
+ * Singly-linked List functions.
+ */
+#define        SLIST_INIT(head) do {                                           \
+       (head)->slh_first = SLIST_END(head);                            \
+} while (/*CONSTCOND*/0)
+
+#define        SLIST_INSERT_AFTER(slistelm, elm, field) do {                   \
+       (elm)->field.sle_next = (slistelm)->field.sle_next;             \
+       (slistelm)->field.sle_next = (elm);                             \
+} while (/*CONSTCOND*/0)
+
+#define        SLIST_INSERT_HEAD(head, elm, field) do {                        \
+       (elm)->field.sle_next = (head)->slh_first;                      \
+       (head)->slh_first = (elm);                                      \
+} while (/*CONSTCOND*/0)
+
+#define        SLIST_REMOVE_AFTER(slistelm, field) do {                        \
+       (slistelm)->field.sle_next =                                    \
+           SLIST_NEXT(SLIST_NEXT((slistelm), field), field);           \
+} while (/*CONSTCOND*/0)
+
+#define        SLIST_REMOVE_HEAD(head, field) do {                             \
+       (head)->slh_first = (head)->slh_first->field.sle_next;          \
+} while (/*CONSTCOND*/0)
+
+#define        SLIST_REMOVE(head, elm, type, field) do {                       \
+       if ((head)->slh_first == (elm)) {                               \
+               SLIST_REMOVE_HEAD((head), field);                       \
+       }                                                               \
+       else {                                                          \
+               struct type *curelm = (head)->slh_first;                \
+               while(curelm->field.sle_next != (elm))                  \
+                       curelm = curelm->field.sle_next;                \
+               curelm->field.sle_next =                                \
+                   curelm->field.sle_next->field.sle_next;             \
+       }                                                               \
+} while (/*CONSTCOND*/0)
+
+
+/*
+ * List definitions.
+ */
+#define        LIST_HEAD(name, type)                                           \
+struct name {                                                          \
+       struct type *lh_first;  /* first element */                     \
+}
+
+#define        LIST_HEAD_INITIALIZER(head)                                     \
+       { NULL }
+
+#define        LIST_ENTRY(type)                                                \
+struct {                                                               \
+       struct type *le_next;   /* next element */                      \
+       struct type **le_prev;  /* address of previous next element */  \
+}
+
+/*
+ * List access methods.
+ */
+#define        LIST_FIRST(head)                ((head)->lh_first)
+#define        LIST_END(head)                  NULL
+#define        LIST_EMPTY(head)                ((head)->lh_first == LIST_END(head))
+#define        LIST_NEXT(elm, field)           ((elm)->field.le_next)
+
+#define        LIST_FOREACH(var, head, field)                                  \
+       for ((var) = ((head)->lh_first);                                \
+           (var) != LIST_END(head);                                    \
+           (var) = ((var)->field.le_next))
+
+#define        LIST_FOREACH_SAFE(var, head, field, tvar)                       \
+       for ((var) = LIST_FIRST((head));                                \
+           (var) != LIST_END(head) &&                                  \
+           ((tvar) = LIST_NEXT((var), field), 1);                      \
+           (var) = (tvar))
+
+#define        LIST_MOVE(head1, head2) do {                                    \
+       LIST_INIT((head2));                                             \
+       if (!LIST_EMPTY((head1))) {                                     \
+               (head2)->lh_first = (head1)->lh_first;                  \
+               LIST_INIT((head1));                                     \
+       }                                                               \
+} while (/*CONSTCOND*/0)
+
+/*
+ * List functions.
+ */
+#if defined(QUEUEDEBUG)
+#define        QUEUEDEBUG_LIST_INSERT_HEAD(head, elm, field)                   \
+       if ((head)->lh_first &&                                         \
+           (head)->lh_first->field.le_prev != &(head)->lh_first)       \
+               QUEUEDEBUG_ABORT("LIST_INSERT_HEAD %p %s:%d", (head),   \
+                   __FILE__, __LINE__);
+#define        QUEUEDEBUG_LIST_OP(elm, field)                                  \
+       if ((elm)->field.le_next &&                                     \
+           (elm)->field.le_next->field.le_prev !=                      \
+           &(elm)->field.le_next)                                      \
+               QUEUEDEBUG_ABORT("LIST_* forw %p %s:%d", (elm),         \
+                   __FILE__, __LINE__);                                \
+       if (*(elm)->field.le_prev != (elm))                             \
+               QUEUEDEBUG_ABORT("LIST_* back %p %s:%d", (elm),         \
+                   __FILE__, __LINE__);
+#define        QUEUEDEBUG_LIST_POSTREMOVE(elm, field)                          \
+       (elm)->field.le_next = (void *)1L;                              \
+       (elm)->field.le_prev = (void *)1L;
+#else
+#define        QUEUEDEBUG_LIST_INSERT_HEAD(head, elm, field)
+#define        QUEUEDEBUG_LIST_OP(elm, field)
+#define        QUEUEDEBUG_LIST_POSTREMOVE(elm, field)
+#endif
+
+#define        LIST_INIT(head) do {                                            \
+       (head)->lh_first = LIST_END(head);                              \
+} while (/*CONSTCOND*/0)
+
+#define        LIST_INSERT_AFTER(listelm, elm, field) do {                     \
+       QUEUEDEBUG_LIST_OP((listelm), field)                            \
+       if (((elm)->field.le_next = (listelm)->field.le_next) !=        \
+           LIST_END(head))                                             \
+               (listelm)->field.le_next->field.le_prev =               \
+                   &(elm)->field.le_next;                              \
+       (listelm)->field.le_next = (elm);                               \
+       (elm)->field.le_prev = &(listelm)->field.le_next;               \
+} while (/*CONSTCOND*/0)
+
+#define        LIST_INSERT_BEFORE(listelm, elm, field) do {                    \
+       QUEUEDEBUG_LIST_OP((listelm), field)                            \
+       (elm)->field.le_prev = (listelm)->field.le_prev;                \
+       (elm)->field.le_next = (listelm);                               \
+       *(listelm)->field.le_prev = (elm);                              \
+       (listelm)->field.le_prev = &(elm)->field.le_next;               \
+} while (/*CONSTCOND*/0)
+
+#define        LIST_INSERT_HEAD(head, elm, field) do {                         \
+       QUEUEDEBUG_LIST_INSERT_HEAD((head), (elm), field)               \
+       if (((elm)->field.le_next = (head)->lh_first) != LIST_END(head))\
+               (head)->lh_first->field.le_prev = &(elm)->field.le_next;\
+       (head)->lh_first = (elm);                                       \
+       (elm)->field.le_prev = &(head)->lh_first;                       \
+} while (/*CONSTCOND*/0)
+
+#define        LIST_REMOVE(elm, field) do {                                    \
+       QUEUEDEBUG_LIST_OP((elm), field)                                \
+       if ((elm)->field.le_next != NULL)                               \
+               (elm)->field.le_next->field.le_prev =                   \
+                   (elm)->field.le_prev;                               \
+       *(elm)->field.le_prev = (elm)->field.le_next;                   \
+       QUEUEDEBUG_LIST_POSTREMOVE((elm), field)                        \
+} while (/*CONSTCOND*/0)
+
+#define LIST_REPLACE(elm, elm2, field) do {                            \
+       if (((elm2)->field.le_next = (elm)->field.le_next) != NULL)     \
+               (elm2)->field.le_next->field.le_prev =                  \
+                   &(elm2)->field.le_next;                             \
+       (elm2)->field.le_prev = (elm)->field.le_prev;                   \
+       *(elm2)->field.le_prev = (elm2);                                \
+       QUEUEDEBUG_LIST_POSTREMOVE((elm), field)                        \
+} while (/*CONSTCOND*/0)
+
+/*
+ * Simple queue definitions.
+ */
+#define        SIMPLEQ_HEAD(name, type)                                        \
+struct name {                                                          \
+       struct type *sqh_first; /* first element */                     \
+       struct type **sqh_last; /* addr of last next element */         \
+}
+
+#define        SIMPLEQ_HEAD_INITIALIZER(head)                                  \
+       { NULL, &(head).sqh_first }
+
+#define        SIMPLEQ_ENTRY(type)                                             \
+struct {                                                               \
+       struct type *sqe_next;  /* next element */                      \
+}
+
+/*
+ * Simple queue access methods.
+ */
+#define        SIMPLEQ_FIRST(head)             ((head)->sqh_first)
+#define        SIMPLEQ_END(head)               NULL
+#define        SIMPLEQ_EMPTY(head)             ((head)->sqh_first == SIMPLEQ_END(head))
+#define        SIMPLEQ_NEXT(elm, field)        ((elm)->field.sqe_next)
+
+#define        SIMPLEQ_FOREACH(var, head, field)                               \
+       for ((var) = ((head)->sqh_first);                               \
+           (var) != SIMPLEQ_END(head);                                 \
+           (var) = ((var)->field.sqe_next))
+
+#define        SIMPLEQ_FOREACH_SAFE(var, head, field, next)                    \
+       for ((var) = ((head)->sqh_first);                               \
+           (var) != SIMPLEQ_END(head) &&                               \
+           ((next = ((var)->field.sqe_next)), 1);                      \
+           (var) = (next))
+
+/*
+ * Simple queue functions.
+ */
+#define        SIMPLEQ_INIT(head) do {                                         \
+       (head)->sqh_first = NULL;                                       \
+       (head)->sqh_last = &(head)->sqh_first;                          \
+} while (/*CONSTCOND*/0)
+
+#define        SIMPLEQ_INSERT_HEAD(head, elm, field) do {                      \
+       if (((elm)->field.sqe_next = (head)->sqh_first) == NULL)        \
+               (head)->sqh_last = &(elm)->field.sqe_next;              \
+       (head)->sqh_first = (elm);                                      \
+} while (/*CONSTCOND*/0)
+
+#define        SIMPLEQ_INSERT_TAIL(head, elm, field) do {                      \
+       (elm)->field.sqe_next = NULL;                                   \
+       *(head)->sqh_last = (elm);                                      \
+       (head)->sqh_last = &(elm)->field.sqe_next;                      \
+} while (/*CONSTCOND*/0)
+
+#define        SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do {            \
+       if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\
+               (head)->sqh_last = &(elm)->field.sqe_next;              \
+       (listelm)->field.sqe_next = (elm);                              \
+} while (/*CONSTCOND*/0)
+
+#define        SIMPLEQ_REMOVE_HEAD(head, field) do {                           \
+       if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
+               (head)->sqh_last = &(head)->sqh_first;                  \
+} while (/*CONSTCOND*/0)
+
+#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do {                    \
+       if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \
+           == NULL)                                                    \
+               (head)->sqh_last = &(elm)->field.sqe_next;              \
+} while (/*CONSTCOND*/0)
+
+#define        SIMPLEQ_REMOVE(head, elm, type, field) do {                     \
+       if ((head)->sqh_first == (elm)) {                               \
+               SIMPLEQ_REMOVE_HEAD((head), field);                     \
+       } else {                                                        \
+               struct type *curelm = (head)->sqh_first;                \
+               while (curelm->field.sqe_next != (elm))                 \
+                       curelm = curelm->field.sqe_next;                \
+               if ((curelm->field.sqe_next =                           \
+                       curelm->field.sqe_next->field.sqe_next) == NULL) \
+                           (head)->sqh_last = &(curelm)->field.sqe_next; \
+       }                                                               \
+} while (/*CONSTCOND*/0)
+
+#define        SIMPLEQ_CONCAT(head1, head2) do {                               \
+       if (!SIMPLEQ_EMPTY((head2))) {                                  \
+               *(head1)->sqh_last = (head2)->sqh_first;                \
+               (head1)->sqh_last = (head2)->sqh_last;          \
+               SIMPLEQ_INIT((head2));                                  \
+       }                                                               \
+} while (/*CONSTCOND*/0)
+
+#define        SIMPLEQ_LAST(head, type, field)                                 \
+       (SIMPLEQ_EMPTY((head)) ?                                                \
+               NULL :                                                  \
+               ((struct type *)(void *)                                \
+               ((char *)((head)->sqh_last) - offsetof(struct type, field))))
+
+/*
+ * Tail queue definitions.
+ */
+#define        _TAILQ_HEAD(name, type, qual)                                   \
+struct name {                                                          \
+       qual type *tqh_first;           /* first element */             \
+       qual type *qual *tqh_last;      /* addr of last next element */ \
+}
+#define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,)
+
+#define        TAILQ_HEAD_INITIALIZER(head)                                    \
+       { TAILQ_END(head), &(head).tqh_first }
+
+#define        _TAILQ_ENTRY(type, qual)                                        \
+struct {                                                               \
+       qual type *tqe_next;            /* next element */              \
+       qual type *qual *tqe_prev;      /* address of previous next element */\
+}
+#define TAILQ_ENTRY(type)      _TAILQ_ENTRY(struct type,)
+
+/*
+ * Tail queue access methods.
+ */
+#define        TAILQ_FIRST(head)               ((head)->tqh_first)
+#define        TAILQ_END(head)                 (NULL)
+#define        TAILQ_NEXT(elm, field)          ((elm)->field.tqe_next)
+#define        TAILQ_LAST(head, headname) \
+       (*(((struct headname *)((head)->tqh_last))->tqh_last))
+#define        TAILQ_PREV(elm, headname, field) \
+       (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+#define        TAILQ_EMPTY(head)               (TAILQ_FIRST(head) == TAILQ_END(head))
+
+
+#define        TAILQ_FOREACH(var, head, field)                                 \
+       for ((var) = ((head)->tqh_first);                               \
+           (var) != TAILQ_END(head);                                   \
+           (var) = ((var)->field.tqe_next))
+
+#define        TAILQ_FOREACH_SAFE(var, head, field, next)                      \
+       for ((var) = ((head)->tqh_first);                               \
+           (var) != TAILQ_END(head) &&                                 \
+           ((next) = TAILQ_NEXT(var, field), 1); (var) = (next))
+
+#define        TAILQ_FOREACH_REVERSE(var, head, headname, field)               \
+       for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last));\
+           (var) != TAILQ_END(head);                                   \
+           (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last)))
+
+#define        TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, prev)    \
+       for ((var) = TAILQ_LAST((head), headname);                      \
+           (var) != TAILQ_END(head) &&                                 \
+           ((prev) = TAILQ_PREV((var), headname, field), 1); (var) = (prev))
+
+/*
+ * Tail queue functions.
+ */
+#if defined(QUEUEDEBUG)
+#define        QUEUEDEBUG_TAILQ_INSERT_HEAD(head, elm, field)                  \
+       if ((head)->tqh_first &&                                        \
+           (head)->tqh_first->field.tqe_prev != &(head)->tqh_first)    \
+               QUEUEDEBUG_ABORT("TAILQ_INSERT_HEAD %p %s:%d", (head),  \
+                   __FILE__, __LINE__);
+#define        QUEUEDEBUG_TAILQ_INSERT_TAIL(head, elm, field)                  \
+       if (*(head)->tqh_last != NULL)                                  \
+               QUEUEDEBUG_ABORT("TAILQ_INSERT_TAIL %p %s:%d", (head),  \
+                   __FILE__, __LINE__);
+#define        QUEUEDEBUG_TAILQ_OP(elm, field)                                 \
+       if ((elm)->field.tqe_next &&                                    \
+           (elm)->field.tqe_next->field.tqe_prev !=                    \
+           &(elm)->field.tqe_next)                                     \
+               QUEUEDEBUG_ABORT("TAILQ_* forw %p %s:%d", (elm),        \
+                   __FILE__, __LINE__);                                \
+       if (*(elm)->field.tqe_prev != (elm))                            \
+               QUEUEDEBUG_ABORT("TAILQ_* back %p %s:%d", (elm),        \
+                   __FILE__, __LINE__);
+#define        QUEUEDEBUG_TAILQ_PREREMOVE(head, elm, field)                    \
+       if ((elm)->field.tqe_next == NULL &&                            \
+           (head)->tqh_last != &(elm)->field.tqe_next)                 \
+               QUEUEDEBUG_ABORT("TAILQ_PREREMOVE head %p elm %p %s:%d",\
+                   (head), (elm), __FILE__, __LINE__);
+#define        QUEUEDEBUG_TAILQ_POSTREMOVE(elm, field)                         \
+       (elm)->field.tqe_next = (void *)1L;                             \
+       (elm)->field.tqe_prev = (void *)1L;
+#else
+#define        QUEUEDEBUG_TAILQ_INSERT_HEAD(head, elm, field)
+#define        QUEUEDEBUG_TAILQ_INSERT_TAIL(head, elm, field)
+#define        QUEUEDEBUG_TAILQ_OP(elm, field)
+#define        QUEUEDEBUG_TAILQ_PREREMOVE(head, elm, field)
+#define        QUEUEDEBUG_TAILQ_POSTREMOVE(elm, field)
+#endif
+
+#define        TAILQ_INIT(head) do {                                           \
+       (head)->tqh_first = TAILQ_END(head);                            \
+       (head)->tqh_last = &(head)->tqh_first;                          \
+} while (/*CONSTCOND*/0)
+
+#define        TAILQ_INSERT_HEAD(head, elm, field) do {                        \
+       QUEUEDEBUG_TAILQ_INSERT_HEAD((head), (elm), field)              \
+       if (((elm)->field.tqe_next = (head)->tqh_first) != TAILQ_END(head))\
+               (head)->tqh_first->field.tqe_prev =                     \
+                   &(elm)->field.tqe_next;                             \
+       else                                                            \
+               (head)->tqh_last = &(elm)->field.tqe_next;              \
+       (head)->tqh_first = (elm);                                      \
+       (elm)->field.tqe_prev = &(head)->tqh_first;                     \
+} while (/*CONSTCOND*/0)
+
+#define        TAILQ_INSERT_TAIL(head, elm, field) do {                        \
+       QUEUEDEBUG_TAILQ_INSERT_TAIL((head), (elm), field)              \
+       (elm)->field.tqe_next = TAILQ_END(head);                        \
+       (elm)->field.tqe_prev = (head)->tqh_last;                       \
+       *(head)->tqh_last = (elm);                                      \
+       (head)->tqh_last = &(elm)->field.tqe_next;                      \
+} while (/*CONSTCOND*/0)
+
+#define        TAILQ_INSERT_AFTER(head, listelm, elm, field) do {              \
+       QUEUEDEBUG_TAILQ_OP((listelm), field)                           \
+       if (((elm)->field.tqe_next = (listelm)->field.tqe_next) !=      \
+           TAILQ_END(head))                                            \
+               (elm)->field.tqe_next->field.tqe_prev =                 \
+                   &(elm)->field.tqe_next;                             \
+       else                                                            \
+               (head)->tqh_last = &(elm)->field.tqe_next;              \
+       (listelm)->field.tqe_next = (elm);                              \
+       (elm)->field.tqe_prev = &(listelm)->field.tqe_next;             \
+} while (/*CONSTCOND*/0)
+
+#define        TAILQ_INSERT_BEFORE(listelm, elm, field) do {                   \
+       QUEUEDEBUG_TAILQ_OP((listelm), field)                           \
+       (elm)->field.tqe_prev = (listelm)->field.tqe_prev;              \
+       (elm)->field.tqe_next = (listelm);                              \
+       *(listelm)->field.tqe_prev = (elm);                             \
+       (listelm)->field.tqe_prev = &(elm)->field.tqe_next;             \
+} while (/*CONSTCOND*/0)
+
+#define        TAILQ_REMOVE(head, elm, field) do {                             \
+       QUEUEDEBUG_TAILQ_PREREMOVE((head), (elm), field)                \
+       QUEUEDEBUG_TAILQ_OP((elm), field)                               \
+       if (((elm)->field.tqe_next) != TAILQ_END(head))                 \
+               (elm)->field.tqe_next->field.tqe_prev =                 \
+                   (elm)->field.tqe_prev;                              \
+       else                                                            \
+               (head)->tqh_last = (elm)->field.tqe_prev;               \
+       *(elm)->field.tqe_prev = (elm)->field.tqe_next;                 \
+       QUEUEDEBUG_TAILQ_POSTREMOVE((elm), field);                      \
+} while (/*CONSTCOND*/0)
+
+#define TAILQ_REPLACE(head, elm, elm2, field) do {                     \
+        if (((elm2)->field.tqe_next = (elm)->field.tqe_next) !=        \
+           TAILQ_END(head))                                            \
+                (elm2)->field.tqe_next->field.tqe_prev =               \
+                    &(elm2)->field.tqe_next;                           \
+        else                                                           \
+                (head)->tqh_last = &(elm2)->field.tqe_next;            \
+        (elm2)->field.tqe_prev = (elm)->field.tqe_prev;                        \
+        *(elm2)->field.tqe_prev = (elm2);                              \
+       QUEUEDEBUG_TAILQ_POSTREMOVE((elm), field);                      \
+} while (/*CONSTCOND*/0)
+
+#define        TAILQ_CONCAT(head1, head2, field) do {                          \
+       if (!TAILQ_EMPTY(head2)) {                                      \
+               *(head1)->tqh_last = (head2)->tqh_first;                \
+               (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
+               (head1)->tqh_last = (head2)->tqh_last;                  \
+               TAILQ_INIT((head2));                                    \
+       }                                                               \
+} while (/*CONSTCOND*/0)
+
+/*
+ * Singly-linked Tail queue declarations.
+ */
+#define        STAILQ_HEAD(name, type)                                         \
+struct name {                                                          \
+       struct type *stqh_first;        /* first element */             \
+       struct type **stqh_last;        /* addr of last next element */ \
+}
+
+#define        STAILQ_HEAD_INITIALIZER(head)                                   \
+       { NULL, &(head).stqh_first }
+
+#define        STAILQ_ENTRY(type)                                              \
+struct {                                                               \
+       struct type *stqe_next; /* next element */                      \
+}
+
+/*
+ * Singly-linked Tail queue access methods.
+ */
+#define        STAILQ_FIRST(head)      ((head)->stqh_first)
+#define        STAILQ_END(head)        NULL
+#define        STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
+#define        STAILQ_EMPTY(head)      (STAILQ_FIRST(head) == STAILQ_END(head))
+
+/*
+ * Singly-linked Tail queue functions.
+ */
+#define        STAILQ_INIT(head) do {                                          \
+       (head)->stqh_first = NULL;                                      \
+       (head)->stqh_last = &(head)->stqh_first;                                \
+} while (/*CONSTCOND*/0)
+
+#define        STAILQ_INSERT_HEAD(head, elm, field) do {                       \
+       if (((elm)->field.stqe_next = (head)->stqh_first) == NULL)      \
+               (head)->stqh_last = &(elm)->field.stqe_next;            \
+       (head)->stqh_first = (elm);                                     \
+} while (/*CONSTCOND*/0)
+
+#define        STAILQ_INSERT_TAIL(head, elm, field) do {                       \
+       (elm)->field.stqe_next = NULL;                                  \
+       *(head)->stqh_last = (elm);                                     \
+       (head)->stqh_last = &(elm)->field.stqe_next;                    \
+} while (/*CONSTCOND*/0)
+
+#define        STAILQ_INSERT_AFTER(head, listelm, elm, field) do {             \
+       if (((elm)->field.stqe_next = (listelm)->field.stqe_next) == NULL)\
+               (head)->stqh_last = &(elm)->field.stqe_next;            \
+       (listelm)->field.stqe_next = (elm);                             \
+} while (/*CONSTCOND*/0)
+
+#define        STAILQ_REMOVE_HEAD(head, field) do {                            \
+       if (((head)->stqh_first = (head)->stqh_first->field.stqe_next) == NULL) \
+               (head)->stqh_last = &(head)->stqh_first;                        \
+} while (/*CONSTCOND*/0)
+
+#define        STAILQ_REMOVE(head, elm, type, field) do {                      \
+       if ((head)->stqh_first == (elm)) {                              \
+               STAILQ_REMOVE_HEAD((head), field);                      \
+       } else {                                                        \
+               struct type *curelm = (head)->stqh_first;               \
+               while (curelm->field.stqe_next != (elm))                        \
+                       curelm = curelm->field.stqe_next;               \
+               if ((curelm->field.stqe_next =                          \
+                       curelm->field.stqe_next->field.stqe_next) == NULL) \
+                           (head)->stqh_last = &(curelm)->field.stqe_next; \
+       }                                                               \
+} while (/*CONSTCOND*/0)
+
+#define        STAILQ_FOREACH(var, head, field)                                \
+       for ((var) = ((head)->stqh_first);                              \
+               (var);                                                  \
+               (var) = ((var)->field.stqe_next))
+
+#define        STAILQ_FOREACH_SAFE(var, head, field, tvar)                     \
+       for ((var) = STAILQ_FIRST((head));                              \
+           (var) && ((tvar) = STAILQ_NEXT((var), field), 1);           \
+           (var) = (tvar))
+
+#define        STAILQ_CONCAT(head1, head2) do {                                \
+       if (!STAILQ_EMPTY((head2))) {                                   \
+               *(head1)->stqh_last = (head2)->stqh_first;              \
+               (head1)->stqh_last = (head2)->stqh_last;                \
+               STAILQ_INIT((head2));                                   \
+       }                                                               \
+} while (/*CONSTCOND*/0)
+
+#define        STAILQ_LAST(head, type, field)                                  \
+       (STAILQ_EMPTY((head)) ?                                         \
+               NULL :                                                  \
+               ((struct type *)(void *)                                \
+               ((char *)((head)->stqh_last) - offsetof(struct type, field))))
+
+#endif /* !_QUEUE_H_ */
diff --git a/screenhack/resources.c b/screenhack/resources.c
new file mode 100644 (file)
index 0000000..7b54adc
--- /dev/null
@@ -0,0 +1,296 @@
+/* xscreensaver, Copyright (c) 1992-2014 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#include "utils.h"
+#include "resources.h"
+
+extern char *progname;
+
+
+#if !defined(HAVE_COCOA) && !defined(HAVE_ANDROID)
+
+#include <X11/Xresource.h>
+
+/* These are the Xlib/Xrm versions of these functions.
+   The Cocoa versions are on OSX/XScreenSaverView.m.
+ */
+
+extern char *progclass;
+extern XrmDatabase XtDatabase (Display *);
+
+static unsigned int get_time_resource (Display *dpy, 
+                                       char *res_name, char *res_class,
+                                      Bool sec_p);
+
+#ifndef isupper
+# define isupper(c)  ((c) >= 'A' && (c) <= 'Z')
+#endif
+#ifndef _tolower
+# define _tolower(c)  ((c) - 'A' + 'a')
+#endif
+
+char *
+get_string_resource (Display *dpy, char *res_name, char *res_class)
+{
+  XrmValue value;
+  char *type;
+  char full_name [1024], full_class [1024];
+  strcpy (full_name, progname);
+  strcat (full_name, ".");
+  strcat (full_name, res_name);
+  strcpy (full_class, progclass);
+  strcat (full_class, ".");
+  strcat (full_class, res_class);
+  if (XrmGetResource (XtDatabase (dpy), full_name, full_class, &type, &value))
+    {
+      char *str = (char *) malloc (value.size + 1);
+      strncpy (str, (char *) value.addr, value.size);
+      str [value.size] = 0;
+      return str;
+    }
+  return 0;
+}
+
+Bool 
+get_boolean_resource (Display *dpy, char *res_name, char *res_class)
+{
+  char *tmp, buf [100];
+  char *s = get_string_resource (dpy, res_name, res_class);
+  char *os = s;
+  if (! s) return 0;
+  for (tmp = buf; *s; s++)
+    *tmp++ = isupper (*s) ? _tolower (*s) : *s;
+  *tmp = 0;
+  free (os);
+
+  while (*buf &&
+        (buf[strlen(buf)-1] == ' ' ||
+         buf[strlen(buf)-1] == '\t'))
+    buf[strlen(buf)-1] = 0;
+
+  if (!strcmp (buf, "on") || !strcmp (buf, "true") || !strcmp (buf, "yes"))
+    return 1;
+  if (!strcmp (buf,"off") || !strcmp (buf, "false") || !strcmp (buf,"no"))
+    return 0;
+  fprintf (stderr, "%s: %s must be boolean, not %s.\n",
+          progname, res_name, buf);
+  return 0;
+}
+
+int 
+get_integer_resource (Display *dpy, char *res_name, char *res_class)
+{
+  int val;
+  char c, *s = get_string_resource (dpy, res_name, res_class);
+  char *ss = s;
+  if (!s) return 0;
+
+  while (*ss && *ss <= ' ') ss++;                      /* skip whitespace */
+
+  if (ss[0] == '0' && (ss[1] == 'x' || ss[1] == 'X'))  /* 0x: parse as hex */
+    {
+      if (1 == sscanf (ss+2, "%x %c", (unsigned int *) &val, &c))
+       {
+         free (s);
+         return val;
+       }
+    }
+  else                                                 /* else parse as dec */
+    {
+      if (1 == sscanf (ss, "%d %c", &val, &c))
+       {
+         free (s);
+         return val;
+       }
+    }
+
+  fprintf (stderr, "%s: %s must be an integer, not %s.\n",
+          progname, res_name, s);
+  free (s);
+  return 0;
+}
+
+double
+get_float_resource (Display *dpy, char *res_name, char *res_class)
+{
+  double val;
+  char c, *s = get_string_resource (dpy, res_name, res_class);
+  if (! s) return 0.0;
+  if (1 == sscanf (s, " %lf %c", &val, &c))
+    {
+      free (s);
+      return val;
+    }
+  fprintf (stderr, "%s: %s must be a float, not %s.\n",
+          progname, res_name, s);
+  free (s);
+  return 0.0;
+}
+
+#endif /* !HAVE_COCOA && !HAVE_ANDROID */
+
+
+/* These functions are the same with Xlib, Cocoa and Android:
+ */
+
+
+unsigned int
+get_pixel_resource (Display *dpy, Colormap cmap,
+                    char *res_name, char *res_class)
+{
+  XColor color;
+  char *s = get_string_resource (dpy, res_name, res_class);
+  char *s2;
+  Bool ok = True;
+  if (!s) goto DEFAULT;
+
+  for (s2 = s + strlen(s) - 1; s2 > s; s2--)
+    if (*s2 == ' ' || *s2 == '\t')
+      *s2 = 0;
+    else
+      break;
+
+  if (! XParseColor (dpy, cmap, s, &color))
+    {
+      fprintf (stderr, "%s: can't parse color %s", progname, s);
+      ok = False;
+      goto DEFAULT;
+    }
+  if (! XAllocColor (dpy, cmap, &color))
+    {
+      fprintf (stderr, "%s: couldn't allocate color %s", progname, s);
+      ok = False;
+      goto DEFAULT;
+    }
+  free (s);
+  return (unsigned int) color.pixel;
+ DEFAULT:
+  if (s) free (s);
+
+  {
+    Bool black_p = (strlen(res_class) >= 10 &&
+                    !strcasecmp ("Background",
+                                 res_class + strlen(res_class) - 10));
+    if (!ok)
+      fprintf (stderr, ": using %s.\n", (black_p ? "black" : "white"));
+    color.flags = DoRed|DoGreen|DoBlue;
+    color.red = color.green = color.blue = (black_p ? 0 : 0xFFFF);
+    if (XAllocColor (dpy, cmap, &color))
+      return (unsigned int) color.pixel;
+    else
+      {
+        fprintf (stderr, "%s: couldn't allocate %s either!\n", progname,
+                 (black_p ? "black" : "white"));
+        /* We can't use BlackPixel/WhitePixel here, because we don't know
+           what screen we're allocating on (only an issue when running inside
+           the xscreensaver daemon: for hacks, DefaultScreen is fine.)
+         */
+        return 0;
+      }
+  }
+}
+
+
+int
+parse_time (const char *string, Bool seconds_default_p, Bool silent_p)
+{
+  unsigned int h, m, s;
+  char c;
+  if (3 == sscanf (string,   " %u : %2u : %2u %c", &h, &m, &s, &c))
+    ;
+  else if (2 == sscanf (string, " : %2u : %2u %c", &m, &s, &c) ||
+          2 == sscanf (string,    " %u : %2u %c", &m, &s, &c))
+    h = 0;
+  else if (1 == sscanf (string,       " : %2u %c", &s, &c))
+    h = m = 0;
+  else if (1 == sscanf (string,          " %u %c",
+                       (seconds_default_p ? &s : &m), &c))
+    {
+      h = 0;
+      if (seconds_default_p) m = 0;
+      else s = 0;
+    }
+  else
+    {
+      if (! silent_p)
+       fprintf (stderr, "%s: invalid time interval specification \"%s\".\n",
+                progname, string);
+      return -1;
+    }
+  if (s >= 60 && (h != 0 || m != 0))
+    {
+      if (! silent_p)
+       fprintf (stderr, "%s: seconds > 59 in \"%s\".\n", progname, string);
+      return -1;
+    }
+  if (m >= 60 && h > 0)
+    {
+      if (! silent_p)
+       fprintf (stderr, "%s: minutes > 59 in \"%s\".\n", progname, string);
+      return -1;
+    }
+  return ((h * 60 * 60) + (m * 60) + s);
+}
+
+static unsigned int 
+get_time_resource (Display *dpy, char *res_name, char *res_class, Bool sec_p)
+{
+  int val;
+  char *s = get_string_resource (dpy, res_name, res_class);
+  if (!s) return 0;
+  val = parse_time (s, sec_p, False);
+  free (s);
+  return (val < 0 ? 0 : val);
+}
+
+unsigned int 
+get_seconds_resource (Display *dpy, char *res_name, char *res_class)
+{
+  return get_time_resource (dpy, res_name, res_class, True);
+}
+
+unsigned int 
+get_minutes_resource (Display *dpy, char *res_name, char *res_class)
+{
+  return get_time_resource (dpy, res_name, res_class, False);
+}
+
+
+/* A utility function for event-handler functions:
+   Returns True if the event is a simple click, Space, Tab, etc.
+   Returns False otherwise.
+   The idea here is that most hacks interpret to clicks or basic
+   keypresses as "change it up".
+
+   This isn't really the right file for this, but whatever.
+ */
+Bool
+screenhack_event_helper (Display *dpy, Window window, XEvent *event)
+{
+  if (event->xany.type == KeyPress)
+    {
+      KeySym keysym;
+      char c = 0;
+      XLookupString (&event->xkey, &c, 1, &keysym, 0);
+      if (c == ' ' || c == '\t' || c == '\r' || c == '\n' ||
+          keysym == XK_Left || keysym == XK_Right ||
+          keysym == XK_Up || keysym == XK_Down ||
+          keysym == XK_Prior || keysym == XK_Next)
+        return True;
+    }
+  else if (event->xany.type == ButtonPress)
+    {
+      if (event->xbutton.button == 1)
+        return True;
+    }
+
+  return False;
+}
diff --git a/screenhack/resources.h b/screenhack/resources.h
new file mode 100644 (file)
index 0000000..cbfc8d1
--- /dev/null
@@ -0,0 +1,41 @@
+/* xscreensaver, Copyright (c) 1992-2014 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifndef __XSCREENSAVER_RESOURCES_H__
+#define __XSCREENSAVER_RESOURCES_H__
+
+extern char *get_string_resource (Display*,char*,char*);
+extern Bool get_boolean_resource (Display*,char*,char*);
+extern int get_integer_resource (Display*,char*,char*);
+extern double get_float_resource (Display*,char*,char*);
+extern unsigned int get_pixel_resource (Display*,Colormap,char*,char*);
+extern unsigned int get_minutes_resource (Display*,char*,char*);
+extern unsigned int get_seconds_resource (Display*,char*,char*);
+extern int parse_time (const char *string, Bool seconds_default_p,
+                       Bool silent_p);
+extern Pixmap
+xscreensaver_logo (Screen *screen, Visual *visual,
+                   Drawable drawable, Colormap cmap,
+                   unsigned long background_color,
+                   unsigned long **pixels_ret, int *npixels_ret,
+                   Pixmap *mask_ret,
+                   Bool big_p);
+
+
+/* A utility function for event-handler functions:
+   Returns True if the event is a simple click, Space, Tab, etc.
+   Returns False otherwise.
+   The idea here is that most hacks interpret to clicks or basic
+   keypresses as "change it up".
+ */
+extern Bool screenhack_event_helper (Display *, Window, XEvent *);
+
+#endif /* __XSCREENSAVER_RESOURCES_H__ */
diff --git a/screenhack/screenhack.c b/screenhack/screenhack.c
new file mode 100644 (file)
index 0000000..5f11049
--- /dev/null
@@ -0,0 +1,1008 @@
+/* xscreensaver, Copyright (c) 1992-2020 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ *
+ * And remember: X Windows is to graphics hacking as roman numerals are to
+ * the square root of pi.
+ */
+
+/* This file contains simple code to open a window or draw on the root.
+   The idea being that, when writing a graphics hack, you can just link
+   with this .o to get all of the uninteresting junk out of the way.
+
+   Create a few static global procedures and variables:
+
+      static void *YOURNAME_init (Display *, Window);
+
+          Return an opaque structure representing your drawing state.
+
+      static unsigned long YOURNAME_draw (Display *, Window, void *closure);
+
+          Draw one frame.
+          The `closure' arg is your drawing state, that you created in `init'.
+          Return the number of microseconds to wait until the next frame.
+
+          This should return in some small fraction of a second. 
+          Do not call `usleep' or loop excessively.  For long loops, use a
+          finite state machine.
+
+      static void YOURNAME_reshape (Display *, Window, void *closure,
+                                   unsigned int width, unsigned int height);
+
+          Called when the size of the window changes with the new size.
+
+      static Bool YOURNAME_event (Display *, Window, void *closure,
+                                 XEvent *event);
+
+          Called when a keyboard or mouse event arrives.
+          Return True if you handle it in some way, False otherwise.
+
+      static void YOURNAME_free (Display *, Window, void *closure);
+
+           Called when you are done: free everything you've allocated,
+           including your private `state' structure.  
+
+           NOTE: this is called in windowed-mode when the user typed
+           'q' or clicks on the window's close box; but when
+           xscreensaver terminates this screenhack, it does so by
+           killing the process with SIGSTOP.  So this callback is
+           mostly useless.
+
+      static char YOURNAME_defaults [] = { "...", "...", ... , 0 };
+
+           This variable is an array of strings, your default resources.
+           Null-terminate the list.
+
+      static XrmOptionDescRec YOURNAME_options[] = { { ... }, ... { 0,0,0,0 } }
+
+           This variable describes your command-line options.
+           Null-terminate the list.
+
+      Finally , invoke the XSCREENSAVER_MODULE() macro to tie it all together.
+
+   Additional caveats:
+
+      - Make sure that all functions in your module are static (check this
+        by running "nm -g" on the .o file).
+
+      - Do not use global variables: all such info must be stored in the
+        private `state' structure.
+
+      - Do not use static function-local variables, either.  Put it in `state'.
+
+        Assume that there are N independent runs of this code going in the
+        same address space at the same time: they must not affect each other.
+
+      - Don't forget to write an XML file to describe the user interface
+        of your screen saver module.  See .../hacks/config/README for details.
+ */
+
+#define DEBUG_PAIR
+
+#include <stdio.h>
+#include <X11/Intrinsic.h>
+#include <X11/IntrinsicP.h>
+#include <X11/CoreP.h>
+#include <X11/Shell.h>
+#include <X11/StringDefs.h>
+#include <X11/keysym.h>
+
+#ifdef __sgi
+# include <X11/SGIScheme.h>    /* for SgiUseSchemes() */
+#endif /* __sgi */
+
+#ifdef HAVE_XMU
+# ifndef VMS
+#  include <X11/Xmu/Error.h>
+# else /* VMS */
+#  include <Xmu/Error.h>
+# endif
+#else
+# include "xmu.h"
+#endif
+
+#include "screenhackI.h"
+#include "version.h"
+#include "vroot.h"
+#include "fps.h"
+
+#ifdef HAVE_RECORD_ANIM
+# include "recanim.h"
+#endif
+
+#ifndef _XSCREENSAVER_VROOT_H_
+# error Error!  You have an old version of vroot.h!  Check -I args.
+#endif /* _XSCREENSAVER_VROOT_H_ */
+
+#ifndef isupper
+# define isupper(c)  ((c) >= 'A' && (c) <= 'Z')
+#endif
+#ifndef _tolower
+# define _tolower(c)  ((c) - 'A' + 'a')
+#endif
+
+
+/* This is defined by the SCREENHACK_MAIN() macro via screenhack.h.
+ */
+extern struct xscreensaver_function_table *xscreensaver_function_table;
+
+
+const char *progname;   /* used by hacks in error messages */
+const char *progclass;  /* used by ../utils/resources.c */
+Bool mono_p;           /* used by hacks */
+
+#ifdef EXIT_AFTER
+static time_t exit_after;      /* Exit gracefully after N seconds */
+#endif
+
+static XrmOptionDescRec default_options [] = {
+  { "-root",   ".root",                XrmoptionNoArg, "True" },
+  { "-window", ".root",                XrmoptionNoArg, "False" },
+  { "-mono",   ".mono",                XrmoptionNoArg, "True" },
+  { "-install",        ".installColormap",     XrmoptionNoArg, "True" },
+  { "-noinstall",".installColormap",   XrmoptionNoArg, "False" },
+  { "-visual", ".visualID",            XrmoptionSepArg, 0 },
+  { "-window-id", ".windowID",         XrmoptionSepArg, 0 },
+  { "-fps",    ".doFPS",               XrmoptionNoArg, "True" },
+  { "-no-fps",  ".doFPS",              XrmoptionNoArg, "False" },
+
+# ifdef DEBUG_PAIR
+  { "-pair",   ".pair",                XrmoptionNoArg, "True" },
+# endif
+# ifdef HAVE_RECORD_ANIM
+  { "-record-animation", ".recordAnim", XrmoptionSepArg, 0 },
+# endif
+# ifdef EXIT_AFTER
+  { "-exit-after",     ".exitAfter",   XrmoptionSepArg, 0 },
+# endif
+
+  { 0, 0, 0, 0 }
+};
+
+static char *default_defaults[] = {
+  ".root:              false",
+  "*geometry:          1280x720", /* this should be .geometry, but noooo... */
+  "*mono:              false",
+  "*installColormap:   false",
+  "*doFPS:             false",
+  "*multiSample:       false",
+  "*visualID:          default",
+  "*windowID:          ",
+  "*desktopGrabber:    xscreensaver-getimage %s",
+  0
+};
+
+static XrmOptionDescRec *merged_options;
+static int merged_options_size;
+static char **merged_defaults;
+
+
+static void
+merge_options (void)
+{
+  struct xscreensaver_function_table *ft = xscreensaver_function_table;
+
+  const XrmOptionDescRec *options = ft->options;
+  const char * const *defaults    = ft->defaults;
+  const char *progclass           = ft->progclass;
+
+  int def_opts_size, opts_size;
+  int def_defaults_size, defaults_size;
+
+  for (def_opts_size = 0; default_options[def_opts_size].option;
+       def_opts_size++)
+    ;
+  for (opts_size = 0; options[opts_size].option; opts_size++)
+    ;
+
+  merged_options_size = def_opts_size + opts_size;
+  merged_options = (XrmOptionDescRec *)
+    malloc ((merged_options_size + 1) * sizeof(*default_options));
+  memcpy (merged_options, default_options,
+         (def_opts_size * sizeof(*default_options)));
+  memcpy (merged_options + def_opts_size, options,
+         ((opts_size + 1) * sizeof(*default_options)));
+
+  for (def_defaults_size = 0; default_defaults[def_defaults_size];
+       def_defaults_size++)
+    ;
+  for (defaults_size = 0; defaults[defaults_size]; defaults_size++)
+    ;
+  merged_defaults = (char **)
+    malloc ((def_defaults_size + defaults_size + 1) * sizeof (*defaults));;
+  memcpy (merged_defaults, default_defaults,
+         def_defaults_size * sizeof(*defaults));
+  memcpy (merged_defaults + def_defaults_size, defaults,
+         (defaults_size + 1) * sizeof(*defaults));
+
+  /* This totally sucks.  Xt should behave like this by default.
+     If the string in `defaults' looks like ".foo", change that
+     to "Progclass.foo".
+   */
+  {
+    char **s;
+    for (s = merged_defaults; *s; s++)
+      if (**s == '.')
+       {
+         const char *oldr = *s;
+         char *newr = (char *) malloc(strlen(oldr) + strlen(progclass) + 3);
+         strcpy (newr, progclass);
+         strcat (newr, oldr);
+         *s = newr;
+       }
+      else
+        *s = strdup (*s);
+  }
+}
+
+\f
+/* Make the X errors print out the name of this program, so we have some
+   clue which one has a bug when they die under the screensaver.
+ */
+
+static int
+screenhack_ehandler (Display *dpy, XErrorEvent *error)
+{
+  fprintf (stderr, "\nX error in %s:\n", progname);
+  if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
+    exit (-1);
+  else
+    fprintf (stderr, " (nonfatal.)\n");
+  return 0;
+}
+
+static Bool
+MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
+{
+  return (event->xany.type == MapNotify &&
+         event->xvisibility.window == (Window) window);
+}
+
+
+static Atom XA_WM_PROTOCOLS, XA_WM_DELETE_WINDOW, XA_NET_WM_PID;
+
+/* Dead-trivial event handling: exits if "q" or "ESC" are typed.
+   Exit if the WM_PROTOCOLS WM_DELETE_WINDOW ClientMessage is received.
+   Returns False if the screen saver should now terminate.
+ */
+static Bool
+screenhack_handle_event_1 (Display *dpy, XEvent *event)
+{
+  switch (event->xany.type)
+    {
+    case KeyPress:
+      {
+        KeySym keysym;
+        char c = 0;
+        XLookupString (&event->xkey, &c, 1, &keysym, 0);
+        if (c == 'q' ||
+            c == 'Q' ||
+            c == 3 ||  /* ^C */
+            c == 27)   /* ESC */
+          return False;  /* exit */
+        else if (! (keysym >= XK_Shift_L && keysym <= XK_Hyper_R))
+          XBell (dpy, 0);  /* beep for non-chord keys */
+      }
+      break;
+    case ButtonPress:
+      XBell (dpy, 0);
+      break;
+    case ClientMessage:
+      {
+        if (event->xclient.message_type != XA_WM_PROTOCOLS)
+          {
+            char *s = XGetAtomName(dpy, event->xclient.message_type);
+            if (!s) s = "(null)";
+            fprintf (stderr, "%s: unknown ClientMessage %s received!\n",
+                     progname, s);
+          }
+        else if (event->xclient.data.l[0] != XA_WM_DELETE_WINDOW)
+          {
+            char *s1 = XGetAtomName(dpy, event->xclient.message_type);
+            char *s2 = XGetAtomName(dpy, event->xclient.data.l[0]);
+            if (!s1) s1 = "(null)";
+            if (!s2) s2 = "(null)";
+            fprintf (stderr, "%s: unknown ClientMessage %s[%s] received!\n",
+                     progname, s1, s2);
+          }
+        else
+          {
+            return False;  /* exit */
+          }
+      }
+      break;
+    }
+  return True;
+}
+
+
+static Visual *
+pick_visual (Screen *screen)
+{
+  struct xscreensaver_function_table *ft = xscreensaver_function_table;
+
+  if (ft->pick_visual_hook)
+    {
+      Visual *v = ft->pick_visual_hook (screen);
+      if (v) return v;
+    }
+
+  return get_visual_resource (screen, "visualID", "VisualID", False);
+}
+
+
+/* Notice when the user has requested a different visual or colormap
+   on a pre-existing window (e.g., "-root -visual truecolor" or
+   "-window-id 0x2c00001 -install") and complain, since when drawing
+   on an existing window, we have no choice about these things.
+ */
+static void
+visual_warning (Screen *screen, Window window, Visual *visual, Colormap cmap,
+                Bool window_p)
+{
+  struct xscreensaver_function_table *ft = xscreensaver_function_table;
+
+  char *visual_string = get_string_resource (DisplayOfScreen (screen),
+                                             "visualID", "VisualID");
+  Visual *desired_visual = pick_visual (screen);
+  char win[100];
+  char why[100];
+
+  if (window == RootWindowOfScreen (screen))
+    strcpy (win, "root window");
+  else
+    sprintf (win, "window 0x%lx", (unsigned long) window);
+
+  if (window_p)
+    sprintf (why, "-window-id 0x%lx", (unsigned long) window);
+  else
+    strcpy (why, "-root");
+
+  if (visual_string && *visual_string)
+    {
+      char *s;
+      for (s = visual_string; *s; s++)
+        if (isupper (*s)) *s = _tolower (*s);
+
+      if (!strcmp (visual_string, "default") ||
+          !strcmp (visual_string, "default") ||
+          !strcmp (visual_string, "best"))
+        /* don't warn about these, just silently DWIM. */
+        ;
+      else if (visual != desired_visual)
+        {
+          fprintf (stderr, "%s: ignoring `-visual %s' because of `%s'.\n",
+                   progname, visual_string, why);
+          fprintf (stderr, "%s: using %s's visual 0x%lx.\n",
+                   progname, win, XVisualIDFromVisual (visual));
+        }
+      free (visual_string);
+    }
+
+  if (visual == DefaultVisualOfScreen (screen) &&
+      has_writable_cells (screen, visual) &&
+      get_boolean_resource (DisplayOfScreen (screen),
+                            "installColormap", "InstallColormap"))
+    {
+      fprintf (stderr, "%s: ignoring `-install' because of `%s'.\n",
+               progname, why);
+      fprintf (stderr, "%s: using %s's colormap 0x%lx.\n",
+               progname, win, (unsigned long) cmap);
+    }
+
+  if (ft->validate_visual_hook)
+    {
+      if (! ft->validate_visual_hook (screen, win, visual))
+        exit (1);
+    }
+}
+
+
+static void
+fix_fds (void)
+{
+  /* Bad Things Happen if stdin, stdout, and stderr have been closed
+     (as by the `sh incantation "attraction >&- 2>&-").  When you do
+     that, the X connection gets allocated to one of these fds, and
+     then some random library writes to stderr, and random bits get
+     stuffed down the X pipe, causing "Xlib: sequence lost" errors.
+     So, we cause the first three file descriptors to be open to
+     /dev/null if they aren't open to something else already.  This
+     must be done before any other files are opened (or the closing
+     of that other file will again free up one of the "magic" first
+     three FDs.)
+
+     We do this by opening /dev/null three times, and then closing
+     those fds, *unless* any of them got allocated as #0, #1, or #2,
+     in which case we leave them open.  Gag.
+
+     Really, this crap is technically required of *every* X program,
+     if you want it to be robust in the face of "2>&-".
+   */
+  int fd0 = open ("/dev/null", O_RDWR);
+  int fd1 = open ("/dev/null", O_RDWR);
+  int fd2 = open ("/dev/null", O_RDWR);
+  if (fd0 > 2) close (fd0);
+  if (fd1 > 2) close (fd1);
+  if (fd2 > 2) close (fd2);
+}
+
+
+static Boolean
+screenhack_table_handle_events (Display *dpy,
+                                const struct xscreensaver_function_table *ft,
+                                Window window, void *closure
+#ifdef DEBUG_PAIR
+                                , Window window2, void *closure2
+#endif
+                                )
+{
+  XtAppContext app = XtDisplayToApplicationContext (dpy);
+
+  if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
+    XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
+
+  while (XPending (dpy))
+    {
+      XEvent event;
+      XNextEvent (dpy, &event);
+
+      if (event.xany.type == ConfigureNotify)
+        {
+          if (event.xany.window == window)
+            ft->reshape_cb (dpy, window, closure,
+                            event.xconfigure.width, event.xconfigure.height);
+#ifdef DEBUG_PAIR
+          if (window2 && event.xany.window == window2)
+            ft->reshape_cb (dpy, window2, closure2,
+                            event.xconfigure.width, event.xconfigure.height);
+#endif
+        }
+      else if (event.xany.type == ClientMessage ||
+               (! (event.xany.window == window
+                   ? ft->event_cb (dpy, window, closure, &event)
+#ifdef DEBUG_PAIR
+                   : (window2 && event.xany.window == window2)
+                   ? ft->event_cb (dpy, window2, closure2, &event)
+#endif
+                   : 0)))
+        if (! screenhack_handle_event_1 (dpy, &event))
+          return False;
+
+      if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
+        XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
+    }
+
+# ifdef EXIT_AFTER
+  if (exit_after != 0 && time ((time_t *) 0) >= exit_after)
+    return False;
+# endif
+
+  return True;
+}
+
+
+static Boolean
+usleep_and_process_events (Display *dpy,
+                           const struct xscreensaver_function_table *ft,
+                           Window window, fps_state *fpst, void *closure,
+                           unsigned long delay
+#ifdef DEBUG_PAIR
+                         , Window window2, fps_state *fpst2, void *closure2,
+                           unsigned long delay2
+#endif
+# ifdef HAVE_RECORD_ANIM
+                         , record_anim_state *anim_state
+# endif
+                           )
+{
+  do {
+    unsigned long quantum = 33333;  /* 30 fps */
+    if (quantum > delay) 
+      quantum = delay;
+    delay -= quantum;
+
+    XSync (dpy, False);
+
+#ifdef HAVE_RECORD_ANIM
+    if (anim_state) screenhack_record_anim (anim_state);
+#endif
+
+    if (quantum > 0)
+      {
+        usleep (quantum);
+        if (fpst) fps_slept (fpst, quantum);
+#ifdef DEBUG_PAIR
+        if (fpst2) fps_slept (fpst2, quantum);
+#endif
+      }
+
+    if (! screenhack_table_handle_events (dpy, ft, window, closure
+#ifdef DEBUG_PAIR
+                                          , window2, closure2
+#endif
+                                          ))
+      return False;
+  } while (delay > 0);
+
+  return True;
+}
+
+
+static void
+screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
+{
+  fps_compute (fpst, 0, -1);
+  fps_draw (fpst);
+}
+
+
+static void
+run_screenhack_table (Display *dpy, 
+                      Window window,
+# ifdef DEBUG_PAIR
+                      Window window2,
+# endif
+# ifdef HAVE_RECORD_ANIM
+                      record_anim_state *anim_state,
+# endif
+                      const struct xscreensaver_function_table *ft)
+{
+
+  /* Kludge: even though the init_cb functions are declared to take 2 args,
+     actually call them with 3, for the benefit of xlockmore_init() and
+     xlockmore_setup().
+   */
+  void *(*init_cb) (Display *, Window, void *) = 
+    (void *(*) (Display *, Window, void *)) ft->init_cb;
+
+  void (*fps_cb) (Display *, Window, fps_state *, void *) = ft->fps_cb;
+
+  void *closure = init_cb (dpy, window, ft->setup_arg);
+  fps_state *fpst = fps_init (dpy, window);
+  unsigned long delay = 0;
+
+#ifdef DEBUG_PAIR
+  void *closure2 = 0;
+  fps_state *fpst2 = 0;
+  unsigned long delay2 = 0;
+  if (window2) closure2 = init_cb (dpy, window2, ft->setup_arg);
+  if (window2) fpst2 = fps_init (dpy, window2);
+#endif
+
+  if (! closure)  /* if it returns nothing, it can't possibly be re-entrant. */
+    abort();
+
+  if (! fps_cb) fps_cb = screenhack_do_fps;
+
+  while (1)
+    {
+      if (! usleep_and_process_events (dpy, ft,
+                                       window, fpst, closure, delay
+#ifdef DEBUG_PAIR
+                                       , window2, fpst2, closure2, delay2
+#endif
+#ifdef HAVE_RECORD_ANIM
+                                       , anim_state
+#endif
+                                       ))
+        break;
+
+      delay = ft->draw_cb (dpy, window, closure);
+#ifdef DEBUG_PAIR
+      delay2 = 0;
+      if (window2) delay2 = ft->draw_cb (dpy, window2, closure2);
+#endif
+
+      if (fpst) fps_cb (dpy, window, fpst, closure);
+#ifdef DEBUG_PAIR
+      if (fpst2) fps_cb (dpy, window2, fpst2, closure2);
+#endif
+    }
+
+#ifdef HAVE_RECORD_ANIM
+  /* Exiting before target frames hit: write the video anyway. */
+  if (anim_state) screenhack_record_anim_free (anim_state);
+#endif
+
+  ft->free_cb (dpy, window, closure);
+  if (fpst) ft->fps_free (fpst);
+
+#ifdef DEBUG_PAIR
+  if (window2) ft->free_cb (dpy, window2, closure2);
+  if (fpst2) ft->fps_free (fpst2);
+#endif
+}
+
+
+static Widget
+make_shell (Screen *screen, Widget toplevel, int width, int height)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  Visual *visual = pick_visual (screen);
+  Boolean def_visual_p = (toplevel && 
+                          visual == DefaultVisualOfScreen (screen));
+
+  if (width  <= 0) width  = 600;
+  if (height <= 0) height = 480;
+
+  if (def_visual_p)
+    {
+      Window window;
+      XtVaSetValues (toplevel,
+                     XtNmappedWhenManaged, False,
+                     XtNwidth, width,
+                     XtNheight, height,
+                     XtNinput, True,  /* for WM_HINTS */
+                     NULL);
+      XtRealizeWidget (toplevel);
+      window = XtWindow (toplevel);
+
+      if (get_boolean_resource (dpy, "installColormap", "InstallColormap"))
+        {
+          Colormap cmap = 
+            XCreateColormap (dpy, window, DefaultVisualOfScreen (screen),
+                             AllocNone);
+          XSetWindowColormap (dpy, window, cmap);
+        }
+    }
+  else
+    {
+      unsigned int bg, bd;
+      Widget new;
+      Colormap cmap = XCreateColormap (dpy, VirtualRootWindowOfScreen(screen),
+                                       visual, AllocNone);
+      bg = get_pixel_resource (dpy, cmap, "background", "Background");
+      bd = get_pixel_resource (dpy, cmap, "borderColor", "Foreground");
+
+      new = XtVaAppCreateShell (progname, progclass,
+                                topLevelShellWidgetClass, dpy,
+                                XtNmappedWhenManaged, False,
+                                XtNvisual, visual,
+                                XtNdepth, visual_depth (screen, visual),
+                                XtNwidth, width,
+                                XtNheight, height,
+                                XtNcolormap, cmap,
+                                XtNbackground, (Pixel) bg,
+                                XtNborderColor, (Pixel) bd,
+                                XtNinput, True,  /* for WM_HINTS */
+                                NULL);
+
+      if (!toplevel)  /* kludge for the second window in -pair mode... */
+        XtVaSetValues (new, XtNx, 0, XtNy, 550, NULL);
+
+      XtRealizeWidget (new);
+      toplevel = new;
+    }
+
+  return toplevel;
+}
+
+static void
+init_window (Display *dpy, Widget toplevel, const char *title)
+{
+  Window window;
+  XWindowAttributes xgwa;
+  long pid = getpid();
+  XtPopup (toplevel, XtGrabNone);
+  XtVaSetValues (toplevel, XtNtitle, title, NULL);
+
+  /* Select KeyPress, and announce that we accept WM_DELETE_WINDOW.
+   */
+  window = XtWindow (toplevel);
+  XGetWindowAttributes (dpy, window, &xgwa);
+  XSelectInput (dpy, window,
+                (xgwa.your_event_mask | KeyPressMask | KeyReleaseMask |
+                 ButtonPressMask | ButtonReleaseMask));
+  XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
+                   PropModeReplace,
+                   (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
+  XChangeProperty (dpy, window, XA_NET_WM_PID, XA_CARDINAL, 32,
+                   PropModeReplace, (unsigned char *)&pid, 1);
+}
+
+
+int
+main (int argc, char **argv)
+{
+  struct xscreensaver_function_table *ft = xscreensaver_function_table;
+
+  XWindowAttributes xgwa;
+  Widget toplevel;
+  Display *dpy;
+  Window window;
+# ifdef DEBUG_PAIR
+  Window window2 = 0;
+  Widget toplevel2 = 0;
+# endif
+# ifdef HAVE_RECORD_ANIM
+  record_anim_state *anim_state = 0;
+# endif
+  XtAppContext app;
+  Bool root_p;
+  Window on_window = 0;
+  XEvent event;
+  Boolean dont_clear;
+  char version[255];
+
+  fix_fds();
+
+  progname = argv[0];   /* reset later */
+  progclass = ft->progclass;
+
+  if (ft->setup_cb)
+    ft->setup_cb (ft, ft->setup_arg);
+
+  merge_options ();
+
+#ifdef __sgi
+  /* We have to do this on SGI to prevent the background color from being
+     overridden by the current desktop color scheme (we'd like our backgrounds
+     to be black, thanks.)  This should be the same as setting the
+     "*useSchemes: none" resource, but it's not -- if that resource is
+     present in the `default_defaults' above, it doesn't work, though it
+     does work when passed as an -xrm arg on the command line.  So screw it,
+     turn them off from C instead.
+   */
+  SgiUseSchemes ("none"); 
+#endif /* __sgi */
+
+  toplevel = XtAppInitialize (&app, progclass, merged_options,
+                             merged_options_size, &argc, argv,
+                             merged_defaults, 0, 0);
+
+  dpy = XtDisplay (toplevel);
+
+  XtGetApplicationNameAndClass (dpy,
+                                (char **) &progname,
+                                (char **) &progclass);
+
+  /* half-assed way of avoiding buffer-overrun attacks. */
+  if (strlen (progname) >= 100) ((char *) progname)[100] = 0;
+
+  XSetErrorHandler (screenhack_ehandler);
+
+  XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
+  XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
+  XA_NET_WM_PID = XInternAtom (dpy, "_NET_WM_PID", False);
+
+  {
+    char *v = (char *) strdup(strchr(screensaver_id, ' '));
+    char *s1, *s2, *s3, *s4;
+    const char *ot = get_string_resource (dpy, "title", "Title");
+    s1 = (char *) strchr(v,  ' '); s1++;
+    s2 = (char *) strchr(s1, ' ');
+    s3 = (char *) strchr(v,  '('); s3++;
+    s4 = (char *) strchr(s3, ')');
+    *s2 = 0;
+    *s4 = 0;
+    if (ot && !*ot) ot = 0;
+    sprintf (version, "%.50s%s%s: from the XScreenSaver %s distribution (%s)",
+             (ot ? ot : ""),
+             (ot ? ": " : ""),
+            progclass, s1, s3);
+    free(v);
+  }
+
+  if (argc > 1)
+    {
+      const char *s;
+      int i;
+      int x = 18;
+      int end = 78;
+      Bool help_p = (!strcmp(argv[1], "-help") ||
+                     !strcmp(argv[1], "--help"));
+      fprintf (stderr, "%s\n", version);
+      for (s = progclass; *s; s++) fprintf(stderr, " ");
+      fprintf (stderr, "  https://www.jwz.org/xscreensaver/\n\n");
+
+      if (!help_p)
+       fprintf(stderr, "Unrecognised option: %s\n", argv[1]);
+      fprintf (stderr, "Options include: ");
+      for (i = 0; i < merged_options_size; i++)
+       {
+         char *sw = merged_options [i].option;
+         Bool argp = (merged_options [i].argKind == XrmoptionSepArg);
+         int size = strlen (sw) + (argp ? 6 : 0) + 2;
+         if (x + size >= end)
+           {
+             fprintf (stderr, "\n\t\t ");
+             x = 18;
+           }
+         x += size;
+         fprintf (stderr, "%s", sw);
+         if (argp) fprintf (stderr, " <arg>");
+         if (i != merged_options_size - 1) fprintf (stderr, ", ");
+       }
+
+      fprintf (stderr, ".\n");
+
+#if 0
+      if (help_p)
+        {
+          fprintf (stderr, "\nResources:\n\n");
+          for (i = 0; i < merged_options_size; i++)
+            {
+              const char *opt = merged_options [i].option;
+              const char *res = merged_options [i].specifier + 1;
+              const char *val = merged_options [i].value;
+              char *s = get_string_resource (dpy, (char *) res, (char *) res);
+
+              if (s)
+                {
+                  int L = strlen(s);
+                while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
+                  s[--L] = 0;
+                }
+
+              fprintf (stderr, "    %-16s %-18s ", opt, res);
+              if (merged_options [i].argKind == XrmoptionSepArg)
+                {
+                  fprintf (stderr, "[%s]", (s ? s : "?"));
+                }
+              else
+                {
+                  fprintf (stderr, "%s", (val ? val : "(null)"));
+                  if (val && s && !strcasecmp (val, s))
+                    fprintf (stderr, " [default]");
+                }
+              fprintf (stderr, "\n");
+            }
+          fprintf (stderr, "\n");
+        }
+#endif
+
+      exit (help_p ? 0 : 1);
+    }
+
+  {
+    char **s;
+    for (s = merged_defaults; *s; s++)
+      free(*s);
+  }
+
+  free (merged_options);
+  free (merged_defaults);
+  merged_options = 0;
+  merged_defaults = 0;
+
+  dont_clear = get_boolean_resource (dpy, "dontClearRoot", "Boolean");
+  mono_p = get_boolean_resource (dpy, "mono", "Boolean");
+  if (CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2)
+    mono_p = True;
+
+  root_p = get_boolean_resource (dpy, "root", "Boolean");
+
+# ifdef EXIT_AFTER
+  {
+    int secs = get_integer_resource (dpy, "exitAfter", "Integer");
+    exit_after = (secs > 0
+                  ? time((time_t *) 0) + secs
+                  : 0);
+  }      
+# endif
+
+  {
+    char *s = get_string_resource (dpy, "windowID", "WindowID");
+    if (s && *s)
+      on_window = get_integer_resource (dpy, "windowID", "WindowID");
+    if (s) free (s);
+  }
+
+  if (on_window)
+    {
+      window = (Window) on_window;
+      XtDestroyWidget (toplevel);
+      XGetWindowAttributes (dpy, window, &xgwa);
+      visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, True);
+
+      /* Select KeyPress and resize events on the external window.
+       */
+      xgwa.your_event_mask |= KeyPressMask | StructureNotifyMask;
+      XSelectInput (dpy, window, xgwa.your_event_mask);
+
+      /* Select ButtonPress and ButtonRelease events on the external window,
+         if no other app has already selected them (only one app can select
+         ButtonPress at a time: BadAccess results.)
+       */
+      if (! (xgwa.all_event_masks & (ButtonPressMask | ButtonReleaseMask)))
+        XSelectInput (dpy, window,
+                      (xgwa.your_event_mask |
+                       ButtonPressMask | ButtonReleaseMask));
+    }
+  else if (root_p)
+    {
+      window = VirtualRootWindowOfScreen (XtScreen (toplevel));
+      XtDestroyWidget (toplevel);
+      XGetWindowAttributes (dpy, window, &xgwa);
+      /* With RANDR, the root window can resize! */
+      XSelectInput (dpy, window, xgwa.your_event_mask | StructureNotifyMask);
+      visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, False);
+    }
+  else
+    {
+      Widget new = make_shell (XtScreen (toplevel), toplevel,
+                               toplevel->core.width,
+                               toplevel->core.height);
+      if (new != toplevel)
+        {
+          XtDestroyWidget (toplevel);
+          toplevel = new;
+        }
+
+      init_window (dpy, toplevel, version);
+      window = XtWindow (toplevel);
+      XGetWindowAttributes (dpy, window, &xgwa);
+
+# ifdef DEBUG_PAIR
+      if (get_boolean_resource (dpy, "pair", "Boolean"))
+        {
+          toplevel2 = make_shell (xgwa.screen, 0,
+                                  toplevel->core.width,
+                                  toplevel->core.height);
+          init_window (dpy, toplevel2, version);
+          window2 = XtWindow (toplevel2);
+        }
+# endif /* DEBUG_PAIR */
+    }
+
+  if (!dont_clear)
+    {
+      unsigned int bg = get_pixel_resource (dpy, xgwa.colormap,
+                                            "background", "Background");
+      XSetWindowBackground (dpy, window, bg);
+      XClearWindow (dpy, window);
+# ifdef DEBUG_PAIR
+      if (window2)
+        {
+          XSetWindowBackground (dpy, window2, bg);
+          XClearWindow (dpy, window2);
+        }
+# endif
+    }
+
+  if (!root_p && !on_window)
+    /* wait for it to be mapped */
+    XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
+
+  XSync (dpy, False);
+
+  /* This is the one and only place that the random-number generator is
+     seeded in any screenhack.  You do not need to seed the RNG again,
+     it is done for you before your code is invoked. */
+# undef ya_rand_init
+  ya_rand_init (0);
+
+
+#ifdef HAVE_RECORD_ANIM
+  {
+    int frames = get_integer_resource (dpy, "recordAnim", "Integer");
+    if (frames > 0)
+      anim_state = screenhack_record_anim_init (xgwa.screen, window, frames);
+  }
+#endif
+
+  run_screenhack_table (dpy, window, 
+# ifdef DEBUG_PAIR
+                        window2,
+# endif
+# ifdef HAVE_RECORD_ANIM
+                        anim_state,
+# endif
+                        ft);
+
+#ifdef HAVE_RECORD_ANIM
+  if (anim_state) screenhack_record_anim_free (anim_state);
+#endif
+
+  XtDestroyWidget (toplevel);
+  XtDestroyApplicationContext (app);
+
+  return 0;
+}
diff --git a/screenhack/screenhack.h b/screenhack/screenhack.h
new file mode 100644 (file)
index 0000000..16e629b
--- /dev/null
@@ -0,0 +1,68 @@
+/* xscreensaver, Copyright (c) 1992-2018 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifndef __SCREENHACK_H__
+#define __SCREENHACK_H__
+
+#include "screenhackI.h"
+
+/* In an Xlib world, we define two global symbols here:
+   a struct in `MODULENAME_xscreensaver_function_table',
+   and a pointer to that in `xscreensaver_function_table'.
+
+   In a Cocoa/Android world, we only define the prefixed symbol;
+   the un-prefixed symbol does not exist.
+ */
+#ifdef HAVE_JWXYZ
+# define XSCREENSAVER_LINK(NAME)
+#else
+# define XSCREENSAVER_LINK(NAME) \
+   struct xscreensaver_function_table *xscreensaver_function_table = &NAME;
+#endif
+
+
+#if defined(HAVE_JWXYZ) && !defined(__XLOCKMORE_INTERNAL_H__)
+  /* this is one enormous kludge... */
+# undef ya_rand_init
+  static void
+  xscreensaver_common_setup(struct xscreensaver_function_table *xsft, void *a)
+  { ya_rand_init(0); }
+#else
+# define xscreensaver_common_setup 0
+#endif
+
+
+#ifdef HAVE_JWXYZ
+# define SCREENHACK_VISUAL DEFAULT_VISUAL
+#else /* !HAVE_JWXYZ */
+# define SCREENHACK_VISUAL 0, 0
+#endif /* !HAVE_JWXYZ */
+
+#define XSCREENSAVER_MODULE_2(CLASS,NAME,PREFIX)               \
+  struct xscreensaver_function_table                           \
+        NAME ## _xscreensaver_function_table = {               \
+          CLASS,                                               \
+          PREFIX ## _defaults,                                 \
+          PREFIX ## _options,                                  \
+          xscreensaver_common_setup, 0,                        \
+          PREFIX ## _init,                                     \
+          PREFIX ## _draw,                                     \
+          PREFIX ## _reshape,                                  \
+          PREFIX ## _event,                                    \
+          PREFIX ## _free,                                     \
+          0, fps_free,                                         \
+          SCREENHACK_VISUAL };                                 \
+  XSCREENSAVER_LINK (NAME ## _xscreensaver_function_table)
+
+#define XSCREENSAVER_MODULE(CLASS,PREFIX)                      \
+      XSCREENSAVER_MODULE_2(CLASS,PREFIX,PREFIX)
+
+#endif /* __SCREENHACK_H__ */
diff --git a/screenhack/screenhackI.h b/screenhack/screenhackI.h
new file mode 100644 (file)
index 0000000..36b0635
--- /dev/null
@@ -0,0 +1,163 @@
+/* xscreensaver, Copyright (c) 1992-2018 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/* Found in Don Hopkins' .plan file:
+ *
+ *   The color situation is a total flying circus.  The X approach to
+ *   device independence is to treat everything like a MicroVax framebuffer
+ *   on acid.  A truely portable X application is required to act like the
+ *   persistent customer in the Monty Python ``Cheese Shop'' sketch.  Even
+ *   the simplest applications must answer many difficult questions, like:
+ *
+ *   WHAT IS YOUR DISPLAY?
+ *       display = XOpenDisplay("unix:0");
+ *   WHAT IS YOUR ROOT?
+ *       root = RootWindow(display, DefaultScreen(display));
+ *   AND WHAT IS YOUR WINDOW?
+ *       win = XCreateSimpleWindow(display, root, 0, 0, 256, 256, 1,
+ *                                 BlackPixel(display, DefaultScreen(display)),
+ *                                 WhitePixel(display, DefaultScreen(display)))
+ *   OH ALL RIGHT, YOU CAN GO ON.
+ *
+ *   WHAT IS YOUR DISPLAY?
+ *         display = XOpenDisplay("unix:0");
+ *   WHAT IS YOUR COLORMAP?
+ *         cmap = DefaultColormap(display, DefaultScreen(display));
+ *   AND WHAT IS YOUR FAVORITE COLOR?
+ *         favorite_color = 0; / * Black. * /
+ *         / * Whoops! No, I mean: * /
+ *         favorite_color = BlackPixel(display, DefaultScreen(display));
+ *         / * AAAYYYYEEEEE!! (client dumps core & falls into the chasm) * /
+ *
+ *   WHAT IS YOUR DISPLAY?
+ *         display = XOpenDisplay("unix:0");
+ *   WHAT IS YOUR VISUAL?
+ *         struct XVisualInfo vinfo;
+ *         if (XMatchVisualInfo(display, DefaultScreen(display),
+ *                              8, PseudoColor, &vinfo) != 0)
+ *            visual = vinfo.visual;
+ *   AND WHAT IS THE NET SPEED VELOCITY OF AN XConfigureWindow REQUEST?
+ *         / * Is that a SubStructureRedirectMask or a ResizeRedirectMask? * /
+ *   WHAT?! HOW AM I SUPPOSED TO KNOW THAT?
+ *   AAAAUUUGGGHHH!!!! (server dumps core & falls into the chasm)
+ */
+
+#ifndef __SCREENHACK_I_H__
+#define __SCREENHACK_I_H__
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <time.h>
+
+#ifdef __hpux
+ /* Which of the ten billion standards does values.h belong to?
+    What systems always have it? */
+# include <values.h>
+#endif
+
+#ifdef HAVE_JWXYZ
+# include "jwxyz.h"
+# include <string.h> /* X11/Xos.h brings this in. */
+/* From utils/visual.c. */
+# define DEFAULT_VISUAL        -1
+# define GL_VISUAL     -6
+#else  /* real X11 */
+# include <X11/Xlib.h>
+# include <X11/Xutil.h>
+# include <X11/Xresource.h>
+# include <X11/Xos.h>
+#endif /* !HAVE_JWXYZ */
+
+#if defined(HAVE_IPHONE) || defined(HAVE_ANDROID)
+# define HAVE_MOBILE
+#endif
+
+#ifdef HAVE_ANDROID
+ /* So that hacks' debug output shows up in logcat... */
+# undef  fprintf
+# define fprintf(S, ...) Log(__VA_ARGS__)
+#endif
+
+/* M_PI ought to have been defined in math.h, but... */
+#ifndef M_PI
+# define M_PI 3.1415926535
+#endif
+
+#ifndef M_PI_2
+# define M_PI_2 1.5707963267
+#endif
+
+#ifndef Button6
+# define Button6 6
+# define Button7 7
+#endif
+
+#include "yarandom.h"
+#include "usleep.h"
+#include "resources.h"
+#include "hsv.h"
+#include "colors.h"
+#include "grabscreen.h"
+#include "visual.h"
+#include "fps.h"
+#include "font-retry.h"
+
+#ifdef HAVE_RECORD_ANIM
+# include "recanim.h"
+#endif
+
+/* Be Posixly correct */
+#undef  bzero
+#define bzero  __ERROR_use_memset_not_bzero_in_xscreensaver__
+#undef  bcopy
+#define bcopy  __ERROR_use_memcpy_not_bcopy_in_xscreensaver__
+#undef  ftime
+#define ftime  __ERROR_use_gettimeofday_not_ftime_in_xscreensaver__
+#undef  sleep
+#define sleep  __ERROR_do_not_sleep_in_xscreensaver__
+
+extern Bool mono_p;
+
+struct xscreensaver_function_table {
+
+  const char *progclass;
+  const char * const *defaults;
+  const XrmOptionDescRec *options;
+
+  void           (*setup_cb)   (struct xscreensaver_function_table *, void *);
+  void *         setup_arg;
+
+  void *         (*init_cb)    (Display *, Window);
+  unsigned long  (*draw_cb)    (Display *, Window, void *);
+  void           (*reshape_cb) (Display *, Window, void *,
+                                unsigned int w, unsigned int h);
+  Bool           (*event_cb)   (Display *, Window, void *, XEvent *);
+  void           (*free_cb)    (Display *, Window, void *);
+  void           (*fps_cb)     (Display *, Window, fps_state *, void *);
+  void           (*fps_free)   (fps_state *);
+
+# ifndef HAVE_JWXYZ
+  Visual *       (*pick_visual_hook) (Screen *);
+  Bool           (*validate_visual_hook) (Screen *, const char *, Visual *);
+# else
+  int            visual;
+# endif
+
+};
+
+extern const char *progname;
+
+#endif /* __SCREENHACK_I_H__ */
diff --git a/screenhack/spline.c b/screenhack/spline.c
new file mode 100644 (file)
index 0000000..1a73ea6
--- /dev/null
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) 1987, 1988, 1989 Stanford University
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of Stanford not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  Stanford makes no representations about
+ * the suitability of this software for any purpose.  It is provided "as is"
+ * without express or implied warranty.
+ *
+ * STANFORD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+ * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+ * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* This code came with the InterViews distribution, and was translated
+   from C++ to C by Matthieu Devin <devin@lucid.com> some time in 1992.
+ */
+
+#include "utils.h"
+#include "spline.h"
+
+#define SMOOTHNESS 1.0
+
+static void grow_spline_points (spline* s);
+static void mid_point (double x0, double y0, double x1, double y1,
+                      double *mx, double *my);
+static int can_approx_with_line (double x0, double y0, double x2,
+                                double y2, double x3, double y3);
+static void add_line (spline* s, double x0, double y0, double x1, double y1);
+static void add_bezier_arc (spline* s,
+                           double x0, double y0, double x1, double y1,
+                           double x2, double y2, double x3, double y3);
+static void third_point (double x0, double y0, double x1, double y1,
+                        double *tx, double *ty);
+static void calc_section (spline* s, double cminus1x, double cminus1y,
+                         double cx, double cy, double cplus1x, double cplus1y,
+                         double cplus2x, double cplus2y);
+
+spline*
+make_spline (unsigned int size)
+{
+  spline* s = (spline*)calloc (1, sizeof (spline));
+  if (!s) abort();
+  s->n_controls = size;
+  s->control_x = (double*)calloc (s->n_controls, sizeof (double));
+  s->control_y = (double*)calloc (s->n_controls, sizeof (double));
+
+  s->n_points = 0;
+  s->allocated_points = s->n_controls;
+  s->points = (XPoint*)calloc (s->allocated_points, sizeof (XPoint));
+
+  if (!s->control_x || !s->control_y || !s->points)
+    abort();
+
+  return s;
+}
+
+static void
+grow_spline_points (spline *s)
+{
+  s->allocated_points = 10 + (s->allocated_points * 1.3);
+  s->points =
+    (XPoint*)realloc (s->points, s->allocated_points * sizeof (XPoint));
+  if (!s->points) abort();
+}
+
+static void 
+mid_point (double x0, double y0,
+          double x1, double y1,
+          double *mx, double *my)
+{
+  *mx = (x0 + x1) / 2.0;
+  *my = (y0 + y1) / 2.0;
+}
+
+static void 
+third_point (double x0, double y0,
+            double x1, double y1,
+            double *tx, double *ty)
+{
+  *tx = (2 * x0 + x1) / 3.0;
+  *ty = (2 * y0 + y1) / 3.0;
+}
+
+static int
+can_approx_with_line (double x0, double y0,
+                     double x2, double y2,
+                     double x3, double y3)
+{
+  double triangle_area, side_squared, dx, dy;
+  
+  triangle_area = x0 * y2 - x2 * y0 + x2 * y3 - x3 * y2 + x3 * y0 - x0 * y3;
+  /* actually 4 times the area. */
+  triangle_area *= triangle_area;
+  dx = x3 - x0;
+  dy = y3 - y0;
+  side_squared = dx * dx + dy * dy;
+  return triangle_area <= SMOOTHNESS * side_squared;
+}
+
+static void
+add_line (spline *s,
+         double x0, double y0,
+         double x1, double y1)
+{
+  if (s->n_points >= s->allocated_points)
+    grow_spline_points (s);
+
+  if (s->n_points == 0)
+    {
+      s->points [s->n_points].x = x0;
+      s->points [s->n_points].y = y0;
+      s->n_points += 1;
+    }
+  s->points [s->n_points].x = x1;
+  s->points [s->n_points].y = y1;
+  s->n_points += 1;
+}
+
+static void 
+add_bezier_arc (spline *s,
+               double x0, double y0,
+               double x1, double y1,
+               double x2, double y2,
+               double x3, double y3)
+{
+  double midx01, midx12, midx23, midlsegx, midrsegx, cx,
+  midy01, midy12, midy23, midlsegy, midrsegy, cy;
+    
+  mid_point (x0, y0, x1, y1, &midx01, &midy01);
+  mid_point (x1, y1, x2, y2, &midx12, &midy12);
+  mid_point (x2, y2, x3, y3, &midx23, &midy23);
+  mid_point (midx01, midy01, midx12, midy12, &midlsegx, &midlsegy);
+  mid_point (midx12, midy12, midx23, midy23, &midrsegx, &midrsegy);
+  mid_point (midlsegx, midlsegy, midrsegx, midrsegy, &cx, &cy);    
+
+  if (can_approx_with_line (x0, y0, midlsegx, midlsegy, cx, cy))
+    add_line (s, x0, y0, cx, cy);
+  else if ((midx01 != x1) || (midy01 != y1) || (midlsegx != x2)
+          || (midlsegy != y2) || (cx != x3) || (cy != y3))
+    add_bezier_arc (s, x0, y0, midx01, midy01, midlsegx, midlsegy, cx, cy);
+
+  if (can_approx_with_line (cx, cy, midx23, midy23, x3, y3))
+    add_line (s, cx, cy, x3, y3);
+  else if ((cx != x0) || (cy != y0) || (midrsegx != x1) || (midrsegy != y1)
+          || (midx23 != x2) || (midy23 != y2))  
+    add_bezier_arc (s, cx, cy, midrsegx, midrsegy, midx23, midy23, x3, y3);
+}
+
+static void
+calc_section (spline *s,
+             double cminus1x, double cminus1y,
+             double cx, double cy,
+             double cplus1x, double cplus1y,
+             double cplus2x, double cplus2y)
+{
+  double p0x, p1x, p2x, p3x, tempx,
+  p0y, p1y, p2y, p3y, tempy;
+  
+  third_point (cx, cy, cplus1x, cplus1y, &p1x, &p1y);
+  third_point (cplus1x, cplus1y, cx, cy, &p2x, &p2y);
+  third_point (cx, cy, cminus1x, cminus1y, &tempx, &tempy);
+  mid_point (tempx, tempy, p1x, p1y, &p0x, &p0y);
+  third_point (cplus1x, cplus1y, cplus2x, cplus2y, &tempx, &tempy);
+  mid_point (tempx, tempy, p2x, p2y, &p3x, &p3y);
+  add_bezier_arc (s, p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y);
+}
+
+void
+compute_spline (spline *s)
+{
+  int i;
+  s->n_points = 0;
+  
+  if (s->n_controls < 3)
+    return;
+
+  calc_section (s, s->control_x [0], s->control_y [0], s->control_x [0],
+               s->control_y [0], s->control_x [0], s->control_y [0],
+               s->control_x [1], s->control_y [1]);
+  calc_section (s, s->control_x [0], s->control_y [0], s->control_x [0],
+               s->control_y [0], s->control_x [1], s->control_y [1],
+               s->control_x [2], s->control_y [2]);
+
+  for (i = 1; i < s->n_controls - 2; i++)
+    calc_section (s, s->control_x [i - 1], s->control_y [i - 1],
+                 s->control_x [i], s->control_y [i],
+                 s->control_x [i + 1], s->control_y [i + 1],
+                 s->control_x [i + 2], s->control_y [i + 2]);
+  
+  calc_section (s, s->control_x [i - 1], s->control_y [i - 1],
+               s->control_x [i], s->control_y [i],
+               s->control_x [i + 1], s->control_y [i + 1],
+               s->control_x [i + 1], s->control_y [i + 1]);
+  calc_section (s, s->control_x [i], s->control_y [i],
+               s->control_x [i + 1], s->control_y [i + 1],
+               s->control_x [i + 1], s->control_y [i + 1],
+               s->control_x [i + 1], s->control_y [i + 1]);
+}
+
+void 
+compute_closed_spline (spline *s)
+{
+  int i;
+  s->n_points = 0;
+
+  if (s->n_controls < 3) 
+    return;
+
+  calc_section (s,
+               s->control_x [s->n_controls - 1],
+               s->control_y [s->n_controls - 1],
+               s->control_x [0], s->control_y [0],
+               s->control_x [1], s->control_y [1],
+               s->control_x [2], s->control_y [2]);
+
+  for (i = 1; i < s->n_controls - 2; i++)
+    calc_section (s, s->control_x [i - 1], s->control_y [i - 1],
+                 s->control_x [i], s->control_y [i],
+                 s->control_x [i + 1], s->control_y [i + 1],
+                 s->control_x [i + 2], s->control_y [i + 2]);
+      
+  calc_section (s, s->control_x [i - 1], s->control_y [i - 1],
+               s->control_x [i], s->control_y [i],
+               s->control_x [i + 1], s->control_y [i + 1],
+               s->control_x [0], s->control_y [0]);
+  calc_section (s, s->control_x [i], s->control_y [i],
+               s->control_x [i + 1], s->control_y [i + 1],
+               s->control_x [0], s->control_y [0],
+               s->control_x [1], s->control_y [1]);
+}
+
+void
+just_fill_spline (spline *s)
+{
+  int i;
+
+  while (s->allocated_points < s->n_controls + 1)
+    grow_spline_points (s);
+    
+  for (i = 0; i < s->n_controls; i++)
+    {
+      s->points [i].x = s->control_x [i];
+      s->points [i].y = s->control_y [i];
+    }
+  s->points [s->n_controls].x = s->control_x [0];
+  s->points [s->n_controls].y = s->control_y [0];
+  s->n_points = s->n_controls + 1;
+}
+
+void
+append_spline_points (spline *s1, spline *s2)
+{
+  int i;
+  while (s1->allocated_points < s1->n_points + s2->n_points)
+    grow_spline_points (s1);
+  for (i = s1->n_points; i < s1->n_points + s2->n_points; i++)
+    {
+      s1->points [i].x = s2->points [i - s1->n_points].x;
+      s1->points [i].y = s2->points [i - s1->n_points].y;
+    }
+  s1->n_points = s1->n_points + s2->n_points;
+}
+
+void
+spline_bounding_box (spline *s, XRectangle *rectangle_out)
+{
+  int min_x;
+  int max_x;
+  int min_y;
+  int max_y;
+  int i;
+
+  if (s->n_points == 0)
+    {
+      rectangle_out->x = 0;
+      rectangle_out->y = 0;
+      rectangle_out->width = 0;
+      rectangle_out->height = 0;
+    }
+
+  min_x = s->points [0].x;
+  max_x = min_x;
+  min_y = s->points [0].y;
+  max_y = min_y;
+
+  for (i = 1; i < s->n_points; i++)
+    {
+      if (s->points [i].x < min_x)
+       min_x = s->points [i].x;
+      if (s->points [i].x > max_x)
+       max_x = s->points [i].x;
+      if (s->points [i].y < min_y)
+       min_y = s->points [i].y;
+      if (s->points [i].y > max_y)
+       max_y = s->points [i].y;
+    }
+  rectangle_out->x = min_x;
+  rectangle_out->y = min_y;
+  rectangle_out->width = max_x - min_x;
+  rectangle_out->height = max_y - min_y;
+}
+
+void
+free_spline(spline * s)
+{
+  free ((void *) s->control_x);
+  free ((void *) s->control_y);
+  free ((void *) s->points);
+  free ((void *) s);
+}
diff --git a/screenhack/spline.h b/screenhack/spline.h
new file mode 100644 (file)
index 0000000..a5a366c
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 1987, 1988, 1989 Stanford University
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of Stanford not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  Stanford makes no representations about
+ * the suitability of this software for any purpose.  It is provided "as is"
+ * without express or implied warranty.
+ *
+ * STANFORD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+ * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+ * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* This code came with the InterViews distribution, and was translated
+   from C++ to C by Matthieu Devin <devin@lucid.com> some time in 1992.
+ */
+
+#ifndef _SPLINE_H_
+#define _SPLINE_H_
+
+typedef struct _spline
+{
+  /* input */
+  unsigned int n_controls;
+  double*      control_x;
+  double*      control_y;
+
+  /* output */
+  unsigned int         n_points;
+  XPoint*      points;
+  unsigned int         allocated_points;
+} spline;
+
+spline* make_spline (unsigned int size);
+void compute_spline (spline* s);
+void compute_closed_spline (spline* s);
+void just_fill_spline (spline* s);
+void append_spline_points (spline* s1, spline* s2);
+void spline_bounding_box (spline* s, XRectangle* rectangle_out);
+void free_spline(spline *s);
+
+#endif /* _SPLINE_H_ */
diff --git a/screenhack/textclient.c b/screenhack/textclient.c
new file mode 100644 (file)
index 0000000..636b308
--- /dev/null
@@ -0,0 +1,747 @@
+/* xscreensaver, Copyright (c) 2012-2020 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ *
+ * Running programs under a pipe or pty and returning bytes from them.
+ * Uses these X resources:
+ * 
+ * program:            What to run.  Usually "xscreensaver-text".
+ * relaunchDelay: secs How long after the command dies before restarting.
+ * usePty: bool                Whether to run the command interactively.
+ * metaSendsESC: bool  Whether to send Alt-x as ESC x in pty-mode.
+ * swapBSDEL: bool     Swap Backspace and Delete in pty-mode.
+ *
+ * On iOS and Android, textclient-mobile.c is used instead.
+ */
+
+#include "utils.h"
+
+#if !defined(HAVE_IPHONE) && !defined(HAVE_ANDROID)  /* whole file */
+
+#include "textclient.h"
+#include "resources.h"
+
+#ifndef HAVE_COCOA
+# define XK_MISCELLANY
+# include <X11/keysymdef.h>
+# include <X11/Xatom.h>
+# include <X11/Intrinsic.h>
+#endif
+
+#include <stdio.h>
+
+#include <signal.h>
+#include <sys/wait.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+# include <fcntl.h>  /* for O_RDWR */
+#endif
+
+#ifdef HAVE_FORKPTY
+# include <sys/ioctl.h>
+# ifdef HAVE_PTY_H
+#  include <pty.h>
+# endif
+# ifdef HAVE_UTIL_H
+#  include <util.h>
+# endif
+# ifdef HAVE_SYS_TERMIOS_H
+#  include <sys/termios.h>
+# endif
+#endif /* HAVE_FORKPTY */
+
+#undef DEBUG
+
+extern const char *progname;
+
+struct text_data {
+  Display *dpy;
+  char *program;
+  int pix_w, pix_h, char_w, char_h;
+  int max_lines;
+
+  Bool pty_p;
+  XtIntervalId pipe_timer;
+  FILE *pipe;
+  pid_t pid;
+  XtInputId pipe_id;
+  Bool input_available_p;
+  Time subproc_relaunch_delay;
+  XComposeStatus compose;
+
+  Bool meta_sends_esc_p;
+  Bool swap_bs_del_p;
+  Bool meta_done_once;
+  unsigned int meta_mask;
+
+  const char *out_buffer;
+  int out_column;
+};
+
+
+static void
+subproc_cb (XtPointer closure, int *source, XtInputId *id)
+{
+  text_data *d = (text_data *) closure;
+# ifdef DEBUG
+  if (! d->input_available_p)
+    fprintf (stderr, "%s: textclient: input available\n", progname);
+# endif
+  d->input_available_p = True;
+}
+
+
+# define BACKSLASH(c) \
+  (! ((c >= 'a' && c <= 'z') || \
+      (c >= 'A' && c <= 'Z') || \
+      (c >= '0' && c <= '9') || \
+      c == '.' || c == '_' || c == '-' || c == '+' || c == '/'))
+
+#ifdef HAVE_COCOA
+static char *
+escape_str (char *s, const char *src)
+{
+  while (*src) {
+    char c = *src++;
+    if (BACKSLASH(c)) *s++ = '\\';
+    *s++ = c;
+  }
+  return s;
+}
+#endif
+
+
+/* Let's see if we're able to fork and exec at all. Thanks, macOS.
+ */
+static Bool
+selftest (void)
+{
+  static Bool done = False;
+  pid_t pid;
+  char buf [255];
+  if (done) return True;
+
+  pid = fork ();
+  switch ((int) pid)
+    {
+    case -1:
+      sprintf (buf, "%s: textclient: selftest: couldn't fork", progname);
+      perror (buf);
+      return False;
+
+    case 0:                                    /* child */
+      {
+        char * const av[] = { "/bin/sh", "-c", "true", 0 };
+        execvp (av[0], av);
+        exit (1);  /* exits child fork */
+        break;
+      }
+
+    default:                                   /* parent */
+      {
+        int status = -1;
+        int i;
+        /* Busy-loops are bad mmmmkayyyy */
+        struct timeval tv;
+        tv.tv_sec  = 0;
+        tv.tv_usec = 100000L;          /* 0.1 sec */
+        for (i = 0; i < 50; i++) {     /* 5 sec max */
+          pid_t pid2 = waitpid (pid, &status, 0);
+          if (pid == pid2) break;
+          (void) select (0, 0, 0, 0, &tv);
+        }
+
+        if (status != 0)
+          {
+# ifdef DEBUG
+            fprintf (stderr, "%s: selftest: textclient: status %d\n",
+                     progname, status);
+# endif
+            return False;
+          }
+        else
+          {
+# ifdef DEBUG
+            fprintf (stderr, "%s: textclient: selftest ok\n", progname);
+# endif
+            done = True;
+          }
+        break;
+      }
+    }
+
+  return True;
+}
+
+
+static void start_timer (text_data *d);
+
+static void
+launch_text_generator (text_data *d)
+{
+  XtAppContext app = XtDisplayToApplicationContext (d->dpy);
+  char buf[255];
+  const char *oprogram = d->program;
+  char *s;
+
+  size_t oprogram_size = strlen(oprogram);
+  size_t len;
+
+# ifdef HAVE_COCOA
+  /* /bin/sh on OS X 10.10 wipes out the PATH. */
+  const char *path = getenv("PATH");
+  size_t cmd_capacity = (oprogram_size + strlen(path)) * 2 + 100;
+  char *cmd = s = malloc (cmd_capacity);
+  strcpy (s, "export PATH=");
+  s += strlen (s);
+  s = escape_str (s, path);
+  strcpy (s, "; ");
+  s += strlen (s);
+# else
+  char *cmd = s = malloc ((strlen(oprogram)) * 2 + 100);
+# endif
+
+  if (!selftest())
+    {
+      if (!d->out_buffer || !*d->out_buffer)
+        d->out_buffer = "Can't exec; Gatekeeper problem?\r\n\r\n";
+      start_timer (d);
+      return;
+    }
+
+  strcpy (s, "( ");
+  strcat (s, oprogram);
+  s += strlen (s);
+
+  /* Kludge!  Special-case "xscreensaver-text" to tell it how wide
+     the screen is.  We used to do this by just always feeding
+     `program' through sprintf() and setting the default value to
+     "xscreensaver-text --cols %d", but that makes things blow up
+     if someone ever uses a --program that includes a % anywhere.
+   */
+  len = 17; /* strlen("xscreensaver-text") */
+  if (oprogram_size >= len &&
+    !memcmp (oprogram, "xscreensaver-text", len) &&
+    (oprogram[len] == ' ' || !oprogram[len]))
+    {
+      /* strstr is sloppy here. Technically, we should be parsing the command
+         line to identify flags and their arguments. This will blow up if one
+         of those pesky end users could set .textLiteral to "--cols".
+       */
+      if (d->char_w && !strstr (oprogram, "--cols "))
+        sprintf (s, " --cols %d", d->char_w);
+      if (d->max_lines && !strstr (oprogram, "--lines "))
+        sprintf (s, " --lines %d", d->max_lines);
+      s += strlen(s);
+
+# ifdef HAVE_COCOA
+      /* Also special-case "xscreensaver-text" to specify the text content on
+         the command line. defaults(1) on macOS doesn't know about the default
+         screenhack resources that don't make it into the
+         ~/Library/Preferences/ByHost/org.jwz.xscreensaver.*.plist.
+       */
+
+      char *text_mode_flag = " --date";
+      char *value_res = NULL;
+      char *text_mode = get_string_resource (d->dpy, "textMode", "String");
+
+      if (text_mode)
+        {
+          if (!strcmp (text_mode, "1") || !strcmp (text_mode, "literal"))
+            {
+              text_mode_flag = " --text";
+              value_res = "textLiteral";
+            }
+          else if (!strcmp (text_mode, "2") || !strcmp (text_mode, "file"))
+            {
+              text_mode_flag = " --file";
+              value_res = "textFile";
+            }
+          else if (!strcmp (text_mode, "3") || !strcmp (text_mode, "url"))
+            {
+              text_mode_flag = " --url";
+              value_res = "textURL";
+            }
+          else if (!strcmp (text_mode, "4") || !strcmp (text_mode, "program"))
+            {
+              text_mode_flag = " --program";
+              value_res = "textProgram";
+            }
+
+          free (text_mode);
+        }
+
+      strcpy (s, text_mode_flag);
+      s += strlen (s);
+
+      if (value_res)
+        {
+          size_t old_s = s - cmd;
+          char *value = get_string_resource (d->dpy, value_res, "");
+          if (!value)
+            value = strdup("");
+          cmd = realloc(cmd, cmd_capacity + strlen(value) * 2);
+          s = cmd + old_s;
+          *s = ' ';
+          ++s;
+          s = escape_str(s, value);
+          free(value);
+        }
+# endif /* HAVE_COCOA */
+    }
+
+  strcpy (s, " ) 2>&1");
+
+# ifdef DEBUG
+  fprintf (stderr, "%s: textclient: launch %s: %s\n", progname, 
+           (d->pty_p ? "pty" : "pipe"), cmd);
+# endif
+
+#ifdef HAVE_FORKPTY
+  if (d->pty_p)
+    {
+      int fd;
+      struct winsize ws;
+      
+      ws.ws_col = d->char_w;
+      ws.ws_row = d->char_h;
+      ws.ws_xpixel = d->pix_w;
+      ws.ws_ypixel = d->pix_h;
+      
+      d->pipe = 0;
+
+# ifdef HAVE_COCOA
+      if (getenv ("MallocScribble"))
+        /* This is here to stop me from wasting my time trying to answer
+           this question the next time I forget about it. */
+        fprintf (stderr, "%s: WARNING: forkpty hates 'Enable Guard Malloc'\n",
+                 progname);
+# endif
+
+      if ((d->pid = forkpty(&fd, NULL, NULL, &ws)) < 0)
+       {
+          /* Unable to fork */
+          sprintf (buf, "%.100s: forkpty", progname);
+         perror (buf);
+       }
+      else if (!d->pid)
+       {
+          /* This is the child fork. */
+          char *av[10];
+          int i = 0;
+         if (putenv ("TERM=vt100"))
+            abort();
+          av[i++] = "/bin/sh";
+          av[i++] = "-c";
+          av[i++] = cmd;
+          av[i] = 0;
+# ifdef DEBUG
+          {
+            int j;
+            fprintf (stderr, "%s: textclient: execvp:", progname);
+            for (j = 0; j < i; j++)
+              fprintf (stderr, " %s", av[j]);
+            fprintf (stderr, "\n");
+          }
+# endif
+          execvp (av[0], av);
+          sprintf (buf, "%.100s: %.100s", progname, oprogram);
+         perror (buf);
+         exit (1);
+       }
+      else
+       {
+          /* This is the parent fork. */
+          if (d->pipe) abort();
+         d->pipe = fdopen (fd, "r+");
+          if (d->pipe_id) abort();
+         d->pipe_id =
+           XtAppAddInput (app, fileno (d->pipe),
+                          (XtPointer) (XtInputReadMask | XtInputExceptMask),
+                          subproc_cb, (XtPointer) d);
+# ifdef DEBUG
+          fprintf (stderr, "%s: textclient: pid = %d\n", progname, d->pid);
+# endif
+       }
+    }
+  else
+#endif /* HAVE_FORKPTY */
+    {
+      /* don't mess up controlling terminal on "-pipe -program tcsh". */
+      static int protected_stdin_p = 0;
+      if (! protected_stdin_p) {
+        fclose (stdin);
+        open ("/dev/null", O_RDWR); /* re-allocate fd 0 */
+        protected_stdin_p = 1;
+      }
+
+      if (d->pipe) abort();
+      if ((d->pipe = popen (cmd, "r")))
+       {
+          if (d->pipe_id) abort();
+         d->pipe_id =
+           XtAppAddInput (app, fileno (d->pipe),
+                          (XtPointer) (XtInputReadMask | XtInputExceptMask),
+                          subproc_cb, (XtPointer) d);
+# ifdef DEBUG
+          fprintf (stderr, "%s: textclient: popen\n", progname);
+# endif
+       }
+      else
+       {
+          sprintf (buf, "%.100s: %.100s", progname, cmd);
+         perror (buf);
+       }
+    }
+
+  free (cmd);
+}
+
+
+static void
+relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
+{
+  text_data *d = (text_data *) closure;
+  /* if (!d->pipe_timer) abort(); */
+  d->pipe_timer = 0;
+# ifdef DEBUG
+  fprintf (stderr, "%s: textclient: launch timer fired\n", progname);
+# endif
+  launch_text_generator (d);
+}
+
+
+static void
+start_timer (text_data *d)
+{
+  XtAppContext app = XtDisplayToApplicationContext (d->dpy);
+
+# ifdef DEBUG
+  fprintf (stderr, "%s: textclient: relaunching in %d\n", progname, 
+           (int) d->subproc_relaunch_delay);
+# endif
+  if (d->pipe_timer)
+    XtRemoveTimeOut (d->pipe_timer);
+  d->pipe_timer =
+    XtAppAddTimeOut (app, d->subproc_relaunch_delay,
+                     relaunch_generator_timer,
+                     (XtPointer) d);
+}
+
+
+static void
+close_pipe (text_data *d)
+{
+  if (d->pid)
+    {
+# ifdef DEBUG
+      fprintf (stderr, "%s: textclient: kill %d\n", progname, d->pid);
+# endif
+      kill (d->pid, SIGTERM);
+    }
+  d->pid = 0;
+
+  if (d->pipe_id)
+    XtRemoveInput (d->pipe_id);
+  d->pipe_id = 0;
+
+  if (d->pipe)
+    {
+# ifdef DEBUG
+      fprintf (stderr, "%s: textclient: pclose\n", progname);
+# endif
+      pclose (d->pipe);
+    }
+  d->pipe = 0;
+
+
+}
+
+
+void
+textclient_reshape (text_data *d,
+                    int pix_w, int pix_h,
+                    int char_w, int char_h,
+                    int max_lines)
+{
+# if defined(HAVE_FORKPTY) && defined(TIOCSWINSZ)
+
+  d->pix_w  = pix_w;
+  d->pix_h  = pix_h;
+  d->char_w = char_w;
+  d->char_h = char_h;
+  d->max_lines = max_lines;
+
+# ifdef DEBUG
+  fprintf (stderr, "%s: textclient: reshape: %dx%d, %dx%d\n", progname,
+           pix_w, pix_h, char_w, char_h);
+# endif
+
+  if (d->pid && d->pipe)
+    {
+      /* Tell the sub-process that the screen size has changed. */
+      struct winsize ws;
+      ws.ws_col = char_w;
+      ws.ws_row = char_h;
+      ws.ws_xpixel = pix_w;
+      ws.ws_ypixel = pix_h;
+      ioctl (fileno (d->pipe), TIOCSWINSZ, &ws);
+      kill (d->pid, SIGWINCH);
+#  ifdef DEBUG
+      fprintf (stderr, "%s: textclient: SIGWINCH\n", progname);
+#  endif
+    }
+# endif /* HAVE_FORKPTY && TIOCSWINSZ */
+
+
+  /* If we're running xscreensaver-text, then kill and restart it any
+     time the window is resized so that it gets an updated --cols arg
+     right away.  But if we're running something else, leave it alone.
+   */
+  if (!strcmp (d->program, "xscreensaver-text"))
+    {
+# ifdef DEBUG
+      fprintf (stderr, "%s: textclient: reshape relaunch\n", progname);
+# endif
+      close_pipe (d);
+      d->input_available_p = False;
+      start_timer (d);
+    }
+}
+
+
+text_data *
+textclient_open (Display *dpy)
+{
+  text_data *d = (text_data *) calloc (1, sizeof (*d));
+
+# ifdef DEBUG
+  fprintf (stderr, "%s: textclient: init\n", progname);
+# endif
+
+  d->dpy = dpy;
+
+  if (get_boolean_resource (dpy, "usePty", "UsePty"))
+    {
+# ifdef HAVE_FORKPTY
+      d->pty_p = True;
+# else
+      fprintf (stderr,
+               "%s: no pty support on this system; using a pipe instead.\n",
+               progname);
+# endif
+    }
+
+  d->subproc_relaunch_delay =
+    get_integer_resource (dpy, "relaunchDelay", "Time");
+  if (d->subproc_relaunch_delay < 1)
+    d->subproc_relaunch_delay = 1;
+  d->subproc_relaunch_delay *= 1000;
+
+
+  d->meta_sends_esc_p = get_boolean_resource (dpy, "metaSendsESC", "Boolean");
+  d->swap_bs_del_p    = get_boolean_resource (dpy, "swapBSDEL",    "Boolean");
+
+  d->program = get_string_resource (dpy, "program", "Program");
+
+
+# ifdef HAVE_FORKPTY
+  /* Kludge for MacOS standalone mode: see OSX/SaverRunner.m. */
+  {
+    const char *s = getenv ("XSCREENSAVER_STANDALONE");
+    if (s && *s && strcmp(s, "0"))
+      {
+        d->pty_p = 1;
+        d->program = strdup (getenv ("SHELL"));
+#  ifdef DEBUG
+        fprintf (stderr, "%s: textclient: standalone: %s\n",
+                 progname, d->program);
+#  endif
+      }
+  }
+# endif
+
+  start_timer (d);
+
+  return d;
+}
+
+
+void
+textclient_close (text_data *d)
+{
+# ifdef DEBUG
+  fprintf (stderr, "%s: textclient: free\n", progname);
+# endif
+
+  close_pipe (d);
+  if (d->program)
+    free (d->program);
+  if (d->pipe_timer)
+    XtRemoveTimeOut (d->pipe_timer);
+  d->pipe_timer = 0;
+  memset (d, 0, sizeof (*d));
+  free (d);
+}
+
+int
+textclient_getc (text_data *d)
+{
+  XtAppContext app = XtDisplayToApplicationContext (d->dpy);
+  int ret = -1;
+
+  if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
+    XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
+
+  if (d->out_buffer && *d->out_buffer)
+    {
+      ret = *d->out_buffer;
+      d->out_buffer++;
+    }
+  else if (d->input_available_p && d->pipe)
+    {
+      unsigned char s[2];
+      int n = read (fileno (d->pipe), (void *) s, 1);
+      if (n > 0)
+        ret = s[0];
+      else             /* EOF */
+        {
+         if (d->pid)
+           {
+# ifdef DEBUG
+              fprintf (stderr, "%s: textclient: waitpid %d\n",
+                       progname, d->pid);
+# endif
+             waitpid (d->pid, NULL, 0);
+              d->pid = 0;
+           }
+
+          close_pipe (d);
+
+          if (d->out_column > 0)
+            {
+# ifdef DEBUG
+              fprintf (stderr, "%s: textclient: adding blank line at EOF\n",
+                       progname);
+# endif
+              d->out_buffer = "\r\n\r\n";
+            }
+
+          start_timer (d);
+        }
+      d->input_available_p = False;
+    }
+
+  if (ret == '\r' || ret == '\n')
+    d->out_column = 0;
+  else if (ret > 0)
+    d->out_column++;
+
+# ifdef DEBUG
+  if (ret <= 0)
+    fprintf (stderr, "%s: textclient: getc: %d\n", progname, ret);
+  else if (ret < ' ')
+    fprintf (stderr, "%s: textclient: getc: %03o\n", progname, ret);
+  else
+    fprintf (stderr, "%s: textclient: getc: '%c'\n", progname, (char) ret);
+# endif
+
+  return ret;
+}
+
+
+/* The interpretation of the ModN modifiers is dependent on what keys
+   are bound to them: Mod1 does not necessarily mean "meta".  It only
+   means "meta" if Meta_L or Meta_R are bound to it.  If Meta_L is on
+   Mod5, then Mod5 is the one that means Meta.  Oh, and Meta and Alt
+   aren't necessarily the same thing.  Icepicks in my forehead!
+ */
+static unsigned int
+do_icccm_meta_key_stupidity (Display *dpy)
+{
+  unsigned int modbits = 0;
+# ifndef HAVE_COCOA
+  int i, j, k;
+  XModifierKeymap *modmap = XGetModifierMapping (dpy);
+  for (i = 3; i < 8; i++)
+    for (j = 0; j < modmap->max_keypermod; j++)
+      {
+        int code = modmap->modifiermap[i * modmap->max_keypermod + j];
+        KeySym *syms;
+        int nsyms = 0;
+        if (code == 0) continue;
+        syms = XGetKeyboardMapping (dpy, code, 1, &nsyms);
+        for (k = 0; k < nsyms; k++)
+          if (syms[k] == XK_Meta_L || syms[k] == XK_Meta_R ||
+              syms[k] == XK_Alt_L  || syms[k] == XK_Alt_R)
+            modbits |= (1 << i);
+        XFree (syms);
+      }
+  XFreeModifiermap (modmap);
+# endif /* HAVE_COCOA */
+  return modbits;
+}
+
+
+/* Returns a mask of the bit or bits of a KeyPress event that mean "meta". 
+ */
+static unsigned int
+meta_modifier (text_data *d)
+{
+  if (!d->meta_done_once)
+    {
+      /* Really, we are supposed to recompute this if a KeymapNotify
+         event comes in, but fuck it. */
+      d->meta_done_once = True;
+      d->meta_mask = do_icccm_meta_key_stupidity (d->dpy);
+# ifdef DEBUG
+      fprintf (stderr, "%s: textclient: ICCCM Meta is 0x%08X\n",
+               progname, d->meta_mask);
+# endif
+    }
+  return d->meta_mask;
+}
+
+
+Bool
+textclient_putc (text_data *d, XKeyEvent *k)
+{
+  KeySym keysym;
+  unsigned char c = 0;
+  XLookupString (k, (char *) &c, 1, &keysym, &d->compose);
+  if (c != 0 && d->pipe)
+    {
+      if (!d->swap_bs_del_p) ;
+      else if (c == 127) c = 8;
+      else if (c == 8)   c = 127;
+
+      /* If meta was held down, send ESC, or turn on the high bit. */
+      if (k->state & meta_modifier (d))
+        {
+          if (d->meta_sends_esc_p)
+            fputc ('\033', d->pipe);
+          else
+            c |= 0x80;
+        }
+
+      fputc (c, d->pipe);
+      fflush (d->pipe);
+      k->type = 0;  /* don't interpret this event defaultly. */
+
+# ifdef DEBUG
+      fprintf (stderr, "%s: textclient: putc '%c'\n", progname, (char) c);
+# endif
+
+      return True;
+    }
+  return False;
+}
+
+#endif /* !HAVE_IPHONE -- whole file */
diff --git a/screenhack/textclient.h b/screenhack/textclient.h
new file mode 100644 (file)
index 0000000..5be8079
--- /dev/null
@@ -0,0 +1,37 @@
+/* xscreensaver, Copyright (c) 2012-2016 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ *
+ * Running "xscreensaver-text" and returning bytes from it.
+ */
+
+#ifndef __TEXTCLIENT_H__
+#define __TEXTCLIENT_H__
+
+# ifdef HAVE_IPHONE
+#  undef HAVE_FORKPTY
+# endif
+
+typedef struct text_data text_data;
+
+extern text_data *textclient_open (Display *);
+extern void textclient_close (text_data *);
+extern void textclient_reshape (text_data *,
+                                int pix_w, int pix_h,
+                                int char_w, int char_h,
+                                int max_lines);
+extern int textclient_getc (text_data *);
+extern Bool textclient_putc (text_data *, XKeyEvent *);
+
+# if defined(HAVE_IPHONE) || defined(HAVE_ANDROID)
+extern char *textclient_mobile_date_string (void);
+extern char *textclient_mobile_url_string (Display *, const char *url);
+# endif
+
+#endif /* __TEXTCLIENT_H__ */
diff --git a/screenhack/thread_util.c b/screenhack/thread_util.c
new file mode 100644 (file)
index 0000000..71c8633
--- /dev/null
@@ -0,0 +1,1043 @@
+/* -*- mode: c; tab-width: 4; fill-column: 78 -*- */
+/* vi: set ts=4 tw=78: */
+
+/*
+thread_util.c, Copyright (c) 2014 Dave Odell <dmo2118@gmail.com>
+
+Permission to use, copy, modify, distribute, and sell this software and its
+documentation for any purpose is hereby granted without fee, provided that
+the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation.  No representations are made about the suitability of this
+software for any purpose.  It is provided "as is" without express or
+implied warranty.
+*/
+
+#if HAVE_CONFIG_H
+#      include "config.h"
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h> /* Only used by thread_memory_alignment(). */
+#include <string.h>
+
+#if HAVE_ALLOCA_H
+#      include <alloca.h>
+#endif
+
+#if HAVE_UNISTD_H
+#      include <unistd.h>
+#endif
+
+#if defined __MACH__ && defined __APPLE__ /* OS X, iOS */
+#      include <sys/sysctl.h>
+#      include <inttypes.h>
+#endif
+
+#include "thread_util.h"
+
+#include "aligned_malloc.h"
+#include "resources.h"
+
+#define IS_POWER_OF_2(x) ((x) > 0 && !((x) & ((x) - 1)))
+
+/*
+   arraysize(a). Also known as countof(x), XtNumber(x), NELEMS(x), LEN(x),
+   NUMOF(x), ARRAY_SIZE(x), etc., since the fine folks behind C never got
+   around to including this incredibly useful macro in the standard library,
+   which is where it belongs.
+
+   Much of the code here assumes that multiple processors in a system all use
+   the same cache line size...which might be wrong on occasion.
+*/
+
+#define arraysize(a) (sizeof(a) / sizeof(*(a)))
+#define arrayend(a) ((a) + arraysize(a))
+
+/*
+These numbers are from:
+- Linux: arch/(arch name)/include/asm/cache.h, note
+  L1_CACHE_BYTES/L1_CACHE_SHIFT/SMP_CACHE_BYTES.
+- FreeBSD: sys/(sys name)/include/param.h, note
+  CACHE_LINE_SHIFT/CACHE_LINE_SIZE.
+
+Preprocessor symbols come from:
+- TARGET_CPU_CPP_BUILTINS() in the GNU C preprocessor
+  <http://code.ohloh.net/?s=%22TARGET_CPU_CPP_BUILTINS%22&fp=304413>
+- http://predef.sourceforge.net/
+*/
+
+/*
+Several architectures need preprocessor symbols.
+
+Qualcomm Hexagon: 1 << 5
+Imagination Technologies META: 1 << 6
+OpenRISC: 16 (Linux has the cache line size as a todo.)
+Unicore: 1 << 5
+*/
+
+#if HAVE_PTHREAD
+
+#      if !HAVE_UNISTD_H
+#              error unistd.h must be present whenever pthread.h is.
+#      endif
+
+#      if defined __MACH__ && defined __APPLE__ /* OS X, iOS */
+#              include <TargetConditionals.h> /* For TARGET_OS_IPHONE. */
+#              ifdef TARGET_OS_IPHONE
+#                      define _CACHE_LINE_SIZE 64
+#              endif
+#      endif
+
+#      if defined __FreeBSD__ && !defined _CACHE_LINE_SIZE
+#              include <machine/param.h>
+#              ifdef CACHE_LINE_SIZE
+#                      define _CACHE_LINE_SIZE CACHE_LINE_SIZE
+#              endif
+#      endif
+
+#      if !defined _CACHE_LINE_SIZE
+#              if defined __alpha || defined __alpha__
+/* DEC Alpha */
+#                      define _CACHE_LINE_SIZE 64 /* EV6 and above. EV4 and EV5 use 32 bytes. */
+#              elif defined __arm__
+/* ARM architecture */
+#                      define _CACHE_LINE_SIZE (1 << 6)
+#              elif defined __AVR || defined __AVR__
+/* Atmel AVR32 */
+#                      define _CACHE_LINE_SIZE (1 << 5)
+#              elif defined __bfin || defined __BFIN__
+/* Analog Devices Blackfin */
+#                      define _CACHE_LINE_SIZE (1 << 5)
+#              elif defined _TMS320C6X || defined __TMS320C6X__
+/* Texas Instruments TMS320C6x */
+#                      define _CACHE_LINE_SIZE (1 << 7) /* From L2. L1 data cache line is 1 << 6. */
+#              elif defined __cris
+/* Axis Communications ETRAX CRIS */
+#                      define _CACHE_LINE_SIZE 32
+#              elif defined __ia64__ || defined _IA64
+/* Intel Itanium */
+#                      define _CACHE_LINE_SIZE (1 << 7)
+#              elif defined __M32R__ || defined __m32r__
+/* Mitsubishi/Renesas M32R */
+#                      define _CACHE_LINE_SIZE (1 << 4)
+#              elif defined __m68k__ || defined M68000 || defined __MC68K__
+/* Motorola 68000 */
+#                      define _CACHE_LINE_SIZE (1 << 4)
+#              elif defined __MICROBLAZE__ || defined __microblaze__
+/* Xilinx MicroBlaze */
+#                      define _CACHE_LINE_SIZE (1 << 5)
+#              elif defined __mips__ || defined __mips || defined __MIPS__
+/* MIPS */
+#                      define _CACHE_LINE_SIZE (1 << 6)
+#              elif defined __mn10300__ || defined __MN10300__
+/* Matsushita/Panasonic MN103 */
+#                      define _CACHE_LINE_SIZE 32 /* MN103E010 has 16 bytes. */
+#              elif defined __hppa || defined __hppa__
+/* Hewlett-Packard PA-RISC */
+#                      define _CACHE_LINE_SIZE 64 /* PA-RISC 2.0 uses 64 bytes, PA-RISC 1.1 uses 32. */
+#              elif defined __powerpc || defined _ARCH_PPC
+/* Power Architecture (a.k.a. PowerPC) */
+#                      define _CACHE_LINE_SIZE (1 << 7) /* Linux has a list of PPC models with associated L1_CACHE_SHIFT values. */
+#              elif defined __s390__ || defined __370__ || defined __zarch__ || defined __SYSC_ZARCH__
+/* IBM System/390 */
+#                      define _CACHE_LINE_SIZE 256
+#              elif defined SUNPLUS || defined __SCORE__ || defined __score__
+/* Sunplus S+core */
+#                      define _CACHE_LINE_SIZE (1 << 4)
+#              elif defined __sh__
+/* Hitachi SuperH */
+#                      define _CACHE_LINE_SIZE (1 << 5) /* SH3 and earlier used 1 << 4. */
+#              elif defined __sparc__ || defined __sparc
+/* SPARC */
+#                      define _CACHE_LINE_SIZE (1 << 7) /* Linux and FreeBSD disagree as to what this should be. */
+#              elif defined __tile__
+/* Tilera TILE series */
+#                      define _CACHE_LINE_SIZE (1 << 6) /* TILEPro uses different sizes for L1 and L2. */
+#              elif defined __i386 || defined __x86_64
+/* x86(-64) */
+#                      define _CACHE_LINE_SIZE (1 << 7)
+#              elif defined __xtensa__ || defined __XTENSA__
+/* Cadence Design Systems/Tensilica Xtensa */
+#                      define _CACHE_LINE_SIZE (1 << 5) /* 1 << 4 on some models. */
+#              endif
+#      endif /* !defined _CACHE_LINE_SIZE */
+
+#      if defined __NetBSD__ && !defined _CACHE_LINE_SIZE
+/*
+NetBSD defines COHERENCY_UNIT to be 32 on MIPS, and 64 for all other platforms -- which is wrong. Still, this is what the kernel
+uses; if this value didn't work, the system wouldn't run.
+*/
+#              include <sys/param.h>
+#                      ifdef COHERENCY_UNIT
+#                      define _CACHE_LINE_SIZE COHERENCY_UNIT
+#              endif
+#      endif
+
+#      ifndef _CACHE_LINE_SIZE
+#              define _CACHE_LINE_SIZE 256 /* Fallback cache line size. */
+#      endif
+
+static unsigned _get_cache_line_size(void)
+{
+       /*
+       The general idea:
+       - Try to get the actual cache line size from the operating system.
+         - In the interest of keeping things simple, this only checks with
+        glibc and OS X.
+           - A few other methods that could be added:
+             - Query x86 CPUs directly with the CPUID instruction.
+             - Query various ELF systems through the auxillary vector.
+            (Power, Alpha, SuperH)
+             - Query Linux through
+            /sys/devices/system/cpu/cpu?/cache/index?/coherency_line_size
+            (x86 only, AFAIK)
+             - Query Linux through cache_alignment in /proc/cpuinfo
+             - Query Solaris through PICL.
+       - If that fails, return a value appropriate for the current CPU
+      architecture.
+       - Otherwise, return a sufficiently large number.
+       */
+
+       /*
+       sysconf(3) is not a syscall, it's a glibc call that, for cache line sizes,
+       uses CPUID on x86 and returns 0 on other platforms. If it were to work on
+       most other platforms, it would have to get cache information from the
+       kernel, since that information is usually made available by the processor
+       only in privileged mode.
+       https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/i386/sysconf.c;hb=HEAD
+       */
+
+       /* uClibc, newlib, dietlibc, musl, Bionic do not have this. */
+
+#      if HAVE_UNISTD_H && ( \
+       defined _SC_LEVEL1_DCACHE_LINESIZE || \
+       defined _SC_LEVEL2_CACHE_LINESIZE || \
+       defined _SC_LEVEL3_CACHE_LINESIZE || \
+       defined _SC_LEVEL4_CACHE_LINESIZE)
+       {
+               static const int names[] =
+               {
+#              ifdef _SC_LEVEL1_DCACHE_LINESIZE
+                       _SC_LEVEL1_DCACHE_LINESIZE,
+#              endif
+#              ifdef _SC_LEVEL2_CACHE_LINESIZE
+                       _SC_LEVEL2_CACHE_LINESIZE,
+#              endif
+#              ifdef _SC_LEVEL3_CACHE_LINESIZE
+                       _SC_LEVEL3_CACHE_LINESIZE,
+#              endif
+#              ifdef  _SC_LEVEL4_CACHE_LINESIZE
+                       _SC_LEVEL4_CACHE_LINESIZE
+#              endif
+               };
+
+               const int *name;
+               long result = 0;
+
+               for(name = names; name != arrayend(names); ++name)
+               {
+                       long sysconf_result = sysconf(*name); /* Can return -1 or 0 on
+                                                     failure. */
+
+                       if(sysconf_result > result)
+                               result = sysconf_result;
+               }
+
+               if(result)
+                       return result;
+
+               /* Currently, this fails for every platform that isn't x86. Perhaps
+           future versions will support other processors? */
+       }
+#      endif
+
+#      if defined __MACH__ && defined __APPLE__
+       {
+               uint32_t result; /* sysctl.h says that hw.cachelinesize is a
+                            CTLTYPE_INT. */
+               size_t size = sizeof(result);
+               static const int name[] = {CTL_HW, HW_CACHELINE};
+
+               if(!sysctl((int *)name, 2, &result, &size, NULL, 0)) /* (int *) is for OS X. */
+               {
+                       assert(size == sizeof(result));
+                       return result;
+               };
+       }
+#      endif
+
+       /* Guess based on the CPU type. */
+       return _CACHE_LINE_SIZE;
+}
+
+const pthread_mutex_t mutex_initializer =
+#      if defined _GNU_SOURCE && !defined NDEBUG
+       PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP
+#      else
+       PTHREAD_MUTEX_INITIALIZER
+#      endif
+       ;
+
+const pthread_cond_t cond_initializer = PTHREAD_COND_INITIALIZER;
+
+static int _has_pthread = 0; /* Initialize when needed. */
+static int _cache_line_size = sizeof(void *);
+
+/* This is actually the init function for various things in here. */
+int threads_available(Display *dpy)
+{
+/*     This is maybe not thread-safe, but: this should -- and generally will --
+       be called before the program launches its second thread. */
+
+       if(!_has_pthread)
+       {
+#      if _POSIX_THREADS
+               _has_pthread = _POSIX_THREADS;
+#      else
+               _has_pthread = sysconf(_SC_THREADS);
+#      endif
+
+               if(_has_pthread >= 0)
+               {
+                       if(get_boolean_resource(dpy, "useThreads", "Boolean"))
+                       {
+                               _cache_line_size = _get_cache_line_size();
+                               assert(_cache_line_size >= sizeof(void *));
+                               assert(IS_POWER_OF_2(_cache_line_size));
+                       }
+                       else
+                       {
+                               _has_pthread = -1;
+                       }
+               }
+       }
+
+       return _has_pthread;
+}
+
+#endif /* HAVE_PTHREAD */
+
+/*
+   hardware_concurrency() -
+
+   Various platforms offer various statistics that look like they should be
+   useful: sysconf(_SC_NPROCESSORS_ONLN) (i.e. the number of 'online'
+   processors) in particular is available on many Unixes, and is frequently
+   used for functions like hardware_concurrency(). But 'online' is somewhat
+   ambiguous; it can mean:
+
+  1. The number of CPU cores that are not (temporarily) asleep. (e.g. Android
+     can sometimes put cores to sleep if they aren't being used, and this is
+     reflected in _SC_NPROCESSORS_ONLN.)
+
+  2. The maximum number of CPU cores that can be provided to this application,
+     as currently set by the system administrator.  (2) is the one that
+     hardware_concurrency() ultimately needs.
+*/
+
+/*
+   Shamelessly plagarized from Boost.Thread and Stack Overflow
+   <http://stackoverflow.com/q/150355>.  GNU libstdc++ has some of this too,
+   see thread::hardware_concurrency() in thread.cc.
+   http://gcc.gnu.org/viewcvs/gcc/trunk/libstdc%2B%2B-v3/src/c%2B%2B11/thread.cc?view=markup
+
+   This might not work right on less common systems for various reasons.
+*/
+
+#if HAVE_PTHREAD
+#      if defined __APPLE__ && defined __MACH__ || \
+               defined __FreeBSD__ || \
+               defined __OpenBSD__ || \
+               defined __NetBSD__ || \
+               defined __DragonFly__ || \
+               defined __minix
+
+/*
+   BSD Unixes use sysctl(3) for this.
+   Some BSDs also support sysconf(3) for this, but in each case this was added
+   after sysctl(3).
+   Linux: sysctl is present, but strongly deprecated.
+   Minix uses the NetBSD userspace, so it has both this and sysconf(3).
+   QNX: sysctl is present for kern.* and net.*, but it doesn't say anything
+   about hw.*
+*/
+
+/* __APPLE__ without __MACH__ is OS 9 or earlier. __APPLE__ with __MACH__ is OS X. */
+
+/*
+The usual thing to do here is for sysctl(3) to call __sysctl(2).
+  http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libc/gen/sysctl.c?only_with_tag=HEAD
+  http://svnweb.freebsd.org/base/head/lib/libc/gen/sysctl.c?view=markup
+*/
+
+/*
+   OS X: Xcode Instruments (as of Xcode 4; Apple likes to move things like
+   this around) can disable CPUs as a debugging tool.
+   Instruments -> Preferences... (Command-,) -> General -> Active Processor Cores
+   FreeBSD, OpenBSD: It doesn't look like CPUs can be disabled.
+   NetBSD: CPUs can be disabled manually through cpuctl(8).
+*/
+
+#              include <stddef.h>
+
+/* FreeBSD: sys/sysctl.h needs sys/types.h, but the one doesn't bring the
+   other in automatically. */
+#              include <sys/types.h>
+#              include <sys/sysctl.h>
+
+static unsigned _hardware_concurrency(void)
+{
+       int count;
+       size_t size = sizeof(count);
+
+#              if defined __APPLE__ && defined __MACH__
+       /* Apple sez: sysctl("hw.logicalcpu") is affected by the "current power
+       management mode", so use hw.logicalcpu_max. */
+       /* https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man3/sysctl.3.html */
+       if(!sysctlbyname("hw.logicalcpu_max", &count, &size, NULL, 0)) /* Preferred on more recent Darwin. */
+       {
+               assert(size == sizeof(count));
+               return count;
+       }
+#              endif
+
+#              if defined HW_NCPUONLINE
+       /* NetBSD has this. */
+       {
+               static const int name[] = {CTL_HW, HW_NCPUONLINE};
+               if(!sysctl(name, 2, &count, &size, NULL, 0))
+               {
+                       assert(size == sizeof(count));
+                       return count;
+               }
+       }
+#              endif
+
+       {
+               static const int name[] = {CTL_HW, HW_NCPU};
+               if(!sysctl((int *)name, 2, &count, &size, NULL, 0)) /* (int *) is for OS X. */
+               {
+                       assert(size == sizeof(count));
+                       return count;
+               }
+       }
+
+       return 1;
+}
+
+#      elif HAVE_UNISTD_H && defined _SC_NPROCESSORS_ONLN
+
+/*
+Supported by:
+Linux 2.0 was the first version to provide SMP support via clone(2).
+  (e)glibc on Linux provides this, which in turn uses get_nprocs().
+  get_nprocs in turn uses /sys/devices/system/cpu/online, /proc/stat, or /proc/cpuinfo, whichever's available.
+  https://sourceware.org/git/?p=glibc.git;a=blob;f=posix/sysconf.c;hb=HEAD
+  https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/getsysstats.c;hb=HEAD
+  Linux usually isn't configured to auto-enable/disable cores.
+SunOS (Solaris), sometime between 4.1.3 and 5.5.1.
+  This includes all open source derivatives of 5.10. (Illumos, OpenIndiana)
+  sysconf(_SC_NPROCESSORS_ONLN) call _sysconfig(2).
+  Not sure if CPU power management (enabled by default, see cpupm and
+  cpu_deep_idle in power.conf(4)) affects this.
+  psradm(1M) can bring up/down CPU cores, which affects
+  sysconf(_SC_NPROCESSORS_ONLN).
+  http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/sysconf.c
+  Minix 3.2, at the latest. (This is the first version to support SMP.)
+  AIX 7.1, probably earlier.
+
+Also:
+Mac OS X apparently has this on 10.5+.
+FreeBSD 5.0, NetBSD 5.0 also have this. They both call sysctl(3).
+  http://svnweb.freebsd.org/base/head/lib/libc/gen/sysconf.c?view=markup
+  http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libc/gen/sysconf.c?only_with_tag=HEAD
+
+QNX has sysconf(3), but it doesn't have _SC_NPROCESSORS_*.
+*/
+
+static unsigned _hardware_concurrency(void)
+{
+       long count = sysconf(_SC_NPROCESSORS_ONLN);
+       return count > 0 ? count : 1;
+}
+
+#      else
+
+static unsigned _hardware_concurrency(void)
+{
+       return 1; /* Fallback for unknown systems. */
+}
+
+#      endif
+#endif
+
+unsigned hardware_concurrency(Display *dpy)
+{
+#if HAVE_PTHREAD
+       if(threads_available(dpy) >= 0)
+               return _hardware_concurrency();
+#endif
+       return 1;
+}
+
+/* thread_memory_alignment() - */
+
+unsigned thread_memory_alignment(Display *dpy)
+{
+       (void)threads_available(dpy);
+#if HAVE_PTHREAD
+       return _cache_line_size;
+#else
+       return sizeof(void *);
+#endif
+}
+
+/* Thread pool - */
+
+static unsigned _threadpool_count_serial(struct threadpool *self)
+{
+#if HAVE_PTHREAD
+       assert(_has_pthread);
+       if(_has_pthread >= 0)
+               return self->count ? 1 : 0;
+#endif
+       return self->count;
+}
+
+static void _serial_destroy(struct threadpool *self)
+{
+       void *thread = self->serial_threads;
+       unsigned i, count = _threadpool_count_serial(self);
+
+       for(i = 0; i != count; ++i)
+       {
+               self->thread_destroy(thread);
+               thread = (char *)thread + self->thread_size;
+       }
+
+       free(self->serial_threads);
+}
+
+#if HAVE_PTHREAD
+
+static void _parallel_abort(struct threadpool *self)
+{
+       assert(self->count > 1);
+       self->count = self->parallel_unfinished + 1 /* The '+ 1' should technically be _threadpool_count_serial(self). */;
+       PTHREAD_VERIFY(pthread_cond_broadcast(&self->cond));
+}
+
+struct _parallel_startup_type
+{
+       struct threadpool *parent;
+       int (*thread_create)(void *self, struct threadpool *pool, unsigned id);
+       int last_errno;
+};
+
+static unsigned _threadpool_count_parallel(struct threadpool *self)
+{
+       assert(_has_pthread);
+       assert(self->count >= 1);
+       return self->count - 1 /* The '- 1' should technically be _threadpool_count_serial(self). */;
+}
+
+static void *_start_routine(void *startup_raw);
+
+/* Tricky lock sequence: _add_next_thread unlocks on error. */
+static void _add_next_thread(struct _parallel_startup_type *self)
+{
+       assert(!self->last_errno);
+
+       if(self->parent->parallel_unfinished == _threadpool_count_parallel(self->parent))
+       {
+               PTHREAD_VERIFY(pthread_cond_broadcast(&self->parent->cond));
+       }
+       else
+       {
+               pthread_t *thread = self->parent->parallel_threads + self->parent->parallel_unfinished;
+               self->last_errno = pthread_create(thread, NULL, _start_routine, self);
+               if(self->last_errno)
+                       _parallel_abort(self->parent);
+       }
+}
+
+static void *_thread_free_and_unlock(struct threadpool *self, void *thread)
+{
+       PTHREAD_VERIFY(pthread_mutex_unlock(&self->mutex));
+#      if !HAVE_ALLOCA
+       thread_free(thread);
+#      endif
+       return NULL;
+}
+
+static void *_thread_destroy_and_unlock(struct threadpool *self, void *thread)
+{
+       self->thread_destroy(thread);
+       return _thread_free_and_unlock(self, thread);
+}
+
+/* At one point, one of the threads refused to destroy itself at the end. Why?! And why won't it happen again? */
+
+static void *_start_routine(void *startup_raw)
+{
+       struct _parallel_startup_type *startup = (struct _parallel_startup_type *)startup_raw;
+
+       struct threadpool *parent = startup->parent;
+
+       void *thread;
+
+       PTHREAD_VERIFY(pthread_mutex_lock(&parent->mutex));
+       ++parent->parallel_unfinished;
+
+#      if HAVE_ALLOCA
+/*     Ideally, the thread object goes on the thread's stack. This guarantees no false sharing with other threads, and in a NUMA
+       configuration, ensures that the thread object is using memory from the right node. */
+       thread = alloca(parent->thread_size);
+#      else
+       startup->last_errno = thread_malloc(&thread, NULL, parent->thread_size);
+       if(startup->last_errno)
+       {
+               _parallel_abort(parent);
+               PTHREAD_VERIFY(pthread_mutex_unlock(&parent->mutex));
+               return NULL;
+       }
+#      endif
+
+/*     Setting thread affinity for threads running in lock-step can cause delays
+       and jumpiness.  Ideally, there would be some way to recommend (but not
+       require) that a thread run on a certain core/set of cores. */
+
+/*     Neither Linux nor libnuma seem to support the concept of a preferred/ideal
+       CPU for a thread/process. */
+
+/*     Untested. */
+/*     {
+               cpu_set_t cpu_set;
+               CPU_ZERO(&cpu_set);
+               CPU_SET(&cpu_set, &parent._threads_unfinished);
+               pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpu_set);
+       } */
+
+       startup->last_errno = startup->thread_create(thread, parent, parent->parallel_unfinished);
+       if(startup->last_errno)
+       {
+               _parallel_abort(parent);
+               return _thread_free_and_unlock(parent, thread); /* Tail calls make everything better. */
+       }
+
+       assert(!startup->last_errno);
+       _add_next_thread(startup); /* Calls _parallel_abort() on failure. */
+       if(startup->last_errno)
+               return _thread_destroy_and_unlock(parent, thread);
+
+       for(;;)
+       {
+               for(;;)
+               {
+                       /*
+                       This must come before the '.threads' check, otherwise if
+                       threadpool_destroy is called immediately after a run starts, then
+                       it's possible that not all threads would be launched for the final
+                       run. This can cause deadlock in conjunction with things like
+                       barriers.
+                       */
+                       if(parent->parallel_pending)
+                               break; /* Start a run. */
+
+                       if(!parent->parallel_threads)
+                               return _thread_destroy_and_unlock(parent, thread); /* Threads are shutting down. */
+
+                       PTHREAD_VERIFY(pthread_cond_wait(&parent->cond, &parent->mutex));
+               }
+
+               --parent->parallel_pending;
+               if(!parent->parallel_pending)
+                       PTHREAD_VERIFY(pthread_cond_broadcast(&parent->cond));
+                       /* All threads have started processing, other threads can finish. */
+
+               PTHREAD_VERIFY(pthread_mutex_unlock(&parent->mutex));
+
+               parent->thread_run(thread);
+
+               PTHREAD_VERIFY(pthread_mutex_lock(&parent->mutex));
+#      if 0
+               if(!parent->parallel_threads) /* I don't think this is necessary anymore. */
+                       break;
+#      endif
+               /* Don't loop around until all other threads have begun processing. */
+
+               /* I suspect it doesn't matter whether this comes before or after the threads_unfinished check. */
+               while(parent->parallel_pending)
+                       PTHREAD_VERIFY(pthread_cond_wait(&parent->cond, &parent->mutex));
+
+               --parent->parallel_unfinished;
+               if(!parent->parallel_unfinished)
+                       PTHREAD_VERIFY(pthread_cond_broadcast(&parent->cond)); /* All threads done for now. */
+       }
+
+       /* return _thread_destroy_and_unlock(parent, thread); */
+}
+
+static void _unlock_and_destroy(struct threadpool *self)
+{
+       pthread_t *threads;
+
+       threads = self->parallel_threads;
+       self->parallel_threads = NULL;
+
+       if(threads)
+               PTHREAD_VERIFY(pthread_cond_broadcast(&self->cond));
+
+       PTHREAD_VERIFY(pthread_mutex_unlock(&self->mutex));
+
+       if(threads)
+       {
+               unsigned i, count = _threadpool_count_parallel(self);
+               for(i = 0; i != count; ++i)
+                       PTHREAD_VERIFY(pthread_join(threads[i], NULL));
+
+               free(threads);
+               PTHREAD_VERIFY(pthread_cond_destroy(&self->cond));
+               PTHREAD_VERIFY(pthread_mutex_destroy(&self->mutex));
+       }
+
+       _serial_destroy(self);
+}
+
+#endif /* HAVE_PTHREAD */
+
+int threadpool_create(struct threadpool *self, const struct threadpool_class *cls, Display *dpy, unsigned count)
+{
+       (void)threads_available(dpy);
+
+       self->count = count;
+
+/*     If threads are not present, run each "thread" in sequence on the calling
+       thread. Otherwise, only run the first thread on the main thread. */
+
+       assert(cls);
+
+       self->thread_size = cls->size;
+       self->thread_destroy = cls->destroy;
+
+       {
+               void *thread;
+               unsigned i, count_serial = _threadpool_count_serial(self);
+
+               if(count_serial)
+               {
+                       thread = malloc(cls->size * count_serial);
+                       if(!thread)
+                               return ENOMEM;
+               }
+               else
+               {
+                       /* Might as well skip the malloc. */
+                       thread = NULL;
+               }
+
+               self->serial_threads = thread;
+
+               for(i = 0; i != count_serial; ++i)
+               {
+                       int error = cls->create(thread, self, i);
+                       if(error)
+                       {
+                               self->count = i;
+                               _serial_destroy(self);
+                               return error;
+                       }
+
+                       thread = (char *)thread + self->thread_size;
+               }
+       }
+
+#if HAVE_PTHREAD
+       assert(_has_pthread); /* _has_pthread should be either -1 or >0. */
+       if(_has_pthread >= 0)
+       {
+               unsigned count_parallel = _threadpool_count_parallel(self);
+               self->mutex = mutex_initializer;
+               self->cond = cond_initializer;
+               self->parallel_pending = 0;
+               self->parallel_unfinished = 0;
+               if(!count_parallel)
+               {
+                       self->parallel_threads = NULL;
+                       return 0;
+               }
+
+               self->parallel_threads = malloc(sizeof(pthread_t) * count_parallel);
+               if(!self->parallel_threads)
+                       return ENOMEM;
+
+               {
+                       struct _parallel_startup_type startup;
+                       startup.parent = self;
+                       startup.thread_create = cls->create;
+                       startup.last_errno = 0;
+
+                       PTHREAD_VERIFY(pthread_mutex_lock(&self->mutex));
+                       _add_next_thread(&startup);
+
+                       if(!startup.last_errno)
+                       {
+                               while(self->parallel_unfinished != count_parallel && self->parallel_threads)
+                                       PTHREAD_VERIFY(pthread_cond_wait(&self->cond, &self->mutex));
+                       }
+
+                       /* This must come after the if(!startup.last_errno). */
+                       if(startup.last_errno)
+                       {
+                               _unlock_and_destroy(self);
+                       }
+                       else
+                       {
+                               self->parallel_unfinished = 0;
+                               PTHREAD_VERIFY(pthread_mutex_unlock(&self->mutex));
+                       }
+
+                       return startup.last_errno;
+               }
+       }
+#endif
+
+       return 0;
+}
+
+void threadpool_destroy(struct threadpool *self)
+{
+#if HAVE_PTHREAD
+       if(_has_pthread >= 0)
+       {
+               PTHREAD_VERIFY(pthread_mutex_lock(&self->mutex));
+               _unlock_and_destroy(self);
+               return;
+       }
+#endif
+
+       _serial_destroy(self);
+}
+
+void threadpool_run(struct threadpool *self, void (*func)(void *))
+{
+#if HAVE_PTHREAD
+       if(_has_pthread >= 0)
+       {
+               unsigned count = _threadpool_count_parallel(self);
+               PTHREAD_VERIFY(pthread_mutex_lock(&self->mutex));
+
+               /* Do not call threadpool_run() twice without a threadpool_wait() in the middle. */
+               assert(!self->parallel_pending);
+               assert(!self->parallel_unfinished);
+
+               self->parallel_pending = count;
+               self->parallel_unfinished = count;
+               self->thread_run = func;
+               PTHREAD_VERIFY(pthread_cond_broadcast(&self->cond));
+               PTHREAD_VERIFY(pthread_mutex_unlock(&self->mutex));
+       }
+#endif
+
+       /* It's perfectly valid to move this to the beginning of threadpool_wait(). */
+       {
+               void *thread = self->serial_threads;
+               unsigned i, count = _threadpool_count_serial(self);
+               for(i = 0; i != count; ++i)
+               {
+                       func(thread);
+                       thread = (char *)thread + self->thread_size;
+               }
+       }
+}
+
+void threadpool_wait(struct threadpool *self)
+{
+#if HAVE_PTHREAD
+       if(_has_pthread >= 0)
+       {
+               PTHREAD_VERIFY(pthread_mutex_lock(&self->mutex));
+               while(self->parallel_unfinished)
+                       PTHREAD_VERIFY(pthread_cond_wait(&self->cond, &self->mutex));
+               PTHREAD_VERIFY(pthread_mutex_unlock(&self->mutex));
+       }
+#endif
+}
+
+/* io_thread - */
+
+#if HAVE_PTHREAD
+/* Without threads at compile time, there's only stubs in thread_util.h. */
+
+#      define VERSION_CHECK(cc_major, cc_minor, req_major, req_minor) \
+       ((cc_major) > (req_major) || \
+       (cc_major) == (req_major) && (cc_minor) >= (req_minor))
+
+#      if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 7) || \
+       defined(__clang__) && \
+               (!defined(__apple_build_version__) && VERSION_CHECK(__clang_major__, __clang_minor__, 3, 1) || \
+                 defined(__apple_build_version__) && VERSION_CHECK(__clang_major__, __clang_minor__, 3, 1)) || \
+       defined(__ICC) && __ICC >= 1400
+
+/*
+   Clang 3.0 has a partial implementation of GNU atomics; 3.1 rounds it out.
+   http://llvm.org/viewvc/llvm-project/cfe/tags/RELEASE_30/final/include/clang/Basic/Builtins.def?view=markup
+   http://llvm.org/viewvc/llvm-project/cfe/tags/RELEASE_31/final/include/clang/Basic/Builtins.def?view=markup
+
+   Apple changes the Clang version to track Xcode versions; use
+   __apple_build_version__ to distinguish between the two.
+
+   Xcode 4.3 uses Apple LLVM 3.1, which corresponds to Clang 3.1.
+   https://en.wikipedia.org/wiki/Xcode
+
+   Earlier versions of Intel C++ may also support these intrinsics.
+ */
+
+#define _status_load(status) (__atomic_load_n((status), __ATOMIC_SEQ_CST))
+#define _status_exchange(obj, desired) (__atomic_exchange_n((obj), (desired), __ATOMIC_SEQ_CST))
+
+/* C11 atomics are around the corner, but they're not here yet for many
+   systems. (Including mine.) */
+/*
+#elif __STDC_VERSION__ >= 201112l && !defined __STDC_NO_ATOMICS__
+
+#include <stdatomic.h>
+
+#define _status_load(status) (atomic_load((status)))
+#define _status_exchange(obj, desired) (atomic_exchange((obj), (desired)))
+*/
+
+/* Solaris profiles atomic ops on at least Solaris 10. See atomic_swap(3C) and
+   membar_ops(3C). This would probably also need a snippet in configure.in.
+   http://graegert.com/programming/using-atomic-operations-in-c-on-solaris-10
+*/
+
+#      else
+
+/* No atomic variables, so here's some ugly mutex-based code instead. */
+
+/* Nothing ever destroys this mutex. */
+pthread_mutex_t _global_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+#define _lock()        PTHREAD_VERIFY(pthread_mutex_lock(&_global_mutex))
+#define _unlock() PTHREAD_VERIFY(pthread_mutex_unlock(&_global_mutex))
+
+static enum _io_thread_status _status_load(enum _io_thread_status *status)
+{
+       enum _io_thread_status result;
+       _lock();
+       result = *status;
+       _unlock();
+       return result;
+}
+
+static enum _io_thread_status _status_exchange(enum _io_thread_status *obj, enum _io_thread_status desired)
+{
+       enum _io_thread_status result;
+       _lock();
+       result = *obj;
+       *obj = desired;
+       _unlock();
+       return result;
+}
+
+#      endif
+
+void *io_thread_create(struct io_thread *self, void *parent, void *(*start_routine)(void *), Display *dpy, unsigned stacksize)
+{
+       if(threads_available(dpy) >= 0)
+       {
+               int error;
+               pthread_attr_t attr;
+               pthread_attr_t *attr_ptr = NULL;
+
+               if(stacksize)
+               {
+                       attr_ptr = &attr;
+                       if(pthread_attr_init(&attr))
+                               return NULL;
+#   if (defined _POSIX_SOURCE || defined _POSIX_C_SOURCE || defined _XOPEN_SOURCE) && !defined __GNU__
+                       /* PTHREAD_STACK_MIN needs the above test. */
+                       assert(stacksize >= PTHREAD_STACK_MIN);
+#   endif
+                       PTHREAD_VERIFY(pthread_attr_setstacksize(&attr, stacksize));
+               }
+
+               /* This doesn't need to be an atomic store, since pthread_create(3)
+                  "synchronizes memory with respect to other threads".
+                  http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_11 */
+               self->status = _io_thread_working;
+
+               error = pthread_create(&self->thread, attr_ptr, start_routine, parent);
+               assert(!error || error == EAGAIN);
+               if(error)
+                       parent = NULL;
+
+               if(attr_ptr)
+                       PTHREAD_VERIFY(pthread_attr_destroy(attr_ptr));
+
+               return parent;
+       }
+
+       return NULL;
+}
+
+int io_thread_return(struct io_thread *self)
+{
+       if(_has_pthread >= 0)
+       {
+               enum _io_thread_status old_status = _status_exchange(&self->status, _io_thread_done);
+               assert(old_status == _io_thread_working ||
+                      old_status == _io_thread_cancelled);
+               return old_status != _io_thread_working;
+       }
+
+       return 0;
+}
+
+int io_thread_is_done(struct io_thread *self)
+{
+       if(_has_pthread >= 0)
+       {
+               int result = _status_load(&self->status);
+               assert(result != _io_thread_cancelled);
+               return result;
+       }
+       return 1;
+}
+
+int io_thread_cancel(struct io_thread *self)
+{
+       if(_has_pthread >= 0)
+       {
+               enum _io_thread_status old_status =
+                       _status_exchange(&self->status, _io_thread_cancelled);
+               assert(old_status == _io_thread_working ||
+                      old_status == _io_thread_done);
+
+               PTHREAD_VERIFY(pthread_detach(self->thread));
+               return old_status != _io_thread_working;
+       }
+
+       return 0;
+}
+
+void io_thread_finish(struct io_thread *self)
+{
+       if(_has_pthread >= 0)
+       {
+#      ifndef NDEBUG
+               enum _io_thread_status status = _status_load(&self->status);
+               assert(status == _io_thread_working ||
+                      status == _io_thread_done);
+#      endif
+               PTHREAD_VERIFY(pthread_join(self->thread, NULL));
+               assert(_status_load(&self->status) == _io_thread_done);
+       }
+}
+
+#endif /* HAVE_PTHREAD */
diff --git a/screenhack/thread_util.h b/screenhack/thread_util.h
new file mode 100644 (file)
index 0000000..6dff8de
--- /dev/null
@@ -0,0 +1,446 @@
+/* -*- mode: c; tab-width: 4; fill-column: 78 -*- */
+/* vi: set ts=4 tw=78: */
+
+/*
+thread_util.h, Copyright (c) 2014 Dave Odell <dmo2118@gmail.com>
+
+Permission to use, copy, modify, distribute, and sell this software and its
+documentation for any purpose is hereby granted without fee, provided that
+the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation.  No representations are made about the suitability of this
+software for any purpose.  It is provided "as is" without express or
+implied warranty.
+*/
+
+#ifndef THREAD_UTIL_H
+#define THREAD_UTIL_H
+
+/* thread_util.h because C11 took threads.h. */
+
+/* And POSIX threads because there aren't too many systems that support C11
+   threads that don't already support POSIX threads.
+   ...Not that it would be too hard to convert from the one to the other.
+   Or to have both.
+ */
+
+/* Beware!
+   Multithreading is a great way to add insidious and catastrophic bugs to
+   a program. Make sure you understand the risks.
+
+   You may wish to become familiar with race conditions, deadlocks, mutexes,
+   condition variables, and, in lock-free code, memory ordering, cache
+   hierarchies, etc., before working with threads.
+
+   On the other hand, if a screenhack locks up or crashes, it's not the
+   end of the world: XScreenSaver won't unlock the screen if that happens.
+*/
+
+/*
+   The basic stragegy for applying threads to a CPU-hungry screenhack:
+
+   1. Find the CPU-hungry part of the hack.
+
+   2. Change that part so the workload can be divided into N equal-sized
+      loads, where N is the number of CPU cores in the machine.
+      (For example: with two cores, one core could render even scan lines,
+      and the other odd scan lines.)
+
+   2a. Keeping in mind that two threads should not write to the same memory
+       at the same time. Specifically, they should not be writing to the
+       same cache line at the same time -- so align memory allocation and
+       memory accesses to the system cache line size as necessary.
+
+   3. On screenhack_init, create a threadpool object. This creates N worker
+      threads, and each thread creates and owns a user-defined struct.
+      After creation, the threads are idle.
+
+   4. On screenhack_frame, call threadpool_run(). Each thread simultaneously
+      wakes up, calls a function that does one of the equal-sized loads,
+      then goes back to sleep. The main thread then calls threadpool_wait(),
+      which returns once all the worker threads have finished.
+
+      Using this to implement SMP won't necessarily increase performance by
+      a factor of N (again, N is CPU cores.). Both X11 and Cocoa on OS X can
+      impose a not-insignificant amount of overhead even when simply blitting
+      full-screen XImages @ 30 FPS.
+
+      On systems with simultaneous multithreading (a.k.a. Hyper-threading),
+      performance gains may be slim to non-existant.
+ */
+
+#include "aligned_malloc.h"
+
+#if HAVE_CONFIG_H
+/* For HAVE_PTHREAD. */
+#      include "config.h"
+#endif
+
+#include <stddef.h>
+
+#if HAVE_UNISTD_H
+/* For _POSIX_THREADS. */
+#      include <unistd.h>
+#endif
+
+#if defined HAVE_JWXYZ
+#      include "jwxyz.h"
+#else
+#      include <X11/Xlib.h>
+#endif
+
+#if HAVE_PTHREAD
+int threads_available(Display *dpy);
+#else
+#      define threads_available(dpy) (-1)
+#endif
+/* > 0: Threads are available. This is normally _POSIX_VERSION.
+    -1: Threads are not available.
+*/
+
+unsigned hardware_concurrency(Display *dpy);
+/* This is supposed to return the number of available CPU cores. This number
+   isn't necessarily constant: a system administrator can hotplug or
+   enable/disable CPUs on certain systems, or the system can deactivate a
+   malfunctioning core -- but these are rare.
+
+   If threads are unavailable, this function will return 1.
+
+   This function isn't fast; the result should be cached.
+*/
+
+unsigned thread_memory_alignment(Display *dpy);
+
+/* Returns the proper alignment for memory allocated by a thread that is
+   shared with other threads.
+
+   A typical CPU accesses the system RAM through a cache, and this cache is
+   divided up into cache lines - aligned chunks of memory typically 32 or 64
+   bytes in size. Cache faults cause cache lines to be populated from
+   memory. And, in a multiprocessing environment, two CPU cores can access the
+   same cache line. The consequences of this depend on the CPU model:
+
+   - x86 implements the MESI protocol [1] to maintain cache coherency between
+     CPU cores, with a serious performance penalty on both Intel [1] and AMD
+     [2].  Intel uses the term "false sharing" to describe two CPU cores
+     accessing different memory in the same cache line.
+
+   - ARM allows CPU caches to become inconsistent in this case [3]. Memory
+     fences are needed to prevent horrible non-deterministic bugs from
+     occurring.  Other CPU architectures have similar behavior to one of the
+     above, depending on whether they are "strongly-orderered" (like x86), or
+     "weakly-ordered" (like ARM).
+
+   Aligning multithreaded memory accesses according to the cache line size
+   neatly sidesteps both issues.
+
+   One complication is that CPU caches are divided up into separate levels,
+   and occasionally different levels can have different cache line sizes, so
+   to be safe this function returns the largest cache line size among all
+   levels.
+
+   If multithreading is not in effect, this returns sizeof(void *), because
+   posix_memalign(3) will error out if the alignment is set to be smaller than
+   that.
+
+   [1] Intel(R) 64 and IA-32 Architectures Optimization Reference Manual
+      (Order Number: 248966-026): 2.1.5 Cache Hierarchy
+   [2] Software Optimization Guide for AMD Family 10h Processors (Publication
+       #40546): 11.3.4 Data Sharing between Caches
+   [3] http://wanderingcoder.net/2011/04/01/arm-memory-ordering/
+*/
+
+/*
+   Note: aligned_malloc uses posix_memalign(3) when available, or malloc(3)
+   otherwise. As of SUSv2 (1997), and *probably* earlier, these are guaranteed
+   to be thread-safe. C89 does not discuss threads, or thread safety;
+   non-POSIX systems, watch out!
+   http://pubs.opengroup.org/onlinepubs/7908799/xsh/threads.html
+   http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html
+*/
+
+/* int thread_malloc(void **ptr, Display *dpy, unsigned size); */
+#define thread_malloc(ptr, dpy, size) \
+  (aligned_malloc((ptr), thread_memory_alignment(dpy), (size)))
+
+/*
+   This simply does a malloc aligned to thread_memory_alignment(). See
+   above. On failure, an errno is returned, usually ENOMEM.
+
+   It's possible for two malloc()'d blocks to at least partially share the
+   same cache line. When a different thread is writing to each block, then bad
+   things can happen (see thread_memory_alignment). Better malloc()
+   implementations will divide memory into pools belonging to one thread or
+   another, causing memory blocks belonging to different threads to typically
+   be located on different memory pages (see getpagesize(2)), mitigating the
+   problem in question...but there's nothing stopping threads from passing
+   memory to each other. And it's not practical for the system to align each
+   block to 64 or 128 byte boundaries -- it's not uncommon to need lots and
+   lots of 8-32 byte allocations, and the waste could become a bit excessive.
+
+   Some rules of thumb to take away from this:
+
+   1. Use thread_alloc for memory that might be written to by a thread that
+   didn't originally allocate the object.
+
+   2. Use thread_alloc for memory that will be handed from one thread to
+   another.
+
+   3. Use malloc if a single thread allocates, reads from, writes to, and
+   frees the block of memory.
+
+   Oddly, I (Dave) have not seen this problem described anywhere else.
+*/
+
+#define thread_free(ptr) aligned_free(ptr)
+
+#if HAVE_PTHREAD
+#      if defined _POSIX_THREADS && _POSIX_THREADS >= 0
+/*
+   See The Open Group Base Specifications Issue 7, <unistd.h>, Constants for
+   Options and Option Groups
+   http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/unistd.h.html#tag_13_77_03_02
+*/
+
+#              include <pthread.h>
+
+/* Most PThread synchronization functions only fail when they are misused. */
+#              if defined NDEBUG
+#                      define PTHREAD_VERIFY(expr) (void)(expr)
+#              else
+#                      include <assert.h>
+#                      define PTHREAD_VERIFY(expr) assert(!(expr))
+#              endif
+
+extern const pthread_mutex_t mutex_initializer;
+extern const pthread_cond_t cond_initializer;
+
+#      else
+               /* Whatever caused HAVE_PTHREAD to be defined (configure script,
+           usually) made a mistake if this is reached. */
+               /* Maybe this should be a warning. */
+#              error HAVE_PTHREAD is defined, but _POSIX_THREADS is not.
+               /* #undef HAVE_PTHREAD */
+#      endif
+#endif
+
+struct threadpool
+{
+/*     This is always the same as the count parameter fed to threadpool_create().
+       Here's a neat trick: if the threadpool is zeroed out with a memset, and
+       threadpool_create() is never called to create 0 threads, then
+       threadpool::count can be used to determine if the threadpool object was
+       ever initialized. */
+       unsigned count;
+
+       /* Copied from threadpool_class. No need for thread_create here, though. */
+       size_t thread_size;
+       void (*thread_run)(void *self);
+       void (*thread_destroy)(void *self);
+
+       void *serial_threads;
+
+#if HAVE_PTHREAD
+       pthread_mutex_t mutex;
+       pthread_cond_t cond;
+
+       /* Number of threads waiting for the startup signal. */
+       unsigned parallel_pending;
+
+       /* Number of threads still running. During startup, this is the index of the thread currently being initialized. */
+       unsigned parallel_unfinished;
+
+       pthread_t *parallel_threads;
+#endif
+};
+
+/*
+   The threadpool_* functions manage a group of threads (naturally).  Each
+   thread owns an object described by a threadpool_class. When
+   threadpool_run() is called, the specified func parameter is called on each
+   thread in parallel. Sometime after calling threadpool_run(), call
+   threadpool_wait(), which waits for each thread to return from
+   threadpool_class::run().
+
+   Note that thread 0 runs on the thread from which threadpool_run is called
+   from, so if each thread has an equal workload, then when threadpool_run
+   returns, the other threads will be finished or almost finished. Adding code
+   between threadpool_run and threadpool_wait increases the odds that
+   threadpool_wait won't actually have to wait at all -- which is nice.
+
+   If the system does not provide threads, then these functions will fake it:
+   everything will appear to work normally from the perspective of the caller,
+   but when threadpool_run() is called, the "threads" are run synchronously;
+   threadpool_wait() does nothing.
+*/
+
+struct threadpool_class
+{
+       /* Size of the thread private object. */
+       size_t size;
+
+/*     Create the thread private object. Called in sequence for each thread
+       (effectively) from threadpool_create.
+    self: A pointer to size bytes of memory, allocated to hold the thread
+          object.
+    pool: The threadpool object that owns all the threads. If the threadpool
+          is nested in another struct, try GET_PARENT_OBJ.
+    id:   The ID for the thread; numbering starts at zero and goes up by one
+          for each thread.
+    Return 0 on success. On failure, return a value from errno.h; this will
+    be returned from threadpool_create. */
+       int (*create)(void *self, struct threadpool *pool, unsigned id);
+
+/*     Destroys the thread private object. Called in sequence (though not always
+       the same sequence as create).  Warning: During shutdown, it is possible
+       for destroy() to be called while other threads are still in
+       threadpool_run(). */
+       void (*destroy)(void *self);
+};
+
+/* Returns 0 on success, on failure can return ENOMEM, or any error code from
+   threadpool_class.create. */
+int threadpool_create(struct threadpool *self, const struct threadpool_class *cls, Display *dpy, unsigned count);
+void threadpool_destroy(struct threadpool *self);
+
+void threadpool_run(struct threadpool *self, void (*func)(void *));
+void threadpool_wait(struct threadpool *self);
+
+/*
+   io_thread is meant to wrap blocking I/O operations in a one-shot worker
+   thread, with cancel semantics.
+
+   Unlike threadpool_*, io_thread will not 'fake it'; it is up to the caller
+   to figure out what to do if the system doesn't have threads. In
+   particular, the start_routine passed to io_thread_create will never be
+   called.
+
+   Clients of io_thread implement four functions:
+   - state *process_start(...);
+     Starts the worker thread.
+   - bool process_is_done(state *);
+     Returns true if the I/O operation is complete.
+   - void process_cancel(state *);
+     "Cancels" the I/O operation. The thread will continue to run, but it
+     will detach, and clean itself up upon completion.
+   - int process_finish(state *, ...)
+     Waits for the I/O operation to complete, returns results, and cleans up.
+
+   Or:        /---\
+             \/   |    /--> cancel
+   start -> is_done --+
+                       \--> finish
+
+   These functions follow a basic pattern:
+   - start:
+     1. Allocate a thread state object with thread_alloc. This state object
+        contains an io_thread member.
+     2. Save parameters from the start parameters to the state object.
+     3. Start the thread with _io_thread_create. The thread receives the state
+        object as its parameter.
+   - On the worker thread:
+     1. Do the I/O.
+     2. Call io_thread_return.
+       2a. If the result != 0, free the state object.
+   - is_done:
+     1. Just call _io_thread_is_done.
+   - cancel:
+     1. Call io_thread_cancel.
+       1a. If the result != 0, free the state object.
+   - finish:
+     1. Call io_thread_finish.
+     2. Copy results out of the state object as needed.
+     3. Free the state object...or return it to the caller.
+
+   Incidentally, there may sometimes be asynchronous versions of blocking I/O
+   functions (struct aiocb and friends, for example); these should be
+   preferred over io_thread when performance is a concern.
+ */
+
+enum _io_thread_status
+{
+       _io_thread_working, _io_thread_done, _io_thread_cancelled
+};
+
+struct io_thread
+{
+#if HAVE_PTHREAD
+       /* Common misconception: "volatile" should be applied to atomic variables,
+          such as 'status', below. This is false, see
+          <http://stackoverflow.com/q/2484980>. */
+       enum _io_thread_status status;
+       pthread_t thread;
+#else
+       char gcc_emits_a_warning_when_the_struct_has_no_members;
+#endif
+};
+
+#if HAVE_PTHREAD
+
+void *io_thread_create(struct io_thread *self, void *parent, void *(*start_routine)(void *), Display *dpy, unsigned stacksize);
+/*
+   Create the thread, returns NULL on failure.  Failure is usually due to
+   ENOMEM, or the system doesn't support threads.
+   self:          The io_thread object to be initialized.
+   parent:        The parameter to start_routine.  The io_thread should be
+                  contained within or be reachable from this.
+   start_routine: The start routine for the worker thread.
+   dpy:           The X11 Display, so that '*useThreads' is honored.
+   stacksize:     The stack size for the thread. Set to 0 for the system
+                  default.
+   A note about stacksize: Linux, for example, uses a default of 2 MB of
+   stack per thread. Now, this memory is usually committed on the first
+   write, so lots of threads won't waste RAM, but it does mean that on a
+   32-bit system, there's a limit of just under 1024 threads with the 2 MB
+   default due to typical address space limitations of 2 GB for userspace
+   processes.  And 1024 threads might not always be enough...
+ */
+
+int io_thread_return(struct io_thread *self);
+/* Called at the end of start_routine, from above. Returns non-zero if the
+   thread has been cancelled, and cleanup needs to take place. */
+
+int io_thread_is_done(struct io_thread *self);
+/* Call from the main thread. Returns non-zero if the thread finished. */
+
+int io_thread_cancel(struct io_thread *self);
+/* Call from the main thread if the results from the worker thread are not
+   needed. This cleans up the io_thread. Returns non-zero if cleanup needs
+   to take place. */
+
+void io_thread_finish(struct io_thread *self);
+/* Call from the main thread to wait for the worker thread to finish. This
+   cleans up the io_thread. */
+
+#else
+
+#define IO_THREAD_STACK_MIN 0
+
+#define io_thread_create(self, parent, start_routine, dpy, stacksize) NULL
+#define io_thread_return(self) 0
+#define io_thread_is_done(self) 1
+#define io_thread_cancel(self) 0
+#define io_thread_finish(self)
+
+#endif
+
+#if HAVE_PTHREAD
+#      define THREAD_DEFAULTS       "*useThreads: True",
+#      define THREAD_DEFAULTS_XLOCK "*useThreads: True\n"
+#      define THREAD_OPTIONS \
+       {"-threads",    ".useThreads", XrmoptionNoArg, "True"}, \
+       {"-no-threads", ".useThreads", XrmoptionNoArg, "False"},
+#else
+#      define THREAD_DEFAULTS
+#      define THREAD_DEFAULTS_XLOCK
+#      define THREAD_OPTIONS
+#endif
+
+/*
+   If a variable 'member' is known to be a member (named 'member_name') of a
+   struct (named 'struct_name'), then this can find a pointer to the struct
+   that contains it.
+*/
+#define GET_PARENT_OBJ(struct_name, member_name, member) (struct_name *)((char *)member - offsetof(struct_name, member_name));
+
+#endif
diff --git a/screenhack/usleep.c b/screenhack/usleep.c
new file mode 100644 (file)
index 0000000..f065729
--- /dev/null
@@ -0,0 +1,64 @@
+/* xscreensaver, Copyright (c) 1992, 1996, 1997, 2003
+ *  Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#else  /* !HAVE_CONFIG_H */
+# ifndef NO_SELECT
+#  define HAVE_SELECT
+# endif
+#endif /* !HAVE_CONFIG_H */
+
+#ifdef __STDC__
+# include <stdlib.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#if defined(VMS)
+# include <descrip.h>
+# include <stdio.h>
+# include <lib$routines.h>
+#elif defined(HAVE_SELECT)
+# include <sys/time.h>         /* for struct timeval */
+#endif
+
+
+#ifdef __SCREENHACK_USLEEP_H__
+ERROR, do not include that here
+#endif
+
+extern void screenhack_usleep (unsigned long usecs); /* suppress warning */
+
+void
+screenhack_usleep (unsigned long usecs)
+{
+# if defined(VMS)
+  float seconds = ((float) usecs)/1000000.0;
+  unsigned long int statvms = lib$wait(&seconds);
+
+#elif defined(HAVE_SELECT)
+  /* usleep() doesn't exist everywhere, and select() is faster anyway. */
+  struct timeval tv;
+  tv.tv_sec  = usecs / 1000000L;
+  tv.tv_usec = usecs % 1000000L;
+  (void) select (0, 0, 0, 0, &tv);
+
+#else /* !VMS && !HAVE_SELECT */
+  /* If you don't have select() or usleep(), I guess you lose...
+     Maybe you have napms() instead?  Let me know. */
+  usleep (usecs);
+
+#endif /* !VMS && !HAVE_SELECT */
+}
diff --git a/screenhack/usleep.h b/screenhack/usleep.h
new file mode 100644 (file)
index 0000000..f32a9c9
--- /dev/null
@@ -0,0 +1,24 @@
+/* xscreensaver, Copyright (c) 1992, 1996 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifndef __SCREENHACK_USLEEP_H__
+#define __SCREENHACK_USLEEP_H__
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+extern void screenhack_usleep (unsigned long usecs);
+
+#undef usleep
+#define usleep(usecs) screenhack_usleep(usecs)
+
+#endif /* __SCREENHACK_USLEEP_H__ */
diff --git a/screenhack/utf8wc.c b/screenhack/utf8wc.c
new file mode 100644 (file)
index 0000000..b445f30
--- /dev/null
@@ -0,0 +1,928 @@
+/* xscreensaver, Copyright (c) 2014-2016 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifdef HAVE_JWXYZ
+# include "jwxyz.h"
+#else /* !HAVE_JWXYZ */
+# include <X11/Xlib.h>
+#endif
+
+#include "utf8wc.h"
+
+
+/* "Unicode Replacement Character", displayed in lieu of invalid characters. */
+# define INVALID 0xFFFD
+
+
+/* Mask the number to be within the valid range of unicode characters.
+ */
+static unsigned long
+uc_truncate (unsigned long uc)
+{
+  uc &= 0x7FFFFFFFL;                   /* Unicode is 31 bits */
+  if (uc > 0x10FFFF) uc = INVALID;     /* But UTF-8 is 4 bytes */
+  if (uc == 0) uc = INVALID;           /* no nulls */
+
+  if (uc >= 0xD800 && uc <= 0xDFFF)
+    /* Reserved for use with UTF-16: not a real character. */
+    uc = INVALID;
+
+  return uc;
+}
+
+
+/* Parse the first UTF8 character at the front of the string.
+   Return the Unicode character, and the number of bytes read.
+ */
+long
+utf8_decode (const unsigned char *in, long length, unsigned long *unicode_ret)
+{
+  const unsigned char *start = in;
+  const unsigned char *end = in + length;
+  unsigned long uc = INVALID;
+  unsigned long min = 0;
+  unsigned char c;
+
+  if (length <= 0) goto DONE;
+
+  c = *in++;
+
+# define PREMATURE_EOF { in = end; goto DONE; }
+
+  if ((c & 0xC0) == 0x80) {        /* 10xxxxxx - lonely continuation byte */
+    uc = INVALID;
+
+  } else if ((c & 0x80) == 0) {    /* 0xxxxxxx - 7 bits in 1 byte */
+    uc = (c & 0x7F);               /* 01111111 */
+
+  } else if ((c & 0xE0) == 0xC0) { /* 110xxxxx - 11 bits in 2 bytes */
+    if (in+1 > end) PREMATURE_EOF;
+    min = 1 << 7;
+    uc = (((c    & 0x1F) << 6) |   /* 00011111------ */
+          (in[0] & 0x3F));         /*       00111111 */
+    in += 1;
+
+  } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx - 16 bits in 3 bytes */
+    if (in+2 > end) PREMATURE_EOF;
+    min = 1 << 11;
+    uc = (((c     & 0x0F) << 12) | /* 00001111----+------- */
+          ((in[0] & 0x3F) <<  6) | /*       00111111------ */
+          ((in[1] & 0x3F)));       /*             00111111 */
+    in += 2;
+
+  } else if ((c & 0xF8) == 0xF0) { /* 11110xxx - 21 bits in 4 bytes */
+    if (in+3 > end) PREMATURE_EOF;
+    min = 1 << 16;
+    uc = (((c     & 0x07) << 18) | /* 00000111--+-------+------- */
+          ((in[0] & 0x3F) << 12) | /*       01111111----+------- */
+          ((in[1] & 0x3F) <<  6) | /*             00111111------ */
+          ((in[2] & 0x3F)));       /*                   00111111 */
+    in += 3;
+
+  } else if ((c & 0xFC) == 0xF8) { /* 111110xx - 26 bits in 5 bytes */
+    if (in+4 > end) PREMATURE_EOF;
+    min = 1 << 21;
+    uc = (((c     & 0x03) << 24) | /* 00000011--------+-------+------- */
+          ((in[0] & 0x3F) << 18) | /*       00111111--+-------+------- */
+          ((in[1] & 0x3F) << 12) | /*             00111111----+------- */
+          ((in[2] & 0x3F) << 6)  | /*                   00111111------ */
+          ((in[3] & 0x3F)));       /*                         00111111 */
+    in += 4;
+
+  } else if ((c & 0xFE) == 0xFC) { /* 1111110x - 31 bits in 6 bytes */
+    if (in+5 > end) PREMATURE_EOF;
+    min = 1 << 26;
+    uc = (((c     & 0x01) << 30) | /* 00000001------+-------+-------+------- */
+          ((in[0] & 0x3F) << 24) | /*       00111111+-------+-------+------- */
+          ((in[1] & 0x3F) << 18) | /*             00111111--+-------+------- */
+          ((in[2] & 0x3F) << 12) | /*                   00111111----+------- */
+          ((in[3] & 0x3F) << 6)  | /*                         00111111------ */
+          ((in[4] & 0x3F)));       /*                               00111111 */
+    in += 5;
+  } else {
+    uc = INVALID;                 /* Unparsable sequence. */
+  }
+
+ DONE:
+
+  length = in - start;
+
+  /* If any of the continuation bytes didn't begin with the continuation tag,
+     the sequence is invalid; stop at the bad byte, not consuming later ones.
+     (It's easier to check this after the fact than up above.) */
+  {
+    int i;
+    for (i = 1; i < length; i++)
+      if ((start[i] & 0xC0) != 0x80) {
+        uc = INVALID;
+        length = i+1;
+        break;
+      }
+  }
+
+  if (uc < min)
+    /* A multi-byte sequence encoded a character that could have been
+       encoded with a shorter sequence, e.g., hiding ASCII inside a
+       multi-byte sequence. Something hinky's going on. Reject it. */
+    uc = INVALID;
+
+  uc = uc_truncate (uc);
+
+  if (unicode_ret)
+    *unicode_ret = uc;
+
+  return length;
+}
+
+
+/* Converts a Unicode character to a multi-byte UTF8 sequence.
+   Returns the number of bytes written.
+ */
+int
+utf8_encode (unsigned long uc, char *out, long length)
+{
+  const char *old = out;
+
+  uc = uc_truncate (uc);
+
+  if (uc < 0x80 && length >= 1)                        /* 7 bits in 1 byte */
+    {
+      *out++ = uc;                             /* 0xxxxxxx */
+    }
+  else if (uc < 0x800 && length >= 2)          /* 11 bits in 2 bytes */
+    {
+      *out++ = (0xC0 | ((uc >> 6)  & 0x1F));   /* 110xxxxx */
+      *out++ = (0x80 |  (uc        & 0x3F));   /* 10xxxxxx */
+    }
+  else if (uc < 0x10000L && length >= 3)       /* 16 bits in 3 bytes */
+    {
+      *out++ = (0xE0 | ((uc >> 12) & 0x0F));   /* 1110xxxx */
+      *out++ = (0x80 | ((uc >>  6) & 0x3F));   /* 10xxxxxx */
+      *out++ = (0x80 |  (uc        & 0x3F));   /* 10xxxxxx */
+    }
+  else if (uc < 0x200000L && length >= 4)      /* 21 bits in 4 bytes */
+    {
+      *out++ = (0xF0 | ((uc >> 18) & 0x07));   /* 11110xxx */
+      *out++ = (0x80 | ((uc >> 12) & 0x3F));   /* 10xxxxxx */
+      *out++ = (0x80 | ((uc >>  6) & 0x3F));   /* 10xxxxxx */
+      *out++ = (0x80 |  (uc        & 0x3F));   /* 10xxxxxx */
+    } 
+  else if (uc < 0x4000000L && length >= 5)     /* 26 bits in 5 bytes */
+    {
+      *out++ = (0xF8 | ((uc >> 24) & 0x03));   /* 111110xx */
+      *out++ = (0x80 | ((uc >> 18) & 0x3F));   /* 10xxxxxx */
+      *out++ = (0x80 | ((uc >> 12) & 0x3F));   /* 10xxxxxx */
+      *out++ = (0x80 | ((uc >>  6) & 0x3F));   /* 10xxxxxx */
+      *out++ = (0x80 |  (uc        & 0x3F));   /* 10xxxxxx */
+    }
+  else if (length >= 6)                                /* 31 bits in 6 bytes */
+    {
+      *out++ = (0xFC | ((uc >> 30) & 0x01));   /* 1111110x */
+      *out++ = (0x80 | ((uc >> 24) & 0x3F));   /* 10xxxxxx */
+      *out++ = (0x80 | ((uc >> 18) & 0x3F));   /* 10xxxxxx */
+      *out++ = (0x80 | ((uc >> 12) & 0x3F));   /* 10xxxxxx */
+      *out++ = (0x80 | ((uc >>  6) & 0x3F));   /* 10xxxxxx */
+      *out++ = (0x80 |  (uc        & 0x3F));   /* 10xxxxxx */
+    }
+
+  return (int) (out - old);
+}
+
+
+/* Converts a null-terminated UTF8 string to a null-terminated XChar2b array.
+   This only handles characters that can be represented in 16 bits, the
+   Basic Multilingual Plane. (No hieroglyphics, Elvish, Klingon or Emoji.)
+ */
+XChar2b *
+utf8_to_XChar2b (const char *string, int *length_ret)
+{
+  long in_len = strlen(string);
+  const unsigned char *in = (const unsigned char *) string;
+  const unsigned char *in_end = in + in_len;
+  XChar2b *c2b = (XChar2b *) malloc ((in_len + 1) * sizeof(*c2b));
+  XChar2b *out = c2b;
+  if (! out) return 0;
+
+  while (in < in_end)
+    {
+      unsigned long uc = 0;
+      long L = utf8_decode (in, in_end - in, &uc);
+      in += L;
+
+      /* If it can't be represented in a 16-bit XChar2b,
+         use "Unicode Replacement Character". */
+      if (uc > 0xFFFF) uc = INVALID;
+
+      out->byte1 = (uc >> 8) & 0xFF;
+      out->byte2 = uc & 0xFF;
+      out++;
+    }
+
+  out->byte1 = 0;
+  out->byte2 = 0;
+
+  if (length_ret)
+    *length_ret = (int) (out - c2b);
+
+  /* shrink */
+  c2b = (XChar2b *) realloc (c2b, (out - c2b + 1) * sizeof(*c2b));
+
+  return c2b;
+}
+
+
+/* Split a UTF8 string into an array of strings, one per character.
+   The sub-strings will be null terminated and may be multiple bytes.
+ */
+char **
+utf8_split (const char *string, int *length_ret)
+{
+  const unsigned char *in = (const unsigned char *) string;
+  long len = strlen (string);
+  const unsigned char *end = in + len;
+  char **ret = (char **) malloc ((len+1) * sizeof(*ret));
+  int i = 0;
+  int zwjp = 0;
+  if (!ret) return 0;
+
+  while (in < end)
+    {
+      unsigned long uc;
+      long len2 = utf8_decode (in, len, &uc);
+      char tmp[10];
+      memcpy (tmp, (char *) in, len2);
+      tmp[len2] = 0;
+      ret[i++] = strdup (tmp);
+      in += len2;
+
+      /* If this is a Combining Diacritical, append it to the previous
+         character. E.g., "y\314\206\314\206" is one string, not three.
+
+         If this is ZWJ, Zero Width Joiner, then we append both this character
+         and the following character, e.g. "X ZWJ Y" is one string not three.
+
+         #### Hmmm, should this also include every character in the
+         "Symbol, Modifier" category, or does ZWJ get used for those?
+         https://www.fileformat.info/info/unicode/category/Sk/list.htm
+
+         Is it intended that "Latin small letter C, 0063" + "Cedilla, 00B8"
+         should be a single glyph? Or is that what "Combining Cedilla, 0327"
+         is for?  I'm confused by the fact that the skin tones (1F3FB-1F3FF)
+         do not seem to be in a readily-identifiable block the way the various
+         combining diacriticals are.
+       */
+      if (i > 1 && 
+          ((uc >=   0x300 && uc <=   0x36F) || /* Combining Diacritical */
+           (uc >=  0x1AB0 && uc <=  0x1AFF) || /* Combining Diacritical Ext. */
+           (uc >=  0x1DC0 && uc <=  0x1DFF) || /* Combining Diacritical Supp. */
+           (uc >=  0x20D0 && uc <=  0x20FF) || /* Combining Diacritical Sym. */
+           (uc >=  0xFE20 && uc <=  0xFE2F) || /* Combining Half Marks */
+           (uc >= 0x1F3FB && uc <= 0x1F3FF) || /* Emoji skin tone modifiers */
+           zwjp || uc == 0x200D))              /* Zero Width Joiner */
+        {
+          long L1 = strlen(ret[i-2]);
+          long L2 = strlen(ret[i-1]);
+          char *s2 = (char *) malloc (L1 + L2 + 1);
+          memcpy (s2,      ret[i-2], L1);
+          memcpy (s2 + L1, ret[i-1], L2);
+          s2[L1 + L2] = 0;
+          free (ret[i-2]);
+          ret[i-2] = s2;
+          i--;
+          zwjp = (uc == 0x200D);  /* Swallow the next character as well */
+        }
+    }
+  ret[i] = 0;
+
+  if (length_ret)
+    *length_ret = i;
+
+  /* shrink */
+  ret = (char **) realloc (ret, (i+1) * sizeof(*ret));
+
+  return ret;
+}
+
+
+/* Converts a null-terminated XChar2b array to a null-terminated UTF8 string.
+ */
+char *
+XChar2b_to_utf8 (const XChar2b *in, int *length_ret)
+{
+  int in_len = 0;
+  const XChar2b *in_end;
+  int out_len;
+  char *utf8, *out;
+  const char *out_end;
+
+  /* Find the null termination on the XChar2b. */
+  for (in_end = in; in_end->byte1 || in_end->byte2; in_end++, in_len++)
+    ;
+
+  out_len = (in_len + 1) * 3;             /* 16 bit chars = 3 bytes max */
+  utf8 = out = (char *) malloc (out_len + 1);
+  if (! out) return 0;
+  out_end = out + out_len;
+
+  while (in < in_end)
+    {
+      unsigned long uc = (in->byte1 << 8) | in->byte2;
+      int wrote = utf8_encode (uc, out, out_end - out);
+      if (wrote > 3) abort();  /* Can't happen with 16 bit input */
+      out += wrote;
+      in++;
+    }
+  *out = 0;
+
+  out_len = (int) (out - utf8 + 1);
+
+  if (length_ret)
+    *length_ret = out_len;
+
+  /* shrink */
+  utf8 = (char *) realloc (utf8, out_len);
+
+  return utf8;
+}
+
+
+/* Converts a UTF8 string to the closest Latin1 or ASCII equivalent.
+ */
+char *
+utf8_to_latin1 (const char *string, Bool ascii_p)
+{
+  long in_len = strlen(string);
+  const unsigned char *in = (const unsigned char *) string;
+  const unsigned char *in_end = in + in_len;
+  unsigned char *ret = (unsigned char *) malloc (in_len + 1);
+  unsigned char *out = ret;
+
+  if (! ret) return 0;
+
+  while (in < in_end)
+    {
+      unsigned long uc = 0;
+      long len2 = utf8_decode (in, in_end - in, &uc);
+      in += len2;
+
+      if (uc == '\240')        /* &nbsp; */
+        uc = ' ';
+      else if (uc >= 0x300 && uc <= 0x36F)
+        uc = 0;                /* Discard "Combining Diacritical Marks" */
+      else if (uc >= 0x1AB0 && uc <= 0x1AFF)
+        uc = 0;                /* Discard "Combining Diacritical Marks Extended" */
+      else if (uc >= 0x1DC0 && uc <= 0x1DFF)
+        uc = 0;                /* Discard "Combining Diacritical Marks Supplement" */
+      else if (uc >= 0x20D0 && uc <= 0x20FF)
+        uc = 0;                /* Discard "Combining Diacritical Marks for Symbols" */
+      else if (uc >= 0xFE20 && uc <= 0xFE2F)
+        uc = 0;                /* Discard "Combining Half Marks" */
+
+      else if (uc > 0xFF)
+        switch (uc) {
+
+        /* Map "Unicode General Punctuation Block" to Latin1 equivalents. */
+
+        case 0x2000:   /* EN QUAD */
+        case 0x2001:   /* EM QUAD */
+        case 0x2002:   /* EN SPACE */
+        case 0x2003:   /* EM SPACE */
+        case 0x2004:   /* THREE-PER-EM SPACE */
+        case 0x2005:   /* FOUR-PER-EM SPACE */
+        case 0x2006:   /* SIX-PER-EM SPACE */
+        case 0x2007:   /* FIGURE SPACE */
+        case 0x2008:   /* PUNCTUATION SPACE */
+        case 0x2009:   /* THIN SPACE */
+        case 0x200A:   /* HAIR SPACE */
+          uc = ' ';
+         break;
+
+        case 0x2010:   /* HYPHEN */
+        case 0x2011:   /* NON-BREAKING HYPHEN */
+        case 0x2012:   /* FIGURE DASH */
+        case 0x2013:   /* EN DASH */
+        case 0x2014:   /* EM DASH */
+        case 0x2015:   /* HORIZONTAL BAR */
+          uc = '-';
+         break;
+
+        case 0x2018:   /* LEFT SINGLE QUOTATION MARK */
+        case 0x2019:   /* SINGLE LOW-9 QUOTATION MARK */
+        case 0x201A:   /* SINGLE LOW-9 QUOTATION MARK */
+        case 0x201B:   /* SINGLE HIGH-REVERSED-9 QUOTATION MARK */
+          uc = '\'';
+         break;
+
+        case 0x201C:   /* LEFT DOUBLE QUOTATION MARK */
+        case 0x201D:   /* RIGHT DOUBLE QUOTATION MARK */
+        case 0x201E:   /* DOUBLE LOW-9 QUOTATION MARK */
+        case 0x201F:   /* DOUBLE HIGH-REVERSED-9 QUOTATION MARK */
+          uc = '"';
+         break;
+
+        case 0x2022: uc = '\267'; break; /* BULLET */
+        case 0x2023: uc = '\273'; break; /* TRIANGULAR BULLET */
+        case 0x2027: uc = '\267'; break; /* HYPHENATION POINT */
+        case 0x202F: uc = ' ';   break; /* NARROW NO-BREAK SPACE */
+        case 0x2038: uc = '^';   break; /* CARET */
+        case 0x2039: uc = '\253'; break; /* SINGLE LEFT ANGLE QUOTATION MARK */
+        case 0x203A: uc = '\273'; break; /* SINGLE RIGHT ANGLE QUOTATION MARK*/
+        case 0x2041: uc = '^';   break; /* CARET INSERTION POINT */
+        case 0x2042: uc = '*';   break; /* ASTERISM */
+        case 0x2043: uc = '=';   break; /* HYPHEN BULLET */
+        case 0x2044: uc = '/';   break; /* FRACTION SLASH */
+        case 0x204B: uc = '\266'; break; /* REVERSED PILCROW SIGN */
+        case 0x204C: uc = '\267'; break; /* BLACK LEFTWARDS BULLET */
+        case 0x204D: uc = '\267'; break; /* BLACK RIGHTWARDS BULLET */
+        case 0x204E: uc = '*';   break; /* LOW ASTERISK */
+        case 0x204F: uc = ';';   break; /* REVERSED SEMICOLON */
+        default:
+          break;
+        }
+
+      if (uc > 0xFF)
+        /* "Inverted question mark" looks enough like 0xFFFD,
+           the "Unicode Replacement Character". */
+        uc = (ascii_p ? '#' : '\277');
+
+      if (ascii_p)     /* Map Latin1 to the closest ASCII versions. */
+        {
+          const unsigned char latin1_to_ascii[96] =
+             " !C##Y|S_C#<=-R_##23'uP.,1o>###?"
+             "AAAAAAECEEEEIIIIDNOOOOOx0UUUUYpS"
+             "aaaaaaeceeeeiiiionooooo/ouuuuypy";
+          if (uc >= 0xA0)
+            uc = latin1_to_ascii[uc - 0xA0];
+        }
+
+      if (uc > 0)
+        *out++ = (unsigned char) uc;
+    }
+  *out = 0;
+
+  /* shrink */
+  ret = (unsigned char *) realloc (ret, (out - ret + 1) * sizeof(*ret));
+
+  return (char *) ret;
+}
+
+
+/*************************************************************************
+
+ cd ../hacks ; make test-utf8wc
+
+ *************************************************************************/
+
+#ifdef SELFTEST
+
+/* Convert a UTF8 string to Unicode and back again.
+ */
+static char *
+split_and_join (const char *string)
+{
+  const unsigned char *in = (const unsigned char *) string;
+  int len = strlen (string);
+  const unsigned char *end = in + len;
+  unsigned long *unicode = (unsigned long *)
+    malloc((len + 1) * sizeof(*unicode));
+  int i = 0;
+  char *ret, *out, *out_end;
+
+  while (in < end)
+    {
+      long len2 = utf8_decode (in, len, &unicode[i]);
+      i++;
+      in += len2;
+    }
+  unicode[i] = 0;
+
+  i = i*6 + 1;
+  out = ret = (char *) malloc(i);
+  out_end = out + i;
+  i = 0;
+  while (unicode[i])
+    {
+      int len2 = utf8_encode (unicode[i], out, out_end - out);
+      out += len2;
+      i++;
+    }
+  *out = 0;
+  free (unicode);
+
+  return ret;
+}
+
+
+static void
+LOG (FILE *out, const char *prefix, const char *s)
+{
+  fprintf (out, "%6s: \"", prefix);
+  while (*s)
+    {
+      unsigned char c = *s;
+      if (c == '"' || c == '\\') fprintf(out, "\\%c", c);
+      else if (c < 32 || c >= 127) fprintf(out, "\\%03o", c);
+      else fprintf (out, "%c", c);
+      s++;
+    }
+  fprintf (out, "\"\n");
+}
+
+
+int
+main (int argc, char **argv)
+{
+  /* Adapted from http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
+   */
+
+#  define URC "\357\277\275"   /* 0xFFFD, "Unicode Replacement Character" */
+
+  static const struct { const char *name, *in, *target, *target2; } tests[] = {
+    /* 1  Some correct UTF-8 text */
+
+    /* The Greek word 'kosme': */
+    { "1", "\316\272\341\275\271\317\203\316\274\316\265" },
+
+
+    /* 2  Boundary condition test cases */
+
+    /* 2.1  First possible sequence of a certain length */
+
+    { "2.1.1", /*  1 byte  (U-00000000): */  "\000" },
+    { "2.1.2", /*  2 bytes (U-00000080): */  "\302\200" },
+    { "2.1.3", /*  3 bytes (U-00000800): */  "\340\240\200" },
+    { "2.1.4", /*  4 bytes (U-00010000): */  "\360\220\200\200", 0, URC },
+    { "2.1.5", /*  5 bytes (U-00200000): */  "\370\210\200\200\200", URC },
+    { "2.1.6", /*  6 bytes (U-04000000): */  "\374\204\200\200\200\200", URC },
+
+    /* 2.2  Last possible sequence of a certain length */
+
+    { "2.2.1", /*  1 byte  (U-0000007F): */  "\177" },
+    { "2.2.2", /*  2 bytes (U-000007FF): */  "\337\277" },
+    { "2.2.3", /*  3 bytes (U-0000FFFF): */  "\357\277\277" },
+    { "2.2.4", /*  4 bytes (U-001FFFFF): */  "\367\277\277\277", URC },
+    { "2.2.5", /*  5 bytes (U-03FFFFFF): */  "\373\277\277\277\277", URC },
+    { "2.2.6", /*  6 bytes (U-7FFFFFFF): */  "\375\277\277\277\277\277", URC },
+
+    /* 2.3  Other boundary conditions */
+
+    { "2.3.1", /*  U-0000D7FF = ed 9f bf = */   "\355\237\277" },
+    { "2.3.2", /*  U-0000E000 = ee 80 80 = */   "\356\200\200" },
+    { "2.3.3", /*  U-0000FFFD = ef bf bd = */   URC },
+    { "2.3.4", /*  U-0010FFFF = f4 8f bf bf = */ "\364\217\277\277", 0, URC },
+    { "2.3.5", /*  U-00110000 = f4 90 80 80 = */ "\364\220\200\200", URC },
+
+
+    /* 3  Malformed sequences */
+
+    /* 3.1  Unexpected continuation bytes */
+
+    /* Each unexpected continuation byte should be separately signalled as a
+       malformed sequence of its own. */
+
+    { "3.1.1", /*  First continuation byte 0x80: */ "\200", URC },
+    { "3.1.2", /*  Last  continuation byte 0xbf: */ "\277", URC },
+    { "3.1.3", /*  2 continuation bytes: */ "\200\277",     URC URC },
+    { "3.1.4", /*  3 continuation bytes: */ "\200\277\200", URC URC URC },
+    { "3.1.5", /*  4 continuation bytes: */ "\200\277\200\277",
+      URC URC URC URC },
+    { "3.1.6", /*  5 continuation bytes: */ "\200\277\200\277\200",
+      URC URC URC URC URC },
+    { "3.1.7", /*  6 continuation bytes: */ "\200\277\200\277\200\277",
+      URC URC URC URC URC URC },
+    { "3.1.8", /*  7 continuation bytes: */ "\200\277\200\277\200\277\200",
+      URC URC URC URC URC URC URC },
+
+    { "3.1.9", /* Sequence of all 64 possible continuation bytes (0x80-0xbf):*/
+
+      "\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217"
+      "\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237"
+      "\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257"
+      "\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277",
+      URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC
+      URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC
+      URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC
+      URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC },
+
+    /* 3.2  Lonely start characters */
+
+    { "3.2.1", /*  All 32 first bytes of 2-byte sequences (0xc0-0xdf),
+                   each followed by a space character: */
+
+      "\300 \301 \302 \303 \304 \305 \306 \307 \310 \311 \312 \313 \314 "
+      "\315 \316 \317 \320 \321 \322 \323 \324 \325 \326 \327 \330 \331 "
+      "\332 \333 \334 \335 \336 \337 ",
+      URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC
+      URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC },
+
+    { "3.2.2", /*  All 16 first bytes of 3-byte sequences (0xe0-0xef),
+                   each followed by a space character: */
+      "\340 \341 \342 \343 \344 \345 \346 \347 "
+      "\350 \351 \352 \353 \354 \355 \356 \357 ",
+      URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC },
+
+    { "3.2.3", /*  All 8 first bytes of 4-byte sequences (0xf0-0xf7),
+                   each followed by a space character: */
+      URC URC URC URC URC URC URC URC },
+
+    { "3.2.4", /*  All 4 first bytes of 5-byte sequences (0xf8-0xfb),
+                   each followed by a space character: */
+      "\370 \371 \372 \373 ",
+      URC URC URC URC },
+
+    { "3.2.5", /*  All 2 first bytes of 6-byte sequences (0xfc-0xfd),
+                   each followed by a space character: */
+      "\374 \375 ", URC URC },
+
+    /* 3.3  Sequences with last continuation byte missing */
+
+    /* All bytes of an incomplete sequence should be signalled as a single
+       malformed sequence, i.e., you should see only a single replacement
+       character in each of the next 10 tests. (Characters as in section 2) */
+
+    { "3.3.1", /*  2-byte sequence with last byte missing (U+0000): */
+      "\300", URC },
+    { "3.3.2", /*  3-byte sequence with last byte missing (U+0000): */
+      "\340\200", URC },
+    { "3.3.3", /*  4-byte sequence with last byte missing (U+0000): */
+      "\360\200\200", URC },
+    { "3.3.4", /*  5-byte sequence with last byte missing (U+0000): */
+      "\370\200\200\200", URC },
+    { "3.3.5", /*  6-byte sequence with last byte missing (U+0000): */
+      "\374\200\200\200\200", URC },
+    { "3.3.6", /*  2-byte sequence with last byte missing (U-000007FF): */
+      "\337", URC },
+    { "3.3.7", /*  3-byte sequence with last byte missing (U-0000FFFF): */
+      "\357\277", URC },
+    { "3.3.8", /*  4-byte sequence with last byte missing (U-001FFFFF): */
+      "\367\277\277", URC },
+    { "3.3.9", /*  5-byte sequence with last byte missing (U-03FFFFFF): */
+      "\373\277\277\277", URC },
+    { "3.3.10", /* 6-byte sequence with last byte missing (U-7FFFFFFF): */
+      "\375\277\277\277\277", URC },
+
+    /* 3.4  Concatenation of incomplete sequences */
+
+    /* All the 10 sequences of 3.3 concatenated, you should see 10 malformed
+       sequences being signalled: */
+
+    { "3.4",   "\300\340\200\360\200\200\370\200\200\200\374\200\200\200\200"
+      "\337\357\277\367\277\277\373\277\277\277\375\277\277\277\277",
+      URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC },
+
+    /* 3.5  Impossible bytes */
+
+    /* The following two bytes cannot appear in a correct UTF-8 string */
+
+    { "3.5.1", /*  fe = */     "\376", URC },
+    { "3.5.2", /*  ff = */     "\377", URC },
+    { "3.5.3", /*  fe fe ff ff = */    "\376\376\377\377", URC URC URC URC },
+
+
+    /* 4  Overlong sequences */
+
+    /* 4.1  Examples of an overlong ASCII character */
+
+    { "4.1.1", /* U+002F = c0 af             = */ "\300\257", URC },
+    { "4.1.2", /* U+002F = e0 80 af          = */ "\340\200\257", URC },
+    { "4.1.3", /* U+002F = f0 80 80 af       = */ "\360\200\200\257", URC },
+    { "4.1.4", /* U+002F = f8 80 80 80 af    = */ "\370\200\200\200\257",
+      URC },
+    { "4.1.5", /* U+002F = fc 80 80 80 80 af = */ "\374\200\200\200\200\257",
+      URC },
+
+    /* 4.2  Maximum overlong sequences */
+
+    { "4.2.1", /*  U-0000007F = c1 bf             = */ "\301\277", URC },
+    { "4.2.2", /*  U-000007FF = e0 9f bf          = */ "\340\237\277", URC },
+    { "4.2.3", /*  U-0000FFFF = f0 8f bf bf       = */ "\360\217\277\277",
+      URC },
+    { "4.2.4", /*  U-001FFFFF = f8 87 bf bf bf    = */ "\370\207\277\277\277",
+      URC },
+    { "4.2.5", /*  U-03FFFFFF = fc 83 bf bf bf bf = */  URC },
+
+    /* 4.3  Overlong representation of the NUL character */
+
+    { "4.3.1", /*  U+0000 = c0 80             = */  "\300\200", URC },
+    { "4.3.2", /*  U+0000 = e0 80 80          = */  "\340\200\200", URC },
+    { "4.3.3", /*  U+0000 = f0 80 80 80       = */  "\360\200\200\200", URC },
+    { "4.3.4", /*  U+0000 = f8 80 80 80 80    = */  "\370\200\200\200\200",
+      URC },
+    { "4.3.5", /*  U+0000 = fc 80 80 80 80 80 = */  "\374\200\200\200\200\200",
+      URC },
+
+
+    /* 5  Illegal code positions */
+
+    /* 5.1 Single UTF-16 surrogates */
+
+    { "5.1.1", /*  U+D800 = ed a0 80 = */      "\355\240\200", URC },
+    { "5.1.2", /*  U+DB7F = ed ad bf = */      "\355\255\277", URC },
+    { "5.1.3", /*  U+DB80 = ed ae 80 = */      "\355\256\200", URC },
+    { "5.1.4", /*  U+DBFF = ed af bf = */      "\355\257\277", URC },
+    { "5.1.5", /*  U+DC00 = ed b0 80 = */      "\355\260\200", URC },
+    { "5.1.6", /*  U+DF80 = ed be 80 = */      "\355\276\200", URC },
+    { "5.1.7", /*  U+DFFF = ed bf bf = */      "\355\277\277", URC },
+
+    /* 5.2 Paired UTF-16 surrogates */
+
+    { "5.2.1", /*  U+D800 U+DC00 = ed a0 80 ed b0 80 = */ URC URC },
+    { "5.2.2", /*  U+D800 U+DFFF = ed a0 80 ed bf bf = */ URC URC },
+    { "5.2.3", /*  U+DB7F U+DC00 = ed ad bf ed b0 80 = */ URC URC },
+    { "5.2.4", /*  U+DB7F U+DFFF = ed ad bf ed bf bf = */ URC URC },
+    { "5.2.5", /*  U+DB80 U+DC00 = ed ae 80 ed b0 80 = */ URC URC },
+    { "5.2.6", /*  U+DB80 U+DFFF = ed ae 80 ed bf bf = */ URC URC },
+    { "5.2.7", /*  U+DBFF U+DC00 = ed af bf ed b0 80 = */ URC URC },
+    { "5.2.8", /*  U+DBFF U+DFFF = ed af bf ed bf bf = */ URC URC },
+
+    /* 5.3 Other illegal code positions */
+
+    { "5.3.1", /*  U+FFFE = ef bf be = */      "\357\277\276" },
+    { "5.3.2", /*  U+FFFF = ef bf bf = */      "\357\277\277" },
+
+
+    /* 6 Some other junk */
+
+    { "6.0", "" },
+    { "6.1", "\001\002\003\004\005 ABC" },
+    { "6.2", /* every non-ASCII Latin1 character */
+      "\302\241\302\242\302\243\302\244\302\245\302\246\302\247\302\250"
+      "\302\251\302\252\302\253\302\254\302\255\302\256\302\257\302\260"
+      "\302\261\302\262\302\263\302\264\302\265\302\266\302\267\302\270"
+      "\302\271\302\272\302\273\302\274\302\275\302\276\302\277\303\200"
+      "\303\201\303\202\303\203\303\204\303\205\303\206\303\207\303\210"
+      "\303\211\303\212\303\213\303\214\303\215\303\216\303\217\303\220"
+      "\303\221\303\222\303\223\303\224\303\225\303\226\303\227\303\230"
+      "\303\231\303\232\303\233\303\234\303\235\303\236\303\237\303\240"
+      "\303\241\303\242\303\243\303\244\303\245\303\246\303\247\303\250"
+      "\303\251\303\252\303\253\303\254\303\255\303\256\303\257\303\260"
+      "\303\261\303\262\303\263\303\264\303\265\303\266\303\267\303\270"
+      "\303\271\303\272\303\273\303\274\303\275\303\276\303\277" },
+
+    { "6.3", /* Christmas tree */
+      "\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020"
+      "\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\040"
+      "\041\042\043\044\045\046\047\050\051\052\053\054\055\056\057\060"
+      "\061\062\063\064\065\066\067\070\071\072\073\074\075\076\077\100"
+      "\101\102\103\104\105\106\107\110\111\112\113\114\115\116\117\120"
+      "\121\122\123\124\125\126\127\130\131\132\133\134\135\136\137\140"
+      "\141\142\143\144\145\146\147\150\151\152\153\154\155\156\157\160"
+      "\161\162\163\164\165\166\167\170\171\172\173\174\175\176\177\200"
+      "\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220"
+      "\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240"
+      "\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260"
+      "\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300"
+      "\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320"
+      "\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340"
+      "\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360"
+      "\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377",
+
+      "\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020"
+      "\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037"
+      " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+      "[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\177"
+      URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC
+      URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC
+      URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC
+      URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC
+      URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC URC
+      URC URC URC URC URC URC URC URC URC URC URC URC },
+  };
+
+  int i;
+  int ok = 1;
+  for (i = 0; i < sizeof(tests)/sizeof(*tests); i++)
+    {
+      const char *name = tests[i].name;
+      const char *in   = tests[i].in;
+      const char *target = (tests[i].target ? tests[i].target : in);
+      const char *target2 = (tests[i].target2 ? tests[i].target2 : target);
+      char *out = split_and_join (in);
+      XChar2b *out16 = utf8_to_XChar2b (in, 0);
+      char *out2 = XChar2b_to_utf8 (out16, 0);
+      if (strcmp (out, target))
+        {
+          LOG (stderr, name, target);
+          LOG (stderr, "FAIL", out);
+          fprintf (stderr, "\n");
+          ok = 0;
+        }
+      if (strcmp (out2, target2))
+        {
+          LOG (stderr, name, target2);
+          LOG (stderr, "FAIL2", out2);
+          fprintf (stderr, "\n");
+          ok = 0;
+        }
+      free (out);
+      free (out2);
+      free (out16);
+    }
+
+  /* Check conversion from UTF8 to Latin1 and ASCII. */
+  {
+    const char *utf8 = ("son \303\256le int\303\251rieure, \303\240 "
+                        "c\303\264t\303\251 de l'alc\303\264ve "
+                        "ovo\303\257de, o\303\271 les b\303\273ches "
+                        "se consument dans l'\303\242tre");
+    const char *latin1 = ("son \356le int\351rieure, \340 "
+                          "c\364t\351 de l'alc\364ve ovo\357de, "
+                          "o\371 les b\373ches se consument dans "
+                          "l'\342tre");
+    const char *ascii = ("son ile interieure, a cote de l'alcove "
+                         "ovoide, ou les buches se consument dans "
+                         "l'atre");
+    char *latin1b = utf8_to_latin1 (utf8, False);
+    char *ascii2  = utf8_to_latin1 (utf8, True);
+    if (strcmp (latin1, latin1b))
+      {
+        LOG (stderr, "LATIN1", utf8);
+        LOG (stderr, "FAIL3", latin1b);
+        fprintf (stderr, "\n");
+        ok = 0;
+      }
+    if (strcmp (ascii, ascii2))
+      {
+        LOG (stderr, "ASCII", utf8);
+        LOG (stderr, "FAIL4", ascii2);
+        fprintf (stderr, "\n");
+        ok = 0;
+      }
+    free (latin1b);
+    free (ascii2);
+  }
+
+  /* Check de-composition of emoji that should all be treated as a unit
+     for measurement and display purposes. */
+  {
+    static const char * const tests[] = { 
+
+      /* 0: "Man" */
+      " \360\237\221\250 ",
+
+      /* 1: "Blackula" = "Vampire, dark skin tone" = 1F9DB 1F3FF */
+      " \360\237\247\233\360\237\217\277 ",
+
+      /* 2: "Black male teacher" = "Man, dark skin tone, ZWJ, school" =
+            1F468 1F3FF 200D 1F3EB
+       */
+      " \360\237\221\250\360\237\217\277\342\200\215\360\237\217\253 ",
+
+      /* 3: "Female runner" = "Runner, ZWJ, female sign" = 1F3C3 200D 2640 */
+      " \360\237\217\203\342\200\215\342\231\200 ",
+
+      /* 4: "Woman astronaut" = "Woman, ZWJ, rocket ship" = 1F3C3 200D 1F680 */
+      " \360\237\217\203\342\200\215\360\237\232\200 ",
+
+      /* 5:
+         Group of people displayed as a single glyph:
+           Woman, dark skin tone, ZWJ,   1F469 1F3FF 200D
+           Man, light skin tone, ZWJ,    1F468 1F3FB 200D
+           Boy, medium skin tone, ZWJ,   1F466 1F3FD 200D
+           Girl, dark skin tone.         1F467 1F3FF
+       */
+      " \360\237\221\251\360\237\217\277\342\200\215"
+       "\360\237\221\250\360\237\217\273\342\200\215"
+       "\360\237\221\246\360\237\217\275\342\200\215"
+       "\360\237\221\247\360\237\217\277 ",
+    };
+    int i;
+    for (i = 0; i < sizeof(tests)/sizeof(*tests); i++)
+      {
+        int L = 0;
+        char **out = utf8_split (tests[i], &L);
+        char name[100];
+        int j;
+        sprintf (name, "SPLIT %d: %d glyphs", i, L-2);
+        if (L != 3)
+          {
+            LOG (stderr, name, tests[i]);
+            ok = 0;
+          }
+        for (j = 0; j < L; j++)
+          free (out[j]);
+        free (out);
+      }
+  }
+
+  if (ok) fprintf (stderr, "OK\n");
+  return (ok == 0);
+}
+
+#endif /* SELFTEST */
diff --git a/screenhack/utf8wc.h b/screenhack/utf8wc.h
new file mode 100644 (file)
index 0000000..d91567c
--- /dev/null
@@ -0,0 +1,47 @@
+/* xscreensaver, Copyright (c) 2014-2015 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifndef __XSCREENSAVER_UTF8WC_H__
+#define __XSCREENSAVER_UTF8WC_H__
+
+/* Utilities for converting between UTF8 and XChar2b. */
+
+/* Converts a null-terminated UTF8 string to a null-terminated XChar2b array.
+   This only handles characters that can be represented in 16 bits, the
+   Basic Multilingual Plane. (No hieroglyphics, Elvish, Klingon or Emoji.)
+ */
+extern XChar2b * utf8_to_XChar2b (const char *, int *length_ret);
+
+/* Converts a null-terminated XChar2b array to a null-terminated UTF8 string.
+ */
+extern char *    XChar2b_to_utf8 (const XChar2b *, int *length_ret);
+
+/* Split a UTF8 string into an array of strings, one per character.
+   The sub-strings will be null terminated and may be multiple bytes.
+ */
+extern char ** utf8_split (const char *string, int *length_ret);
+
+/* Converts a UTF8 string to the closest Latin1 or ASCII equivalent.
+ */
+extern char *utf8_to_latin1 (const char *string, Bool ascii_p);
+
+/* Converts a Unicode character to a multi-byte UTF8 sequence.
+   Returns the number of bytes written.
+ */
+extern int utf8_encode (unsigned long uc, char *out, long length);
+
+/* Parse the first UTF8 character at the front of the string.
+   Return the Unicode character, and the number of bytes read.
+ */
+extern long utf8_decode (const unsigned char *in, long length,
+                         unsigned long *unicode_ret);
+
+#endif /* __XSCREENSAVER_UTF8WC_H__ */
diff --git a/screenhack/utils.h b/screenhack/utils.h
new file mode 100644 (file)
index 0000000..09b4da8
--- /dev/null
@@ -0,0 +1,27 @@
+/* xscreensaver, Copyright (c) 1997-2014 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#ifdef HAVE_JWXYZ
+# include "jwxyz.h"
+#else /* real X11 */
+# include <X11/Xlib.h>
+# include <X11/Xutil.h>
+# include <X11/Xos.h>
+#endif /* !HAVE_JWXYZ */
diff --git a/screenhack/version.h b/screenhack/version.h
new file mode 100644 (file)
index 0000000..054dd99
--- /dev/null
@@ -0,0 +1,2 @@
+static const char screensaver_id[] =
+       "@(#)xscreensaver 5.45 (08-Dec-2020), by Jamie Zawinski (jwz@jwz.org)";
diff --git a/screenhack/visual.c b/screenhack/visual.c
new file mode 100644 (file)
index 0000000..c6764ba
--- /dev/null
@@ -0,0 +1,555 @@
+/* xscreensaver, Copyright (c) 1993-2017 by Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/* This file contains some code for intelligently picking the best visual
+   (where "best" is biased in the direction of either: high color counts;
+   or: having writable color cells...)
+ */
+
+#include "utils.h"
+#include "resources.h"  /* for get_string_resource() */
+#include "visual.h"
+
+#include <string.h>
+#ifndef HAVE_ANDROID
+#include <X11/Xutil.h>
+#else
+#include "../android/android-visual.h"
+#endif
+
+extern char *progname;
+
+#ifndef isupper
+# define isupper(c)  ((c) >= 'A' && (c) <= 'Z')
+#endif
+#ifndef _tolower
+# define _tolower(c)  ((c) - 'A' + 'a')
+#endif
+
+
+static Visual *pick_best_visual (Screen *, Bool, Bool);
+static Visual *pick_mono_visual (Screen *);
+static Visual *pick_best_visual_of_class (Screen *, int);
+static Visual *pick_best_gl_visual (Screen *);
+
+
+#define DEFAULT_VISUAL -1
+#define BEST_VISUAL    -2
+#define MONO_VISUAL    -3
+#define GRAY_VISUAL    -4
+#define COLOR_VISUAL   -5
+#define GL_VISUAL      -6
+#define SPECIFIC_VISUAL        -7
+
+Visual *
+get_visual (Screen *screen, const char *string, Bool prefer_writable_cells,
+           Bool verbose_p)
+{
+  char *v = (string ? strdup(string) : 0);
+  char c, *tmp;
+  int vclass;
+  unsigned long id;
+  Visual *result = 0;
+
+  if (v)
+    for (tmp = v; *tmp; tmp++)
+      if (isupper (*tmp)) *tmp = _tolower (*tmp);
+
+  if (!v || !*v)                                 vclass = BEST_VISUAL;
+  else if (!strcmp (v, "default"))               vclass = DEFAULT_VISUAL;
+  else if (!strcmp (v, "best"))                  vclass = BEST_VISUAL;
+  else if (!strcmp (v, "mono"))                  vclass = MONO_VISUAL;
+  else if (!strcmp (v, "monochrome"))            vclass = MONO_VISUAL;
+  else if (!strcmp (v, "gray"))                  vclass = GRAY_VISUAL;
+  else if (!strcmp (v, "grey"))                  vclass = GRAY_VISUAL;
+  else if (!strcmp (v, "color"))                 vclass = COLOR_VISUAL;
+  else if (!strcmp (v, "gl"))                    vclass = GL_VISUAL;
+  else if (!strcmp (v, "staticgray"))            vclass = StaticGray;
+  else if (!strcmp (v, "staticcolor"))           vclass = StaticColor;
+  else if (!strcmp (v, "truecolor"))             vclass = TrueColor;
+  else if (!strcmp (v, "grayscale"))             vclass = GrayScale;
+  else if (!strcmp (v, "greyscale"))             vclass = GrayScale;
+  else if (!strcmp (v, "pseudocolor"))           vclass = PseudoColor;
+  else if (!strcmp (v, "directcolor"))           vclass = DirectColor;
+  else if (1 == sscanf (v, " %lu %c", &id, &c))          vclass = SPECIFIC_VISUAL;
+  else if (1 == sscanf (v, " 0x%lx %c", &id, &c)) vclass = SPECIFIC_VISUAL;
+  else
+    {
+      fprintf (stderr, "%s: unrecognized visual \"%s\".\n", progname, v);
+      vclass = DEFAULT_VISUAL;
+    }
+
+  if (vclass == DEFAULT_VISUAL)
+    result = DefaultVisualOfScreen (screen);
+  else if (vclass == BEST_VISUAL)
+    result = pick_best_visual (screen, prefer_writable_cells, False);
+  else if (vclass == MONO_VISUAL)
+    {
+      result = pick_mono_visual (screen);
+      if (!result && verbose_p)
+       fprintf (stderr, "%s: no monochrome visuals.\n", progname);
+    }
+  else if (vclass == GRAY_VISUAL)
+    {
+      if (prefer_writable_cells)
+       result = pick_best_visual_of_class (screen, GrayScale);
+      if (!result)
+       result = pick_best_visual_of_class (screen, StaticGray);
+      if (!result)
+       result = pick_best_visual_of_class (screen, GrayScale);
+      if (!result && verbose_p)
+       fprintf (stderr, "%s: no GrayScale or StaticGray visuals.\n",
+                progname);
+    }
+  else if (vclass == COLOR_VISUAL)
+    {
+      int class;
+      /* First see if the default visual will do. */
+      result = DefaultVisualOfScreen (screen);
+      class = visual_class(screen, result);
+      if (class != TrueColor &&
+         class != PseudoColor &&
+         class != DirectColor &&
+         class != StaticColor)
+       result = 0;
+      if (result && visual_depth(screen, result) <= 1)
+       result = 0;
+
+      /* Else, find the best non-default color visual */
+      if (!result)
+       result = pick_best_visual (screen, prefer_writable_cells, True);
+
+      if (!result && verbose_p)
+       fprintf (stderr, "%s: no color visuals.\n", progname);
+    }
+  else if (vclass == GL_VISUAL)
+    {
+      Visual *visual = pick_best_gl_visual (screen);
+      if (visual)
+       result = visual;
+      else if (verbose_p)
+       fprintf (stderr, "%s: no visual suitable for GL.\n", progname);
+    }
+  else if (vclass == SPECIFIC_VISUAL)
+    {
+      result = id_to_visual (screen, id);
+      if (!result && verbose_p)
+       fprintf (stderr, "%s: no visual with id 0x%x.\n", progname,
+                (unsigned int) id);
+    }
+  else
+    {
+      Visual *visual = pick_best_visual_of_class (screen, vclass);
+      if (visual)
+       result = visual;
+      else if (verbose_p)
+       fprintf (stderr, "%s: no visual of class %s.\n", progname, v);
+    }
+
+  if (v) free (v);
+  return result;
+}
+
+Visual *
+get_visual_resource (Screen *screen, char *name, char *class,
+                    Bool prefer_writable_cells)
+{
+  char *string = get_string_resource (DisplayOfScreen (screen), name, class);
+  Visual *v = get_visual (screen, string, prefer_writable_cells, True);
+  if (string)
+    free(string);
+  if (v)
+    return v;
+  else
+    return DefaultVisualOfScreen (screen);
+}
+
+
+static Visual *
+pick_best_visual (Screen *screen, Bool prefer_writable_cells, Bool color_only)
+{
+  Visual *visual;
+
+  if (!prefer_writable_cells)
+    {
+      /* If we don't prefer writable cells, then the "best" visual is the one
+        on which we can allocate the largest range and number of colors.
+
+        Therefore, a TrueColor visual which is at least 16 bits deep is best.
+        (The assumption here being that a TrueColor of less than 16 bits is
+        really just a PseudoColor visual with a pre-allocated color cube.)
+
+        The next best thing is a PseudoColor visual of any type.  After that
+        come the non-colormappable visuals, and non-color visuals.
+       */
+      if ((visual = pick_best_visual_of_class (screen, TrueColor)) &&
+         visual_depth (screen, visual) >= 16)
+       return visual;
+    }
+
+#define TRY_CLASS(CLASS) \
+  if ((visual = pick_best_visual_of_class (screen, CLASS)) && \
+      (!color_only || visual_depth(screen, visual) > 1)) \
+    return visual
+  TRY_CLASS(PseudoColor);
+  TRY_CLASS(TrueColor);
+  TRY_CLASS(DirectColor);
+  TRY_CLASS(StaticColor);
+  if (!color_only)
+    {
+      TRY_CLASS(GrayScale);
+      TRY_CLASS(StaticGray);
+    }
+#undef TRY_CLASS
+
+  visual = DefaultVisualOfScreen (screen);
+  if (!color_only || visual_depth(screen, visual) > 1)
+    return visual;
+  else
+    return 0;
+}
+
+static Visual *
+pick_mono_visual (Screen *screen)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  XVisualInfo vi_in, *vi_out;
+  int out_count;
+
+  vi_in.depth = 1;
+  vi_in.screen = screen_number (screen);
+  vi_out = XGetVisualInfo (dpy, (VisualDepthMask | VisualScreenMask),
+                          &vi_in, &out_count);
+  if (vi_out)
+    {
+      Visual *v = (out_count > 0 ? vi_out [0].visual : 0);
+      if (v && vi_out[0].depth != 1)
+       v = 0;
+      XFree ((char *) vi_out);
+      return v;
+    }
+  else
+    return 0;
+}
+
+
+static Visual *
+pick_best_visual_of_class (Screen *screen, int visual_class)
+{
+  /* The best visual of a class is the one which on which we can allocate
+     the largest range and number of colors, which means the one with the
+     greatest depth and number of cells.
+
+     (But actually, for XDaliClock, all visuals of the same class are
+     probably equivalent - either we have writable cells or we don't.)
+   */
+  Display *dpy = DisplayOfScreen (screen);
+  XVisualInfo vi_in, *vi_out;
+  int out_count;
+
+  vi_in.class = visual_class;
+  vi_in.screen = screen_number (screen);
+  vi_out = XGetVisualInfo (dpy, (VisualClassMask | VisualScreenMask),
+                          &vi_in, &out_count);
+  if (vi_out)
+    {
+      /* choose the 'best' one, if multiple */
+      int i, best;
+      Visual *visual;
+/*      for (i = 0, best = 0; i < out_count; i++) */
+      for (i = out_count-1, best = i; i >= 0; i--) /* go backwards */
+       /* It's better if it's deeper, or if it's the same depth with
+          more cells (does that ever happen?  Well, it could...) */
+       if ((vi_out [i].depth > vi_out [best].depth) ||
+           ((vi_out [i].depth == vi_out [best].depth) &&
+            (vi_out [i].colormap_size > vi_out [best].colormap_size)))
+         best = i;
+      visual = (best < out_count ? vi_out [best].visual : 0);
+      XFree ((char *) vi_out);
+      return visual;
+    }
+  else
+    return 0;
+}
+
+static Visual *
+pick_best_gl_visual (Screen *screen)
+{
+  /* The best visual for GL is a TrueColor visual that is half as deep as
+     the screen.  If such a thing doesn't exist, then TrueColor is best.
+     Failing that, the deepest available color visual is best.
+
+     Compare this function to get_gl_visual() in visual-gl.c.
+     This function tries to find the best GL visual using Xlib calls,
+     whereas that function does the same thing using GLX calls.
+   */
+  Display *dpy = DisplayOfScreen (screen);
+  XVisualInfo vi_in, *vi_out;
+  int out_count;
+  Visual *result = 0;
+
+  int ndepths = 0;
+  int *depths = XListDepths (dpy, screen_number (screen), &ndepths);
+  int screen_depth = (depths && ndepths) ? depths[ndepths - 1] : 0;
+  XFree (depths);
+
+  vi_in.class = TrueColor;
+  vi_in.screen = screen_number (screen);
+  vi_in.depth = screen_depth / 2;
+  vi_out = XGetVisualInfo (dpy, (VisualClassMask | VisualScreenMask |
+                                 VisualDepthMask),
+                          &vi_in, &out_count);
+  if (out_count > 0)
+    result = vi_out[0].visual;
+
+  if (vi_out)
+    XFree ((char *) vi_out);
+
+  if (!result && screen_depth > 24)
+    {
+      /* If it's a 32-deep screen and we didn't find a depth-16 visual,
+         see if there's a depth-12 visual. */
+      vi_in.class = TrueColor;
+      vi_in.screen = screen_number (screen);
+      vi_in.depth = 12;
+      vi_out = XGetVisualInfo (dpy, (VisualClassMask | VisualScreenMask |
+                                     VisualDepthMask),
+                               &vi_in, &out_count);
+      if (out_count > 0)
+        result = vi_out[0].visual;
+    }
+
+  if (!result)
+    /* No half-depth TrueColor?  Ok, try for any TrueColor (the deepest.) */
+    result = pick_best_visual_of_class (screen, TrueColor);
+
+  if (!result)
+    /* No TrueColor?  Ok, try for anything. */
+    result = pick_best_visual (screen, False, False);
+
+  return result;
+}
+
+
+static XVisualInfo *
+visual_info_id (Screen *screen, int id)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  XVisualInfo vi_in;
+  int out_count;
+  vi_in.screen = screen_number (screen);
+  vi_in.visualid = id;
+  return XGetVisualInfo (dpy, VisualScreenMask | VisualIDMask,
+                         &vi_in, &out_count);
+}
+
+static XVisualInfo *
+visual_info (Screen *screen, Visual *visual)
+{
+  XVisualInfo *vi_out = visual_info_id (screen, XVisualIDFromVisual (visual));
+  if (! vi_out) abort ();
+  return vi_out;
+}
+
+Visual *
+id_to_visual (Screen *screen, int id)
+{
+  XVisualInfo *vi_out = visual_info_id (screen, id);
+  if (vi_out)
+    {
+      Visual *v = vi_out[0].visual;
+      XFree ((char *) vi_out);
+      return v;
+    }
+  return 0;
+}
+
+int
+visual_depth (Screen *screen, Visual *visual)
+{
+  XVisualInfo *vi_out = visual_info (screen, visual);
+  int d = vi_out [0].depth;
+  XFree ((char *) vi_out);
+  return d;
+}
+
+
+/* You very probably don't want to be using this.
+   Pixmap depth doesn't refer to the depths of pixmaps, but rather, to
+   the depth of protocol-level on-the-wire pixmap data, that is, XImages.
+   To get this info, you should be looking at XImage->bits_per_pixel
+   instead.  (And allocating the data for your XImage structures by
+   multiplying ximage->bytes_per_line by ximage->height.)
+
+   Still, it can be useful to know bits_per_pixel before the XImage exists.
+
+   XCreateImage calls _XGetBitsPerPixel to figure this out, but that function
+   is private to Xlib.
+
+   For some reason, _XGetBitsPerPixel tries a hard-coded list of depths if
+   it doesn't find a matching pixmap format, but I (Dave Odell) couldn't
+   find any justification for this in the X11 spec. And the XFree86 CVS
+   repository doesn't quite go back far enough to shed any light on what
+   the deal is with that.
+   http://cvsweb.xfree86.org/cvsweb/xc/lib/X11/ImUtil.c
+
+   The hard-coded list apparently was added between X11R5 and X11R6.
+   See <ftp://ftp.x.org/pub/>.
+ */
+int
+visual_pixmap_depth (Screen *screen, Visual *visual)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  int vdepth = visual_depth (screen, visual);
+  int pdepth = vdepth;
+  int i, pfvc = 0;
+  XPixmapFormatValues *pfv = XListPixmapFormats (dpy, &pfvc);
+
+  /* Return the first matching depth in the pixmap formats.  If there are no
+     matching pixmap formats (which shouldn't be able to happen at all) then
+     return the visual depth instead. */
+  for (i = 0; i < pfvc; i++)
+    if (pfv[i].depth == vdepth)
+      {
+       pdepth = pfv[i].bits_per_pixel;
+       break;
+      }
+  if (pfv)
+    XFree (pfv);
+  return pdepth;
+}
+
+
+int
+visual_class (Screen *screen, Visual *visual)
+{
+  XVisualInfo *vi_out = visual_info (screen, visual);
+  int c = vi_out [0].class;
+  XFree ((char *) vi_out);
+  return c;
+}
+
+Bool
+has_writable_cells (Screen *screen, Visual *visual)
+{
+  switch (visual_class (screen, visual))
+    {
+    case GrayScale:    /* Mappable grays. */
+    case PseudoColor:  /* Mappable colors. */
+    case DirectColor:  /* Like TrueColor, but with three colormaps:
+                           one each for red, green, and blue. */
+      return True;
+    case StaticGray:   /* Fixed grays. */
+    case TrueColor:    /* Fixed colors. */
+    case StaticColor:  /* Like PseudoColor with an unmodifiable colormap. */
+      return False;
+    default:
+      abort();
+      return False;
+    }
+}
+
+void
+describe_visual (FILE *f, Screen *screen, Visual *visual, Bool private_cmap_p)
+{
+  char n[10];
+  XVisualInfo *vi_out = visual_info (screen, visual);
+  if (private_cmap_p)
+    sprintf(n, "%3d", vi_out->colormap_size);
+  else
+    strcpy(n, "default");
+
+  fprintf (f, "0x%02x (%s depth: %2d, cmap: %s)\n",
+          (unsigned int) vi_out->visualid,
+          (vi_out->class == StaticGray  ? "StaticGray, " :
+           vi_out->class == StaticColor ? "StaticColor," :
+           vi_out->class == TrueColor   ? "TrueColor,  " :
+           vi_out->class == GrayScale   ? "GrayScale,  " :
+           vi_out->class == PseudoColor ? "PseudoColor," :
+           vi_out->class == DirectColor ? "DirectColor," :
+                                          "UNKNOWN:    "),
+          vi_out->depth, n);
+  XFree ((char *) vi_out);
+}
+
+int
+screen_number (Screen *screen)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  int i;
+  for (i = 0; i < ScreenCount (dpy); i++)
+    if (ScreenOfDisplay (dpy, i) == screen)
+      return i;
+  abort ();
+  return 0;
+}
+
+int
+visual_cells (Screen *screen, Visual *visual)
+{
+  XVisualInfo *vi_out = visual_info (screen, visual);
+  int c = vi_out [0].colormap_size;
+  XFree ((char *) vi_out);
+  return c;
+}
+
+Visual *
+find_similar_visual(Screen *screen, Visual *old_visual)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  XVisualInfo vi_in, *vi_out;
+  Visual *result = 0;
+  int out_count;
+
+  vi_in.screen = screen_number (screen);
+  vi_in.class  = visual_class (screen, old_visual);
+  vi_in.depth  = visual_depth (screen, old_visual);
+
+  /* Look for a visual of the same class and depth.
+   */
+  vi_out = XGetVisualInfo (dpy, (VisualScreenMask | VisualClassMask |
+                                VisualDepthMask),
+                          &vi_in, &out_count);
+  if (vi_out && out_count > 0)
+    result = vi_out[0].visual;
+  if (vi_out) XFree (vi_out);
+  vi_out = 0;
+
+  /* Failing that, look for a visual of the same class.
+   */
+  if (!result)
+    {
+      vi_out = XGetVisualInfo (dpy, (VisualScreenMask | VisualClassMask),
+                              &vi_in, &out_count);
+      if (vi_out && out_count > 0)
+       result = vi_out[0].visual;
+      if (vi_out) XFree (vi_out);
+      vi_out = 0;
+    }
+
+  /* Failing that, return the default visual. */
+  if (!result)
+    result = DefaultVisualOfScreen (screen);
+
+  return result;
+}
+
+
+void
+visual_rgb_masks (Screen *screen, Visual *visual, unsigned long *red_mask,
+                  unsigned long *green_mask, unsigned long *blue_mask)
+{
+  XVisualInfo *vi_out = visual_info (screen, visual);
+  *red_mask = vi_out->red_mask;
+  *green_mask = vi_out->green_mask;
+  *blue_mask = vi_out->blue_mask;
+  XFree ((char *) vi_out);
+}
diff --git a/screenhack/visual.h b/screenhack/visual.h
new file mode 100644 (file)
index 0000000..18ff7a6
--- /dev/null
@@ -0,0 +1,36 @@
+/* xscreensaver, Copyright (c) 1993-2014 by Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifndef __VISUAL_H__
+#define __VISUAL_H__
+
+extern Visual *get_visual (Screen *, const char *name, Bool, Bool);
+extern Visual *get_visual_resource (Screen *, char *, char *, Bool);
+extern int visual_depth (Screen *, Visual *);
+extern int visual_pixmap_depth (Screen *, Visual *);
+extern int visual_class (Screen *, Visual *);
+extern int visual_cells (Screen *, Visual *);
+extern int screen_number (Screen *);
+extern Visual *find_similar_visual (Screen *, Visual *old);
+extern void describe_visual (FILE *f, Screen *, Visual *, Bool private_cmap_p);
+extern Visual *get_overlay_visual (Screen *, unsigned long *pixel_return);
+extern Bool has_writable_cells (Screen *, Visual *);
+extern Visual *id_to_visual (Screen *, int);
+extern void visual_rgb_masks (Screen *screen, Visual *visual,
+                              unsigned long *red_mask,
+                              unsigned long *green_mask,
+                              unsigned long *blue_mask);
+
+extern Visual *get_gl_visual (Screen *);
+extern void describe_gl_visual (FILE *, Screen *, Visual *, Bool priv_cmap_p);
+extern Bool validate_gl_visual (FILE *, Screen *, const char *, Visual *);
+
+#endif /* __VISUAL_H__ */
diff --git a/screenhack/vroot.h b/screenhack/vroot.h
new file mode 100644 (file)
index 0000000..65097b8
--- /dev/null
@@ -0,0 +1,156 @@
+/* -*- Mode: C; tab-width: 2 -*-                                             */
+/*****************************************************************************/
+/**                   Copyright 1991 by Andreas Stolcke                     **/
+/**               Copyright 1990 by Solbourne Computer Inc.                 **/
+/**                          Longmont, Colorado                             **/
+/**                                                                         **/
+/**                           All Rights Reserved                           **/
+/**                                                                         **/
+/**    Permission to use, copy, modify, and distribute this software and    **/
+/**    its documentation  for  any  purpose  and  without  fee is hereby    **/
+/**    granted, provided that the above copyright notice appear  in  all    **/
+/**    copies and that both  that  copyright  notice  and  this  permis-    **/
+/**    sion  notice appear in supporting  documentation,  and  that  the    **/
+/**    name of Solbourne not be used in advertising                         **/
+/**    in publicity pertaining to distribution of the  software  without    **/
+/**    specific, written prior permission.                                  **/
+/**                                                                         **/
+/**    ANDREAS STOLCKE AND SOLBOURNE COMPUTER INC. DISCLAIMS ALL WARRANTIES **/
+/**    WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF    **/
+/**    MERCHANTABILITY  AND  FITNESS,  IN  NO  EVENT SHALL ANDREAS STOLCKE  **/
+/**    OR SOLBOURNE BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL    **/
+/**    DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA   **/
+/**    OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER    **/
+/**    TORTIOUS ACTION, ARISING OUT OF OR IN  CONNECTION  WITH  THE  USE    **/
+/**    OR PERFORMANCE OF THIS SOFTWARE.                                     **/
+/*****************************************************************************/
+/*
+ * vroot.h -- Virtual Root Window handling header file
+ *
+ * This header file redefines the X11 macros RootWindow and DefaultRootWindow,
+ * making them look for a virtual root window as provided by certain `virtual'
+ * window managers like swm and tvtwm. If none is found, the ordinary root
+ * window is returned, thus retaining backward compatibility with standard
+ * window managers.
+ * The function implementing the virtual root lookup remembers the result of
+ * its last invocation to avoid overhead in the case of repeated calls
+ * on the same display and screen arguments. 
+ * The lookup code itself is taken from Tom LaStrange's ssetroot program.
+ *
+ * Most simple root window changing X programs can be converted to using
+ * virtual roots by just including
+ *
+ * #include <X11/vroot.h>
+ *
+ * after all the X11 header files.  It has been tested on such popular
+ * X clients as xphoon, xfroot, xloadimage, and xaqua.
+ * It also works with the core clients xprop, xwininfo, xwd, and editres
+ * (and is necessary to get those clients working under tvtwm).
+ * It does NOT work with xsetroot; get the xsetroot replacement included in
+ * the tvtwm distribution instead.
+ *
+ * Andreas Stolcke <stolcke@ICSI.Berkeley.EDU>, 9/7/90
+ * - replaced all NULL's with properly cast 0's, 5/6/91
+ * - free children list (suggested by Mark Martin <mmm@cetia.fr>), 5/16/91
+ * - include X11/Xlib.h and support RootWindowOfScreen, too 9/17/91
+ *
+ * Jamie Zawinski <jwz@jwz.org>, 28-Apr-1997
+ * - use ANSI C
+ *
+ * Jamie Zawinski <jwz@jwz.org>, 3-Sep-2003
+ * - if the environment variable "XSCREENSAVER_WINDOW" is set, use that
+ *   as the root window instead of searching for __SWM_VROOT.
+ *
+ * Jamie Zawinski <jwz@jwz.org>, 14-Aug-2004
+ * - changes to get gcc to stop whining about "type punning".
+ *
+ * Jamie Zawinski <jwz@jwz.org>, 16-Dec-2004
+ * - fixed that last fix.
+ */
+
+#ifndef _VROOT_H_
+#define _VROOT_H_
+#define _XSCREENSAVER_VROOT_H_
+
+#if !defined(lint) && !defined(SABER)
+static const char vroot_rcsid[] =
+ "#Id: vroot.h,v 1.8 2004/12/16 05:33:54 jwz Exp #" "\n"
+ "#Id: vroot.h,v 1.4 1991/09/30 19:23:16 stolcke Exp stolcke #";
+#endif
+
+#include <X11/X.h>
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+
+static Window
+#ifdef __STDC__ /* ANSIfication added by jwz, to avoid superfluous warnings. */
+VirtualRootWindowOfScreen(Screen *screen)
+#else /* !__STDC__ */
+VirtualRootWindowOfScreen(screen) Screen *screen;
+#endif /* !__STDC__ */
+{
+       static Screen *save_screen = (Screen *)0;
+       static Window root = (Window)0;
+
+       if (screen != save_screen) {
+               Display *dpy = DisplayOfScreen(screen);
+               Atom __SWM_VROOT = None;
+               int i;
+               Window rootReturn, parentReturn, *children;
+               unsigned int numChildren;
+
+    /* first check for a hex or decimal window ID in the environment */
+    const char *xss_id = getenv("XSCREENSAVER_WINDOW");
+    if (xss_id && *xss_id) {
+      unsigned long id = 0;
+      char c;
+      if (1 == sscanf (xss_id, " 0x%lx %c", &id, &c) ||
+          1 == sscanf (xss_id, " %lu %c",   &id, &c)) {
+        root = (Window) id;
+        save_screen = screen;
+        return root;
+      }
+    }
+
+               root = RootWindowOfScreen(screen);
+
+               /* go look for a virtual root */
+               __SWM_VROOT = XInternAtom(dpy, "__SWM_VROOT", False);
+               if (XQueryTree(dpy, root, &rootReturn, &parentReturn,
+                                &children, &numChildren)) {
+                       for (i = 0; i < numChildren; i++) {
+                               Atom actual_type;
+                               int actual_format;
+                               unsigned long nitems, bytesafter;
+                               unsigned char *newRoot = 0;
+
+                               if (XGetWindowProperty(dpy, children[i],
+                                       __SWM_VROOT, 0, 1, False, XA_WINDOW,
+                                       &actual_type, &actual_format,
+                                       &nitems, &bytesafter,
+                                       &newRoot) == Success
+                                   && newRoot) {
+                                   root = *((Window *) newRoot);
+                                   break;
+                               }
+                       }
+                       if (children)
+                               XFree((char *)children);
+               }
+
+               save_screen = screen;
+       }
+
+       return root;
+}
+
+#undef RootWindowOfScreen
+#define RootWindowOfScreen(s) VirtualRootWindowOfScreen(s)
+
+#undef RootWindow
+#define RootWindow(dpy,screen) VirtualRootWindowOfScreen(ScreenOfDisplay(dpy,screen))
+
+#undef DefaultRootWindow
+#define DefaultRootWindow(dpy) VirtualRootWindowOfScreen(DefaultScreenOfDisplay(dpy))
+
+#endif /* _VROOT_H_ */
diff --git a/screenhack/xdbe.c b/screenhack/xdbe.c
new file mode 100644 (file)
index 0000000..d62183e
--- /dev/null
@@ -0,0 +1,75 @@
+/* xscreensaver, Copyright (c) 1998, 1999, 2006
+ *  by Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/* The XDBE (Double Buffering) extension is pretty tricky to use, since you
+   can get X errors at inconvenient times during initialization.  This file
+   contains a utility routine to make it easier to deal with.
+ */
+
+#include "utils.h"
+#include "xdbe.h"
+#include "resources.h"         /* for get_string_resource() */
+
+/* #define DEBUG */
+
+#ifdef DEBUG
+# include <X11/Xmu/Error.h>
+#endif
+
+extern char *progname;
+
+#ifdef HAVE_DOUBLE_BUFFER_EXTENSION    /* whole file */
+
+static Bool xdbe_got_x_error = False;
+static int
+xdbe_ehandler (Display *dpy, XErrorEvent *error)
+{
+  xdbe_got_x_error = True;
+
+#ifdef DEBUG
+  fprintf (stderr, "\n%s: ignoring X error from DOUBLE-BUFFER:\n", progname);
+  XmuPrintDefaultErrorMessage (dpy, error, stderr);
+  fprintf (stderr, "\n");
+#endif
+
+  return 0;
+}
+
+
+XdbeBackBuffer 
+xdbe_get_backbuffer (Display *dpy, Window window,
+                     XdbeSwapAction action)
+{
+  XdbeBackBuffer b;
+  XErrorHandler old_handler;
+  int maj, min;
+
+  if (!get_boolean_resource(dpy, "useDBE", "Boolean"))
+    return 0;
+
+  if (!XdbeQueryExtension (dpy, &maj, &min))
+    return 0;
+
+  XSync (dpy, False);
+  xdbe_got_x_error = False;
+  old_handler = XSetErrorHandler (xdbe_ehandler);
+  b = XdbeAllocateBackBufferName(dpy, window, XdbeUndefined);
+  XSync (dpy, False);
+  XSetErrorHandler (old_handler);
+
+  if (xdbe_got_x_error)
+    return 0;
+
+  return b;
+}
+
+#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
diff --git a/screenhack/xdbe.h b/screenhack/xdbe.h
new file mode 100644 (file)
index 0000000..26f2de8
--- /dev/null
@@ -0,0 +1,27 @@
+/* xscreensaver, Copyright (c) 1998, 1999 by Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/* The XDBE (Double Buffering) extension is pretty tricky to use, since you
+   can get X errors at inconvenient times during initialization.  This file
+   contains a utility routine to make it easier to deal with.
+ */
+
+#ifndef __XSCREENSAVER_XDBE_H__
+
+#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
+
+# include <X11/extensions/Xdbe.h>
+
+extern XdbeBackBuffer xdbe_get_backbuffer (Display *, Window, XdbeSwapAction);
+
+#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
+
+#endif /* __XSCREENSAVER_XDBE_H__ */
diff --git a/screenhack/xft.c b/screenhack/xft.c
new file mode 100644 (file)
index 0000000..9245752
--- /dev/null
@@ -0,0 +1,364 @@
+/* xscreensaver, Copyright (c) 2014-2018 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/* Compatibility layer using XDrawString, XDrawString16() or Xutf8DrawString().
+   This layer is used by X11 systems without Xft, and by MacOS / iOS.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifndef HAVE_XFT
+
+# include "utils.h"
+# include "resources.h"
+# include "xft.h"
+# include "utf8wc.h"
+
+extern const char *progname;
+
+struct _XftDraw {
+  Display   *dpy;
+  Drawable  drawable;
+  GC gc;
+  unsigned long pixel;
+  Font fid;
+  Visual *visual;
+  Colormap  colormap;
+};
+
+
+XftFont *
+XftFontOpenXlfd (Display *dpy, int screen, _Xconst char *xlfd)
+{
+  XftFont *ff = (XftFont *) calloc (1, sizeof(*ff));
+
+  if (!dpy || !xlfd) abort();
+  if (!ff) return 0;
+  ff->xfont = XLoadQueryFont (dpy, xlfd);
+  if (!ff->xfont)
+    {
+      free (ff);
+      return 0;
+    }
+
+  ff->name = strdup (xlfd);
+  ff->ascent  = ff->xfont->ascent;
+  ff->descent = ff->xfont->descent;
+  ff->height = ff->ascent + ff->descent;
+
+# ifdef HAVE_XUTF8DRAWSTRING
+  {
+    unsigned i;
+
+    // In the event of -*-random-* (under JWXYZ), get the actual XLFD,
+    // otherwise we'll get another random font that doesn't match ff->xfont.
+    char *xlfd_resolved = NULL;
+
+    char **missing_charset_list_return;
+    int missing_charset_count_return;
+    char *def_string_return;
+
+    char *ss;
+
+    for (i = 0; i != ff->xfont->n_properties; ++i) {
+      if (ff->xfont->properties[i].name == XA_FONT) {
+        xlfd_resolved = XGetAtomName (dpy, ff->xfont->properties[i].card32);
+        if (xlfd_resolved)
+          xlfd = xlfd_resolved;
+        break;
+      }
+    }
+
+    ss = (char *) malloc (strlen(xlfd) + 10);
+    strcpy (ss, xlfd);
+    strcat (ss, ",*");
+    ff->fontset = XCreateFontSet (dpy, ss,
+                                  &missing_charset_list_return,
+                                  &missing_charset_count_return,
+                                  &def_string_return);
+
+# if 0
+    {
+      int i;
+      for (i = 0; i < missing_charset_count_return; i++)
+        fprintf (stderr, "%s: missing charset: %s\n",
+                 ss, missing_charset_list_return[i]);
+    }
+# endif
+
+    /* Apparently this is not to be freed. */
+    /* if (def_string_return) XFree (def_string_return); */
+
+    if (missing_charset_list_return)
+      XFreeStringList (missing_charset_list_return);
+
+    free (ss);
+    free (xlfd_resolved);
+  }
+# endif
+
+  return ff;
+}
+
+
+void
+XftFontClose (Display *dpy, XftFont *font)
+{
+  if (!dpy || !font) abort();
+  free (font->name);
+  XFreeFont (dpy, font->xfont);
+# ifdef HAVE_XUTF8DRAWSTRING
+  XFreeFontSet (dpy, font->fontset);
+# endif
+  free (font);
+}
+
+
+Bool
+XftColorAllocName (Display  *dpy,
+                  _Xconst Visual *visual,
+                  Colormap cmap,
+                  _Xconst char *name,
+                  XftColor *result)
+{
+  XColor color;
+  if (!dpy || !visual || !name || !result) abort();
+
+  if (! XParseColor (dpy, cmap, name, &color))
+    {
+      fprintf (stderr, "%s: can't parse color %s", progname, name);
+      return False;
+    }
+  else if (! XAllocColor (dpy, cmap, &color))
+    {
+      fprintf (stderr, "%s: couldn't allocate color %s", progname, name);
+      return False;
+    }
+  else
+    {
+      XRenderColor color2;
+      color2.red    = color.red;
+      color2.green  = color.green;
+      color2.blue   = color.blue;
+      color2.alpha  = 0xFFFF;
+      XftColorAllocValue (dpy, visual, cmap, &color2, result);
+      result->pixel = color.pixel;
+      return True;
+    }
+}
+
+
+static short
+maskbase (unsigned long m)
+{
+  short i;
+  if (!m)
+    return 0;
+  i = 0;
+  while (! (m&1))
+    {
+      m >>= 1;
+      i++;
+    }
+  return i;
+}
+
+
+static short
+masklen (unsigned long m)
+{
+  unsigned long y;
+  y = (m >> 1) & 033333333333;
+  y = m - y - ((y >>1) & 033333333333);
+  return (short) (((y + (y >> 3)) & 030707070707) % 077);
+}
+
+
+Bool
+XftColorAllocValue (Display *dpy,
+                   _Xconst Visual *visual,
+                   Colormap cmap,
+                   _Xconst XRenderColor *color,
+                   XftColor *result)
+{
+  if (!dpy || !visual || !color || !result) abort();
+  if (visual->class == TrueColor)
+    {
+      int red_shift   = maskbase (visual->red_mask);
+      int red_len     = masklen  (visual->red_mask);
+      int green_shift = maskbase (visual->green_mask);
+      int green_len   = masklen (visual->green_mask);
+      int blue_shift  = maskbase (visual->blue_mask);
+      int blue_len    = masklen (visual->blue_mask);
+      result->pixel = (((color->red   >> (16 - red_len))   << red_shift)   |
+                       ((color->green >> (16 - green_len)) << green_shift) |
+                       ((color->blue  >> (16 - blue_len))  << blue_shift));
+# ifdef HAVE_JWXYZ
+      result->pixel |= BlackPixel(dpy, 0);  /* alpha */
+# endif
+    }
+  else
+    {
+      XColor xcolor;
+      xcolor.red   = color->red;
+      xcolor.green = color->green;
+      xcolor.blue  = color->blue;
+      if (!XAllocColor (dpy, cmap, &xcolor))
+        return False;
+      result->pixel = xcolor.pixel;
+    }
+  result->color.red   = color->red;
+  result->color.green = color->green;
+  result->color.blue  = color->blue;
+  result->color.alpha = color->alpha;
+  return True;
+}
+
+
+void
+XftColorFree (Display *dpy,
+              Visual *visual,
+              Colormap cmap,
+              XftColor *color)
+{
+  if (!dpy || !visual || !color) abort();
+  if (visual->class != TrueColor)
+    XFreeColors (dpy, cmap, &color->pixel, 1, 0);
+}
+
+
+XftDraw *
+XftDrawCreate (Display   *dpy,
+              Drawable  drawable,
+              Visual    *visual,
+              Colormap  colormap)
+{
+  XftDraw *dd = (XftDraw *) calloc (1, sizeof(*dd));
+  if (!dpy || !drawable || !visual) abort();
+  if (!dd) return 0;
+
+  dd->dpy = dpy;
+  dd->drawable = drawable;
+  dd->visual = visual;
+  dd->colormap = colormap;
+  dd->gc = XCreateGC (dpy, drawable, 0, 0);
+  return dd;
+}
+
+
+void
+XftDrawDestroy (XftDraw        *draw)
+{
+  if (!draw) abort();
+  XFreeGC (draw->dpy, draw->gc);
+  free (draw);
+}
+
+
+void
+XftTextExtentsUtf8 (Display        *dpy,
+                   XftFont         *font,
+                   _Xconst FcChar8 *string,
+                   int             len,
+                   XGlyphInfo      *extents)
+{
+  XCharStruct overall;
+
+  if (!dpy || !font || !string || !extents) abort();
+
+# ifdef HAVE_XUTF8DRAWSTRING
+  {
+    XRectangle ink;
+    int advancement =
+      Xutf8TextExtents (font->fontset, (const char *) string, len, &ink, 0);
+    XmbRectangle_to_XCharStruct (ink, overall, advancement);
+  }
+# else  /* !HAVE_XUTF8DRAWSTRING */
+  {
+    char *s2 = (char *) malloc (len + 1);
+    int direction, ascent, descent;
+    XChar2b *s16;
+    int s16_len = 0;
+    strncpy (s2, (char *) string, len);
+    s2[len] = 0;
+    s16 = utf8_to_XChar2b (s2, &s16_len);
+    XTextExtents16 (font->xfont, s16, s16_len,
+                    &direction, &ascent, &descent, &overall);
+    free (s2);
+    free (s16);
+  }
+# endif /* !HAVE_XUTF8DRAWSTRING */
+
+  XCharStruct_to_XGlyphInfo (overall, *extents);
+}
+
+
+void
+XftDrawStringUtf8 (XftDraw         *draw,
+                  _Xconst XftColor *color,
+                  XftFont          *font,
+                  int              x,
+                  int              y,
+                  _Xconst FcChar8  *string,
+                  int              len)
+{
+  if (!draw || !color || !font || !string) abort();
+
+  if (color->pixel != draw->pixel)
+    {
+      XSetForeground (draw->dpy, draw->gc, color->pixel);
+      draw->pixel = color->pixel;
+    }
+  if (font->xfont->fid != draw->fid)
+    {
+      XSetFont (draw->dpy, draw->gc, font->xfont->fid);
+      draw->fid = font->xfont->fid;
+    }
+
+# ifdef HAVE_XUTF8DRAWSTRING
+  /* If we have Xutf8DrawString, use it instead of XDrawString16 because
+     there is some chance it will handle characters of more than 16 bits
+     (beyond the Basic Multilingual Plane).
+   */
+
+  /* #### I guess I don't really understand how FontSet works, because when
+          using the real X11 implementation of Xutf8DrawString, this seems
+          to just truncate the text at the first non-ASCII character.
+
+          The XDrawString16() path works, however, at the expense of losing
+          everything above Basic Multilingual.  However, that path is only
+          taken on X11 systems that are old enough to not have libXft,
+          which means that the chance of Unicode working was already slim.
+   */
+  Xutf8DrawString (draw->dpy, draw->drawable, font->fontset, draw->gc, x, y, 
+                   (const char *) string, len);
+# else
+  {
+    int s16_len = 0;
+    char *s2 = (char *) malloc (len + 1);
+    XChar2b *s16;
+    strncpy (s2, (char *) string, len);
+    s2[len] = 0;
+    s16 = utf8_to_XChar2b (s2, &s16_len);
+    free (s2);
+    XDrawString16 (draw->dpy, draw->drawable, draw->gc, x, y, s16, s16_len);
+    free (s16);
+  }
+# endif
+}
+
+#else  /* HAVE_XFT */
+
+const int Wempty_translation_unit_is_a_dumb_warning = 0;
+
+#endif /* HAVE_XFT */
diff --git a/screenhack/xft.h b/screenhack/xft.h
new file mode 100644 (file)
index 0000000..385e28b
--- /dev/null
@@ -0,0 +1,167 @@
+/* xscreensaver, Copyright (c) 2014-2015 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/* Compatibility layer using XDrawString, XDrawString16() or Xutf8DrawString().
+   This layer is used by X11 systems without Xft, and by MacOS / iOS.
+ */
+
+#ifndef __XSCREENSAVER_XFT_H__
+#define __XSCREENSAVER_XFT_H__
+
+/* The XGlyphInfo field names and values are, of course, arbitrarily
+   different from XCharStruct for no sensible reason.  These macros
+   translate between them.
+ */
+
+# define XGlyphInfo_to_XCharStruct(G,C) do {           \
+    (C).lbearing  =  -(G).x;                           \
+    (C).rbearing  =   (G).width - (G).x;               \
+    (C).ascent    =   (G).y;                           \
+    (C).descent   =   (G).height - (G).y;              \
+    (C).width     =   (G).xOff;                                \
+} while (0)
+
+# define XCharStruct_to_XGlyphInfo(C,G) do {           \
+    (G).x         =  -(C).lbearing;                    \
+    (G).y         =   (C).ascent;                      \
+    (G).xOff      =   (C).width;                       \
+    (G).yOff      =   0;                               \
+    (G).width     =   (C).rbearing - (C).lbearing;     \
+    (G).height    =   (C).ascent   + (C).descent;      \
+} while (0)
+
+/* Xutf8TextExtents returns a bounding box in an XRectangle, which
+   conveniently interprets everything in the opposite direction
+   from XGlyphInfo!
+ */
+# define XCharStruct_to_XmbRectangle(C,R) do {         \
+    (R).x         =   (C).lbearing;                    \
+    (R).y         =  -(C).ascent;                      \
+    (R).width     =   (C).rbearing - (C).lbearing;     \
+    (R).height    =   (C).ascent   + (C).descent;      \
+} while (0)
+
+# define XmbRectangle_to_XCharStruct(R,C,ADV) do {     \
+    (C).lbearing  =   (R).x;                           \
+    (C).rbearing  =   (R).width + (R).x;               \
+    (C).ascent    =  -(R).y;                           \
+    (C).descent   =   (R).height + (R).y;              \
+    (C).width     =   (ADV);                           \
+} while (0)
+
+
+# ifdef HAVE_XFT
+
+#  include <X11/Xft/Xft.h>
+
+# else  /* !HAVE_XFT -- the rest of the file */
+
+# ifdef HAVE_COCOA
+#  include "jwxyz.h"
+#elif defined(HAVE_ANDROID)
+#  include "jwxyz.h"
+# else
+#  include <X11/Xlib.h>
+# endif
+
+/* This doesn't seem to work right under X11.  See comment in xft.c. */
+# ifndef HAVE_COCOA
+#  undef HAVE_XUTF8DRAWSTRING
+# endif
+
+
+# ifndef _Xconst
+#  define _Xconst const
+# endif
+
+typedef struct _XGlyphInfo {
+  unsigned short width, height;     /* bounding box of the ink */
+  short x, y;          /* distance from upper left of bbox to glyph origin. */
+  short xOff, yOff;    /* distance from glyph origin to next origin. */
+} XGlyphInfo;
+
+
+typedef struct _XftFont {
+  XFontStruct *xfont;
+# ifdef HAVE_XUTF8DRAWSTRING
+  XFontSet fontset;
+# endif
+  char *name;
+  int ascent;
+  int descent;
+  int height;
+} XftFont;
+
+typedef struct {
+  unsigned short   red;
+  unsigned short   green;
+  unsigned short   blue;
+  unsigned short   alpha;
+} XRenderColor;
+
+typedef struct _XftColor {
+  unsigned long pixel;
+  XRenderColor color;
+} XftColor;
+
+typedef struct _XftDraw XftDraw;
+
+typedef unsigned char FcChar8;
+
+
+XftFont *XftFontOpenXlfd (Display *dpy, int screen, _Xconst char *xlfd);
+#define XftFontOpenName XftFontOpenXlfd
+
+void XftFontClose (Display *dpy, XftFont *font);
+
+Bool XftColorAllocName (Display  *dpy,
+                        _Xconst Visual *visual,
+                        Colormap cmap,
+                        _Xconst char *name,
+                        XftColor *result);
+
+Bool XftColorAllocValue (Display *dpy,
+                         _Xconst Visual *visual,
+                         Colormap cmap,
+                         _Xconst XRenderColor *color,
+                         XftColor *result);
+
+void XftColorFree (Display *dpy,
+                   Visual *visual,
+                   Colormap cmap,
+                   XftColor *color);
+
+XftDraw *XftDrawCreate (Display   *dpy,
+                        Drawable  drawable,
+                        Visual    *visual,
+                        Colormap  colormap);
+
+void XftDrawDestroy (XftDraw *draw);
+
+void
+XftTextExtentsUtf8 (Display        *dpy,
+                   XftFont         *pub,
+                   _Xconst FcChar8 *string,
+                   int             len,
+                   XGlyphInfo      *extents);
+
+void
+XftDrawStringUtf8 (XftDraw         *draw,
+                  _Xconst XftColor *color,
+                  XftFont          *pub,
+                  int              x,
+                  int              y,
+                  _Xconst FcChar8  *string,
+                  int              len);
+
+# endif /* !HAVE_XFT */
+
+#endif /* __XSCREENSAVER_XFT_H__ */
diff --git a/screenhack/ximage-loader.c b/screenhack/ximage-loader.c
new file mode 100644 (file)
index 0000000..1bcbc2c
--- /dev/null
@@ -0,0 +1,706 @@
+/* ximage-loader.c --- converts image files or data to XImages or Pixmap.
+ * xscreensaver, Copyright (c) 1998-2020 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifdef HAVE_JWXYZ
+# include "jwxyz.h"
+#else
+# include <X11/Xlib.h>
+# include <X11/Xutil.h>
+#endif
+
+#include "ximage-loader.h"
+
+#if defined(HAVE_GDK_PIXBUF) || defined(HAVE_COCOA) || defined(HAVE_ANDROID)
+# undef HAVE_LIBPNG
+#endif
+
+#ifdef HAVE_COCOA
+# include "grabscreen.h"  /* for osx_load_image_file() */
+#endif
+
+#ifdef HAVE_GDK_PIXBUF
+
+# if (__GNUC__ >= 4)   /* Ignore useless warnings generated by GTK headers */
+#  pragma GCC diagnostic push
+#  pragma GCC diagnostic ignored "-Wlong-long"
+#  pragma GCC diagnostic ignored "-Wvariadic-macros"
+#  pragma GCC diagnostic ignored "-Wpedantic"
+# endif
+
+# include <gdk-pixbuf/gdk-pixbuf.h>
+# ifdef HAVE_GTK2
+#  include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
+# else  /* !HAVE_GTK2 */
+#  include <gdk-pixbuf/gdk-pixbuf-xlib.h>
+# endif /* !HAVE_GTK2 */
+
+# if (__GNUC__ >= 4)
+#  pragma GCC diagnostic pop
+# endif
+
+#endif /* HAVE_GDK_PIXBUF */
+
+#ifdef HAVE_LIBPNG
+# include <png.h>
+#endif
+
+#ifdef HAVE_ANDROID
+ /* So that debug output shows up in logcat... */
+extern void Log(const char *format, ...);
+# undef  fprintf
+# define fprintf(S, ...) Log(__VA_ARGS__)
+#endif
+
+extern char *progname;
+
+static Bool
+bigendian (void)
+{
+  union { int i; char c[sizeof(int)]; } u;
+  u.i = 1;
+  return !u.c[0];
+}
+
+
+#ifdef HAVE_GDK_PIXBUF
+
+/* Loads the image to an XImage, RGBA -- GDK Pixbuf version.
+ */
+static XImage *
+make_ximage (Display *dpy, Visual *visual, const char *filename,
+             const unsigned char *image_data, unsigned long data_size)
+{
+  GdkPixbuf *pb;
+  static int initted = 0;
+# ifdef HAVE_GTK2
+  GError *gerr = NULL;
+# endif
+
+  if (!initted)
+    {
+# ifdef HAVE_GTK2
+#  if !GLIB_CHECK_VERSION(2, 36 ,0)
+      g_type_init ();
+#  endif
+# endif
+      if (dpy)
+        {
+          /* Turns out gdk-pixbuf works even if you don't have display
+             connection, which is good news for analogtv-cli. */
+          gdk_pixbuf_xlib_init (dpy, DefaultScreen (dpy));
+          xlib_rgb_init (dpy, DefaultScreenOfDisplay (dpy));
+        }
+      initted = 1;
+    }
+
+  if (filename)
+    {
+# ifdef HAVE_GTK2
+      pb = gdk_pixbuf_new_from_file (filename, &gerr);
+      if (!pb)
+        {
+          fprintf (stderr, "%s: %s\n", progname, gerr->message);
+          return 0;
+        }
+# else
+      pb = gdk_pixbuf_new_from_file (filename);
+      if (!pb)
+        {
+          fprintf (stderr, "%s: GDK unable to load %s: %s\n",
+                   progname, filename, (gerr ? gerr->message : "?"));
+          return 0;
+        }
+# endif /* HAVE_GTK2 */
+    }
+  else
+    {
+# ifdef HAVE_GTK2
+      GInputStream *s =
+        g_memory_input_stream_new_from_data (image_data, data_size, 0);
+      pb = gdk_pixbuf_new_from_stream (s, 0, &gerr);
+
+      g_input_stream_close (s, NULL, NULL);
+      /* #### valgrind on xflame says there's a small leak in s? */
+      g_object_unref (s);
+
+      if (! pb)
+        {
+          /* fprintf (stderr, "%s: GDK unable to parse image data: %s\n",
+                   progname, (gerr ? gerr->message : "?")); */
+          return 0;
+        }
+# else /* !HAVE_GTK2 */
+      fprintf (stderr, "%s: image loading not supported with GTK 1.x\n",
+               progname);
+      return 0;
+# endif /* !HAVE_GTK2 */
+    }
+
+  if (!pb) abort();
+
+  {
+    XImage *image;
+    int w = gdk_pixbuf_get_width (pb);
+    int h = gdk_pixbuf_get_height (pb);
+    guchar *row = gdk_pixbuf_get_pixels (pb);
+    int stride = gdk_pixbuf_get_rowstride (pb);
+    int chan = gdk_pixbuf_get_n_channels (pb);
+    int x, y;
+
+    image = XCreateImage (dpy, visual, 32, ZPixmap, 0, 0, w, h, 32, 0);
+    image->data = (char *) malloc(h * image->bytes_per_line);
+
+    /* Set the bit order in the XImage structure to whatever the
+       local host's native bit order is.
+    */
+    image->bitmap_bit_order =
+      image->byte_order =
+      (bigendian() ? MSBFirst : LSBFirst);
+
+    if (!image->data)
+      {
+        fprintf (stderr, "%s: out of memory (%d x %d)\n", progname, w, h);
+        return 0;
+      }
+
+    for (y = 0; y < h; y++)
+      {
+        guchar *i = row;
+        for (x = 0; x < w; x++)
+          {
+            unsigned long rgba = 0;
+            switch (chan) {
+            case 1:
+              rgba = ((0xFF << 24) |
+                      (*i   << 16) |
+                      (*i   <<  8) |
+                       *i);
+              i++;
+              break;
+            case 3:
+              rgba = ((0xFF << 24) |
+                      (i[2] << 16) |
+                      (i[1] <<  8) |
+                      i[0]);
+              i += 3;
+              break;
+            case 4:
+              rgba = ((i[3] << 24) |
+                      (i[2] << 16) |
+                      (i[1] <<  8) |
+                      i[0]);
+              i += 4;
+              break;
+            default:
+              abort();
+              break;
+            }
+            XPutPixel (image, x, y, rgba);
+          }
+        row += stride;
+      }
+
+    /* #### valgrind on xflame says there's a small leak in pb? */
+    g_object_unref (pb);
+    return image;
+  }
+}
+
+#elif defined(HAVE_JWXYZ) /* MacOS, iOS or Android */
+
+/* Loads the image to an XImage, RGBA -- MacOS, iOS or Android version.
+ */
+static XImage *
+make_ximage (Display *dpy, Visual *visual, const char *filename,
+             const unsigned char *image_data, unsigned long data_size)
+{
+  XImage *ximage = 0;
+
+  if (filename)
+    {
+# ifdef HAVE_COCOA  /* MacOS */
+      XRectangle geom;
+      Screen *screen = DefaultScreenOfDisplay (dpy);
+      Window window = RootWindowOfScreen (screen);
+      XWindowAttributes xgwa;
+      XGetWindowAttributes (dpy, window, &xgwa);
+      Pixmap pixmap =
+        XCreatePixmap (dpy, window, xgwa.width, xgwa.height, xgwa.depth);
+      int x, y;
+
+      if (! osx_load_image_file (screen, window, pixmap, filename, &geom))
+        {
+          fprintf (stderr, "%s: %s failed\n", progname, filename);
+          return 0;
+        }
+
+      ximage = XGetImage (dpy, pixmap, geom.x, geom.y,
+                          geom.width, geom.height,
+                          ~0L, ZPixmap);
+      if (!ximage) abort();
+
+      /* Have to convert ABGR to RGBA */
+      for (y = 0; y < ximage->height; y++)
+        for (x = 0; x < ximage->width; x++)
+          {
+            unsigned long p = XGetPixel (ximage, x, y);
+            unsigned long a = (p >> 24) & 0xFF;
+            unsigned long b = (p >> 16) & 0xFF;
+            unsigned long g = (p >>  8) & 0xFF;
+            unsigned long r = (p >>  0) & 0xFF;
+            p = (r << 24) | (g << 16) | (b << 8) | (a << 0);
+            XPutPixel (ximage, x, y, p);
+          }
+
+      XFreePixmap (dpy, pixmap);
+
+# else   /* !HAVE_COCOA -- iOS or Android. */
+      fprintf (stderr, "%s: image file loading not supported\n", progname);
+      return 0;
+# endif  /* !HAVE_COCOA */
+    }
+  else
+    {
+      ximage = jwxyz_png_to_ximage (dpy, visual, image_data, data_size);
+    }
+
+  return ximage;
+}
+
+#elif defined(HAVE_LIBPNG)
+
+typedef struct {
+  const unsigned char *buf;
+  png_size_t siz, ptr;
+} png_read_closure;
+
+static void
+png_reader_fn (png_structp png_ptr, png_bytep buf, png_size_t siz)
+{
+  png_read_closure *r = png_get_io_ptr (png_ptr);
+  if (siz > r->siz - r->ptr)
+    png_error (png_ptr, "PNG internal read error");
+  memcpy (buf, r->buf + r->ptr, siz);
+  r->ptr += siz;
+}
+
+
+/* Loads the image to an XImage, RGBA -- libpng version.
+ */
+static XImage *
+make_ximage (Display *dpy, Visual *visual,
+             const char *filename, const unsigned char *image_data,
+             unsigned long data_size)
+{
+  XImage *image = 0;
+  png_structp png_ptr;
+  png_infop info_ptr;
+  png_infop end_info;
+  png_uint_32 width, height, channels;
+  int bit_depth, color_type, interlace_type;
+  FILE *fp = 0;
+
+  png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, 0, 0, 0);
+  if (!png_ptr) return 0;
+
+  info_ptr = png_create_info_struct (png_ptr);
+  if (!info_ptr)
+    {
+      png_destroy_read_struct (&png_ptr, 0, 0);
+      return 0;
+    }
+
+  end_info = png_create_info_struct (png_ptr);
+  if (!end_info)
+    {
+      png_destroy_read_struct (&png_ptr, &info_ptr, 0);
+      return 0;
+    }
+
+  if (setjmp (png_jmpbuf(png_ptr)))
+    {
+      png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
+      return 0;
+    }
+
+  if (filename)
+    {
+      fp = fopen (filename, "r");
+      if (! fp)
+        {
+          fprintf (stderr, "%s: unable to read %s\n", progname, filename);
+          return 0;
+        }
+      png_init_io (png_ptr, fp);
+    }
+  else
+    {
+      png_read_closure closure;
+      closure.buf = image_data;
+      closure.siz = data_size;
+      closure.ptr = 0;
+      png_set_read_fn (png_ptr, (void *) &closure, png_reader_fn);
+    }
+
+  png_read_info (png_ptr, info_ptr);
+  png_get_IHDR (png_ptr, info_ptr,
+                &width, &height, &bit_depth, &color_type,
+                &interlace_type, 0, 0);
+
+  png_set_strip_16 (png_ptr);  /* Truncate 16 bits per component to 8 */
+  png_set_packing (png_ptr);   /* Unpack to 1 pixel per byte */
+
+# if 0
+  if (color_type == PNG_COLOR_TYPE_PALETTE)  /* Colormap to RGB */
+    png_set_palette_rgb (png_ptr);
+
+  if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)  /* Mono to 8bit */
+    png_set_gray_1_2_4_to_8 (png_ptr);
+# endif
+
+  if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS)) /* Fix weird alpha */
+    png_set_tRNS_to_alpha (png_ptr);
+
+  /* At least 8 bits deep */
+  if (color_type == PNG_COLOR_TYPE_PALETTE && bit_depth <= 8)
+    png_set_expand (png_ptr);
+
+   if (bit_depth == 8 &&          /* Convert RGB to RGBA */
+           (color_type == PNG_COLOR_TYPE_RGB ||
+            color_type == PNG_COLOR_TYPE_PALETTE))
+     png_set_filler (png_ptr, 0xFF, PNG_FILLER_AFTER);
+
+  /* Grayscale to color */
+  if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
+    png_set_expand (png_ptr);
+
+
+  /* Convert graysale to color */
+  if (color_type == PNG_COLOR_TYPE_GRAY ||
+      color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+    png_set_gray_to_rgb (png_ptr);
+
+# if 0
+  {
+    png_color_16 *bg;
+    if (png_get_bKGD (png_ptr, info_ptr, &bg))
+      png_set_background (png_ptr, bg, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0);
+  }
+# endif
+
+  /* Commit */
+  png_read_update_info (png_ptr, info_ptr);
+
+  channels = png_get_channels (png_ptr, info_ptr);
+
+  {
+    png_bytep *rows = png_malloc (png_ptr, height * sizeof(*rows));
+    int x, y;
+    for (y = 0; y < height; y++)
+      rows[y] = png_malloc (png_ptr, png_get_rowbytes (png_ptr, info_ptr));
+    png_read_image (png_ptr, rows);
+    png_read_end (png_ptr, info_ptr);
+
+    image = XCreateImage (dpy, visual, 32, ZPixmap, 0, 0,
+                          width, height, 32, 0);
+    image->data = (char *) malloc (height * image->bytes_per_line);
+
+    /* Set the bit order in the XImage structure to whatever the
+       local host's native bit order is.
+     */
+    image->bitmap_bit_order =
+      image->byte_order =
+        (bigendian() ? MSBFirst : LSBFirst);
+
+    if (!image->data)
+      {
+        fprintf (stderr, "%s: out of memory (%lu x %lu)\n",
+                 progname, (unsigned long)width, (unsigned long)height);
+        return 0;
+      }
+
+    for (y = 0; y < height; y++)
+      {
+        png_bytep i = rows[y];
+        for (x = 0; x < width; x++)
+          {
+            unsigned long rgba;
+            switch (channels) {
+            case 4:
+              rgba = ((i[3] << 24) |
+                      (i[2] << 16) |
+                      (i[1] << 8)  |
+                       i[0]);
+              break;
+            case 3:
+              rgba = ((0xFF << 24) |
+                      (i[2] << 16) |
+                      (i[1] << 8)  |
+                       i[0]);
+              break;
+            case 2:
+              rgba = ((i[1] << 24) |
+                      (i[0] << 16) |
+                      (i[0] << 8)  |
+                       i[0]);
+              break;
+            case 1:
+              rgba = ((0xFF << 24) |
+                      (i[0] << 16) |
+                      (i[0] << 8)  |
+                       i[0]);
+              break;
+            default:
+              abort();
+            }
+            XPutPixel (image, x, y, rgba);
+            i += channels;
+          }
+        png_free (png_ptr, rows[y]);
+      }
+
+    png_free (png_ptr, rows);
+  }
+
+  png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);  
+  if (fp) fclose (fp);
+
+  return image;
+}
+
+
+#else /* No image loaders! */
+
+static XImage *
+make_ximage (Display *dpy, Visual *visual,
+             const char *filename, const unsigned char *image_data,
+             unsigned long data_size)
+{
+  fprintf (stderr, "%s: no image loading support!\n", progname);
+  return 0;
+}
+
+#endif /* no loaders */
+
+
+/* Given a bitmask, returns the position and width of the field.
+ */
+static void
+decode_mask (unsigned long mask, unsigned long *pos_ret,
+             unsigned long *size_ret)
+{
+  int i;
+  for (i = 0; i < 32; i++)
+    if (mask & (1L << i))
+      {
+        int j = 0;
+        *pos_ret = i;
+        for (; i < 32; i++, j++)
+          if (! (mask & (1L << i)))
+            break;
+        *size_ret = j;
+        return;
+      }
+}
+
+
+/* Loads the image to a Pixmap and optional 1-bit mask.
+ */
+static Pixmap
+make_pixmap (Display *dpy, Window window,
+             const char *filename,
+             const unsigned char *image_data, unsigned long data_size,
+             int *width_ret, int *height_ret, Pixmap *mask_ret)
+{
+  XWindowAttributes xgwa;
+  XImage *in, *out, *mask = 0;
+  Pixmap pixmap;
+  XGCValues gcv;
+  GC gc;
+  int x, y;
+
+  unsigned long crpos=0, cgpos=0, cbpos=0, capos=0; /* bitfield positions */
+  unsigned long srpos=0, sgpos=0, sbpos=0;
+  unsigned long srmsk=0, sgmsk=0, sbmsk=0;
+  unsigned long srsiz=0, sgsiz=0, sbsiz=0;
+
+# ifdef HAVE_JWXYZ
+  // BlackPixel has alpha: 0xFF000000.
+  unsigned long black = BlackPixelOfScreen (DefaultScreenOfDisplay (dpy));
+#else
+  unsigned long black = 0;
+# endif
+
+  XGetWindowAttributes (dpy, window, &xgwa);
+
+  in = make_ximage (dpy, xgwa.visual, filename, image_data, data_size);
+  if (!in) return 0;
+
+  /* Create a new image in the depth and bit-order of the server. */
+  out = XCreateImage (dpy, xgwa.visual, xgwa.depth, ZPixmap, 0, 0,
+                      in->width, in->height, 8, 0);
+
+  out->bitmap_bit_order = in->bitmap_bit_order;
+  out->byte_order = in->byte_order;
+
+  out->bitmap_bit_order = BitmapBitOrder (dpy);
+  out->byte_order = ImageByteOrder (dpy);
+
+  out->data = (char *) malloc (out->height * out->bytes_per_line);
+  if (!out->data) abort();
+
+  if (mask_ret)
+    {
+      mask = XCreateImage (dpy, xgwa.visual, 1, XYPixmap, 0, 0,
+                           in->width, in->height, 8, 0);
+      mask->byte_order = in->byte_order;
+      mask->data = (char *) malloc (mask->height * mask->bytes_per_line);
+    }
+
+  /* Find the server's color masks.
+   */
+  srmsk = out->red_mask;
+  sgmsk = out->green_mask;
+  sbmsk = out->blue_mask;
+
+  if (!(srmsk && sgmsk && sbmsk)) abort();  /* No server color masks? */
+
+  decode_mask (srmsk, &srpos, &srsiz);
+  decode_mask (sgmsk, &sgpos, &sgsiz);
+  decode_mask (sbmsk, &sbpos, &sbsiz);
+
+  /* 'in' is RGBA in client endianness.  Convert to what the server wants. */
+  if (bigendian())
+    crpos = 24, cgpos = 16, cbpos =  8, capos =  0;
+  else
+    crpos =  0, cgpos =  8, cbpos = 16, capos = 24;
+
+  for (y = 0; y < in->height; y++)
+    for (x = 0; x < in->width; x++)
+      {
+        unsigned long p = XGetPixel (in, x, y);
+        unsigned char a = (p >> capos) & 0xFF;
+        unsigned char b = (p >> cbpos) & 0xFF;
+        unsigned char g = (p >> cgpos) & 0xFF;
+        unsigned char r = (p >> crpos) & 0xFF;
+        XPutPixel (out, x, y, ((r << srpos) |
+                               (g << sgpos) |
+                               (b << sbpos) |
+                               black));
+        if (mask)
+          XPutPixel (mask, x, y, (a ? 1 : 0));
+      }
+
+  XDestroyImage (in);
+  in = 0;
+
+  pixmap = XCreatePixmap (dpy, window, out->width, out->height, xgwa.depth);
+  gc = XCreateGC (dpy, pixmap, 0, &gcv);
+  XPutImage (dpy, pixmap, gc, out, 0, 0, 0, 0, out->width, out->height);
+  XFreeGC (dpy, gc);
+
+  if (mask)
+    {
+      Pixmap p2 = XCreatePixmap (dpy, window, mask->width, mask->height, 1);
+      gcv.foreground = 1;
+      gcv.background = 0;
+      gc = XCreateGC (dpy, p2, GCForeground|GCBackground, &gcv);
+      XPutImage (dpy, p2, gc, mask, 0, 0, 0, 0, mask->width, mask->height);
+      XFreeGC (dpy, gc);
+      XDestroyImage (mask);
+      mask = 0;
+      *mask_ret = p2;
+    }
+
+  if (width_ret)  *width_ret  = out->width;
+  if (height_ret) *height_ret = out->height;
+
+  XDestroyImage (out);
+
+  return pixmap;
+}
+
+
+/* Textures are upside down, so invert XImages before returning them.
+ */
+static void
+flip_ximage (XImage *ximage)
+{
+  char *data2, *in, *out;
+  int y;
+
+  if (!ximage) return;
+  data2 = malloc (ximage->bytes_per_line * ximage->height);
+  if (!data2) abort();
+  in = ximage->data;
+  out = data2 + ximage->bytes_per_line * (ximage->height - 1);
+  for (y = 0; y < ximage->height; y++)
+    {
+      memcpy (out, in, ximage->bytes_per_line);
+      in  += ximage->bytes_per_line;
+      out -= ximage->bytes_per_line;
+    }
+  free (ximage->data);
+  ximage->data = data2;
+}
+
+
+Pixmap
+image_data_to_pixmap (Display *dpy, Window window, 
+                      const unsigned char *image_data, unsigned long data_size,
+                      int *width_ret, int *height_ret,
+                      Pixmap *mask_ret)
+{
+  return make_pixmap (dpy, window, 0, image_data, data_size,
+                      width_ret, height_ret, mask_ret);
+}
+
+Pixmap
+file_to_pixmap (Display *dpy, Window window, const char *filename,
+                int *width_ret, int *height_ret,
+                Pixmap *mask_ret)
+{
+  return make_pixmap (dpy, window, filename, 0, 0,
+                      width_ret, height_ret, mask_ret);
+}
+
+
+/* This XImage has RGBA data, which is what OpenGL code typically expects.
+   Also it is upside down: the origin is at the bottom left of the image.
+   X11 typically expects 0RGB as it has no notion of alpha, only 1-bit masks.
+   With X11 code, you should probably use the _pixmap routines instead.
+ */
+XImage *
+image_data_to_ximage (Display *dpy, Visual *visual,
+                      const unsigned char *image_data,
+                      unsigned long data_size)
+{
+  XImage *ximage = make_ximage (dpy, visual, 0, image_data, data_size);
+  flip_ximage (ximage);
+  return ximage;
+}
+
+XImage *
+file_to_ximage (Display *dpy, Visual *visual, const char *filename)
+{
+  XImage *ximage = make_ximage (dpy, visual, filename, 0, 0);
+  flip_ximage (ximage);
+  return ximage;
+}
diff --git a/screenhack/ximage-loader.h b/screenhack/ximage-loader.h
new file mode 100644 (file)
index 0000000..bed163f
--- /dev/null
@@ -0,0 +1,37 @@
+/* ximage-loader.h --- converts XPM data to Pixmaps.
+ * xscreensaver, Copyright (c) 1998-2018 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifndef _XIMAGE_LOADER_H_
+#define _XIMAGE_LOADER_H_
+
+extern Pixmap file_to_pixmap (Display *, Window, const char *filename,
+                              int *width_ret, int *height_ret,
+                              Pixmap *mask_ret);
+
+extern Pixmap image_data_to_pixmap (Display *, Window, 
+                                    const unsigned char *image_data,
+                                    unsigned long data_size,
+                                    int *width_ret, int *height_ret,
+                                    Pixmap *mask_ret);
+
+/* This XImage has RGBA data, which is what OpenGL code typically expects.
+   Also it is upside down: the origin is at the bottom left of the image.
+   X11 typically expects 0RGB as it has no notion of alpha, only 1-bit masks.
+   With X11 code, you should probably use the _pixmap routines instead.
+ */
+extern XImage *image_data_to_ximage (Display *, Visual *,
+                                     const unsigned char *image_data,
+                                     unsigned long data_size);
+
+extern XImage *file_to_ximage (Display *, Visual *, const char *filename);
+
+#endif /* _XIMAGE_LOADER_H_ */
diff --git a/screenhack/xmu.c b/screenhack/xmu.c
new file mode 100644 (file)
index 0000000..2f273d4
--- /dev/null
@@ -0,0 +1,173 @@
+/* This file contains compatibility routines for systems without Xmu.
+ * You would be better served by installing Xmu on your machine or
+ * yelling at your vendor to ship it.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifndef HAVE_XMU
+/*
+ * Copyright 1989 Massachusetts Institute of Technology
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of M.I.T. not be used in advertising
+ * or publicity pertaining to distribution of the software without specific,
+ * written prior permission.  M.I.T. makes no representations about the
+ * suitability of this software for any purpose.  It is provided "as is"
+ * without express or implied warranty.
+ *
+ * M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL M.I.T.
+ * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "xmu.h"
+
+#ifndef NEED_EVENTS
+# define NEED_EVENTS           /* to make Xproto.h define xEvent */
+#endif
+#ifndef VMS
+# include <X11/Xproto.h>       /* for xEvent (used by Xlibint.h) */
+# include <X11/Xlibint.h>      /* for _XExtension */
+#else /* VMS */
+# include <X11/Xlib.h>
+#endif /* VMS */
+#include <X11/Intrinsic.h>     /* for XtSpecificationRelease */
+
+/*
+ * XmuPrintDefaultErrorMessage - print a nice error that looks like the usual 
+ * message.  Returns 1 if the caller should consider exitting else 0.
+ */
+int XmuPrintDefaultErrorMessage (Display *dpy, XErrorEvent *event, FILE *fp)
+{
+    char buffer[BUFSIZ];
+    char mesg[BUFSIZ];
+    char number[32];
+    char *mtype = "XlibMessage";
+    _XExtension *ext = (_XExtension *)NULL;
+    XGetErrorText(dpy, event->error_code, buffer, BUFSIZ);
+    XGetErrorDatabaseText(dpy, mtype, "XError", "X Error", mesg, BUFSIZ);
+    (void) fprintf(fp, "%s:  %s\n  ", mesg, buffer);
+    XGetErrorDatabaseText(dpy, mtype, "MajorCode", "Request Major code %d", 
+       mesg, BUFSIZ);
+    (void) fprintf(fp, mesg, event->request_code);
+    if (event->request_code < 128) {
+       sprintf(number, "%d", event->request_code);
+       XGetErrorDatabaseText(dpy, "XRequest", number, "", buffer, BUFSIZ);
+    } else {
+       /* XXX this is non-portable */
+       for (ext = dpy->ext_procs;
+            ext && (ext->codes.major_opcode != event->request_code);
+            ext = ext->next)
+         ;
+       if (ext)
+           strcpy(buffer, ext->name);
+       else
+           buffer[0] = '\0';
+    }
+    (void) fprintf(fp, " (%s)", buffer);
+    fputs("\n  ", fp);
+#if (XtSpecificationRelease >= 5)
+    if (event->request_code >= 128) {
+       XGetErrorDatabaseText(dpy, mtype, "MinorCode", "Request Minor code %d",
+                             mesg, BUFSIZ);
+       (void) fprintf(fp, mesg, event->minor_code);
+       if (ext) {
+           sprintf(mesg, "%s.%d", ext->name, event->minor_code);
+           XGetErrorDatabaseText(dpy, "XRequest", mesg, "", buffer, BUFSIZ);
+           (void) fprintf(fp, " (%s)", buffer);
+       }
+       fputs("\n  ", fp);
+    }
+    if (event->error_code >= 128) {
+       /* let extensions try to print the values */
+       /* XXX this is non-portable code */
+       for (ext = dpy->ext_procs; ext; ext = ext->next) {
+           if (ext->error_values)
+               (*ext->error_values)(dpy, event, fp);
+       }
+       /* the rest is a fallback, providing a simple default */
+       /* kludge, try to find the extension that caused it */
+       buffer[0] = '\0';
+       for (ext = dpy->ext_procs; ext; ext = ext->next) {
+           if (ext->error_string) 
+               (*ext->error_string)(dpy, event->error_code, &ext->codes,
+                                    buffer, BUFSIZ);
+           if (buffer[0])
+               break;
+       }    
+       if (buffer[0])
+           sprintf(buffer, "%s.%d", ext->name,
+                   event->error_code - ext->codes.first_error);
+       else
+           strcpy(buffer, "Value");
+       XGetErrorDatabaseText(dpy, mtype, buffer, "", mesg, BUFSIZ);
+       if (*mesg) {
+           (void) fprintf(fp, mesg, event->resourceid);
+           fputs("\n  ", fp);
+       }
+    } else if ((event->error_code == BadWindow) ||
+              (event->error_code == BadPixmap) ||
+              (event->error_code == BadCursor) ||
+              (event->error_code == BadFont) ||
+              (event->error_code == BadDrawable) ||
+              (event->error_code == BadColor) ||
+              (event->error_code == BadGC) ||
+              (event->error_code == BadIDChoice) ||
+              (event->error_code == BadValue) ||
+              (event->error_code == BadAtom)) {
+       if (event->error_code == BadValue)
+           XGetErrorDatabaseText(dpy, mtype, "Value", "Value 0x%x",
+                                 mesg, BUFSIZ);
+       else if (event->error_code == BadAtom)
+           XGetErrorDatabaseText(dpy, mtype, "AtomID", "AtomID 0x%x",
+                                 mesg, BUFSIZ);
+       else
+           XGetErrorDatabaseText(dpy, mtype, "ResourceID", "ResourceID 0x%x",
+                                 mesg, BUFSIZ);
+       (void) fprintf(fp, mesg, event->resourceid);
+       fputs("\n  ", fp);
+    }
+#elif (XtSpecificationRelease == 4)
+    XGetErrorDatabaseText(dpy, mtype, "MinorCode", "Request Minor code %d",
+                         mesg, BUFSIZ);
+    (void) fprintf(fp, mesg, event->minor_code);
+    fputs("\n  ", fp);
+    if (ext) {
+      sprintf(mesg, "%s.%d", ext->name, event->minor_code);
+      XGetErrorDatabaseText(dpy, "XRequest", mesg, "", buffer, BUFSIZ);
+      (void) fprintf(fp, " (%s)", buffer);
+    }
+    XGetErrorDatabaseText(dpy, mtype, "ResourceID", "ResourceID 0x%x",
+                         mesg, BUFSIZ);
+    (void) fprintf(fp, mesg, event->resourceid);
+    fputs("\n  ", fp);
+#else
+ERROR! Unsupported release of X11
+#endif
+    XGetErrorDatabaseText(dpy, mtype, "ErrorSerial", "Error Serial #%d", 
+       mesg, BUFSIZ);
+    (void) fprintf(fp, mesg, event->serial);
+    fputs("\n  ", fp);
+    XGetErrorDatabaseText(dpy, mtype, "CurrentSerial", "Current Serial #%d",
+       mesg, BUFSIZ);
+    (void) fprintf(fp, mesg, NextRequest(dpy)-1);
+    fputs("\n", fp);
+    if (event->error_code == BadImplementation) return 0;
+    return 1;
+}
+
+#else  /* HAVE_XMU */
+
+/* Shut up the stupid "gcc -pedantic" warning */
+int _I_dont_care_that_ISO_C_forbids_an_empty_source_file_ = 1;
+
+#endif /* HAVE_XMU */
diff --git a/screenhack/xmu.h b/screenhack/xmu.h
new file mode 100644 (file)
index 0000000..48084f7
--- /dev/null
@@ -0,0 +1,14 @@
+/* This file contains compatibility routines for systems without Xmu.
+ * You would be better served by installing Xmu on your machine or
+ * yelling at your vendor to ship it.
+ */
+
+#ifndef __XMU_H__
+#define __XMU_H__
+
+#include <X11/Xlib.h>
+#include <stdio.h>
+
+int XmuPrintDefaultErrorMessage (Display *dpy, XErrorEvent *event, FILE *fp);
+
+#endif /* __XMU_H__ */
diff --git a/screenhack/xshm.c b/screenhack/xshm.c
new file mode 100644 (file)
index 0000000..3c99623
--- /dev/null
@@ -0,0 +1,340 @@
+/* xscreensaver, Copyright (c) 1993-2017 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/* The MIT-SHM (Shared Memory) extension is pretty tricky to use.
+   This file contains the common boiler-plate for creating a shared
+   XImage structure, and for making sure that the shared memory segments
+   get allocated and shut down cleanly.
+
+   This code currently deals only with shared XImages, not with shared Pixmaps.
+   It also doesn't use "completion events", but so far that doesn't seem to
+   be a problem (and I'm not entirely clear on when they would actually be
+   needed, anyway.)
+
+   If you don't have man pages for this extension, see
+   https://www.x.org/releases/current/doc/xextproto/shm.html
+
+   (This document seems not to ever remain available on the web in one place
+   for very long; you can search for it by the title, "MIT-SHM -- The MIT
+   Shared Memory Extension".)
+
+   To monitor the system's shared memory segments, run "ipcs -m".
+  */
+
+#include "utils.h"
+
+/* #define DEBUG */
+
+#include <errno.h>             /* for perror() */
+
+#ifdef HAVE_JWXYZ
+# include "jwxyz.h"
+#else
+# include <X11/Xutil.h>                /* for XDestroyImage() */
+#endif
+
+#include "xshm.h"
+#include "resources.h"         /* for get_string_resource() */
+#include "thread_util.h"        /* for thread_malloc() */
+
+#ifdef DEBUG
+# include <X11/Xmu/Error.h>
+#endif
+
+extern char *progname;
+
+
+/* The documentation for the XSHM extension implies that if the server
+   supports XSHM but is not the local machine, the XShm calls will return
+   False; but this turns out not to be the case.  Instead, the server
+   throws a BadAccess error.  So, we need to catch X errors around all
+   of our XSHM calls, sigh.
+ */
+
+#ifdef HAVE_XSHM_EXTENSION
+
+static Bool shm_got_x_error = False;
+XErrorHandler old_handler = 0;
+static int
+shm_ehandler (Display *dpy, XErrorEvent *error)
+{
+  shm_got_x_error = True;
+
+#ifdef DEBUG
+  fprintf (stderr, "\n%s: ignoring X error from XSHM:\n", progname);
+  XmuPrintDefaultErrorMessage (dpy, error, stderr);
+  fprintf (stderr, "\n");
+#endif
+
+  return 0;
+}
+
+
+#define CATCH_X_ERROR(DPY) do {                                \
+  XSync((DPY), False);                                         \
+  shm_got_x_error = False;                             \
+  if (old_handler != shm_ehandler)                     \
+    old_handler = XSetErrorHandler (shm_ehandler);     \
+} while(0)
+
+#define UNCATCH_X_ERROR(DPY) do {                      \
+  XSync((DPY), False);                                         \
+  if (old_handler)                                     \
+    XSetErrorHandler (old_handler);                    \
+  old_handler = 0;                                     \
+} while(0)
+
+#endif /* HAVE_XSHM_EXTENSION */
+
+
+static void
+print_error (int err)
+{
+  fprintf(stderr, "%s: %s\n", progname, strerror(err));
+}
+
+static XImage *
+create_fallback (Display *dpy, Visual *visual,
+                 unsigned int depth,
+                 int format, XShmSegmentInfo *shm_info,
+                 unsigned int width, unsigned int height)
+{
+  XImage *image = XCreateImage (dpy, visual, depth, format, 0, NULL,
+                                width, height, BitmapPad(dpy), 0);
+  shm_info->shmid = -1;
+
+  if (!image) {
+    print_error (ENOMEM);
+  } else {
+    /* Sometimes the XImage data needs to be aligned, such as for SIMD (SSE2
+       in Fireworkx), or multithreading (AnalogTV).
+     */
+    int error = thread_malloc ((void **)&image->data, dpy,
+                               image->height * image->bytes_per_line);
+    if (error) {
+      print_error (error);
+      XDestroyImage (image);
+      image = NULL;
+    } else {
+      memset (image->data, 0, image->height * image->bytes_per_line);
+    }
+  }
+
+  return image;
+}
+
+
+XImage *
+create_xshm_image (Display *dpy, Visual *visual,
+                  unsigned int depth,
+                  int format, XShmSegmentInfo *shm_info,
+                  unsigned int width, unsigned int height)
+{
+#ifndef HAVE_XSHM_EXTENSION
+
+  return create_fallback (dpy, visual, depth, format, shm_info, width, height);
+
+#else /* HAVE_XSHM_EXTENSION */
+
+  Status status;
+  XImage *image = 0;
+  if (!get_boolean_resource(dpy, "useSHM", "Boolean") ||
+      !XShmQueryExtension (dpy)) {
+    return create_fallback (dpy, visual, depth, format, shm_info,
+                            width, height);
+  }
+
+  CATCH_X_ERROR(dpy);
+  image = XShmCreateImage(dpy, visual, depth,
+                          format, NULL, shm_info, width, height);
+  UNCATCH_X_ERROR(dpy);
+  if (shm_got_x_error)
+    return create_fallback (dpy, visual, depth, format, shm_info,
+                            width, height);
+
+#ifdef DEBUG
+  fprintf(stderr, "\n%s: XShmCreateImage(... %d, %d)\n", progname,
+         width, height);
+#endif
+
+  shm_info->shmid = shmget(IPC_PRIVATE,
+                          image->bytes_per_line * image->height,
+                          IPC_CREAT | 0777);
+#ifdef DEBUG
+  fprintf(stderr, "%s: shmget(IPC_PRIVATE, %d, IPC_CREAT | 0777) ==> %d\n",
+         progname, image->bytes_per_line * image->height, shm_info->shmid);
+#endif
+
+  if (shm_info->shmid == -1)
+    {
+      char buf[1024];
+      sprintf (buf, "%s: shmget failed", progname);
+      perror(buf);
+      XDestroyImage (image);
+      image = 0;
+      XSync(dpy, False);
+    }
+  else
+    {
+      shm_info->readOnly = False;
+      image->data = shm_info->shmaddr = shmat(shm_info->shmid, 0, 0);
+
+#ifdef DEBUG
+      fprintf(stderr, "%s: shmat(%d, 0, 0) ==> %d\n", progname,
+             shm_info->shmid, (int) image->data);
+#endif
+
+      CATCH_X_ERROR(dpy);
+      status = XShmAttach(dpy, shm_info);
+      UNCATCH_X_ERROR(dpy);
+      if (shm_got_x_error)
+       status = False;
+
+      if (!status)
+       {
+         fprintf (stderr, "%s: XShmAttach failed!\n", progname);
+         XDestroyImage (image);
+         XSync(dpy, False);
+         shmdt (shm_info->shmaddr);
+         image = 0;
+       }
+#ifdef DEBUG
+      else
+        fprintf(stderr, "%s: XShmAttach(dpy, shm_info) ==> True\n", progname);
+#endif
+
+      XSync(dpy, False);
+
+      /* Delete the shared segment right now; the segment won't actually
+        go away until both the client and server have deleted it.  The
+        server will delete it as soon as the client disconnects, so we
+        should delete our side early in case of abnormal termination.
+        (And note that, in the context of xscreensaver, abnormal
+        termination is the rule rather than the exception, so this would
+        leak like a sieve if we didn't do this...)
+
+        #### Are we leaking anyway?  Perhaps because of the window of
+        opportunity between here and the XShmAttach call above, during
+        which we might be killed?  Do we need to establish a signal
+        handler for this case?
+       */
+      shmctl (shm_info->shmid, IPC_RMID, 0);
+
+#ifdef DEBUG
+      fprintf(stderr, "%s: shmctl(%d, IPC_RMID, 0)\n\n", progname,
+             shm_info->shmid);
+#endif
+    }
+
+  if (!image) {
+    return create_fallback (dpy, visual, depth, format, shm_info,
+                            width, height);
+  }
+
+  return image;
+
+#endif /* HAVE_XSHM_EXTENSION */
+}
+
+
+Bool
+put_xshm_image (Display *dpy, Drawable d, GC gc, XImage *image,
+                int src_x, int src_y, int dest_x, int dest_y,
+                unsigned int width, unsigned int height,
+                XShmSegmentInfo *shm_info)
+{
+#ifdef HAVE_XSHM_EXTENSION
+  assert (shm_info); /* Don't just s/XShmPutImage/put_xshm_image/. */
+  if (shm_info->shmid != -1) {
+    /* XShmPutImage is asynchronous; the contents of the XImage must not be
+       modified until the server has placed the pixels on the screen and the
+       client has received an XShmCompletionEvent. Breaking this rule can cause
+       tearing. That said, put_xshm_image doesn't provide a send_event
+       parameter, so we're always breaking this rule. Not that it seems to
+       matter; everything (so far) looks fine without it.
+
+       ####: Add a send_event parameter. And fake it for XPutImage.
+     */
+    return XShmPutImage (dpy, d, gc, image, src_x, src_y, dest_x, dest_y,
+                         width, height, False);
+  }
+#endif /* HAVE_XSHM_EXTENSION */
+
+  return XPutImage (dpy, d, gc, image, src_x, src_y, dest_x, dest_y,
+                    width, height);
+}
+
+
+Bool
+get_xshm_image (Display *dpy, Drawable d, XImage *image, int x, int y,
+                unsigned long plane_mask, XShmSegmentInfo *shm_info)
+{
+#ifdef HAVE_XSHM_EXTENSION
+  if (shm_info->shmid != -1) {
+    return XShmGetImage (dpy, d, image, x, y, plane_mask);
+  }
+#endif /* HAVE_XSHM_EXTENSION */
+  return XGetSubImage (dpy, d, x, y, image->width, image->height, plane_mask,
+                       image->format, image, 0, 0) != NULL;
+}
+
+
+void
+destroy_xshm_image (Display *dpy, XImage *image, XShmSegmentInfo *shm_info)
+{
+#ifdef HAVE_XSHM_EXTENSION
+  Status status;
+
+  if (shm_info->shmid == -1) {
+#endif /* HAVE_XSHM_EXTENSION */
+
+    /* Don't let XDestroyImage free image->data. */
+    thread_free (image->data);
+    image->data = NULL;
+    XDestroyImage (image);
+    return;
+
+#ifdef HAVE_XSHM_EXTENSION
+  }
+
+  CATCH_X_ERROR(dpy);
+  status = XShmDetach (dpy, shm_info);
+  UNCATCH_X_ERROR(dpy);
+  if (shm_got_x_error)
+    status = False;
+  if (!status)
+    fprintf (stderr, "%s: XShmDetach failed!\n", progname);
+# ifdef DEBUG
+  else
+    fprintf (stderr, "%s: XShmDetach(dpy, shm_info) ==> True\n", progname);
+# endif
+
+  XDestroyImage (image);
+  XSync(dpy, False);
+
+  status = shmdt (shm_info->shmaddr);
+
+  if (status != 0)
+    {
+      char buf[1024];
+      sprintf (buf, "%s: shmdt(0x%lx) failed", progname,
+               (unsigned long) shm_info->shmaddr);
+      perror(buf);
+    }
+# ifdef DEBUG
+  else
+    fprintf (stderr, "%s: shmdt(shm_info->shmaddr) ==> 0\n", progname);
+# endif
+
+  XSync(dpy, False);
+
+#endif /* HAVE_XSHM_EXTENSION */
+}
diff --git a/screenhack/xshm.h b/screenhack/xshm.h
new file mode 100644 (file)
index 0000000..6159b36
--- /dev/null
@@ -0,0 +1,54 @@
+/* xscreensaver, Copyright (c) 1993, 1994, 1995, 1996, 1997, 1998, 2001
+ *  by Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/* The MIT-SHM (Shared Memory) extension is pretty tricky to use.
+   This file contains the common boiler-plate for creating a shared
+   XImage structure, and for making sure that the shared memory segments
+   get allocated and shut down cleanly.
+ */
+
+#ifndef __XSCREENSAVER_XSHM_H__
+#define __XSCREENSAVER_XSHM_H__
+
+#ifdef HAVE_XSHM_EXTENSION
+
+# include <sys/ipc.h>
+# include <sys/shm.h>
+# include <X11/extensions/XShm.h>
+
+#else /* !HAVE_XSHM_EXTENSION */
+
+typedef struct {
+  int shmid; /* Always -1. */
+} dummy_segment_info;
+
+/* In case XShmSegmentInfo  */
+#undef XShmSegmentInfo
+#define XShmSegmentInfo dummy_segment_info
+
+#endif
+
+extern XImage *create_xshm_image (Display *dpy, Visual *visual,
+                                  unsigned int depth,
+                                  int format, XShmSegmentInfo *shm_info,
+                                  unsigned int width, unsigned int height);
+extern Bool put_xshm_image (Display *dpy, Drawable d, GC gc, XImage *image,
+                            int src_x, int src_y, int dest_x, int dest_y,
+                            unsigned int width, unsigned int height,
+                            XShmSegmentInfo *shm_info);
+extern Bool get_xshm_image (Display *dpy, Drawable d, XImage *image,
+                            int x, int y, unsigned long plane_mask,
+                            XShmSegmentInfo *shm_info);
+extern void destroy_xshm_image (Display *dpy, XImage *image,
+                                XShmSegmentInfo *shm_info);
+
+#endif /* __XSCREENSAVER_XSHM_H__ */
diff --git a/screenhack/yarandom.c b/screenhack/yarandom.c
new file mode 100644 (file)
index 0000000..0f51cb6
--- /dev/null
@@ -0,0 +1,139 @@
+/* yarandom.c -- Yet Another Random Number Generator.
+ * Copyright (c) 1997-2014 by Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/* The unportable mess that is rand(), random(), drand48() and friends led me
+   to ask Phil Karlton <karlton@netscape.com> what the Right Thing to Do was.
+   He responded with this.  It is non-cryptographically secure, reasonably
+   random (more so than anything that is in any C library), and very fast.
+
+   I don't understand how it works at all, but he says "look at Knuth,
+   Vol. 2 (original edition), page 26, Algorithm A.  In this case n=55,
+   k=24 and m=2^32."
+
+   So there you have it.
+
+   ---------------------------
+   Note: xlockmore 4.03a10 uses this very simple RNG:
+
+       if ((seed = seed % 44488 * 48271 - seed / 44488 * 3399) < 0)
+         seed += 2147483647;
+       return seed-1;
+
+   of which it says
+
+       ``Dr. Park's algorithm published in the Oct. '88 ACM  "Random Number
+         Generators: Good Ones Are Hard To Find" His version available at
+         ftp://cs.wm.edu/pub/rngs.tar Present form by many authors.''
+
+   Karlton says: ``the usual problem with that kind of RNG turns out to
+   be unexepected short cycles for some word lengths.''
+
+   Karlton's RNG is faster, since it does three adds and two stores, while the
+   xlockmore RNG does two multiplies, two divides, three adds, and one store.
+
+   Compiler optimizations make a big difference here:
+       gcc -O:     difference is 1.2x.
+       gcc -O2:    difference is 1.4x.
+       gcc -O3:    difference is 1.5x.
+       SGI cc -O:  difference is 2.4x.
+       SGI cc -O2: difference is 2.4x.
+       SGI cc -O3: difference is 5.1x.
+   Irix 6.2; Indy r5k; SGI cc version 6; gcc version 2.7.2.1.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>  /* for getpid() */
+#endif
+#include <sys/time.h> /* for gettimeofday() */
+
+#include "yarandom.h"
+# undef ya_rand_init
+
+
+/* The following 'random' numbers are taken from CRC, 18th Edition, page 622.
+   Each array element was taken from the corresponding line in the table,
+   except that a[0] was from line 100. 8s and 9s in the table were simply
+   skipped. The high order digit was taken mod 4.
+ */
+#define VectorSize 55
+static unsigned int a[VectorSize] = {
+ 035340171546, 010401501101, 022364657325, 024130436022, 002167303062, /*  5 */
+ 037570375137, 037210607110, 016272055420, 023011770546, 017143426366, /* 10 */
+ 014753657433, 021657231332, 023553406142, 004236526362, 010365611275, /* 14 */
+ 007117336710, 011051276551, 002362132524, 001011540233, 012162531646, /* 20 */
+ 007056762337, 006631245521, 014164542224, 032633236305, 023342700176, /* 25 */
+ 002433062234, 015257225043, 026762051606, 000742573230, 005366042132, /* 30 */
+ 012126416411, 000520471171, 000725646277, 020116577576, 025765742604, /* 35 */
+ 007633473735, 015674255275, 017555634041, 006503154145, 021576344247, /* 40 */
+ 014577627653, 002707523333, 034146376720, 030060227734, 013765414060, /* 45 */
+ 036072251540, 007255221037, 024364674123, 006200353166, 010126373326, /* 50 */
+ 015664104320, 016401041535, 016215305520, 033115351014, 017411670323  /* 55 */
+};
+
+static int i1, i2;
+
+unsigned int
+ya_random (void)
+{
+  register int ret = a[i1] + a[i2];
+  a[i1] = ret;
+  if (++i1 >= VectorSize) i1 = 0;
+  if (++i2 >= VectorSize) i2 = 0;
+  return ret;
+}
+
+void
+ya_rand_init(unsigned int seed)
+{
+  int i;
+  if (seed == 0)
+    {
+      struct timeval tp;
+#ifdef GETTIMEOFDAY_TWO_ARGS
+      struct timezone tzp;
+      gettimeofday(&tp, &tzp);
+#else
+      gettimeofday(&tp);
+#endif
+      /* Since the multiplications will have a larger effect on the
+         upper bits than the lower bits, after every addition in the
+         seed, perform a bitwise rotate by an odd number, resulting
+         in a better distribution of randomness throughout the bits.
+         -- Brian Carlson, 2010.
+       */
+#define ROT(X,N) (((X)<<(N)) | ((X)>>((sizeof(unsigned int)*8)-(N))))
+      seed = (999U * (unsigned int) tp.tv_sec);
+      seed = ROT (seed, 11);
+      seed += (1001 * (unsigned int) tp.tv_usec);
+      seed = ROT (seed, 7);
+      seed += (1003 * (unsigned int) getpid());
+      seed = ROT (seed, 13);
+    }
+
+  a[0] += seed;
+  for (i = 1; i < VectorSize; i++)
+    {
+      seed = seed*999;
+      seed = ROT (seed, 9);
+      seed += a[i-1]*1001;
+      seed = ROT (seed, 15);
+      a[i] += seed;
+    }
+
+  i1 = a[0] % VectorSize;
+  i2 = (i1 + 24) % VectorSize;
+}
diff --git a/screenhack/yarandom.h b/screenhack/yarandom.h
new file mode 100644 (file)
index 0000000..9a75cdf
--- /dev/null
@@ -0,0 +1,133 @@
+/* xscreensaver, Copyright (c) 1997, 1998, 2003 by Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifndef __YARANDOM_H__
+#define __YARANDOM_H__
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+
+#undef random
+#undef rand
+#undef drand48
+#undef srandom
+#undef srand
+#undef frand
+#undef sranddev
+#undef srandomdev
+#undef arc4random
+#undef arc4random_addrandom
+#undef arc4random_buf
+#undef arc4random_stir
+#undef arc4random_uniform
+#undef erand48
+#undef jrand48
+#undef lcong48
+#undef lrand48
+#undef mrand48
+#undef nrand48
+#undef seed48
+#undef srand48
+#undef rand_r
+#undef RAND_MAX
+
+#ifdef VMS
+# include "vms-gtod.h"
+#endif
+
+extern unsigned int ya_random (void);
+extern void ya_rand_init (unsigned int);
+
+#define random()   ya_random()
+#define RAND_MAX   0xFFFFFFFF
+
+/*#define srandom(i) ya_rand_init(0)*/
+
+/* Define these away to keep people from using the wrong APIs in xscreensaver.
+ */
+#define rand          __ERROR_use_random_not_rand_in_xscreensaver__
+#define drand48       __ERROR_use_frand_not_drand48_in_xscreensaver__
+#define srandom       __ERROR_do_not_call_srandom_in_xscreensaver__
+#define srand         __ERROR_do_not_call_srand_in_xscreensaver__
+#define sranddev      __ERROR_do_not_call_sranddev_in_xscreensaver__
+#define ya_rand_init  __ERROR_do_not_call_ya_rand_init_in_xscreensaver__
+#define srandomdev    __ERROR_do_not_call_srandomdev_in_xscreensaver__
+#define arc4random    __ERROR_do_not_call_arc4random_in_xscreensaver__
+#define arc4random_addrandom __ERROR_do_not_call_arc4random_in_xscreensaver__
+#define arc4random_buf       __ERROR_do_not_call_arc4random_in_xscreensaver__
+#define arc4random_stir      __ERROR_do_not_call_arc4random_in_xscreensaver__
+#define arc4random_uniform   __ERROR_do_not_call_arc4random_in_xscreensaver__
+#define erand48    __ERROR_do_not_call_erand48_in_xscreensaver__
+#define jrand48    __ERROR_do_not_call_jrand48_in_xscreensaver__
+#define lcong48    __ERROR_do_not_call_lcong48_in_xscreensaver__
+#define lrand48    __ERROR_do_not_call_lrand48_in_xscreensaver__
+#define mrand48    __ERROR_do_not_call_mrand48_in_xscreensaver__
+#define nrand48    __ERROR_do_not_call_nrand48_in_xscreensaver__
+#define seed48     __ERROR_do_not_call_seed48_in_xscreensaver__
+#define srand48    __ERROR_do_not_call_srand48_in_xscreensaver__
+#define rand_r     __ERROR_do_not_call_rand_r_in_xscreensaver__
+
+
+#if defined (__GNUC__) && (__GNUC__ >= 2)
+ /* Implement frand using GCC's statement-expression extension. */
+
+# define frand(f)                                                      \
+  __extension__                                                                \
+  ({ double tmp = ((((double) random()) * ((double) (f))) /            \
+                  ((double) ((unsigned int)~0)));                      \
+     tmp < 0 ? (-tmp) : tmp; })
+
+#else /* not GCC2 - implement frand using a global variable.*/
+
+static double _frand_tmp_;
+# define frand(f)                                                      \
+  (_frand_tmp_ = ((((double) random()) * ((double) (f))) /             \
+                 ((double) ((unsigned int)~0))),                       \
+   _frand_tmp_ < 0 ? (-_frand_tmp_) : _frand_tmp_)
+
+#endif /* not GCC2 */
+
+/* Compatibility with the xlockmore RNG API
+   (note that the xlockmore hacks never expect negative numbers.)
+ */
+#define LRAND()         ((long) (random() & 0x7fffffff))
+
+/* The first NRAND(n) is much faster, when uint64_t is available to allow it.
+ * Especially on ARM and other processors without built-in division.
+ *
+ * n must be greater than zero.
+ *
+ * The division by RAND_MAX+1 should optimize down to a bit shift.
+ *
+ * Although the result here is never negative, this needs to return signed
+ * integers: A binary operator in C requires operands have the same type, and
+ * if one side is signed and the other is unsigned, but they're both the same
+ * size, then the signed operand is cast to unsigned, and the result is
+ * similarly unsigned. This can potentially cause problems when idioms like
+ * "NRAND(n) * RANDSIGN()" (where RANDSIGN() returns either -1 or 1) is used
+ * to get random numbers from the range (-n,n).
+ */
+#ifdef HAVE_INTTYPES_H
+# define NRAND(n)       ((int32_t) ((uint64_t) random() * (uint32_t) (n) / \
+                                    ((uint64_t) RAND_MAX + 1)))
+#else
+# define NRAND(n)       ((int) (LRAND() % (n)))
+#endif
+
+#define MAXRAND         (2147483648.0) /* unsigned 1<<31 as a float */
+#define SRAND(n)        /* already seeded by screenhack.c */
+
+#endif /* __YARANDOM_H__ */