/* This file is part of STonX, the Atari ST Emulator for Unix/X
 * ============================================================
 * STonX is free software and comes with NO WARRANTY - read the file
 * COPYING for details
 */

/* TODO in the next few releases:
 * - allocate colors on demand (not all 4096 immediately)
 * - clean up code, remove dead flags and code
 * - test on more displays
 */
#include "defs.h"
#include "tosdefs.h"
#include "debug.h"
#include "ikbd.h"
#include "main.h"
#include "screen.h"
#include "utils.h"
#include "xlib_vdi.h"
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <sys/times.h>
#include <time.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <X11/keysym.h>

#ifndef CLK_TCK
#define CLK_TCK HZ
#endif

#define GCURX 0x25a
#define GCURY 0x258

UW stcolor_buf[320*200];

#define GRABMODE 0
#define HIDEMODE 1
#define REDRAW 1
#define BENCH_REFRESH 0
#define NUM_AVG	100
#define SET_MOUSE 1

#ifdef SH_MEM
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
#ifndef SHMMAX
#define SHMMAX (1<<22)
#endif

static XShmSegmentInfo shminfo;
static shm_used;

#endif
static char txt[256];
Cursor cursors[2];
static int cursidx=0;
static int warped=0;
#if BENCH_REFRESH
static int cref=0;
static clock_t reftime;
static int chunks_seen=0,chunks_drawn=0;
#endif
static Pixmap empty;
#if GRABMODE
static int grabbed=1;
#endif
static Window in_which=None;
static XVisualInfo vi;
static int tc_cols[16];


/* new stuff */
#define STE_COLORS 4096
static int mono_mode;
static int bits_per_pixel;
static int indexed_color;
static unsigned long pixel_value[STE_COLORS];
static unsigned long pixel_value_by_index[16];
static unsigned char pixel_index[16];
static int is_allocated[STE_COLORS];
SCRDEF scr_def[3];

/* some interesting events: */
#define E_MASK (KeyPressMask|KeyReleaseMask|ButtonPressMask	\
				|ButtonReleaseMask|PointerMotionMask|ColormapChangeMask|\
				EnterWindowMask|LeaveWindowMask|ExposureMask)

static int old_shiftmod=-1;	/* last known shift mode, used to detect changes */
static UW old_color[16];	/* last known color palette */
static int shm_mono=1;
static char **ksyms;
static int redraw_flag=0;
static char win_name[64];
static Visual *visual = NULL;
static Status status;
static unsigned long wp, bp;		/* pixel values for black & white */
static int screen, nplanes, ncolors;
static int Max_x, Max_y;
static XEvent event;
static XSetWindowAttributes attrib;
static GC gc;
static XGCValues gc_val;
static XSizeHints sizehints;
Window imagewin;		/* window ID for image */
static XImage *image = NULL;
static Colormap hlcmap;
static char keybuf[64];
static int refcount=0;
static int lastmx=0,lastmy=0;
static unsigned char *buf;
static int shm_ct;

Display *display;
int kmin,kmax;
int shmflag = 1;
Colormap cmap=0;

#define BUF(a,b) buf[(viewy * (b)) + (a)]
#undef SHM_WAIT

static int get_bpp(void)
{
	int count, i;
	XPixmapFormatValues *x;
    x=XListPixmapFormats(display,&count);
    for (i=0; i<count; i++)
        if (x[i].depth == DefaultDepth(display,screen))
		 return x[i].bits_per_pixel;
    return 0;
}

/* Destroy the current emulator window
 */
static void destroy_image (void)
{
	if (image == NULL) return;
#if 0
	dprintf("destroying XImage\n");
#endif
	XDestroyImage (image);
#ifdef SH_MEM
#if 0
	/* XDestroyImage() seems to take care of that; the manpage says that it
	 * only deallocates the memory associated with the XImage structure
	 */
	if (buf != NULL && buf != (unsigned char *)shminfo.shmaddr)
	{	
		free(buf);
	}
#endif
    if (shmflag && shm_used)
    {
#if 0
		dprintf("Cleaning up the shared memory mess\n");
#endif
		XShmDetach (display, &shminfo);
		shmdt (shminfo.shmaddr);
		shmctl (shminfo.shmid, IPC_RMID, 0);
		shm_used = 0;
    }
#endif
}

void x_screen_close (void)
{
	XAutoRepeatOn(display);
	XFlush(display);
	destroy_image();
}

void set_window_name (Window id, char *name)
{
	XStoreName (display, id, name);
}

/* Create a new image for the emulation window, using shared memoy if possible
 */
static void create_image (int w, int h, int format)
{
	int planes = (format == XYBitmap ? 1 : DefaultDepth(display,screen));
	Max_x = w-1;
	Max_y = h-1;
#ifdef SH_MEM
    if (shmflag && (planes != 1 || shm_mono))
    {
#if 0
		dprintf("Creating a shared memory XImage\n");
#endif
		image = XShmCreateImage (display, DefaultVisual(display,screen),
								DisplayPlanes(display,screen), format,
				 				NULL, &shminfo, w, h);
		if (image == NULL)
		{
			fprintf(stderr,"Error allocating a SHM-Image\n");
			XSync(display,False);
			exit(6);
		}
		shminfo.shmid = shmget (IPC_PRIVATE, image->bytes_per_line
								* image->height, IPC_CREAT | 0777);
		if (shminfo.shmid < 0)
		{
			fprintf(stderr,
			"Unable to allocate shared memory segment of size %ld bytes.\n"
			"Please increase the max. size of a segment (now %ld).\n",
			(long)image->bytes_per_line * image->height,(long) SHMMAX);
		}
		shminfo.shmaddr = image->data = shmat (shminfo.shmid, 0, 0);
		if (indexed_color)
		{
			buf = (unsigned char *)shminfo.shmaddr;
		}
		else
		{
#if 0
			dprintf("Allocating separate buffer for conversion\n");
#endif
			buf = (unsigned char *)malloc(image->height * image->width);
#if 0
			dprintf("buf=%lx, shm=%lx\n",
				(long)buf,(long)shminfo.shmaddr);
#endif
		}

		shminfo.readOnly = False;
		XShmAttach (display, &shminfo);
		XSync(display, False);
		shmctl(shminfo.shmid, IPC_RMID, 0);
		shm_used = 1;
#if 0
		bzero (&shminfo, sizeof(shminfo));
#endif
    }
    else
#endif
	{
		unsigned char *bb;
#if 0
		dprintf("Creating a plain XImage\n");
#endif
		/* 8 because that's what the conversion routines return at the moment*/
    	buf = (unsigned char *)malloc (w * h / (format == XYBitmap ? 8 : 1));

		/* we can write to the image directly in these cases: */
		if (indexed_color || format == XYBitmap) bb=buf;
		else
		{
			/* need a buffer since display != 8bpp */
			bb=(unsigned char *)malloc(w*h*BitmapUnit(display)/8);
#if 0
			dprintf("Allocating separate buffer for conversion (size=%ld)\n",(long)w*h*BitmapUnit(display)/8);
			dprintf("buf=%lx, shm=%lx\n",(long)buf,(long)bb);
#endif
		}
    	image = XCreateImage (display, DefaultVisual(display,screen),
								format == XYBitmap ?
								1 : DisplayPlanes(display,screen), format,
								0, (char *)bb, w, h, 16, 0);
#if 0
		dprintf("Image->data=%lx, bb=%lx, buf=%lx\n",
			(long)image->data,(long)bb,(long)buf);
#endif
		if (image == NULL)
		{
			fprintf(stderr,"Error allocating an XImage\n");
			XSync(display,False);
			exit(6);
		}
#ifdef SH_MEM
		shm_used=0;
#endif
	}
	if (planes == 1)
	{
		image -> bitmap_bit_order = MSBFirst;
		image -> byte_order = MSBFirst;
	}
	XSync(display,False);
}

static void allocate_color(UW stcolor)
{
	if (!is_allocated[stcolor])
	{
		XColor col;
		int c=stcolor;
		col.flags = DoRed | DoGreen | DoBlue;
		col.red 	= (((c>>8)&7)<<13)|((c>>11)<<12);
		col.green	= (((c>>4)&7)<<13)|(((c>>7)&1)<<12);
		col.blue	= ((c&7)<<13)|(((c>>3)&1)<<12);
		if (!XAllocColor(display,cmap,&col))
		{
			fprintf(stderr,
				"Color allocation failed, exiting. This should"
				" never happen, please notify the author!\n");
			exit(4);
		}
		pixel_value[c]=col.pixel;
		is_allocated[c]=1;
#if 0
		dprintf("Allocating %04x -> %08lx\n",stcolor,col.pixel);
#endif
	}
}

/* Change the window colormap according to the contents of old_color[].
 * The colors are converted from ST/STe format (least-significant 9/12 bits
 * of a 16 bit integer) to the 16 bit per RGB component format used in X11.
 */
static int allocatedcols[COLS];
char mapcol[COLS];
static void change_colors (void)
{
	XColor colors[COLS];
	UW c;
	int i;
	int q=0;
	unsigned long pixels[COLS];
	int j,k;
	if (shiftmod == 2)
	{
		for (i=0; i<COLS; i++)
		{
			colors[i].pixel = i;
			colors[i].flags = DoRed | DoGreen | DoBlue;
			colors[i].red = colors[i].green = colors[i].blue = 0;
#if 0
			dprintf("[%d] %03x\n",i,old_color[i]);
#endif
		}
		if (old_color[0] == 0) {
			/* mono show_screen does just memcpy; XPutImage... */
			colors[1].red = colors[1].green = colors[1].blue = 0xffff;
			XSetForeground (display, gc, wp);
			XSetBackground (display, gc, bp);
		} else {
#if 0
			dprintf("Colors are black on white background\n");
#endif
			colors[0].red = colors[0].green = colors[0].blue = 0xffff;
			XSetForeground (display, gc, bp);
			XSetBackground (display, gc, wp);
		}
	}
	else
	{
		if (indexed_color)
		{
			for (i=0; i<COLS; i++)
			{
				c = (i < (shiftmod == 0 ? 16 : 4) ? old_color[i] : 0);
#if !STE
				c &= 0x777;
#endif
				colors[i].pixel = i;
				colors[i].flags = DoRed | DoGreen | DoBlue;
				colors[i].red 	= (((c>>8)&7)<<13)|((c>>11)<<12);
				colors[i].green	= (((c>>4)&7)<<13)|(((c>>7)&1)<<12);
				colors[i].blue	= ((c&7)<<13)|(((c>>3)&1)<<12);
			}
		}
		else
		{	
			/* saves one indirection in the conversion routines... */
			for (i=0; i<COLS; i++)
			{
				if (!is_allocated[old_color[i]])
				{
					XColor col;
					int c=old_color[i];
					col.flags = DoRed | DoGreen | DoBlue;
					col.red 	= (((c>>8)&7)<<13)|((c>>11)<<12);
					col.green	= (((c>>4)&7)<<13)|(((c>>7)&1)<<12);
					col.blue	= ((c&7)<<13)|(((c>>3)&1)<<12);
					if (!XAllocColor(display,cmap,&col))
					{
						fprintf(stderr,
							"Color allocation failed, exiting. This should"
							" never happen, please notify the author!\n");
						exit(4);
					}
#if 0
					dprintf("Allocating %3x to pixel value %06lx\n",
						c,col.pixel);
#endif
					pixel_value[c]=col.pixel;
					is_allocated[c]=1;
				}
				pixel_value_by_index[i]=pixel_value[old_color[i]];
			}
		}
	}

	if (indexed_color)
	{
		if (!priv_cmap)
		{
			for (i=0; i<COLS; i++)
			{
				if (allocatedcols[i]>=0)
				{
					pixels[q++]=allocatedcols[i];
				}
			}
			if (q>0) XFreeColors(display, cmap, pixels, q, 0);
			for (i=0; i<COLS; i++)
			{
				XAllocColor (display, cmap, &colors[i]);
				mapcol[i]=colors[i].pixel;
			}
		}
		else
		{
			XStoreColors (display, cmap, colors, COLS);
		}
	}
	else
	{
		for (i=0; i<scr_height/CHUNK_LINES; i++)
			draw_chunk[i]|=1;
	}

#if 0
	XMapRaised (display, imagewin);
	XFlush(display);
#endif
#if 0
	XUnmapWindow (display, imagewin);
	XMapWindow (display, imagewin);
#endif
}

/* Do something appropriate when the 'shiftmod' hardware register has changed:
 * 1. The window is resized
 * 2. A new image is created
 * 3. A new conversion function is used every VBL
 */
static void change_mode (void)
{
	scr_width = scr_def[shiftmod].w;
	scr_height = scr_def[shiftmod].h;
	scr_planes=4>>shiftmod;
#if 0
	dprintf("Screen is now %dx%d, shift mode %d\n",scr_width,scr_height,shiftmod);
#endif
	switch (shiftmod)
	{
		case 0:
			XResizeWindow (display, imagewin, scr_width, scr_height);
			destroy_image();
			create_image (scr_width, scr_height, ZPixmap);
			change_colors();
			break;

		case 1:
			XResizeWindow (display, imagewin, scr_width, scr_height*2);
			destroy_image();
			create_image (scr_width, scr_height*2, ZPixmap);
			change_colors();
			break;

		case 2:
			XResizeWindow (display, imagewin, scr_width, scr_height);
			destroy_image();
			create_image (scr_width, scr_height, XYBitmap);
			change_colors();
			break;
	}
}

/* Process all events that occured since we last came here...
 */
void x_process_events (void)
{
	int tx,ty;
	XEvent e;
	XExposeEvent *x;
	while (XCheckMaskEvent (display, E_MASK, &e))
	{
		switch (e.type)
		{
		case EnterNotify:
#if 0
			dprintf("Entering STonX window\n");
#endif
			XAutoRepeatOff(display);
#if XLIB_VDI
			if (vdi && xw != None) vdi_mode = (e.xcrossing.window != imagewin);
#endif
			in_which = e.xcrossing.window;

			if (in_which == imagewin)
			{
#if SET_MOUSE
				if (abase != 0)
				{
					lastmx = LM_W(MEM(abase-GCURX));
					lastmy = LM_W(MEM(abase-GCURY));
				}
				if (lastmx >= 0 && lastmx < scr_width
					&& lastmy >= 0 && lastmy < scr_height)
				{
					XWarpPointer (display, None, imagewin,
								0, 0, 0, 0, 
								lastmx, lastmy);
					XDefineCursor (display, imagewin, cursors[cursidx]);
					warped=1;
				}
#else
				int mx,my,nx,ny;
#if 0
				mx=LM_W(MEM(abase-GCURX));
				my=LM_W(MEM(abase-GCURY));
#endif
				nx=(((XCrossingEvent *)&e)->x);
				ny=(((XCrossingEvent *)&e)->y);
#if 0
				dprintf("Repositioning mouse from %d,%d to %d,%d\n",
					mx,my,nx,ny);
				ikbd_adjust(nx-lastmx,ny-lastmy);
				lastmx=mx;
				lastmy=my;
#endif
				ikbd_pointer(nx,ny,Max_x,Max_y);
#endif
			}
			break;
		case LeaveNotify:
#if 0
			dprintf("Leaving STonX window\n");
#endif
			XAutoRepeatOn(display); /* BUG: should restore previous value */
			if (e.xcrossing.window == imagewin) XUndefineCursor(display,imagewin);
			in_which = None;
			break;
		case KeyPress:
			/* fprintf (stderr, "Keypress: %d\n", ((XKeyEvent *)&e)->keycode);*/
		    if (XLookupKeysym ((XKeyEvent *)&e,0) == XK_Pause)
				flags |= F_CONFIG;
			else ikbd_key (((XKeyEvent *)&e)->keycode, 1);
			break;
		case KeyRelease:
		    if (XLookupKeysym ((XKeyEvent *)&e,0) != XK_Pause)
				ikbd_key (((XKeyEvent *)&e)->keycode, 0);
			break;
		case ButtonPress:
			if (((XButtonEvent *)&e)->button == 3
				&& (((XButtonEvent *)&e)->state & ControlMask))
				exit(0);
#if GRABMODE
			if (((XButtonEvent *)&e)->button == 3)
			{
#if 0
				dprintf( "in window %lx\n",(long)in_which);
#endif
				if (grabbed)
				{
					XUngrabKeyboard (display, CurrentTime);
					XUngrabPointer (display, CurrentTime);
					grabbed=0;
				}
				else
#if XLIB_VDI
				if (in_which != xw)
#endif
				{
					XGrabPointer (display, imagewin, True, PointerMotionMask
					|ButtonPressMask|ButtonReleaseMask,
					GrabModeAsync, GrabModeAsync, None, hidden, CurrentTime);
					XGrabKeyboard (display, imagewin,True, GrabModeAsync,
						GrabModeAsync, CurrentTime);
					grabbed=1;
					fprintf (stderr, "grabbed\n");
				}
			}
#elif HIDEMODE
			if (((XButtonEvent *)&e)->button == 3)
			{
				cursidx = !cursidx;
				XDefineCursor (display, imagewin, cursors[cursidx]);
				break;
			}
#endif
			ikbd_button (((XButtonEvent *)&e)->button, 1);
			break;
		case ButtonRelease:
#if 0
		fprintf (stderr, "Buttonrelease: %d\n", ((XButtonEvent *)&e)->button);
#endif
			ikbd_button (((XButtonEvent *)&e)->button, 0);
			break;
		case Expose:
			x = (XExposeEvent *)&e;
#if XBUFFER
#if XLIB_VDI
			if (xw == x->window)
				vdi_redraw(x->x,x->y,x->width,x->height);
#endif
#endif
			{
				int i;
				for (i=0; i<scr_height/CHUNK_LINES; i++)
					draw_chunk[i]|=1;
			}
			break;
		case MotionNotify:
			if (e.xmotion.window == imagewin && warped)
			{
				warped=0;
#if 0
				lastmx = ((XMotionEvent *)&e)->x;
				lastmy = ((XMotionEvent *)&e)->y;
				break;
#endif
			}
#if 0

#if 0	
			if (e.xmotion.window == imagewin)
#endif
/*	fprintf (stderr, "Pointer: %d,%d\n", ((XMotionEvent *)&e)->x,((XMotionEvent *)&e)->y); */
#if 0
			SM_W(MEM(abase-GCURX),((XMotionEvent *)&e)->x);
			SM_W(MEM(abase-GCURY),((XMotionEvent *)&e)->y);
			SM_W(MEM(abase-0x158),((XMotionEvent *)&e)->x);
			SM_W(MEM(abase-0x156),((XMotionEvent *)&e)->y);
			SM_B(MEM(abase-0x154),0xff);
#endif
#if 0
#if 1
			if (abase != 0)
			{
				tx=LM_W(MEM(abase-0x158));
				ty=LM_W(MEM(abase-0x156));
			}
#if 1
			if (tx==lastmx && ty==lastmy) break;
#endif
			lastmx=tx;
			lastmy=ty;
#endif
			dprintf("Adjusting: (%d,%d)->(%d,%d)\n",lastmx,lastmy,
			((XMotionEvent *)&e)->x,((XMotionEvent *)&e)->y);
			ikbd_adjust (((XMotionEvent *)&e)->x-lastmx,
						((XMotionEvent *)&e)->y-lastmy);
#else
			ikbd_adjust (((XMotionEvent *)&e)->x-lastmx,
						((XMotionEvent *)&e)->y-lastmy);
			lastmx = ((XMotionEvent *)&e)->x;
			lastmy = ((XMotionEvent *)&e)->y;
#endif

#else
#if 0
			ikbd_pointer (((XMotionEvent *)&e)->x,((XMotionEvent *)&e)->y,Max_x,Max_y);
#else
			ikbd_adjust (((XMotionEvent *)&e)->x-lastmx,
                        ((XMotionEvent *)&e)->y-lastmy);
            lastmx = ((XMotionEvent *)&e)->x;
            lastmy = ((XMotionEvent *)&e)->y;
#endif
#endif
			break;
		}
	}
}

void x_screen_open (void)
{
    int i, j;
    int shm_major, shm_minor;
    Bool shm_pixmaps;
    XSizeHints h;
    XFontStruct *font_info;
    XImage *t;
	XColor ccols[2];
	XSetWindowAttributes xa;

	scr_width = scr_def[shiftmod].w;
	scr_height = scr_def[shiftmod].h;

    strcpy (win_name, "STonX Main Window");

    if ((display = XOpenDisplay (NULL)) == NULL)
    {
		fprintf (stderr,"Could not open display.\n");
		exit (1);
    }

#if 0
	XAutoRepeatOff(display);
#endif
	fprintf (stderr,"Obtaining Keysym mappings for display...\n");
	XDisplayKeycodes (display, &kmin, &kmax);
	fprintf (stderr,"Keycode range = %d..%d\n", kmin, kmax);
	ksyms = (char **)malloc(sizeof(char *)*(kmax-kmin+1));
	for (i=kmin; i<=kmax; i++)
	{
		char *s = XKeysymToString(XKeycodeToKeysym(display,i,0));
		ksyms[i-kmin] = (s == NULL ? s : strdup(s));
#if 0
		fprintf (stderr, "Keycode %d = %s\n", i,s);
#endif
	}

#ifdef SH_MEM
    if (shmflag)
    {
		if (!XShmQueryVersion (display, &shm_major, &shm_minor, &shm_pixmaps))
		{
	    	fprintf(stderr,
					"MIT Shared Memory Extension not supported.\n");
	    	shmflag = 0;
		}
		else
		{
			int foo;
	  		fprintf (stderr,"Using MIT Shared Memory Extension %d.%d," \
		  	   " %s shared pixmaps.\n", shm_major, shm_minor,
		   	  (shm_pixmaps ? "with" : "without"));
			foo = XShmPixmapFormat (display);
			fprintf (stderr,"XShm Pixmap format: %s\n", ((foo == XYBitmap ? 
				"XYBitmap" : (foo == XYPixmap ? "XYPixmap" : "ZPixmap"))));
			fprintf (stderr,
			"If you get a BadAccess error, try the -noshm option!\n");
		}
    }
#endif
	
    screen = XDefaultScreen (display);
    depth = DefaultDepth (display, screen);
	if (depth == 1)
	{
		SM_B(MEM(0xff8260),2);
/*		old_shiftmod = 2; */
	}
    visual = DefaultVisual (display, screen);
    nplanes = XDisplayPlanes (display, screen);

	/* See if we are on a local little-endian display - in that case,
	   XShmPutImage will refuse to swap bits most of the time! */
	if (shmflag)
	{
		char *c=(char *)malloc(64);
		t = XCreateImage (display, visual, 1, XYBitmap, 0, c, 32, 16, 8, 0);
		if (t->bitmap_bit_order == LSBFirst)
		{
#if 0
			fprintf (stderr, "NOTE: Because XShmPutImage apparently can't swap bits, shared memory support\nwas turned off for monochrome mode!\n");
#endif
			shm_mono=0;
		}
		XDestroyImage (t);
	}

    h.width = scr_width;
    h.height = scr_height;
    h.x = 100;
    h.y = 0;

    h.flags = PSize | PPosition;
    bp = BlackPixel (display, screen);
    wp = WhitePixel (display, screen);
#if 0
	dprintf("Black=%ld, White=%ld\n",bp,wp);
#endif

	if (XMatchVisualInfo(display,screen,8,PseudoColor, &vi))
	{
		fprintf(stderr,"Using 8 bpp PseudoColor visual\n");
		indexed_color = 1;
		bits_per_pixel = 8;
	}
	else
	{
		indexed_color = 0;
		bits_per_pixel = get_bpp();
		priv_cmap = 1; /* temp kludge for screen XXX */
	}
	fprintf(stderr,
		"Indexed=%d, bits per pixel=%d\n",indexed_color,bits_per_pixel);

#if 0
	dprintf("Trying to create a (%d,%d,%d,%d) window\n",
		h.x,h.y,h.width,h.height);
#endif
    imagewin = XCreateWindow (display, RootWindow (display, screen),
			h.x, h.y, h.width, h.height, 0, DefaultDepth(display,screen),
				InputOutput, visual,
				0, &xa);
    XSelectInput (display, imagewin, StructureNotifyMask|E_MASK);
    XSetStandardProperties (display, imagewin, win_name, win_name, None,
			    NULL, 0, &h);
    XMapWindow (display, imagewin);

    for (;;)
    {
		XEvent e;
		XNextEvent (display, &e);
		if (e.type == MapNotify && e.xmap.event == imagewin) break;
    }
    gc_val.background = 0;
    gc_val.foreground = 1;
    gc = XCreateGC (display, imagewin, GCForeground | GCBackground, &gc_val);
#ifdef SH_MEM
    shm_ct = XShmGetEventBase (display) + ShmCompletion;
#endif
	if (indexed_color)
	{
		if (priv_cmap)
			cmap = XCreateColormap (display, RootWindow(display,screen),
									visual, AllocAll);
		else
		{
			cmap = DefaultColormap (display, screen);
			for (i=0; i<COLS; i++) allocatedcols[i]=-1;
		}
		XSetWindowColormap (display, imagewin, cmap);
	}
	else
	{
		int colors=512;
#if 1
		int pr=0,pr0=4096/10;
		fprintf(stderr,"Allocating %d colors",colors);
#if 1
		cmap = XCreateColormap (display, RootWindow(display,screen),
								visual, AllocNone);
#else
		cmap = DefaultColormap (display, screen);
#endif
		for (i=0; i<4096; i++)
		{
			XColor col;
			if ((i & 0x777) == i)
			{
				col.flags = DoRed | DoGreen | DoBlue;
				col.red 	= (((i>>8)&7)<<13)|((i>>11)<<12);
				col.green	= (((i>>4)&7)<<13)|(((i>>7)&1)<<12);
				col.blue	= ((i&7)<<13)|(((i>>3)&1)<<12);
				if (!XAllocColor(display,cmap,&col))
				{
					fprintf(stderr,"Color allocation failed, exiting.\n");
					exit(4);
				}
				pixel_value[i]=col.pixel;
			}
			else pixel_value[i]=pixel_value[i&0x777];

			if (++pr == pr0)
			{
				putc('.',stderr);
				pr=0;
			}
		}
		fprintf(stderr,"\n");
#else
		cmap = DefaultColormap (display, screen);
#endif
	}
    XClearWindow (display, imagewin);
#if 0
	XWarpPointer (display, None, imagewin, 0, 0, 0, 0, 320, 200);
#endif
    XSelectInput (display, imagewin, E_MASK);
#if !MONITOR && GRAB
	XGrabPointer (display, imagewin, False, ButtonPressMask|ButtonReleaseMask
					|PointerMotionMask, GrabModeAsync,
					GrabModeAsync, None, None, CurrentTime);
#endif
	if (depth == 1) create_image (scr_width,scr_height,XYBitmap);
	empty = XCreatePixmapFromBitmapData (display, imagewin, (char*)calloc(32,1),
			16, 16,0,0,1);
	ccols[0].pixel = 0;
	ccols[1].pixel = 0;
	cursors[0] = XCreatePixmapCursor (display, empty, empty, &ccols[0], &ccols[1],
				0,0);
	cursors[1] = XCreateFontCursor (display, XC_tcross);
#if GRABMODE
	XGrabPointer (display, imagewin, True, PointerMotionMask
	|ButtonPressMask|ButtonReleaseMask,
	GrabModeAsync, GrabModeAsync, None, hidden, CurrentTime);
	XGrabKeyboard (display, imagewin,True, GrabModeAsync,
		GrabModeAsync, CurrentTime);
	fprintf (stderr, "The X pointer and keyboard have been grabbed by STonX - press the 3rd mouse\nbutton to release/grab again.\n");
#endif
	OnExit (x_screen_close);
}

/*
 * What about unaligned buffers here?
 */
static void stcolor_to_image (UW *b, unsigned char *p,
							int y0, int nlines)
{
	int n,i,cv;

	b += image->width * y0 * 2;
	p += (image->width * y0 * bits_per_pixel)/8;	/* XXX for <8 bpp... */
	n = nlines * image->width;						/* pixels to convert */

	switch(bits_per_pixel)
	{
		case 32:
			for (i=0; i<n; i++)
			{
				UW col;
				col=b[i];
#if 0
				if (!is_allocated[col])
					allocate_color(col);
#endif
				*(int *)p=pixel_value[col];
				p+=4;
			}
			break;
		case 24:	/* UNTESTED */
			for (i=0; i<n; i++)
			{
#if 0
				allocate_color(b[i]);
#endif
				cv=pixel_value[b[i]];
				*p++ = cv>>16;
				*p++ = (cv>>8)&0xff;
				*p++ = cv&0xff;
			}
			break;
		case 16: 	/* UNTESTED */
			for (i=0; i<n; i++)
			{
#if 0
				allocate_color(b[i]);
#endif
				*(UW *)p=pixel_value[b[i]];
				p+=2;
			}
			break;
		case 8:		/* UNTESTED */
			for (i=0; i<n; i++)
			{
#if 0
				allocate_color(b[i]);
#endif
				*p++ = pixel_value[b[i]];
			}
			break;
		default:
			fprintf(stderr,"Weird screen format, exiting.\n");
			exit(5);
	}
}

static void buffer_to_image (unsigned char *b, unsigned char *p,
							int y0, int nlines)
{
	int n,i,cv;

	if (indexed_color || shiftmod == 2) return;

	b += image->width * y0;
	p += (image->width * y0 * bits_per_pixel)/8;	/* XXX for <8 bpp... */
	n = nlines * image->width;						/* pixels to convert */

	switch(bits_per_pixel)
	{
		case 32:
			for (i=0; i<n; i++)
			{
				*(int *)p=pixel_value_by_index[b[i]];
				p+=4;
			}
			break;
		case 24:	/* UNTESTED */
			for (i=0; i<n; i++)
			{
				cv=pixel_value_by_index[b[i]];
				*p++ = cv>>16;
				*p++ = (cv>>8)&0xff;
				*p++ = cv&0xff;
			}
			break;
		case 16: 	/* UNTESTED */
			for (i=0; i<n; i++)
			{
				*(UW *)p=pixel_value_by_index[b[i]];
				p+=2;
			}
			break;
		case 8:		/* UNTESTED */
			for (i=0; i<n; i++)
				*p++ = pixel_value_by_index[b[i]];
			break;
		default:
			fprintf(stderr,"Weird screen format, exiting.\n");
			exit(5);
	}
}

/* Update the X window using the shifter mode known when change_mode was last
 * called...
 */
static void show_screen (void)
{
	int w, h;
	int xzoom,yzoom=1;
	int i,n;
	UB *p;
	long cv;

	w=scr_width;
	h=scr_height;
	switch (old_shiftmod)
	{
		case 0:
			st16c_to_z ((unsigned char*)MEM(vbase), buf);
			break;
		case 1:
			yzoom=2;
			st4c_to_z ((unsigned char *)MEM(vbase), buf); 
			break;
		case 2:
			stmono_to_xy(buf, (unsigned char *)MEM(vbase));
			break;
	}
#ifdef SH_MEM
    if (shm_used) /* shmflag && (old_shiftmod != 2 || shm_mono)) */
   	{
		XEvent xev;
		if (chunky)
		{
			int i;
			for (i=0; i<h/CHUNK_LINES; i++)
				if (draw_chunk[i])
				{	
					buffer_to_image(buf,(unsigned char *)image->data,
									yzoom*i*CHUNK_LINES,
									yzoom*CHUNK_LINES);
					XShmPutImage (display, imagewin, gc, image,
						0, yzoom*i*CHUNK_LINES,
						0, yzoom*i*CHUNK_LINES, w,
						yzoom*CHUNK_LINES,
						False);
					draw_chunk[i]=0;
#if BENCH_REFRESH
					chunks_drawn++;
#endif
				}
#if BENCH_REFRESH
			chunks_seen+=h/CHUNK_LINES;
#endif
		}
		else
		{
			buffer_to_image(buf,(unsigned char *)image->data,0,h*yzoom);
			XShmPutImage (display, imagewin, gc, image, 0, 0, 0, 0, w, h*yzoom,
					False);
		}
#if 1
		XSync (display,False);
#endif
#ifdef SHM_WAIT
		do
		{
		    XNextEvent (display, &xev);
		}
		while (xev.type != shm_ct);
#endif
   	}
    else
#endif
	{
		if (chunky)
		{
			int i;
			for (i=0; i<h/CHUNK_LINES; i++)
				if (draw_chunk[i])
				{
					buffer_to_image(buf,(unsigned char *)image->data,0,h*yzoom);
					XPutImage (display, imagewin, gc, image,
						0, yzoom*i*CHUNK_LINES,
						0, yzoom*i*CHUNK_LINES, w,
						yzoom*CHUNK_LINES);
					draw_chunk[i]=0;
#if BENCH_REFRESH
					chunks_drawn++;
#endif
				}
#if BENCH_REFRESH
			chunks_seen+=h/CHUNK_LINES;
#endif
		}
		else
		{
			buffer_to_image(buf,(unsigned char *)image->data,0,h*yzoom);
			XPutImage (display, imagewin, gc, image, 0, 0, 0, 0, w, h*yzoom);
		}
		XFlush (display);
	}
#if 1
    XSync (display, False);
#endif
}

static void manage_window (void)
{
    XKeyEvent *keypress;
    KeySym keysym;
    XComposeStatus cstat;

    keypress = (XKeyEvent *) & event;

	if (XPending (display))
	{
	    XNextEvent (display, &event);
	    switch ((int) event.type)
	    {
		case KeyPress:
		    if (XLookupString (keypress, keybuf, sizeof (keybuf),
				&keysym, &cstat) != 0)
			{
		/*		update_IKBD_BUFFER (); */
			}
			break;
		default:
		    break;
	    }
	}
}

/* This function is called periodically (from a signal handler) to check what's
 * new with the shifter mode, colors, and update ("shift") the screen. It
 * should probably rewritten to check whether it's necessary to update the
 * screen, since for color modes this is a very costly operation...
 *
 * The Shifter status information is changed in hw.c, where the hardware regs
 * are read.
 */
void x_screen_shifter (void)
{
	int i, w, pal = 0;

	x_process_events();

	if (++refcount < refresh) return;
	refcount=0;

	for (i=0; i<16; i++)
	{
		if ((w = LM_UW(&color[i])) != old_color[i])
		{
			pal = 1;	/* palette has changed */
			old_color[i] = w & 0xfff;
		}
	}

	if (pal) change_colors();	/* update the window colormap */

/*	if (depth > 1 && old_shiftmod != shiftmod)*/
	if (old_shiftmod != shiftmod)
	{
		old_shiftmod = shiftmod;
		change_mode();
	}

#if XLIB_VDI
	if (!vdi_mode || xw == None)
#endif
	{
#if BENCH_REFRESH
		struct tms ta;
		clock_t rt;
		times(&ta);
		rt=ta.tms_utime+ta.tms_stime;
		show_screen();
		times(&ta);
		reftime += ta.tms_utime+ta.tms_stime-rt;
		cref++;
		if (cref==NUM_AVG)
		{
			fprintf(stderr, "Refresh time (%d samples): %lf ms\n", NUM_AVG,
				1000.0*(double)reftime/(NUM_AVG*CLK_TCK));
			if (chunky) dprintf("Chunks drawn/seen = %d/%d\n",chunks_drawn,chunks_seen);
			reftime=0;
			cref=0;
		}
#else
		show_screen();
#endif
	}
#if 0
	check_ui();
#endif
}

#define KB_DEBUG 0
#define KEYMAP_HARDCODED 0

struct
{
	int c;
	char *s;
} st_keysyms[] =
{
	{ST_1, "ST_1"},
	{ST_2, "ST_2"},
	{ST_3, "ST_3"},
	{ST_4, "ST_4"},
	{ST_5, "ST_5"},
	{ST_6, "ST_6"},
	{ST_7, "ST_7"},
	{ST_8, "ST_8"},
	{ST_9, "ST_9"},
	{ST_0, "ST_0"},
	{ST_F1, "ST_F1"},
	{ST_F2, "ST_F2"},
	{ST_F3, "ST_F3"},
	{ST_F4, "ST_F4"},
	{ST_F5, "ST_F5"},
	{ST_F6, "ST_F6"},
	{ST_F7, "ST_F7"},
	{ST_F8, "ST_F8"},
	{ST_F9, "ST_F9"},
	{ST_F10, "ST_F10"},
	{ST_A, "ST_A"},
	{ST_B, "ST_B"},
	{ST_C, "ST_C"},
	{ST_D, "ST_D"},
	{ST_E, "ST_E"},
	{ST_F, "ST_F"},
	{ST_G, "ST_G"},
	{ST_H, "ST_H"},
	{ST_I, "ST_I"},
	{ST_J, "ST_J"},
	{ST_K, "ST_K"},
	{ST_L, "ST_L"},
	{ST_M, "ST_M"},
	{ST_N, "ST_N"},
	{ST_O, "ST_O"},
	{ST_P, "ST_P"},
	{ST_Q, "ST_Q"},
	{ST_R, "ST_R"},
	{ST_S, "ST_S"},
	{ST_T, "ST_T"},
	{ST_U, "ST_U"},
	{ST_V, "ST_V"},
	{ST_W, "ST_W"},
	{ST_X, "ST_X"},
	{ST_Y, "ST_Y"},
	{ST_Z, "ST_Z"},
	{ST_SPACE, "ST_SPACE"},
	{ST_ALT, "ST_ALT"},
	{ST_ESC, "ST_ESC"},
	{ST_MINUS, "ST_MINUS"},
	{ST_EQUAL, "ST_EQUAL"},
	{ST_GRAVE, "ST_GRAVE"},
	{ST_BS, "ST_BS"},
	{ST_DELETE, "ST_DELETE"},
	{ST_ISO, "ST_ISO"},
	{ST_INSERT, "ST_INSERT"},
	{ST_TAB, "ST_TAB"},
	{ST_SQ_OPEN, "ST_SQ_OPEN"},
	{ST_SQ_CLOSE, "ST_SQ_CLOSE"},
	{ST_UP, "ST_UP"},
	{ST_LEFT, "ST_LEFT"},
	{ST_RIGHT, "ST_RIGHT"},
	{ST_DOWN, "ST_DOWN"},
	{ST_CONTROL, "ST_CONTROL"},
	{ST_LSH, "ST_LSH"},
	{ST_RSH, "ST_RSH"},
	{ST_CAPSLOCK, "ST_CAPSLOCK"},
	{ST_SEMIC, "ST_SEMIC"},
	{ST_APOST, "ST_APOST"},
	{ST_BACKSL, "ST_BACKSL"},
	{ST_COMMA, "ST_COMMA"},
	{ST_DOT, "ST_DOT"},
	{ST_SLASH, "ST_SLASH"},
	{ST_RETURN, "ST_RETURN"},
	{ST_KP_ENTER, "ST_KP_ENTER"},
	{ST_KP_MINUS, "ST_KP_MINUS"},
	{ST_KP_PLUS, "ST_KP_PLUS"},
	{ST_HELP, "ST_HELP"},
	{ST_UNDO, "ST_UNDO"},
	{ST_KP_OPEN, "ST_KP_OPEN"},
	{ST_KP_CLOSE, "ST_KP_CLOSE"},
	{ST_KP_DIV, "ST_KP_DIV"},
	{ST_KP_MULT, "ST_KP_MULT"},
	{ST_KP_DOT, "ST_KP_DOT"},
	{ST_KP_0, "ST_KP_0"},
	{ST_KP_1, "ST_KP_1"},
	{ST_KP_2, "ST_KP_2"},
	{ST_KP_3, "ST_KP_3"},
	{ST_KP_4, "ST_KP_4"},
	{ST_KP_5, "ST_KP_5"},
	{ST_KP_6, "ST_KP_6"},
	{ST_KP_7, "ST_KP_7"},
	{ST_KP_8, "ST_KP_8"},
	{ST_KP_9, "ST_KP_9"},
	{ST_HOME, "ST_HOME"},
};

#define NUM_STK (sizeof(st_keysyms)/sizeof(st_keysyms[0]))
static char mapped[NUM_STK];

extern char **ksyms;

/* This is the keycode translation table. It is dependent on your kind of X
 * terminal, and on the mapping you want to use for the ST keys.
 * To make your own table, you'll need to examine the `xmodmap -pke' output
 * on your system, and write the corresponding ST keycode as the item with
 * the index of the keycode you want it on in the array below.
 *
 * Maybe, sometime in the future, a translation table based on Keysyms would
 * be a better idea...
 */
#if 0
int keycodes[] =
{
#if KEYMAP_HARDCODED
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_F1, ST_F2, ST_F10, ST_F3, ST_UNDEF, ST_F4, ST_UNDEF, ST_F5, ST_UNDEF,
	ST_F6, ST_UNDEF, ST_F7, ST_F8, ST_F9, ST_ALT,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDO,
	ST_UNDEF,
	ST_UNDEF,
	ST_ESC,
	ST_1, ST_2, ST_3, ST_4, ST_5, ST_6, ST_7, ST_8, ST_9, ST_0, ST_MINUS,
	ST_EQUAL, ST_GRAVE, ST_BS,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_UNDEF,
	ST_DELETE,
	ST_UNDEF,
	ST_UNDEF,
	ST_TAB, ST_Q, ST_W, ST_E, ST_R, ST_T, ST_Y, ST_U, ST_I, ST_O, ST_P,
	ST_SQ_OPEN, ST_SQ_CLOSE, ST_DELETE, ST_UNDEF, ST_UNDEF, ST_UP,
	ST_UNDEF, ST_KP_MINUS,
	ST_UNDEF, ST_UNDEF, ST_UNDEF, ST_UNDEF, ST_CONTROL, ST_A, ST_S, ST_D, ST_F,
	ST_G, ST_H, ST_J, ST_K, ST_L, ST_SEMIC, ST_APOST, ST_BACKSL, ST_RETURN,
	ST_KP_ENTER, ST_LEFT, ST_UNDEF, ST_RIGHT, ST_INSERT, ST_UNDEF, ST_UNDEF,
	ST_UNDEF, ST_UNDEF, ST_LSH, ST_Z, ST_X, ST_C, ST_V, ST_B, ST_N, ST_M,
	ST_COMMA, ST_DOT, ST_SLASH, ST_RSH, ST_UNDEF, ST_UNDEF, ST_DOWN, ST_UNDEF,
	ST_UNDEF, ST_UNDEF, ST_UNDEF, ST_HELP, ST_CAPSLOCK, ST_UNDEF, ST_SPACE,
	ST_UNDEF, ST_UNDEF, ST_UNDEF, ST_KP_PLUS
#else
#include "keytab.c"
#endif
};
#endif

char *kdefsfile = "Keysyms";
void x_init_keys(void)
{
	int i, l=0, j;
	FILE *f;
	char b[1000], x[1000], y[1000];
	keycodes = (int *)malloc(sizeof(int)*(kmax+1));
	for (i=0; i<=kmax; i++)
		keycodes[i] = ST_UNDEF;
	fprintf (stderr,"Reading keycode mappings from `%s'...\n", kdefsfile);
	f = fopen(kdefsfile, "r");
	if (f == NULL)
	{
		fprintf(stderr,"FATAL error: File `%s' not found - exiting...\n",
			kdefsfile);
		exit(1);
	}
	while (fgets(b, 1000, f) != NULL)
	{
		l++;
		if (b[0] == '#') continue;
		if (sscanf(b, "%s %s", x, y) < 2)
		{
				fprintf (stderr,"Malformed line %d in file %s, ignoring it\n",
						l, kdefsfile);
		}
		else 
		{
			int q;
			int ok=0, u;
			for (j=kmin; j<=kmax; j++)
			{
#if KB_DEBUG
				if (*x == 'F')
				{
					dprintf ("x=<%s>, ksyms[%d]=<%s>, cmp=%d\n",
						x,j-kmin,ksyms[j-kmin],(ksyms[j-kmin]==NULL?999:strcasecmp(x,ksyms[j-kmin])));
				}
#endif
				if (ksyms[j-kmin] != NULL && strcasecmp (x, ksyms[j-kmin]) == 0)
				{
					q = /*XKeysymToKeycode(display,
						XStringToKeysym(ksyms[*/ j /* -kmin ]))*/; 
					for (u=0; u<sizeof(st_keysyms)/sizeof(st_keysyms[0]); u++)
					{
						if (strcasecmp (y, st_keysyms[u].s) == 0)
						{
							keycodes[q] = st_keysyms[u].c;
							mapped[u] = 1;
							break;
						}
					}
					if (u == sizeof(st_keysyms)/sizeof(st_keysyms[0]))
					{
						fprintf(stderr,
							"Error: Illegal ST-Keysym in %s, line %d `%s'\n",
							kdefsfile, l, y);
						exit(1);
					}
					ok=1;
				}
			}
			if (!ok)
			{
				fprintf (stderr,"Warning: unknown KeySym in %s, line %d `%s'\n",
								kdefsfile, l, x);
				continue;
			}
		}
	}
	for (i=0; i<NUM_STK; i++)
	{
		if (!mapped[i] && verbose)
			fprintf (stderr,"Warning: %s not mapped!\n", st_keysyms[i].s);
	}
	SM_UB(MEM(0xfffc00),0x0e);
}

