/*
 * Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
 *
 * (c) Copyright 1996, 1997, 1998 Gary Henderson (gary@daniver.demon.co.uk) and
 *                                Jerremy Koot (jkoot@euronet.nl)
 *
 * Super FX C emulator code 
 * (c) Copyright 1997, 1998 Lestat (lstat@hotmail.com) and
 *                          Gary Henderson.
 * Super FX assembler emulator code (c) Copyright 1998 zsKnight and _Demo_.
 *
 * DSP1 emulator code (c) Copyright 1998 Lestat and Gary Henderson.
 * DOS port code contains the works of other authors. See headers in
 * individual files.
 *
 * Permission to use, copy, modify and distribute Snes9x in both binary and
 * source form, for non-commercial purposes, is hereby granted without fee,
 * providing that this license information and copyright notice appear with
 * all copies and any derived work.
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event shall the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Snes9x is freeware for PERSONAL USE only. Commercial users should
 * seek permission of the copyright holders first. Commercial use includes
 * charging money for Snes9x or software derived from Snes9x.
 *
 * The copyright holders request that bug fixes and improvements to the code
 * should be forwarded to them so everyone can benefit from the modifications
 * in future versions.
 *
 * Super NES and Super Nintendo Entertainment System are trademarks of
 * Nintendo Co., Limited and its subsidiary companies.
 */

#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#include "snes9x.h"
#include "memmap.h"
#include "debug.h"
#include "ppu.h"
#include "snapshot.h"
#include "gfx.h"
#include "display.h"
#include "apu.h"

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

Display *display = NULL;
Screen *screen = NULL;
Visual *visual = NULL;
Window window = 0;
GC gc = 0;
XImage *image = NULL;
Colormap cmap = 0;
Cursor point_cursor = 0;
Cursor cross_hair_cursor = 0;
XColor colors [256] = { {0} };
bool8 pseudo = TRUE;
bool8 grayscale = FALSE;
int depth = 8;
int window_width = SNES_WIDTH;
int window_height = SNES_HEIGHT_EXTENDED;
short RedShift = 0;
short BlueShift = 0;
short GreenShift = 0;
short RedSize = 0;
short GreenSize = 0;
short BlueSize = 0;
uint16 *Table8to16 = NULL;
static int mouse_x = 0;
static int mouse_y = 0;
static uint32 mouse_buttons = 0;
static bool8 mod1_pressed = FALSE;
static bool8 superscope = FALSE;
static uint32 superscope_turbo = 0;
static uint32 superscope_pause = 0;
static double mouse_scale_h = 1.0;
static double mouse_scale_v = 1.0;
static int mouse_offset_x = 0;
static int mouse_offset_y = 0;

XColor fixed_colours [256] = { {0} };
uint8 palette [0x10000] = {0};

extern uint32 joypads [5];

#ifdef MITSHM
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
XShmSegmentInfo SHMInfo;
int UseSHM = 1;
#endif

void Scale8BitImage (int width, int height);
void Scale16BitImage (int width, int height);
void ConvertTo16BitImage (int width, int height);
void ConvertTo8BitImage (int width, int height);
void ConvertTo24BitImage ();
void ConvertTo24BitImagePacked ();
void BuildColorLookupTable ();
void SetupImage ();
int ErrorHandler (Display *, XErrorEvent *);

void RGBtoHSI (unsigned short red, unsigned short green, unsigned short blue,
	       short &h, short &s, short &i)
{
    int min, max, other, t;
    double in, sa;
    double tmp1, tmp2;

    max = red;
    other = green;
    min = blue;
    /* Sort numeric values */
    if (other > max)
	t = other, other = max, max = t;
    if (min > other)
	t = other, other = min, min = t;
    if (other > max)
	t = other, other = max, max = t;

    in = (double) max / 0xffff;
    i = (short) (in * 100 + 0.5);
    if (i == 0)
	sa = 0;
    else
	sa = 1 - ((double) min / (0xffff * in));
    s = (short) (sa * 100 + 0.5);
    if (i == 0 || s == 0)
	h = 0;
    else
    {
	tmp1 = in * 100;
	tmp2 = sa * 100;

	if (red >= green && red >= blue)
	{
	    if (green >= blue)
		h = (short) (((((double) green * 10000) / (tmp1 * 0xffff) -
		    (100 - tmp2)) / tmp2) * 60);
	    else
		h = (short) (360 - (((((double) blue * 10000) / (tmp1 * 0xffff) -
		    (100 - tmp2)) / tmp2) * 60));
	}
	else
	{
	    if (green >= red && green >= blue)
	    {
		if (red >= blue)
		    h = (short) (120 - (((((double) red * 10000) / (tmp1 * 0xffff) -
				 (100 - tmp2)) / tmp2) * 60));
		else
		    h = (short) (120 + ((((double) blue * 10000) / (tmp1 * 0xffff) -
				 (100 - tmp2)) / tmp2 * 60));
	    }
	    else
	    {
		if (green >= red)
		    h = (short) (240 - (((((double) green * 10000) / (tmp1 * 0xffff) -
				 (100 - tmp2)) / tmp2) * 60));
		else
		    h = (short) (240 + (((((double) red * 10000) / (tmp1 * 0xffff) -
				 (100 - tmp2)) / tmp2) * 60));
	    }
	}
    }
}

void S9xDeinitDisplay ()
{
#ifdef MITSHM
    if(UseSHM)
    {
	XShmDetach (display, &SHMInfo);
	if(SHMInfo.shmaddr) shmdt(SHMInfo.shmaddr);
	if(SHMInfo.shmid>=0) shmctl(SHMInfo.shmid,IPC_RMID,0);
    }
#endif
    XAutoRepeatOn (display);
    XSync (display, False);
}

void S9xInitDisplay (int, char **)
{
    if (!(display = XOpenDisplay (NULL)))
    {
	fprintf (stderr, "Failed to connect to X server.\n");
	exit (1);
    }
    screen = DefaultScreenOfDisplay (display);
    visual = DefaultVisualOfScreen (screen);
    window_width = IMAGE_WIDTH;
    window_height = IMAGE_HEIGHT;

    XVisualInfo plate;
    XVisualInfo *matches;
    int count;

    plate.visualid = XVisualIDFromVisual (visual);
    matches = XGetVisualInfo (display, VisualIDMask, &plate, &count);

    if (!count)
    {
	fprintf (stderr, "Your X Window System server is unwell!\n");
	exit (1);
    }
    depth = matches[0].depth;
    if ((depth != 8 && depth != 16 && depth != 24) ||
	(matches[0].c_class != PseudoColor && matches[0].c_class != TrueColor &&
	 matches[0].c_class != GrayScale))
    {
	fprintf (stderr, "\
Snes9x needs an X Window System server set to 8, 16 or 24 bit colour depth\n\
supporting PseudoColor, TrueColor or GrayScale.\n");
	exit (1);
    }
    if (depth >= 16 && !Settings.ForceNoTransparency)
    {
	Settings.Transparency = TRUE;
	Settings.SixteenBit = TRUE;
    }

    pseudo = matches[0].c_class == PseudoColor ||
	     matches[0].c_class == GrayScale;
    grayscale = matches[0].c_class == GrayScale;
    XFree ((char *) matches);

    if (depth != 8 || !pseudo)
    {
	RedShift = ffs (matches[0].red_mask) - 1;
	GreenShift = ffs (matches[0].green_mask) - 1;
	BlueShift = ffs (matches[0].blue_mask) - 1;
	RedSize = matches[0].red_mask >> RedShift;
	GreenSize = matches[0].green_mask >> GreenShift;
	BlueSize = matches[0].blue_mask >> BlueShift;

	if (depth == 16)
	    BuildColorLookupTable ();
    }

    int l = 0;
    long diffs [216];
    int i;

    for (i = 0; i < 6; i++)
    {
	int r = (i * 31) / (6 - 1);
	for (int j = 0; j < 6; j++)
	{
	    int g = (j * 31) / (6 - 1);
	    for (int k = 0; k < 6; k++)
	    { 
		int b = (k * 31) / (6 - 1);
		short hue, saturation, intensity;
		fixed_colours [l].red = r;
		fixed_colours [l].green = g;
		fixed_colours [l].blue = b;
		RGBtoHSI (r * 2114, g * 2114, b * 2114,
			  hue, saturation, intensity);
		diffs [l++] = hue * hue + saturation * saturation +
			      intensity * intensity;
	    }
	}
    }
    {
	int *color_diff = new int [0x10000];
	int diffr, diffg, diffb, maxdiff = 0, won = 0, lost;
	int r, d = 8;
	for (r = 0; r < 32; r++)
	{
	    int cr, g, q;
	  
	    int k = 6 - 1;
	    cr = (r * k) / 31;
	    q  = (r * k) % 31;
	    if (q > d && cr < k) 
		cr++;
	    diffr = abs (cr * k - r);
	    for (g = 0; g < 32; g++)
	    {
		int cg, b;
	      
		k  = 6 - 1;
		cg = (g * k) / 31;
		q  = (g * k) % 31;
		if(q > d && cg < k)
		    cg++;
		diffg = abs (cg * k - g);
		for (b = 0; b < 32; b++) 
		{
		    int cb;
		    int rgb = BUILD_PIXEL(r, g, b);

		    k  = 6 - 1;
		    cb = (b * k) / 31;
		    q  = (b * k) % 31;
		    if (q > d && cb < k)
			cb++;
		    diffb = abs (cb * k - b);
		    palette[rgb] = (cr * 6 + cg) * 6 + cb;
		    color_diff[rgb] = diffr + diffg + diffb;
		    if (color_diff[rgb] > maxdiff)
			maxdiff = color_diff[rgb];
		}
	    }
	}
	while (maxdiff > 0 && l < 256)
	{
	    int newmaxdiff = 0;
	    lost = 0; won++;
	    for(r = 31; r >= 0; r--)
	    {
		int g;
	  
		for (g = 31; g >= 0; g--)
		{
		    int b;
	      
		    for (b = 31; b >= 0; b--) 
		    {
			int rgb = BUILD_PIXEL(r, g, b);

			if (color_diff[rgb] == maxdiff)
			{
			    if (l >= 256)
				lost++;
			    else
			    {
				fixed_colours [l].red = r;
				fixed_colours [l].green = g;
				fixed_colours [l].blue = b;
				palette [rgb] = l;
				l++;
			    }
			    color_diff[rgb] = 0;
			}
			else
			    if (color_diff[rgb] > newmaxdiff)
				newmaxdiff = color_diff[rgb];
			
		    }
		}
	    }
	    maxdiff = newmaxdiff;
	}
	delete color_diff;
    }

    window = XCreateSimpleWindow (display, RootWindowOfScreen (screen),
				  (WidthOfScreen(screen) - window_width) / 2,
				  (HeightOfScreen(screen) - window_height) / 2,
				  window_width, window_height, 0, 0, 0);

    static XColor bg = { 0 };
    static XColor fg = { 0 };
    static char data [8] = { 0x01 };

    Pixmap bitmap = XCreateBitmapFromData (display, window, data, 8, 8);
    point_cursor = XCreatePixmapCursor (display, bitmap, bitmap, &fg, &bg, 0, 0);
    XDefineCursor (display, window, point_cursor);
    cross_hair_cursor = XCreateFontCursor (display, XC_crosshair);
    gc = DefaultGCOfScreen (screen);
    {
        XSizeHints Hints;
	XWMHints WMHints;

	Hints.flags = PSize | PMinSize;
	Hints.min_width = Hints.base_width = SNES_WIDTH;
	Hints.min_height = Hints.base_height = SNES_HEIGHT_EXTENDED;
	WMHints.input = True;
	WMHints.flags = InputHint;
	XSetWMHints (display, window, &WMHints);
	XSetWMNormalHints (display, window, &Hints);
    }
    XSelectInput (display, window, FocusChangeMask | ExposureMask |
		  KeyPressMask | KeyReleaseMask | StructureNotifyMask |
		  ButtonPressMask | ButtonReleaseMask);

    if (pseudo)
    {
	cmap = XCreateColormap (display, window, visual, True);
	XSetWindowColormap (display, window, cmap);
	for (i = 0; i < 256; i++)
	{
	    colors[i].red = colors[i].green = colors[i].blue = 0;
	    colors[i].pixel = i;
	    colors[i].flags = DoRed | DoGreen | DoBlue;
	}
	XStoreColors (display, cmap, colors, 256);
    }
    XMapRaised (display, window);
    XClearWindow (display, window);
    SetupImage ();
}

void SetupImage ()
{
    int image_width = window_width;
    int image_height = window_height;

    if (image_width < IMAGE_WIDTH)
	image_width = IMAGE_WIDTH;
    if (image_height < IMAGE_HEIGHT)
	image_height = IMAGE_HEIGHT;

    if (image)
    {
#ifdef MITSHM
	if(UseSHM)
	{
	    if (GFX.Screen == (uint8 *) SHMInfo.shmaddr)
	    {
		GFX.Screen = NULL;
	    }
	    XShmDetach (display, &SHMInfo);
	    XDestroyImage (image);
	    if (SHMInfo.shmaddr)
		shmdt (SHMInfo.shmaddr);
	    if (SHMInfo.shmid >= 0)
		shmctl (SHMInfo.shmid, IPC_RMID, 0);
	    image = NULL;
        }
	else
#endif
	{
	    if (GFX.Screen == (uint8 *) image->data)
		image->data = NULL;

	    free ((char *) GFX.Screen);
	    GFX.Screen = NULL;
	    XDestroyImage (image);
	    image = NULL;
	}
    }

#ifdef MITSHM
    UseSHM = 1;
    int major, minor;
    Bool shared;
    if (!XShmQueryVersion (display, &major, &minor, &shared) || !shared)
	image = NULL;
    else
	image = XShmCreateImage (display, visual, depth, ZPixmap, NULL, &SHMInfo,
				 image_width, image_height);
    if (!image)
    {
	fprintf (stderr, "XShmCreateImage failed, switching to XPutImage\n");
	UseSHM = 0;
    }
    else
    {
	SHMInfo.shmid = shmget (IPC_PRIVATE, 
				image->bytes_per_line * image->height,
				IPC_CREAT | 0777);
	if (SHMInfo.shmid < 0)
	{
	    fprintf (stderr, "shmget failed, switching to XPutImage\n");
	    XDestroyImage (image);
	    UseSHM = 0;
	}
	else
	{
	    image->data = SHMInfo.shmaddr = (char *) shmat (SHMInfo.shmid, 0, 0);
	    if (!image->data)
	    {
		fprintf (stderr, "shmat failed, switching to XPutImage\n");
		XDestroyImage (image);
		shmctl (SHMInfo.shmid, IPC_RMID, 0);
		UseSHM = 0;
	    }
	    else
	    {
		SHMInfo.readOnly = False;

		XErrorHandler error_handler = XSetErrorHandler (ErrorHandler);
		XShmAttach (display, &SHMInfo);
		XSync (display, False);
		(void) XSetErrorHandler (error_handler);

		// X Error handler might clear UseSHM if XShmAttach failed
		if (!UseSHM)
		{
		    fprintf (stderr, "XShmAttach failed, switching to XPutImage\n");
		    XDestroyImage (image);
		    shmdt (SHMInfo.shmaddr);
		    shmctl (SHMInfo.shmid, IPC_RMID, 0);
		}
	    }
	}
    }

    if (!UseSHM)
    {
#endif
	image = XCreateImage (display, visual, depth, ZPixmap, 0,
			      (char *) NULL, image_width, image_height,
			      BitmapUnit (display), 0);
	image->data = (char *) malloc (image_height *
				       image->bytes_per_line);
#ifdef LSB_FIRST
	image->byte_order = LSBFirst;
#else
	image->byte_order = MSBFirst;
#endif

#ifdef MITSHM
    }
    else
    printf ("Using MIT Shared Memory Pixmap support.\n");
#endif

    if (window_width == IMAGE_WIDTH &&
	window_height == IMAGE_HEIGHT &&
	((Settings.SixteenBit && depth == 16) ||
	 (!Settings.SixteenBit && depth == 8)))
    {
	GFX.Pitch = image->bytes_per_line;
	GFX.Screen = (uint8 *) image->data;
	GFX.SubScreen = (uint8 *) malloc (image->bytes_per_line *
					 image->height);
    }
    else
    {
	GFX.Screen = (uint8 *) malloc (IMAGE_WIDTH * IMAGE_HEIGHT * 
				       (Settings.SixteenBit ? 2 : 1));
	GFX.Pitch = IMAGE_WIDTH * (Settings.SixteenBit ? 2 : 1);
	GFX.SubScreen = (uint8 *) malloc (IMAGE_WIDTH * IMAGE_HEIGHT *
					 (Settings.SixteenBit ? 2 : 1));
    }
}

int ErrorHandler (Display *, XErrorEvent *)
{
    UseSHM = 0;
    return (0);
}

void S9xSetTitle (const char *string)
{
    XStoreName (display, window, string);
    XFlush (display);
}
    
bool8 S9xReadMousePosition (int which1, int &x, int &y, uint32 &buttons)
{
    if (which1 == 0)
    {
	x = mouse_x;
	y = mouse_y;
	buttons = mouse_buttons;
	return (TRUE);
    }
    return (FALSE);
}

bool8 S9xReadSuperScopePosition (int &x, int &y, uint32 &buttons)
{
    x = mouse_x;
    y = mouse_y;
    buttons = (mouse_buttons & 3) | (superscope_turbo << 2) |
	      (superscope_pause << 3);
    return (TRUE);
}

void S9xProcessEvents (bool8 block)
{
    while (block || XPending (display))
    {
	XEvent event;
	XNextEvent (display, &event);
	block = FALSE;

	uint8 byte1 = 0;
	uint8 byte2 = 0;
	uint8 byte3 = 0;
	uint8 byte4 = 0;
	
	switch (event.type)
	{
	case KeyPress:
	case KeyRelease:
	{
	    int key;
	    switch (key = XKeycodeToKeysym (display, event.xkey.keycode, 0))
	    {
	    case XK_k:
	    case XK_Right:	byte2 = 1;	break;
	    case XK_h:
	    case XK_Left:	byte2 = 2;	break;
	    case XK_j:
	    case XK_n:
	    case XK_Down:	byte2 = 4;	break;
	    case XK_u:
	    case XK_Up:		byte2 = 8;	break;

	    case XK_Return:	byte2 = 16;	break; // Start
	    case XK_space:	byte2 = 32;	break; // Select

	    case XK_period:
	    case XK_t:
	    case XK_d:		byte1 = 128;	break; // A

	    case XK_slash:
	    case XK_y:
	    case XK_c:		byte2 = 128;	break; // B

	    case XK_m:
	    case XK_e:
	    case XK_s:		byte1 = 64;	break; // X

	    case XK_comma:
	    case XK_r:
	    case XK_x:		byte2 = 64;	break; // Y

	    case XK_v:
	    case XK_q:
	    case XK_a:		byte1 = 32;	break; // TL

	    case XK_b:
	    case XK_w:
	    case XK_z:		byte1 = 16;	break; // TR

	    case XK_KP_4:	byte4 = 1;	break;
	    case XK_KP_6:	byte4 = 2;	break;
	    case XK_KP_2:	byte4 = 4;	break;
	    case XK_KP_8:	byte4 = 8;	break;
	    
	    case XK_KP_Enter:	byte4 = 16;	break; // Start
	    case XK_KP_Add:     byte4 = 32;	break; // Select
	    case XK_Prior:	byte3 = 128;	break; // A
	    case XK_Next:	byte4 = 128;	break; // B
	    case XK_Home:	byte3 = 64;	break; // X
	    case XK_End:	byte4 = 64;	break; // Y
	    case XK_Insert:	byte3 = 32;	break; // TL
	    case XK_Delete:	byte3 = 16;	break; // TR

	    case XK_Escape:	S9xExit ();	break;

	    case XK_0:
		if (event.type == KeyPress)
		    Settings.DisableHDMA = !Settings.DisableHDMA;
		break;
	    case XK_1:
		if (event.type == KeyPress)
		    PPU.BG_Forced ^= 1;
		break;
	    case XK_2:
		if (event.type == KeyPress)
		    PPU.BG_Forced ^= 2;
		break;
	    case XK_3:
		if (event.type == KeyPress)
		    PPU.BG_Forced ^= 4;
		break;
	    case XK_4:
		if (event.type == KeyPress)
		    PPU.BG_Forced ^= 8;
		break;
	    case XK_5:
		if (event.type == KeyPress)
		    PPU.BG_Forced ^= 16;
		break;
	    case XK_6:
		if (event.type == KeyPress)
		    Settings.SwapJoypads = !Settings.SwapJoypads;
		break;
	    case XK_9:
		if (event.type == KeyPress)
		    if (Settings.SixteenBit)
			Settings.Transparency = !Settings.Transparency;
		break;
	    case XK_8:
		if (event.type == KeyPress)
		    Settings.BGLayering = !Settings.BGLayering;
		break;
	    case XK_7:
		if (event.type == KeyPress)
		    S9xNextController ();
		break;

	    case XK_minus:
		if (event.type == KeyPress)
		{
		    if (Settings.SkipFrames <= 1)
			Settings.SkipFrames = AUTO_FRAMERATE;
		    else
		    if (Settings.SkipFrames != AUTO_FRAMERATE)
			Settings.SkipFrames--;
		}
		break;

	    case XK_equal:
	    case XK_plus:
		if (event.type == KeyPress)
		{
		    if (Settings.SkipFrames == AUTO_FRAMERATE)
			Settings.SkipFrames = 1;
		    else
		    if (Settings.SkipFrames < 10)
			Settings.SkipFrames++;
		}
		break;

	    case XK_BackSpace:
		if (event.type == KeyPress)
		{
		    Settings.DisableGraphicWindows = !Settings.DisableGraphicWindows;
		}
		break;
	    case XK_Scroll_Lock:
	    case XK_Pause:
	    case XK_Break:
		if (event.type == KeyPress)
		    Settings.Paused ^= 1;
		break;

	    case XK_Tab:
		if (event.type == KeyPress)
		    superscope_turbo = !superscope_turbo;
		break;

	    case XK_grave:
		superscope_pause = event.type == KeyPress;
		break;
	    case XK_F1:
#ifdef DEBUGGER
		if (event.type == KeyPress && (event.xkey.state & Mod1Mask))
		{
		    CPU.Flags |= DEBUG_MODE_FLAG;
		    break;
		}
#endif
		// Fall...
	    case XK_F2:
		if (event.type == KeyPress && (event.xkey.state & Mod1Mask))
		{
		    S9xLoadSnapshot (S9xChooseFilename (TRUE));
		    break;
		}
		// Fall...
	    case XK_F3:
		if (event.type == KeyPress && (event.xkey.state & Mod1Mask))
		{
		    Snapshot (S9xChooseFilename (FALSE));
		    break;
		}
		// Fall...
	    case XK_F4:
	    case XK_F5:
	    case XK_F6:
	    case XK_F7:
	    case XK_F8:
	    case XK_F9:
	    case XK_F10:
	    case XK_F11:
	    case XK_F12:
		if (event.type == KeyPress)
		{
		    if (!(event.xkey.state & (ShiftMask | Mod1Mask)))
		    {
			if (key == XK_F11)
			{
			    S9xLoadSnapshot (S9xChooseFilename (TRUE));
			    break;
			}
			else if (key == XK_F12)
			{
			    Snapshot (S9xChooseFilename (FALSE));
			    break;
			}
			char def [PATH_MAX];
			char filename [PATH_MAX];
			char drive [_MAX_DRIVE];
			char dir [_MAX_DIR];
			char ext [_MAX_EXT];

			_splitpath (Memory.ROMFilename, drive, dir, def, ext);
			sprintf (filename, "%s%s%s.%03d",
				 S9xGetSnapshotDirectory (), SLASH_STR, def,
				 key - XK_F1);
			S9xLoadSnapshot (filename);
		    }
		    else
		    if (event.xkey.state & Mod1Mask)
		    {
			if (key >= XK_F4)
			    S9xToggleSoundChannel (key - XK_F4);
		    }
		    else
		    {
			char def [PATH_MAX];
			char filename [PATH_MAX];
			char drive [_MAX_DRIVE];
			char dir [_MAX_DIR];
			char ext [_MAX_EXT];

			_splitpath (Memory.ROMFilename, drive, dir, def, ext);
			sprintf (filename, "%s%s%s.%03d",
				 S9xGetSnapshotDirectory (), SLASH_STR, def,
				 key - XK_F1);
			Snapshot (filename);
		    }
		}
		break;
	    
	    }
	    if (event.type == KeyPress)
	    {
		joypads [0] |= byte1;
		joypads [0] |= (byte2 << 8);
		joypads [1] |= byte3;
		joypads [1] |= (byte4 << 8);
	    }
	    else
	    {
		joypads [0] &= ~byte1;
		joypads [0] &= ~(byte2 << 8);
		joypads [1] &= ~byte3;
		joypads [1] &= ~(byte4 << 8);
	    }
	    break;
	}
	case FocusIn:
	    XAutoRepeatOff (display);
	    XFlush (display);
	    //Settings.Paused &= ~2;
	    break;
	case FocusOut:
	    XAutoRepeatOn (display);
	    XFlush (display);
	    //Settings.Paused |= 2;
	    break;
	case ConfigureNotify:
	    if (window_width != event.xconfigure.width ||
		window_height != event.xconfigure.height)
	    {
		window_width = event.xconfigure.width;
		window_height = event.xconfigure.height;
		IPPU.RenderThisFrame = TRUE;
		IPPU.FrameSkip = Settings.SkipFrames;
		SetupImage ();
	    }
	    break;
#if 0
	case ButtonPress:
	    mouse_buttons = (event.xbutton.state | (1 << event.xbutton.button)) & 0x1f;
	    break;
	case ButtonRelease:
	    mouse_buttons = (event.xbutton.state & ~(1 << event.xbutton.button)) & 0x1f;
	    break;
#endif
	}
    }
}

void S9xPutImage (int width, int height)
{
    bool8 scaled = FALSE;

    mouse_scale_h = 1.0;
    mouse_scale_v = 1.0;

    if ((Settings.SixteenBit && depth != 16) ||
	(!Settings.SixteenBit && (!pseudo || depth != 8)) ||
	window_width != SNES_WIDTH ||
	window_height != SNES_HEIGHT_EXTENDED)
    {
	switch (depth)
	{
	case 8:
	    if (Settings.SixteenBit)
	    {
		// Convert 16bit image to 8bit
		ConvertTo8BitImage (width, height);
	    }
	    else
		Scale8BitImage (width, height);
	    break;

	case 16:
	    if (!Settings.SixteenBit)
		ConvertTo16BitImage (width, height);
	    else
	    {
		if (width != 512 || !(height == 448 || height == 478))
		    Scale16BitImage (width, height);
	    }
	    break;

	case 24:
	    if (Settings.SixteenBit)
	    {
		// XXX: convert 16 to 24 bit
		if (image->bits_per_pixel == 32)
		    ConvertTo24BitImage ();
		else
		    ConvertTo24BitImagePacked ();
	    }
	    else
	    {
		if (image->bits_per_pixel == 32)
		    ConvertTo24BitImage ();
		else
		    ConvertTo24BitImagePacked ();
	    }
	    break;
	}
	scaled = TRUE;
	if (window_width != SNES_WIDTH ||
	    window_height != SNES_HEIGHT_EXTENDED)
	{
	    mouse_scale_h = window_width / (double) width;
	    mouse_scale_v = window_height / (double) height;
	}
    }
#ifdef MITSHM
    if(UseSHM)
	XShmPutImage (display, window, gc, image,
		      0, 0,
		      0, 0, window_width, window_height, False);
    else
#endif
	XPutImage (display, window, gc, image,
		   0, 0, 0, 0,
		   window_width, window_height);
    
    Window root, child;
    int root_x, root_y;
    int x, y;
    unsigned int mask;

    // Use QueryPointer to sync X server and as a side effect also gets
    // current pointer position for SNES mouse emulation.
    XQueryPointer (display, window, &root, &child, &root_x, &root_y,
		   &x, &y, &mask);

    if (IPPU.Controller == SNES_SUPERSCOPE)
    {
	if (!superscope)
	{
	    XDefineCursor (display, window, cross_hair_cursor);
	    superscope = TRUE;
	}
    }
    else
    if (superscope)
    {
	XDefineCursor (display, window, point_cursor);
	superscope = FALSE;
    }
    if (x >= 0 && y >= 0 && x < width && y < height)
    {
	// XXX: scale if screen is scaled.
	mouse_x = x;
	mouse_y = y;
	if (mask & Mod1Mask)
	{
	    IPPU.PrevMouseX [0] = IPPU.PrevMouseX [1] = mouse_x;
	    IPPU.PrevMouseY [0] = IPPU.PrevMouseY [1] = mouse_y;
	    if (!mod1_pressed)
	    {
		mod1_pressed = TRUE;
		XDefineCursor (display, window, cross_hair_cursor);
	    }
	}
	else
	if (mod1_pressed)
	{
	    mod1_pressed = FALSE;
	    if (!superscope)
		XDefineCursor (display, window, point_cursor);
	}
	mouse_buttons = ((mask & 0x100) >> 8) | ((mask & 0x200) >> 7) |
			((mask & 0x400) >> 9) | ((mask & 0x800) >> 8);
		    
    }
}

void S9xSetPalette ()
{
    int i;

    if (grayscale)
    {
	uint16 Brightness = IPPU.MaxBrightness;
	    
	for (i = 0; i < 256; i++)
	{
	    colors[i].flags = DoRed | DoGreen | DoBlue;
	    colors[i].red = colors[i].green = colors[i].blue = 
		(uint16)(((((PPU.CGDATA[i] >> 0) & 0x1F) * Brightness * 50) +
		        (((PPU.CGDATA[i] >> 5) & 0x1F) * Brightness * 69) +
			(((PPU.CGDATA[i] >> 10) & 0x1F) * Brightness * 21)) * 1.40935);
	}
	XStoreColors (display, cmap, colors, 256);
    }
    else
    if (pseudo)
    {
	if (Settings.SixteenBit)
	{
	    for (i = 0; i < 256; i++)
	    {
		colors[i].flags = DoRed | DoGreen | DoBlue;
		colors[i].red = fixed_colours[i].red << 11;
		colors[i].green = fixed_colours[i].green << 11;
		colors[i].blue = fixed_colours[i].blue << 11;
	    }
	}
	else
	{
	    uint16 Brightness = (IPPU.MaxBrightness) * 140;
	    
	    for (i = 0; i < 256; i++)
	    {
		colors[i].flags = DoRed | DoGreen | DoBlue;
		colors[i].red = ((PPU.CGDATA[i] >> 0) & 0x1F) * Brightness;
		colors[i].green = ((PPU.CGDATA[i] >> 5) & 0x1F) * Brightness;
		colors[i].blue = ((PPU.CGDATA[i] >> 10) & 0x1F) * Brightness;
	    }
	}
	XStoreColors (display, cmap, colors, 256);
    }
}

const char *S9xSelectFilename (const char *def, const char *dir1,
			    const char *ext1, const char *title)
{
    static char path [PATH_MAX];
    char buffer [PATH_MAX];
    
    printf ("\n%s (default: %s): ", title, def);
    fflush (stdout);
    if (fgets (buffer, sizeof (buffer) - 1, stdin))
    {
	char *p = buffer;
	while (isspace (*p))
	    p++;
	if (!*p)
	{
	    strcpy (buffer, def);
	    p = buffer;
	}

	char *q = strrchr (p, '\n');
	if (q)
	    *q = 0;

	char fname [PATH_MAX];
	char drive [_MAX_DRIVE];
	char dir [_MAX_DIR];
	char ext [_MAX_EXT];

	_splitpath (p, drive, dir, fname, ext);
	_makepath (path, drive, *dir ? dir : dir1, fname, *ext ? ext : ext1);
	return (path);
    }

    return (NULL);
}

void Scale8BitImage (int width, int height)
{
    register uint32 x_error = 0;
    register uint32 x_fraction;
    uint32 y_error = 0;
    uint32 y_fraction;
    int yy = height - 1;
    
    x_fraction = (width * 0x10000) / window_width;
    y_fraction = (height * 0x10000) / window_height;
    
    for (int y = window_height - 1; y >= 0; y--)
    {
	register uint8 *d = (uint8 *) image->data + y * image->bytes_per_line +
			   window_width - 1;
	register uint8 *s = GFX.Screen + yy * GFX.Pitch + width - 1;
	y_error += y_fraction;
	while (y_error >= 0x10000)
	{
	    yy--;
	    y_error -= 0x10000;
	}
	for (register int x = window_width - 1; x >= 0; x--)
	{
	    *d-- = *s;
	    x_error += x_fraction;

	    while (x_error >= 0x10000)
	    {
		s--;
		x_error -= 0x10000;
	    }
	}
    }
}

void Scale16BitImage (int width, int height)
{
    register uint32 x_error = 0;
    register uint32 x_fraction;
    uint32 y_error = 0;
    uint32 y_fraction;
    int yy = height - 1;
    
    x_fraction = (width * 0x10000) / window_width;
    y_fraction = (height * 0x10000) / window_height;
    
    for (int y = window_height - 1; y >= 0; y--)
    {
	register uint16 *d = (uint16 *) (image->data + y * image->bytes_per_line) +
				    window_width - 1;
	register uint16 *s = (uint16 *) (GFX.Screen + yy * GFX.Pitch) + width - 1;
	y_error += y_fraction;
	while (y_error >= 0x10000)
	{
	    yy--;
	    y_error -= 0x10000;
	}
	for (register int x = window_width - 1; x >= 0; x--)
	{
	    *d-- = *s;
	    x_error += x_fraction;

	    while (x_error >= 0x10000)
	    {
		s--;
		x_error -= 0x10000;
	    }
	}
    }
}

void ConvertTo24BitImage ()
{
    uint32 brightness = IPPU.MaxBrightness >> 1;

    if (window_width == SNES_WIDTH &&
	window_height == SNES_HEIGHT_EXTENDED)
    {
	// Convert
	for (register y = 0; y < SNES_HEIGHT_EXTENDED; y++)
	{
	    register uint32 *d = (uint32 *) (image->data +
					   y * image->bytes_per_line);
	    register uint8 *s = GFX.Screen + y * GFX.Pitch;

	    for (register x = 0; x < SNES_WIDTH; x++)
	    {
		uint32 pixel = PPU.CGDATA [*s++];
		*d++ = (((pixel & 0x1f) * brightness) << RedShift) |
		       ((((pixel >> 5) & 0x1f) * brightness) << GreenShift) |
		       ((((pixel >> 10) & 0x1f) * brightness) << BlueShift);
	    }
	}
    }
    else
    {
	// Scale and convert
	register uint32 x_error = 0;
	register uint32 x_fraction;
	uint32 y_error = 0;
	uint32 y_fraction;
	int yy = 0;
	
	x_fraction = (SNES_WIDTH * 0x10000) / window_width;
	y_fraction = (SNES_HEIGHT_EXTENDED * 0x10000) / window_height;
	
	for (int y = 0; y < window_height; y++)
	{
	    register uint32 *d = (uint32 *) (image->data +
					   y * image->bytes_per_line);
	    register uint8 *s = GFX.Screen + yy * GFX.Pitch;
	    y_error += y_fraction;
	    while (y_error >= 0x10000)
	    {
		yy++;
		y_error -= 0x10000;
	    }
	    for (register int x = 0; x < window_width; x++)
	    {
		uint32 pixel = PPU.CGDATA [*s];
		*d++ = (((pixel & 0x1f) * brightness) << RedShift) |
		       ((((pixel >> 5) & 0x1f) * brightness) << GreenShift) |
		       ((((pixel >> 10) & 0x1f) * brightness) << BlueShift);
		       
		x_error += x_fraction;
		while (x_error >= 0x10000)
		{
		    s++;
		    x_error -= 0x10000;
		}
	    }
	}
    }
}

void ConvertTo24BitImagePacked ()
{
    uint32 brightness = IPPU.MaxBrightness >> 1;
    uint8 levels [32];

    for (int l = 0; l < 32; l++)
	levels [l] = l * brightness;
	
    if (window_width == SNES_WIDTH &&
	window_height == SNES_HEIGHT_EXTENDED)
    {
	// Convert
	for (register y = 0; y < SNES_HEIGHT_EXTENDED; y++)
	{
	    register uint8 *d = (uint8 *) (image->data +
					 y * image->bytes_per_line);
	    register uint8 *s = GFX.Screen + y * GFX.Pitch;

#ifdef LSB_FIRST
	    if (RedShift < BlueShift)
#else	    
	    if (RedShift > BlueShift)
#endif
	    {
		// Order is RGB
		for (register x = 0; x < SNES_WIDTH; x++)
		{
		    uint16 pixel = PPU.CGDATA [*s++];
		    *d++ = levels [(pixel & 0x1f)];
		    *d++ = levels [((pixel >> 5) & 0x1f)];
		    *d++ = levels [((pixel >> 10) & 0x1f)];
		}
	    }
	    else
	    {
		// Order is BGR
		for (register x = 0; x < SNES_WIDTH; x++)
		{
		    uint16 pixel = PPU.CGDATA [*s++];
		    *d++ = levels [((pixel >> 10) & 0x1f)];
		    *d++ = levels [((pixel >> 5) & 0x1f)];
		    *d++ = levels [(pixel & 0x1f)];
		}
	    }
	}
    }
    else
    {
	// Scale and convert
	register uint32 x_error = 0;
	register uint32 x_fraction;
	uint32 y_error = 0;
	uint32 y_fraction;
	int yy = 0;
	
	x_fraction = (SNES_WIDTH * 0x10000) / window_width;
	y_fraction = (SNES_HEIGHT_EXTENDED * 0x10000) / window_height;
	
	for (int y = 0; y < window_height; y++)
	{
	    register uint8 *d = (uint8 *) (image->data +
					 y * image->bytes_per_line);
	    register uint8 *s = GFX.Screen + yy * GFX.Pitch;
	    y_error += y_fraction;
	    while (y_error >= 0x10000)
	    {
		yy++;
		y_error -= 0x10000;
	    }
#ifdef LSB_FIRST
	    if (RedShift < BlueShift)
#else
	    if (RedShift > BlueShift)
#endif
	    {
		// Order is RGB
		for (register int x = 0; x < window_width; x++)
		{
		    uint16 pixel = PPU.CGDATA [*s];
		    *d++ = levels [(pixel & 0x1f)];
		    *d++ = levels [((pixel >> 5) & 0x1f)];
		    *d++ = levels [((pixel >> 10) & 0x1f)];
		       
		    x_error += x_fraction;
		    while (x_error >= 0x10000)
		    {
			s++;
			x_error -= 0x10000;
		    }
		}
	    }
	    else
	    {
		// Order is BGR
		for (register int x = 0; x < window_width; x++)
		{
		    uint16 pixel = PPU.CGDATA [*s];
		    *d++ = levels [((pixel >> 10) & 0x1f)];
		    *d++ = levels [((pixel >> 5) & 0x1f)];
		    *d++ = levels [(pixel & 0x1f)];
		       
		    x_error += x_fraction;
		    while (x_error >= 0x10000)
		    {
			s++;
			x_error -= 0x10000;
		    }
		}
	    }
	}
    }
}

void ConvertTo8BitImage (int width, int height)
{
    if (window_width == SNES_WIDTH &&
	window_height == SNES_HEIGHT_EXTENDED)
    {
	// Convert
	for (register y = 0; y < SNES_HEIGHT_EXTENDED; y++)
	{
	    register uint8 *d = (uint8 *) image->data + y * image->bytes_per_line;
	    register uint16 *s = (uint16 *) (GFX.Screen + y * GFX.RealPitch);

	    for (register x = 0; x < SNES_WIDTH; x++)
		*d++ = palette [*s++];
	}
    }
    else
    {
	// Scale and convert
	register uint32 x_error = 0;
	register uint32 x_fraction;
	uint32 y_error = 0;
	uint32 y_fraction;
	int yy = 0;
	
	x_fraction = (width * 0x10000) / window_width;
	y_fraction = (height * 0x10000) / window_height;
	
	for (int y = 0; y < window_height; y++)
	{
	    register uint8 *d = (uint8 *) image->data + y * image->bytes_per_line;
	    register uint16 *s = (uint16 *) (GFX.Screen + yy * GFX.Pitch);
	    y_error += y_fraction;
	    while (y_error >= 0x10000)
	    {
		yy++;
		y_error -= 0x10000;
	    }
	    for (register int x = 0; x < window_width; x++)
	    {
		*d++ = palette [*s];
		       
		x_error += x_fraction;
		while (x_error >= 0x10000)
		{
		    s++;
		    x_error -= 0x10000;
		}
	    }
	}
    }
}

void ConvertTo16BitImage (int width, int height)
{
    uint32 brightness = (IPPU.MaxBrightness >> 1) << 15;
    if (window_width == SNES_WIDTH &&
	window_height == SNES_HEIGHT_EXTENDED)
    {
	// Convert
	for (register y = 0; y < SNES_HEIGHT_EXTENDED; y++)
	{
	    register uint16 *d = (uint16 *) (image->data +
					  y * image->bytes_per_line);
	    register uint8 *s = GFX.Screen + y * GFX.Pitch;

	    for (register x = 0; x < SNES_WIDTH; x++)
		*d++ = Table8to16 [PPU.CGDATA [*s++] + brightness];
	}
    }
    else
    {
	// Scale and convert
	register uint32 x_error = 0;
	register uint32 x_fraction;
	uint32 y_error = 0;
	uint32 y_fraction;
	int yy = 0;
	
	x_fraction = (width * 0x10000) / window_width;
	y_fraction = (height * 0x10000) / window_height;
	
	for (int y = 0; y < window_height; y++)
	{
	    register uint16 *d = (uint16 *) (image->data +
					   y * image->bytes_per_line);
	    register uint8 *s = GFX.Screen + yy * GFX.Pitch;
	    y_error += y_fraction;
	    while (y_error >= 0x10000)
	    {
		yy++;
		y_error -= 0x10000;
	    }
	    for (register int x = 0; x < window_width; x++)
	    {
		*d++ = Table8to16 [PPU.CGDATA [*s] + brightness];
		       
		x_error += x_fraction;
		while (x_error >= 0x10000)
		{
		    s++;
		    x_error -= 0x10000;
		}
	    }
	}
    }
}

void BuildColorLookupTable ()
{
    Table8to16 = (uint16 *) malloc (0x8000 * 8 * 2);
    uint16 scale_red = 0;
    uint16 scale_green = 0;
    uint16 scale_blue = 0;
    
    while (((31 * 7) >> scale_red) > RedSize)
	scale_red++;
	
    while (((31 * 7) >> scale_green) > GreenSize)
	scale_green++;
	
    while (((31 * 7) >> scale_blue) > BlueSize)
	scale_blue++;
	
    for (uint16 brightness = 0; brightness < 8; brightness++)
    {
	int b = brightness + 1;
	if (b > 7)
	    b = 7;
	uint32 bb = brightness << 15;
	
	for (uint16 pixel = 0; pixel < 0x8000; pixel++)
	{
	    uint16 red = pixel & 0x1f;
	    uint16 green = (pixel >> 5) & 0x1f;
	    uint16 blue = (pixel >> 10) & 0x1f;
	    Table8to16 [pixel + bb] =
		    (((red * b) >> scale_red) << RedShift) |
		    (((green * b) >> scale_green) << GreenShift) |
		    (((blue* b) >> scale_blue) << BlueShift);
	}
    }
}

void S9xTextMode ()
{
}

void S9xGraphicsMode ()
{
}

void S9xParseDisplayArg (char **, int &, int)
{
    S9xUsage ();
}

void S9xExtraUsage ()
{
}

int S9xMinCommandLineArgs ()
{
    return (2);
}

void S9xMessage (int type, int number, const char *message)
{
    fprintf (stdout, "%s\n", message);
}

#ifdef __linux
extern "C" {
#include <sys/ioctl.h>

// Select seems to be broken in 2.0.x kernels - if a signal interrupts a
// select system call with a zero timeout, the select call is restarted but
// with an infinite timeout! The call will block until data arrives on the
// selected fd(s).

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
	    struct timeval *timeval)
{
    int ret = 0;
    if (readfds)
    {
	if (readfds->fds_bits [0])
	{
	    int i = ffs (readfds->fds_bits [0]) - 1;
	    int arg = 0;
	    if (ioctl (i, FIONREAD, &arg) == 0 && arg)
		ret = 1;
	}
	else
	    readfds->fds_bits [0] = 0;
    }
    if (writefds && writefds->fds_bits [0])
	ret = 1;
    return (ret);
}
}
#endif
