#include <stdio.h>
#include <ctype.h>
#ifdef VMS
#include <string.h>
#define index strchr
#define bcopy memcpy
#define bzero(x,y)	memset(x,0,y)
#include <time.h>
#else
#include <sys/time.h>
#endif
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#define STRINGLENGTH	256
#define SLEEPTIME	0    /* Sleep for this many seconds before redrawing */

int     dpy2depth;

typedef unsigned char byte;

extern byte *malloc();

char	**Argv;
int	Argc;
Display	*dpy;


main(argc,argv)
     int	argc;
     char	**argv;
{
  Window	watchWin,GetWindowByName();
  int		i,strPos,optIndex;
  char		displayName[64],xWatchName[STRINGLENGTH];
#ifndef VMS
  char		*optstring;
  extern char	*optarg;
  extern int	optind,opterr;
#endif
  int		windowID,windowIDSet,updateTime,updateTimeSet;
  

  if (argc < 2)			    /* Did user enter enough arguments? */
    {				    /* Nope, print an error message and exit */
    printf("Usage:\t%s HostName [-u UpdateTime] [-w windowID] [WindowName]\n",
	   argv[0]);
    exit(1);
    }

  /* Initialize var to update every SLEEPTIME seconds. */
  updateTime = SLEEPTIME;
  updateTimeSet = 0;
  windowIDSet = 0;

#ifndef VMS
  optind = 2;	    /* Skip over first, obligatory argument (hostname) */
  while ((optIndex = getopt(argc,argv,"u:w:")) != -1)   /* get arguments */
    switch (optIndex)
      {
      case 'u':		/* User wants to update every 'optarg' seconds. */
	updateTime = atoi(optarg);
	updateTimeSet = 1;    /* Set flag saying user specified this option. */
	break;
      case 'w':   /* User wants to specify window by id instead of by name */
	sscanf(&optarg[2],"%lx",&windowID);
	windowIDSet = 1;    /* Set flag saying user specified this option. */
	break;
      }			    /* end switch (optIndex) */
#endif

  Argv = argv;
  Argc = argc;

  /* if no ':' in display name, tack default ':0.0' onto end */
  if (index(argv[1],':')==NULL) sprintf(displayName,"%s:0.0",argv[1]);
                           else strcpy(displayName,argv[1]);

  if (!windowIDSet)	   /* Did user specify window to watch by number? */
    for (i=2, bzero(xWatchName, STRINGLENGTH); i < argc; i++)
    /* No, parse rest of arguments as window name to watch */
      {
	if (!strcmp(argv[i],"-u"))	   /* Don't parse optional arguments */
	  {
	    i++;		   /* Skip over argument to '-u' switch. */
	    continue;
	  }
	/* Get current length of xWatchName string */
	strPos = strlen(xWatchName);
	/* Copy another argument to the end of the string */
	strcpy(&xWatchName[strPos],argv[i]);
      }

  /* Attempt to open a connection with the remote X server */
  if ((dpy = XOpenDisplay(displayName)) == NULL)
    {
      /* Couldn't open the display for some reason, so... */
      fprintf(stderr,"%s: Could not open remote display %s\n", 
	      argv[0],displayName);
      exit(1);	    /* ...report the error and exit with an error code */
    }

  if (!windowIDSet)	    /* Did user specify a window to watch by id? */
    /* No, get window id from window name. */
    watchWin = GetWindowByName(XDefaultRootWindow(dpy),xWatchName);
  else watchWin = windowID; /* Yes, use user-specified window id. */

  if (watchWin)	   /* Did the user find the window s/he was looking for? */
    /* Yes, periodically show the contents of that window  */
    WatchWindow(watchWin,updateTime);
  else	/* No, report that the window was not found, and exit. */
    {
      printf("Could not find the window you specified.\n");
      exit(1);
    }
  
}	/* end function main */




/* Takes two strings, removes spaces from the second,... */
/* ...and compares them..  Returns 1 if equal, 0 if not. */
WinNamesEqual(str1,str2)
     char	*str1,*str2;
{
  char	tempStr[STRINGLENGTH],*tempStrPtr;
  int	index;
  
  bzero(tempStr,STRINGLENGTH);	/* Clear the contents of the string, if any */
  /* Go through each character in the second string. */
  for (tempStrPtr=tempStr; *str2; str2++)
    {
      if (!isspace(*str2))    /* Is this character a space?  */
	*tempStrPtr++ = *str2;	/* No, copy this character to a temp string. */
    }
  if (!strcmp(str1,tempStr))	/* Are the two resulting string equal? */
    return(1);			/* Yes, return 1 */
  else
    return(0);			/* No, return 0 */
}				/* end function WinNamesEqual */


WatchWindow(win,updateTime)
     Window	win;
     int	updateTime;
{
  Display		*dpy2;
  Window		copyWin;
  GC			gc;
  XWindowAttributes	copyWinInfo,newWinInfo;
  XSetWindowAttributes	copyWinAttrs;
  XWMHints		wmHints;
  XSizeHints		sizeHints;
  XImage		*image;
#ifdef VMS
  struct timeb		currentTime;
  time_t		timeInSecs;
#else
  struct timeval	currentTime;
  struct timezone	zone;
  long			timeInSecs;
#endif
  Bool			srcWinUnmapped;
  int                   imageform;
  
  /* Get the window attributes of the window we're watching */
  XGetWindowAttributes(dpy,win,&copyWinInfo);

  /* Is the original window in a state to be watched?  */
  if (copyWinInfo.map_state != IsViewable)
    {  /* Nope, tell the user of the problem and exit. */
    printf("The window you wish to look at is not in a state to be viewed\n");
    printf("(perhaps it is iconified or not mapped)\n");
    exit(1);
    }

  /* Attempt to open a connection with the local X server */
  if ((dpy2 = XOpenDisplay(NULL)) == NULL)
    {
    /* Couldn't open the display for some reason, so... */
    fprintf(stderr,"%s: Could not open local display.\n", Argv[0]);
    exit(1); /* ...report the error and exit with an error code */
    }

  /* Set a couple more attributes */
  copyWinAttrs.colormap = XDefaultColormap(dpy2,XDefaultScreen(dpy2));
  copyWinAttrs.bit_gravity = copyWinInfo.bit_gravity;

  /* Check for different depths b/w source & dest displays */
  dpy2depth = XDefaultDepth(dpy2,XDefaultScreen(dpy2));

  if ((copyWinInfo.depth == dpy2depth) || 
      (copyWinInfo.depth == 1 && dpy2depth == 8) ||
      (copyWinInfo.depth == 8 && dpy2depth == 1) ) imageform = ZPixmap;
  else imageform = XYPixmap;

  /* Create a copy of the window we're watching */
  copyWin = XCreateWindow(dpy2,XDefaultRootWindow(dpy2),
			  copyWinInfo.x,copyWinInfo.y,
			  copyWinInfo.width,copyWinInfo.height,
			  copyWinInfo.border_width,
			  dpy2depth,
			  CopyFromParent,
			  XDefaultVisual(dpy2,XDefaultScreen(dpy2)),
			  (CWColormap|CWBitGravity),
			  &copyWinAttrs);

  /* Get size hints for window being watched */
  XGetNormalHints(dpy,win,&sizeHints);

  /* Set standard window properties for my window */
  XSetStandardProperties(dpy2,copyWin,"XWatchWin","XWatchWin",
			 None,Argv,Argc,&sizeHints);

  /* Get window mgr hints for the window being watched */
  XGetWMHints(dpy,win,&wmHints);

  /* Tell the X server about my window manager hints */
  XSetWMHints(dpy2,copyWin,&wmHints);
  gc = XDefaultGC(dpy2,0); /* Get a default graphics context */

  /* Only interested in exposures and button presses */
  XSelectInput(dpy2,copyWin,(ExposureMask|ButtonPressMask));

  /* Put the window up on the display */
  XMapWindow(dpy2,copyWin);
  XSelectInput(dpy,win, /* Only interested if the source window is... */
	       /* ...iconified or if it's mapped/unmapped */
	       (VisibilityChangeMask|StructureNotifyMask));

  /* Store an image of the original window in an XImage */
  image = XGetImage(dpy, win, 0,0, copyWinInfo.width, copyWinInfo.height,
		    AllPlanes, imageform);
  ConvertImage(image);

#ifdef VMS
  ftime (&currentTime);
  timeInSecs = currentTime.time; /* Save the current time in seconds */
#else
  gettimeofday(&currentTime,&zone); /* Get the current time. */
  timeInSecs = currentTime.tv_sec; /* Save the current time in seconds */
#endif

  /* Set variable saying it's okay to watch the src window. */
  srcWinUnmapped = 0;
  while (1) /* Enter an infinite event loop */
    {
      XEvent	event;

      /* Check if the source window was turned into an... */
      /* ...icon or back into a window */

      if (XCheckWindowEvent(dpy,win,
			    (VisibilityChangeMask|StructureNotifyMask),
			    &event))
	{
	  /* Get the window attributes of the window we're watching */
	  XGetWindowAttributes(dpy,win,&newWinInfo);

	  /* Is the original window in a state to be watched?  */
	  if (newWinInfo.map_state != IsViewable)
	    {
	      /* Let program know that the src window is unwatchable. */
	      srcWinUnmapped = 1;
	      printf("The window you are watching just became 'invisible'.\n");
	      printf("I will wait until it is 'visible' again...\n");
	      continue;
	    }
	  else
	    /* Let program know that src window is watchable again. */
	    srcWinUnmapped = 0;
	} /* end if(XCheckWindowEvent... */

      /* Look for window events, but don't sit around... */
      if (XCheckWindowEvent(dpy2,copyWin,
			    (ExposureMask|ButtonPressMask),
			    /* ...waiting for one (i.e., don't block) */
			    &event))
	switch (event.type)
	  {
	  case ButtonPress:
	    XDestroyImage(image);/* Free memory resources used by the image */
	    exit(0);		/* Get outtahere */
	    break;
	  case Expose:
	    /* Put the original window's image into the copy of the window */
            XPutImage(dpy2, copyWin, gc, image, 0,0,0,0,
		      copyWinInfo.width, copyWinInfo.height);

	    break;
	  }				/* end switch (event.type) */

#ifdef VMS
      ftime (&currentTime);
#else
      gettimeofday(&currentTime,&zone);	/* Get the current time. */
#endif


      /* Have 'updateTime' seconds passed? */
#ifdef VMS
      if (currentTime.time > (timeInSecs + updateTime))
#else
      if (currentTime.tv_sec > (timeInSecs + updateTime))
#endif
	{ /* Yes, update the local copy of the window. */

	  if (srcWinUnmapped) /* Is the source window watchable? */
	    continue;	/* No, go through the event loop again. */
	  else	/* Yes, it's watchable, so get a new copy of the window. */
	    {
	      XDestroyImage(image); /* Free memory used by the image */
	      /* Store an image of the original window in an XImage */
	      image = XGetImage(dpy,(Drawable)win,
				0,0,
				copyWinInfo.width,copyWinInfo.height,
				AllPlanes,imageform);
	      ConvertImage(image);
	    } /* end else XDestroyImage... */

	  /* Put the original window's image into the copy of the window */
	  XPutImage(dpy2, copyWin, gc, image, 0,0,0,0,
		    copyWinInfo.width, copyWinInfo.height);

#ifdef VMS
	  timeInSecs = currentTime.time;  /* Update the current time */
#else
	  timeInSecs = currentTime.tv_sec;  /* Update the current time */
#endif
	}				/* end if currentTime... */
    }					/* end while(1) */
}					/* end function WatchWindow */


/* Given the name of a window and the top of a ... */
/* ...window tree, this function will try to find... */
/* the Window ID corresponding to the window name... */
/* ...given as argument. */
Window GetWindowByName(window,windowName)
     Window	window;
     char	*windowName;
{
  Window	rootWin,parentWin,wID;
  Window	*childWinList;
  int		numChildren,i;
  char		*childWinName;

  if (strcmp(windowName,"root")==0 || strcmp(windowName,"XRootWindow")==0) 
    return XDefaultRootWindow(dpy);

  /* Get information about windows that are children... */
  XQueryTree(dpy,window,
	     &rootWin,&parentWin,&childWinList,	/* ...of 'window'. */
	     &numChildren);
  for (i=0;i<numChildren;i++) /* Look at each child of 'window' */
    {
      /* Get the name of that window */
      XFetchName(dpy,childWinList[i],&childWinName);
      if (childWinName != NULL) /* Is there a name attached to this window? */
	{
	  /* Is this the window the user is looking for? */
	  if (WinNamesEqual(windowName,childWinName))
	    {
	      XFree(childWinList);/* Free up space taken by list of windows */
	      XFree(childWinName);/* Return space taken by this window's name */
	      /* Yes, return the Window ID of this window */
	      return(childWinList[i]);
	    }
	  XFree(childWinName);/* Return space taken by this window's name */
	}		/* end if childWinName... */
    }			/* end for i=0... */
  /* If this section of code is reached, then no match was found at this
   * level of the tree
   */
  for (i=0;i<numChildren;i++) /* Recurse on the children of this window */
    {
      wID = GetWindowByName(childWinList[i],windowName);
      if (wID)		/* Was a match found in this window's children? */
	{
	  XFree(childWinList); /* Free up space taken by list of windows */
	  return(wID);	/* Return the ID of the window that matched */
	}
    }			/* end for i=0... */
  /* If this section of code is reached, then no match was found below
   * this level of the tree
   */
  XFree(childWinList); /* Free up space taken by list of windows */
  return((Window)0); /* No match was found, return 0. */
}		/* end function GetWindowByName */






ConvertImage(image)
XImage *image;
{
  int i,j;
  static byte bit[8] = {0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};

  /* conversion between images of different depths.  if the two depths
     are the same, nothing is done.  If the two depths are '1' and '8'
     (either way), uses special case code and ZPixmaps.  Otherwise, uses
     XYPixmap and general code below */


  /* note:  on every color/greyscale server I've had the pleasure to deal
     with (IBM RT Megapel and Sun cgfour), XGetImage(XYPixmap) returns an
     image in which the planes are (in my opinion) reversed.  That is, plane 0
     (the first plane in the image) corresponds to the highest order bit
     plane returned by the server.

     So, in order to correctly display the image when the two depths are
     different, I have to move the planes around.  Examples:  when displaying
     a 1-bit image on an 8-bit display, I convert the image by making up
     an 8-bit image, and copying the 1-bit plane to the 8th plane in the 
     8-bit image, rather than to the 1st plane.

     Likewise, when displaying an 8-bit image on a 1-bit display, I make
     up a 1-bit image, and copy the 8th plane to the (only) plane in the
     1-bit image.

     In theory, if I was displaying a 4 bit image on an 8-bit display, I'd
     copy planes 0-3 of the 4-bit image to planes 4-7, respectively, in the
     8-bit image.

     The code does this, though I haven't checked to see if it does it
     correctly, or even if this is the right thing to do.

     --jhb */

  if (dpy2depth == image->depth) return;

  else if (dpy2depth == 8 || image->depth == 1) {   /* expand ZPixmap 1->8 */
    byte *iptr, *optr, *ilptr, *olptr, *tmp;
    int   obperlin,bit;

    obperlin = ((image->width*8 + image->bitmap_pad - 1) 
		/ image->bitmap_pad) * (image->bitmap_pad / 8);

    ilptr = (byte *) image->data;
    olptr = tmp = malloc(image->height * obperlin);
    if (!olptr) { fprintf(stderr,"couldn't allocate image\n"); exit(1); }

    for (i=0; i<image->height; i++) {
      iptr = ilptr;  optr = olptr;
      for (j=bit=0; j<image->width; j++) {
	*optr++ =  (*iptr&0x80) ? 1 : 0;
	*iptr <<= 1;
	if (!(++bit&7)) iptr++;
      }
      ilptr += image->bytes_per_line;  olptr += obperlin;
    }

    free(image->data);
    image->data = (char *) tmp;
    image->bytes_per_line = obperlin;
    image->depth = image->bits_per_pixel = dpy2depth;
  }
    

  else if (dpy2depth == 1 || image->depth == 8) {   /* compress ZPixmap 8->1 */
    byte *iptr, *optr, *ilptr, *olptr, *tmp;
    int   obperlin,bit;

    obperlin = ((image->width + image->bitmap_pad - 1) 
		/ image->bitmap_pad) * (image->bitmap_pad / 8);

    ilptr = (byte *) image->data;
    olptr = tmp = malloc(image->height * obperlin);
    if (!olptr) { fprintf(stderr,"couldn't allocate image\n"); exit(1); }

    for (i=0; i<image->height; i++) {
      iptr = ilptr;  optr = olptr;
      for (j=bit=0; j<image->width; j++) {
	*optr = (*optr<<1) | (*iptr++ & 0x01);
	if (!(++bit&7)) optr++;
      }
      ilptr += image->bytes_per_line;  olptr += obperlin;
    }

    free(image->data);
    image->data = (char *) tmp;
    image->bytes_per_line = obperlin;
    image->depth = image->bits_per_pixel = dpy2depth;
  }
    

  else if (dpy2depth > image->depth) {    /* expand XYPixmap */
    byte *tmp;
    long planelen = image->height * image->bytes_per_line;
    tmp = malloc(planelen * dpy2depth);
    if (!tmp) { fprintf(stderr,"couldn't allocate image\n"); exit(1); }
    bzero(tmp,planelen * dpy2depth);

    bcopy(image->data, tmp + (dpy2depth - image->depth)*planelen,
	  image->depth * planelen);

    free(image->data);
    image->data = (char *) tmp;
    image->depth = dpy2depth;
  }

  else if (dpy2depth < image->depth) {   /* compress XYPixmap */
    byte *tmp;
    long planelen = image->height * image->bytes_per_line;
    tmp = malloc(planelen * dpy2depth);
    if (!tmp) { fprintf(stderr,"couldn't allocate image\n"); exit(1); }

    bcopy(image->data + (image->depth - dpy2depth)*planelen, tmp,
	  dpy2depth * planelen);

    free(image->data);
    image->data = (char *) tmp;
    image->depth = dpy2depth;
  }
}

