/* This file is part of the Project Athena Zephyr Notification System.
 * Created by: Mark W. Eichin <eichin@athena.mit.edu>
 * $Source: /mit/zephyr/src/zwgc/RCS/draw.c,v $
 * $Author: jtkohl $
 *
 *	Copyright (c) 1988 by the Massachusetts Institute of Technology.
 *	For copying and distribution information, see the file
 *	"mit-copyright.h". 
 */
#include <zephyr/mit-copyright.h>
#ifndef lint
static char rcsid[] = "$Header: /mit/zephyr/src/zwgc/RCS/draw.c,v 2.28 89/10/19 08:28:48 jtkohl Exp $";
#endif lint

#define FONTSPEEDHACK
#include <stdio.h>
#include "draw.h"
#include <strings.h>
#include <zephyr/zephyr.h>

#ifdef X11
Display *dpy;
int ttymode=0;
#else /* !X11 */
int ttymode=1;
#endif /* X11 */

#ifdef X11
/* const */ char *fontnames[] = {
#ifdef X11R3
  "8x13bold",				/* bold */
  "-*-times-medium-i-*-*-*-120-*",	/* italic */
  "8x13",				/* normal */
  "-*-helvetica-bold-r-*-*-*-240-*",	/* font0 */
  "-*-helvetica-bold-r-*-*-*-240-*",
  "-*-helvetica-bold-r-*-*-*-240-*",
  "-*-helvetica-bold-r-*-*-*-240-*",
  "-*-helvetica-bold-r-*-*-*-240-*",
  "-*-helvetica-bold-r-*-*-*-240-*",
  "-*-helvetica-bold-r-*-*-*-240-*",
  "-*-helvetica-bold-r-*-*-*-240-*",
  "-*-helvetica-bold-r-*-*-*-240-*",
  "-*-helvetica-bold-r-*-*-*-240-*",	/* font9 */
#else
  "fgb-13",			/* bold */
  "times-italic14",		/* italic */
  "fg-13",			/* normal */
  "vctl-25",			/* font0 */
  "vctl-25",
  "vctl-25",
  "vctl-25",
  "vctl-25",
  "vctl-25",
  "vctl-25",
  "vctl-25",
  "vctl-25",
  "vctl-25",			/* font9 */
#endif
};
/* const */ char *fontopts[] = {
  "bold",			/* bold */
  "italic",			/* italic */
  "normal",			/* normal */
  "font0",			/* font0 */
  "font1",
  "font2",
  "font3",
  "font4",
  "font5",
  "font6",
  "font7",
  "font8",
  "font9",			/* font9 */
};

#define NFONTS sizeof(fontnames)/sizeof(char *)

XFontStruct *xfs[NFONTS];
GC gcs[NFONTS], backgc;
Cursor globalcursor;

#include <X11/cursorfont.h>
#include <X11/Xutil.h>
/*   #include <X11/X10.h>  for XAssocTable */
#define ZWGCNAME "zwgc"
#define ZWGCCURSOR XC_sailboat
#define ZWGCATOMNAME "ZEPHYR_SUMMARY"

int xtx_istrue(yno, def)
     char *yno;
     int def;
{
  if(!yno) return(def);
  if(!strcasecmp("Yes", yno)) return(TRUE);
  if(!strcasecmp("On", yno)) return(TRUE);
  if(!strcasecmp("True", yno)) return(TRUE);
  if(!strcasecmp("T", yno)) return(TRUE);
  if(!strcasecmp("No", yno)) return(FALSE);
  if(!strcasecmp("Off", yno)) return(FALSE);
  if(!strcasecmp("False", yno)) return(FALSE);
  if(!strcasecmp("nil", yno)) return(FALSE);

  return(def);
}
#include <X11/Xatom.h>

typedef struct _winlist {
  struct _winlist *next;
  Window win;
  unsigned long serial;			/* serial # of the stack request
					   used when this window was mapped */
} winlist;

typedef struct _config_stack {
    struct _config_stack *next;
    Window under, over;			/* Windows involved */
} config_stack;

struct _master {
  int bwidth;			/* borderWidth */
  int inbwid;			/* internalBorder */
  int x;			/* default x position */
  int y;			/* default y position */
  int wid;			/* *ignored* */
  int height;			/* *ignored* */
  int mask;			/* XGeometry result mask */
  int resetscreen;		/* use XResetSaver? */
  int revstack;			/* use reverse stacking? */
  struct _pix {
    int fore;
    int back;
    int border;
  } pix;			/* pixel values */
  char *name;			/* Zwgc */
  char *iconname;		/* ZephyrGram */
  int nplanes;			/* nplanes special option */
  int usebackground;		/* useBackground:on flag */
  XContext exposetab;		/* expose context table, if off */
  int volume;			/* xbell percent */
  int interln;			/* inter line spacing */
  int textupdate;		/* do updates with XDrawImageText */
  int debugback;		/* debugging background flash? */
  Atom tagatom;			/* atom number for tag type */
  winlist *winlist;		/* window stacking list */
  config_stack *cstack;		/* WM redirect configure stack */
} master = {
  1, 0,
  0, 0,				/* x, y */
  0, 0,				/* w, h */
  0,
  0,				/* xreset */
  0,				/* revstack */
  {
    0, 0,			/* fore, back */
    0,				/* border */
  },
  "Zwgc", "ZephyrGram",
  -1,
  1,
  NULL,
  0,
  1,
  1, 0,
  (Atom) 0,
  (winlist *) 0,
  (config_stack *) 0,
};

void xtx_shutdown()
{
  int i, j;
  
  for(i=0;i<NFONTS;i++)
    if(xfs[i])
      {
	for(j=i+1;j<NFONTS;j++)
	  {
	    if(xfs[j]==xfs[i])
	      xfs[j]=NULL;
	  }
	XFreeFont(dpy,xfs[i]);
	XFreeGC(dpy,gcs[i]);
	xfs[i] = NULL;
	gcs[i] = NULL;
      }
  XFreeGC(dpy, backgc);
  
  XCloseDisplay(dpy);
}

int xtx_draw_init(reread)	/* returns file descriptor... */
     int reread;
{
  int i;
  XGCValues xgcv;
  unsigned long vmask;
  int dplanes;
  char *usrval;

  if(!dpy && !ttymode)
    {
      dpy = XOpenDisplay("");
      if(!dpy)
	{
	  char *fb = NULL;
	  
	  if((fb = ZGetVariable("fallback")) && xtx_istrue(fb, TRUE))
	    {
	      ttymode = TRUE;
	      reread = FALSE;	/* or use reread &&!ttymode? */
	    }
	  else
	    {
	      fprintf(stderr,
"\nzwgc:draw: \7\7\7There is no X display available, so zwgc cannot run.\n\
THIS MEANS THAT YOU WILL NOT RECEIVE ANY ZEPHYR MESSAGES.\n\
If you wish to receive Zephyr messages, you should start zwgc\n\
with the -ttymode option (type '/usr/etc/zwgc -ttymode').\n\
Read the zwgc(1) manual page for details on the fallback variable.\n\n");
	      exit(1);
	    }
	}
      else
	{
	  reread = TRUE;
	}
    }
  else if(ttymode)
    {
      reread = FALSE;		/* or use reread &&!ttymode? */
    }
  if(reread)
    {
      dplanes = DefaultDepth(dpy, DefaultScreen(dpy));
      if(dplanes > 1)
	{
	  usrval = XGetDefault(dpy, ZWGCNAME, "useBackground");
	  master.usebackground = xtx_istrue(usrval,FALSE);

	  usrval = XGetDefault(dpy, ZWGCNAME, "textUpdate");
	  master.textupdate = xtx_istrue(usrval,TRUE);

	  usrval = XGetDefault(dpy, ZWGCNAME, "nPlanes");
	  if(usrval)
	    {
	      int npl;
	      if(strcasecmp(usrval, "all"))
		{
		  master.nplanes = dplanes;
		}
	      else if (npl = atoi(usrval))
		{
		  master.nplanes = npl;
		}
	    }
	  else
	    {
	      master.nplanes = dplanes;
	    }
	}
      else
	{
	  usrval = XGetDefault(dpy, ZWGCNAME, "useBackground");
	  master.usebackground = xtx_istrue(usrval,FALSE);

	  usrval = XGetDefault(dpy, ZWGCNAME, "textUpdate");
	  master.textupdate = xtx_istrue(usrval,TRUE);

	  master.nplanes = dplanes;	/* == 1... ??? */
	}

      usrval = XGetDefault(dpy, ZWGCNAME, "cursorCode");
      globalcursor = XCreateFontCursor(dpy, usrval?atoi(usrval):ZWGCCURSOR);
      if (!globalcursor)
	{
	  globalcursor = XCreateFontCursor(dpy,ZWGCCURSOR);
	}

      if(DisplayCells(dpy, DefaultScreen(dpy)) == 2)
	{
	  master.pix.fore = master.pix.border =
	    BlackPixel(dpy, DefaultScreen(dpy));
	  master.pix.back = 
	    WhitePixel(dpy, DefaultScreen(dpy));
	}
      else
	{
	  XColor dcdef, cursdef;
	  Colormap dcolmap = DefaultColormap(dpy, DefaultScreen(dpy));
	  char *back_color;
	  
	  usrval = XGetDefault(dpy, ZWGCNAME, "foreground");
	  if(!usrval) usrval = XGetDefault(dpy, ZWGCNAME, "Foreground");
	  if(usrval
	     && XParseColor(dpy, dcolmap, usrval, &dcdef)
	     && XAllocColor(dpy, dcolmap, &dcdef))
	    {
	      master.pix.fore = dcdef.pixel;
	    }
	  else
	    {
	      master.pix.fore = BlackPixel(dpy, DefaultScreen(dpy));
	    }
	      
	  usrval = XGetDefault(dpy, ZWGCNAME, "background");
	  if(!usrval) usrval = XGetDefault(dpy, ZWGCNAME, "Background");
	  if(usrval
	     && XParseColor(dpy, dcolmap, usrval, &dcdef)
	     && XAllocColor(dpy, dcolmap, &dcdef))
	    {
	      back_color = usrval;
	      master.pix.back = dcdef.pixel;
	    }
	  else
	    {
	      back_color = "white";
	      master.pix.back = WhitePixel(dpy, DefaultScreen(dpy));
	    }
	      
	  usrval = XGetDefault(dpy, ZWGCNAME, "borderColor");
	  if(!usrval) usrval = XGetDefault(dpy, ZWGCNAME, "BorderColor");
	  if(usrval
	     && XParseColor(dpy, dcolmap, usrval, &dcdef)
	     && XAllocColor(dpy, dcolmap, &dcdef))
	    {
	      master.pix.border = dcdef.pixel;
	    }
	  else
	    {
	      master.pix.border = BlackPixel(dpy, DefaultScreen(dpy));
	    }

	  usrval = XGetDefault(dpy, ZWGCNAME, "pointerColor");
	  if(!usrval) usrval = XGetDefault(dpy, ZWGCNAME, "Foreground");
	  if(usrval
	     && XParseColor(dpy, dcolmap, usrval, &cursdef)
	     && XParseColor(dpy, dcolmap, back_color, &dcdef))
	    {
	      XRecolorCursor(dpy, globalcursor, &cursdef, &dcdef);
	    }
	}	  
	
      usrval = XGetDefault(dpy, ZWGCNAME, "reverseVideo");
      if(!usrval) usrval = XGetDefault(dpy, ZWGCNAME, "ReverseVideo");
      if(xtx_istrue(usrval,FALSE))
	{
	  unsigned long swapcolor;
	  if(master.pix.border == master.pix.fore)
	    {
	      master.pix.border = master.pix.back;
	    }
	  else if(master.pix.border == master.pix.back)
	    {
	      master.pix.border = master.pix.fore;
	    }
	  swapcolor = master.pix.fore;
	  master.pix.fore = master.pix.back;
	  master.pix.back = swapcolor;
	}

      xgcv.foreground = master.pix.fore;
      xgcv.background = master.pix.back;
      xgcv.graphics_exposures = FALSE;
      vmask = GCForeground | GCBackground | GCFont | GCGraphicsExposures;

      usrval = XGetDefault(dpy, ZWGCNAME, "borderWidth");
      if(!usrval) usrval = XGetDefault(dpy, ZWGCNAME, "BorderWidth");
      master.bwidth = usrval?atoi(usrval):1;

      usrval = XGetDefault(dpy, ZWGCNAME, "internalBorder");
      if(!usrval) usrval = XGetDefault(dpy, ZWGCNAME, "BorderWidth");
      master.inbwid = usrval?atoi(usrval):2;
      
      for(i=0;i<NFONTS;i++)
	{
	  usrval = XGetDefault(dpy, ZWGCNAME, fontopts[i]);
	  if(usrval)
	    {
	      xfs[i] = XLoadQueryFont(dpy, usrval);
	      if(!xfs[i])
		{
#ifdef FONTSPEEDHACK
		  if(i>3) xfs[i]=xfs[3]; else
#endif
		  xfs[i] = XLoadQueryFont(dpy, fontnames[i]);
		}
	    }
	  else
	    {
#ifdef FONTSPEEDHACK
	      if(i>3) xfs[i]=xfs[3]; else
#endif
	      xfs[i] = XLoadQueryFont(dpy, fontnames[i]);
	    }
	  if(!xfs[i])
	    {
	      xfs[i] = XLoadQueryFont(dpy, "fixed");
	      if(!xfs[i])
		{
		  fprintf(stderr,
			  "\
zwgc:Couldn't load any fonts. Not even 'fixed'!!!\n\
     Try checking font path (use 'xset q' and 'xlsfonts').\n\
     Since you have no fonts, I must revert to ttymode.\n\
     All further messages will appear on this console.\n");
		  dpy = NULL;
		  ttymode = TRUE;
		  return(0);
		}
	    }
	  xgcv.font = xfs[i]->fid;
#ifdef FONTSPEEDHACK
	  if(i>3 && xfs[i]->fid==xfs[3]->fid) gcs[i]=gcs[3]; else
#endif
	  gcs[i] = XCreateGC(dpy, DefaultRootWindow(dpy), vmask, &xgcv);
	}
      xgcv.foreground = master.pix.back;
      backgc = XCreateGC(dpy, DefaultRootWindow(dpy), GCForeground, &xgcv);

      
      master.x = master.y = master.wid = master.height = 0;
      usrval = XGetDefault(dpy, ZWGCNAME, "geometry");
      if(!usrval) usrval = XGetDefault(dpy, ZWGCNAME, "Geometry");
      master.mask = XGeometry(dpy, DefaultScreen(dpy),
			      usrval,
			      "+0+0",
			      master.bwidth, 0, 0, 0, 0,
			      &master.x, &master.y,
			      &master.wid, &master.height); /* 2 ignored */
      
      usrval = XGetDefault(dpy, ZWGCNAME, "resetSaver");
      master.resetscreen = xtx_istrue(usrval,TRUE);

      usrval = XGetDefault(dpy, ZWGCNAME, "reverseStack");
      master.revstack = xtx_istrue(usrval,FALSE);

      usrval = XGetDefault(dpy, ZWGCNAME, "name");
      if(usrval) master.name = usrval;
      
      usrval = XGetDefault(dpy, ZWGCNAME, "iconName");
      if(usrval) master.iconname = usrval;
      
      if(!master.usebackground && !master.exposetab)
	{
	  /* extend this: check null return, tweak bucket size. */
	  /* master.exposetab=XCreateAssocTable(32); */
	  master.exposetab = XUniqueContext();
	  /* perhaps do an XDestroyAssocTable first... */
	}

      usrval = XGetDefault(dpy, ZWGCNAME, "volume");
      master.volume = usrval?atoi(usrval):0;

      usrval = XGetDefault(dpy, ZWGCNAME, "interline");
      master.interln = usrval?atoi(usrval):1;

      usrval = XGetDefault(dpy, ZWGCNAME, "debugBack");
      master.debugback = xtx_istrue(usrval,FALSE);

      master.tagatom = XInternAtom(dpy, ZWGCATOMNAME, False);
    }
  return(ttymode?0:ConnectionNumber(dpy));
}

#else /* !X11 */

int xtx_draw_init(foo)
int foo;
{
  ttymode=1;
  return(0);  /* ttymode */
}

#endif /* X11 */

#define TTYPUNT if(ttymode) return
#define TTYVOID(x) if(ttymode) { x; return; }
#define TTYTWO(x,y) if(ttymode) { x; return y; }

Draw xtx_newdraw()
{
  Draw rv;

  TTYTWO(fputs("\r\n",stdout),NULL);
  
#ifdef X11
  rv = New(draw);
  rv->ln = ANew(line,1);
  rv->nln = 1;

  rv->ln[0].nsd[0] = rv->ln[0].nsd[1] = rv->ln[0].nsd[2] = 0;
  rv->ln[0].lwid = rv->ln[0].amax = rv->ln[0].dmax = 0;
  rv->ln[0].y = 0;
  
  rv->cursid = LEFT;
  rv->curfn = 0;

  rv->pm = NULL;
  return(rv);
#else /* !X11 */
  return(NULL);
#endif /* X11 */
}

void xtx_setfont(d,fn)
     Draw	d;
     int	fn;
{
  TTYPUNT;
  
#ifdef X11  
  d->curfn = fn;
#endif /* X11 */
}
void xtx_setside(d,fn)
     Draw	d;
     int	fn;
{
  TTYPUNT;
  
#ifdef X11
  d->cursid = fn;
#endif /* X11 */
}

void xtx_drawtext_raw();

void xtx_breakline(d)
     Draw	d;
{
  /* can't hurt to put out the \r in normal mode, so do it all the time
     to fix blatting when the tty is in one of the literal modes */
  TTYVOID(fputs("\r\n",stdout));	/* this gets called one extra at end... */

  /* detect if there is nothing on the line and call
   * xtx_drawtext_raw(d, " ") to make a blank line of the right font
   * as a blank line. */
  
#ifdef X11
  if(d->ln[d->nln-1].nsd[LEFT] == 0 &&
     d->ln[d->nln-1].nsd[CENTER] == 0 &&
     d->ln[d->nln-1].nsd[RIGHT] == 0)
    {
      xtx_drawtext_raw(d, " ");
    }

  d->ln = Grow(line, d->ln, ++d->nln);
  d->ln[d->nln-1].nsd[LEFT] =
      d->ln[d->nln-1].nsd[CENTER] =
	d->ln[d->nln-1].nsd[RIGHT] = 0;
  
  d->ln[d->nln-1].lwid = 0;
  d->ln[d->nln-1].amax = 0;
  d->ln[d->nln-1].dmax = 0;
  d->ln[d->nln-1].y = 0;
#endif /* X11 */
}  

static Sides newside()
{
  Sides rv;
  TTYPUNT(NULL);
  
#ifdef X11  
  rv = New(side);
  rv[0].str = NULL;
  rv[0].fontnum = 0;
  rv[0].wid = rv[0].asc = rv[0].des = 0;
  rv[0].x = 0;

  return(rv);
#endif /* X11 */
}

void xtx_drawtext(d, instr)
     Draw	d;
     char	*instr;
{
  char *mark;
  char *set=instr;

  if (ttymode) {
      while (1)
	  if (set = index(instr, '\n')) {
	      /* do newline processing, ick!
	       The convolutions here are to avoid modifying the input */
	      fwrite(instr, sizeof(char), set-instr, stdout);
	      fputs("\r\n", stdout);
	      instr = set + 1;
	      if (!*instr)
		  break;
	  } else {
	      fputs(instr, stdout);
	      break;
	  }
      return;
  }
 
#ifdef X11 
  while(mark=index(set,'\n'))
    {
      *mark = '\0';
      xtx_drawtext_raw(d, set);
      *mark = '\n';
      set = mark+1;
      xtx_breakline(d);
    }
  xtx_drawtext_raw(d, set);
#endif /* X11 */
}

#ifdef X11
void xtx_drawtext_raw(d, instr)
     Draw	d;
     char	*instr;
{
  Lines ll = &d->ln[d->nln-1];
  XCharStruct ovr;
  char *str = strcpy(malloc(strlen(instr)+1),instr);
  Sides ss;
  int dir;
  
  if(ll->nsd[d->cursid]==0)
    {
      ll->sd[d->cursid] = newside();
      ll->nsd[d->cursid] = 1;
    }

  ss = &ll->sd[d->cursid][ll->nsd[d->cursid]-1];

  if(ss->str == NULL)		/* unused prev block? */
    {
      ss->str = str;
      ss->fontnum = d->curfn;
      XTextExtents(xfs[d->curfn], str, strlen(str),
		   &dir,
		   &ss->asc,
		   &ss->des,
		   &ovr);	/* move this to postdraw? */
      ss->wid = ovr.width;
    }
  else if(ss->fontnum == d->curfn)	/* same font block? */
    {
      char **lstr = &ss->str;
      int asc, des;
      
      *lstr = realloc(*lstr,strlen(*lstr)+strlen(str)+1);
      (void)strcat(*lstr, str);
      
      XTextExtents(xfs[d->curfn], str, strlen(str),
		   &dir,
		   &asc, &des, &ovr); /* move this to postdraw? */
      free(str);		/* done with this copy. */
      ss->wid += ovr.width;
      ss->asc = max(ss->asc, asc);
      ss->des = max(ss->des, des);
    }
  else				/* nope, make a new one */
    {
      ll->sd[d->cursid] = Grow(side,ll->sd[d->cursid],++ll->nsd[d->cursid]);
      ss = &ll->sd[d->cursid][ll->nsd[d->cursid]-1];
      ss->str = str;
      ss->fontnum = d->curfn;
      XTextExtents(xfs[d->curfn], str, strlen(str),
		   &dir,
		   &ss->asc,
		   &ss->des,
		   &ovr);	/* move this to postdraw? */
      ss->wid = ovr.width;
    }
}

winlist *new_winlist()
{
  winlist *wn = New(winlist);
  wn->next = NULL;
  wn->win = 0;
  wn->serial = 0;
  return(wn);
}

void free_winlist(wl)
     winlist *wl;
{
  free(wl);
}

unsigned long stackunder(me, with)
     Window me, with;
{
  XWindowChanges xwc;
  unsigned long nextreq;

  xwc.sibling = with;
  xwc.stack_mode = Below;
  nextreq = NextRequest(dpy);
  XConfigureWindow(dpy, me, CWSibling|CWStackMode, &xwc);
  /* This may generate an error on reparenting window managers...
     Hopefully XFlush() will cause the error to appear before we try another
     restack */
  XFlush(dpy);
  return(nextreq);
}


config_stack *new_config_stack()
{
    config_stack *cs = New(config_stack);
    cs->next = NULL;
    return(cs);
}

void append_cstack(cstack, under, over)
config_stack *cstack;
Window under, over;
{
  register config_stack *cs = cstack;
  
  while(cs->next) {
    cs = cs->next;
  }
  cs->next = new_config_stack();
  cs = cs->next;
  cs->under = under;
  cs->over = over;
}

/* get called here if the ConfigureWindow request generated a BadMatch
   (means we were reparented) */
void wmerror_stackunder(serial)
unsigned long serial;
{
    /* we are not allowed to do Xlib calls here... grr */

    winlist *ww = master.winlist, *ow=NULL;
    extern int do_x_configwindow;

    while(ww->next && (ww->serial != serial)) {
	ow = ww;
	ww = ww->next;
    }
    if (!ow || !ww->next)		/* the window has already gone away,
					   or there is only one left */
	return;

    /* ww points to the window to bury
       ow points to the window to bury under */

    if (!master.cstack) {
	master.cstack = new_config_stack();
	master.cstack->over = ow->win;
	master.cstack->under = ww->win;
    } else
	append_cstack(master.cstack, ww->win, ow->win);

    do_x_configwindow = 1;
    return;
}

#define free_config_stack(cs) free((char *)cs);

void handle_x_configwindow()
{
    /*
     * This is per ICCCM, Sec 4.1.5.
     * Under some window managers (e.g. reparenting wm's),
     * the XConfigureWindow above fails because the two windows aren't
     * siblings.  In such a case, we need to send a synthetic event
     * to the root window as specified in Sec. 4.1.5
     */
    register config_stack *cs = master.cstack;
    register config_stack *ncs;
    XConfigureRequestEvent config_event;

    extern int do_x_configwindow;

    while (cs) {
	config_event.type = ConfigureRequest;
	config_event.display = dpy;
	config_event.parent = DefaultRootWindow(dpy);
	config_event.window = cs->under;
	config_event.above = cs->over;
	config_event.detail = Below;
	config_event.value_mask = CWSibling|CWStackMode;
	XSendEvent(dpy, DefaultRootWindow(dpy), False,
		   (SubstructureRedirectMask|SubstructureNotifyMask),
		   (XEvent *)&config_event);
	ncs = cs->next;
	free_config_stack(cs);
	cs = ncs;
    }
    master.cstack = 0;
    do_x_configwindow = 0;
    return;
}

void append_win(list, win)
     winlist *list;
     Window win;
{
  winlist *ww = list, *ow=NULL;
  /* printf("appending:"); */
  
  while(ww->next) {
    ow = ww;
    ww = ww->next;
  }
  if(ow) {
    /* printf("[stacking 0x%x with 0x%x]", win, ow->win); */
    ww->serial = stackunder(win, ow->win);
  }
  /* printf("stuffing 0x%x\n", win); */
  ww->win = win;
  ww->next = new_winlist();
}

void delete_win(list, win)
     winlist *list;
     Window win;
{
  winlist *ww = list;
  /* printf("trying to delete: 0x%x\n", win); */
  while(ww && (ww->win != win)) ww = ww->next;
  if(ww && ww->next)
    {
      winlist *wt;
      /* printf("deleting: 0x%x, stuffing 0x%x\n",
	     ww->win, ww->next->win); */
      ww->win = ww->next->win;
      wt = ww->next;		/* copy, to free later */
      ww->next = ww->next->next;
      free_winlist(wt);
    }
}

void xtx_register_expose(win,pm)
     Window win;
     caddr_t pm;
{
  /*  XMakeAssoc(dpy, master.exposetab, win, pm); */
  XSaveContext(dpy, win, master.exposetab, pm); /* returns error */
}

void xtx_unregister_expose(win)
     Window win;
{
  /* XDeleteAssoc(dpy, master.exposetab, win); */
  caddr_t pm;

  if(XCNOENT!=XFindContext(dpy, win, master.exposetab, &pm))
    {
      if(master.textupdate)
	{
	  xtx_dispose_draw((Draw)pm);
	}
      else
	{
	  XFreePixmap(dpy, (Pixmap)pm);
	}
      XDeleteContext(dpy, win, master.exposetab); /* returns error */
    }
  if (master.revstack) {
      if (master.winlist)
	  delete_win(master.winlist, win);
  }
}

void xtx_dbg_draw(d)
     Draw d;
{
  int i,j,asds;

  printf("disp 0x%x, nln=%d, cursid=%d, curfn=%d\n",
	 d, d->nln, d->cursid, d->curfn);
  for(i=0;i<d->nln;i++)
    {
      printf("line [%d]:lwid=%d, amax=%d, dmax=%d, y=%d\n",
	     i,d->ln[i].lwid,
	     d->ln[i].amax,
	     d->ln[i].dmax,
	     d->ln[i].y);
      for(asds=0;asds<3;asds++)
	{
	  printf("side[%d]:nsd=%d\n",asds,d->ln[i].nsd[asds]);
	  for(j=0;j<d->ln[i].nsd[asds];j++)
	    {
	      printf("piece[%d]:str=<%s>,fn=%d,wid=%d,asc=%d,des=%d, x=%d\n",
		     j,
		     d->ln[i].sd[asds][j].str,
		     d->ln[i].sd[asds][j].fontnum,
		     d->ln[i].sd[asds][j].wid,
		     d->ln[i].sd[asds][j].asc,
		     d->ln[i].sd[asds][j].des,
		     d->ln[i].sd[asds][j].x);
	    }
	}
    }
}
	      

void xtx_expose_text(event, d)
     XEvent *event;
     Draw d;
{
  int i,j,asds, oasds;
  
  for(i=0;i < d->nln; i++)
    {
      if(event->xexpose.y < d->ln[i].y + d->ln[i].dmax + master.inbwid)
	{
	  break;
	}
    }
  if(i >= d->nln) return;	/* no expose done... */

  for(;i<d->nln;i++)
    {
      if(event->xexpose.y+event->xexpose.height
	 < d->ln[i].y - d->ln[i].amax + master.inbwid)
	{
	  break;
	}
      oasds=3;
      for(asds=0;asds<3;asds++)
	for(j=0;j < d->ln[i].nsd[asds];j++)
	  if(event->xexpose.x
	     < d->ln[i].sd[asds][j].x+d->ln[i].sd[asds][j].wid+master.inbwid)
	    {
	      oasds = asds; asds = 3; break;
	    }
      if(oasds>=3) continue;
      for(asds=oasds;asds<3;asds++,j=0)
	for(;j < d->ln[i].nsd[asds];j++)
	  {
	    if(event->xexpose.x+event->xexpose.width
	       < d->ln[i].sd[asds][j].x + master.inbwid)
	      {
		asds = 3; break;
	      }
	    XDrawImageString(dpy, d->pm, gcs[d->ln[i].sd[asds][j].fontnum],
			     d->ln[i].sd[asds][j].x+master.inbwid,
			     d->ln[i].y+master.inbwid,
			     d->ln[i].sd[asds][j].str,
			     strlen(d->ln[i].sd[asds][j].str));
	  }
    }
  XFlush(dpy);
}

void xtx_handle_expose(event)
     XEvent *event;
{
  Window win = event->xexpose.window;
  caddr_t pm;
  
  /*  Pixmap pm = (Pixmap)XLookUpAssoc(dpy, master.exposetab, win); */
  if (XFindContext(dpy, win, master.exposetab, &pm)) {
      /* not there! probably user clicked on something
	 being exposed. */
      return;
  }
  if(master.textupdate)
    {
      xtx_expose_text(event, (Draw)pm);
    }
  else
    {
      XCopyArea(dpy, (Pixmap)pm, win, gcs[0], /* right gc??? */
		event->xexpose.x, event->xexpose.y,
		event->xexpose.width, event->xexpose.height,
		event->xexpose.x, event->xexpose.y);
    }
}
#endif /* X11 */

Window xtx_postdraw(d)
     Draw d;
{
#ifdef X11
  int wid=0, hi= -master.interln;
  int BRD = 2*master.inbwid;
  int *widc, *widr;
  int i,j;
  int asds;
  side nullside;
#endif /* X11 */
  
  TTYPUNT fflush(stdout),1;

#ifdef X11
  nullside.str = NULL;
  nullside.fontnum = 0;
  nullside.wid = nullside.asc = nullside.des = 0;
  nullside.x = 0;

  
  widc = ANew(int, d->nln);
  widr = ANew(int, d->nln);

  for(i=0;i < d->nln;i++)
    {
      side ss[3];
      line *ll = &d->ln[i];
      d->ln[i].lwid=d->ln[i].amax=d->ln[i].dmax=0;
      for(asds=0;asds<3;asds++)
	{
	  ss[asds] = nullside;
	  for(j=0; j < ll->nsd[asds]; j++)
	    {
	      ss[asds].wid += ll->sd[asds][j].wid;
	      ss[asds].asc = max(ss[asds].asc, ll->sd[asds][j].asc);
	      ss[asds].des = max(ss[asds].des, ll->sd[asds][j].des);
	    }
	}
      /*
       * line = 2*max(left,right)+center+2*gap
       *      = 2*max(left+center/2, right+center/2)+2*gap
       *      but that is if center;
       * center == 0:
       * line = left+right+gap
       * if(left==0 || right==0) line -= gap
       *
       * Thus:
       * line = left+right+|left-right|+center+2*gap
       *      = (left+(right+gap)) + (|left-right|+center+gap)
       */
      d->ln[i].lwid = ss[LEFT].wid+ss[RIGHT].wid;
      if(ss[LEFT].wid && ss[RIGHT].wid)
	{
	  d->ln[i].lwid += GAP;
	}
      if(ss[CENTER].wid)
	{
	  d->ln[i].lwid += abs(ss[LEFT].wid-ss[RIGHT].wid) + GAP+ss[CENTER].wid;
	}
      widc[i] = ss[CENTER].wid;
      widr[i] = ss[RIGHT].wid;
      d->ln[i].amax = max(max(ss[LEFT].asc,ss[RIGHT].asc),ss[CENTER].asc);
      d->ln[i].dmax = max(max(ss[LEFT].des,ss[RIGHT].des),ss[CENTER].des);

      wid = max(wid, d->ln[i].lwid);
      hi += d->ln[i].amax + d->ln[i].dmax + master.interln;
      ll->y = hi - d->ln[i].dmax;
    }
  for(i=0;i < d->nln;i++)
    {
      side ss[3];
      line *ll = &d->ln[i];
      ss[LEFT].x = 0;
      ss[CENTER].x = (wid - widc[i])/2;
      ss[RIGHT].x = wid - widr[i];
      for(asds=0;asds<3;asds++)
	{
	  for(j=0; j < ll->nsd[asds]; j++)
	    {
	      ll->sd[asds][j].x = ss[asds].x;
	      ss[asds].x += ll->sd[asds][j].wid;
	    }
	}
    }

  if(wid==0 || hi==0)
    {
      d->cursid = -2;
      return(NULL);		/* still an error! */
    }
  /*
   * Note: because of tiling, this could do wid+master.inbwid, since
   * the blank space on the bottom will just wrap the stuff on top;
   * this is probably easier for the server though.
   */
  if(!master.textupdate)
    {
      d->pm = XCreatePixmap(dpy, DefaultRootWindow(dpy),
			    wid+BRD, hi+BRD,
			    master.nplanes);
      if(!d->pm)
	{
	  d->cursid = -1;
	  return(NULL);		/* an error... but don't crash... */
	}
      /*
       * Since the contents of the pixmap returned by XCreatePixmap are
       * undefined, we have to fill it to clear it. [eichin:19880609.0628EST]
       */
      XFillRectangle(dpy, d->pm,
		     master.debugback?gcs[0]:backgc, /* fix for opus */
		     0, 0, wid+BRD, hi+BRD);
  
      for(i=0;i < d->nln;i++)
	{
	  line *ll = &d->ln[i];

	  for(asds=0;asds<3;asds++)
	    {
	      for(j=0; j < ll->nsd[asds]; j++)
		{
		  Sides ss = &ll->sd[asds][j];

		  XDrawImageString(dpy, d->pm, gcs[ss->fontnum],
				   ss->x+master.inbwid, ll->y+master.inbwid,
				   ss->str, strlen(ss->str));
		}
	    }
	}
    }
  {
    Window w;
    XSetWindowAttributes wvals;
    unsigned long valuemask;
    int thisx,thisy;

    if(master.usebackground)
      {
	wvals.background_pixmap = d->pm;
      }
    else
      {
	wvals.background_pixel =
	  master.debugback?master.pix.fore:master.pix.back;
      }
    wvals.border_pixel = master.pix.border;
    wvals.save_under = True;
    wvals.cursor = globalcursor;

    thisx = (master.mask & XNegative)?
      (master.x-(wid+BRD)):master.x;

    thisy = (master.mask & YNegative)?
      (master.y-(hi+BRD)):master.y;

       
    valuemask = ((master.usebackground?CWBackPixmap:CWBackPixel)
		 | CWBorderPixel
		 | CWSaveUnder
		 | CWCursor);
    
    w = XCreateWindow(dpy, DefaultRootWindow(dpy),
		      thisx, thisy,
		      wid+BRD, hi+BRD, master.bwidth,
		      master.nplanes,
		      CopyFromParent,
		      CopyFromParent,
		      valuemask, &wvals);
    {
	XSizeHints sizeHints;
	XWMHints wmHints;
	XClassHint classHint;

	extern int saved_argc;
	extern char** saved_argv;
	register char *cp;

	sizeHints.flags = USSize | USPosition;
	sizeHints.x = thisx; sizeHints.y = thisy;
	sizeHints.width = wid+BRD; sizeHints.height = hi+BRD;
	XSetStandardProperties(dpy, w, master.name, master.iconname, None,
			       saved_argv, saved_argc, &sizeHints);
	wmHints.flags = InputHint;
	wmHints.input = False;		/* never need keyboard input */
	XSetWMHints(dpy, w, &wmHints);
	cp = rindex(saved_argv[0], '/');
	if (cp)
	    cp++;
	else
	    cp = saved_argv[0];
	classHint.res_name = cp;
	classHint.res_class = "Zwgc";
	XSetClassHint(dpy, w, &classHint);
    }
    if(master.usebackground)
      {
	XSelectInput(dpy, w,ButtonPressMask|ButtonReleaseMask|LeaveWindowMask);
	XFreePixmap(dpy, d->pm);
      }
    else
      {
	XSelectInput(dpy, w,ButtonPressMask|ButtonReleaseMask|LeaveWindowMask
		     |ExposureMask);
	if(master.textupdate)
	  {
	    Draw d2 = New(draw); /* make a shadow copy */
	    *d2 = *d;
	    d2->pm = w;
	    xtx_register_expose(w, (caddr_t)d2);
	    d->nln = 0;
	    d->ln = NULL;
	  }
	else
	  {
	    xtx_register_expose(w, (caddr_t)d->pm);
	  }
      }
    if(!master.textupdate)
      {
	d->pm = NULL;
      }
    d->w = w;
    XFlush (dpy);
    free(widc);
    free(widr);
    if(!w)
      {
	d->cursid = -1;		/* for now, assume irrecoverable. */
      }
    return(w);
  }
#endif /* X11 */
}
void xtx_map_draw(w, str)
     Window w;
     char *str;
{
  TTYPUNT;
#ifdef X11
  XChangeProperty(dpy, w, master.tagatom, XA_STRING,
		  8, PropModeReplace, (unsigned char *)str,
		  strlen(str));
  XMapWindow(dpy, w);
  if (master.revstack) {
      if (!master.winlist) master.winlist = new_winlist();
      append_win(master.winlist, w);
  }
  if(master.resetscreen)
    {
      XResetScreenSaver(dpy);
    }
#endif /* X11 */
}
static void xtx_dispose_line(ln)
     line *ln;
{
  int asds, j;
  
  for(asds=0;asds<3;asds++)
    {
      for(j=0; j < ln->nsd[asds]; j++)
	{
	  free(ln->sd[asds][j].str);
	}
      if(j) free(ln->sd[asds]);	/* were there ANY? */
    }
  /*   free(ln); <-- don't do this! the arg is *not* malloc'd alone */
}
void xtx_dispose_draw(d)
     Draw d;
{
  int i;
  TTYPUNT;
  
#ifdef X11  
  for(i=0;i<d->nln;i++)
    {
      xtx_dispose_line(&d->ln[i]);
    }
  if(d->ln) free(d->ln);	/* might be 0, if a shadow copy... */
  free(d);
#endif /* X11 */
}

void xtx_ring_bell()
{
  TTYVOID(putchar('\007'));
  
#ifdef X11  
  XBell(dpy, master.volume);
#endif /* X11 */
}
