#ifndef lint
static const char	RCSid[] = "$Id: x11.c,v 2.35 2011/05/20 02:06:39 greg Exp $";
#endif
/*
 *  x11.c - driver for X-windows version 11
 */

#include "copyright.h"

#include  "standard.h"
#include  <sys/ioctl.h>
#if  !defined(FNDELAY) && defined(O_NONBLOCK)
#define  FNDELAY  O_NONBLOCK
#endif

#include  <X11/Xlib.h>
#include  <X11/cursorfont.h>
#include  <X11/Xutil.h>

#include  "platform.h"
#include  "color.h"
#include  "driver.h"
#include  "x11twind.h"
#include  "x11icon.h"

#define GAMMA		2.2		/* default exponent correction */

#define MINWIDTH	(32*COMCW)	/* minimum graphics window width */
#define MINHEIGHT	(MINWIDTH/2)	/* minimum graphics window height */

#define BORWIDTH	5		/* border width */
#define COMHEIGHT	(COMLH*COMCH)	/* command line height (pixels) */

#define COMFN		"8x13"		/* command line font name */
#define COMLH		3		/* number of command lines */
#define COMCW		8		/* approx. character width (pixels) */
#define COMCH		14		/* approx. character height (pixels) */

#define  ourscreen	DefaultScreen(ourdisplay)
#define  ourroot	RootWindow(ourdisplay,ourscreen)

#define  levptr(etype)	((etype *)&currentevent)

static XEvent  currentevent;		/* current event */

static int  ncolors = 0;		/* color table size */
static int  mapped = 0;			/* window is mapped? */
static unsigned long  *pixval = NULL;	/* allocated pixels */
static unsigned long  ourblack=0, ourwhite=1;

static Display  *ourdisplay = NULL;	/* our display */

static XVisualInfo  ourvinfo;		/* our visual information */

static Window  gwind = 0;		/* our graphics window */

static Cursor  pickcursor = 0;		/* cursor used for picking */

static int  gwidth, gheight;		/* graphics window size */

static int  comheight;			/* desired comline height */
static TEXTWIND  *comline = NULL;	/* our command line */

static char  c_queue[64];		/* input queue */
static int  c_first = 0;		/* first character in queue */
static int  c_last = 0;			/* last character in queue */

static GC  ourgc = 0;			/* our graphics context for drawing */

static Colormap ourmap = 0;		/* our color map */

#define IC_X11		0
#define IC_IOCTL	1
#define IC_READ		2

static int  inpcheck;			/* whence to check input */

static void x11_errout(char  *msg);

static dr_closef_t x11_close;
static dr_clearf_t x11_clear;
static dr_paintrf_t x11_paintr;
static dr_getcurf_t x11_getcur;
static dr_comoutf_t x11_comout;
static dr_cominf_t x11_comin;
static dr_flushf_t x11_flush;

static dr_cominf_t std_comin;
static dr_comoutf_t std_comout;

static struct driver  x11_driver = {
	x11_close, x11_clear, x11_paintr, x11_getcur,
	NULL, NULL, x11_flush, 1.0
};

static dr_getchf_t x11_getc;

static void freepixels(void);
static int getpixels(void);
static dr_newcolrf_t xnewcolr;
static unsigned long true_pixel(COLOR  col);
static void getevent(void);
static void getkey(XKeyPressedEvent  *ekey);
static void fixwindow(XExposeEvent  *eexp);
static void resizewindow(XConfigureEvent  *ersz);

extern dr_initf_t x11_init; /* XXX this should be in a seperate header file */


extern struct driver *
x11_init(		/* initialize driver */
	char  *name,
	char  *id
)
{
	char  *gv;
	int  nplanes;
	XSetWindowAttributes	ourwinattr;
	XWMHints  ourxwmhints;
	XSizeHints	oursizhints;
					/* open display server */
	ourdisplay = XOpenDisplay(NULL);
	if (ourdisplay == NULL) {
		eputs("cannot open X-windows; DISPLAY variable set?\n");
		return(NULL);
	}
					/* find a usable visual */
	nplanes = DisplayPlanes(ourdisplay, ourscreen);
	if (XMatchVisualInfo(ourdisplay,ourscreen,
				nplanes>12?nplanes:24,TrueColor,&ourvinfo) ||
			XMatchVisualInfo(ourdisplay,ourscreen,
				nplanes>12?nplanes:24,DirectColor,&ourvinfo)) {
		ourblack = 0;
		ourwhite = ourvinfo.red_mask |
				ourvinfo.green_mask |
				ourvinfo.blue_mask ;
	} else {
		if (nplanes < 4) {
			eputs("not enough colors\n");
			return(NULL);
		}
		if (!XMatchVisualInfo(ourdisplay,ourscreen,
					nplanes,PseudoColor,&ourvinfo) &&
				!XMatchVisualInfo(ourdisplay,ourscreen,
					nplanes,GrayScale,&ourvinfo)) {
			eputs("unsupported visual type\n");
			return(NULL);
		}
		ourblack = BlackPixel(ourdisplay,ourscreen);
		ourwhite = WhitePixel(ourdisplay,ourscreen);
	}
					/* set gamma */
	if ((gv = XGetDefault(ourdisplay, "radiance", "gamma")) != NULL
			|| (gv = getenv("DISPLAY_GAMMA")) != NULL)
		make_gmap(atof(gv));
	else
		make_gmap(GAMMA);
					/* X11 command line or no? */
	if (!strcmp(name, "x11"))
		comheight = COMHEIGHT;
	else /* "x11d" */ {
		comheight = 0;
#ifndef  FNDELAY
		eputs("warning: x11d driver not fully functional on this machine\n");
#endif
	}
					/* open window */
	ourwinattr.background_pixel = ourblack;
	ourwinattr.border_pixel = ourblack;
					/* this is stupid */
	ourwinattr.colormap = XCreateColormap(ourdisplay, ourroot,
				ourvinfo.visual, AllocNone);
	gwind = XCreateWindow(ourdisplay, ourroot, 0, 0,
		DisplayWidth(ourdisplay,ourscreen)-2*BORWIDTH,
		DisplayHeight(ourdisplay,ourscreen)-2*BORWIDTH,
		BORWIDTH, ourvinfo.depth, InputOutput, ourvinfo.visual,
		CWBackPixel|CWBorderPixel|CWColormap, &ourwinattr);
	if (gwind == 0) {
		eputs("cannot create window\n");
		return(NULL);
	}
   	XStoreName(ourdisplay, gwind, id);
	/* create a cursor */
	pickcursor = XCreateFontCursor(ourdisplay, XC_diamond_cross);
	ourgc = XCreateGC(ourdisplay, gwind, 0, NULL);
	ourxwmhints.flags = InputHint|IconPixmapHint;
	ourxwmhints.input = True;
	ourxwmhints.icon_pixmap = XCreateBitmapFromData(ourdisplay,
			gwind, x11icon_bits, x11icon_width, x11icon_height);
	XSetWMHints(ourdisplay, gwind, &ourxwmhints);
	oursizhints.min_width = MINWIDTH;
	oursizhints.min_height = MINHEIGHT+comheight;
	oursizhints.flags = PMinSize;
	XSetNormalHints(ourdisplay, gwind, &oursizhints);
	XSelectInput(ourdisplay, gwind, ExposureMask);
	XMapWindow(ourdisplay, gwind);
	XWindowEvent(ourdisplay, gwind, ExposureMask, levptr(XEvent));
	gwidth = levptr(XExposeEvent)->width;
	gheight = levptr(XExposeEvent)->height - comheight;
	x11_driver.xsiz = gwidth < MINWIDTH ? MINWIDTH : gwidth;
	x11_driver.ysiz = gheight < MINHEIGHT ? MINHEIGHT : gheight;
	x11_driver.inpready = 0;
	mapped = 1;
					/* set i/o vectors */
	if (comheight) {
		x11_driver.comin = x11_comin;
		x11_driver.comout = x11_comout;
		erract[COMMAND].pf = x11_comout;
		/*			doesn't work with raypcalls.c
		if (erract[WARNING].pf != NULL)
			erract[WARNING].pf = x11_errout;
		*/
		inpcheck = IC_X11;
	} else {
		x11_driver.comin = std_comin;
		x11_driver.comout = std_comout;
		erract[COMMAND].pf = std_comout;
		inpcheck = IC_IOCTL;
	}
	return(&x11_driver);
}


static void
x11_close(void)			/* close our display */
{
	erract[COMMAND].pf = NULL;		/* reset error vectors */
	if (erract[WARNING].pf != NULL)
		erract[WARNING].pf = wputs;
	if (ourdisplay == NULL)
		return;
	if (comline != NULL) {
		xt_close(comline);
		comline = NULL;
	}
	freepixels();
	XFreeGC(ourdisplay, ourgc);
	XDestroyWindow(ourdisplay, gwind);
	gwind = 0;
	ourgc = 0;
	XFreeCursor(ourdisplay, pickcursor);
	XCloseDisplay(ourdisplay);
	ourdisplay = NULL;
}


static void
x11_clear(			/* clear our display */
	int  xres,
	int  yres
)
{
						/* check limits */
	if (xres < MINWIDTH)
		xres = MINWIDTH;
	if (yres < MINHEIGHT)
		yres = MINHEIGHT;
						/* resize window */
	if (xres != gwidth || yres != gheight) {
		XSelectInput(ourdisplay, gwind, 0);
		XResizeWindow(ourdisplay, gwind, xres, yres+comheight);
		gwidth = xres;
		gheight = yres;
		XFlush(ourdisplay);
		sleep(2);			/* wait for window manager */
		XSync(ourdisplay, 1);		/* discard input */
	}
	XClearWindow(ourdisplay, gwind);
						/* reinitialize color table */
	if (ourvinfo.class == PseudoColor || ourvinfo.class == GrayScale) {
		if (getpixels() == 0)
			eputs("cannot allocate colors\n");
		else
			new_ctab(ncolors);
	}
						/* get new command line */
	if (comline != NULL)
		xt_close(comline);
	if (comheight) {
		comline = xt_open(ourdisplay, gwind, 0, gheight, gwidth,
				comheight, 0, ourblack, ourwhite, COMFN);
		if (comline == NULL) {
			eputs("cannot open command line window\n");
			quit(1);
		}
		XSelectInput(ourdisplay, comline->w, ExposureMask);
						/* remove earmuffs */
		XSelectInput(ourdisplay, gwind,
		StructureNotifyMask|ExposureMask|KeyPressMask|ButtonPressMask);
	} else					/* remove earmuffs */
		XSelectInput(ourdisplay, gwind,
			StructureNotifyMask|ExposureMask|ButtonPressMask);
}


static void
x11_paintr(		/* fill a rectangle */
	COLOR  col,
	int  xmin,
	int  ymin,
	int  xmax,
	int  ymax
)
{
	unsigned long  pixel;

	if (!mapped)
		return;
	if (ncolors > 0)
		pixel = pixval[get_pixel(col, xnewcolr)];
	else
		pixel = true_pixel(col);
	XSetForeground(ourdisplay, ourgc, pixel);
	XFillRectangle(ourdisplay, gwind, 
		ourgc, xmin, gheight-ymax, xmax-xmin, ymax-ymin);
}


static void
x11_flush(void)			/* flush output */
{
	char	buf[256];
	int	n;
						/* check for input */
	XNoOp(ourdisplay);
	n = XPending(ourdisplay);			/* from X server */
	while (n-- > 0)
		getevent();
#ifdef FNDELAY
	if (inpcheck == IC_IOCTL) {			/* from stdin */
#ifdef FIONREAD
		if (ioctl(fileno(stdin), FIONREAD, &n) < 0) {
#else
		if (1) {
#endif
			if (fcntl(fileno(stdin), F_SETFL, FNDELAY) < 0) {
				eputs("cannot change input mode\n");
				quit(1);
			}
			inpcheck = IC_READ;
		} else
			x11_driver.inpready += n;
	}
	if (inpcheck == IC_READ) {
		n = read(fileno(stdin), buf, sizeof(buf)-1);
		if (n > 0) {
			buf[n] = '\0';
			tocombuf(buf, &x11_driver);
		}
	}
#endif
}


static void
x11_comin(		/* read in a command line */
	char  *inp,
	char  *prompt
)
{
	if (prompt != NULL) {
		x11_flush();		/* make sure we get everything */
		if (fromcombuf(inp, &x11_driver))
			return;
		xt_puts(prompt, comline);
	}
	xt_cursor(comline, TBLKCURS);
	editline(inp, x11_getc, x11_comout);
	xt_cursor(comline, TNOCURS);
}


static void
x11_comout(		/* output a string to command line */
	char  *outp
)
{
	if (comline == NULL || outp == NULL || !outp[0])
		return;
	xt_puts(outp, comline);
	if (outp[strlen(outp)-1] == '\n')
		XFlush(ourdisplay);
}


static void
x11_errout(			/* output an error message */
	char  *msg
)
{
	eputs(msg);		/* send to stderr also! */
	x11_comout(msg);
}


static void
std_comin(		/* read in command line from stdin */
	char  *inp,
	char  *prompt
)
{
	if (prompt != NULL) {
		if (fromcombuf(inp, &x11_driver))
			return;
		if (!x11_driver.inpready)
			std_comout(prompt);
	}
#ifdef FNDELAY
	if (inpcheck == IC_READ) {	/* turn off FNDELAY */
		if (fcntl(fileno(stdin), F_SETFL, 0) < 0) {
			eputs("cannot change input mode\n");
			quit(1);
		}
		inpcheck = IC_IOCTL;
	}
#endif
	if (gets(inp) == NULL) {
		strcpy(inp, "quit");
		return;
	}
	x11_driver.inpready -= strlen(inp) + 1;
	if (x11_driver.inpready < 0)
		x11_driver.inpready = 0;
}


static void
std_comout(		/* write out string to stdout */
	char	*outp
)
{
	fputs(outp, stdout);
	fflush(stdout);
}


static int
x11_getcur(		/* get cursor position */
	int  *xp,
	int  *yp
)
{
	while (XGrabPointer(ourdisplay, gwind, True, ButtonPressMask,
			GrabModeAsync, GrabModeAsync, None, pickcursor,
			CurrentTime) != GrabSuccess)
		sleep(2);

	do
		getevent();
	while (c_last <= c_first && levptr(XEvent)->type != ButtonPress);
	*xp = levptr(XButtonPressedEvent)->x;
	*yp = gheight-1 - levptr(XButtonPressedEvent)->y;
	XUngrabPointer(ourdisplay, CurrentTime);
	XFlush(ourdisplay);				/* insure release */
	if (c_last > c_first)			/* key pressed */
		return(x11_getc());
						/* button pressed */
	if (levptr(XButtonPressedEvent)->button == Button1)
		return(MB1);
	if (levptr(XButtonPressedEvent)->button == Button2)
		return(MB2);
	if (levptr(XButtonPressedEvent)->button == Button3)
		return(MB3);
	return(ABORT);
}


static void
xnewcolr(		/* enter a color into hardware table */
	int  ndx,
	int  r,
	int  g,
	int  b
)
{
	XColor  xcolor;

	xcolor.pixel = pixval[ndx];
	xcolor.red = r << 8;
	xcolor.green = g << 8;
	xcolor.blue = b << 8;
	xcolor.flags = DoRed|DoGreen|DoBlue;

	XStoreColor(ourdisplay, ourmap, &xcolor);
}


static int
getpixels(void)				/* get the color map */
{
	XColor  thiscolor;
	register int  i, j;

	if (ncolors > 0)
		return(ncolors);
	if (ourvinfo.visual == DefaultVisual(ourdisplay,ourscreen)) {
		ourmap = DefaultColormap(ourdisplay,ourscreen);
		goto loop;
	}
newmap:
	ourmap = XCreateColormap(ourdisplay,gwind,ourvinfo.visual,AllocNone);
loop:
	for (ncolors = ourvinfo.colormap_size;
			ncolors > ourvinfo.colormap_size/3;
			ncolors = ncolors*.937) {
		pixval = (unsigned long *)malloc(ncolors*sizeof(unsigned long));
		if (pixval == NULL)
			return(ncolors = 0);
		if (XAllocColorCells(ourdisplay,ourmap,0,NULL,0,pixval,ncolors))
			break;
		free((void *)pixval);
		pixval = NULL;
	}
	if (pixval == NULL) {
		if (ourmap == DefaultColormap(ourdisplay,ourscreen))
			goto newmap;		/* try it with our map */
		else
			return(ncolors = 0);	/* failed */
	}
	if (ourmap != DefaultColormap(ourdisplay,ourscreen))
		for (i = 0; i < ncolors; i++) {	/* reset black and white */
			if (pixval[i] != ourblack && pixval[i] != ourwhite)
				continue;
			thiscolor.pixel = pixval[i];
			thiscolor.flags = DoRed|DoGreen|DoBlue;
			XQueryColor(ourdisplay,
					DefaultColormap(ourdisplay,ourscreen),
					&thiscolor);
			XStoreColor(ourdisplay, ourmap, &thiscolor);
			for (j = i; j+1 < ncolors; j++)
				pixval[j] = pixval[j+1];
			ncolors--;
			i--;
		}
	XSetWindowColormap(ourdisplay, gwind, ourmap);
	return(ncolors);
}


static void
freepixels(void)				/* free our pixels */
{
	if (ncolors == 0)
		return;
	XFreeColors(ourdisplay,ourmap,pixval,ncolors,0L);
	free((void *)pixval);
	pixval = NULL;
	ncolors = 0;
	if (ourmap != DefaultColormap(ourdisplay,ourscreen))
		XFreeColormap(ourdisplay, ourmap);
	ourmap = 0;
}


static unsigned long
true_pixel(			/* return true pixel value for color */
	COLOR  col
)
{
	unsigned long  rval;
	uby8  rgb[3];

	map_color(rgb, col);
	rval = ourvinfo.red_mask*rgb[RED]/255 & ourvinfo.red_mask;
	rval |= ourvinfo.green_mask*rgb[GRN]/255 & ourvinfo.green_mask;
	rval |= ourvinfo.blue_mask*rgb[BLU]/255 & ourvinfo.blue_mask;
	return(rval);
}


static int
x11_getc(void)			/* get a command character */
{
	while (c_last <= c_first) {
		c_first = c_last = 0;		/* reset */
		getevent();			/* wait for key */
	}
	x11_driver.inpready--;
	return(c_queue[c_first++]);
}


static void
getevent(void)			/* get next event */
{
	XNextEvent(ourdisplay, levptr(XEvent));
	switch (levptr(XEvent)->type) {
	case ConfigureNotify:
		resizewindow(levptr(XConfigureEvent));
		break;
	case UnmapNotify:
		mapped = 0;
		freepixels();
		break;
	case MapNotify:
		if (ourvinfo.class == PseudoColor ||
				ourvinfo.class == GrayScale) {
			if (getpixels() == 0)
				eputs("cannot allocate colors\n");
			else
				new_ctab(ncolors);
		}
		mapped = 1;
		break;
	case Expose:
		fixwindow(levptr(XExposeEvent));
		break;
	case KeyPress:
		getkey(levptr(XKeyPressedEvent));
		break;
	case ButtonPress:
		break;
	}
}


static void
getkey(				/* get input key */
	register XKeyPressedEvent  *ekey
)
{
	register int  n;

	n = XLookupString(ekey, c_queue+c_last, sizeof(c_queue)-c_last,
				NULL, NULL);
	c_last += n;
	x11_driver.inpready += n;
}


static void
fixwindow(				/* repair damage to window */
	register XExposeEvent  *eexp
)
{
	char  buf[80];

	if (eexp->window == gwind) {
		sprintf(buf, "repaint %d %d %d %d\n",
			eexp->x, gheight - eexp->y - eexp->height,
			eexp->x + eexp->width, gheight - eexp->y);
		tocombuf(buf, &x11_driver);
	} else if (eexp->window == comline->w) {
		if (eexp->count == 0)
			xt_redraw(comline);
	}
}


static void
resizewindow(			/* resize window */
	register XConfigureEvent  *ersz
)
{
	if (ersz->width == gwidth && ersz->height-comheight == gheight)
		return;

	gwidth = ersz->width;
	gheight = ersz->height-comheight;
	x11_driver.xsiz = gwidth < MINWIDTH ? MINWIDTH : gwidth;
	x11_driver.ysiz = gheight < MINHEIGHT ? MINHEIGHT : gheight;

	tocombuf("new\n", &x11_driver);
}
