/*

©Copyright 1984, 1988, 1990, 1993-1995 by Rick Jansen, all rights reserved.

  X S N O W 
        *
  *   *
    *       *
        *
   *

 In 1984 the first Macintosh program I wrote was a computer christmas
 card, which showed a picture of a snowman and falling snow. Later
 a Father Xmas in his sleigh was added. I converted this program to
 an undying desk accessory in 1988 with several additions.
 But, little boys grow up, and when they are forced to a workstation
 they want their thingies there too. So here is Xsnow. For *you*.

 Happy winter and a merry X-mas!

 Rick Jansen
 (rick@sara.nl)


 Availability
 ------------
 Xsnow is available directly from me via my WWW page:

   http://www.sara.nl/Rick.Jansen

 Click on the appropriate item and the tar file will be downloaded
 upon your request.

 Xsnow is also archived at ftp.x.org in the contrib/games directory.
 Ftp to ftp.x.org and enter 'anonymous' as the login name, your
 e-mail address as the password. Then 'cd contrib/games' and a 
 'get xsnow.README' for further information.

 If all else fails you can get a new version from me by e-mail, as
 an uuencoded and compressed tar-file. (email: rick@sara.nl)

 Copyright
 ---------
 Xsnow is available freely and you may give it to other people as is, 
 but I retain all rights. Therefore it does not classify as 'Public 
 Domain' software.

 Credits
 -------
 Xsnow borrows code for drawing on the desktop (the 'root' window as
 they insist on calling it in X) from the infamous xroach program, 
 which is copyright 1991 by J.T. Anderson (jta@locus.com).
 Xroach is available in the 'contrib' directory of 'ftp.x.org' via 
 anonymous ftp.

 For compatibility with virtual window managers Xsnow uses vroot.h
 vroot.h is copyright 1991 by Andreas Stolcke, copyright 1990 by 
 Solbourne Computer Inc. (stolcke@ICSI.Berkeley.EDU)
 The complete distribution of vroot is available in the 'contrib'
 directory of 'ftp.x.org' via anonymous ftp as 'vroot.shar.Z' 

 The big Santa with the nice moving antlers and reins was made by 
 Holger Veit (Holger.Veit@gmd.de).

 The code for wind was done by Eiichi TAZOE (tazoe@yamato.ibm.co.jp,
 tazoe@vnet.ibm.com)
    
 Installation
 ------------
  To build:
      xmkmf
      make depend
      make
  or:
      cc -o xsnow snow.c -lX11 [-lsocketorwhatever] [-lm] [-l...]

 Silicon Graphics
 ----------------
 Silicon Graphics and Irix 5.x users may not see any snow or
 Santa at all, as long the desktop icons are visible. To
 circumvent this problem issue this command:

    /usr/lib/desktop/telldesktop quit

 The icons will disappear and xsnow will work perfectly. To
 restart the desktop just start /usr/lib/desktop/startdesktop
 or select Desktop->Home Directory from the toolchest.

 It's even possible to have both - desktop icons and xsnow
 (and even multiple desks). You need to modify the window
 manager's resource file 4DWm, the file ~/.desktop-
 `hostname`/4DWm. Example:

 *Global.backgroundDescription:   -execute /etc/killall -TERM
 xsnow ; /usr/local/bin/xsnow
 *Desk 1.backgroundDescription:   -execute /etc/killall -TERM
 xsnow ; /usr/local/bin/xsnow
 *Desk 2.backgroundDescription:   -execute /etc/killall -TERM
 xsnow ; /usr/local/bin/xsnow

 Restart the he toolchest and Xsnow should appear. What this
 does is stop the currently running Xsnow and start a new one
 when you switch to another desktop.

 HP
 --
 HP also uses a Workspace Manager which may interfere with
 Xsnow.  If Xsnow does not appear:  In the "Style Manager",
 choose "Backdrop" and select "NoBackdrop".  You should now
 be able to run Xsnow.


 Modification history
 --------------------
 03DEC93 version 0
 04DEC93 background setting
 06DEC93 delay in main loop using select
 10DEC93 keep snow on top of windows and at bottom of display
 11DEC93 snow kept sticking to the right of display (1.11)
 11DEC93 updated calcwindowtops
 12DEC93 call calcwindowtops after last exposure event only
 13DEC93 vroot added for virtual window managers
 14DEC93 separate options for snowdepth on windows and on screen
 15DEC93 patch for fvwm from Mike Hollick (hollick@graphics.cis.upenn.edu)
 16DEC93 Really building up snow at bottom of screen
 16DEC93 A choice of Santa's. Thanks to Holger Veit (Holger.Veit@gmd.de)
         for Santa no. 2. Santa 1 was derived from 2 by scaling down.
 20DEC93 Aaaargh! gnu C doesn't like the initialization of the new
         Santa pixmaps. Extra braces do the trick, which is so often the case
 20DEC93 Deleted PaintSnowAtBottom. It's more of a surprise this way.
 21DEC93 In case of negative x coordinates adapt width (CalcWindowTops)
 22DEC93 Rudolf's red nose. Silly. Grmbll.
 22DEC93 Bug with -nokeepsnowonwindows removed
 10JAN94 Improved handling of erasing snow at screen bottom in case of 
         window in or under the snow.
 18FEB94 X-speed really 0 inside snow layers - prevents snowflakes from 
         erasing already fallen snow a bit better
 30OCT94 More efficiency due to GC's in server and not drawing where
         the windows are.
 30OCT94 1.30 Eiichi TAZOE's code for wind included in general distribution.
 11NOV94 Bug in XRectInRegion call resolved (santa not appearing gradually
         from behind windows)
 15NOV94 Unsigned longs changed to longs, as strtoul is troublesome on many
         systems (snowDelay)
 08DEC94 Snow is really building up on windows now
 10DEC94 1.36 Handle Popups (no snow on them)
 16DEC94 1.37 -offset and (-nokeepsnowonwindows and -nokeepsnowonscreen back)
 20DEC94 1.38 Let Santa be a real snowplough now
 21DEC94 1.39 VMS fix for uSsleep, thanks to Ulrich Kabatek 
              <Kaba@isr.uni-stuttgart.d400.de>
 19NOV95 1.40 Memory leak fixed in CalcWindowTops, thanks to Satoshi Asami
              <asami@cs.berkeley.edu> 
 19NOV95 1.40 Greatly increased performance when using solid backgrounds
              by using XFillRectangle instead of XClearArea. Thanks to
              Benedikt Kessler <bjk@benedikt.munich.sgi.com>
 20NOV95 1.40 Bug fixed with erasing snow at bot of screen
*/

#define debug 0

#define VERSION "Xsnow-1.40, November 22nd 1995 by Rick Jansen (rick@sara.nl)"

#ifdef VMS
#include <socket.h>
# if defined(__SOCKET_TYPEDEFS) && !defined(CADDR_T)
#  define CADDR_T
# endif
#endif


#include <X11/Intrinsic.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>


/**** V R O O T ****/
/* For vroot.h see the credits at the beginning of Xsnow */
/***#include <X11/vroot.h>   /* if vroot.h installed in /usr/include/X11 ***/
#include "vroot.h"  /* Use this if vroot.h installed in current directory */


#include <stdio.h>
#include <math.h>
#include <signal.h>
#include <limits.h>

#if defined(__STDC__) || defined(VMS)
#include <stdlib.h>
#else
long strtol();
/*double strtod();*/
char *getenv();
#endif

char Copyright[] = "\nXsnow\nCopyright 1984, 1988, 1990, 1993, 1994 by Rick Jansen, all rights reserved.\n";

#include "snowmap.h"

/***typedef unsigned long Pixel;***/
typedef int ErrorHandler();

#if !defined(GRAB_SERVER)
#define GRAB_SERVER	0
#endif

Display *display;
int screen;
Window rootWin;
int display_width, display_height;
int center_x, center_y;
GC gc;
char *display_name = NULL;
Pixel black, white;

int done = 0;
int eventBlock = 0;
int errorVal = 0;

int flake;
int SmoothWhirl = 1;
int NoTrees = 0;
int NoSanta = 0;
int Rudolf = 1;
int NoKeepSnow = 0;
int NoKeepSBot = 0;
int NoKeepSWin = 0;
int NoPopuphandling = 0;
int NoWind = 0;
unsigned int borderWidth = 0;
int SnowOffset = 0;
int UseFillForClear = 0;

Snow *snowflakes;
int MaxSnowFlakeHeight = 0;  /* Biggest flake */
int maxSnowflakes = INITIALSNOWFLAKES;
int curSnowflakes = 0;
long snowDelay = 50000; /* useconds */
int MaxXStep = MAXXSTEP;
int MaxYStep = MAXYSTEP;
int WhirlFactor = WHIRLFACTOR;
int MaxWinSnowDepth = INITWINSNOWDEPTH;
int MaxScrSnowDepth = INITSCRSNOWDEPTH;

Santa Claus;   

/* The default Santa 0,1,2 */
int SantaSize = 1;
int SantaSpeed = -1;  /* Not initialized yet */


/* For building up snow at bottom of screen and windows */
Region Windows = NULL;
Region SnowCatchR = NULL;
Region SnowAllowedR = NULL;
Region SubR = NULL;
XRectangle AddRect;
int AddX,AddY;
int ClearX, ClearY;

/* Wind stuff */
int diff=0;
int wind = 0;
int direction=0;
int WindTimer=30;
int current_snow_height;
int geta=0;

/* Forward decls */
void Usage();
void SigHandler();
void SigHupHandler();
void InitSnowflake();
void UpdateSnowflake();
void DrawSnowflake();
void EraseSnowflake();
void PaintSnowAtBottom();
void DrawTannenbaum();
void InitSanta();
void DrawSanta();
void EraseSanta();
void UpdateSanta();
void uSsleep();
int CalcWindowTops();
Pixel AllocNamedColor();
void sig_alarm();

/* Colo(u)rs */
char *snowColor = "snow";
char *slcColor = "black";
char *blackColor = "black";
char *redColor = "red";
char *whiteColor = "white";
char *bgColor = "none";
char *trColor = "chartreuse";
/* The author thoroughly recommends a cup of tea */
/* with a dash of green Chartreuse. Jum!         */
char *greenColor = "chartreuse"; 
Pixel redPix;
Pixel whitePix;
Pixel greenPix;
Pixel blackPix;
Pixel snowcPix;
Pixel bgPix;
Pixel trPix;
Pixel slcPix;

/* GC's */
GC SnowGC[SNOWFLAKEMAXTYPE+1];
GC ClearSnowGC;
GC SleighGCs[3];
GC SantaGC,RudolfGC,FurGC;
GC TreesGC;

#ifdef VMS
int
#else
void
#endif
main(ac, av)
int ac;
char *av[];
{
    XGCValues xgcv;
    int ax;
    char *arg;
    SnowMap *rp;
    TannenbaumMap *tp;
    SleighMap *sp; 
    SantaMap *stp;
    SantaFurMap *stfp;
    XEvent ev;
    int needCalc;
    int Exposed;
    int ConfigureNotified;
    int i; 
    int x,y;

    
    /*
       Process command line options.
    */
    for (ax=1; ax<ac; ax++) {
	arg = av[ax];
	if (strcmp(arg, "-display") == 0) {
	  display_name = av[++ax];
	}
	else if (strcmp(arg, "-sc") == 0) {
	  snowColor = av[++ax];
	}
	else if (strcmp(arg, "-bg") == 0) {
	  bgColor = av[++ax];
 
          /* If solid colour used as bg we can use XFillArea for clear */
          /* This results in a big performance improvement */
          /*** Uncomment this if sure all Xservers are faster with
               XFillRectangle than with XClearArea ***/
          /*** UseFillForClear = 1; ***/
        }
        else if (strcmp(arg, "-solidbg") == 0) {
          /* If solid colour used as bg we can use XFillArea for clear */
          /* This results in a big performance improvement */
          UseFillForClear = 1;
	}
	else if (strcmp(arg, "-tc") == 0) {
	  trColor = av[++ax];
	}
	else if (strcmp(arg, "-slc") == 0) {
	  slcColor = av[++ax];
	}
	else if (strcmp(arg, "-delay") == 0) {
	  snowDelay = strtol(av[++ax], (char **)NULL,0);
          snowDelay = snowDelay * 1000;  /* ms to us */
	}
	else if (strcmp(arg, "-snowflakes") == 0) {
	  maxSnowflakes = strtol(av[++ax], (char **)NULL, 0);
	}
        else if (strcmp(arg, "-unsmooth") == 0) {
          SmoothWhirl = 0;
        }
	else if (strcmp(arg, "-whirl") == 0) {
	  WhirlFactor = strtol(av[++ax], (char **)NULL, 0);
	}
	else if (strcmp(arg, "-nowind") == 0) {
	  NoWind = 1;
	}
	else if (strcmp(arg, "-windtimer") == 0) {
	  WindTimer = strtol(av[++ax], (char **)NULL, 0);
	}
	else if (strcmp(arg, "-xspeed") == 0) {
	  MaxXStep = strtol(av[++ax], (char **)NULL, 0);
	}
	else if (strcmp(arg, "-yspeed") == 0) {
	  MaxYStep = strtol(av[++ax], (char **)NULL, 0);
	}
	else if (strcmp(arg, "-wsnowdepth") == 0) {
	  MaxWinSnowDepth = strtol(av[++ax], (char **)NULL, 0);
	}
	else if (strcmp(arg, "-ssnowdepth") == 0) {
	  MaxScrSnowDepth = strtol(av[++ax], (char **)NULL, 0);
	}
	else if (strcmp(arg, "-offset") == 0) {
	  SnowOffset = strtol(av[++ax], (char **)NULL, 0);
	}
        else if (strcmp(arg, "-notrees") == 0) {
          NoTrees = 1;     
        }
        else if (strcmp(arg, "-nosanta") == 0) {
          NoSanta = 1;     
        }
        else if (strcmp(arg, "-santa") == 0) {
	  SantaSize = strtol(av[++ax], (char **)NULL, 0);
        }
        else if (strcmp(arg, "-norudolf") == 0) {
	  Rudolf = 0;
        }
        else if (strcmp(arg, "-santaspeed") == 0) {
	  SantaSpeed = strtol(av[++ax], (char **)NULL, 0);
        }
        else if (strcmp(arg, "-nokeepsnow") == 0) {
          NoKeepSnow = 1;
          NoKeepSWin = 1;
          NoKeepSBot = 1;
        }
        else if (strcmp(arg, "-nokeepsnowonwindows") == 0) {
          NoKeepSWin = 1;
        }
        else if (strcmp(arg, "-nokeepsnowonscreen") == 0) {
          NoKeepSBot = 1;
        }
        else if (strcmp(arg, "-nonopopup") == 0) {
          NoPopuphandling = 1;
        }
        else if (strcmp(arg, "-version") == 0) {
          printf("\nThis is %s\n\n", VERSION);
          exit(0);
        }
	else {
	  Usage();
	}
    }

    printf("%s\n",VERSION);

    /* Some people... */
    if (maxSnowflakes > MAXSNOWFLAKES) {
      fprintf(stderr,"xx Maximum number of snowflakes is %d\n", MAXSNOWFLAKES);
      exit(1);
    }
    if ((SantaSize < 0) || (SantaSize > MAXSANTA)) {
      fprintf(stderr,"xx Maximum Santa is %d\n",MAXSANTA);
      exit(1);
    }

    /* Eskimo warning */
    if (strstr(snowColor,"yellow") != NULL)
      printf("\nWarning: don't eat yellow snow!\n\n");


    /* Santa speed is different for different size Santa */
    if (SantaSpeed < 0) SantaSpeed = Speed[SantaSize];

    /* Seed random */
#ifdef VMS
    srand((int)time((unsigned long *)NULL));
#else
    srand((int)time((long *)NULL));
#endif


    /*
       Catch some signals so we can erase any visible snowflakes.
    */
    signal(SIGKILL, SigHandler);
    signal(SIGINT, SigHandler);
    signal(SIGTERM, SigHandler);
#if debug
    signal(SIGHUP, SigHupHandler);
#else
    signal(SIGHUP, SigHandler);
#endif

    /* Wind stuff */
    signal(SIGALRM, sig_alarm);
    alarm(WindTimer);


    /* Open X */
    display = XOpenDisplay(display_name);
    if (display == NULL) {
	if (display_name == NULL) display_name = getenv("DISPLAY");
	(void) fprintf(stderr, "%s: cannot connect to X server %s\n", av[0],
	    display_name ? display_name : "(default)");
	exit(1);
    }

    screen = DefaultScreen(display);
    rootWin = RootWindow(display, screen);
    black = BlackPixel(display, screen);
    white = WhitePixel(display, screen);

    display_width = DisplayWidth(display, screen);
    display_height = DisplayHeight(display, screen);
    center_x = display_width / 2;
    center_y = display_height / 2;

    /* No snow at all yet */
    current_snow_height = display_height;

    /* Do not let all of the display snow under */
    if (MaxScrSnowDepth> (display_height-SNOWFREE)) {
      printf("xx Maximum snow depth set to %d\n", display_height-SNOWFREE);
      MaxScrSnowDepth = display_height-SNOWFREE;
    }
    

    /*
        P I X M A P S 
    */

    /* Create the snowflake pixmaps */
    for (flake=0; flake<=SNOWFLAKEMAXTYPE; flake++) {
      rp = &snowPix[flake];
      rp->pixmap = XCreateBitmapFromData(display, rootWin,
            rp->snowBits, rp->width, rp->height);
      /* Whats the biggest flake? (used for erasing kept snow later) */
      if (rp->height > MaxSnowFlakeHeight) MaxSnowFlakeHeight = rp->height;
    }

    /* Allocate structures containing the coordinates and things */
    snowflakes = (Snow *)malloc(sizeof(Snow) * MAXSNOWFLAKES);

    /* Create the tree pixmap */
    tp = &tannenbaumPix[0];
    tp->pixmap = XCreateBitmapFromData(display, rootWin,
            tp->tannenbaumBits, tp->width, tp->height);

    /* Create the Santa pixmaps */
    for (flake=0; flake<=2; flake++) {
      sp = &sleighPix[SantaSize][flake];
      sp->pixmap = XCreateBitmapFromData(display, rootWin,
            sp->sleighBits, sp->width, sp->height);
    }

    /* Pixmaps of the man in the red suit himself */
    stp = &santaPix[SantaSize];
    stp->pixmap = XCreateBitmapFromData(display, rootWin,
            stp->santaBits, stp->width, stp->height);
    /* The fur in his hat */
    stfp = &santaFurPix[SantaSize];
    stfp->pixmap = XCreateBitmapFromData(display, rootWin,
            stfp->santaFurBits, stfp->width, stfp->height);


    
    /* Allocate colors just once */
    redPix =   AllocNamedColor(redColor, black);
    whitePix = AllocNamedColor(whiteColor, white);
    greenPix = AllocNamedColor(greenColor, black);
    blackPix = AllocNamedColor(blackColor, black);
    snowcPix = AllocNamedColor(snowColor, white);   
    trPix = AllocNamedColor(trColor, black);
    slcPix = AllocNamedColor(slcColor, black);


    gc = XCreateGC(display, rootWin, 0, NULL);
    XGetGCValues(display, gc, 0, &xgcv);
    XSetForeground(display, gc, blackPix);
    XSetFillStyle(display, gc, FillStippled);

    /* Set the background color, if specified */
    if(strcmp(bgColor,"none") != 0) {
      bgPix = AllocNamedColor(bgColor, white);

      XSetWindowBackground(display, rootWin, bgPix);
      XClearWindow(display, rootWin);
      XFlush(display);

      /* If -solidbg not specified notify that this may improve things! */
      if (!UseFillForClear) {
        printf("\nNote: when using backgrounds of one color also specifying\n");
        printf("      -solidbg may greatly improve performance!\n");
      }
      else {
        ClearSnowGC = XCreateGC(display, rootWin, 0L, &xgcv);
        XCopyGC(display,gc,0,ClearSnowGC);
        XSetForeground(display,ClearSnowGC, bgPix);
      }
    }
    else if (UseFillForClear) {
      printf("\n");
      printf(
       "Sorry, -solidbg can only be specified with a background color (-bg)");
      printf("\n");
      exit(1);
    }
    
    /* Fill in GCs for snowflakes */
    for (flake=0; flake<=SNOWFLAKEMAXTYPE; flake++) {
      SnowGC[flake] = XCreateGC(display, rootWin, 0L, &xgcv);
      XCopyGC(display,gc,0,SnowGC[flake]);
      XSetStipple(display, SnowGC[flake], snowPix[flake].pixmap);
      XSetForeground(display,SnowGC[flake],snowcPix);
      XSetFillStyle(display, SnowGC[flake], FillStippled);
    }
    
    /* GC for trees */
    TreesGC = XCreateGC(display, rootWin, 0L, &xgcv);
    XCopyGC(display,gc,0,TreesGC);
    XSetStipple(display, TreesGC, tannenbaumPix[0].pixmap);
    XSetForeground(display,TreesGC,trPix);
    XSetFillStyle(display, TreesGC, FillStippled);

    /* GCs for sleighs */
    for (flake=0; flake<=2; flake++) {
      SleighGCs[flake] = XCreateGC(display, rootWin, 0L, &xgcv);
      XCopyGC(display,gc,0,SleighGCs[flake]);
      XSetForeground(display, SleighGCs[flake], slcPix);
      XSetFillStyle(display, SleighGCs[flake], FillStippled);
      XSetStipple(display,SleighGCs[flake],
                  sleighPix[SantaSize][flake].pixmap);
    }

    /* GCs for Santa */
    SantaGC = XCreateGC(display, rootWin, 0L, &xgcv);
    XCopyGC(display,gc,0,SantaGC);
    XSetFillStyle(display, SantaGC, FillStippled);
    XSetForeground(display, SantaGC, redPix);
    XSetStipple(display,SantaGC,
                santaPix[SantaSize].pixmap);

    RudolfGC = XCreateGC(display, rootWin, 0L, &xgcv);
    XCopyGC(display,gc,0,RudolfGC);
    XSetFillStyle(display, RudolfGC, FillSolid);
    XSetForeground(display, RudolfGC, redPix);

    FurGC = XCreateGC(display, rootWin, 0L, &xgcv);
    XCopyGC(display,gc,0,FurGC);
    XSetForeground(display, FurGC, whitePix);
    XSetFillStyle(display, FurGC, FillStippled);
    XSetStipple(display, FurGC, santaFurPix[SantaSize].pixmap);


    /*
     *
     * Here snowing starts
     *
     */

    Windows = XCreateRegion();
    SnowCatchR = XCreateRegion();
    SnowAllowedR = XCreateRegion();

    /* The initial catch region: a little bit below the bottom of the screen */
    AddRect.x = 0;
    AddRect.y = display_height;
    AddRect.width = display_width - 1;
    AddRect.height = MaxYStep+MaxSnowFlakeHeight;
    if (!NoKeepSBot)
      XUnionRectWithRegion(&AddRect, SnowCatchR, SnowCatchR);

    /* Initialize all snowflakes */
    for (i=0; i<maxSnowflakes; i++) InitSnowflake(i);

    InitSanta();   

    /* Notify if part of the root window changed */
    XSelectInput(display, rootWin, ExposureMask | SubstructureNotifyMask);


    needCalc = 0;
    /***if (!NoKeepSnow) needCalc = CalcWindowTops();***/
    needCalc = CalcWindowTops();


    /*
     *  M A I N   L O O P
     */
    while (!done) {

      /* X event ? */
      /* Handle all the expose events and redo CalcWindowTops after */
      Exposed = 0;
      ConfigureNotified = 0;
      while (XPending(display)) {
        XNextEvent(display, &ev);

        /* No need to do all this computing if we're not keeping the snow */
        if (!NoKeepSnow) {

          switch (ev.type) {

              case Expose:    
                
                  Exposed = 1;

                  /* 
                   * Subtract the exposed area from the SnowCatchR 
                   */
                  AddX = ev.xexpose.x - MaxSnowFlakeHeight;
                  AddY = ev.xexpose.y - MaxWinSnowDepth;
                  AddRect.height = ev.xexpose.height +  MaxWinSnowDepth;
                  AddRect.width = ev.xexpose.width + 2*MaxSnowFlakeHeight;
                  if (AddY < 0) {
                    AddRect.height = AddRect.height + AddY;
                    AddY = 0;
                  }
                  if ((AddY+AddRect.height) > display_height) {
                    AddRect.height = display_height - AddY;
                  }

                  AddRect.x = AddX;
                  AddRect.y = AddY;

                  SubR= XCreateRegion();
                    XUnionRectWithRegion(&AddRect, SubR,SubR);
                  
                    /* Clear the snow from screen */
                    AddY = AddY - MaxYStep-MaxSnowFlakeHeight;
                    AddRect.height = 
                      MaxWinSnowDepth+MaxYStep*2+MaxSnowFlakeHeight*2;
                    if (AddY < 0) {
                      AddRect.height = AddRect.height + AddY;
                      AddY = 0;
                    }
                    AddRect.y = AddY;
                    if ((AddY+AddRect.height) > display_height) {
                      AddRect.height = display_height - AddY;
                    }
                    XUnionRectWithRegion(&AddRect, SubR,SubR);
                    XClearArea(display, rootWin,
                               AddX,
                               AddY,
                               AddRect.width,              
                               AddRect.height,
                               False);

                    /* Subtract the erased snow from the SnowCatchR */
                    XSubtractRegion(SnowCatchR,SubR, SnowCatchR);
                  XDestroyRegion(SubR);
                 

#if debug
printf("exp %d %d %d %d\n",AddX,AddY,AddRect.width,AddRect.height);
#endif

                  needCalc = 1;

                  break;


              case MapNotify:
              case UnmapNotify:
              case ConfigureNotify:
#if debug
printf("Configurenotify!\n");
#endif
                  needCalc = 1;
                  break;

          } /* switch */

        }  /* if (!NoKeepSnow) */
       
      }  /* while Xpending */

      /* If things have changed while we were away... */
      if (needCalc) needCalc = CalcWindowTops();


#if debug
/***
XClearWindow(display, rootWin);
XFlush(display);
XSetForeground(display,FurGC,trPix);
XSetFillStyle(display,FurGC, FillSolid);
XSetRegion(display,FurGC,SnowCatchR);
XFillRectangle(display,rootWin,FurGC,0,0,display_width,display_height);
XFlush(display);
***/
#endif


      /* Sleep a bit */
      uSsleep(snowDelay);

      /* 
       *  Update     
       */

      /* Snowflakes */
      for (i=0; i<maxSnowflakes; i++) UpdateSnowflake(i);

      /* Draw dear Andrews */
      if (!NoTrees ) {
        DrawTannenbaum(display_width-150, display_height-500);
        DrawTannenbaum(display_width-100, display_height-200);
        DrawTannenbaum(100, display_height-200);
        DrawTannenbaum(50, display_height-150);
        DrawTannenbaum(center_x, display_height-100);
        DrawTannenbaum(200,400);
      }

      /* Dear Santa */
      if (!NoSanta) {
        UpdateSanta();
      }

    }

/***
sleep(10);
XClearWindow(display, rootWin);
XFlush(display);
XSetFillStyle(display,FurGC, FillSolid);
XSetRegion(display,FurGC,SnowAllowedR);
XFillRectangle(display,rootWin,FurGC,0,0,display_width,display_height);
XFlush(display);
sleep(4);
***/
    
    XDestroyRegion(Windows);
    XDestroyRegion(SnowCatchR);
    XDestroyRegion(SnowAllowedR);

    XClearWindow(display, rootWin);

    XCloseDisplay(display);


    exit(0);
}


#define USEPRT(msg) fprintf(stderr, msg)

void
Usage()
{
    USEPRT("Usage: xsnow [options]\n\n");
    USEPRT("Options:\n");
    USEPRT("       -display     <displayname>\n");
    USEPRT("       -sc          <snowcolor>\n");
    USEPRT("       -tc          <tree color>\n");
    USEPRT("       -bg          <background color>\n");
    USEPRT("       -solidbg     (Performance improvement!)\n");
    USEPRT("       -slc         <sleigh color>\n");
    USEPRT("       -snowflakes  <numsnowflakes>\n");
    USEPRT("       -delay       <delay in milliseconds>\n");
    USEPRT("       -unsmooth\n");
    USEPRT("       -whirl       <whirlfactor>\n");
    USEPRT("       -nowind\n");
    USEPRT("       -windtimer   <Time between windy periods in seconds>\n");
    USEPRT("       -xspeed      <max xspeed snowflakes>\n");
    USEPRT("       -yspeed      <max yspeed snowflakes>\n");
    USEPRT("       -wsnowdepth  <max snow depth on windows>\n");
    USEPRT("       -offset      <shift snow down>\n");
    USEPRT("       -ssnowdepth  <max snow depth at bottom of display>\n");
    USEPRT("       -notrees\n");
    USEPRT("       -nosanta\n");
    USEPRT("       -norudolf\n");
    USEPRT("       -santa       <santa>\n");
    USEPRT("       -santaspeed  <santa_speed>\n");
    USEPRT("       -nokeepsnow\n");
    USEPRT("       -nokeepsnowonwindows\n");
    USEPRT("       -nokeepsnowonscreen\n");
    USEPRT("       -nonopopup\n");
    USEPRT("       -version\n\n");
    USEPRT("Recommended: xsnow -bg SkyBlue3\n");
    
    exit(1);
}

void
SigHandler()
{
  done = 1;
}


void
SigHupHandler()
{
  void (*Sig_Hup_ptr)() = SigHupHandler;

  /*XClearWindow(display, rootWin);*/
  XFlush(display);
  XSetForeground(display,FurGC,trPix);
  XSetFillStyle(display,FurGC, FillSolid);
  XSetRegion(display,FurGC,SnowCatchR);
  XFillRectangle(display,rootWin,FurGC,0,0,display_width,display_height);
  XFlush(display);

  signal(SIGHUP, Sig_Hup_ptr);
}

/*
   Generate random integer between 0 and maxVal-1.
*/
int
RandInt(maxVal)
int maxVal;
{
	return rand() % maxVal;
}




/*
 * sleep for a number of micro-seconds
 */
void uSsleep(usec) 
unsigned long usec;
{
#ifdef SVR3
    poll((struct poll *)0, (size_t)0, usec/1000);   /* ms resolution */
#else
#ifdef VMS
    void lib$wait();
    float t;
    t= usec/1000000.0;
    lib$wait(&t);
#else
    struct timeval t;
    t.tv_usec = usec%(unsigned long)1000000;
    t.tv_sec = usec/(unsigned long)1000000;
    select(0, (void *)0, (void *)0, (void *)0, &t);
#endif
#endif
}


/*
   Check for topleft point of snowflake in specified rectangle.
*/
int
SnowPtInRect(snx, sny, recx, recy, width, height)
int snx;
int sny;
int recx;
int recy;
int width;
int height;
{
    if (snx < recx) return 0;
    if (snx > (recx + width)) return 0;
    if (sny < recy) return 0;
    if (sny > (recy + height)) return 0;
    
    return 1;
}


/*
   Give birth to a snowflake.
*/
void
InitSnowflake(rx)
int rx;
{
    Snow *r;

    r = &snowflakes[rx];

  if (curSnowflakes < maxSnowflakes) {
    r->whatFlake = RandInt(SNOWFLAKEMAXTYPE+1);

    /* Wind strikes! */
    if (wind) {
      if (direction == 1) 
        /* Create snow on the left of the display */
        r->intX = RandInt(display_width/3);
      else
        /* Create snow on the right of the display */
        r->intX = display_width - RandInt(display_width/3);
      r->intY =  RandInt(display_height);
    }
    else  {
      r->intX = RandInt(display_width - snowPix[r->whatFlake].width);
      r->intY =  RandInt(display_height/10);
    }

    r->yStep = RandInt(MaxYStep+1)+1;
    r->xStep = RandInt(r->yStep/4+1);
    if (RandInt(1000) > 500) r->xStep = -r->xStep;
    r->active = 1;
    r->visible = 1;
    r->insnow = 0;
  }
}



/*
   Move a snowflake by erasing and redraw
*/
void
UpdateSnowflake(rx)
int rx;
{
Snow *snow;

int NewX;
int NewY;
int tmp_x;
int TouchDown;
int InVisible;
int NoErase;
    
  /* move an active snowflake */

  snow = &snowflakes[rx];
  NoErase = 0;  /* Always erase the flakes unless special case */


  /* Activate a new snowflake */
  if (!snow->active) {
    InitSnowflake(rx);
    /* Make sure newly created flakes aren't erased before actually drawn */
    /* Especially with wind, where flakes are created all over the screen */
    snow->insnow = 1; 
  }

  /* New x,y */
  if (wind) {
    tmp_x = abs(snow->xStep);
    if( wind == 1 ){  /* going to stop winding */
        tmp_x = tmp_x + (RandInt(WhirlFactor+1) - WhirlFactor/2);
    }else{            /* WINDY */
        tmp_x = tmp_x + (RandInt(20));
    }
    snow->xStep = tmp_x * direction;

    if (snow->xStep > 50) snow->xStep = 50;
    if (snow->xStep < -50) snow->xStep = -50;

    if( wind == 1 ){
        NewY = NewY+3;
    }else{
        NewY = NewY+10;
    }
  }
  NewX = snow->intX + snow->xStep;
  NewY = snow->intY + snow->yStep;


  /* If flake disappears offscreen (below bottom) it becomes inactive */
  /* In case of wind also deactivate flakes blown offscreen sideways, */
  /* so they'll reappear immediately. Make sure snow at bottom isn't  */
  /* disrupted.                          */

  /* Snowflake at the end of it's life? */
  snow->active = (NewY < display_height);
  /* If it is windy we need the flakes that blow offscreen! */
  if (wind) snow->active = 
      (snow->active && (NewX > 0) && (NewX < display_width));
    

  /* If flake covered by windows don't bother drawing it */
  InVisible = XRectInRegion(Windows,
                            NewX,NewY,
                            snowPix[snow->whatFlake].width,
                            snowPix[snow->whatFlake].height);
  snow->visible = (InVisible != RectangleIn);


  /*
   *  Snow touches snow
   */
  if (snow->visible) {

    /* Do not use XRectInRegion here, as that makes snow add to the bottom */
    /* of layers on windows too */
    /* Use middle bottom of flake */
    TouchDown = XPointInRegion(SnowCatchR,
                               NewX+snowPix[snow->whatFlake].width/2,
                               NewY+snowPix[snow->whatFlake].height);

    /* If the flake already is completely inside the region */
    if (TouchDown && snow->visible && !NoKeepSnow) {
      /* Only add snow that's visible, else snow will grow */
      /* on the *back* of windows */

      NoErase = 1;

      /* Add a bit to the snow bottom region to make snow build up */
      /* Snow isn't built up more than about a certain amount: inside */
      /* SnowAllowedR */

      /* If flake already inside snow layer don't add it again */
      TouchDown = XRectInRegion(SnowCatchR,
                                NewX,NewY,
                                snowPix[snow->whatFlake].width,
                                snowPix[snow->whatFlake].height);
      if (TouchDown == RectanglePart) {
        AddRect.x = NewX;
        AddRect.y = NewY + snowPix[snow->whatFlake].height - 2;
        AddRect.height = 2;
        AddRect.width = snowPix[snow->whatFlake].width;               
        if (XRectInRegion(SnowAllowedR,
                          AddRect.x, AddRect.y,
                          AddRect.width, AddRect.height) == RectangleIn) {
          XUnionRectWithRegion(&AddRect, SnowCatchR,SnowCatchR);
        }
      }
    }


    /* Adjust horizontal speed to mimic whirling */
    if (SmoothWhirl) 
      snow->xStep = snow->xStep + (RandInt(WhirlFactor+1) - WhirlFactor/2);
    else {
      /* Jerkier movement */
      snow->xStep = snow->xStep + RandInt(WhirlFactor);
      if (RandInt(1000) > 500) snow->xStep = -snow->xStep;
    }

  }  /* if snow->visible */



  if (!wind) {
    if (snow->xStep > MaxXStep) snow->xStep = MaxXStep;
    if (snow->xStep < -MaxXStep) snow->xStep = -MaxXStep;
  }

  /* Limit X speed inside snowlayers. 's only natural... */
  if (NoErase && !wind) snow->xStep = snow->xStep/2;
  /* xsnow 1.29 method: */
  /***if (NoErase) {
    snow->xStep = 0;
    NewX = snow->intX;
  }***/


  /* Erase, unless something special has happened */
  if (!snow->insnow) EraseSnowflake(rx);

  /* New coords, unless it's windy */
  snow->intX = NewX;
  snow->intY = NewY;

  /* If snowflake inside a snow layer now don't erase it next time */
  snow->insnow = NoErase;

  /* Draw if still active  and visible*/
  if (snow->active && snow->visible) DrawSnowflake(rx);
 
}


    
/*
   Draw a snowflake.
*/
void
DrawSnowflake(rx)
int rx;
{
    Snow *snow;

    snow = &snowflakes[rx];

    XSetTSOrigin(display, SnowGC[snow->whatFlake], snow->intX, snow->intY);
    XFillRectangle(display, rootWin, SnowGC[snow->whatFlake],
         snow->intX, snow->intY,
         snowPix[snow->whatFlake].width, snowPix[snow->whatFlake].height);
}


/*
   Erase a snowflake.
*/
void
EraseSnowflake(rx)
int rx;
{
    Snow *snow;

    snow = &snowflakes[rx];
   
    if (UseFillForClear) {
      XFillRectangle(display, rootWin, ClearSnowGC,
                 snow->intX, snow->intY,
                 snowPix[snow->whatFlake].width, 
                 snowPix[snow->whatFlake].height);
      }
    else {
      XClearArea(display, rootWin, 
                 snow->intX, snow->intY,
                 snowPix[snow->whatFlake].width, 
                 snowPix[snow->whatFlake].height,
                 False);
    }
}


/*
  Paint thick snow at bottom of screen for impaintient people
*/
void
PaintSnowAtBottom(depth)
int depth;
{
int x,y;
Snow *snow;

  for (y=0; y < depth; y++) {
    for (x=0; x<maxSnowflakes; x++) {
      InitSnowflake(x);
      snow = &snowflakes[x];
      snow->intY = display_height - y;
      DrawSnowflake(x);
    }
  }

}


/*
   Draw a tree
*/
void
DrawTannenbaum(x,y)
int x,y;
{
    XSetTSOrigin(display, TreesGC, x,y);
    XFillRectangle(display, rootWin, TreesGC,
         x,y,
         tannenbaumPix[0].width, tannenbaumPix[0].height);
}


/*
   Give birth to a Santa. (What a conception)
*/
void
InitSanta()      
{
  Claus.x = -sleighPix[SantaSize][Claus.whatSanta].width;
  Claus.y = RandInt(display_height / 3)+40;
  Claus.xStep = SantaSpeed;
  Claus.yStep = 1;
  Claus.whatSanta = 0;
  Claus.visible = 1;
}


/*
  Update Santa (How can you update the oldest icon in the world? Oh well. )
*/
void
UpdateSanta()
{
int Visible;

  if (Claus.visible) EraseSanta();

  /* Move forward */
  Claus.x = Claus.x + Claus.xStep;
 
  /* Move down */
  if (RandInt(10) > 3) Claus.y = Claus.y + Claus.yStep; 
  if (Claus.y < 0) Claus.y = 0;
  if (RandInt(100) > 80) Claus.yStep = -Claus.yStep;

  if (Claus.x >= display_width) InitSanta(); 

  /* If Santa entirely covered by windows don't bother drawing him */
  Visible = XRectInRegion(Windows,
                          Claus.x, Claus.y,
                          sleighPix[SantaSize][Claus.whatSanta].width,
                          sleighPix[SantaSize][Claus.whatSanta].height);
  Claus.visible = (Visible != RectangleIn);
  Claus.visible = (Claus.visible || (Claus.x < 0));

  /* Santa inside snow layers? Then make a hole in the snow :)  */
  Visible = XRectInRegion(SnowCatchR,
                          Claus.x, Claus.y,
                          sleighPix[SantaSize][Claus.whatSanta].width,
                          sleighPix[SantaSize][Claus.whatSanta].height);

  /* Santa acts as a real snowplough! */
  if (Visible != RectangleOut) {
    SubR = XCreateRegion();

    AddRect.x = Claus.x;
    AddRect.y = Claus.y;
    AddRect.width = sleighPix[SantaSize][Claus.whatSanta].width;
    AddRect.height = sleighPix[SantaSize][Claus.whatSanta].height;
    XUnionRectWithRegion(&AddRect, SubR,SubR);
    XSubtractRegion(SubR,Windows, SubR);
 
    XSubtractRegion(SnowCatchR,SubR, SnowCatchR);

    XDestroyRegion(SubR);
  }

  

  /* Next sleigh */
  Claus.whatSanta++;
  if (Claus.whatSanta > 2) Claus.whatSanta = 0;

  if (Claus.visible) DrawSanta();
}




/*
  Draw Santa
*/
void
DrawSanta()
{
  XSetTSOrigin(display, SleighGCs[Claus.whatSanta], Claus.x,Claus.y);
  XFillRectangle(display, rootWin, SleighGCs[Claus.whatSanta],
         Claus.x,Claus.y,
         sleighPix[SantaSize][Claus.whatSanta].width, 
         sleighPix[SantaSize][Claus.whatSanta].height);

  /* The Man in the Red Suit himself */
  XSetTSOrigin(display, SantaGC, Claus.x,Claus.y);
  XFillRectangle(display, rootWin, SantaGC,
         Claus.x,Claus.y,
         santaPix[SantaSize].width, 
         santaPix[SantaSize].height);

  /* At last.... Rudolf! */
  if (Rudolf) {
    XFillRectangle(display, rootWin, RudolfGC,
                   Claus.x+NoseX[SantaSize], Claus.y+NoseY[SantaSize],
                   NoseXSiz[SantaSize], NoseYSiz[SantaSize]);
  }

  /* The fur in his hat */
  /* Note: the fur in his hat is *imitation* white-seal fur, of course. */
  /* Santa is a big supporter of Greenpeace.                            */
  XSetTSOrigin(display, FurGC, Claus.x,Claus.y);
  XFillRectangle(display, rootWin, FurGC,
         Claus.x,Claus.y,
         santaFurPix[SantaSize].width, 
         santaFurPix[SantaSize].height);

}

/*
  Erase Santa
*/
void
EraseSanta()
{
  XClearArea(display, rootWin,
             Claus.x , Claus.y,     
             sleighPix[SantaSize][Claus.whatSanta].width,
             sleighPix[SantaSize][Claus.whatSanta].height,
             False);
}



#if !GRAB_SERVER

int
RoachErrors(dpy, err)
Display *dpy;
XErrorEvent *err;
{
    errorVal = err->error_code;

    return 0;
}

#endif /* GRAB_SERVER */

/*
   Calculate Visible region of tops of windows to catch snowflakes
*/
int
CalcWindowTops()
{
    Window *children;
    unsigned int nChildren;
    Window dummy;
    XWindowAttributes wa;
    int wx;
    XRectangle CatchRect;
    XRectangle AllowRect;
    XRectangle WinRect;
    int winX, winY;
    int NouMoe;
    unsigned int winHeight, winWidth;
    unsigned int depth;

    /*
       If we don't grab the server, the XGetWindowAttribute or XGetGeometry
       calls can abort us.  On the other hand, the server grabs can make for
       some annoying delays.
    */
#if GRAB_SERVER
    XGrabServer(display);
#else
    XSetErrorHandler(RoachErrors);
#endif

    /* Rebuild regions */
    XDestroyRegion(Windows);
    XDestroyRegion(SnowAllowedR);
    Windows = XCreateRegion();
    SnowAllowedR = XCreateRegion();

    /*
       Get children of root.
    */
    XQueryTree(display, rootWin, &dummy, &dummy, &children, &nChildren);

    /*
       For each mapped child, add the rectangle on top of the window
       where the snow could be to the region.
    */
    for (wx=0; wx<nChildren; wx++) {
        if (XEventsQueued(display, QueuedAlready)) {
          XFree((char *)children); 
          return 1;
        }
        errorVal = 0;
        XGetWindowAttributes(display, children[wx], &wa);
        if (errorVal) continue;

        /* Popup? */
#if debug
if (wa.save_under) printf("POPUP discarded\n");;
#endif
        if (!NoPopuphandling && wa.save_under) continue;

        if (wa.map_state == IsViewable) {
            XGetGeometry(display, children[wx], &dummy, &winX, &winY,
                &winWidth, &winHeight, &borderWidth, &depth);
#if debug
printf("\nw %d %d %d %d - %d %d \n", winX,winY,winWidth,winHeight,borderWidth,depth);
#endif
            if (errorVal) continue;
 
winY = winY + SnowOffset;
            /* Entirely offscreen? */
            if (winX > display_width) continue;
            if (winY > display_height) continue;
            if (winY <= 0) continue;


            /* Geometry of the window, borders inclusive */
            winX = winX-borderWidth;
            winY = winY-borderWidth;
            winHeight = winHeight + 2*borderWidth;
            winWidth = winWidth + 2*borderWidth;

            /* Entirely offscreen? */
            NouMoe = winX + winWidth;
            if (NouMoe < 0) continue;

            /* Make sure the initial catch region isn't punctured */
            if ((winY+winHeight) > display_height) {
              winHeight = display_height-winY;
            }

            if (winY < 0) {
              winHeight = winHeight + winY;
              winY = 0;
            }

            /* If window partly on screen */
            if (winX < 0) {
              winWidth = winWidth + winX - borderWidth;
              winX = 0;
            }

            WinRect.x = winX;
            WinRect.y = winY;
            WinRect.height = winHeight + 2*borderWidth;
            WinRect.width = winWidth + 2*borderWidth;

            /* The area of the windows themselves */
            XUnionRectWithRegion(&WinRect, Windows, Windows);





            /* Subtract this window from all previous catch regions! */
            SubR= XCreateRegion();
              XUnionRectWithRegion(&WinRect, SubR, SubR);

              XSubtractRegion(SnowCatchR,SubR, SnowCatchR);
            XDestroyRegion(SubR);
              


            /* CatchR */
            CatchRect.y = WinRect.y;
            CatchRect.x = WinRect.x;
            CatchRect.height = MaxYStep+MaxSnowFlakeHeight;
            CatchRect.width = WinRect.width;

            if (!NoKeepSWin) 
              XUnionRectWithRegion(&CatchRect, SnowCatchR, SnowCatchR);
#if debug
printf("c %d %d %d %d\n",CatchRect.x,CatchRect.y,CatchRect.height,CatchRect.width);
#endif


            /* AllowedR */
            AllowRect.x = CatchRect.x;
            winY = CatchRect.y - MaxWinSnowDepth;
            AllowRect.height = CatchRect.height+MaxWinSnowDepth;
            if (winY < 0) {
              AllowRect.height = winY+CatchRect.height;
              winY = 0;
            }
            AllowRect.y = winY;
            AllowRect.width = CatchRect.width;
            if (!NoKeepSWin)
              XUnionRectWithRegion(&AllowRect, SnowAllowedR, SnowAllowedR);


        }
    }

    /* SnowAllowed at screen bottom */
    AllowRect.x = 0;
    AllowRect.y = display_height - MaxScrSnowDepth;
    AllowRect.width = display_width - 1;
    AllowRect.height = MaxYStep+MaxSnowFlakeHeight+MaxScrSnowDepth;
    if (!NoKeepSWin)
      XUnionRectWithRegion(&AllowRect, SnowAllowedR, SnowAllowedR);


    XFree((char *)children);

#if GRAB_SERVER
    XUngrabServer(display);
#else
    XSetErrorHandler((ErrorHandler *)NULL);
#endif


    return 0;
}

 


/*
   Allocate a color by name.
*/
Pixel
AllocNamedColor(colorName, dfltPix)
char *colorName;
Pixel dfltPix;
{
	Pixel pix;
	XColor scrncolor;
	XColor exactcolor;

	if (XAllocNamedColor(display, DefaultColormap(display, screen),
		colorName, &scrncolor, &exactcolor)) {
		pix = scrncolor.pixel;
	}
	else {
		pix = dfltPix;
	}

	return pix;
}


void sig_alarm()
{
    int rand=RandInt(100);
    void (*sig_alarm_ptr)() = sig_alarm;

    /* No wind at all? */
    if (NoWind) return;

    if( rand > 80 ){
        geta -= 1;
    }else if( rand > 55 ){
        geta += 1;
    }

    if( rand > 65 ){
        if( RandInt(10) > 4 ){
            direction = 1;
        }else{
            direction = -1;
        }

        wind = 2;
        signal(SIGALRM, sig_alarm_ptr);

        alarm(abs(RandInt(5))+1);
    }else{
        if( wind == 2 ){
            wind = 1;
            signal(SIGALRM, sig_alarm_ptr);
            alarm(RandInt(3)+1);
        }else{
            wind = 0;
            signal(SIGALRM, sig_alarm_ptr);
            alarm(WindTimer);
        }
    }
}

