161126Selan /*
261126Selan Xroach - A game of skill. Try to find the roaches under your windows.
361126Selan
461126Selan Copyright 1991 by J.T. Anderson
561126Selan
661126Selan jta@locus.com
761126Selan
861126Selan This program may be freely distributed provided that all
961126Selan copyright notices are retained.
1061126Selan
1161126Selan To build:
1261126Selan cc -o xroach roach.c -lX11 [-lsocketorwhatever] [-lm] [-l...]
1361126Selan
1461126Selan Dedicated to Greg McFarlane. (gregm@otc.otca.oz.au)
1561126Selan */
1661126Selan #include <X11/Xlib.h>
1761126Selan #include <X11/Xutil.h>
1861126Selan #include <X11/Xos.h>
1961126Selan
2061126Selan #include <stdio.h>
2161126Selan #include <math.h>
2261126Selan #include <signal.h>
2361126Selan #include <stdlib.h>
2461126Selan
2561126Selan char Copyright[] = "Xroach\nCopyright 1991 J.T. Anderson";
2661126Selan
2761126Selan #include "roachmap.h"
2861126Selan
2961126Selan typedef unsigned long Pixel;
3061126Selan typedef int ErrorHandler();
3161126Selan
3261126Selan #define SCAMPER_EVENT (LASTEvent + 1)
3361126Selan
3461126Selan #if !defined(GRAB_SERVER)
3561126Selan #define GRAB_SERVER 0
3661126Selan #endif
3761126Selan
3861126Selan Display *display;
3961126Selan int screen;
4061126Selan Window rootWin;
4161126Selan unsigned int display_width, display_height;
4261126Selan int center_x, center_y;
4361126Selan GC gc;
4461126Selan char *display_name = NULL;
4561126Selan Pixel black, white;
4661126Selan
4761126Selan int done = 0;
4861126Selan int eventBlock = 0;
4961126Selan int errorVal = 0;
5061126Selan
5161126Selan typedef struct Roach {
5261126Selan RoachMap *rp;
5361126Selan int index;
5461126Selan float x;
5561126Selan float y;
5661126Selan int intX;
5761126Selan int intY;
5861126Selan int hidden;
5961126Selan int turnLeft;
6061126Selan int steps;
6161126Selan } Roach;
6261126Selan
6361126Selan Roach *roaches;
6461126Selan int maxRoaches = 10;
6561126Selan int curRoaches = 0;
6661126Selan float roachSpeed = 20.0;
6761126Selan
6861126Selan Region rootVisible = NULL;
6961126Selan
7061126Selan void Usage();
7161126Selan void SigHandler();
7261126Selan void AddRoach();
7361126Selan void MoveRoach();
7461126Selan void DrawRoaches();
7561126Selan void CoverRoot();
7661126Selan int CalcRootVisible();
7761126Selan int MarkHiddenRoaches();
7861126Selan Pixel AllocNamedColor();
7961126Selan
8061126Selan void
main(ac,av)8161126Selan main(ac, av)
8261126Selan int ac;
8361126Selan char *av[];
8461126Selan {
8561126Selan XGCValues xgcv;
8661126Selan int ax;
8761126Selan char *arg;
8861126Selan RoachMap *rp;
8961126Selan int rx;
9061126Selan float angle;
9161126Selan XEvent ev;
9261126Selan char *roachColor = "black";
9361126Selan int nVis;
9461126Selan int needCalc;
9561126Selan
9661126Selan /*
9761126Selan Process command line options.
9861126Selan */
9961126Selan for (ax=1; ax<ac; ax++) {
10061126Selan arg = av[ax];
10161126Selan if (strcmp(arg, "-display") == 0) {
10261126Selan display_name = av[++ax];
10361126Selan }
10461126Selan else if (strcmp(arg, "-rc") == 0) {
10561126Selan roachColor = av[++ax];
10661126Selan }
10761126Selan else if (strcmp(arg, "-speed") == 0) {
10861126Selan roachSpeed = atof(av[++ax]);
10961126Selan }
11061126Selan else if (strcmp(arg, "-roaches") == 0) {
11161126Selan maxRoaches = strtol(av[++ax], (char **)NULL, 0);
11261126Selan }
11361126Selan else {
11461126Selan Usage();
11561126Selan }
11661126Selan }
11761126Selan
11861126Selan srand((int)time((long *)NULL));
11961126Selan
12061126Selan /*
12161126Selan Catch some signals so we can erase any visible roaches.
12261126Selan */
12361126Selan signal(SIGKILL, SigHandler);
12461126Selan signal(SIGINT, SigHandler);
12561126Selan signal(SIGTERM, SigHandler);
12661126Selan signal(SIGHUP, SigHandler);
12761126Selan
12861126Selan display = XOpenDisplay(display_name);
12961126Selan if (display == NULL) {
13061126Selan if (display_name == NULL) display_name = getenv("DISPLAY");
13161126Selan (void) fprintf(stderr, "%s: cannot connect to X server %s\n", av[0],
13261126Selan display_name ? display_name : "(default)");
13361126Selan exit(1);
13461126Selan }
13561126Selan
13661126Selan screen = DefaultScreen(display);
13761126Selan rootWin = RootWindow(display, screen);
13861126Selan black = BlackPixel(display, screen);
13961126Selan white = WhitePixel(display, screen);
14061126Selan
14161126Selan display_width = DisplayWidth(display, screen);
14261126Selan display_height = DisplayHeight(display, screen);
14361126Selan center_x = display_width / 2;
14461126Selan center_y = display_height / 2;
14561126Selan
14661126Selan /*
14761126Selan Create roach pixmaps at several orientations.
14861126Selan */
14961126Selan for (ax=0; ax<360; ax+=ROACH_ANGLE) {
15061126Selan rx = ax / ROACH_ANGLE;
15161126Selan angle = rx * 0.261799387799;
15261126Selan rp = &roachPix[rx];
15361126Selan rp->pixmap = XCreateBitmapFromData(display, rootWin,
15461126Selan rp->roachBits, rp->width, rp->height);
15561126Selan rp->sine = sin(angle);
15661126Selan rp->cosine = cos(angle);
15761126Selan }
15861126Selan
15961126Selan roaches = (Roach *)malloc(sizeof(Roach) * maxRoaches);
16061126Selan
16161126Selan gc = XCreateGC(display, rootWin, 0L, &xgcv);
16261126Selan XSetForeground(display, gc, AllocNamedColor(roachColor, black));
16361126Selan XSetFillStyle(display, gc, FillStippled);
16461126Selan
16561126Selan while (curRoaches < maxRoaches)
16661126Selan AddRoach();
16761126Selan
16861126Selan XSelectInput(display, rootWin, ExposureMask | SubstructureNotifyMask);
16961126Selan
17061126Selan needCalc = 1;
17161126Selan while (!done) {
17261126Selan if (XPending(display)) {
17361126Selan XNextEvent(display, &ev);
17461126Selan }
17561126Selan else {
17661126Selan if (needCalc) {
17761126Selan needCalc = CalcRootVisible();
17861126Selan }
17961126Selan nVis = MarkHiddenRoaches();
18061126Selan if (nVis) {
18161126Selan ev.type = SCAMPER_EVENT;
18261126Selan }
18361126Selan else {
18461126Selan DrawRoaches();
18561126Selan eventBlock = 1;
18661126Selan XNextEvent(display, &ev);
18761126Selan eventBlock = 0;
18861126Selan }
18961126Selan }
19061126Selan
19161126Selan switch (ev.type) {
19261126Selan
19361126Selan case SCAMPER_EVENT:
19461126Selan for (rx=0; rx<curRoaches; rx++) {
19561126Selan if (!roaches[rx].hidden)
19661126Selan MoveRoach(rx);
19761126Selan }
19861126Selan DrawRoaches();
19961126Selan XSync(display, False);
20061126Selan break;
20161126Selan
20261126Selan case Expose:
20361126Selan case MapNotify:
20461126Selan case UnmapNotify:
20561126Selan case ConfigureNotify:
20661126Selan needCalc = 1;
20761126Selan break;
20861126Selan
20961126Selan }
21061126Selan }
21161126Selan
21261126Selan CoverRoot();
21361126Selan
21461126Selan XCloseDisplay(display);
21561126Selan }
21661126Selan
21761126Selan #define USEPRT(msg) fprintf(stderr, msg)
21861126Selan
21961126Selan void
Usage()22061126Selan Usage()
22161126Selan {
22261126Selan USEPRT("Usage: xroach [options]\n\n");
22361126Selan USEPRT("Options:\n");
22461126Selan USEPRT(" -display displayname\n");
22561126Selan USEPRT(" -rc roachcolor\n");
22661126Selan USEPRT(" -roaches numroaches\n");
22761126Selan USEPRT(" -speed roachspeed\n");
22861126Selan
22961126Selan exit(1);
23061126Selan }
23161126Selan
23261126Selan void
SigHandler()23361126Selan SigHandler()
23461126Selan {
23561126Selan
23661126Selan /*
23761126Selan If we are blocked, no roaches are visible and we can just bail
23861126Selan out. If we are not blocked, then let the main procedure clean
23961126Selan up the root window.
24061126Selan */
24161126Selan if (eventBlock) {
24261126Selan XCloseDisplay(display);
24361126Selan exit(0);
24461126Selan }
24561126Selan else {
24661126Selan done = 1;
24761126Selan }
24861126Selan }
24961126Selan
25061126Selan /*
25161126Selan Generate random integer between 0 and maxVal-1.
25261126Selan */
25361126Selan int
RandInt(maxVal)25461126Selan RandInt(maxVal)
25561126Selan int maxVal;
25661126Selan {
25761126Selan return rand() % maxVal;
25861126Selan }
25961126Selan
26061126Selan /*
26161126Selan Check for roach completely in specified rectangle.
26261126Selan */
26361126Selan int
RoachInRect(roach,rx,ry,x,y,width,height)26461126Selan RoachInRect(roach, rx, ry, x, y, width, height)
26561126Selan Roach *roach;
26661126Selan int rx;
26761126Selan int ry;
26861126Selan int x;
26961126Selan int y;
27061126Selan unsigned int width;
27161126Selan unsigned int height;
27261126Selan {
27361126Selan if (rx < x) return 0;
27461126Selan if ((rx + roach->rp->width) > (x + width)) return 0;
27561126Selan if (ry < y) return 0;
27661126Selan if ((ry + roach->rp->height) > (y + height)) return 0;
27761126Selan
27861126Selan return 1;
27961126Selan }
28061126Selan
28161126Selan /*
28261126Selan Check for roach overlapping specified rectangle.
28361126Selan */
28461126Selan int
RoachOverRect(roach,rx,ry,x,y,width,height)28561126Selan RoachOverRect(roach, rx, ry, x, y, width, height)
28661126Selan Roach *roach;
28761126Selan int rx;
28861126Selan int ry;
28961126Selan int x;
29061126Selan int y;
29161126Selan unsigned int width;
29261126Selan unsigned int height;
29361126Selan {
29461126Selan if (rx >= (x + width)) return 0;
29561126Selan if ((rx + roach->rp->width) <= x) return 0;
29661126Selan if (ry >= (y + height)) return 0;
29761126Selan if ((ry + roach->rp->height) <= y) return 0;
29861126Selan
29961126Selan return 1;
30061126Selan }
30161126Selan
30261126Selan /*
30361126Selan Give birth to a roach.
30461126Selan */
30561126Selan void
AddRoach()30661126Selan AddRoach()
30761126Selan {
30861126Selan Roach *r;
30961126Selan
31061126Selan if (curRoaches < maxRoaches) {
31161126Selan r = &roaches[curRoaches++];
31261126Selan r->index = RandInt(ROACH_HEADINGS);
31361126Selan r->rp = &roachPix[r->index];
31461126Selan r->x = RandInt(display_width - r->rp->width);
31561126Selan r->y = RandInt(display_height - r->rp->height);
31661126Selan r->intX = -1;
31761126Selan r->intY = -1;
31861126Selan r->hidden = 0;
31961126Selan r->steps = RandInt(200);
32061126Selan r->turnLeft = RandInt(100) >= 50;
32161126Selan }
32261126Selan }
32361126Selan
32461126Selan /*
32561126Selan Turn a roach.
32661126Selan */
32761126Selan void
TurnRoach(roach)32861126Selan TurnRoach(roach)
32961126Selan Roach *roach;
33061126Selan {
33161126Selan if (roach->index != (roach->rp - roachPix)) return;
33261126Selan
33361126Selan if (roach->turnLeft) {
33461126Selan roach->index += (RandInt(30) / 10) + 1;
33561126Selan if (roach->index >= ROACH_HEADINGS)
33661126Selan roach->index -= ROACH_HEADINGS;
33761126Selan }
33861126Selan else {
33961126Selan roach->index -= (RandInt(30) / 10) + 1;
34061126Selan if (roach->index < 0)
34161126Selan roach->index += ROACH_HEADINGS;
34261126Selan }
34361126Selan }
34461126Selan
34561126Selan /*
34661126Selan Move a roach.
34761126Selan */
34861126Selan void
MoveRoach(rx)34961126Selan MoveRoach(rx)
35061126Selan int rx;
35161126Selan {
35261126Selan Roach *roach;
35361126Selan Roach *r2;
35461126Selan float newX;
35561126Selan float newY;
35661126Selan int ii;
35761126Selan
35861126Selan roach = &roaches[rx];
35961126Selan newX = roach->x + (roachSpeed * roach->rp->cosine);
36061126Selan newY = roach->y - (roachSpeed * roach->rp->sine);
36161126Selan
36261126Selan if (RoachInRect(roach, (int)newX, (int)newY,
36361126Selan 0, 0, display_width, display_height)) {
36461126Selan
36561126Selan roach->x = newX;
36661126Selan roach->y = newY;
36761126Selan
36861126Selan if (roach->steps-- <= 0) {
36961126Selan TurnRoach(roach);
37061126Selan roach->steps = RandInt(200);
37161126Selan }
37261126Selan
37361126Selan for (ii=rx+1; ii<curRoaches; ii++) {
37461126Selan r2 = &roaches[ii];
37561126Selan if (RoachOverRect(roach, (int)newX, (int)newY,
37661126Selan r2->intX, r2->intY, r2->rp->width, r2->rp->height)) {
37761126Selan
37861126Selan TurnRoach(roach);
37961126Selan }
38061126Selan }
38161126Selan }
38261126Selan else {
38361126Selan TurnRoach(roach);
38461126Selan }
38561126Selan }
38661126Selan
38761126Selan /*
38861126Selan Draw all roaches.
38961126Selan */
39061126Selan void
DrawRoaches()39161126Selan DrawRoaches()
39261126Selan {
39361126Selan Roach *roach;
39461126Selan int rx;
39561126Selan
39661126Selan for (rx=0; rx<curRoaches; rx++) {
39761126Selan roach = &roaches[rx];
39861126Selan
39961126Selan if (roach->intX >= 0) {
40061126Selan XClearArea(display, rootWin, roach->intX, roach->intY,
40161126Selan roach->rp->width, roach->rp->height, False);
40261126Selan }
40361126Selan }
40461126Selan
40561126Selan for (rx=0; rx<curRoaches; rx++) {
40661126Selan roach = &roaches[rx];
40761126Selan
40861126Selan if (!roach->hidden) {
40961126Selan roach->intX = roach->x;
41061126Selan roach->intY = roach->y;
41161126Selan roach->rp = &roachPix[roach->index];
41261126Selan
41361126Selan XSetStipple(display, gc, roach->rp->pixmap);
41461126Selan XSetTSOrigin(display, gc, roach->intX, roach->intY);
41561126Selan XFillRectangle(display, rootWin, gc,
41661126Selan roach->intX, roach->intY, roach->rp->width, roach->rp->height);
41761126Selan }
41861126Selan else {
41961126Selan roach->intX = -1;
42061126Selan }
42161126Selan }
42261126Selan }
42361126Selan
42461126Selan /*
42561126Selan Cover root window to erase roaches.
42661126Selan */
42761126Selan void
CoverRoot()42861126Selan CoverRoot()
42961126Selan {
43061126Selan XSetWindowAttributes xswa;
43161126Selan long wamask;
43261126Selan Window roachWin;
43361126Selan
43461126Selan xswa.background_pixmap = ParentRelative;
43561126Selan xswa.override_redirect = True;
43661126Selan wamask = CWBackPixmap | CWOverrideRedirect;
43761126Selan roachWin = XCreateWindow(display, rootWin, 0, 0,
43861126Selan display_width, display_height, 0, CopyFromParent,
43961126Selan InputOutput, CopyFromParent, wamask, &xswa);
44061126Selan XLowerWindow(display, roachWin);
44161126Selan XMapWindow(display, roachWin);
44261126Selan XFlush(display);
44361126Selan }
44461126Selan
44561126Selan #if !GRAB_SERVER
44661126Selan
44761126Selan int
RoachErrors(dpy,err)44861126Selan RoachErrors(dpy, err)
44961126Selan Display *dpy;
45061126Selan XErrorEvent *err;
45161126Selan {
45261126Selan errorVal = err->error_code;
45361126Selan
45461126Selan return 0;
45561126Selan }
45661126Selan
45761126Selan #endif /* GRAB_SERVER */
45861126Selan
45961126Selan /*
46061126Selan Calculate Visible region of root window.
46161126Selan */
46261126Selan int
CalcRootVisible()46361126Selan CalcRootVisible()
46461126Selan {
46561126Selan Region covered;
46661126Selan Region visible;
46761126Selan Window *children;
46861126Selan int nChildren;
46961126Selan Window dummy;
47061126Selan XWindowAttributes wa;
47161126Selan int wx;
47261126Selan XRectangle rect;
47361126Selan int winX, winY;
47461126Selan unsigned int winHeight, winWidth;
47561126Selan unsigned int borderWidth;
47661126Selan unsigned int depth;
47761126Selan
47861126Selan /*
47961126Selan If we don't grab the server, the XGetWindowAttribute or XGetGeometry
48061126Selan calls can abort us. On the other hand, the server grabs can make for
48161126Selan some annoying delays.
48261126Selan */
48361126Selan #if GRAB_SERVER
48461126Selan XGrabServer(display);
48561126Selan #else
48661126Selan XSetErrorHandler(RoachErrors);
48761126Selan #endif
48861126Selan
48961126Selan /*
49061126Selan Get children of root.
49161126Selan */
49261126Selan XQueryTree(display, rootWin, &dummy, &dummy, &children, &nChildren);
49361126Selan
49461126Selan /*
49561126Selan For each mapped child, add the window rectangle to the covered
49661126Selan region.
49761126Selan */
49861126Selan covered = XCreateRegion();
49961126Selan for (wx=0; wx<nChildren; wx++) {
50061126Selan if (XEventsQueued(display, QueuedAlready)) return 1;
50161126Selan errorVal = 0;
50261126Selan XGetWindowAttributes(display, children[wx], &wa);
50361126Selan if (errorVal) continue;
50461126Selan if (wa.map_state == IsViewable) {
50561126Selan XGetGeometry(display, children[wx], &dummy, &winX, &winY,
50661126Selan &winWidth, &winHeight, &borderWidth, &depth);
50761126Selan if (errorVal) continue;
50861126Selan rect.x = winX;
50961126Selan rect.y = winY;
51061126Selan rect.width = winWidth + (borderWidth * 2);
51161126Selan rect.height = winHeight + (borderWidth * 2);
51261126Selan XUnionRectWithRegion(&rect, covered, covered);
51361126Selan }
51461126Selan }
515*61144Selan XFree((char *)children);
51661126Selan
51761126Selan #if GRAB_SERVER
51861126Selan XUngrabServer(display);
51961126Selan #else
52061126Selan XSetErrorHandler((ErrorHandler *)NULL);
52161126Selan #endif
52261126Selan
52361126Selan /*
52461126Selan Subtract the covered region from the root window region.
52561126Selan */
52661126Selan visible = XCreateRegion();
52761126Selan rect.x = 0;
52861126Selan rect.y = 0;
52961126Selan rect.width = display_width;
53061126Selan rect.height = display_height;
53161126Selan XUnionRectWithRegion(&rect, visible, visible);
53261126Selan XSubtractRegion(visible, covered, visible);
53361126Selan XDestroyRegion(covered);
53461126Selan
53561126Selan /*
53661126Selan Save visible region globally.
53761126Selan */
53861126Selan if (rootVisible)
53961126Selan XDestroyRegion(rootVisible);
54061126Selan rootVisible = visible;
54161126Selan
54261126Selan
54361126Selan /*
54461126Selan Mark all roaches visible.
54561126Selan */
54661126Selan for (wx=0; wx<curRoaches; wx++)
54761126Selan roaches[wx].hidden = 0;
54861126Selan
54961126Selan return 0;
55061126Selan }
55161126Selan
55261126Selan /*
55361126Selan Mark hidden roaches.
55461126Selan */
55561126Selan int
MarkHiddenRoaches()55661126Selan MarkHiddenRoaches()
55761126Selan {
55861126Selan int rx;
55961126Selan Roach *r;
56061126Selan int nVisible;
56161126Selan
56261126Selan nVisible = 0;
56361126Selan for (rx=0; rx<curRoaches; rx++) {
56461126Selan r = &roaches[rx];
56561126Selan
56661126Selan if (!r->hidden) {
56761126Selan if (r->intX > 0 && XRectInRegion(rootVisible, r->intX, r->intY,
56861126Selan r->rp->width, r->rp->height) == RectangleOut) {
56961126Selan r->hidden = 1;
57061126Selan }
57161126Selan else {
57261126Selan nVisible++;
57361126Selan }
57461126Selan }
57561126Selan }
57661126Selan
57761126Selan return nVisible;
57861126Selan }
57961126Selan
58061126Selan /*
58161126Selan Allocate a color by name.
58261126Selan */
58361126Selan Pixel
AllocNamedColor(colorName,dfltPix)58461126Selan AllocNamedColor(colorName, dfltPix)
58561126Selan char *colorName;
58661126Selan Pixel dfltPix;
58761126Selan {
58861126Selan Pixel pix;
58961126Selan XColor scrncolor;
59061126Selan XColor exactcolor;
59161126Selan
59261126Selan if (XAllocNamedColor(display, DefaultColormap(display, screen),
59361126Selan colorName, &scrncolor, &exactcolor)) {
59461126Selan pix = scrncolor.pixel;
59561126Selan }
59661126Selan else {
59761126Selan pix = dfltPix;
59861126Selan }
59961126Selan
60061126Selan return pix;
60161126Selan }
60261126Selan
603