Edit

IABSD.fr/xenocara/app/xlockmore/modes/eyes.c

Branch :

  • Show log

    Commit

  • Author : matthieu
    Date : 2006-11-26 11:07:42
    Hash : 110b2a92
    Message : Importing xlockmore 5.22

  • app/xlockmore/modes/eyes.c
  • /* -*- Mode: C; tab-width: 4 -*- */
    /* eyes --- follow the bouncing Grelb */
    
    #if !defined( lint ) && !defined( SABER )
    static const char sccsid[] = "@(#)eyes.c	5.00 2000/11/01 xlockmore";
    
    #endif
    
    /*-
     * Copyright 1996 by Ron Hitchens <ron@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 PROGCLASS "Eyes"
    #define HACK_INIT init_eyes
    #define HACK_DRAW draw_eyes
    #define eyes_opts xlockmore_opts
    #define DEFAULTS "*delay: 20000 \n" \
     "*count: -8 \n" \
     "*cycles: 5 \n" \
     "*ncolors: 200 \n" \
     "*bitmap: \n" \
     "*trackmouse: False \n"
    #include "xlockmore.h"		/* in xscreensaver distribution */
    
    #else /* STANDALONE */
    #include "xlock.h"		/* in xlockmore distribution */
    
    #endif /* STANDALONE */
    #include "iostuff.h"
    
    #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"}
    };
    
    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", (char *) NULL, &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(Display * display, EyeScrInfo * ep)
    {
    	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;
    	}
    }
    
    /*-
     *    Initialize them eyes.  Called each time the window changes.
     */
    
    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.
             */
    	if (eye_info == NULL) {
    		if ((eye_info = (EyeScrInfo *) calloc(MI_NUM_SCREENS(mi),
    					       sizeof (EyeScrInfo))) == NULL)
    			return;
    	}
    	ep = &eye_info[MI_SCREEN(mi)];
    
    	if (ep->flypix == None) {
    		getPixmap(mi, window, FLY_WIDTH, FLY_HEIGHT, FLY_BITS,
    			  &(ep->flywidth), &(ep->flyheight), &(ep->flypix),
    			  &(ep->graphics_format));
    		if (ep->flypix == None) {
    			free_eyes(display, ep);
    			return;
    		}
    #ifdef XBM_GRELB
    		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(display, ep);
    				return;
    			}
    		}
    #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(display, ep);
    			return;
    		}
    	}
    	if (ep->eyeGC == None) {
    		if ((ep->eyeGC = XCreateGC(display, window,
    			   (unsigned long) 0, (XGCValues *) NULL)) == None) {
    			free_eyes(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(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(display, ep);
    			return;
    		}
    		if ((ep->cursor = XCreatePixmapCursor(display, bit, bit,
    				&black, &black, 0, 0)) == None) {
    			free_eyes(display, ep);
    			return;
    		}
    		XFreePixmap(display, bit);
    	}
    	XDefineCursor(display, window, ep->cursor);
    
    	MI_CLEARWINDOW(mi);
    }
    
    /* ---------------------------------------------------------------------- */
    
    /*-
     *    Called by the mainline code periodically to update the display.
     */
    
    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.
     */
    
    void
    release_eyes(ModeInfo * mi)
    {
    	if (eye_info != NULL) {
    		int         screen;
    
    		for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
    			free_eyes(MI_DISPLAY(mi), &eye_info[screen]);
    		free(eye_info);
    	}
    	eye_info = (EyeScrInfo *) NULL;
    }
    
    /* ---------------------------------------------------------------------- */
    
    /*-
     *    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).
     */
    
    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 /* MODE_eyes */