1*61126Selan /* 2*61126Selan Xroach - A game of skill. Try to find the roaches under your windows. 3*61126Selan 4*61126Selan Copyright 1991 by J.T. Anderson 5*61126Selan 6*61126Selan jta@locus.com 7*61126Selan 8*61126Selan This program may be freely distributed provided that all 9*61126Selan copyright notices are retained. 10*61126Selan 11*61126Selan To build: 12*61126Selan cc -o xroach roach.c -lX11 [-lsocketorwhatever] [-lm] [-l...] 13*61126Selan 14*61126Selan Dedicated to Greg McFarlane. (gregm@otc.otca.oz.au) 15*61126Selan */ 16*61126Selan #include <X11/Xlib.h> 17*61126Selan #include <X11/Xutil.h> 18*61126Selan #include <X11/Xos.h> 19*61126Selan 20*61126Selan #include <stdio.h> 21*61126Selan #include <math.h> 22*61126Selan #include <signal.h> 23*61126Selan #include <stdlib.h> 24*61126Selan 25*61126Selan char Copyright[] = "Xroach\nCopyright 1991 J.T. Anderson"; 26*61126Selan 27*61126Selan #include "roachmap.h" 28*61126Selan 29*61126Selan typedef unsigned long Pixel; 30*61126Selan typedef int ErrorHandler(); 31*61126Selan 32*61126Selan #define SCAMPER_EVENT (LASTEvent + 1) 33*61126Selan 34*61126Selan #if !defined(GRAB_SERVER) 35*61126Selan #define GRAB_SERVER 0 36*61126Selan #endif 37*61126Selan 38*61126Selan Display *display; 39*61126Selan int screen; 40*61126Selan Window rootWin; 41*61126Selan unsigned int display_width, display_height; 42*61126Selan int center_x, center_y; 43*61126Selan GC gc; 44*61126Selan char *display_name = NULL; 45*61126Selan Pixel black, white; 46*61126Selan 47*61126Selan int done = 0; 48*61126Selan int eventBlock = 0; 49*61126Selan int errorVal = 0; 50*61126Selan 51*61126Selan typedef struct Roach { 52*61126Selan RoachMap *rp; 53*61126Selan int index; 54*61126Selan float x; 55*61126Selan float y; 56*61126Selan int intX; 57*61126Selan int intY; 58*61126Selan int hidden; 59*61126Selan int turnLeft; 60*61126Selan int steps; 61*61126Selan } Roach; 62*61126Selan 63*61126Selan Roach *roaches; 64*61126Selan int maxRoaches = 10; 65*61126Selan int curRoaches = 0; 66*61126Selan float roachSpeed = 20.0; 67*61126Selan 68*61126Selan Region rootVisible = NULL; 69*61126Selan 70*61126Selan void Usage(); 71*61126Selan void SigHandler(); 72*61126Selan void AddRoach(); 73*61126Selan void MoveRoach(); 74*61126Selan void DrawRoaches(); 75*61126Selan void CoverRoot(); 76*61126Selan int CalcRootVisible(); 77*61126Selan int MarkHiddenRoaches(); 78*61126Selan Pixel AllocNamedColor(); 79*61126Selan 80*61126Selan void 81*61126Selan main(ac, av) 82*61126Selan int ac; 83*61126Selan char *av[]; 84*61126Selan { 85*61126Selan XGCValues xgcv; 86*61126Selan int ax; 87*61126Selan char *arg; 88*61126Selan RoachMap *rp; 89*61126Selan int rx; 90*61126Selan float angle; 91*61126Selan XEvent ev; 92*61126Selan char *roachColor = "black"; 93*61126Selan int nVis; 94*61126Selan int needCalc; 95*61126Selan 96*61126Selan /* 97*61126Selan Process command line options. 98*61126Selan */ 99*61126Selan for (ax=1; ax<ac; ax++) { 100*61126Selan arg = av[ax]; 101*61126Selan if (strcmp(arg, "-display") == 0) { 102*61126Selan display_name = av[++ax]; 103*61126Selan } 104*61126Selan else if (strcmp(arg, "-rc") == 0) { 105*61126Selan roachColor = av[++ax]; 106*61126Selan } 107*61126Selan else if (strcmp(arg, "-speed") == 0) { 108*61126Selan roachSpeed = atof(av[++ax]); 109*61126Selan } 110*61126Selan else if (strcmp(arg, "-roaches") == 0) { 111*61126Selan maxRoaches = strtol(av[++ax], (char **)NULL, 0); 112*61126Selan } 113*61126Selan else { 114*61126Selan Usage(); 115*61126Selan } 116*61126Selan } 117*61126Selan 118*61126Selan srand((int)time((long *)NULL)); 119*61126Selan 120*61126Selan /* 121*61126Selan Catch some signals so we can erase any visible roaches. 122*61126Selan */ 123*61126Selan signal(SIGKILL, SigHandler); 124*61126Selan signal(SIGINT, SigHandler); 125*61126Selan signal(SIGTERM, SigHandler); 126*61126Selan signal(SIGHUP, SigHandler); 127*61126Selan 128*61126Selan display = XOpenDisplay(display_name); 129*61126Selan if (display == NULL) { 130*61126Selan if (display_name == NULL) display_name = getenv("DISPLAY"); 131*61126Selan (void) fprintf(stderr, "%s: cannot connect to X server %s\n", av[0], 132*61126Selan display_name ? display_name : "(default)"); 133*61126Selan exit(1); 134*61126Selan } 135*61126Selan 136*61126Selan screen = DefaultScreen(display); 137*61126Selan rootWin = RootWindow(display, screen); 138*61126Selan black = BlackPixel(display, screen); 139*61126Selan white = WhitePixel(display, screen); 140*61126Selan 141*61126Selan display_width = DisplayWidth(display, screen); 142*61126Selan display_height = DisplayHeight(display, screen); 143*61126Selan center_x = display_width / 2; 144*61126Selan center_y = display_height / 2; 145*61126Selan 146*61126Selan /* 147*61126Selan Create roach pixmaps at several orientations. 148*61126Selan */ 149*61126Selan for (ax=0; ax<360; ax+=ROACH_ANGLE) { 150*61126Selan rx = ax / ROACH_ANGLE; 151*61126Selan angle = rx * 0.261799387799; 152*61126Selan rp = &roachPix[rx]; 153*61126Selan rp->pixmap = XCreateBitmapFromData(display, rootWin, 154*61126Selan rp->roachBits, rp->width, rp->height); 155*61126Selan rp->sine = sin(angle); 156*61126Selan rp->cosine = cos(angle); 157*61126Selan } 158*61126Selan 159*61126Selan roaches = (Roach *)malloc(sizeof(Roach) * maxRoaches); 160*61126Selan 161*61126Selan gc = XCreateGC(display, rootWin, 0L, &xgcv); 162*61126Selan XSetForeground(display, gc, AllocNamedColor(roachColor, black)); 163*61126Selan XSetFillStyle(display, gc, FillStippled); 164*61126Selan 165*61126Selan while (curRoaches < maxRoaches) 166*61126Selan AddRoach(); 167*61126Selan 168*61126Selan XSelectInput(display, rootWin, ExposureMask | SubstructureNotifyMask); 169*61126Selan 170*61126Selan needCalc = 1; 171*61126Selan while (!done) { 172*61126Selan if (XPending(display)) { 173*61126Selan XNextEvent(display, &ev); 174*61126Selan } 175*61126Selan else { 176*61126Selan if (needCalc) { 177*61126Selan needCalc = CalcRootVisible(); 178*61126Selan } 179*61126Selan nVis = MarkHiddenRoaches(); 180*61126Selan if (nVis) { 181*61126Selan ev.type = SCAMPER_EVENT; 182*61126Selan } 183*61126Selan else { 184*61126Selan DrawRoaches(); 185*61126Selan eventBlock = 1; 186*61126Selan XNextEvent(display, &ev); 187*61126Selan eventBlock = 0; 188*61126Selan } 189*61126Selan } 190*61126Selan 191*61126Selan switch (ev.type) { 192*61126Selan 193*61126Selan case SCAMPER_EVENT: 194*61126Selan for (rx=0; rx<curRoaches; rx++) { 195*61126Selan if (!roaches[rx].hidden) 196*61126Selan MoveRoach(rx); 197*61126Selan } 198*61126Selan DrawRoaches(); 199*61126Selan XSync(display, False); 200*61126Selan break; 201*61126Selan 202*61126Selan case Expose: 203*61126Selan case MapNotify: 204*61126Selan case UnmapNotify: 205*61126Selan case ConfigureNotify: 206*61126Selan needCalc = 1; 207*61126Selan break; 208*61126Selan 209*61126Selan } 210*61126Selan } 211*61126Selan 212*61126Selan CoverRoot(); 213*61126Selan 214*61126Selan XCloseDisplay(display); 215*61126Selan } 216*61126Selan 217*61126Selan #define USEPRT(msg) fprintf(stderr, msg) 218*61126Selan 219*61126Selan void 220*61126Selan Usage() 221*61126Selan { 222*61126Selan USEPRT("Usage: xroach [options]\n\n"); 223*61126Selan USEPRT("Options:\n"); 224*61126Selan USEPRT(" -display displayname\n"); 225*61126Selan USEPRT(" -rc roachcolor\n"); 226*61126Selan USEPRT(" -roaches numroaches\n"); 227*61126Selan USEPRT(" -speed roachspeed\n"); 228*61126Selan 229*61126Selan exit(1); 230*61126Selan } 231*61126Selan 232*61126Selan void 233*61126Selan SigHandler() 234*61126Selan { 235*61126Selan 236*61126Selan /* 237*61126Selan If we are blocked, no roaches are visible and we can just bail 238*61126Selan out. If we are not blocked, then let the main procedure clean 239*61126Selan up the root window. 240*61126Selan */ 241*61126Selan if (eventBlock) { 242*61126Selan XCloseDisplay(display); 243*61126Selan exit(0); 244*61126Selan } 245*61126Selan else { 246*61126Selan done = 1; 247*61126Selan } 248*61126Selan } 249*61126Selan 250*61126Selan /* 251*61126Selan Generate random integer between 0 and maxVal-1. 252*61126Selan */ 253*61126Selan int 254*61126Selan RandInt(maxVal) 255*61126Selan int maxVal; 256*61126Selan { 257*61126Selan return rand() % maxVal; 258*61126Selan } 259*61126Selan 260*61126Selan /* 261*61126Selan Check for roach completely in specified rectangle. 262*61126Selan */ 263*61126Selan int 264*61126Selan RoachInRect(roach, rx, ry, x, y, width, height) 265*61126Selan Roach *roach; 266*61126Selan int rx; 267*61126Selan int ry; 268*61126Selan int x; 269*61126Selan int y; 270*61126Selan unsigned int width; 271*61126Selan unsigned int height; 272*61126Selan { 273*61126Selan if (rx < x) return 0; 274*61126Selan if ((rx + roach->rp->width) > (x + width)) return 0; 275*61126Selan if (ry < y) return 0; 276*61126Selan if ((ry + roach->rp->height) > (y + height)) return 0; 277*61126Selan 278*61126Selan return 1; 279*61126Selan } 280*61126Selan 281*61126Selan /* 282*61126Selan Check for roach overlapping specified rectangle. 283*61126Selan */ 284*61126Selan int 285*61126Selan RoachOverRect(roach, rx, ry, x, y, width, height) 286*61126Selan Roach *roach; 287*61126Selan int rx; 288*61126Selan int ry; 289*61126Selan int x; 290*61126Selan int y; 291*61126Selan unsigned int width; 292*61126Selan unsigned int height; 293*61126Selan { 294*61126Selan if (rx >= (x + width)) return 0; 295*61126Selan if ((rx + roach->rp->width) <= x) return 0; 296*61126Selan if (ry >= (y + height)) return 0; 297*61126Selan if ((ry + roach->rp->height) <= y) return 0; 298*61126Selan 299*61126Selan return 1; 300*61126Selan } 301*61126Selan 302*61126Selan /* 303*61126Selan Give birth to a roach. 304*61126Selan */ 305*61126Selan void 306*61126Selan AddRoach() 307*61126Selan { 308*61126Selan Roach *r; 309*61126Selan 310*61126Selan if (curRoaches < maxRoaches) { 311*61126Selan r = &roaches[curRoaches++]; 312*61126Selan r->index = RandInt(ROACH_HEADINGS); 313*61126Selan r->rp = &roachPix[r->index]; 314*61126Selan r->x = RandInt(display_width - r->rp->width); 315*61126Selan r->y = RandInt(display_height - r->rp->height); 316*61126Selan r->intX = -1; 317*61126Selan r->intY = -1; 318*61126Selan r->hidden = 0; 319*61126Selan r->steps = RandInt(200); 320*61126Selan r->turnLeft = RandInt(100) >= 50; 321*61126Selan } 322*61126Selan } 323*61126Selan 324*61126Selan /* 325*61126Selan Turn a roach. 326*61126Selan */ 327*61126Selan void 328*61126Selan TurnRoach(roach) 329*61126Selan Roach *roach; 330*61126Selan { 331*61126Selan if (roach->index != (roach->rp - roachPix)) return; 332*61126Selan 333*61126Selan if (roach->turnLeft) { 334*61126Selan roach->index += (RandInt(30) / 10) + 1; 335*61126Selan if (roach->index >= ROACH_HEADINGS) 336*61126Selan roach->index -= ROACH_HEADINGS; 337*61126Selan } 338*61126Selan else { 339*61126Selan roach->index -= (RandInt(30) / 10) + 1; 340*61126Selan if (roach->index < 0) 341*61126Selan roach->index += ROACH_HEADINGS; 342*61126Selan } 343*61126Selan } 344*61126Selan 345*61126Selan /* 346*61126Selan Move a roach. 347*61126Selan */ 348*61126Selan void 349*61126Selan MoveRoach(rx) 350*61126Selan int rx; 351*61126Selan { 352*61126Selan Roach *roach; 353*61126Selan Roach *r2; 354*61126Selan float newX; 355*61126Selan float newY; 356*61126Selan int ii; 357*61126Selan 358*61126Selan roach = &roaches[rx]; 359*61126Selan newX = roach->x + (roachSpeed * roach->rp->cosine); 360*61126Selan newY = roach->y - (roachSpeed * roach->rp->sine); 361*61126Selan 362*61126Selan if (RoachInRect(roach, (int)newX, (int)newY, 363*61126Selan 0, 0, display_width, display_height)) { 364*61126Selan 365*61126Selan roach->x = newX; 366*61126Selan roach->y = newY; 367*61126Selan 368*61126Selan if (roach->steps-- <= 0) { 369*61126Selan TurnRoach(roach); 370*61126Selan roach->steps = RandInt(200); 371*61126Selan } 372*61126Selan 373*61126Selan for (ii=rx+1; ii<curRoaches; ii++) { 374*61126Selan r2 = &roaches[ii]; 375*61126Selan if (RoachOverRect(roach, (int)newX, (int)newY, 376*61126Selan r2->intX, r2->intY, r2->rp->width, r2->rp->height)) { 377*61126Selan 378*61126Selan TurnRoach(roach); 379*61126Selan } 380*61126Selan } 381*61126Selan } 382*61126Selan else { 383*61126Selan TurnRoach(roach); 384*61126Selan } 385*61126Selan } 386*61126Selan 387*61126Selan /* 388*61126Selan Draw all roaches. 389*61126Selan */ 390*61126Selan void 391*61126Selan DrawRoaches() 392*61126Selan { 393*61126Selan Roach *roach; 394*61126Selan int rx; 395*61126Selan 396*61126Selan for (rx=0; rx<curRoaches; rx++) { 397*61126Selan roach = &roaches[rx]; 398*61126Selan 399*61126Selan if (roach->intX >= 0) { 400*61126Selan XClearArea(display, rootWin, roach->intX, roach->intY, 401*61126Selan roach->rp->width, roach->rp->height, False); 402*61126Selan } 403*61126Selan } 404*61126Selan 405*61126Selan for (rx=0; rx<curRoaches; rx++) { 406*61126Selan roach = &roaches[rx]; 407*61126Selan 408*61126Selan if (!roach->hidden) { 409*61126Selan roach->intX = roach->x; 410*61126Selan roach->intY = roach->y; 411*61126Selan roach->rp = &roachPix[roach->index]; 412*61126Selan 413*61126Selan XSetStipple(display, gc, roach->rp->pixmap); 414*61126Selan XSetTSOrigin(display, gc, roach->intX, roach->intY); 415*61126Selan XFillRectangle(display, rootWin, gc, 416*61126Selan roach->intX, roach->intY, roach->rp->width, roach->rp->height); 417*61126Selan } 418*61126Selan else { 419*61126Selan roach->intX = -1; 420*61126Selan } 421*61126Selan } 422*61126Selan } 423*61126Selan 424*61126Selan /* 425*61126Selan Cover root window to erase roaches. 426*61126Selan */ 427*61126Selan void 428*61126Selan CoverRoot() 429*61126Selan { 430*61126Selan XSetWindowAttributes xswa; 431*61126Selan long wamask; 432*61126Selan Window roachWin; 433*61126Selan 434*61126Selan xswa.background_pixmap = ParentRelative; 435*61126Selan xswa.override_redirect = True; 436*61126Selan wamask = CWBackPixmap | CWOverrideRedirect; 437*61126Selan roachWin = XCreateWindow(display, rootWin, 0, 0, 438*61126Selan display_width, display_height, 0, CopyFromParent, 439*61126Selan InputOutput, CopyFromParent, wamask, &xswa); 440*61126Selan XLowerWindow(display, roachWin); 441*61126Selan XMapWindow(display, roachWin); 442*61126Selan XFlush(display); 443*61126Selan } 444*61126Selan 445*61126Selan #if !GRAB_SERVER 446*61126Selan 447*61126Selan int 448*61126Selan RoachErrors(dpy, err) 449*61126Selan Display *dpy; 450*61126Selan XErrorEvent *err; 451*61126Selan { 452*61126Selan errorVal = err->error_code; 453*61126Selan 454*61126Selan return 0; 455*61126Selan } 456*61126Selan 457*61126Selan #endif /* GRAB_SERVER */ 458*61126Selan 459*61126Selan /* 460*61126Selan Calculate Visible region of root window. 461*61126Selan */ 462*61126Selan int 463*61126Selan CalcRootVisible() 464*61126Selan { 465*61126Selan Region covered; 466*61126Selan Region visible; 467*61126Selan Window *children; 468*61126Selan int nChildren; 469*61126Selan Window dummy; 470*61126Selan XWindowAttributes wa; 471*61126Selan int wx; 472*61126Selan XRectangle rect; 473*61126Selan int winX, winY; 474*61126Selan unsigned int winHeight, winWidth; 475*61126Selan unsigned int borderWidth; 476*61126Selan unsigned int depth; 477*61126Selan 478*61126Selan /* 479*61126Selan If we don't grab the server, the XGetWindowAttribute or XGetGeometry 480*61126Selan calls can abort us. On the other hand, the server grabs can make for 481*61126Selan some annoying delays. 482*61126Selan */ 483*61126Selan #if GRAB_SERVER 484*61126Selan XGrabServer(display); 485*61126Selan #else 486*61126Selan XSetErrorHandler(RoachErrors); 487*61126Selan #endif 488*61126Selan 489*61126Selan /* 490*61126Selan Get children of root. 491*61126Selan */ 492*61126Selan XQueryTree(display, rootWin, &dummy, &dummy, &children, &nChildren); 493*61126Selan 494*61126Selan /* 495*61126Selan For each mapped child, add the window rectangle to the covered 496*61126Selan region. 497*61126Selan */ 498*61126Selan covered = XCreateRegion(); 499*61126Selan for (wx=0; wx<nChildren; wx++) { 500*61126Selan if (XEventsQueued(display, QueuedAlready)) return 1; 501*61126Selan errorVal = 0; 502*61126Selan XGetWindowAttributes(display, children[wx], &wa); 503*61126Selan if (errorVal) continue; 504*61126Selan if (wa.map_state == IsViewable) { 505*61126Selan XGetGeometry(display, children[wx], &dummy, &winX, &winY, 506*61126Selan &winWidth, &winHeight, &borderWidth, &depth); 507*61126Selan if (errorVal) continue; 508*61126Selan rect.x = winX; 509*61126Selan rect.y = winY; 510*61126Selan rect.width = winWidth + (borderWidth * 2); 511*61126Selan rect.height = winHeight + (borderWidth * 2); 512*61126Selan XUnionRectWithRegion(&rect, covered, covered); 513*61126Selan } 514*61126Selan } 515*61126Selan XFree(children); 516*61126Selan 517*61126Selan #if GRAB_SERVER 518*61126Selan XUngrabServer(display); 519*61126Selan #else 520*61126Selan XSetErrorHandler((ErrorHandler *)NULL); 521*61126Selan #endif 522*61126Selan 523*61126Selan /* 524*61126Selan Subtract the covered region from the root window region. 525*61126Selan */ 526*61126Selan visible = XCreateRegion(); 527*61126Selan rect.x = 0; 528*61126Selan rect.y = 0; 529*61126Selan rect.width = display_width; 530*61126Selan rect.height = display_height; 531*61126Selan XUnionRectWithRegion(&rect, visible, visible); 532*61126Selan XSubtractRegion(visible, covered, visible); 533*61126Selan XDestroyRegion(covered); 534*61126Selan 535*61126Selan /* 536*61126Selan Save visible region globally. 537*61126Selan */ 538*61126Selan if (rootVisible) 539*61126Selan XDestroyRegion(rootVisible); 540*61126Selan rootVisible = visible; 541*61126Selan 542*61126Selan 543*61126Selan /* 544*61126Selan Mark all roaches visible. 545*61126Selan */ 546*61126Selan for (wx=0; wx<curRoaches; wx++) 547*61126Selan roaches[wx].hidden = 0; 548*61126Selan 549*61126Selan return 0; 550*61126Selan } 551*61126Selan 552*61126Selan /* 553*61126Selan Mark hidden roaches. 554*61126Selan */ 555*61126Selan int 556*61126Selan MarkHiddenRoaches() 557*61126Selan { 558*61126Selan int rx; 559*61126Selan Roach *r; 560*61126Selan int nVisible; 561*61126Selan 562*61126Selan nVisible = 0; 563*61126Selan for (rx=0; rx<curRoaches; rx++) { 564*61126Selan r = &roaches[rx]; 565*61126Selan 566*61126Selan if (!r->hidden) { 567*61126Selan if (r->intX > 0 && XRectInRegion(rootVisible, r->intX, r->intY, 568*61126Selan r->rp->width, r->rp->height) == RectangleOut) { 569*61126Selan r->hidden = 1; 570*61126Selan } 571*61126Selan else { 572*61126Selan nVisible++; 573*61126Selan } 574*61126Selan } 575*61126Selan } 576*61126Selan 577*61126Selan return nVisible; 578*61126Selan } 579*61126Selan 580*61126Selan /* 581*61126Selan Allocate a color by name. 582*61126Selan */ 583*61126Selan Pixel 584*61126Selan AllocNamedColor(colorName, dfltPix) 585*61126Selan char *colorName; 586*61126Selan Pixel dfltPix; 587*61126Selan { 588*61126Selan Pixel pix; 589*61126Selan XColor scrncolor; 590*61126Selan XColor exactcolor; 591*61126Selan 592*61126Selan if (XAllocNamedColor(display, DefaultColormap(display, screen), 593*61126Selan colorName, &scrncolor, &exactcolor)) { 594*61126Selan pix = scrncolor.pixel; 595*61126Selan } 596*61126Selan else { 597*61126Selan pix = dfltPix; 598*61126Selan } 599*61126Selan 600*61126Selan return pix; 601*61126Selan } 602*61126Selan 603