/* -*- 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 * * 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 */