1115 lines
30 KiB
C
1115 lines
30 KiB
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 */
|