/*
 * Copyright (c) Des Herriott 1993, 1994
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the copyright holder not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  The copyright holder makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 * Author: Des Herriott
 */

/* Source file spectrum.c:
 * Contains functions to emulate ZX Spectrum specific hardware, i.e.
 * screen, keyboard and I/O ports.  Also contains all the X11 code.
 * Maybe one day it'll handle the speaker as well.
 */

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

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

#if defined(linux) && defined(DEV_AUDIO)
#include <linux/soundcard.h>
#include <sys/ioctl.h>
#endif

#ifdef MITSHM
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
#endif

#include "xzx.icon"

#include "version.h"
#include "z80.h"
#include "spectrum.h"
#include "resource.h"

#include "auxfuncs.c"

#ifdef PCSPKR_AUDIO
#include <unistd.h>	/* for ioperm() */
#include <asm/io.h>	/* for outb() and inb() */
#endif

#ifdef LEVEL_LOADER
#include <string.h>   /* strchr(), strrchr() */
#endif

#define NO_MORE_COLOURS -1

static KeySym *key_table;				/* configurable keymapping table */

static char *xim_data_ptr;
static int bytes_per_line;
static int flash_phase = 0;
static int using_monochrome;
static char unshift_cursor = 0;
static int flash_enabled;
static int screen_dirty = 1;			/* screen needs repainting? */
static int x_min, x_max, y_min, y_max;	/* bounding box of last area changed */


#if defined(DEV_AUDIO) || defined(PCSPKR_AUDIO)
static int audio_enabled = 0;			/* can we do audio? */
#ifdef DEV_AUDIO
static int audio_fd = -1;				/* audio device file descriptor */
#endif
#ifdef PCSPKR_AUDIO
static int have_pcspkr_permission = 0;	/* have perms for port 0x61? */
#endif
#endif


extern Display *open_display(int *argcp, char **argvp,
                             struct config *cb, KeySym *);
extern char *RequestFilename(char *prompt);


#ifdef JOY
extern int joystick_init(char *fname, int tolerance);
extern int joystick_read(void);
#endif

#ifdef LEVEL_LOADER
extern char last_snapshot_name[];
#endif



#if SCALING>1

#define shade(val,inkpap,paper)  (((val)&(inkpap))^(paper))

static int scaleup_done=0;       /* initialisation done or not? 0=no */
static long scaleup[256];   /* to hold table of scaled up bytes, e.g.
			       00110100B -> 0000111100110000B */

static uns8 halftone[16][4]={    /* 8-by-4 bitmaps for halftoning */
	{0xff,0xff,0xff,0xff},   /* black */
	{0xee,0xff,0xbb,0xff},   /* blue */
	{0x55,0xff,0xaa,0xff},   /* red */
	{0x55,0xbb,0x55,0xff},   /* magenta */
	{0xaa,0x55,0xaa,0x55},   /* green */
	{0x51,0xaa,0x15,0xaa},   /* cyan */
	{0xaa,0x44,0xaa,0x00},   /* yellow */
	{0x11,0x00,0x44,0x00},   /* white */
	{0xff,0xff,0xff,0xff},   /* black */
	{0x77,0xdf,0x77,0xfd},   /* bright blue */
	{0xaa,0xdd,0xaa,0x77},   /* bright red */
	{0xae,0x55,0xea,0x55},   /* bright magenta */
	{0x55,0x22,0x55,0x88},   /* bright green */
	{0x88,0x22,0x88,0x22},   /* bright cyan */
	{0x00,0x40,0x00,0x04},   /* bright yellow */
	{0x00,0x00,0x00,0x00}    /* bright white */
};
#endif

/* Maps ASCII keys to a keyboard row and bit to set/reset */
struct ktbl {
	int port;	/* which keyboard row is the key on? */
	int bit;	/* which bit to mask */
} keytable[128] = {
	{8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, 
	{8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, 
	{8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, 
	{8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, 
	/* SPACE ! "" # $ % & ' */
	{7, 0xfe}, {3, 0xfe}, {3, 0xfd}, {3, 0xfb}, {3,0xf7},
	{3,0xef}, {4,0xf7}, {8,0},
	/* ( ) * + , - . / */
	{4, 0xfd}, {4, 0xfe}, {4, 0xfb}, {8, 0}, {8, 0}, {8, 0},
	{8, 0}, {8, 0}, 
	/* 0 1 2 3 4 5 6 7 8 9 */
	{4, 0xfe}, {3, 0xfe}, {3, 0xfd}, {3, 0xfb}, {3, 0xf7},
	{3, 0xef}, {4, 0xef}, {4, 0xf7}, {4, 0xfb}, {4, 0xfd},
	/* : ; < = > ? @ */
	{8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}, {3, 0xfd}, 
	/* A -> Z  (upper & lower case are the same) */
	{1, 0xfe}, {7, 0xef}, {0, 0xf7}, {1, 0xfb}, {2, 0xfb}, {1, 0xf7},
	{1, 0xef}, {6, 0xef}, {5, 0xfb}, {6, 0xf7}, {6, 0xfb}, {6, 0xfd}, 
	{7, 0xfb}, {7, 0xf7}, {5, 0xfd}, {5, 0xfe}, {2, 0xfe}, {2, 0xf7},
	{1, 0xfd}, {2, 0xef}, {5, 0xf7}, {0, 0xef}, {2, 0xfd}, {0, 0xfb},
	{5, 0xef}, {0, 0xfd},
	/* [ \ ] ^ _ ` */
	{8, 0}, {8, 0}, {8, 0}, {4, 0xef}, {8, 0}, {8, 0},
	/* a -> z  (upper & lower case are the same) */
	{1, 0xfe}, {7, 0xef}, {0, 0xf7}, {1, 0xfb}, {2, 0xfb}, {1, 0xf7},
	{1, 0xef}, {6, 0xef}, {5, 0xfb}, {6, 0xf7}, {6, 0xfb}, {6, 0xfd}, 
	{7, 0xfb}, {7, 0xf7}, {5, 0xfd}, {5, 0xfe}, {2, 0xfe}, {2, 0xf7},
	{1, 0xfd}, {2, 0xef}, {5, 0xf7}, {0, 0xef}, {2, 0xfd}, {0, 0xfb},
	{5, 0xef}, {0, 0xfd},
	/* { | } ~ DEL */
	{8, 0}, {8, 0}, {8, 0}, {8, 0}, {8, 0}
};

/* RGB triples describing the Spectrum's 16 colours.  Note that black
 * and bright black are the same.
 */
static unsigned short rgb_vals[NCOLORS][3] = {
	/* dull */
	{ 0x0000, 0x0000, 0x0000}, { 0x0000, 0x0000, 0xbfff},
	{ 0xbfff, 0x0000, 0x0000}, { 0xbfff, 0x0000, 0xbfff},
	{ 0x0000, 0xbfff, 0x0000}, { 0x0000, 0xbfff, 0xbfff},
	{ 0xbfff, 0xbfff, 0x0000}, { 0xbfff, 0xbfff, 0xbfff}, 
	/* bright */
	{ 0x0000, 0x0000, 0x0000}, { 0x0000, 0x0000, 0xffff},
	{ 0xffff, 0x0000, 0x0000}, { 0xffff, 0x0000, 0xffff},
	{ 0x0000, 0xffff, 0x0000}, { 0x0000, 0xffff, 0xffff},
	{ 0xffff, 0xffff, 0x0000}, { 0xffff, 0xffff, 0xffff},
};

/* X11 information */
static Display *dpy;
static Colormap cmap;
static Window root;
static Window borderWin, mainWin;
static int scrn;
static Screen *sptr;
static XImage *xim;
static GC theGc;
#ifdef MITSHM
static XShmSegmentInfo xshminfo;
#endif
static int mitshm;

/* An array of X11 pixel values.  Initialised by get_colours() */
static unsigned long pixels[NCOLORS];

/* Holds the values of the 8 keyboard rows  (+1 for unscanned keys) */
static uns8 keyports[9];

/* Base of display file and attribute area */
static uns8 *dfile, *afile;

int border_col = 7;


/* Allocate the 16 colours the Spectrum uses.  Note that black and bright
 * black are the same colour.  <pix_ret> holds an array of the allocated
 * pixel values.  Return 0 on success, non-zero on failure.
 * 0..7  hold black, blue, red, magenta, green, cyan, yellow, white.
 * 8..15 hold brightened versions.
 */
static int
get_colours(unsigned long *pix_ret, int mono)
{
	XColor xc;
	int i;

	using_monochrome = mono;

	if (mono || DefaultDepthOfScreen(sptr) == 2) {
#ifdef DEBUG
		fprintf(stderr, "xzx: using monochrome colormap\n");
#endif
		for (i = 0; i < 8; i++) {
			pix_ret[i] = i < 4 ? BlackPixel(dpy, scrn) : WhitePixel(dpy, scrn);
			pix_ret[i+8] = i < 4 ? BlackPixel(dpy, scrn) : WhitePixel(dpy, scrn);
		}
		using_monochrome = 1;
	} else {
		xc.flags = DoRed | DoGreen | DoBlue;
		for (i = 0; i < NCOLORS; i++) {
			xc.red   = rgb_vals[i][0];
			xc.green = rgb_vals[i][1];
			xc.blue  = rgb_vals[i][2];
			if (!XAllocColor(dpy, cmap, &xc)) {
				fprintf(stderr, "xzx: XAllocColor failed on colour %d!\n", i);
				return(NO_MORE_COLOURS);
			}
			pix_ret[i] = xc.pixel;
		}
	}

	return(0);
}


#if SCALING > 1
/* Initialise the halftoning stipples for monochrome displays */
static void
init_scaleup_table(void)
{
	int j, k, l, m;
	unsigned long bigval; /* SCALING must be <= 4! */

	scaleup_done = 1;

	for(l = 0; l < 256; scaleup[l++] = bigval)
		for (m = l, bigval = j = 0; j < 8; j++) {
			for(k = 0; k < SCALING; k++)
				if (BitmapBitOrder(dpy) == LSBFirst)
					bigval = (bigval << 1) | (m & 1);
				else
					bigval = (bigval >>1 ) | ((m & 1) << 31);
			m>>=1;
		}
		if ((SCALING < 4) && (ImageByteOrder(dpy) == MSBFirst))
			if (BitmapBitOrder(dpy) == MSBFirst)
				bigval >>= 32 - 8 * SCALING;
			else
				bigval <<= 32 - 8 * SCALING;

	/* reverse the shading patterns if white==1 */
	if (WhitePixel(dpy, scrn))
		for (j = 0; j < 16; j++)
			for(k = 0; k < 4; k++)
				halftone[j][k]^=255;
}
#endif


/*
 * Enable/disable key repeat depending on <state>.
 */
static void
key_repeat(int state)
{
	XKeyboardControl kbs;

	kbs.auto_repeat_mode = state ? AutoRepeatModeOn : AutoRepeatModeOff;
	XChangeKeyboardControl(dpy, KBAutoRepeatMode, &kbs);
}


/*
 * Shutdown the Display connection, and free any shared memory we might have.
 */
static void
closedown(void)
{
#ifdef MITSHM
	if (mitshm) {
		XShmDetach(dpy, &xshminfo);
		XDestroyImage(xim);
		shmdt(xshminfo.shmaddr);
		shmctl(xshminfo.shmid, IPC_RMID, 0);
#ifdef DEBUG
		fprintf(stderr, "xzx: shared memory released\n");
#endif
	}
#endif

	key_repeat(1);
	XCloseDisplay(dpy);
}


/*
 * Tell the window manager some stuff it might need to know -
 * let's be environmentally friendly about this.
 */
static void
inform_wm(int argcnt, char **argvec)
{
	Pixmap icon_pix;
	XWMHints xwmh;
	XSizeHints xsh;
	XClassHint xch;
	XTextProperty appName, iconName;
	char *app_name;
	char *icon_name="xzx";

	icon_pix = XCreatePixmapFromBitmapData(dpy,
		root, xzx_bits, xzx_width, xzx_height,
		BlackPixel(dpy, scrn), WhitePixel(dpy, scrn),
		DefaultDepth(dpy, scrn));

	app_name = (char *)malloc(20);
	if (!app_name)
		app_name = "xzx";
	else
		sprintf(app_name, "xzx %d.%d", ZX_Major, ZX_Minor);

	xsh.flags = PPosition | PSize | PMinSize;
	xsh.min_width =  HSize;
	xsh.min_height = VSize;

	if (XStringListToTextProperty(&app_name, 1, &appName) == 0) {
		fprintf(stderr, "xzx: oops, can't create a TextProperty!\n");
		return;
	}
	if (XStringListToTextProperty(&icon_name, 1, &iconName) == 0) {
		fprintf(stderr, "xzx: oops, can't create a TextProperty!\n");
		return;
	}

	xwmh.initial_state = NormalState;
	xwmh.input = True;
	xwmh.icon_pixmap = icon_pix;
	xwmh.flags = StateHint | IconPixmapHint | InputHint;

	xch.res_name = "xzx";
	xch.res_class = "Xzx";

	XSetWMProperties(dpy, borderWin, &appName, &iconName, 
		argvec, argcnt, &xsh, &xwmh, &xch);
}


/* Open a connection to the X server, initialise the drawing area, 
 * allocate the colourmap, create windows.
 * Return -1 on failure, 0 for non-mitshm, 1 for mitshm.
 */
int
init_spectrum(int *argcp, char **argvp, struct config *cb)
{
	int maj, min, does_pixmaps;		/* MIT-SHM details */
	int i;
	Window r_ret, w_ret;
	int xpos, ypos, winx, winy;
	unsigned int mask_ret;
	extern void OnQuit(PFV);

	key_table = (KeySym *)malloc(sizeof(KeySym) * NUM_KEYMAPPINGS);

	x_max = y_max = 0; x_min = HSize - 1; y_min = VSize - 1;

	/* connect to the X server and get the resources */
	if ((dpy = open_display(argcp, argvp, cb, key_table)) == NULL) {
		fprintf(stderr, "xzx: Failed to open display!\n");
		return(-1);
	}

	/* if flashing is disabled, <flash_phase> is always 1, therefore
	 * any flashing bytes will be inverse instead.
 	 */
	flash_enabled = cb->flashing;
	flash_phase = !flash_enabled;

	scrn  = DefaultScreen(dpy);
	sptr  = DefaultScreenOfDisplay(dpy);
	root  = DefaultRootWindow(dpy);

	theGc = XCreateGC(dpy, root, 0, NULL);
	XCopyGC(dpy, DefaultGC(dpy, scrn), ~0, theGc);
	XSetGraphicsExposures(dpy, theGc, False);

	if (cb->private)
		cmap = XCreateColormap(dpy, root, DefaultVisual(dpy, scrn), AllocNone);
	else
		cmap  = DefaultColormap(dpy, scrn);

	/* Failure to allocate colourmap should be dealt with gracefully, 
	 * e.g. use a monochrome colourmap?
	 */
	if (get_colours(pixels, cb->monochrome) != 0) {
		fprintf(stderr, "xzx: Colour allocation failed - trying monochrome\n");
		cb->monochrome = 1;
		get_colours(pixels, cb->monochrome);
	}
#ifdef DEBUG
	else fprintf(stderr, "xzx: Successfully allocated 16 colours\n");
#endif
#ifdef PCSPKR_AUDIO
	if (cb->use_sound && audio_pcspkr_init() == -1)
		fprintf(stderr, "xzx: audio initialisation failed, sorry\n");
	setuid(getuid());
	setgid(getgid());	/* give up root permissions */
#endif

	/* Try using MIT-SHM for image transfer (unless we've been
	 * specifically told not to)
	 */
#ifdef MITSHM
	if (cb->try_mitshm && XShmQueryVersion(dpy, &maj, &min, &does_pixmaps)) {
#ifdef DEBUG
		fprintf(stderr,
			"xzx: MIT-SHM %d.%d, shared pixmaps %ssupported\n",
			maj, min, does_pixmaps ? "" : "not ");
#endif
		mitshm = True;
		xim = XShmCreateImage(dpy, DefaultVisual(dpy, scrn),
			DefaultDepth(dpy, scrn), ZPixmap, NULL, &xshminfo, HSize, VSize); 
		if (!xim) {
			fprintf(stderr, "xzx: failed to create a shared XImage!\n");
			return(-1);
		}
		xshminfo.shmid = shmget(IPC_PRIVATE, xim->bytes_per_line * xim->height,
			IPC_CREAT|0777);
		if (xshminfo.shmid == -1) {
			perror("shmget <xshminfo.shmid>");
			return(-1);
		}
		xshminfo.shmaddr = xim->data = shmat(xshminfo.shmid, 0, 0);
		if (!xshminfo.shmaddr) {
			perror("shmat <xshminfo.shmaddr>");
			return(-1);
		}
		xshminfo.readOnly = False;
		if (!XShmAttach(dpy, &xshminfo)) {
			perror("XShmAttach");
			return(-1);
		}
#ifdef DEBUG
		fprintf(stderr, "xzx: Shared X Image successfully created and attached\n");
#endif
	} else {
#endif
#ifdef MITSHM
		fprintf(stderr, "xzx: X server does not support MITSHM, performance may be poor\n");
#else
		fprintf(stderr, "xzx: xzx does not support MIT-SHM, performance may be poor\n");
#endif
		mitshm = False;
		/* interrupt recalibration without MIT-SHM tends to lock the emulator */
		cb->recalibrate = 0;
		xim = XCreateImage(dpy,  DefaultVisual(dpy, scrn), 
			DefaultDepth(dpy, scrn), ZPixmap, 0, NULL, HSize, VSize, 8, 0);
		if (!xim) {
			fprintf(stderr, "xzx: XCreateImage failed!\n");
			return(-1);
		}
		if ((xim->data = (char *)malloc(xim->bytes_per_line * xim->height))
			== NULL) {
			perror("malloc <xim->data>");
			return(-1);
		}
#ifdef DEBUG
		fprintf(stderr, "xzx: X Image successfully created.\n");
#endif
#ifdef MITSHM
	}
#endif

	bytes_per_line = xim->bytes_per_line;
	xim_data_ptr = xim->data;

	/* no keys initially being pressed */
	for (i = 0; i < 8; i++)
		keyports[i] = 0xff;

	dfile = theMemory + 16384;	/* base of screen memory */
	afile = dfile + 6144;		/* base of attribute map */

#ifdef JOY
	if (cb->use_js && joystick_init(cb->js_device, cb->js_tolerance) == -1)
		fprintf(stderr, "xzx: joystick initialisation failed, sorry\n");
#endif
#ifdef DEV_AUDIO
	if (cb->use_sound && audio_init(cb->audio_dev) == -1)
		fprintf(stderr, "xzx: audio initialisation failed, sorry\n");
#endif

	/* Try to make xzx appear under the current pointer location -
	 * this is, however, ultimately up to the window manager
	 */
	if (!XQueryPointer(dpy, root, &r_ret, &w_ret, &xpos, &ypos, 
			&winx, &winy, &mask_ret)) {
		xpos = 0; ypos = 0;
	} else {
		xpos -= (BORDER_WIDTH + HSize / 2);
		ypos -= (BORDER_WIDTH + VSize / 2);
	}
	if (xpos < 0) xpos = 0;
	if (ypos < 0) ypos = 0;

	borderWin = XCreateSimpleWindow(dpy, root, xpos, ypos,
			HSize + BORDER_WIDTH * 2, VSize + BORDER_WIDTH * 2,
			0, pixels[7], pixels[7]);
	mainWin = XCreateSimpleWindow(dpy, borderWin, BORDER_WIDTH, BORDER_WIDTH,
			HSize, VSize, 0, pixels[7], pixels[7]);	/* dull white */

	inform_wm(*argcp, argvp);

	if (cb->private)
		XSetWindowColormap(dpy, borderWin, cmap);

	/* Events all get propagated to the border window, so don't
	 * bother selecting events on the main drawing window.
	 */
	XSelectInput(dpy, borderWin, KeyPressMask | KeyReleaseMask | ExposureMask |
		EnterWindowMask | LeaveWindowMask | StructureNotifyMask);
	XMapRaised(dpy, borderWin);
	XMapRaised(dpy, mainWin);

	XFlush(dpy);

	OnQuit(closedown);

#ifdef DEBUG
	fprintf(stderr, "xzx: X11 initialisation successful\n");
#endif

	return(mitshm ? 1 : 0);
}


/* Refresh the window from our XImage, and make sure the border colour
 * is OK.  Called every n interrupts, where n depends on whether or not
 * we can use MIT-SHM.
 */
void
refresh_screen(void)
{
	static uns8 round_up[] = { 0x07, 0x0f, 0x17, 0x1f };

	if (!screen_dirty)
		return;
	screen_dirty = 0;

	/* round up to next largest multiple of (8 * SCALING) */
	x_max |= round_up[SCALING - 1];

#ifdef MITSHM
	if (mitshm)
		XShmPutImage(dpy, mainWin, theGc, xim, x_min, y_min, x_min, y_min,
			x_max - (x_min - 1), y_max - (y_min - 1), False);
	else 
#endif
		XPutImage(dpy, mainWin, theGc, xim, x_min, y_min, x_min, y_min,
			x_max - (x_min - 1), y_max - (y_min - 1));

	x_max = y_max = 0; x_min = HSize - 1; y_min = VSize - 1;

	XSetWindowBackground(dpy, borderWin, pixels[border_col]);
	XClearWindow(dpy, borderWin);
	XFlush(dpy);
}


/* Byte <val> has just been written to screen address <addr>.
 * Update the XImage.
 */
void
screen_write(uns16 addr, uns8 val)
{
	int x, y;
	uns8 *baseptr;
	uns16 i = addr - D_FILE;
	unsigned int ink, paper;
	unsigned long i_pix, p_pix, tmp;
	uns8 attr;

#if SCALING > 1 
	uns8 pmask, ipmask;
	int j, k;
	long bigval;
#endif

	screen_dirty++;

	x = dtbl[i][XVal]; y = dtbl[i][YVal];
	attr = afile[XYToAttr(x,y)];
	ink = Foreground(attr); paper = Background(attr);
	/* toggle the colours if we're in the inverse part of the FLASH cycle */
	if (flash_phase && (attr & 0x80)) {
		tmp=ink; ink=paper; paper=tmp;
	}

	/* In monochrome mode, make sure that we get two contrasting colours */
	if (using_monochrome) {
		if (paper > ink) {
			p_pix = WhitePixel(dpy, scrn); i_pix = BlackPixel(dpy, scrn);
		} else if (paper < ink) {
			i_pix = WhitePixel(dpy, scrn); p_pix = BlackPixel(dpy, scrn);
		} else {
			if (paper < 4 || (paper > 7 && paper < 12)  ) {
				i_pix = BlackPixel(dpy, scrn); p_pix = BlackPixel(dpy, scrn);
			} else {
				i_pix = WhitePixel(dpy, scrn); p_pix = WhitePixel(dpy, scrn);
			}
		}
#if SCALING>1
		pmask=255*p_pix;
		ipmask=(255*i_pix)^pmask;
#endif
	} else {
		i_pix = pixels[ink];
		p_pix = pixels[paper];
	}

/* #ifdef this just in case the compiler is braindead */
#if SCALING > 1
	x *= SCALING; y *= SCALING;
#endif

	/* Extend the bounding box if necessary */
	if (x < x_min) x_min = x;
	if (x > x_max) x_max = x + SCALING - 1;
	if (y < y_min) y_min = y;
	if (y > y_max) y_max = y + SCALING - 1;

#if SCALING == 1
	if (bytes_per_line == 256) {
		baseptr = (uns8 *)xim_data_ptr + x + (y << 8);
		baseptr[0] = val & 0x80 ? i_pix : p_pix;
		baseptr[1] = val & 0x40 ? i_pix : p_pix;
		baseptr[2] = val & 0x20 ? i_pix : p_pix;
		baseptr[3] = val & 0x10 ? i_pix : p_pix;
		baseptr[4] = val & 0x08 ? i_pix : p_pix;
		baseptr[5] = val & 0x04 ? i_pix : p_pix;
		baseptr[6] = val & 0x02 ? i_pix : p_pix;
		baseptr[7] = val & 0x01 ? i_pix : p_pix;
	} else /* bytes_per_line == 32 */ {
		baseptr = (uns8 *)xim_data_ptr + x/8 + (y << 5);
		baseptr[0] = (i_pix ? val : 0) | (p_pix ? val^255 : 0);
	}
#else /* SCALING > 1 */
	if (bytes_per_line == 256*SCALING) {
		for (j = 0; j < SCALING; j++)
			for (k = 0; k < SCALING; k++) {
				baseptr = (uns8 *)xim_data_ptr + x + y*HSize;
				baseptr[0 * SCALING + j * HSize + k] = val & 0x80? i_pix: p_pix;
				baseptr[1 * SCALING + j * HSize + k] = val & 0x40? i_pix: p_pix;
				baseptr[2 * SCALING + j * HSize + k] = val & 0x20? i_pix: p_pix;
				baseptr[3 * SCALING + j * HSize + k] = val & 0x10? i_pix: p_pix;
				baseptr[4 * SCALING + j * HSize + k] = val & 0x08? i_pix: p_pix;
				baseptr[5 * SCALING + j * HSize + k] = val & 0x04? i_pix: p_pix;
				baseptr[6 * SCALING + j * HSize + k] = val & 0x02? i_pix: p_pix;
				baseptr[7 * SCALING + j * HSize + k] = val & 0x01? i_pix: p_pix;
			}
	} else /* bytes_per_line == 32*SCALING */ {
		if (!scaleup_done)
			init_scaleup_table();

		bigval = scaleup[val];
		baseptr = (uns8 *)xim_data_ptr + (x >> 3) + (y<<5) * SCALING;
		y &= 0x03; /* y is now the line within the 8x4 shading bitmap */
		for (j = 0; j < SCALING; j++) {
			if (!using_monochrome) {
				pmask  = halftone[paper][y];
				ipmask = halftone[ink][y]^pmask;
			}
			/* This should make it work for any client/server endian-ness */
			if (ImageByteOrder(dpy) == BitmapBitOrder(dpy)) {
				baseptr[0] = shade(bigval, ipmask, pmask);
				baseptr[1] = shade(bigval >> 8, ipmask, pmask);
#if SCALING>2
				baseptr[2] = shade(bigval >> 16, ipmask, pmask);
#endif
#if SCALING>3
				baseptr[3] = shade(bigval >> 24, ipmask, pmask);
#endif
			} else {
				baseptr[0] = shade(bigval >> 24, ipmask, pmask);
				baseptr[1] = shade(bigval >> 16, ipmask, pmask);
#if SCALING>2
				baseptr[2] = shade(bigval >> 8, ipmask, pmask);
#endif
#if SCALING>3
				baseptr[3] = shade(bigval, ipmask, pmask);
#endif
			}
			baseptr += SCALING * 32;
			if (++y == 4) y=0;
		}
	}
#endif
}

#ifdef PCSPKR_AUDIO
/* it's important these functions aren't inline */
static void 
port_out(uns8 value, uns16 port)
{
	outb(value, port);
}

static uns8 
port_in(uns16 port)
{
	return(inb(port));
}
#endif


/* Byte <val> has just been written to attribute address <addr>
 * Use screen_write() to update the 8 pixel rows in that square.
 */ 
void
attr_write(uns16 addr, uns8 val)
{
	int a_off = addr - A_FILE;
	int d_off;

	d_off = ((a_off & 0x300) << 3) +	/* Which third of the dfile */
			(a_off & 0xff) + D_FILE;

	screen_write(d_off + 0x000, theMemory[d_off + 0x000]);
	screen_write(d_off + 0x100, theMemory[d_off + 0x100]);
	screen_write(d_off + 0x200, theMemory[d_off + 0x200]);
	screen_write(d_off + 0x300, theMemory[d_off + 0x300]);
	screen_write(d_off + 0x400, theMemory[d_off + 0x400]);
	screen_write(d_off + 0x500, theMemory[d_off + 0x500]);
	screen_write(d_off + 0x600, theMemory[d_off + 0x600]);
	screen_write(d_off + 0x700, theMemory[d_off + 0x700]);
}


/*
 * Functions to implement the I/O ports.  Currently deals with keyboard
 * reading, changing the border colour, pseudo stdio port emulation,
 * microdrive emulation, and on some machines, the Kempston joystick and
 * audio emulation.
 */
uns8
in_byte(uns16 port)
{
	int res = 0xff;
	int highbyte;

	if ((port & 0xff) == 0x1f) {		/* kempston joystick port */
#ifdef JOY
		inPorts[0x1f] = joystick_read();
#endif
		return(inPorts[0x1f]);
	} else if ((port & 0xff) == 0xfe) {	/* keyboard */
		highbyte = port >> 8;
		if (!(highbyte & 0x80)) res &= keyports[7];
		if (!(highbyte & 0x40)) res &= keyports[6];
		if (!(highbyte & 0x20)) res &= keyports[5];
		if (!(highbyte & 0x10)) res &= keyports[4];
		if (!(highbyte & 0x08)) res &= keyports[3];
		if (!(highbyte & 0x04)) res &= keyports[2];
		if (!(highbyte & 0x02)) res &= keyports[1];
		if (!(highbyte & 0x01)) res &= keyports[0];
		return(res);
	} else
#ifdef ZX_IF1
        if ((port & 0xff) == 0xe7)
        	return in_port_e7();
        else if ((port & 0xff) == 0xef)
                return in_port_ef();
        else
#endif
#ifdef PSEUDO_IO
		/* Only really useful for text files - return value of 255
		 * could be EOF or just a 255 in the input stream
		 */
		if (port == ((PSEUDO_IO_SELECT << 8) | STDIN_PSEUDO_PORT))
			return (uns8)getchar();
#endif
		return 0xff;
}

void
out_byte(uns16 port, uns8 val)
{
	if ((port & 0xff) == 0xfe) {
	{
#ifdef ZX_IF1
		/* ensure we get a flashing border effect */

		if (border_col != (val & 0x07))
		{
			border_col = val & 0x07;
			XSetWindowBackground(dpy, borderWin, pixels[border_col]);
			XClearWindow(dpy, borderWin);
		}
#else
		border_col = val & 0x07;
#endif
#ifdef PCSPKR_AUDIO
		/* cheap 'n' easy audio - bang it to the speaker */
		if (audio_enabled && have_pcspkr_permission)
			{
			uns8 tmp;
			
			tmp=port_in(0x61)&0xfc;
			if (val & 0x10)
				port_out(tmp|2,0x61);
			else
				port_out(tmp,0x61);
		}
#endif
	}
#ifdef DEV_AUDIO
		if (audio_enabled && (val & 0x10) != (outPorts[0xfe] & 0x10)) {
			uns8 snd;
			snd = (val & 0x10) ? 0x01 : 0x00;
			write(audio_fd, &snd, 1);
#ifdef linux
			if (!snd) ioctl(audio_fd, SNDCTL_DSP_SYNC, NULL);
#endif
		}
#endif
	}
#ifdef ZX_IF1
	if ((port & 0xff) == 0xef)
        	out_port_ef(val);

	if ((port & 0xff) == 0xe7)
        	out_port_e7(val);
#endif
#ifdef PSEUDO_IO
	if (port == ((PSEUDO_IO_SELECT << 8) | STDOUT_PSEUDO_PORT))
		putc((int)val, stdout);
	if (port == ((PSEUDO_IO_SELECT << 8) | STDERR_PSEUDO_PORT))
		putc((int)val, stderr);
#endif

	outPorts[(uns8)port & 0xff] = val;
}


/* Check for incoming X events */
void
check_x_events(void)
{
	XEvent xev;
	XKeyEvent *kev;
	XCrossingEvent *cev;
	XConfigureEvent *conf_ev;
	KeySym ks;
	char buf[3];
	char *snapname;
#ifdef DEBUG
	extern int logging;	/* from main.c */
#endif
#ifdef SLOWDOWN
	extern void adjust_speed(int inc);	/* from main.c */
#endif
	extern void Quit(void);

	while (XEventsQueued(dpy, QueuedAfterReading)) {
		XNextEvent(dpy, &xev);
        switch (xev.type) {
		  case Expose:
			/* force a refresh of the entire screen */
			x_max = HSize - 1; y_max = VSize - 1; x_min = y_min = 0;
			screen_dirty++;
			break;
		  case ConfigureNotify:
			conf_ev = (XConfigureEvent *)&xev;
			XMoveWindow(dpy, mainWin, (conf_ev->width - HSize) / 2,
				(conf_ev->height - VSize) / 2);
			break;
		  case MapNotify: case UnmapNotify: case ReparentNotify: 
			/* don't do anything here - just avoid complaints about
			 * unhandled X events. */
			break;
		  case EnterNotify:
			cev = (XCrossingEvent *)&xev;
			if (cev->detail != NotifyInferior)
				key_repeat(0);
			break;
		  case LeaveNotify:
			cev = (XCrossingEvent *)&xev;
			if (cev->detail != NotifyInferior)
				key_repeat(1);
			break;
          case KeyPress:
            kev = (XKeyEvent *)&xev;
            XLookupString(kev, buf, 2, &ks, NULL);
			if (ks == key_table[KM_KEMPST_UP])
				inPorts[0x1f] |= 0x08;
			else if (ks == key_table[KM_KEMPST_DOWN])
				inPorts[0x1f] |= 0x04;
			else if (ks == key_table[KM_KEMPST_LEFT])
				inPorts[0x1f] |= 0x02;
			else if (ks == key_table[KM_KEMPST_RIGHT])
				inPorts[0x1f] |= 0x01;
			else if (ks == key_table[KM_KEMPST_FIRE])
				inPorts[0x1f] |= 0x10;
#ifdef SLOWDOWN
			else if (ks == key_table[KEY_SLOWER])
				adjust_speed(1);
			else if (ks == key_table[KEY_FASTER])
				adjust_speed(-1);
#endif
          	switch (ks) {
          		case XK_F1: case XK_F2:
					DoReset();
					break;
          		case XK_F3:
					snapname = RequestFilename("Name of snapshot to save");
					WriteSNAFormat(snapname);
					break;
          		case XK_F4:
					snapname = RequestFilename("Name of snapshot to load");
					ReadSnapshot(snapname);
					break;
#if defined(DEV_AUDIO) || defined(PCSPKR_AUDIO)
				case XK_F5:
					audio_enabled = 1 - audio_enabled;
					break;
#endif
				case XK_F8:
					/* Generate an NMI */
					if (theMemory[*pc] == 0x76)
						(*pc)++;	/* terminate a HALT */
					theProcessor->iff1=0;
					PushValue(*pc);
					*pc=0x0066;
					break;
#ifdef DEBUG
          		case XK_F9:
					logging = 1 - logging;
					printf("disassembly logging switched %s\n",
						logging? "on" : "off");
					break;
#endif
          		case XK_F10: case XK_F12:
					Quit();
					break;
				case XK_Return:
					keyports[6] &= 0xfe; break;
				case XK_Control_L: case XK_Control_R:
				case XK_Shift_L: case XK_Shift_R:
					keyports[0] &= 0xfe; break;
				case XK_Alt_L: case XK_Alt_R:
				case XK_Meta_L: case XK_Meta_R:
					keyports[7] &= 0xfd; break;
				case XK_BackSpace: case XK_Delete:
					keyports[0] &= 0xfe; keyports[4] &= 0xfe; break;
				case XK_Escape:
					keyports[0] &= 0xfe; keyports[3] &= 0xfe; break;
				case XK_Tab:
					unshift_cursor = !unshift_cursor; break;
				case XK_Up:
					unshift_cursor || (keyports[0] &= 0xfe);
						keyports[4] &= 0xf7; break;
				case XK_Down:
					unshift_cursor || (keyports[0] &= 0xfe);
						keyports[4] &= 0xef; break;
				case XK_Left:
					unshift_cursor || (keyports[0] &= 0xfe);
						keyports[3] &= 0xef; break;
				case XK_Right:
					unshift_cursor || (keyports[0] &= 0xfe);
						keyports[4] &= 0xfb; break;
				case XK_minus:
					keyports[7] &= 0xfd; keyports[6] &= 0xf7; break;
				case XK_underscore:
					keyports[0] |= 0x01; keyports[7] &= 0xfd;
					keyports[4] &= 0xfe; break;
				case XK_equal:
					keyports[7] &= 0xfd; keyports[6] &= 0xfd; break;
				case XK_plus:
					keyports[0] |= 0x01; keyports[7] &= 0xfd;
					keyports[6] &= 0xfb; break;
				case XK_semicolon:
					keyports[7] &= 0xfd; keyports[5] &= 0xfd; break;
				case XK_colon:
					keyports[0] |= 0x01; keyports[7] &= 0xfd;
					keyports[0] &= 0xfd; break;
				case XK_apostrophe:
					keyports[7] &= 0xfd; keyports[4] &= 0xf7; break;
/*			
				case XK_at:
					keyports[0] |= 0x01; keyports[7] &= 0xfd;
					keyports[3] &= 0xfd; break;
				case XK_numbersign:
					keyports[7] &= 0xfd; keyports[3] &= 0xfb; break;
*/
				case XK_comma:
					keyports[7] &= 0xfd; keyports[7] &= 0xf7; break;
				case XK_period:
					keyports[7] &= 0xfd; keyports[7] &= 0xfb; break;
				case XK_less:
					keyports[0] |= 0x01; keyports[7] &= 0xfd;
					keyports[2] &= 0xf7; break;
				case XK_greater:
					keyports[0] |= 0x01; keyports[7] &= 0xfd;
					keyports[2] &= 0xef; break;
				case XK_slash:
					keyports[7] &= 0xfd; keyports[0] &= 0xef; break;
				case XK_question:
					keyports[0] |= 0x01; keyports[7] &= 0xfd;
					keyports[0] &= 0xf7; break;
				case XK_backslash: case XK_bar:
					keyports[0]= keyports[1]= keyports[2]= keyports[3]=
					keyports[4]= keyports[5]= keyports[6]= keyports[7]=0xff;
					break;
				default:
					if (ks < 128)
						keyports[keytable[ks].port] &= keytable[ks].bit;
					break;
          	}
            break;
          case KeyRelease:
            kev = (XKeyEvent *)&xev;
            XLookupString(kev, buf, 2, &ks, NULL);
			if (ks == key_table[KM_KEMPST_UP])
				inPorts[0x1f] &= ~0x08;
			else if (ks == key_table[KM_KEMPST_DOWN])
				inPorts[0x1f] &= ~0x04;
			else if (ks == key_table[KM_KEMPST_LEFT])
				inPorts[0x1f] &= ~0x02;
			else if (ks == key_table[KM_KEMPST_RIGHT])
				inPorts[0x1f] &= ~0x01;
			else if (ks == key_table[KM_KEMPST_FIRE])
				inPorts[0x1f] &= ~0x10;
          	switch (ks) {
				case XK_Return:
					keyports[6] |= 0x01; break;
				case XK_Control_L: case XK_Control_R:
				case XK_Shift_L: case XK_Shift_R:
					keyports[0] |= 0x01; break;
				case XK_Alt_L: case XK_Alt_R:
				case XK_Meta_L: case XK_Meta_R:
					keyports[7] |= 0x02; break;
				case XK_BackSpace: case XK_Delete:
					keyports[0] |= 0x01; keyports[4] |= 0x01; break;
				case XK_Escape:
					keyports[0] |= 0x01; keyports[3] |= 0x01; break;
				case XK_Up:
					unshift_cursor || (keyports[0] |= 0x01);
						keyports[4] |= 0x08; break;
				case XK_Down:
					unshift_cursor || (keyports[0] |= 0x01);
						keyports[4] |= 0x10; break;
				case XK_Left:
					unshift_cursor || (keyports[0] |= 0x01);
						keyports[3] |= 0x10; break;
				case XK_Right:
					unshift_cursor || (keyports[0] |= 0x01);
						keyports[4] |= 0x04; break;
				case XK_minus:
					keyports[7] |= 0x02; keyports[6] |= 0x08; break;
				case XK_underscore:
					keyports[0] &= 0xfe; keyports[7] |= 0x02;
					keyports[4] |= 0x01; break;
				case XK_equal:
					keyports[7] |= 0x02; keyports[6] |= 0x02; break;
				case XK_plus:
					keyports[0] &= 0xfe; keyports[7] |= 0x02;
					keyports[6] |= 0x04; break;
				case XK_semicolon:
					keyports[7] |= 0x02; keyports[5] |= 0x02; break;
				case XK_colon:
					keyports[0] &= 0xfe; keyports[7] |= 0x02;
					keyports[0] |= 0x02; break;
				case XK_apostrophe:
					keyports[7] |= 0x02; keyports[4] |= 0x08; break;
				/*
				case XK_at:
					keyports[0] &= 0xfe; keyports[7] |= 0x02;
					keyports[3] |= 0x02; break;
				case XK_numbersign:
					keyports[7] |= 0x02; keyports[3] |= 0x04; break;
				*/
				case XK_comma:
					keyports[7] |= 0x02; keyports[7] |= 0x08; break;
				case XK_period:
					keyports[7] |= 0x02; keyports[7] |= 0x04; break;
				case XK_less:
					keyports[0] &= 0xfe; keyports[7] |= 0x02;
					keyports[2] |= 0x08; break;
				case XK_greater:
					keyports[0] &= 0xfe; keyports[7] |= 0x02;
					keyports[2] |= 0x10; break;
				case XK_slash:
					keyports[7] |= 0x02; keyports[0] |= 0x10; break;
				case XK_question:
					keyports[0] &= 0xfe; keyports[7] |= 0x02;
					keyports[0] |= 0x08; break;
				case XK_backslash: case XK_bar:
					keyports[0]= keyports[1]= keyports[2]= keyports[3]=
					keyports[4]= keyports[5]= keyports[6]= keyports[7]=0xff;
					break;
				default:
					if (ks < 128)
						keyports[keytable[ks].port] |= ~keytable[ks].bit;
					break;
          	}
          	break;
          default:
            fprintf(stderr, "xzx: unhandled X event, type %d\n", xev.type);
            break;
		}
	}
}


/* Any byte in the attribute area with the FLASH bit set causes
 * its screen block to be inverted.
 * FIXME: this routine is very inefficient at the moment.
 */
void
handle_flashing(void)
{
	int i;
	uns8 val, tmpval;

	if (!flash_enabled)
		return;

	flash_phase = 1 - flash_phase;

	for (i = 0; i < 768; i++) {
		val = afile[i];
		if (val & 0x80) {
            tmpval = val;
			afile[i] = tmpval;
			attr_write((uns16)(afile + i - theMemory), tmpval);
			afile[i] = val;
		}
	}
}


#ifdef DEV_AUDIO
/* Open a connection to the audio device */
int 
audio_init(char *dev_name)
{
	audio_fd = open(dev_name, O_WRONLY);

	audio_enabled = (audio_fd != -1);

#ifdef linux
	if (audio_enabled) ioctl(audio_fd, SNDCTL_DSP_SPEED, 12000);
#endif

	return audio_enabled ? 0 : -1;
}
#endif

#ifdef PCSPKR_AUDIO
/* Get permissions for the speaker port - binary must be setuid root! */
int
audio_pcspkr_init()
{
	return (audio_enabled = have_pcspkr_permission =
			!ioperm(0x61,1,1)) ? 0 : -1;
}
#endif

#ifdef LEVEL_LOADER
/* Load level A from file into memory at HL */
void
level_loader_trap()
{
	FILE *in;
	char *ptr;
	char name[256];
	extern FILE *open_xzx_file(char *name, char *access);

	if(*hl<16384) {
	  fprintf(stderr,"xzx: Warning: attempt to load level over ROM!\n");
	  return;
	}

	/* look at last_snapshot_name and remove ".sna" or ".z80" */
	strcpy(name,last_snapshot_name);
	ptr=strrchr(name,'.');
	if(ptr==NULL) ptr=name+strlen(name);

	/* now add A as text */
	sprintf(ptr,"%d.dat",*a);

	if((in = open_xzx_file(name,"r"))==NULL) {
	  fprintf(stderr,"xzx: warning: couldn't find level '%s'!\n",name);
	  return;
	}

	/* just read as much as possible, up to top of memory or EOF */
#ifdef DEBUG
	fprintf(stderr, "reading overlay %s, at address 0x%04x\n..", name, *hl);
#endif
	fread(theMemory+*hl,1,65536-(*hl),in);
	fclose(in);
}
#endif
