/* -*- Mode: C; tab-width: 4 -*- */
/* eyes --- follow the bouncing Grelb */

#if 0
static const char sccsid[] = "@(#)eyes.c	5.00 2000/11/01 xlockmore";

#endif

/*-
 * Copyright 1996 by Ron Hitchens <ron AT idiom.com>
 *
 * Adapted from the ubiquitous xeyes demo supplied by MIT with the
 * X Window System.
 * That code is Copyright 1991 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.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 *
 * Revision History:
 * 01-Nov-2000: Allocation checks
 * 04-Sep-1997: Added interactive frobbing with mouse (copied from julia.c)
 * 10-May-1997: Compatible with xscreensaver
 * 18-Mar-1996: Changes for new hook calling conventions.  Keep per-screen
 *              state information.  Remove global accesses.
 * 21-Feb-1996: Recoded to keep an off-screen image for each pair of eyes,
 *              and to only paint the changed parts to the screen.
 *              Allow the Grelb to enter from either side.
 * 18-Feb-1996: Got the code into mostly working condition.
 * 15-Feb-1996: Had a brainwave, started hacking the xeyes code.
 *
 * Put "random rotations and horizontal shifts in.
 * At first try make the shifts "random" with a weighting scheme favouring
 * shifts towards the most empty region.
 */

#ifdef STANDALONE
#define MODE_eyes
#define DEFAULTS "*delay: 20000 \n" \
	"*count: -8 \n" \
	"*cycles: 5 \n" \
	"*ncolors: 200 \n" \
	"*bitmap: \n" \

# define reshape_eyes 0
# define eyes_handle_event 0
# include "xlockmore.h"		/* in xscreensaver distribution */
#else /* STANDALONE */
# include "xlock.h"		/* in xlockmore distribution */
# include "iostuff.h"
#endif /* STANDALONE */

#ifdef MODE_eyes

#define DEF_TRACKMOUSE  "False"

static Bool trackmouse;

static XrmOptionDescRec opts[] =
{
	{(char *) "-trackmouse", (char *) ".eyes.trackmouse", XrmoptionNoArg, (caddr_t) "on"},
	{(char *) "+trackmouse", (char *) ".eyes.trackmouse", XrmoptionNoArg, (caddr_t) "off"}
};

static argtype vars[] =
{
	{(void *) & trackmouse, (char *) "trackmouse", (char *) "TrackMouse", (char *) DEF_TRACKMOUSE, t_Bool}
};

static OptionStruct desc[] =
{
	{(char *) "-/+trackmouse", (char *) "turn on/off the tracking of the mouse"}
};

ENTRYPOINT ModeSpecOpt eyes_opts =
{sizeof opts / sizeof opts[0], opts, sizeof vars / sizeof vars[0], vars, desc};

#ifdef USE_MODULES
ModStruct   eyes_description =
{"eyes", "init_eyes", "draw_eyes", "release_eyes",
 "refresh_eyes", "init_eyes", "free_eyes", &eyes_opts,
 20000, -8, 5, 1, 64, 1.0, "",
 "Shows eyes following a bouncing grelb", 0, NULL};

#endif

/* definitions for the xlock version of xeyes */
#define MAX_EYES		200	/* limit or uses too much memory */
#define MIN_EYE_SIZE		50	/* smallest size eyes can be */
#define FLY_ICON_SIZE		5	/* the size of a fly when iconic */
#define FLY_MAX_SPEED		10	/* max (slowest) delay between steps */
#define FLY_SIDE_LEFT		0	/* enter and leave by left side */
#define FLY_SIDE_RIGHT		1	/* enter and leave by right side */
#define LIFE_MIN		100	/* shortest life, cycles  */
#define LIFE_RANGE		1000	/* range of possible lifetimes */
#define MAX_CYCLES		10	/* max value of cycles */
#define FRICTION		24	/* affects bounciness */

/* definitions from the original MIT xeyes code */
#define NUM_EYES		2
#define EYE_X(n)		((n) * 2.0)
#define EYE_Y(n)		(0.0)
#define EYE_OFFSET		(0.1)	/* padding between eyes */
#define EYE_THICK		(0.175)		/* thickness of eye rim */
#define BALL_WIDTH		(0.3)
#define BALL_PAD		(0.05)
#define EYE_WIDTH		(2.0 - (EYE_THICK + EYE_OFFSET) * 2)
#define EYE_HEIGHT		EYE_WIDTH
#define EYE_HWIDTH		(EYE_WIDTH / 2.0)
#define EYE_HHEIGHT		(EYE_HEIGHT / 2.0)
#define BALL_HEIGHT		BALL_WIDTH
#define BALL_DIST		((EYE_WIDTH - BALL_WIDTH) / 2.0 - BALL_PAD)
#define W_MIN_X			(-1.0 + EYE_OFFSET)
#define W_MAX_X			(3.0 - EYE_OFFSET)
#define W_MIN_Y			(-1.0 + EYE_OFFSET)
#define W_MAX_Y			(1.0 - EYE_OFFSET)

/* ---------------------------------------------------------------------- */

/* definitions of matrix math code used by xeyes */

#define TPointEqual(a, b)	((a).x == (b).x && (a).y == (b).y)
#define XPointEqual(a, b)	((a).x == (b).x && (a).y == (b).y)

typedef struct _transform {
	double      mx, bx;
	double      my, by;
} Transform;

typedef struct _TPoint {
	double      x, y;
} TPoint;

#define Xx(x,y,t)		((int)((t)->mx * (x) + (t)->bx + 0.5))
#define Xy(x,y,t)		((int)((t)->my * (y) + (t)->by + 0.5))
#define Xwidth(w,h,t)		((int)((t)->mx * (w) + 0.5))
#define Xheight(w,h,t)		((int)((t)->my * (h) + 0.5))
#define Tx(x,y,t)		((((double) (x)) - (t)->bx) / (t)->mx)
#define Ty(x,y,t)		((((double) (y)) - (t)->by) / (t)->my)
#define Twidth(w,h,t)		(((double) (w)) / (t)->mx)
#define Theight(w,h,t)		(((double) (h)) / (t)->my)

/* ---------------------------------------------------------------------- */

/* aliases for vars defined in the bitmap file */
#define FLY_WIDTH		image_width
#define FLY_HEIGHT		image_height
#define FLY_BITS		image_bits

#include "eyes.xbm"
#ifdef XBM_GRELB
#include "eyes2.xbm"
#define FLY2_WIDTH   image2_width
#define FLY2_HEIGHT  image2_height
#define FLY2_BITS    image2_bits
#endif

typedef struct {		/* info about a "fly" */
	int         x, y;
	int         oldx, oldy;
	int         width, height;
	int         vx, vy;
	int         side;
	unsigned long pixel;
	int         zero_y;
} Fly;

typedef struct {		/* info about a pair of eyes */
	int         x, y;
	int         width;
	int         height;
	int         rectw, recth;
	int         painted;
	unsigned long time_to_die;
	unsigned long eyelid_pixel, eyeball_pixel, pupil_pixel;
	Pixmap      pixmap;
	XRectangle  bbox;
	Transform   transform;
	TPoint      pupil[2];
	TPoint      last_pupil[2];
} Eyes;

typedef struct {		/* per-screen info */
	Pixmap      flypix;
	int         flywidth, flyheight;
#ifdef XBM_GRELB
	Pixmap      fly2pix;
	int         fly2width, fly2height;
#endif
	int         graphics_format;
	GC          eyeGC;
	GC          flyGC;
	int         num_eyes;
	Eyes       *eyes;
	Fly         fly;
	Cursor      cursor;
	unsigned long time;
} EyeScrInfo;

/* ---------------------------------------------------------------------- */

static EyeScrInfo *eye_info = (EyeScrInfo *) NULL;

/* ---------------------------------------------------------------------- */

/*-
 *    Fill an arc, using a tranformation matrix.  Lifted from xeyes.
 *      The code to return the bounding box is a local addition.
 */

static void
TFillArc(register Display * dpy, Drawable d, GC gc, Transform * t, double x, double y, double width, double height, int angle1, int angle2, XRectangle * rect)
{
	int         xx, xy, xw, xh;

	xx = Xx(x, y, t);
	xy = Xy(x, y, t);
	xw = Xwidth(width, height, t);
	xh = Xheight(width, height, t);
	if (xw < 0) {
		xx += xw;
		xw = -xw;
	}
	if (xh < 0) {
		xy += xh;
		xh = -xh;
	}
	XFillArc(dpy, d, gc, xx, xy, xw, xh, angle1, angle2);

	if (rect != NULL) {
		rect->x = xx;
		rect->y = xy;
		rect->width = xw;
		rect->height = xh;
	}
}


/*-
 *    Set a tranform matrix from the given arguments.  Lifted from xeyes.
 */

static void
SetTransform(Transform * t, int xx1, int xx2, int xy1, int xy2, double tx1, double tx2, double ty1, double ty2)
{
	t->mx = ((double) xx2 - xx1) / (tx2 - tx1);
	t->bx = ((double) xx1) - t->mx * tx1;
	t->my = ((double) xy2 - xy1) / (ty2 - ty1);
	t->by = ((double) xy1) - t->my * ty1;
}

/* ---------------------------------------------------------------------- */

/*-
 *    Given two rectangles, return the rectangle which encloses both.
 *      Used to clculate "damage" when the pupil moves, to minimize the
 *      number of pixels which must be copied out to the screen.
 */

static void
join_rects(XRectangle * r1, XRectangle * r2, XRectangle * ret)
{
	XRectangle  tmp;
	int         n1, n2;

	/* find min x and min y */
	tmp.x = (r1->x <= r2->x) ? r1->x : r2->x;
	tmp.y = (r1->y <= r2->y) ? r1->y : r2->y;
	/* find max x, plus one (just past the right side) */
	n1 = r1->x + r1->width;
	n2 = r2->x + r2->width;
	/* compute width, relative to min x (left side) */
	tmp.width = ((n1 > n2) ? n1 : n2) - tmp.x;
	/* same for y */
	n1 = r1->y + r1->height;
	n2 = r2->y + r2->height;
	tmp.height = ((n1 > n2) ? n1 : n2) - tmp.y;
	*ret = tmp;		/* copy out result rectangle */
}

/* ---------------------------------------------------------------------- */

/*-
 *    Do the math to figure out where the pupil should be drawn.
 *      This code lifted intact from the xeyes widget.
 */

#if defined( SVR4 ) || defined( SYSV ) && defined( SYSV386 )
extern double hypot(double, double);

#endif

static  void
computePupil(int num, TPoint mouse, TPoint *ret)
{
	double      cx, cy;
	double      dist;
	double      angle;
	double      x, y;
	double      h;
	double      dx, dy;
	double      cosa, sina;

	dx = mouse.x - EYE_X(num);
	dy = mouse.y - EYE_Y(num);
	if (dx == 0 && dy == 0) {
		cx = EYE_X(num);
		cy = EYE_Y(num);
	} else {
		/* Avoid atan2: DOMAIN error message */
		if (dx == 0.0 && dy == 0.0)
			angle = 0.0;
		else
			angle = atan2((double) dy, (double) dx);
		cosa = cos(angle);
		sina = sin(angle);
		h = hypot(EYE_HHEIGHT * cosa, EYE_HWIDTH * sina);
		x = (EYE_HWIDTH * EYE_HHEIGHT) * cosa / h;
		y = (EYE_HWIDTH * EYE_HHEIGHT) * sina / h;
		dist = BALL_DIST * hypot(x, y);
		if (dist > hypot((double) dx, (double) dy)) {
			cx = dx + EYE_X(num);
			cy = dy + EYE_Y(num);
		} else {
			cx = dist * cosa + EYE_X(num);
			cy = dist * sina + EYE_Y(num);
		}
	}
	(*ret).x = cx;
	(*ret).y = cy;
}

/* ---------------------------------------------------------------------- */

/*-
 *    Create the eye image, using the data in the structure pointed
 *      to by "e", in the Drawable "d".  The "full" flag indicates
 *      whether to create the full eye image, or to just paint the
 *      pupil in a new position.
 */

static void
make_eye(ModeInfo * mi, Drawable d, Eyes * e, int n, int full)
{
	EyeScrInfo *ep = &eye_info[MI_SCREEN(mi)];
	Display    *display = MI_DISPLAY(mi);
	GC          gc = ep->eyeGC;
	XRectangle *bbox = &e->bbox;
	XRectangle  tmp1, tmp2;

	if (full) {
		/* draw the outer (eyelid) oval */
		XSetForeground(display, gc, e->eyelid_pixel);
		TFillArc(display, d, gc, &e->transform,
			 EYE_X(n) - EYE_HWIDTH - EYE_THICK,
			 EYE_Y(n) - EYE_HHEIGHT - EYE_THICK,
			 EYE_WIDTH + EYE_THICK * 2.0,
			 EYE_HEIGHT + EYE_THICK * 2.0,
			 90 * 64, 360 * 64, &tmp1);

		/* draw the inner (eyeball) oval */
		XSetForeground(display, gc, e->eyeball_pixel);
		TFillArc(display, d, gc, &e->transform,
			 EYE_X(n) - EYE_HWIDTH, EYE_Y(n) - EYE_HHEIGHT,
			 EYE_WIDTH, EYE_HEIGHT, 90 * 64, 360 * 64, &tmp2);

		join_rects(&tmp1, &tmp2, &tmp1);

		/* draw the pupil on top of the eyeball oval */
		XSetForeground(display, gc, e->pupil_pixel);
		TFillArc(display, d, gc, &e->transform,
			 e->pupil[n].x - BALL_WIDTH / 2.0,
			 e->pupil[n].y - BALL_HEIGHT / 2.0,
			 BALL_WIDTH, BALL_HEIGHT, 90 * 64, 360 * 64, &tmp2);

		join_rects(&tmp1, &tmp2, bbox);
	} else {
		/* undraw the pupil */
		XSetForeground(display, gc, e->eyeball_pixel);
		TFillArc(display, d, gc, &e->transform,
			 e->last_pupil[n].x - BALL_WIDTH / 2.0,
			 e->last_pupil[n].y - BALL_HEIGHT / 2.0,
			 BALL_WIDTH, BALL_HEIGHT, 90 * 64, 360 * 64, &tmp1);

		/* draw the pupil on top of the eyeball oval */
		XSetForeground(display, gc, e->pupil_pixel);
		TFillArc(display, d, gc, &e->transform,
			 e->pupil[n].x - BALL_WIDTH / 2.0,
			 e->pupil[n].y - BALL_HEIGHT / 2.0,
			 BALL_WIDTH, BALL_HEIGHT, 90 * 64, 360 * 64, &tmp2);

		join_rects(&tmp1, &tmp2, bbox);
	}
}

/* ---------------------------------------------------------------------- */

/*-
 *    Check to see if the flyer touches this pair of eyes.
 */

static      Bool
fly_touches(Fly * f, Eyes * e, int old)
{
	int         x = (old) ? f->oldx : f->x;
	int         y = (old) ? f->oldy : f->y;

	if ((x + f->width) <= e->x)
		return (False);
	if (x >= (e->x + e->width))
		return (False);
	if ((y + f->height) <= e->y)
		return (False);
	if (y >= (e->y + e->height))
		return (False);
	return (True);
}

static      Bool
fly_touches_eye(Fly * f, Eyes * e)
{
	if (fly_touches(f, e, True) || fly_touches(f, e, False)) {
		return (True);
	}
	return (False);
}

/*-
 *    Check to see if two pairs of eyes overlap.
 */

static      Bool
eyes_overlap(Eyes * e1, Eyes * e2)
{
	if ((e1->x + e1->width) < e2->x)
		return (False);
	if (e1->x >= (e2->x + e2->width))
		return (False);
	if ((e1->y + e1->height) < e2->y)
		return (False);
	if (e1->y >= (e2->y + e2->height))
		return (False);
	return (True);
}

/* ---------------------------------------------------------------------- */

/*-
 *    Initialize the flyer.  Called when the window changes, and
 *      whenever she bounces off the screen.
 *      In the first version, the eyes followed a "fly", which was
 *      just a flickering spot that moved at random.  That didn't
 *      work so well.  It was replaced with a bouncing gelb, but the
 *      name "fly" has yet to be purged.
 */

static void
init_fly(ModeInfo * mi, Fly * f)
{
	EyeScrInfo *ep = &eye_info[MI_SCREEN(mi)];
	int         win_width = MI_WIDTH(mi);
	int         win_height = MI_HEIGHT(mi);

	(void) memset((char *) f, 0, sizeof (Fly));	/* clear everything to zero */

	f->side = FLY_SIDE_LEFT;
	if (!MI_IS_ICONIC(mi))
	{
		f->width = ep->flywidth;
		f->height = ep->flyheight;
		f->vx = NRAND(15) + 1;	/* random horiz velocity */
	} else
	{
		/* image is just a dot when iconic */
		f->width = f->height = FLY_ICON_SIZE;
		f->vx = NRAND(4) + 1;	/* slower when iconic */
	}

	f->y = NRAND(win_height);
	if (f->y > (win_height / 2)) {
		f->side = FLY_SIDE_RIGHT;	/* change to right side */
		f->y -= win_height / 2;		/* start in top half */
		f->x = win_width - f->width;	/* move to right of screen */
		f->vx = -(f->vx);	/* flip direction */
	}
	f->oldx = -(f->width);	/* prevent undraw 1st time */

	if (MI_NPIXELS(mi) <= 2) {
		f->pixel = MI_WHITE_PIXEL(mi);	/* always white when mono */
	} else {
		f->pixel = MI_PIXEL(mi, NRAND(MI_NPIXELS(mi)));
	}
}

/*-
 *    Unpaint the flyer by painting the image in black.
 */

static void
unpaint_fly(ModeInfo * mi, Fly * f)
{
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	GC          gc = MI_GC(mi);

	if (MI_IS_ICONIC(mi)) {
		XSetForeground(display, gc, MI_BLACK_PIXEL(mi));
		XFillArc(display, window, gc, f->oldx, f->oldy,
			 f->width, f->height, 90 * 64, 360 * 64);
	} else {
		XSetForeground(display, gc, MI_BLACK_PIXEL(mi));
#ifdef FLASH
		XFillRectangle(display, window, gc,
			       f->oldx, f->oldy, f->width, f->height);
#else
		ERASE_IMAGE(display, window, gc, f->x, f->y,
			    f->oldx, f->oldy, f->width, f->height);
#endif
	}
}

/*-
 *    Paint the bouncing grelb on the screen.  If not in iconic
 *      mode, unpaint the previous image.  When iconic, the fly
 *      doesn't need to be undrawn, because it will always be on top
 *      of the eyes, which are repainted before the fly is painted.
 */

static void
paint_fly(ModeInfo * mi, Fly * f)
{
	EyeScrInfo *ep = &eye_info[MI_SCREEN(mi)];
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	int         x = f->x, y = f->y;

	if (MI_IS_ICONIC(mi)) {
		/* don't need to unpaint when iconic
		 * ep->flyGC has stipple set, don't use when iconic
		 */
		XSetForeground(display, MI_GC(mi), f->pixel);
		XFillArc(display, window, MI_GC(mi), x, y,
			 f->width, f->height, 90 * 64, 360 * 64);
	} else {
		unpaint_fly(mi, f);
		XSetForeground(display, ep->flyGC, f->pixel);
#ifdef XBM_GRELB
		if (ep->fly2pix != None) {
			XSetStipple(display, ep->flyGC, (f->vy <= 0) ? ep->flypix : ep->fly2pix);
		} else
#endif
			XSetStipple(display, ep->flyGC, ep->flypix);
		XSetTSOrigin(display, ep->flyGC, x, y);
#ifdef FLASH
		XSetFillStyle(display, ep->flyGC, FillStippled);
#else
		XSetFillStyle(display, ep->flyGC, FillOpaqueStippled);
#endif
		XFillRectangle(display, window, ep->flyGC,
			       x, y, f->width, f->height);
		XFlush(display);
	}
}

/*-
 *    Compute the new position of the fly.  The bouncy-boinginess
 *      algorithm is borrowed from the "bounce" (soccer ball) mode.
 */

static void
move_fly(ModeInfo * mi, Fly * f)
{
	int         win_width = MI_WIDTH(mi);
	int         win_height = MI_HEIGHT(mi);
	int         left = (f->side == FLY_SIDE_LEFT) ? -(f->width) : 0;
	int         right = (f->side == FLY_SIDE_RIGHT) ? win_width :
	win_width - f->width;
	Bool        track_p = trackmouse;
	int         cx, cy;


	f->oldx = f->x;		/* remember position before moving, */
	f->oldy = f->y;		/* for unpainting previous image */

	if (track_p) {
		Window      r, c;
		int         rx, ry;
		unsigned int m;

		(void) XQueryPointer(MI_DISPLAY(mi), MI_WINDOW(mi),
				     &r, &c, &rx, &ry, &cx, &cy, &m);
		if (cx <= 0 || cy <= 0 ||
		    cx >= MI_WIDTH(mi) - f->width - 1 ||
		    cy >= MI_HEIGHT(mi) - f->width - 1) {
			track_p = False;
		}
	}
	if (track_p) {
		f->x = cx;
		f->y = cy;
		return;
	}
	f->x += f->vx;		/* apply x velocity */

	if (f->x > right) {
		if (f->side == FLY_SIDE_RIGHT) {
			unpaint_fly(mi, f);	/* went off the edge, reset */
			init_fly(mi, f);
		} else {
			/* Bounce off the right edge */
			f->x = 2 * (win_width - f->width) - f->x;
			f->vx = -f->vx + f->vx / FRICTION;
		}
	} else if (f->x < left) {
		if (f->side == FLY_SIDE_LEFT) {
			unpaint_fly(mi, f);	/* went off the edge, reset */
			init_fly(mi, f);
		} else {
			/* Bounce off the left edge */
			f->x = -f->x;
			f->vx = -f->vx + f->vx / FRICTION;
		}
	}
	f->vy++;		/* gravity, accelerate in y direction */
	f->y += f->vy;		/* apply y velocity */

	if (f->y >= (win_height - f->height)) {
		/* Bounce off the bottom edge */
		f->y = (win_height - f->height);
		f->vy = -f->vy + f->vy / FRICTION;
		/* every once in a while, go apeshit to clean "high lurkers" */
		if (NRAND(50) == 0) {
			f->vy *= 4;
		}
	} else if (f->y < 0) {
		/* Bounce off the top edge */
		f->y = -f->y;
		f->vy = -f->vy + f->vy / FRICTION;
	}
	/* if he settles to the bottom, move him off quick */
	if (abs(f->vy) < 2) {
		if ((f->zero_y++) > 10) {
			f->vx += (f->side == FLY_SIDE_LEFT) ? -1 : 1;
		}
	} else {
		f->zero_y = 0;	/* still bouncing */
	}
}

/* ---------------------------------------------------------------------- */

/*-
 *    Initialize one pair of eyes
 */

static void
create_eyes(ModeInfo * mi, Eyes * e, Eyes * eyes, int num_eyes)
{
	EyeScrInfo *ep = &eye_info[MI_SCREEN(mi)];
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	int         win_width = MI_WIDTH(mi);
	int         win_height = MI_HEIGHT(mi);
	unsigned long black_pixel = MI_BLACK_PIXEL(mi);
	unsigned long white_pixel = MI_WHITE_PIXEL(mi);
	Bool        iconic = MI_IS_ICONIC(mi);
	Pixmap      pix = e->pixmap;	/* preserve pixmap handle */
	int         w = e->width;	/* remember last w/h */
	int         h = e->height;
	int         npixels = MI_NPIXELS(mi);	/* num colors in colormap */
	int         cycs = MI_CYCLES(mi);	/* affects eye lifetime */
	int         maxw = win_width / 2;	/* widest eyes can be */
	int         color, lid_color;
	int         i;

	(void) memset((char *) e, 0, sizeof (Eyes));	/* wipe everything */
	e->pixmap = pix;	/* remember Pixmap handle */

	/* sanity check the cycles value */
	if (cycs < 1)
		cycs = 1;
	if (cycs > MAX_CYCLES)
		cycs = MAX_CYCLES;
	e->time_to_die = (unsigned long) LIFE_MIN + NRAND(LIFE_RANGE);
	e->time_to_die *= (unsigned long) cycs;		/* multiply life by cycles */
	e->time_to_die += ep->time;

	e->pupil_pixel = black_pixel;	/* pupil is always black */

	if (MI_NPIXELS(mi) <= 2) {
		/* TODO: stipple the eyelid? */
		e->eyelid_pixel = black_pixel;
		e->eyeball_pixel = white_pixel;
	} else {
		lid_color = NRAND(npixels);
		e->eyelid_pixel = MI_PIXEL(mi, lid_color);

		while ((color = NRAND(npixels + 5)) == lid_color) {
			/* empty */
		}
		if (color >= npixels) {
			/* give white a little better chance */
			e->eyeball_pixel = white_pixel;
		} else {
			e->eyeball_pixel = MI_PIXEL(mi, color);
		}
	}

	if (iconic) {
		/* only one pair of eyes, fills entire window */
		e->width = win_width;
		e->height = win_height;
	} else {
		if (maxw - MIN_EYE_SIZE > MIN_EYE_SIZE)
			e->width = NRAND(maxw - MIN_EYE_SIZE) + MIN_EYE_SIZE;
		else
			e->width = NRAND(MIN_EYE_SIZE) + MIN_EYE_SIZE;
		e->x = (win_width - e->width > 0) ? NRAND(win_width - e->width) : 0;
		e->height = NRAND(e->width * 3 / 4) + (e->width / 4);
		e->y = (win_height - e->height > 0) ? NRAND(win_height - e->height) : 0;

		/* check for overlap with other eyes */
		for (i = 0; i < num_eyes; i++) {
			if (&eyes[i] == e) {	/* that's me */
				continue;
			}
			if (eyes_overlap(e, &eyes[i])) {
				/* collision, force retry on next cycle */
				e->time_to_die = 0;
				break;
			}
		}
	}

	/* If the Pixmap is smaller than the new size, make it bigger */
	if ((e->width > w) || (e->height > h)) {
		if (e->pixmap != None) {
			XFreePixmap(display, e->pixmap);
		}
		if ((e->pixmap = XCreatePixmap(display, window,
					  e->width, e->height, MI_DEPTH(mi))) == None) {
			e->width = e->height = 0;
			return;
		}
	}
	/* Set the transformation matrix for this set of eyes
	 * If iconic, make the eyes image one pixel shorter and
	 * skinnier, they seem to fit in the icon box better that way.
	 */
	SetTransform(&e->transform, 0, (iconic) ? e->width - 1 : e->width,
		     (iconic) ? e->height - 1 : e->height, 0,
		     W_MIN_X, W_MAX_X, W_MIN_Y, W_MAX_Y);

	/* clear the offscreen pixmap to background color */
	XSetForeground(display, ep->eyeGC, black_pixel);
	XFillRectangle(display, (Drawable) e->pixmap, ep->eyeGC, 0, 0, e->width, e->height);

	/* make the full eye images in the offscreen Pixmap */
	make_eye(mi, e->pixmap, e, 0, True);
	make_eye(mi, e->pixmap, e, 1, True);
}


/*-
 *    Paint an eye pair onto the screen.
 *      This is normally only the change rectangles for each pupil,
 *      unless in iconic mode, which always repaints the full image.
 */

static void
paint_eyes(ModeInfo * mi, Eyes * e, Fly * f, Eyes * eyes, int num_eyes)
{
	EyeScrInfo *ep = &eye_info[MI_SCREEN(mi)];
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	GC          gc = ep->eyeGC;
	Bool        iconic = MI_IS_ICONIC(mi);
	int         focusx = (f->x + (f->width / 2)) - e->x;
	int         focusy = (f->y + (f->height / 2)) - e->y;
	Pixmap      pix = e->pixmap;
	TPoint      point;
	int         i;

	if (pix == None) {
		e->time_to_die = 0;	/* "should not happen" */
	}
	if (ep->time >= e->time_to_die) {
		/* Sorry Bud, your time is up */
		if (e->painted) {
			/* only unpaint it if previously painted */
			XSetForeground(display, gc, MI_BLACK_PIXEL(mi));
			XFillRectangle(display, window, gc, e->x, e->y, e->width, e->height);
		}
		/* randomly place the eyes elsewhere */
		create_eyes(mi, e, eyes, num_eyes);
		pix = e->pixmap;	/* pixmap may have changed */
	}
	/* If the bouncer would intersect this pair of eyes, force the
	 * eyes to move.  This simplifies the code, because we do not
	 * have to deal with drawing the bouncer on top of the eyes.
	 * When trying to do so, there was too much annoying flashing
	 * and ghost images from the undraw.  I decided to observe the
	 * KISS principle and keep it simple.  I think the effect is
	 * better also.
	 * We must draw the flyer on the eyes when iconic, but that is
	 * easy because the eyes repaint the whole box each time.
	 */
	if ((!iconic) && (fly_touches_eye(f, e))) {
		e->time_to_die = 0;
	}
	if (e->time_to_die == 0) {
		return;		/* collides with something */
	}
	/* set the point to look at and compute the pupil position  */
	point.x = Tx(focusx, focusy, &e->transform);
	point.y = Ty(focusx, focusy, &e->transform);
	computePupil(0, point, &(e->pupil[0]));
	computePupil(1, point, &(e->pupil[1]));

	if (e->painted) {
		/* if still looking at the same point, do nothing further */
		if (TPointEqual(e->pupil[0], e->last_pupil[0]) &&
		    TPointEqual(e->pupil[1], e->last_pupil[1])) {
			return;
		}
	}
	for (i = 0; i < 2; i++) {
		/* update the eye, calculates the changed rectangle */
		make_eye(mi, pix, e, i, False);

		/* Only blit the change if the full image has been painted */
		if (e->painted) {
			/* copy the changed rectangle out to the screen */
			XCopyArea(display, pix, window, gc,
				  e->bbox.x, e->bbox.y,
				  (int) e->bbox.width, (int) e->bbox.height,
				  e->x + e->bbox.x, e->y + e->bbox.y);
		}
		/* remember where we're looking, for the next time around */
		e->last_pupil[i] = e->pupil[i];
	}

	/* always do full paint when iconic, eliminates need to track fly */
	if (iconic || (!e->painted)) {
		XCopyArea(display, pix, window, gc, 0, 0,
			  e->width, e->height, e->x, e->y);
	}
	/* when iconic, pretend to never paint, causes full paint each time */
	if (!iconic) {
		e->painted++;	/* note that a paint has been done */
	}
}

/* ---------------------------------------------------------------------- */


static void
freePairsOfEyes(Display * display, EyeScrInfo * ep)
{
	int         en;

	if (ep->eyes) {
		for (en = 0; en < ep->num_eyes; en++)
			if (ep->eyes[en].pixmap != None)
				XFreePixmap(display, ep->eyes[en].pixmap);
		free(ep->eyes);
		ep->eyes = (Eyes *) NULL;
	}
}

static void
free_eyes_screen(Display * display, EyeScrInfo * ep)
{
	if (ep == NULL) {
		return;
	}
	if (ep->flyGC != None) {
		XFreeGC(display, ep->flyGC);
		ep->flyGC = None;
	}
	if (ep->eyeGC != None) {
		XFreeGC(display, ep->eyeGC);
		ep->eyeGC = None;
	}
	if (ep->flypix != None) {
		XFreePixmap(display, ep->flypix);
		ep->flypix = None;
	}
#ifdef XBM_GRELB
	if (ep->fly2pix != None) {
		XFreePixmap(display, ep->fly2pix);
		ep->fly2pix = None;
	}
#endif
	freePairsOfEyes(display, ep);
	if (ep->cursor != None) {
		XFreeCursor(display, ep->cursor);
		ep->cursor = None;
	}
	ep = NULL;
}

ENTRYPOINT void
free_eyes(ModeInfo * mi)
{
	free_eyes_screen(MI_DISPLAY(mi), &eye_info[MI_SCREEN(mi)]);
}

/*-
 *    Initialize them eyes.  Called each time the window changes.
 */

ENTRYPOINT void
init_eyes(ModeInfo * mi)
{
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	EyeScrInfo *ep;
	int         i;

        /*-
         *    Initialization that only needs to be done once.  If the
         *      release hook is called, this stuff may be freed and this
         *      function will have to allocate it again next time the
         *      init hook is called.
         */
	MI_INIT(mi, eye_info);
	ep = &eye_info[MI_SCREEN(mi)];

	if (ep->flypix == None) {
#ifdef STANDALONE
		getPixmap(display, window, FLY_WIDTH, FLY_HEIGHT, FLY_BITS,
			  &(ep->flywidth), &(ep->flyheight), &(ep->flypix));
#else
		getPixmap(mi, window, FLY_WIDTH, FLY_HEIGHT, FLY_BITS,
			  &(ep->flywidth), &(ep->flyheight), &(ep->flypix),
			  &(ep->graphics_format));
#endif
		if (ep->flypix == None) {
			free_eyes_screen(display, ep);
			return;
		}
#ifdef XBM_GRELB
#ifdef STANDALONE
			ep->graphics_format = 0;
			getPixmap(display, window,
			  FLY2_WIDTH, FLY2_HEIGHT, FLY2_BITS,
			  &(ep->fly2width), &(ep->fly2height), &(ep->fly2pix));
			if (ep->fly2pix == None) {
				free_eyes_screen(display, ep);
				return;
			}
#else
		if (ep->graphics_format == IS_XBM) {
			ep->graphics_format = 0;
			getPixmap(mi, window,
			  FLY2_WIDTH, FLY2_HEIGHT, FLY2_BITS,
			  &(ep->fly2width), &(ep->fly2height), &(ep->fly2pix),
			  &(ep->graphics_format));
			if (ep->fly2pix == None) {
				free_eyes_screen(display, ep);
				return;
			}
		}
#endif
#endif
	}
	if (ep->flyGC == None) {
		XGCValues   gcv;

		gcv.foreground = MI_BLACK_PIXEL(mi);
		gcv.background = MI_BLACK_PIXEL(mi);
		if ((ep->flyGC = XCreateGC(display, window,
				 GCForeground | GCBackground, &gcv)) == None) {
			free_eyes_screen(display, ep);
			return;
		}
	}
	if (ep->eyeGC == None) {
		if ((ep->eyeGC = XCreateGC(display, window,
			   (unsigned long) 0, (XGCValues *) NULL)) == None) {
			free_eyes_screen(display, ep);
			return;
		}
	}
	ep->time = 0;
	/* don't want any exposure events from XCopyArea */
	XSetGraphicsExposures(display, ep->eyeGC, False);

	freePairsOfEyes(display, ep);
	if (MI_IS_ICONIC(mi))
		ep->num_eyes = 1;
	else {
		ep->num_eyes = MI_COUNT(mi);
		/* MAX_EYES is used or one may quickly run out of memory */
		if (ep->num_eyes > MAX_EYES)
			ep->num_eyes = MAX_EYES;
		if (ep->num_eyes < 0) {
			if (ep->num_eyes < -MAX_EYES)
				ep->num_eyes = NRAND(MAX_EYES) + 1;
			else
				ep->num_eyes = NRAND(-ep->num_eyes) + 1;	/* Add 1 so its not too boring */
		}
	}
	if (!ep->eyes) {
		if ((ep->eyes = (Eyes *) calloc(ep->num_eyes, sizeof (Eyes))) == NULL) {
			free_eyes_screen(display, ep);
			return;
		}
	}

	for (i = 0; i < ep->num_eyes; i++) {	/* place each eye pair */
		/* don't assume None == 0 */
		ep->eyes[i].pixmap = None;
		create_eyes(mi, &(ep->eyes[i]), ep->eyes, ep->num_eyes);
	}

	init_fly(mi, &(ep->fly));	/* init the bouncer */

	if (trackmouse && !ep->cursor) {	/* Create an invisible cursor */
		Pixmap      bit;
		XColor      black;

		black.red = 0;
		black.green = 0;
		black.blue = 0;
		black.flags = DoRed | DoGreen | DoBlue;
		if ((bit = XCreatePixmapFromBitmapData(display, window,
				(char *) "\000", 1, 1, MI_BLACK_PIXEL(mi),
				MI_BLACK_PIXEL(mi), 1)) == None) {
			free_eyes_screen(display, ep);
			return;
		}
		if ((ep->cursor = XCreatePixmapCursor(display, bit, bit,
				&black, &black, 0, 0)) == None) {
			free_eyes_screen(display, ep);
			return;
		}
		XFreePixmap(display, bit);
	}
	XDefineCursor(display, window, ep->cursor);

	MI_CLEARWINDOW(mi);
}

/* ---------------------------------------------------------------------- */

/*-
 *    Called by the mainline code periodically to update the display.
 */

ENTRYPOINT void
draw_eyes(ModeInfo * mi)
{
	int         i;
	EyeScrInfo *ep;

	if (eye_info == NULL)
		return;
	ep = &eye_info[MI_SCREEN(mi)];
	if (ep->flypix == None)
		return;

	MI_IS_DRAWN(mi) = True;

	move_fly(mi, &(ep->fly));
	ep->time++;
	for (i = 0; i < ep->num_eyes; i++) {
		paint_eyes(mi, &(ep->eyes[i]), &(ep->fly), ep->eyes, ep->num_eyes);
	}

	paint_fly(mi, &(ep->fly));
}

/* ---------------------------------------------------------------------- */

/*-
 *    The display is being taken away from us.  Free up malloc'ed
 *      memory and X resources that we've alloc'ed.  Only called
 *      once, we must zap everything for every screen.
 */

ENTRYPOINT void
release_eyes(ModeInfo * mi)
{
	if (eye_info != NULL) {
		int         screen;

		for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
			free_eyes_screen(MI_DISPLAY(mi), &eye_info[screen]);
		free(eye_info);
	}
	eye_info = (EyeScrInfo *) NULL;
}

/* ---------------------------------------------------------------------- */

#ifndef STANDALONE
/*-
 *    Called when the mainline xlock code notices possible window
 *      damage.  This hook should take steps to repaint the entire
 *      window (no specific damage area information is provided).
 */

ENTRYPOINT void
refresh_eyes(ModeInfo * mi)
{
	int         i;
	EyeScrInfo *ep;

	if (eye_info == NULL)
		return;
	ep = &eye_info[MI_SCREEN(mi)];
	if (ep->eyeGC == None)
		return;

	MI_CLEARWINDOW(mi);

	/* simply flag all the eyes as not painted, will repaint next time */
	for (i = 0; i < ep->num_eyes; i++) {
		ep->eyes[i].painted = False;
	}
}
#endif

XSCREENSAVER_MODULE ("Eyes", eyes)

#endif /* MODE_eyes */
