/*
 * Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
 *
 * (c) Copyright 1996, 1997, 1998 Gary Henderson (gary@daniver.demon.co.uk) and
 *                                Jerremy Koot (jkoot@snes9x.com)
 *
 * Super FX C emulator code 
 * (c) Copyright 1997, 1998 Ivar (Ivar@snes9x.com) and
 *                          Gary Henderson.
 * Super FX assembler emulator code (c) Copyright 1998 zsKnight and _Demo_.
 *
 * DSP1 emulator code (c) Copyright 1998 Ivar, _Demo_ and Gary Henderson.
 * DOS port code contains the works of other authors. See headers in
 * individual files.
 *
 * Snes9x homepage: www.snes9x.com
 *
 * 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 <sys/stat.h>
#include <fcntl.h>
#include <unistd.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 "soundux.h"

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

#ifdef USE_DGA_EXTENSION
#include <X11/extensions/xf86dga.h>

void CreateFullScreenWindow ();
void S9xSwitchToFullScreen (bool8 enable);
static Window fs_window = 0;
static scale = TRUE;

#ifdef USE_VIDMODE_EXTENSION
#if defined (__cplusplus) || defined (c_plusplus)
#define private c_private
#include <X11/extensions/xf86vmode.h>
#endif

#define ALL_DEVICE_EVENTS 0
#endif

typedef struct 
{
    bool8 full_screen_available;
    bool8 is_full_screen;
    bool8 scale;
    char *vram;
    int line_width;
    int bank_size;
    int size;
    int window_width;
    int window_height;
    int saved_window_width;
    int saved_window_height;
    bool8 SavedImageNeedsScaling;

#ifdef USE_VIDMODE_EXTENSION
    bool8 switch_video_mode;
    XF86VidModeModeInfo **all_modes;
    int num_modes;
    XF86VidModeModeInfo orig;
    XF86VidModeModeInfo *best;
    bool8 no_mode_switch;
#endif
} XF86Data;

static XF86Data xf86data;
#endif

static Display *display = NULL;
static Screen *screen = NULL;
static int screen_num = 0;
static Visual *visual = NULL;
static Window window = 0;
static GC gc = 0;
static XImage *image = NULL;
static uint8 *image_data = NULL;
static int bytes_per_line = 0;
static int bytes_per_pixel = 0;
static uint8 *OutputScreen = NULL;
static uint32 OutputPitch = 0;
static uint8 *InterpolateScreen = NULL;
static uint8 *DeltaScreen = NULL;
static Colormap cmap = 0;
static Cursor point_cursor = 0;
static Cursor cross_hair_cursor = 0;
static XColor colors [256] = { {0} };
static bool8 pseudo = TRUE;
static bool8 grayscale = FALSE;
static bool8 interpolate = FALSE;
static bool8 ImageNeedsScaling = FALSE;
static int depth = 8;
static int window_width = SNES_WIDTH;
static int window_height = SNES_HEIGHT_EXTENDED;
static int last_snes_width = 0;
static int last_snes_height = 0;
static uint32 RedShift = 0;
static uint32 BlueShift = 0;
static uint32 GreenShift = 0;
static uint32 RedSize = 0;
static uint32 GreenSize = 0;
static uint32 BlueSize = 0;
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;

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

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 Scale8 (int width, int height);
void Scale16 (int width, int height);
void Convert8To16 (int width, int height);
void Convert16To8 (int width, int height);
void Convert8To24 (int width, int height);
void Convert8To24Packed (int width, int height);
void Convert16To24 (int width, int height);
void Convert16To24Packed (int width, int height);
void SetupImage ();
int ErrorHandler (Display *, XErrorEvent *);
void TVMode (int width, int height);
void S9xDisplayStateChange (const char *str, bool8 on);

void S9xDeinitDisplay ()
{
    S9xTextMode ();
    if (InterpolateScreen &&
	(!image || InterpolateScreen != (uint8 *) image->data))
    {
	free ((char *) InterpolateScreen);
    }
    InterpolateScreen = NULL;

    if (DeltaScreen)
	free ((char *) DeltaScreen);
    DeltaScreen = NULL;

    if (GFX.SubScreen)
	free ((char *) GFX.SubScreen);

    GFX.SubScreen = NULL;

    if (image)
    {
#ifdef MITSHM
	if(UseSHM)
	{
	    if (GFX.Screen == (uint8 *) SHMInfo.shmaddr)
		GFX.Screen = NULL;
	    else
		free ((char *) GFX.Screen);

	    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;
	}
    }
    XSync (display, False);
    XCloseDisplay (display);
}

void S9xInitDisplay (int, char **)
{
    if (!(display = XOpenDisplay (NULL)))
    {
	fprintf (stderr, "Failed to connect to X server.\n");
	exit (1);
    }
    screen = DefaultScreenOfDisplay (display);
    screen_num = XScreenNumberOfScreen (screen);
    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 != 15 && 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, 15, 16, 24 or 32-bit colour depth\n\
supporting PseudoColor, TrueColor or GrayScale.\n");
	exit (1);
    }
    if (depth >= 15 && !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 && GreenSize == 63)
	    GreenShift++;
#ifdef GFX_MULTI_FORMAT
	switch (depth)
	{
	default:
	case 24:
	case 8:
	    S9xSetRenderPixelFormat (RGB565);
	    break;
	case 16:
	    if (RedSize != GreenSize || BlueSize != GreenSize)
	    {
		// 565 format
		if (GreenShift > BlueShift && GreenShift > RedShift)
		    S9xSetRenderPixelFormat (GBR565);
		else
		if (RedShift > BlueShift)
		    S9xSetRenderPixelFormat (RGB565);
		else
		    S9xSetRenderPixelFormat (BGR565);
		break;
	    }
	    /* FALL ... */
	case 15:
	    if (GreenShift > BlueShift && GreenShift > RedShift)
		S9xSetRenderPixelFormat (GBR555);
	    else
	    if (RedShift > BlueShift)
		S9xSetRenderPixelFormat (RGB555);
	    else
		S9xSetRenderPixelFormat (BGR555);
	    break;
	}
#endif	
    }

    int l = 0;
    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);

		fixed_colours [l].red = r;
		fixed_colours [l].green = g;
		fixed_colours [l++].blue = b;
	    }
	}
    }

    int *color_diff = new int [0x10000];
    int diffr, diffg, diffb, maxdiff = 0, won = 0, lost;
    int r, d = 8;
    for (r = 0; r <= MAX_RED; r++)
    {
	int cr, g, q;
      
	int k = 6 - 1;
	cr = (r * k) / MAX_RED;
	q  = (r * k) % MAX_RED;
	if (q > d && cr < k) 
	    cr++;
	diffr = abs (cr * k - r);
	for (g = 0; g <= MAX_GREEN; g++)
	{
	    int cg, b;
	  
	    k  = 6 - 1;
	    cg = (g * k) / MAX_GREEN;
	    q  = (g * k) % MAX_GREEN;
	    if(q > d && cg < k)
		cg++;
	    diffg = abs (cg * k - g);
	    for (b = 0; b <= MAX_BLUE; b++) 
	    {
		int cb;
		int rgb = BUILD_PIXEL2(r, g, b);

		k  = 6 - 1;
		cb = (b * k) / MAX_BLUE;
		q  = (b * k) % MAX_BLUE;
		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 = MAX_RED; r >= 0; r--)
	{
	    int g;
      
	    for (g = MAX_GREEN; g >= 0; g--)
	    {
		int b;
	  
		for (b = MAX_BLUE; b >= 0; b--) 
		{
		    int rgb = BUILD_PIXEL2(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);

#ifdef USE_DGA_EXTENSION
    CreateFullScreenWindow ();
#endif

    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);
#ifdef USE_DGA_EXTENSION
    if (xf86data.full_screen_available)
	XDefineCursor (display, fs_window, point_cursor);
#endif

    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);
#ifdef USE_DGA_EXTENSION
    if (xf86data.full_screen_available)
	XSelectInput (display, fs_window, FocusChangeMask | ExposureMask |
		      KeyPressMask | KeyReleaseMask | StructureNotifyMask |
		      ButtonPressMask | ButtonReleaseMask);
#endif

    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 ();

    switch (depth)
    {
    case 8:
	bytes_per_pixel = 1;
	break;

    case 15:
    case 16:
	bytes_per_pixel = 2;
	break;

    case 24:
	if (image->bits_per_pixel == 24)
	    bytes_per_pixel = 3;
	else
	    bytes_per_pixel = 4;
	break;

    case 32:
	bytes_per_pixel = 4;
	break;
    }
}

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 (interpolate)
    {
	if (image_width < 514)
	    image_width = 514;
	if (image_height < 239 * 2)
	    image_height = 239 * 2;
	ImageNeedsScaling = window_width != 512 || 
			    window_height != 239 * 2;
    }
    else
    {
	ImageNeedsScaling = window_width != IMAGE_WIDTH ||
			    window_height != IMAGE_HEIGHT ||
			    Settings.SupportHiRes;
    }

    if (InterpolateScreen &&
	(!image || InterpolateScreen != (uint8 *) image->data))
    {
	free ((char *) InterpolateScreen);
    }
    InterpolateScreen = NULL;

    if (DeltaScreen)
	free ((char *) DeltaScreen);
    DeltaScreen = NULL;

    if (GFX.SubScreen)
	free ((char *) GFX.SubScreen);

    GFX.SubScreen = NULL;

    if (image)
    {
#ifdef MITSHM
	if(UseSHM)
	{
	    if (GFX.Screen == (uint8 *) SHMInfo.shmaddr)
		GFX.Screen = NULL;
	    else
		free ((char *) GFX.Screen);

	    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
    }
#endif

    if (!Settings.SixteenBit)
    {
	if (ImageNeedsScaling || depth != 8)
	{
	    GFX.Screen = (uint8 *) malloc (IMAGE_WIDTH * IMAGE_HEIGHT);
	    GFX.Pitch = IMAGE_WIDTH;
	}
	else
	{
	    GFX.Screen = (uint8 *) image->data;
	    GFX.Pitch = image->bytes_per_line;
	}
	GFX.SubScreen = NULL;
    }
    else
    if (depth == 8)
    {
	GFX.Screen = (uint8 *) malloc ((IMAGE_WIDTH + 2) * IMAGE_HEIGHT * 2);
	GFX.Pitch = (IMAGE_WIDTH + 2) * 2;
	GFX.SubScreen = (uint8 *) malloc ((IMAGE_WIDTH + 2) * IMAGE_HEIGHT * 2);
	if (interpolate)
	{
	    InterpolateScreen = (uint8 *) malloc (514 * 478 * 2);
	    DeltaScreen = (uint8 *) malloc ((IMAGE_WIDTH + 2) * IMAGE_HEIGHT * 2);
	}
    }
    else
    {
	if ((depth != 15 && depth != 16) || interpolate)
	{
	    GFX.Screen = (uint8 *) malloc ((IMAGE_WIDTH + 2) * IMAGE_HEIGHT * 2);
	    GFX.Pitch = (IMAGE_WIDTH + 2) * 2;
	    if (interpolate)
	    {
		if (ImageNeedsScaling || (depth != 15 && depth != 16)
#ifdef USE_DGA_EXTENSION
		    || xf86data.scale
#endif
)
		    InterpolateScreen = (uint8 *) malloc (514 * 478 * 2);
		DeltaScreen = (uint8 *) malloc ((IMAGE_WIDTH + 2) * IMAGE_HEIGHT * 2);
	    }
	}
	else
	{
	    GFX.Screen = (uint8 *) image->data;
	    GFX.Pitch = image->bytes_per_line;
	}
	GFX.SubScreen = (uint8 *) malloc (GFX.Pitch * IMAGE_HEIGHT);
    }
    GFX.Delta = (GFX.SubScreen - GFX.Screen) >> 1;
    ZeroMemory (GFX.Screen, GFX.Pitch * IMAGE_HEIGHT);
    if ((uint8 *) image->data != GFX.Screen)
	ZeroMemory (image->data, image->bytes_per_line * image->height);
    if (DeltaScreen)
	memset (DeltaScreen, 0xff, GFX.Pitch * IMAGE_HEIGHT);
    if (InterpolateScreen)
	ZeroMemory (InterpolateScreen, 514 * 478 * 2);
    image_data = (uint8 *) image->data;
    bytes_per_line = image->bytes_per_line;
}

int ErrorHandler (Display *, XErrorEvent *)
{
#ifdef MITSHM
    UseSHM = 0;
#endif
    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:
		if (event.type == KeyPress)
		{
#ifdef USE_DGA_EXTENSION
		    if (xf86data.is_full_screen)
		    {
			S9xSwitchToFullScreen (FALSE);
			break;
		    }
#endif
		    S9xExit ();
		}
		break;

	    case XK_0:
		if (event.type == KeyPress)
		{
		    Settings.DisableHDMA = !Settings.DisableHDMA;
		    S9xDisplayStateChange ("HDMA emulation", !Settings.DisableHDMA);
		}
		break;
	    case XK_1:
		if (event.type == KeyPress)
		{
		    PPU.BG_Forced ^= 1;
		    S9xDisplayStateChange ("BG#0", !(PPU.BG_Forced & 1));
		}
		break;
	    case XK_2:
		if (event.type == KeyPress)
		{
		    PPU.BG_Forced ^= 2;
		    S9xDisplayStateChange ("BG#1", !(PPU.BG_Forced & 2));
		}
		break;
	    case XK_3:
		if (event.type == KeyPress)
		{
		    PPU.BG_Forced ^= 4;
		    S9xDisplayStateChange ("BG#2", !(PPU.BG_Forced & 4));
		}
		break;
	    case XK_4:
		if (event.type == KeyPress)
		{
		    PPU.BG_Forced ^= 8;
		    S9xDisplayStateChange ("BG#3", !(PPU.BG_Forced & 8));
		}
		break;
	    case XK_5:
		if (event.type == KeyPress)
		{
		    PPU.BG_Forced ^= 16;
		    S9xDisplayStateChange ("Sprites", !(PPU.BG_Forced & 16));
		}
		break;
	    case XK_6:
		if (event.type == KeyPress)
		{
		    Settings.SwapJoypads = !Settings.SwapJoypads;
		    S9xDisplayStateChange ("Joypad swapping", Settings.SwapJoypads);
		}
		break;
	    case XK_7:
		if (event.type == KeyPress)
		{
		    static char *controllers [] = {
			"Multiplayer 5 on #0", "Joypad on #0", "Mouse on #1",
			"Mouse on #0", "Superscope on #1"
		    };
		    S9xNextController ();
		    S9xSetInfoString (controllers [IPPU.Controller]);
		}
		break;

	    case XK_8:
		if (event.type == KeyPress)
		{
		    Settings.BGLayering = !Settings.BGLayering;
		    S9xDisplayStateChange ("Background layering hack", 
					   Settings.BGLayering);
		}
		break;
	    case XK_9:
		if (event.type == KeyPress)
		    if (Settings.SixteenBit)
		    {
			Settings.Transparency = !Settings.Transparency;
			S9xDisplayStateChange ("Transparency effects", 
					       Settings.Transparency);
		    }
		break;
	    case XK_minus:
		if (event.type == KeyPress)
		{
		    if (event.xkey.state & ShiftMask)
		    {
			// Decrease emulated frame time by 1ms
			if (Settings.FrameTime >= 1000)
			    Settings.FrameTime -= 1000;

			sprintf (InfoString, "Emulated frame time: %dms",
				 Settings.FrameTime / 1000);
			S9xSetInfoString (InfoString);
		    }
		    else
		    {
			if (Settings.SkipFrames <= 1)
			    Settings.SkipFrames = AUTO_FRAMERATE;
			else
			    if (Settings.SkipFrames != AUTO_FRAMERATE)
				Settings.SkipFrames--;

			if (Settings.SkipFrames == AUTO_FRAMERATE)
			    S9xSetInfoString ("Auto frame skip");
			else
			{
			    sprintf (InfoString, "Frame skip: %d",
				     Settings.SkipFrames);
			    S9xSetInfoString (InfoString);
			}
		    }
		}
		break;

	    case XK_equal:
	    case XK_plus:
		if (event.type == KeyPress)
		{
		    if (event.xkey.state & ShiftMask)
		    {
			// Increase emulated frame time by 1ms
			Settings.FrameTime += 1000;
			sprintf (InfoString, "Emulated frame time: %dms",
				 Settings.FrameTime / 1000);
			S9xSetInfoString (InfoString);
		    }
		    else
		    {
			if (Settings.SkipFrames == AUTO_FRAMERATE)
			    Settings.SkipFrames = 1;
			else
			if (Settings.SkipFrames < 10)
			    Settings.SkipFrames++;

			if (Settings.SkipFrames == AUTO_FRAMERATE)
			    S9xSetInfoString ("Auto frame skip");
			else
			{
			    sprintf (InfoString, "Frame skip: %d",
				     Settings.SkipFrames);
			    S9xSetInfoString (InfoString);
			}
		    }
		}
		break;

	    case XK_BackSpace:
		if (event.type == KeyPress)
		{
		    Settings.DisableGraphicWindows = !Settings.DisableGraphicWindows;
		    S9xDisplayStateChange ("Graphic clip windows",
					   !Settings.DisableGraphicWindows);
		}
		break;
	    case XK_bracketleft:
		if (event.type == KeyPress)
		{
		    Settings.InterpolatedSound ^= 1;
		    S9xDisplayStateChange ("Interpolated sound",
					   Settings.InterpolatedSound);
		}
		break;
	    case XK_bracketright:
		if (event.type == KeyPress)
		{
		    Settings.SoundSync ^= 1;
		    S9xDisplayStateChange ("Synchronised sound",
					   Settings.SoundSync);
		}
		break;
	    case XK_Scroll_Lock:
	    case XK_Pause:
	    case XK_Break:
		if (event.type == KeyPress)
		{
		    Settings.Paused ^= 1;
		    S9xDisplayStateChange ("Pause", Settings.Paused);
		}
		break;

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

	    case XK_grave:
		superscope_pause = event.type == KeyPress;
		break;
#ifdef USE_DGA_EXTENSION
	    case XK_f:
		if (!(event.xkey.state & Mod1Mask))
		    break;
		/* FALL */
	    case XK_Print:
		if (event.type == KeyPress)
		    S9xSwitchToFullScreen (!xf86data.is_full_screen);
		break;
#endif
	    case XK_F1:
#ifdef DEBUGGER
		if (event.type == KeyPress && 
		    (event.xkey.state & (Mod1Mask | ControlMask)))
		{
		    CPU.Flags |= DEBUG_MODE_FLAG;
		    break;
		}
#endif
		// Fall...
	    case XK_F2:
		if (event.type == KeyPress && 
		    (event.xkey.state & (Mod1Mask | ControlMask)))
		{
		    S9xLoadSnapshot (S9xChooseFilename (TRUE));
		    break;
		}
		// Fall...
	    case XK_F3:
		if (event.type == KeyPress && 
		    (event.xkey.state & (Mod1Mask | ControlMask)))
		{
		    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 | ControlMask)))
		    {
			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);
			sprintf (InfoString, "%s.%03d loaded", def, key - XK_F1);
			S9xSetInfoString (InfoString);
		    }
		    else
		    if (event.xkey.state & (Mod1Mask | ControlMask))
		    {
			if (key >= XK_F4)
			    S9xToggleSoundChannel (key - XK_F4);
			if (key == XK_F12)
			    S9xSetInfoString ("All sound channels on");
			else
			{
			    sprintf (InfoString, "Sound channel %d", key - XK_F4,
				     (so.sound_switch & (1 << (key - XK_F4))));
			    S9xSetInfoString (InfoString);
			}
		    }
		    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);
			sprintf (InfoString, "%s.%03d saved", def, key - XK_F1);
			S9xSetInfoString (InfoString);
			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 (event.xany.window == window &&
		(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 snes_width, int snes_height)
{
    bool8 scaled = FALSE;
    bool8 done = FALSE;
    int width, height;

    mouse_scale_h = 1.0;
    mouse_scale_v = 1.0;
    
    if (interpolate && Settings.SixteenBit)
    {
	if (snes_width == 512 && snes_height > 240)
	{
	    if (ImageNeedsScaling)
	    {
		OutputScreen = InterpolateScreen;
		OutputPitch = 514 * 2;
	    }
	    else
	    {
		OutputScreen = GFX.Screen;
		OutputPitch = GFX.Pitch;
		    OutputScreen = (uint8 *) image->data;
		    OutputPitch = image->bytes_per_line;
	    }
	}
	else
	{
	    if (ImageNeedsScaling || (depth != 15 && depth != 16))
	    {
		OutputScreen = InterpolateScreen;
		OutputPitch = 514 * 2;
	    }
	    else
	    {
#ifdef USE_DGA_EXTENSION
		if (xf86data.is_full_screen)
		{
		    OutputScreen = image_data;
		    OutputPitch = bytes_per_line;
		    done = TRUE;
		}
		else
#endif
		{
		    OutputScreen = (uint8 *) image->data;
		    OutputPitch = image->bytes_per_line;
		}
	    }

	    if (snes_width != last_snes_width ||
		snes_height != last_snes_height)
	    {
		memset (DeltaScreen, 255, GFX.Pitch * snes_height);
	    }
	    TVMode (snes_width, snes_height);
	    width = 512;
	    if (snes_height < 240)
		height = snes_height << 1;
	    else
		height = snes_height;
	}
    }
    else
    {
	OutputScreen = GFX.Screen;
	OutputPitch = GFX.Pitch;
	width = snes_width;
	height = snes_height;
    }

    if ((Settings.SixteenBit && depth != 15 && depth != 16) ||
	(!Settings.SixteenBit && (!pseudo || depth != 8)) ||
	ImageNeedsScaling)
    {
	switch (depth)
	{
	case 8:
	    if (Settings.SixteenBit)
		Convert16To8 (width, height);
	    else
		Scale8 (width, height);
	    break;

	case 15:
	case 16:
	    if (!Settings.SixteenBit)
		Convert8To16 (width, height);
	    else
		Scale16 (width, height);
	    break;

	case 24:
	    if (Settings.SixteenBit)
	    {
		if (image->bits_per_pixel == 32)
		    Convert16To24 (width, height);
		else
		    Convert16To24Packed (width, height);
	    }
	    else
	    {
		if (image->bits_per_pixel == 32)
		    Convert8To24 (width, height);
		else
		    Convert8To24Packed (width, height);
	    }
	    break;
	}
	scaled = TRUE;
	if (ImageNeedsScaling)
	{
	    mouse_scale_h = window_width / (double) width;
	    mouse_scale_v = window_height / (double) height;
	}
    }
#ifdef USE_DGA_EXTENSION
    else
    {
	if (xf86data.is_full_screen && !done)
	{
	    for (int y = 0; y < snes_height; y++)
	    {
		memmove (image_data + y * bytes_per_line,
			 GFX.Screen + GFX.Pitch * y,
			 snes_width * bytes_per_pixel);
	    }
	}
    }
#endif

#ifdef USE_DGA_EXTENSION
    if (!xf86data.is_full_screen)
    {
#endif
#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);
#ifdef USE_DGA_EXTENSION
    }
#endif
    last_snes_width = snes_width;
    last_snes_height = snes_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);
#ifdef USE_DGA_EXTENSION
	    if (xf86data.is_full_screen)
		XDefineCursor (display, fs_window, cross_hair_cursor);
#endif
	    superscope = TRUE;
	}
    }
    else
    if (superscope)
    {
	XDefineCursor (display, window, point_cursor);
#ifdef USE_DGA_EXTENSION
	if (xf86data.is_full_screen)
	    XDefineCursor (display, fs_window, point_cursor);
#endif
	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);
#ifdef USE_DGA_EXTENSION
		if (xf86data.is_full_screen)
		    XDefineCursor (display, fs_window, cross_hair_cursor);
#endif
	    }
	}
	else
	if (mod1_pressed)
	{
	    mod1_pressed = FALSE;
	    if (!superscope)
	    {
		XDefineCursor (display, window, point_cursor);
#ifdef USE_DGA_EXTENSION
		if (xf86data.is_full_screen)
		    XDefineCursor (display, fs_window, point_cursor);
#endif
	    }
	}
	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 Scale8 (int width, int height)
{
    register uint32 x_error;
    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 * bytes_per_line +
			   window_width - 1;
	register uint8 *s = OutputScreen + yy * OutputPitch + width - 1;
	y_error += y_fraction;
	while (y_error >= 0x10000)
	{
	    yy--;
	    y_error -= 0x10000;
	}
	x_error = 0;
	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 Scale16 (int width, int height)
{
    register uint32 x_error;
    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 * bytes_per_line) +
					 window_width - 1;
	register uint16 *s = (uint16 *) (OutputScreen + yy * OutputPitch) + width - 1;
	y_error += y_fraction;
	while (y_error >= 0x10000)
	{
	    yy--;
	    y_error -= 0x10000;
	}
	x_error = 0;
	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 Convert8To24 (int width, int height)
{
    uint32 brightness = IPPU.MaxBrightness >> 1;

    if (!ImageNeedsScaling)
    {
	// Convert
	for (register y = 0; y < height; y++)
	{
	    register uint32 *d = (uint32 *) (image_data +
					     y * bytes_per_line);
	    register uint8 *s = OutputScreen + y * OutputPitch;

	    for (register x = 0; x < 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;
	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 uint32 *d = (uint32 *) (image_data +
					   y * bytes_per_line);
	    register uint8 *s = OutputScreen + yy * OutputPitch;
	    y_error += y_fraction;
	    while (y_error >= 0x10000)
	    {
		yy++;
		y_error -= 0x10000;
	    }
	    x_error = 0;
	    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 Convert16To24 (int width, int height)
{
    if (!ImageNeedsScaling)
    {
	// Convert
	for (register y = 0; y < height; y++)
	{
	    register uint32 *d = (uint32 *) (image_data +
					     y * bytes_per_line);
	    register uint16 *s = (uint16 *) (OutputScreen + y * OutputPitch);

	    for (register x = 0; x < width; x++)
	    {
		uint32 pixel = *s++;
		*d++ = (((pixel >> 11) & 0x1f) << (RedShift + 3)) |
		       (((pixel >> 6) & 0x1f) << (GreenShift + 3)) |
		       ((pixel & 0x1f) << (BlueShift + 3));
	    }
	}
    }
    else
    {
	// Scale and convert
	register uint32 x_error;
	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 uint32 *d = (uint32 *) (image_data +
					     y * bytes_per_line);
	    register uint16 *s = (uint16 *) (OutputScreen + yy * OutputPitch);
	    y_error += y_fraction;
	    while (y_error >= 0x10000)
	    {
		yy++;
		y_error -= 0x10000;
	    }
	    x_error = 0;
	    for (register int x = 0; x < window_width; x++)
	    {
		uint32 pixel = *s;
		*d++ = (((pixel >> 11) & 0x1f) << (RedShift + 3)) |
		       (((pixel >> 6) & 0x1f) << (GreenShift + 3)) |
		       ((pixel & 0x1f) << (BlueShift + 3));
		       
		x_error += x_fraction;
		while (x_error >= 0x10000)
		{
		    s++;
		    x_error -= 0x10000;
		}
	    }
	}
    }
}

void Convert8To24Packed (int width, int height)
{
    uint32 brightness = IPPU.MaxBrightness >> 1;
    uint8 levels [32];

    for (int l = 0; l < 32; l++)
	levels [l] = l * brightness;
	
    if (!ImageNeedsScaling)
    {
	// Convert
	for (register y = 0; y < height; y++)
	{
	    register uint8 *d = (uint8 *) (image_data + y * bytes_per_line);
	    register uint8 *s = OutputScreen + y * OutputPitch;

#ifdef LSB_FIRST
	    if (RedShift < BlueShift)
#else	    
	    if (RedShift > BlueShift)
#endif
	    {
		// Order is RGB
		for (register x = 0; x < 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 < 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;
	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 * bytes_per_line);
	    register uint8 *s = OutputScreen + yy * OutputPitch;
	    y_error += y_fraction;
	    while (y_error >= 0x10000)
	    {
		yy++;
		y_error -= 0x10000;
	    }
	    x_error = 0;
#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 Convert16To24Packed (int width, int height)
{
    if (!ImageNeedsScaling)
    {
	// Convert
	for (register y = 0; y < height; y++)
	{
	    register uint8 *d = (uint8 *) (image_data +
					 y * bytes_per_line);
	    register uint16 *s = (uint16 *) (OutputScreen + y * OutputPitch);

#ifdef LSB_FIRST
	    if (RedShift < BlueShift)
#else	    
	    if (RedShift > BlueShift)
#endif
	    {
		// Order is RGB
		for (register x = 0; x < width; x++)
		{
		    uint32 pixel = *s++;
		    *d++ = (pixel >> (11 - 3)) & 0xf8;
		    *d++ = (pixel >> (6 - 3)) & 0xf8;
		    *d++ = (pixel & 0x1f) << 3;
		}
	    }
	    else
	    {
		// Order is BGR
		for (register x = 0; x < width; x++)
		{
		    uint32 pixel = *s++;
		    *d++ = (pixel & 0x1f) << 3;
		    *d++ = (pixel >> (6 - 3)) & 0xf8;
		    *d++ = (pixel >> (11 - 3)) & 0xf8;
		}
	    }
	}
    }
    else
    {
	// Scale and convert
	register uint32 x_error;
	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 * bytes_per_line);
	    register uint16 *s = (uint16 *) (OutputScreen + yy * OutputPitch);
	    y_error += y_fraction;
	    while (y_error >= 0x10000)
	    {
		yy++;
		y_error -= 0x10000;
	    }
	    x_error = 0;
#ifdef LSB_FIRST
	    if (RedShift < BlueShift)
#else
	    if (RedShift > BlueShift)
#endif
	    {
		// Order is RGB
		for (register int x = 0; x < window_width; x++)
		{
		    uint32 pixel = *s;
		    *d++ = (pixel >> (11 - 3)) & 0xf8;
		    *d++ = (pixel >> (6 - 3)) & 0xf8;
		    *d++ = (pixel & 0x1f) << 3;
		       
		    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++)
		{
		    uint32 pixel = *s;
		    *d++ = (pixel & 0x1f) << 3;
		    *d++ = (pixel >> (6 - 3)) & 0xf8;
		    *d++ = (pixel >> (11 - 3)) & 0xf8;
		       
		    x_error += x_fraction;
		    while (x_error >= 0x10000)
		    {
			s++;
			x_error -= 0x10000;
		    }
		}
	    }
	}
    }
}

void Convert16To8 (int width, int height)
{
    if (!ImageNeedsScaling)
    {
	// Convert
	for (register y = 0; y < height; y++)
	{
	    register uint8 *d = (uint8 *) image_data + y * bytes_per_line;
	    register uint16 *s = (uint16 *) (OutputScreen + y * GFX.RealPitch);

	    for (register x = 0; x < width; x++)
		*d++ = palette [*s++];
	}
    }
    else
    {
	// Scale and convert
	register uint32 x_error;
	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 * bytes_per_line;
	    register uint16 *s = (uint16 *) (OutputScreen + yy * OutputPitch);
	    y_error += y_fraction;
	    while (y_error >= 0x10000)
	    {
		yy++;
		y_error -= 0x10000;
	    }
	    x_error = 0;
	    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 Convert8To16 (int width, int height)
{
    uint32 levels [32];

    for (int l = 0; l < 32; l++)
	levels [l] = (l * IPPU.MaxBrightness) >> 4;
	
    if (!ImageNeedsScaling)
    {
	// Convert
	for (register y = 0; y < height; y++)
	{
	    register uint16 *d = (uint16 *) (image_data + y * bytes_per_line);
	    register uint8 *s = OutputScreen + y * OutputPitch;

	    for (register x = 0; x < width; x++)
	    {
		uint32 pixel = PPU.CGDATA [*s++];
		*d++ = (levels [pixel & 0x1f] << RedShift) |
		       (levels [(pixel >> 5) & 0x1f] << GreenShift) |
		       (levels [(pixel >> 10) & 0x1f] << BlueShift);
	    }
	}
    }
    else
    {
	// Scale and convert
	register uint32 x_error;
	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 * bytes_per_line);
	    register uint8 *s = OutputScreen + yy * OutputPitch;
	    y_error += y_fraction;
	    while (y_error >= 0x10000)
	    {
		yy++;
		y_error -= 0x10000;
	    }
	    x_error = 0;
	    for (register int x = 0; x < window_width; x++)
	    {
		uint32 pixel = PPU.CGDATA [*s];
		*d++ = (levels [pixel & 0x1f] << RedShift) |
		       (levels [(pixel >> 5) & 0x1f] << GreenShift) |
		       (levels [(pixel >> 10) & 0x1f] << BlueShift);
		       
		x_error += x_fraction;
		while (x_error >= 0x10000)
		{
		    s++;
		    x_error -= 0x10000;
		}
	    }
	}
    }
}

void S9xTextMode ()
{
#ifdef USE_DGA_EXTENSION
    if (xf86data.full_screen_available && xf86data.is_full_screen)
    {
	XF86DGADirectVideo (display, screen_num, 0);
#ifdef USE_VIDMODE_EXTENSION
	if (xf86data.switch_video_mode)
	    XF86VidModeSwitchToMode (display, screen_num, &xf86data.orig);
#endif
	XUngrabKeyboard (display, CurrentTime);
	XUngrabPointer (display, CurrentTime);
	XUnmapWindow (display, fs_window);
	XWarpPointer (display, None, window, 0, 0, 0, 0, 0, 0);
	XSync (display, False);
    }
#endif
    XAutoRepeatOn (display);
}

void S9xGraphicsMode ()
{
#ifdef USE_DGA_EXTENSION
    if (xf86data.full_screen_available && xf86data.is_full_screen)
    {
	XMapRaised (display, fs_window);
	XClearWindow (display, fs_window);
	XGrabKeyboard (display, fs_window, False, GrabModeAsync, GrabModeAsync,
		       CurrentTime);
	XGrabPointer (display, fs_window, False, ALL_DEVICE_EVENTS,
		      GrabModeAsync, GrabModeAsync, fs_window, point_cursor,
		      CurrentTime);

	XWarpPointer (display, None, RootWindowOfScreen (screen),
		      0, 0, 0, 0, 0, 0);
	XSync (display, False);

#ifdef USE_VIDMODE_EXTENSION
	if (xf86data.switch_video_mode)
	{
	    XF86VidModeSwitchToMode (display, screen_num, xf86data.best);
	    XF86DGAGetVideo (display, screen_num, &xf86data.vram,
			     &xf86data.line_width, &xf86data.bank_size,
			     &xf86data.size);
	    XF86VidModeSetViewPort (display, screen_num, 0, 0);
	    XSync (display, False);
	}
#endif
	XF86DGADirectVideo (display, screen_num, XF86DGADirectGraphics);
	XF86VidModeSetViewPort (display, screen_num, 0, 0);
	XSync (display, False);

	//memset (xf86data.vram, 0, xf86data.size * 1024);
    }
#endif
    XAutoRepeatOff (display);
}

void S9xParseDisplayArg (char **argv, int &ind, int)
{
    if (strcasecmp (argv [ind], "-y") == 0 ||
	strcasecmp (argv [ind], "-interpolate") == 0)
    {
	Settings.SixteenBit = TRUE;
        Settings.SupportHiRes = TRUE;
        Settings.ForceTransparency = TRUE;
        interpolate = TRUE;
    }
#ifdef USE_DGA_EXTENSION
    else
    if (strcasecmp (argv [ind], "-scale") == 0 ||
	strcasecmp (argv [ind], "-sc") == 0)
	xf86data.scale = TRUE;
#ifdef USE_VIDMODE_EXTENSION
    else
    if (strcasecmp (argv [ind], "-nms") == 0 ||
	strcasecmp (argv [ind], "-nomodeswitch") == 0)
	xf86data.no_mode_switch = TRUE;
#endif	
#endif
    else
	S9xUsage ();
}

void S9xExtraUsage ()
{
}

int S9xMinCommandLineArgs ()
{
    return (2);
}

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

void TVMode (int width, int height)
{
    uint8 *nextLine, *srcPtr, *deltaPtr, *finish;
    uint8 *dstPtr;
    uint32 colorMask = ~(RGB_LOW_BITS_MASK | (RGB_LOW_BITS_MASK << 16));
    uint32 lowPixelMask = RGB_LOW_BITS_MASK;

    srcPtr = GFX.Screen;
    deltaPtr = DeltaScreen;
    dstPtr = OutputScreen;
    nextLine = OutputScreen + OutputPitch;

    if (width == 256)
    {
	do
	{
	    uint32 *bP = (uint32 *) srcPtr;
	    uint32 *xP = (uint32 *) deltaPtr;
	    uint32 *dP = (uint32 *) dstPtr;
	    uint32 *nL = (uint32 *) nextLine;
	    uint32 currentPixel;
	    uint32 nextPixel;
	    uint32 currentDelta;
	    uint32 nextDelta;

	    finish = (uint8 *) bP + ((width + 2) << 1);
	    nextPixel = *bP++;
	    nextDelta = *xP++;

	    do
	    {
		currentPixel = nextPixel;
		currentDelta = nextDelta;
		nextPixel = *bP++;
		nextDelta = *xP++;

		if ((nextPixel != nextDelta) || (currentPixel != currentDelta))
		{
		    uint32 colorA, colorB, product, darkened;

		    *(xP - 2) = currentPixel;
#ifdef LSB_FIRST
		    colorA = currentPixel & 0xffff;
#else
		    colorA = (currentPixel & 0xffff0000) >> 16;
#endif

#ifdef LSB_FIRST
		    colorB = (currentPixel & 0xffff0000) >> 16;
		    *(dP) = product = colorA |
				      ((((colorA & colorMask) >> 1) +
					((colorB & colorMask) >> 1) +
					(colorA & colorB & lowPixelMask)) << 16);
#else
		    colorB = currentPixel & 0xffff;
		    *(dP) = product = (colorA << 16) | 
				      (((colorA & colorMask) >> 1) +
				       ((colorB & colorMask) >> 1) +
				       (colorA & colorB & lowPixelMask));
#endif
		    darkened = (product = ((product & colorMask) >> 1));
		    darkened += (product = ((product & colorMask) >> 1));
		    darkened += (product & colorMask) >> 1;
		    *(nL) = darkened;

#ifdef LSB_FIRST
		    colorA = nextPixel & 0xffff;
		    *(dP + 1) = product = colorB |
					  ((((colorA & colorMask) >> 1) +
					    ((colorB & colorMask) >> 1) +
					    (colorA & colorB & lowPixelMask)) << 16);
#else
		    colorA = (nextPixel & 0xffff0000) >> 16;
		    *(dP + 1) = product = (colorB << 16) | 
					   (((colorA & colorMask) >> 1) +
					    ((colorB & colorMask) >> 1) + 
					    (colorA & colorB & lowPixelMask));
#endif
		    darkened = (product = ((product & colorMask) >> 1));
		    darkened += (product = ((product & colorMask) >> 1));
		    darkened += (product & colorMask) >> 1;
		    *(nL + 1) = darkened;
		}

		dP += 2;
		nL += 2;
	    }
	    while ((uint8 *) bP < finish);

	    deltaPtr += GFX.Pitch;
	    srcPtr += GFX.Pitch;
	    dstPtr += OutputPitch * 2;
	    nextLine += OutputPitch * 2;
	}
	while (--height);
    }
    else
    {
	do
	{
	    uint32 *bP = (uint32 *) srcPtr;
	    uint32 *xP = (uint32 *) deltaPtr;
	    uint32 *dP = (uint32 *) dstPtr;
	    uint32 currentPixel;

	    finish = (uint8 *) bP + ((width + 2) << 1);

	    do
	    {
		currentPixel = *bP++;

		if (currentPixel != *xP++)
		{
		    uint32 product, darkened;

		    *(xP - 1) = currentPixel;
		    *dP = currentPixel;
		    darkened = (product = ((currentPixel & colorMask) >> 1));
		    darkened += (product = ((product & colorMask) >> 1));
		    darkened += (product & colorMask) >> 1;
		    *(uint32 *) ((uint8 *) dP + OutputPitch) = darkened;
		}

		dP++;
	    }
	    while ((uint8 *) bP < finish);

	    deltaPtr += GFX.Pitch;
	    srcPtr += GFX.Pitch;
	    dstPtr += OutputPitch * 2;
	}
	while (--height);
    }
}

#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

#ifdef USE_DGA_EXTENSION
void CreateFullScreenWindow ()
{
    int major, minor;

    xf86data.full_screen_available = False;

    if (XF86DGAQueryVersion (display, &major, &minor))
    {
	int fd;

	// Need to test for access to /dev/mem here because XF86DGAGetVideo
	// just calls exit if it can't access this device.
	if ((fd = open ("/dev/mem", O_RDWR) < 0))
	{
	    perror ("Can't open \"/dev/mem\", full screen mode not available");
	    return;
	}
	else
	    close (fd);

	XF86DGAGetVideo (display, screen_num, &xf86data.vram,
			 &xf86data.line_width, &xf86data.bank_size,
			 &xf86data.size);

	xf86data.full_screen_available = True;

	XSetWindowAttributes attributes;

	attributes.override_redirect = True;
	attributes.background_pixel = BlackPixelOfScreen (screen);
	fs_window = XCreateWindow (display, RootWindowOfScreen (screen),
				   0, 0, WidthOfScreen (screen), 
				   HeightOfScreen (screen),
				   0, depth,
				   InputOutput, visual, 
				   CWOverrideRedirect | CWBackPixel,
				   &attributes);

	xf86data.window_width = WidthOfScreen (screen);
	xf86data.window_height = HeightOfScreen (screen);

#ifdef USE_VIDMODE_EXTENSION
	XF86VidModeModeLine current;
	int dot_clock;

	if (!xf86data.no_mode_switch &&
	    XF86VidModeGetAllModeLines (display, screen_num,
					&xf86data.num_modes,
					&xf86data.all_modes) &&
	    XF86VidModeGetModeLine (display, screen_num,
				    &dot_clock, &current))
	{
	    int i;

	    xf86data.orig.dotclock = dot_clock;
	    xf86data.orig.hdisplay = current.hdisplay;
	    xf86data.orig.hdisplay = current.hdisplay;
	    xf86data.orig.hsyncstart = current.hsyncstart;
	    xf86data.orig.hsyncend = current.hsyncend;
	    xf86data.orig.htotal = current.htotal;
	    xf86data.orig.vdisplay = current.vdisplay;
	    xf86data.orig.vsyncstart = current.vsyncstart;
	    xf86data.orig.vsyncend = current.vsyncend;
	    xf86data.orig.vtotal = current.vtotal;
	    xf86data.orig.flags = current.flags;
	    xf86data.orig.c_private = current.c_private;
	    xf86data.orig.privsize = current.privsize;

	    int best_width_so_far = current.hdisplay;
	    int best_height_so_far = current.vdisplay;
	    xf86data.best = NULL;
	    xf86data.switch_video_mode = False;
	    
	    for (i = 0; i < xf86data.num_modes; i++)
	    {
		if (xf86data.all_modes [i]->hdisplay >= IMAGE_WIDTH &&
		    xf86data.all_modes [i]->hdisplay <= best_width_so_far &&
		    xf86data.all_modes [i]->vdisplay >= IMAGE_HEIGHT &&
		    xf86data.all_modes [i]->vdisplay <= best_height_so_far &&
		    (xf86data.all_modes [i]->hdisplay != current.hdisplay ||
		     xf86data.all_modes [i]->vdisplay != current.vdisplay))
		{
		    best_width_so_far = xf86data.all_modes [i]->hdisplay;
		    best_height_so_far = xf86data.all_modes [i]->vdisplay;
		    xf86data.best = xf86data.all_modes [i];
		}
	    }
	    if (xf86data.best)
		xf86data.switch_video_mode = True;
	    else
		xf86data.best = &xf86data.orig;

	    xf86data.window_width = xf86data.best->hdisplay;
	    xf86data.window_height = xf86data.best->vdisplay;
	}
#endif
    }
}

void S9xSwitchToFullScreen (bool8 enable)
{
    if (xf86data.full_screen_available && enable != xf86data.is_full_screen)
    {
	S9xTextMode ();
	xf86data.is_full_screen = enable;
	if (DeltaScreen)
	    memset (DeltaScreen, 0, GFX.Pitch * 478);
	S9xGraphicsMode ();

	if (enable)
	{
	    xf86data.SavedImageNeedsScaling = ImageNeedsScaling;
	    xf86data.saved_window_width = window_width;
	    xf86data.saved_window_height = window_height;
	    bytes_per_line = xf86data.line_width * bytes_per_pixel;

	    if (xf86data.scale)
	    {
		image_data = (uint8 *) xf86data.vram;
		window_width = xf86data.window_width;
		window_height = xf86data.window_height;
		ImageNeedsScaling = TRUE;
	    }
	    else
	    {
		// Centre image in available width/height
		image_data = (uint8 *) xf86data.vram +
			     ((xf86data.window_width - IMAGE_WIDTH) / 2) * bytes_per_pixel +
			     ((xf86data.window_height - IMAGE_HEIGHT) / 2) * bytes_per_line;
		ImageNeedsScaling = FALSE;
	    }
	}
	else
	{
	    ImageNeedsScaling = xf86data.SavedImageNeedsScaling;
	    window_width = xf86data.saved_window_width;
	    window_height = xf86data.saved_window_height;
	    image_data = (uint8 *) image->data;
	    bytes_per_line = image->bytes_per_line;
	}
    }
}
#endif

void S9xDisplayStateChange (const char *str, bool8 on)
{
    static char string [100];

    sprintf (string, "%s %s", str, on ? "on" : "off");
    S9xSetInfoString (string);
}
