From: Aaron Taylor Date: Wed, 10 Mar 2021 09:38:09 +0000 (-0800) Subject: Adding files related to the `screenhack` API for X11 hacks. X-Git-Url: http://git.subgeniuskitty.com/screensavers/.git/commitdiff_plain/3144ee8aa82b5c910158f07c2aeb0abdd64eaf9a Adding files related to the `screenhack` API for X11 hacks. All files imported without modification from `xscreensaver-5.45.tar.gz`. See file headers for license details. --- 3144ee8aa82b5c910158f07c2aeb0abdd64eaf9a diff --git a/screenhack/aligned_malloc.c b/screenhack/aligned_malloc.c new file mode 100644 index 0000000..b345ad1 --- /dev/null +++ b/screenhack/aligned_malloc.c @@ -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 + +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 +#include + +#include +#include + +/* 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 index 0000000..b3f43c9 --- /dev/null +++ b/screenhack/aligned_malloc.h @@ -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 + +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 + + /* 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 index 0000000..5b18e85 --- /dev/null +++ b/screenhack/alpha.c @@ -0,0 +1,215 @@ +/* xscreensaver, Copyright (c) 1992, 1995, 1996, 1997, 2006 + * Jamie Zawinski + * + * 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 + +#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< 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 index 0000000..4ff9903 --- /dev/null +++ b/screenhack/alpha.h @@ -0,0 +1,22 @@ +/* xscreensaver, Copyright (c) 1992, 1995, 1996, 1997 + * Jamie Zawinski + * + * 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 index 0000000..01f8fc6 --- /dev/null +++ b/screenhack/colors.c @@ -0,0 +1,747 @@ +/* xscreensaver, Copyright (c) 1997-2018 Jamie Zawinski + * + * 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 index 0000000..526a38a --- /dev/null +++ b/screenhack/colors.h @@ -0,0 +1,147 @@ +/* xscreensaver, Copyright (c) 1992-2013 Jamie Zawinski + * + * 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 index 0000000..7e1cbc9 --- /dev/null +++ b/screenhack/erase.c @@ -0,0 +1,811 @@ +/* erase.c: Erase the screen in various more or less interesting ways. + * Copyright (c) 1997-2008 Jamie Zawinski + * + * 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 : + * 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 /* 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 + */ +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 */ +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 index 0000000..1e720f9 --- /dev/null +++ b/screenhack/erase.h @@ -0,0 +1,21 @@ +/* erase.c: Erase the screen in various more or less interesting ways. + * Copyright (c) 1997-2001, 2006 Jamie Zawinski + * + * 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 index 0000000..7a2ce2b --- /dev/null +++ b/screenhack/fade.c @@ -0,0 +1,962 @@ +/* xscreensaver, Copyright (c) 1992-2011 Jamie Zawinski + * + * 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 /* 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; +} + + + +/* SGI Gamma fading */ + +#ifdef HAVE_SGI_VC_EXTENSION + +# include + +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 */ + + + +/* XFree86 4.x+ Gamma fading */ + +#ifdef HAVE_XF86VMODE_GAMMA + +#include + +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 index 0000000..bedae93 --- /dev/null +++ b/screenhack/fade.h @@ -0,0 +1,21 @@ +/* xscreensaver, Copyright (c) 1992-1997, 2003 Jamie Zawinski + * + * 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 index 0000000..d726378 --- /dev/null +++ b/screenhack/font-retry.c @@ -0,0 +1,226 @@ +/* xscreensaver, Copyright (c) 2018-2020 by Jamie Zawinski + * + * 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 = ""; + 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 index 0000000..83ba0e0 --- /dev/null +++ b/screenhack/font-retry.h @@ -0,0 +1,24 @@ +/* xscreensaver, Copyright (c) 2018 by Jamie Zawinski + * + * 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 index 0000000..176084a --- /dev/null +++ b/screenhack/fps.c @@ -0,0 +1,295 @@ +/* fps, Copyright (c) 2001-2019 Jamie Zawinski + * 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 +#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 index 0000000..d3832c1 --- /dev/null +++ b/screenhack/fps.h @@ -0,0 +1,35 @@ +/* fps, Copyright (c) 2001-2011 Jamie Zawinski + * 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 index 0000000..552de9c --- /dev/null +++ b/screenhack/fpsI.h @@ -0,0 +1,40 @@ +/* fps, Copyright (c) 2001-2014 Jamie Zawinski + * 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 index 0000000..77fe3c9 --- /dev/null +++ b/screenhack/grabscreen.c @@ -0,0 +1,937 @@ +/* xscreensaver, Copyright (c) 1992-2016 Jamie Zawinski + * + * 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 +#include + +#ifdef HAVE_XMU +# ifndef VMS +# include +# else /* VMS */ +# include +# 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 + 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); +} + + + +/* 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 index 0000000..a17e671 --- /dev/null +++ b/screenhack/grabscreen.h @@ -0,0 +1,109 @@ +/* xscreensaver, Copyright (c) 1992-2014 Jamie Zawinski + * + * 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 index 0000000..cf1cc8d --- /dev/null +++ b/screenhack/hsv.c @@ -0,0 +1,81 @@ +/* xscreensaver, Copyright (c) 1992, 1997 Jamie Zawinski + * + * 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 index 0000000..e0fdfb0 --- /dev/null +++ b/screenhack/hsv.h @@ -0,0 +1,27 @@ +/* xscreensaver, Copyright (c) 1992, 1997 Jamie Zawinski + * + * 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 index 0000000..997e628 --- /dev/null +++ b/screenhack/minixpm.c @@ -0,0 +1,251 @@ +/* xscreensaver, Copyright (c) 2001-2014 Jamie Zawinski + * + * 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 +#include +#include + +#ifdef HAVE_JWXYZ +# include "jwxyz.h" +#else /* real Xlib */ +# include +# include +#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 index 0000000..f027894 --- /dev/null +++ b/screenhack/minixpm.h @@ -0,0 +1,28 @@ +/* xscreensaver, Copyright (c) 2001-2006 Jamie Zawinski + * + * 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 index 0000000..0f42dcd --- /dev/null +++ b/screenhack/overlay.c @@ -0,0 +1,158 @@ +/* xscreensaver, Copyright (c) 1997, 2001, 2004 Jamie Zawinski + * + * 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 +#include + +#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 index 0000000..9cce3d8 --- /dev/null +++ b/screenhack/pow2.c @@ -0,0 +1,51 @@ +/* pow2, Copyright (c) 2016 Dave Odell + * + * 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 + +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 index 0000000..3927e84 --- /dev/null +++ b/screenhack/pow2.h @@ -0,0 +1,20 @@ +/* pow2, Copyright (c) 2016 Dave Odell + * + * 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 + +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 index 0000000..94bbf23 --- /dev/null +++ b/screenhack/queue.h @@ -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 index 0000000..7b54adc --- /dev/null +++ b/screenhack/resources.c @@ -0,0 +1,296 @@ +/* xscreensaver, Copyright (c) 1992-2014 Jamie Zawinski + * + * 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 + +/* 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 index 0000000..cbfc8d1 --- /dev/null +++ b/screenhack/resources.h @@ -0,0 +1,41 @@ +/* xscreensaver, Copyright (c) 1992-2014 Jamie Zawinski + * + * 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 index 0000000..5f11049 --- /dev/null +++ b/screenhack/screenhack.c @@ -0,0 +1,1008 @@ +/* xscreensaver, Copyright (c) 1992-2020 Jamie Zawinski + * + * 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 +#include +#include +#include +#include +#include +#include + +#ifdef __sgi +# include /* for SgiUseSchemes() */ +#endif /* __sgi */ + +#ifdef HAVE_XMU +# ifndef VMS +# include +# else /* VMS */ +# include +# 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); + } +} + + +/* 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, " "); + 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 index 0000000..16e629b --- /dev/null +++ b/screenhack/screenhack.h @@ -0,0 +1,68 @@ +/* xscreensaver, Copyright (c) 1992-2018 Jamie Zawinski + * + * 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 index 0000000..36b0635 --- /dev/null +++ b/screenhack/screenhackI.h @@ -0,0 +1,163 @@ +/* xscreensaver, Copyright (c) 1992-2018 Jamie Zawinski + * + * 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 +#include +#include +#include + +#ifdef __hpux + /* Which of the ten billion standards does values.h belong to? + What systems always have it? */ +# include +#endif + +#ifdef HAVE_JWXYZ +# include "jwxyz.h" +# include /* X11/Xos.h brings this in. */ +/* From utils/visual.c. */ +# define DEFAULT_VISUAL -1 +# define GL_VISUAL -6 +#else /* real X11 */ +# include +# include +# include +# include +#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 index 0000000..1a73ea6 --- /dev/null +++ b/screenhack/spline.c @@ -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 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 index 0000000..a5a366c --- /dev/null +++ b/screenhack/spline.h @@ -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 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 index 0000000..636b308 --- /dev/null +++ b/screenhack/textclient.c @@ -0,0 +1,747 @@ +/* xscreensaver, Copyright (c) 2012-2020 Jamie Zawinski + * + * 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 +# include +# include +#endif + +#include + +#include +#include + +#ifdef HAVE_UNISTD_H +# include +# include /* for O_RDWR */ +#endif + +#ifdef HAVE_FORKPTY +# include +# ifdef HAVE_PTY_H +# include +# endif +# ifdef HAVE_UTIL_H +# include +# endif +# ifdef HAVE_SYS_TERMIOS_H +# include +# 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 index 0000000..5be8079 --- /dev/null +++ b/screenhack/textclient.h @@ -0,0 +1,37 @@ +/* xscreensaver, Copyright (c) 2012-2016 Jamie Zawinski + * + * 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 index 0000000..71c8633 --- /dev/null +++ b/screenhack/thread_util.c @@ -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 + +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 +#include +#include +#include +#include /* Only used by thread_memory_alignment(). */ +#include + +#if HAVE_ALLOCA_H +# include +#endif + +#if HAVE_UNISTD_H +# include +#endif + +#if defined __MACH__ && defined __APPLE__ /* OS X, iOS */ +# include +# include +#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://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 /* For TARGET_OS_IPHONE. */ +# ifdef TARGET_OS_IPHONE +# define _CACHE_LINE_SIZE 64 +# endif +# endif + +# if defined __FreeBSD__ && !defined _CACHE_LINE_SIZE +# include +# 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 +# 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 + . 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 + +/* FreeBSD: sys/sysctl.h needs sys/types.h, but the one doesn't bring the + other in automatically. */ +# include +# include + +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 + +#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 index 0000000..6dff8de --- /dev/null +++ b/screenhack/thread_util.h @@ -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 + +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 + +#if HAVE_UNISTD_H +/* For _POSIX_THREADS. */ +# include +#endif + +#if defined HAVE_JWXYZ +# include "jwxyz.h" +#else +# include +#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, , Constants for + Options and Option Groups + http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/unistd.h.html#tag_13_77_03_02 +*/ + +# include + +/* Most PThread synchronization functions only fail when they are misused. */ +# if defined NDEBUG +# define PTHREAD_VERIFY(expr) (void)(expr) +# else +# include +# 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 + . */ + 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 index 0000000..f065729 --- /dev/null +++ b/screenhack/usleep.c @@ -0,0 +1,64 @@ +/* xscreensaver, Copyright (c) 1992, 1996, 1997, 2003 + * Jamie Zawinski + * + * 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 +#endif + +#ifdef HAVE_UNISTD_H +# include +#endif + +#if defined(VMS) +# include +# include +# include +#elif defined(HAVE_SELECT) +# include /* 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 index 0000000..f32a9c9 --- /dev/null +++ b/screenhack/usleep.h @@ -0,0 +1,24 @@ +/* xscreensaver, Copyright (c) 1992, 1996 Jamie Zawinski + * + * 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 +#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 index 0000000..b445f30 --- /dev/null +++ b/screenhack/utf8wc.c @@ -0,0 +1,928 @@ +/* xscreensaver, Copyright (c) 2014-2016 Jamie Zawinski + * + * 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 +#include +#include + +#ifdef HAVE_JWXYZ +# include "jwxyz.h" +#else /* !HAVE_JWXYZ */ +# include +#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') /*   */ + 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 index 0000000..d91567c --- /dev/null +++ b/screenhack/utf8wc.h @@ -0,0 +1,47 @@ +/* xscreensaver, Copyright (c) 2014-2015 Jamie Zawinski + * + * 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 index 0000000..09b4da8 --- /dev/null +++ b/screenhack/utils.h @@ -0,0 +1,27 @@ +/* xscreensaver, Copyright (c) 1997-2014 Jamie Zawinski + * + * 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 +#include +#include +#include + +#ifdef HAVE_JWXYZ +# include "jwxyz.h" +#else /* real X11 */ +# include +# include +# include +#endif /* !HAVE_JWXYZ */ diff --git a/screenhack/version.h b/screenhack/version.h new file mode 100644 index 0000000..054dd99 --- /dev/null +++ b/screenhack/version.h @@ -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 index 0000000..c6764ba --- /dev/null +++ b/screenhack/visual.c @@ -0,0 +1,555 @@ +/* xscreensaver, Copyright (c) 1993-2017 by Jamie Zawinski + * + * 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 +#ifndef HAVE_ANDROID +#include +#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 . + */ +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 index 0000000..18ff7a6 --- /dev/null +++ b/screenhack/visual.h @@ -0,0 +1,36 @@ +/* xscreensaver, Copyright (c) 1993-2014 by Jamie Zawinski + * + * 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 index 0000000..65097b8 --- /dev/null +++ b/screenhack/vroot.h @@ -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 + * + * 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 , 9/7/90 + * - replaced all NULL's with properly cast 0's, 5/6/91 + * - free children list (suggested by Mark Martin ), 5/16/91 + * - include X11/Xlib.h and support RootWindowOfScreen, too 9/17/91 + * + * Jamie Zawinski , 28-Apr-1997 + * - use ANSI C + * + * Jamie Zawinski , 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 , 14-Aug-2004 + * - changes to get gcc to stop whining about "type punning". + * + * Jamie Zawinski , 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 +#include +#include + +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 index 0000000..d62183e --- /dev/null +++ b/screenhack/xdbe.c @@ -0,0 +1,75 @@ +/* xscreensaver, Copyright (c) 1998, 1999, 2006 + * by Jamie Zawinski + * + * 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 +#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 index 0000000..26f2de8 --- /dev/null +++ b/screenhack/xdbe.h @@ -0,0 +1,27 @@ +/* xscreensaver, Copyright (c) 1998, 1999 by Jamie Zawinski + * + * 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 + +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 index 0000000..9245752 --- /dev/null +++ b/screenhack/xft.c @@ -0,0 +1,364 @@ +/* xscreensaver, Copyright (c) 2014-2018 Jamie Zawinski + * + * 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 index 0000000..385e28b --- /dev/null +++ b/screenhack/xft.h @@ -0,0 +1,167 @@ +/* xscreensaver, Copyright (c) 2014-2015 Jamie Zawinski + * + * 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 + +# else /* !HAVE_XFT -- the rest of the file */ + +# ifdef HAVE_COCOA +# include "jwxyz.h" +#elif defined(HAVE_ANDROID) +# include "jwxyz.h" +# else +# include +# 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 index 0000000..1bcbc2c --- /dev/null +++ b/screenhack/ximage-loader.c @@ -0,0 +1,706 @@ +/* ximage-loader.c --- converts image files or data to XImages or Pixmap. + * xscreensaver, Copyright (c) 1998-2020 Jamie Zawinski + * + * 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 +#include +#include + +#ifdef HAVE_JWXYZ +# include "jwxyz.h" +#else +# include +# include +#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 +# ifdef HAVE_GTK2 +# include +# else /* !HAVE_GTK2 */ +# include +# endif /* !HAVE_GTK2 */ + +# if (__GNUC__ >= 4) +# pragma GCC diagnostic pop +# endif + +#endif /* HAVE_GDK_PIXBUF */ + +#ifdef HAVE_LIBPNG +# include +#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 index 0000000..bed163f --- /dev/null +++ b/screenhack/ximage-loader.h @@ -0,0 +1,37 @@ +/* ximage-loader.h --- converts XPM data to Pixmaps. + * xscreensaver, Copyright (c) 1998-2018 Jamie Zawinski + * + * 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 index 0000000..2f273d4 --- /dev/null +++ b/screenhack/xmu.c @@ -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 /* for xEvent (used by Xlibint.h) */ +# include /* for _XExtension */ +#else /* VMS */ +# include +#endif /* VMS */ +#include /* 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 index 0000000..48084f7 --- /dev/null +++ b/screenhack/xmu.h @@ -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 +#include + +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 index 0000000..3c99623 --- /dev/null +++ b/screenhack/xshm.c @@ -0,0 +1,340 @@ +/* xscreensaver, Copyright (c) 1993-2017 Jamie Zawinski + * + * 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 /* for perror() */ + +#ifdef HAVE_JWXYZ +# include "jwxyz.h" +#else +# include /* for XDestroyImage() */ +#endif + +#include "xshm.h" +#include "resources.h" /* for get_string_resource() */ +#include "thread_util.h" /* for thread_malloc() */ + +#ifdef DEBUG +# include +#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 index 0000000..6159b36 --- /dev/null +++ b/screenhack/xshm.h @@ -0,0 +1,54 @@ +/* xscreensaver, Copyright (c) 1993, 1994, 1995, 1996, 1997, 1998, 2001 + * by Jamie Zawinski + * + * 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 +# include +# include + +#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 index 0000000..0f51cb6 --- /dev/null +++ b/screenhack/yarandom.c @@ -0,0 +1,139 @@ +/* yarandom.c -- Yet Another Random Number Generator. + * Copyright (c) 1997-2014 by Jamie Zawinski + * + * 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 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 /* for getpid() */ +#endif +#include /* 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 index 0000000..9a75cdf --- /dev/null +++ b/screenhack/yarandom.h @@ -0,0 +1,133 @@ +/* xscreensaver, Copyright (c) 1997, 1998, 2003 by Jamie Zawinski + * + * 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 +#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__ */