/*****************************************************************************\
     Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
                This file is licensed under the Snes9x License.
   For further information, consult the LICENSE file in the root directory.
\*****************************************************************************/

#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#include <sys/stat.h>
#include <sys/ioctl.h>

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

#ifdef USE_XVIDEO
#include <X11/extensions/Xvlib.h>

#ifdef USE_LIBYUV
#include <libyuv.h>
#endif

#define FOURCC_YUY2 0x32595559
#define FOURCC_I420 0x30323449
#endif

#ifdef USE_XINERAMA
#include <X11/extensions/Xinerama.h>
#endif

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

#include "snes9x.h"
#include "memmap.h"
#include "ppu.h"
#include "controls.h"
#include "movie.h"
#include "conffile.h"
#include "blit.h"
#include "display.h"

// Wrapper struct to make generic XvImage vs XImage
struct Image
{
#ifdef USE_XVIDEO
	union
	{
		XvImage*	xvimage;
#endif
		XImage*	ximage;
#ifdef USE_XVIDEO
	};
#endif

	char *data;

	uint32 height;
	uint32 data_size;
	uint32 bits_per_pixel;
	uint32 bytes_per_line;
};

struct GUIData
{
	Display			*display;
	Screen			*screen;
	Visual			*visual;
	GC				gc;
	int				screen_num;
	int				depth;
	int				pixel_format;
	int				bytes_per_pixel;
	uint32			red_shift;
	uint32			blue_shift;
	uint32			green_shift;
	uint32			red_size;
	uint32			green_size;
	uint32			blue_size;
	Window			window;
	Image			*image;
	uint8			*filter_buffer;
	uint8			*blit_screen;
	uint32			blit_screen_pitch;
	bool8			need_convert;
	Cursor			point_cursor;
	Cursor			cross_hair_cursor;
	int				video_mode;
	int				mouse_x;
	int				mouse_y;
	bool8			mod1_pressed;
	bool8			no_repeat;
	bool8			fullscreen;
	bool8			js_event_latch;
	int				x_offset;
	int				y_offset;
#ifdef USE_XVIDEO
	bool8			use_xvideo;
	int				xv_port;
	int				scale_w;
	int				scale_h;

	bool8			maxaspect;
	int			imageHeight;

	int				xv_format;
	int				xv_bpp;
	unsigned char		y_table[1 << 15];
	unsigned char		u_table[1 << 15];
	unsigned char		v_table[1 << 15];
#endif
#ifdef USE_XINERAMA
    uint32 xinerama_head;
#endif
#ifdef MITSHM
	XShmSegmentInfo	sm_info;
	bool8			use_shared_memory;
#endif
};

static struct GUIData	GUI;

typedef	std::pair<std::string, std::string>	strpair_t;
extern	std::vector<strpair_t>				keymaps;

typedef	void (* Blitter) (uint8 *, int, uint8 *, int, int, int);

#ifdef __linux
// Select seems to be broken in 2.x.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).
//
// The workaround is to stop the X library calling select in the first
// place! Replace XPending - which polls for data from the X server using
// select - with an ioctl call to poll for data and then only call the blocking
// XNextEvent if data is waiting.
#define SELECT_BROKEN_FOR_SIGNALS
#endif

enum
{
	VIDEOMODE_BLOCKY = 1,
	VIDEOMODE_TV,
	VIDEOMODE_SMOOTH,
	VIDEOMODE_SUPEREAGLE,
	VIDEOMODE_2XSAI,
	VIDEOMODE_SUPER2XSAI,
	VIDEOMODE_EPX,
	VIDEOMODE_HQ2X
};

static int ErrorHandler (Display *, XErrorEvent *);
static bool8 CheckForPendingXEvents (Display *);
static void SetXRepeat (bool8);
static void SetupImage (void);
static void TakedownImage (void);
static void SetupXImage (void);
static void TakedownXImage (void);
#ifdef USE_XVIDEO
static void SetupXvImage (void);
static void TakedownXvImage (void);
#endif
static void Repaint (bool8);
static void Convert16To24 (int, int);
static void Convert16To24Packed (int, int);


void S9xExtraDisplayUsage (void)
{
	/*                               12345678901234567890123456789012345678901234567890123456789012345678901234567890 */

	S9xMessage(S9X_INFO, S9X_USAGE, "-setrepeat                      Allow altering keyboard auto-repeat");
	S9xMessage(S9X_INFO, S9X_USAGE, "");
	S9xMessage(S9X_INFO, S9X_USAGE, "-fullscreen                     Switch to full-screen on start");
#ifdef USE_XVIDEO
	S9xMessage(S9X_INFO, S9X_USAGE, "-xvideo                         Hardware accelerated scaling");
	S9xMessage(S9X_INFO, S9X_USAGE, "-maxaspect                      Try to fill the display, in fullscreen");
#endif
#ifdef USE_XINERAMA
	S9xMessage(S9X_INFO, S9X_USAGE, "-xineramahead                   Xinerama head number for multi-monitor setups");
#endif
	S9xMessage(S9X_INFO, S9X_USAGE, "");
	S9xMessage(S9X_INFO, S9X_USAGE, "-v1                             Video mode: Blocky (default)");
	S9xMessage(S9X_INFO, S9X_USAGE, "-v2                             Video mode: TV");
	S9xMessage(S9X_INFO, S9X_USAGE, "-v3                             Video mode: Smooth");
	S9xMessage(S9X_INFO, S9X_USAGE, "-v4                             Video mode: SuperEagle");
	S9xMessage(S9X_INFO, S9X_USAGE, "-v5                             Video mode: 2xSaI");
	S9xMessage(S9X_INFO, S9X_USAGE, "-v6                             Video mode: Super2xSaI");
	S9xMessage(S9X_INFO, S9X_USAGE, "-v7                             Video mode: EPX");
	S9xMessage(S9X_INFO, S9X_USAGE, "-v8                             Video mode: hq2x");
	S9xMessage(S9X_INFO, S9X_USAGE, "");
}

void S9xParseDisplayArg (char **argv, int &i, int argc)
{
	if (!strcasecmp(argv[i], "-setrepeat"))
		GUI.no_repeat = FALSE;
	else
	if (!strcasecmp(argv[i], "-fullscreen"))
		GUI.fullscreen = TRUE;
	else
#ifdef USE_XVIDEO
	if (!strcasecmp(argv[i], "-xvideo"))
		GUI.use_xvideo = TRUE;
	else
	if (!strcasecmp(argv[i], "-maxaspect"))
		GUI.maxaspect = TRUE;
	else
#endif
#ifdef USE_XINERAMA
	if (!strcasecmp(argv[i], "-xineramahead"))
	{
		if (i + 1 < argc)
			GUI.xinerama_head = atoi(argv[++i]);
		else
			S9xUsage();
	}
	else
#endif
	if (!strncasecmp(argv[i], "-v", 2))
	{
		switch (argv[i][2])
		{
			case '1':	GUI.video_mode = VIDEOMODE_BLOCKY;		break;
			case '2':	GUI.video_mode = VIDEOMODE_TV;			break;
			case '3':	GUI.video_mode = VIDEOMODE_SMOOTH;		break;
			case '4':	GUI.video_mode = VIDEOMODE_SUPEREAGLE;	break;
			case '5':	GUI.video_mode = VIDEOMODE_2XSAI;		break;
			case '6':	GUI.video_mode = VIDEOMODE_SUPER2XSAI;	break;
			case '7':	GUI.video_mode = VIDEOMODE_EPX;			break;
			case '8':	GUI.video_mode = VIDEOMODE_HQ2X;		break;
		}
	}
	else
		S9xUsage();
}

const char * S9xParseDisplayConfig (ConfigFile &conf, int pass)
{
	if (pass != 1)
		return ("Unix/X11");

	if (!conf.GetBool("Unix::ClearAllControls", false))
	{
		keymaps.push_back(strpair_t("K00:k",            "Joypad1 Right"));
		keymaps.push_back(strpair_t("K00:Right",        "Joypad1 Right"));
		keymaps.push_back(strpair_t("K00:h",            "Joypad1 Left"));
		keymaps.push_back(strpair_t("K00:Left",         "Joypad1 Left"));
		keymaps.push_back(strpair_t("K00:j",            "Joypad1 Down"));
		keymaps.push_back(strpair_t("K00:n",            "Joypad1 Down"));
		keymaps.push_back(strpair_t("K00:Down",         "Joypad1 Down"));
		keymaps.push_back(strpair_t("K00:u",            "Joypad1 Up"));
		keymaps.push_back(strpair_t("K00:Up",           "Joypad1 Up"));
		keymaps.push_back(strpair_t("K00:Return",       "Joypad1 Start"));
		keymaps.push_back(strpair_t("K00:space",        "Joypad1 Select"));
		keymaps.push_back(strpair_t("K00:S+d",          "Joypad1 ToggleTurbo A"));
		keymaps.push_back(strpair_t("K00:C+d",          "Joypad1 ToggleSticky A"));
		keymaps.push_back(strpair_t("K00:d",            "Joypad1 A"));
		keymaps.push_back(strpair_t("K00:S+c",          "Joypad1 ToggleTurbo B"));
		keymaps.push_back(strpair_t("K00:C+c",          "Joypad1 ToggleSticky B"));
		keymaps.push_back(strpair_t("K00:c",            "Joypad1 B"));
		keymaps.push_back(strpair_t("K00:S+s",          "Joypad1 ToggleTurbo X"));
		keymaps.push_back(strpair_t("K00:C+s",          "Joypad1 ToggleSticky X"));
		keymaps.push_back(strpair_t("K00:s",            "Joypad1 X"));
		keymaps.push_back(strpair_t("K00:S+x",          "Joypad1 ToggleTurbo Y"));
		keymaps.push_back(strpair_t("K00:C+x",          "Joypad1 ToggleSticky Y"));
		keymaps.push_back(strpair_t("K00:x",            "Joypad1 Y"));
		keymaps.push_back(strpair_t("K00:S+a",          "Joypad1 ToggleTurbo L"));
		keymaps.push_back(strpair_t("K00:S+v",          "Joypad1 ToggleTurbo L"));
		keymaps.push_back(strpair_t("K00:C+a",          "Joypad1 ToggleSticky L"));
		keymaps.push_back(strpair_t("K00:C+v",          "Joypad1 ToggleSticky L"));
		keymaps.push_back(strpair_t("K00:a",            "Joypad1 L"));
		keymaps.push_back(strpair_t("K00:v",            "Joypad1 L"));
		keymaps.push_back(strpair_t("K00:S+z",          "Joypad1 ToggleTurbo R"));
		keymaps.push_back(strpair_t("K00:C+z",          "Joypad1 ToggleSticky R"));
		keymaps.push_back(strpair_t("K00:z",            "Joypad1 R"));

		keymaps.push_back(strpair_t("K00:KP_Left",      "Joypad2 Left"));
		keymaps.push_back(strpair_t("K00:KP_Right",     "Joypad2 Right"));
		keymaps.push_back(strpair_t("K00:KP_Up",        "Joypad2 Up"));
		keymaps.push_back(strpair_t("K00:KP_Down",      "Joypad2 Down"));
		keymaps.push_back(strpair_t("K00:KP_Enter",     "Joypad2 Start"));
		keymaps.push_back(strpair_t("K00:KP_Add",       "Joypad2 Select"));
		keymaps.push_back(strpair_t("K00:Prior",        "Joypad2 A"));
		keymaps.push_back(strpair_t("K00:Next",         "Joypad2 B"));
		keymaps.push_back(strpair_t("K00:Home",         "Joypad2 X"));
		keymaps.push_back(strpair_t("K00:End",          "Joypad2 Y"));
		keymaps.push_back(strpair_t("K00:Insert",       "Joypad2 L"));
		keymaps.push_back(strpair_t("K00:Delete",       "Joypad2 R"));

		keymaps.push_back(strpair_t("K00:A+F4",         "SoundChannel0"));
		keymaps.push_back(strpair_t("K00:C+F4",         "SoundChannel0"));
		keymaps.push_back(strpair_t("K00:A+F5",         "SoundChannel1"));
		keymaps.push_back(strpair_t("K00:C+F5",         "SoundChannel1"));
		keymaps.push_back(strpair_t("K00:A+F6",         "SoundChannel2"));
		keymaps.push_back(strpair_t("K00:C+F6",         "SoundChannel2"));
		keymaps.push_back(strpair_t("K00:A+F7",         "SoundChannel3"));
		keymaps.push_back(strpair_t("K00:C+F7",         "SoundChannel3"));
		keymaps.push_back(strpair_t("K00:A+F8",         "SoundChannel4"));
		keymaps.push_back(strpair_t("K00:C+F8",         "SoundChannel4"));
		keymaps.push_back(strpair_t("K00:A+F9",         "SoundChannel5"));
		keymaps.push_back(strpair_t("K00:C+F9",         "SoundChannel5"));
		keymaps.push_back(strpair_t("K00:A+F10",        "SoundChannel6"));
		keymaps.push_back(strpair_t("K00:C+F10",        "SoundChannel6"));
		keymaps.push_back(strpair_t("K00:A+F11",        "SoundChannel7"));
		keymaps.push_back(strpair_t("K00:C+F11",        "SoundChannel7"));
		keymaps.push_back(strpair_t("K00:A+F12",        "SoundChannelsOn"));
		keymaps.push_back(strpair_t("K00:C+F12",        "SoundChannelsOn"));

		keymaps.push_back(strpair_t("K00:S+1",          "BeginRecordingMovie"));
		keymaps.push_back(strpair_t("K00:S+2",          "EndRecordingMovie"));
		keymaps.push_back(strpair_t("K00:S+3",          "LoadMovie"));
		keymaps.push_back(strpair_t("K00:A+F1",         "SaveSPC"));
		keymaps.push_back(strpair_t("K00:C+F1",         "SaveSPC"));
		keymaps.push_back(strpair_t("K00:F10",          "LoadOopsFile"));
		keymaps.push_back(strpair_t("K00:A+F2",         "LoadFreezeFile"));
		keymaps.push_back(strpair_t("K00:C+F2",         "LoadFreezeFile"));
		keymaps.push_back(strpair_t("K00:F11",          "LoadFreezeFile"));
		keymaps.push_back(strpair_t("K00:A+F3",         "SaveFreezeFile"));
		keymaps.push_back(strpair_t("K00:C+F3",         "SaveFreezeFile"));
		keymaps.push_back(strpair_t("K00:F12",          "SaveFreezeFile"));
		keymaps.push_back(strpair_t("K00:F1",           "QuickLoad000"));
		keymaps.push_back(strpair_t("K00:F2",           "QuickLoad001"));
		keymaps.push_back(strpair_t("K00:F3",           "QuickLoad002"));
		keymaps.push_back(strpair_t("K00:F4",           "QuickLoad003"));
		keymaps.push_back(strpair_t("K00:F5",           "QuickLoad004"));
		keymaps.push_back(strpair_t("K00:F6",           "QuickLoad005"));
		keymaps.push_back(strpair_t("K00:F7",           "QuickLoad006"));
		keymaps.push_back(strpair_t("K00:F8",           "QuickLoad007"));
		keymaps.push_back(strpair_t("K00:F9",           "QuickLoad008"));
		keymaps.push_back(strpair_t("K00:S+F1",         "QuickSave000"));
		keymaps.push_back(strpair_t("K00:S+F2",         "QuickSave001"));
		keymaps.push_back(strpair_t("K00:S+F3",         "QuickSave002"));
		keymaps.push_back(strpair_t("K00:S+F4",         "QuickSave003"));
		keymaps.push_back(strpair_t("K00:S+F5",         "QuickSave004"));
		keymaps.push_back(strpair_t("K00:S+F6",         "QuickSave005"));
		keymaps.push_back(strpair_t("K00:S+F7",         "QuickSave006"));
		keymaps.push_back(strpair_t("K00:S+F8",         "QuickSave007"));
		keymaps.push_back(strpair_t("K00:S+F9",         "QuickSave008"));

		keymaps.push_back(strpair_t("K00:Scroll_Lock",  "Pause"));
		keymaps.push_back(strpair_t("K00:CS+Escape",    "Reset"));
		keymaps.push_back(strpair_t("K00:S+Escape",     "SoftReset"));
		keymaps.push_back(strpair_t("K00:Escape",       "ExitEmu"));
		keymaps.push_back(strpair_t("K00:Tab",          "EmuTurbo"));
		keymaps.push_back(strpair_t("K00:S+Tab",        "ToggleEmuTurbo"));
		keymaps.push_back(strpair_t("K00:A+equal",      "IncEmuTurbo"));
		keymaps.push_back(strpair_t("K00:A+minus",      "DecEmuTurbo"));
		keymaps.push_back(strpair_t("K00:C+equal",      "IncTurboSpeed"));
		keymaps.push_back(strpair_t("K00:C+minus",      "DecTurboSpeed"));
		keymaps.push_back(strpair_t("K00:equal",        "IncFrameRate"));
		keymaps.push_back(strpair_t("K00:minus",        "DecFrameRate"));
		keymaps.push_back(strpair_t("K00:S+equal",      "IncFrameTime"));
		keymaps.push_back(strpair_t("K00:S+minus",      "DecFrameTime"));
		keymaps.push_back(strpair_t("K00:6",            "SwapJoypads"));
		keymaps.push_back(strpair_t("K00:Print",        "Screenshot"));

		keymaps.push_back(strpair_t("K00:1",            "ToggleBG0"));
		keymaps.push_back(strpair_t("K00:2",            "ToggleBG1"));
		keymaps.push_back(strpair_t("K00:3",            "ToggleBG2"));
		keymaps.push_back(strpair_t("K00:4",            "ToggleBG3"));
		keymaps.push_back(strpair_t("K00:5",            "ToggleSprites"));
		keymaps.push_back(strpair_t("K00:9",            "ToggleTransparency"));
		keymaps.push_back(strpair_t("K00:BackSpace",    "ClipWindows"));
		keymaps.push_back(strpair_t("K00:A+Escape",     "Debugger"));

		keymaps.push_back(strpair_t("M00:B0",           "{Mouse1 L,Superscope Fire,Justifier1 Trigger}"));
		keymaps.push_back(strpair_t("M00:B1",           "{Justifier1 AimOffscreen Trigger,Superscope AimOffscreen}"));
		keymaps.push_back(strpair_t("M00:B2",           "{Mouse1 R,Superscope Cursor,Justifier1 Start}"));
		keymaps.push_back(strpair_t("M00:Pointer",      "Pointer Mouse1+Superscope+Justifier1"));
		keymaps.push_back(strpair_t("K00:grave",        "Superscope ToggleTurbo"));
		keymaps.push_back(strpair_t("K00:slash",        "Superscope Pause"));

		keymaps.push_back(strpair_t("K00:r",            "Rewind"));
                keymaps.push_back(strpair_t("K00:l",            "Advance"));
	}

	GUI.no_repeat = !conf.GetBool("Unix/X11::SetKeyRepeat", TRUE);
	GUI.fullscreen = conf.GetBool("Unix/X11::Fullscreen", FALSE);
#ifdef USE_XVIDEO
	GUI.use_xvideo = conf.GetBool("Unix/X11::Xvideo", FALSE);
	GUI.maxaspect = conf.GetBool("Unix/X11::MaxAspect", FALSE);
#endif
#ifdef USE_XINERAMA
    GUI.xinerama_head = conf.GetUInt("Unix/X11::XineramaHead", 0);
#endif

	if (conf.Exists("Unix/X11::VideoMode"))
	{
		GUI.video_mode = conf.GetUInt("Unix/X11::VideoMode", VIDEOMODE_BLOCKY);
		if (GUI.video_mode < 1 || GUI.video_mode > 8)
			GUI.video_mode = VIDEOMODE_BLOCKY;
	}
	else
		GUI.video_mode = VIDEOMODE_BLOCKY;

	return ("Unix/X11");
}

static void FatalError (const char *str)
{
	fprintf(stderr, "%s\n", str);
	S9xExit();
}

static int ErrorHandler (Display *display, XErrorEvent *event)
{
#ifdef MITSHM
	GUI.use_shared_memory = FALSE;
#endif
	return (0);
}

#ifdef USE_XVIDEO
static int get_inv_shift (uint32 mask, int bpp)
{
    int i;

    // Find mask
    for (i = 0; (i < bpp) && !(mask & (1 << i)); i++) {};

    // Find start of mask
    for (; (i < bpp) && (mask & (1 << i)); i++) {};

    return (bpp - i);
}

static unsigned char CLAMP (int v, int min, int max)
{
	if (v < min) return min;
	if (v > max) return max;
	return v;
}

static bool8 SetupXvideo()
{
	int ret;

	// Init xv_port
	GUI.xv_port = -1;

	/////////////////////
	// Check that Xvideo extension seems OK
	unsigned int p_version, p_release, p_request_base, p_event_base, p_error_base;
	ret = XvQueryExtension(GUI.display,
		&p_version, &p_release, &p_request_base,
		&p_event_base, &p_error_base);
	if (ret != Success) { fprintf(stderr,"XvQueryExtension error\n"); return FALSE; }
	printf("XvExtension version %i.%i\n",p_version,p_release);

	/////////////////////
	// Get info about the Adaptors available for this window
	unsigned int	p_num_adaptors;
	XvAdaptorInfo*	ai;
	ret = XvQueryAdaptors(GUI.display, GUI.window, &p_num_adaptors, &ai);

	if (ret != Success || p_num_adaptors == 0) {
		fprintf(stderr,"XvQueryAdaptors error.");
		return FALSE;
	}
	printf("XvQueryAdaptors: %d adaptor(s) found.\n",p_num_adaptors);

	unsigned int minAdaptor = 0, maxAdaptor = p_num_adaptors;
	// Allow user to force adaptor choice
	/* if (adaptor >= 0 && adaptor < p_num_adaptors)
	{
		if (verbose) std::cout << "Forcing adaptor " << adaptor << ", '" << ai[adaptor].name << "'" << std::endl;
		minAdaptor = adaptor;
		maxAdaptor = adaptor + 1;
	} */

	/////////////////////
	// Iterate through list of available adaptors.
	//  Grab a port if we can.
	for (unsigned int i = minAdaptor; i < maxAdaptor && GUI.xv_port < 0; i++)
	{
		// We need to find one supporting XvInputMask and XvImageMask.
		if (! (ai[i].type & XvImageMask)) continue;
		if (! (ai[i].type & XvInputMask)) continue;

		printf("\tAdaptor #%d: [%s]: %ld port(s) available.\n", i, ai[i].name, ai[i].num_ports);

		// Get encodings available here
		//  AFAIK all ports on an adapter share the same encodings info.
		unsigned int encodings;
		XvEncodingInfo	*ei;
		ret = XvQueryEncodings(GUI.display, ai[i].base_id, &encodings, &ei);
		if (ret != Success || encodings == 0) {
	                fprintf(stderr,"XvQueryEncodings error.");
			continue;
		}

		// Ensure the XV_IMAGE encoding available has sufficient width/height for us.
		bool8 can_fit = FALSE;
		for (unsigned int j = 0; j < encodings; j++)
		{
			if (strcmp(ei[j].name,"XV_IMAGE")) continue;
			if (ei[j].width >= SNES_WIDTH * 2 &&
				ei[j].height >= SNES_HEIGHT_EXTENDED * 2)
			{
				can_fit = TRUE;
				break;
			}
		}
		XvFreeEncodingInfo(ei);

		if (can_fit == FALSE)
		{
			fprintf(stderr,"\tDid not find XV_IMAGE encoding with enough max size\n");
			continue;
		}

		// Phew. If we've made it this far, we can try to choose it for our output port.
		for (unsigned int p = ai[i].base_id; p < ai[i].base_id+ai[i].num_ports; p++)
		{
			ret = XvGrabPort(GUI.display, p, CurrentTime);
			if (ret == Success)
			{
				printf("\tSuccessfully bound to Xv port %d\n",p);
				GUI.xv_port = p;
				break;
			} else {
				fprintf(stderr,"\tXvGrabPort port %d fail.\n",p);
			}
		}
	}
	XvFreeAdaptorInfo(ai);

	/////////////////////
	// Bail out here if we haven't managed to bind to any port.
	if (GUI.xv_port < 0)
	{
		fprintf(stderr,"No suitable xv_port found in any Adaptors.\n");
		return FALSE;
	}

	// Xv ports can have Attributes (hue, saturation, etc)
	/* Set XV_AUTOPAINT_COLORKEY _only_ if available */
	int num_attrs;
	XvAttribute*	port_attr;
	port_attr = XvQueryPortAttributes (GUI.display, GUI.xv_port, &num_attrs);

	for (int i = 0; i < num_attrs; i++)
	{
		if (!strcmp (port_attr[i].name, "XV_AUTOPAINT_COLORKEY"))
		{
			Atom colorkey;

			colorkey = XInternAtom (GUI.display, "XV_AUTOPAINT_COLORKEY", True);
			if (colorkey != None)
			{
				XvSetPortAttribute (GUI.display, GUI.xv_port, colorkey, 1);
				printf("\tSet XV_AUTOPAINT_COLORKEY.\n");
			}
		}
	}
	XFree(port_attr);

	// Now we need to find to find the image format to use for output.
	//  There are two steps to this:
	//    Prefer an XvRGB version of lowest bitdepth.
	//  If that's not available use YUY2
	int	formats;
	XvImageFormatValues*	fo;
	fo = XvListImageFormats(GUI.display, GUI.xv_port, &formats);
	if (formats == 0)
	{
		fprintf(stderr,"No valid image formats for Xv port!");
		return FALSE;
	}

	/* Ok time to search for a good Format */
	GUI.xv_format = FOURCC_I420;
	GUI.xv_bpp = 0x7FFFFFFF;

	for (int i = 0; i < formats; i++)
	{
        //prefer I420
        if (fo[i].id == FOURCC_YUY2) {
            GUI.xv_format = FOURCC_YUY2;
        }
		else if (fo[i].id == 0x3 || fo[i].type == XvRGB)
		{
			if (fo[i].bits_per_pixel < GUI.xv_bpp)
			{
				GUI.xv_format = fo[i].id;
				GUI.xv_bpp = fo[i].bits_per_pixel;
				GUI.bytes_per_pixel = (GUI.xv_bpp == 15) ? 2 : GUI.xv_bpp >> 3;
				GUI.depth = fo[i].depth;

				GUI.red_shift = get_inv_shift (fo[i].red_mask, GUI.xv_bpp);
				GUI.green_shift = get_inv_shift (fo[i].green_mask, GUI.xv_bpp);
				GUI.blue_shift = get_inv_shift (fo[i].blue_mask, GUI.xv_bpp);

				/* Check for red-blue inversion on SiliconMotion drivers */
				if (fo[i].red_mask  == 0x001f &&
					fo[i].blue_mask == 0x7c00)
				{
					int copy = GUI.red_shift;
					GUI.red_shift = GUI.blue_shift;
					GUI.blue_shift = copy;
				}
			}
		}
	}
	free (fo);

	if (GUI.xv_format != FOURCC_YUY2 && GUI.xv_format != FOURCC_I420) {
		printf("Selected XvRGB format: %d bpp\n",GUI.xv_bpp);
	} else {
		// use I420 or YUY2
		if(GUI.xv_format == FOURCC_I420) {
		    printf("Xvideo I420 image format.\n");
        } else {
		    printf("Xvideo YUY2 image format.\n");
		    GUI.depth = 15;

		    /* Build a table for yuv conversion */
		    for (unsigned int color = 0; color < (1 << 15); color++)
		    {
		    	int r, g, b;
		    	int y, u, v;

		    	r = (color & 0x7c00) >> 7;
		    	g = (color & 0x03e0) >> 2;
		    	b = (color & 0x001F) << 3;

		    	y = (int) ((0.257  * ((double) r)) + (0.504  * ((double) g)) + (0.098  * ((double) b)) + 16.0);
		    	u = (int) ((-0.148 * ((double) r)) + (-0.291 * ((double) g)) + (0.439  * ((double) b)) + 128.0);
		    	v = (int) ((0.439  * ((double) r)) + (-0.368 * ((double) g)) + (-0.071 * ((double) b)) + 128.0);

		    	GUI.y_table[color] = CLAMP (y, 0, 255);
		    	GUI.u_table[color] = CLAMP (u, 0, 255);
		    	GUI.v_table[color] = CLAMP (v, 0, 255);
		    }
        }
	}

	return TRUE;
}
#endif

void S9xInitDisplay (int argc, char **argv)
{
	GUI.display = XOpenDisplay(NULL);
	if (GUI.display == NULL)
		FatalError("Failed to connect to X server.");

	GUI.screen     = DefaultScreenOfDisplay(GUI.display);
	GUI.screen_num = XScreenNumberOfScreen(GUI.screen);
	GUI.visual     = DefaultVisualOfScreen(GUI.screen);

	XVisualInfo	plate, *matches;
	int			count;

	plate.visualid = XVisualIDFromVisual(GUI.visual);
	matches = XGetVisualInfo(GUI.display, VisualIDMask, &plate, &count);
	if (!count)
		FatalError("Your X Window System server is unwell!");

	GUI.depth = matches[0].depth;
	if ((GUI.depth != 15 && GUI.depth != 16 && GUI.depth != 24) || (matches[0].c_class != TrueColor))
		FatalError("Requiers 15, 16, 24 or 32-bit color depth supporting TrueColor.");

	GUI.red_shift   = ffs(matches[0].red_mask)   - 1;
	GUI.green_shift = ffs(matches[0].green_mask) - 1;
	GUI.blue_shift  = ffs(matches[0].blue_mask)  - 1;
	GUI.red_size    = matches[0].red_mask   >> GUI.red_shift;
	GUI.green_size  = matches[0].green_mask >> GUI.green_shift;
	GUI.blue_size   = matches[0].blue_mask  >> GUI.blue_shift;
	if (GUI.depth == 16 && GUI.green_size == 63)
		GUI.green_shift++;

	XFree(matches);

	// Init various scale-filters
	S9xBlitFilterInit();
	S9xBlit2xSaIFilterInit();
	S9xBlitHQ2xFilterInit();

	/* Set up parameters for creating the window */
	XSetWindowAttributes	attrib;

	memset(&attrib, 0, sizeof(attrib));
	attrib.background_pixel = BlackPixelOfScreen(GUI.screen);
	attrib.colormap = XCreateColormap(GUI.display, RootWindowOfScreen(GUI.screen), GUI.visual, AllocNone);

	int screen_left = 0, screen_top = 0;
	int screen_w = WidthOfScreen(GUI.screen), screen_h = HeightOfScreen(GUI.screen);

#ifdef USE_XINERAMA
	int heads = 0;
	XineramaScreenInfo* si = 0;

	int useless1, useless2;
	if (!XineramaQueryExtension(GUI.display, &useless1, &useless2)) {
		puts("Xinerama is not available");
		goto xinerama_end;
	}

	if (!XineramaIsActive(GUI.display)) {
		puts("Xinerama is not active");
		goto xinerama_end;
	}

	si = XineramaQueryScreens(GUI.display, &heads);
	if (!si) {
		puts("XineramaQueryScreens failed");
		goto xinerama_end;
	}

	if (GUI.xinerama_head >= heads) {
		printf("Invalid xinerama head id (expected 0-%d, got %u)\n", heads - 1, GUI.xinerama_head);
		goto xinerama_end;
	}

	si = &si[GUI.xinerama_head];
	screen_left = si->x_org;
	screen_top = si->y_org;
	screen_w = si->width;
	screen_h = si->height;

	printf("Selected xinerama head %u (%d,%d %dx%d)\n", GUI.xinerama_head, screen_left, screen_top, screen_w, screen_h);

xinerama_end:
#endif

	XSizeHints      Hints;
	memset((void *) &Hints, 0, sizeof(XSizeHints));

	/* Try to switch to Fullscreen. */
	if (GUI.fullscreen == TRUE)
	{
		Hints.flags = PPosition;
		Hints.x     = screen_left;
		Hints.y     = screen_top;

		/* Create the window with maximum screen width,height positioned at 0,0. */
		GUI.window = XCreateWindow(GUI.display, RootWindowOfScreen(GUI.screen),
							Hints.x, Hints.y,
							screen_w, screen_h, 0,
							GUI.depth, InputOutput, GUI.visual, CWBackPixel | CWColormap, &attrib);

		/* Try to tell the Window Manager not to decorate this window. */
		Atom wm_state   = XInternAtom (GUI.display, "_NET_WM_STATE", true );
		Atom wm_fullscreen = XInternAtom (GUI.display, "_NET_WM_STATE_FULLSCREEN", true );

		XChangeProperty(GUI.display, GUI.window, wm_state, XA_ATOM, 32, PropModeReplace, (unsigned char *)&wm_fullscreen, 1);

#ifdef USE_XVIDEO
		if (GUI.use_xvideo)
		{
			// Set some defaults
			GUI.scale_w = screen_w;
			GUI.scale_h = screen_h;

			GUI.imageHeight = SNES_HEIGHT_EXTENDED * 2;

			if (! GUI.maxaspect)
			{
				// Compute the maximum screen size for scaling xvideo window.
				double screenAspect = (double)screen_w / screen_h;
				double snesAspect = (double)SNES_WIDTH / SNES_HEIGHT_EXTENDED;
				double ratio = screenAspect / snesAspect;

				printf("\tScreen (%dx%d) aspect %f vs SNES (%dx%d) aspect %f (ratio: %f)\n",
					screen_w,screen_h,screenAspect,
					SNES_WIDTH,SNES_HEIGHT_EXTENDED,snesAspect,
					ratio);

				// Correct aspect ratio
				if (screenAspect > snesAspect)
				{
					// widescreen monitor, 4:3 snes
					//  match height, scale width
					GUI.scale_w /= ratio;
					GUI.x_offset = (screen_w - GUI.scale_w) / 2;
				} else {
					// narrow monitor, 4:3 snes
					//  match width, scale height
					GUI.scale_h *= ratio;
					GUI.y_offset = (screen_h - GUI.scale_h) / 2;
				}
			}

			printf("\tUsing size %dx%d with offset (%d,%d)\n",GUI.scale_w,GUI.scale_h,GUI.x_offset,GUI.y_offset);
		}
		else
#endif
		{
			/* Last: position the output window in the center of the screen. */
			GUI.x_offset = (screen_w - SNES_WIDTH * 2) / 2;
			GUI.y_offset = (screen_h - SNES_HEIGHT_EXTENDED * 2) / 2;
		}
	} else {
		/* Tell the Window Manager that we do not wish to be resizable */
		Hints.flags      = PSize | PMinSize | PMaxSize | PPosition;
		Hints.x          = screen_left + (screen_w - SNES_WIDTH * 2) / 2;
		Hints.y          = screen_top + (screen_h - SNES_HEIGHT_EXTENDED * 2) / 2;
		Hints.min_width  = Hints.max_width  = Hints.base_width  = SNES_WIDTH * 2;
		Hints.min_height = Hints.max_height = Hints.base_height = SNES_HEIGHT_EXTENDED * 2;

		/* Create the window. */
		GUI.window = XCreateWindow(GUI.display, RootWindowOfScreen(GUI.screen),
								   Hints.x, Hints.y,
								   SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2, 0, GUI.depth, InputOutput, GUI.visual, CWBackPixel | CWColormap, &attrib);

		/* Last: Windowed SNES is not drawn with any offsets. */
		GUI.x_offset = GUI.y_offset = 0;
#ifdef USE_XVIDEO
		GUI.scale_w = SNES_WIDTH * 2;
		GUI.scale_h = SNES_HEIGHT_EXTENDED * 2;
#endif
	}

	XSetWMNormalHints(GUI.display, GUI.window, &Hints);

	/* Load UI cursors */
	static XColor	bg, fg;
	static char		data[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
	Pixmap			bitmap;

	bitmap = XCreateBitmapFromData(GUI.display, GUI.window, data, 8, 8);
	GUI.point_cursor = XCreatePixmapCursor(GUI.display, bitmap, bitmap, &fg, &bg, 0, 0);
	XDefineCursor(GUI.display, GUI.window, GUI.point_cursor);
	GUI.cross_hair_cursor = XCreateFontCursor(GUI.display, XC_crosshair);

	GUI.gc = DefaultGCOfScreen(GUI.screen);

	/* Other window-manager hints */
	XWMHints	WMHints;

	memset((void *) &WMHints, 0, sizeof(XWMHints));

	/* Rely on the Window Manager to provide us with keyboard input */
	WMHints.input    = True;
	WMHints.flags    = InputHint;

	XSetWMHints(GUI.display, GUI.window, &WMHints);
	XSelectInput(GUI.display, GUI.window, FocusChangeMask | ExposureMask | KeyPressMask | KeyReleaseMask | StructureNotifyMask | ButtonPressMask | ButtonReleaseMask);

	/* Bring up our window (and put it in foreground) */
	XMapRaised(GUI.display, GUI.window);
	XClearWindow(GUI.display, GUI.window);

	// Wait for map
	XEvent event;
	do {
		XNextEvent(GUI.display, &event);
	} while (event.type != MapNotify || event.xmap.event != GUI.window);

#ifdef USE_XVIDEO
	if (GUI.use_xvideo)
	{
		GUI.use_xvideo = SetupXvideo();
	}
#endif

	GUI.pixel_format = 565;

	SetupImage();

	switch (GUI.depth)
	{
		default:
		case 32:
			GUI.bytes_per_pixel = 4;
			break;

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

		case 15:
		case 16:
			GUI.bytes_per_pixel = 2;
			break;
	}

	printf("Using internal pixel format %d\n",GUI.pixel_format);
}

void S9xDeinitDisplay (void)
{
	TakedownImage();
	if (GUI.display != NULL)
	{
#ifdef USE_XVIDEO
		if (GUI.use_xvideo)
		{
			XvUngrabPort(GUI.display,GUI.xv_port,CurrentTime);
		}
#endif
		S9xTextMode();
		XSync(GUI.display, False);
		XCloseDisplay(GUI.display);
	}
	S9xBlitFilterDeinit();
	S9xBlit2xSaIFilterDeinit();
	S9xBlitHQ2xFilterDeinit();
}

static void SetupImage (void)
{
	TakedownImage();

	// Create new image struct
	GUI.image = (Image *) calloc(sizeof(Image), 1);

#ifdef USE_XVIDEO
	if (GUI.use_xvideo)
		SetupXvImage();
	if (!GUI.use_xvideo)
#endif
		SetupXImage();

	GUI.filter_buffer = (uint8 *) calloc((SNES_WIDTH * 2) * 2 * (SNES_HEIGHT_EXTENDED * 2), 1);
	if (!GUI.filter_buffer)
		FatalError("Failed to allocate GUI.filter_buffer.");

#ifdef USE_XVIDEO
	if ((GUI.depth == 15 || GUI.depth == 16) && GUI.xv_format != FOURCC_YUY2 && GUI.xv_format != FOURCC_I420)
#else
	if (GUI.depth == 15 || GUI.depth == 16)
#endif
	{
		GUI.blit_screen_pitch = GUI.image->bytes_per_line;
		GUI.blit_screen       = (uint8 *) GUI.image->data;
		GUI.need_convert      = FALSE;
	}
	else
	{
		GUI.blit_screen_pitch = (SNES_WIDTH * 2) * 2;
		GUI.blit_screen       = GUI.filter_buffer;
		GUI.need_convert      = TRUE;
	}
	if (GUI.need_convert) { printf("\tImage conversion needed before blit.\n"); }

	S9xGraphicsInit();
}

static void TakedownImage (void)
{
	if (GUI.filter_buffer)
	{
		free(GUI.filter_buffer);
		GUI.filter_buffer = NULL;
	}

	if (GUI.image)
	{
#ifdef USE_XVIDEO
		if (GUI.use_xvideo)
			TakedownXvImage();
		else
#endif
			TakedownXImage();

		free(GUI.image);
		GUI.image = NULL;
	}

	S9xGraphicsDeinit();
}

static void SetupXImage (void)
{
#ifdef MITSHM
	GUI.use_shared_memory = TRUE;

	int		major, minor;
	Bool	shared;

	if (!XShmQueryVersion(GUI.display, &major, &minor, &shared) || !shared)
		GUI.image->ximage = NULL;
	else
		GUI.image->ximage = XShmCreateImage(GUI.display, GUI.visual, GUI.depth, ZPixmap, NULL, &GUI.sm_info, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2);

	if (!GUI.image->ximage)
		GUI.use_shared_memory = FALSE;
	else
	{
		// set main Image struct vars
		GUI.image->height = GUI.image->ximage->height;
		GUI.image->bytes_per_line = GUI.image->ximage->bytes_per_line;
		GUI.image->data_size = GUI.image->bytes_per_line * GUI.image->height;

		GUI.sm_info.shmid = shmget(IPC_PRIVATE, GUI.image->data_size, IPC_CREAT | 0777);
		if (GUI.sm_info.shmid < 0)
		{
			XDestroyImage(GUI.image->ximage);
			GUI.use_shared_memory = FALSE;
		}
		else
		{
			GUI.image->ximage->data = GUI.sm_info.shmaddr = (char *) shmat(GUI.sm_info.shmid, 0, 0);
			if (!GUI.image->ximage->data)
			{
				XDestroyImage(GUI.image->ximage);
				shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
				GUI.use_shared_memory = FALSE;
			}
			else
			{
				GUI.sm_info.readOnly = False;

				XSetErrorHandler(ErrorHandler);
				XShmAttach(GUI.display, &GUI.sm_info);
				XSync(GUI.display, False);

				// X Error handler might clear GUI.use_shared_memory if XShmAttach failed.
				if (!GUI.use_shared_memory)
				{
					XDestroyImage(GUI.image->ximage);
					shmdt(GUI.sm_info.shmaddr);
					shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
				} else
					printf("Created XShmImage, size %d\n",GUI.image->data_size);
			}
		}
	}

	if (!GUI.use_shared_memory)
	{
		fprintf(stderr, "use_shared_memory failed, switching to XPutImage.\n");
#endif
		GUI.image->ximage = XCreateImage(GUI.display, GUI.visual, GUI.depth, ZPixmap, 0, NULL, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2, BitmapUnit(GUI.display), 0);
		// set main Image struct vars
		GUI.image->height = GUI.image->ximage->height;
		GUI.image->bytes_per_line = GUI.image->ximage->bytes_per_line;
		GUI.image->data_size = GUI.image->bytes_per_line * GUI.image->height;

		GUI.image->ximage->data = (char *) malloc(GUI.image->data_size);
		if (!GUI.image->ximage || !GUI.image->ximage->data)
			FatalError("XCreateImage failed.");
		printf("Created XImage, size %d\n",GUI.image->data_size);
#ifdef MITSHM
	}
#endif

	// Set final values
	GUI.image->bits_per_pixel = GUI.image->ximage->bits_per_pixel;
	GUI.image->data = GUI.image->ximage->data;

#ifdef LSB_FIRST
	GUI.image->ximage->byte_order = LSBFirst;
#else
	GUI.image->ximage->byte_order = MSBFirst;
#endif
}

static void TakedownXImage (void)
{
	if (GUI.image->ximage)
	{
	#ifdef MITSHM
		if (GUI.use_shared_memory)
		{
			XShmDetach(GUI.display, &GUI.sm_info);
			GUI.image->ximage->data = NULL;
			XDestroyImage(GUI.image->ximage);
			if (GUI.sm_info.shmaddr)
				shmdt(GUI.sm_info.shmaddr);
			if (GUI.sm_info.shmid >= 0)
				shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
			GUI.image->ximage = NULL;
		}
		else
	#endif
		{
			XDestroyImage(GUI.image->ximage);
			GUI.image->ximage = NULL;
		}
	}
}

#ifdef USE_XVIDEO
static void SetupXvImage (void)
{
#ifdef MITSHM
	GUI.use_shared_memory = TRUE;

	int		major, minor;
	Bool	shared;

	if (!XShmQueryVersion(GUI.display, &major, &minor, &shared) || !shared)
		GUI.image->xvimage = NULL;
	else
		GUI.image->xvimage = XvShmCreateImage(GUI.display, GUI.xv_port, GUI.xv_format, NULL, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2, &GUI.sm_info);

	if (!GUI.image->xvimage)
		GUI.use_shared_memory = FALSE;
	else
	{
		GUI.image->height = SNES_HEIGHT_EXTENDED * 2;
		GUI.image->data_size = GUI.image->xvimage->data_size;
		GUI.image->bytes_per_line = GUI.image->data_size / GUI.image->height;
		GUI.sm_info.shmid = shmget(IPC_PRIVATE, GUI.image->data_size, IPC_CREAT | 0777);
		if (GUI.sm_info.shmid < 0)
		{
			XFree(GUI.image->xvimage);
			GUI.use_shared_memory = FALSE;
		}
		else
		{
			GUI.image->xvimage->data = GUI.sm_info.shmaddr = (char *) shmat(GUI.sm_info.shmid, 0, 0);
			if (!GUI.image->xvimage->data)
			{
				XFree(GUI.image->xvimage);
				shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
				GUI.use_shared_memory = FALSE;
			}
			else
			{
				GUI.sm_info.readOnly = False;

				XSetErrorHandler(ErrorHandler);
				XShmAttach(GUI.display, &GUI.sm_info);
				XSync(GUI.display, False);

				// X Error handler might clear GUI.use_shared_memory if XShmAttach failed.
				if (!GUI.use_shared_memory)
				{
					XFree(GUI.image->xvimage);
					shmdt(GUI.sm_info.shmaddr);
					shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
				} else
					printf("Created XvShmImage, size %d\n",GUI.image->data_size);
			}
		}
	}

	if (!GUI.use_shared_memory)
	{
		fprintf(stderr, "use_shared_memory failed, switching to XvPutImage.\n");
#endif
		GUI.image->xvimage = XvCreateImage(GUI.display, GUI.xv_port, GUI.xv_format, NULL, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2);
		GUI.image->height = SNES_HEIGHT_EXTENDED * 2;
		GUI.image->data_size = GUI.image->xvimage->data_size;
		GUI.image->bytes_per_line = GUI.image->data_size / GUI.image->height;

		GUI.image->xvimage->data = (char *) malloc(GUI.image->data_size);
		if (!GUI.image->xvimage || !GUI.image->xvimage->data)
		{
			fprintf(stderr, "XvCreateImage failed, falling back to software blit.\n");
			GUI.use_xvideo = FALSE;
			return;
		}
		printf("Created XvImage, size %d\n",GUI.image->data_size);
#ifdef MITSHM
	}
#endif
	// Set final values
	GUI.image->bits_per_pixel = GUI.xv_bpp;
	GUI.image->data = GUI.image->xvimage->data;
}

static void TakedownXvImage (void)
{
	if (GUI.image->xvimage)
	{
	#ifdef MITSHM
		if (GUI.use_shared_memory)
		{
			XShmDetach(GUI.display, &GUI.sm_info);
			GUI.image->xvimage->data = NULL;
			XFree(GUI.image->xvimage);
			if (GUI.sm_info.shmaddr)
				shmdt(GUI.sm_info.shmaddr);
			if (GUI.sm_info.shmid >= 0)
				shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
			GUI.image->xvimage = NULL;
		}
		else
	#endif
		{
			free(GUI.image->xvimage->data);
			//GUI.image->xvimage->data = NULL;
			XFree(GUI.image->xvimage);
			GUI.image->xvimage = NULL;
		}
	}
}
#endif

#if defined(USE_XVIDEO) && !defined(USE_LIBYUV)
void rgb565ToRgb24(uint8 *dest, uint8 *s, int width, int height) {
    for (int i=0, j=0; i< width * height * 2 ; i+=2) {
        uint16 c = s[i] + (s[i+1]<<8);
        uint8 r = uint8(((c & 0xF800) >> 11) << 3);
        uint8 g = uint8(((c & 0x7E0) >> 5) << 2);
        uint8 b = uint8(((c & 0x1F)) << 3);
        dest[j++]=r;
        dest[j++]=g;
        dest[j++]=b;
    }
}

void rgb24ToI420(uint8 *dest, uint8 *rgb, int width, int height) {
    int image_size = width * height;
    int upos = image_size;
    int vpos = upos + upos / 4;
    int i = 0;
    for (int line = 0; line < height; ++line) {
        if( !(line % 2) ) {
            for ( int x = 0; x < width; x += 2 ) {
                uint8 r = rgb[3 * i];
                uint8 g = rgb[3 * i + 1];
                uint8 b = rgb[3 * i + 2];

                dest[i++] = ((66*r + 129*g + 25*b) >> 8) + 16;

                dest[upos++] = ((-38*r + -74*g + 112*b) >> 8) + 128;
                dest[vpos++] = ((112*r + -94*g + -18*b) >> 8) + 128;

                r = rgb[3 * i];
                g = rgb[3 * i + 1];
                b = rgb[3 * i + 2];

                dest[i++] = ((66*r + 129*g + 25*b) >> 8) + 16;
            }
        } else {
            for (int x = 0; x < width; x += 1) {
                uint8 r = rgb[3 * i];
                uint8 g = rgb[3 * i + 1];
                uint8 b = rgb[3 * i + 2];

                dest[i++] = ((66*r + 129*g + 25*b) >> 8) + 16;
            }
        }
    }
}
#endif

void S9xPutImage (int width, int height)
{
	static int	prevWidth = 0, prevHeight = 0;
	int			copyWidth, copyHeight;
	Blitter		blitFn = NULL;

	if (GUI.video_mode == VIDEOMODE_BLOCKY || GUI.video_mode == VIDEOMODE_TV || GUI.video_mode == VIDEOMODE_SMOOTH)
		if ((width <= SNES_WIDTH) && ((prevWidth != width) || (prevHeight != height)))
			S9xBlitClearDelta();

	if (width <= SNES_WIDTH)
	{
		if (height > SNES_HEIGHT_EXTENDED)
		{
			copyWidth  = width * 2;
			copyHeight = height;
			blitFn = S9xBlitPixSimple2x1;
		}
		else
		{
			copyWidth  = width  * 2;
			copyHeight = height * 2;

			switch (GUI.video_mode)
			{
				case VIDEOMODE_BLOCKY:		blitFn = S9xBlitPixSimple2x2;		break;
				case VIDEOMODE_TV:			blitFn = S9xBlitPixTV2x2;			break;
				case VIDEOMODE_SMOOTH:		blitFn = S9xBlitPixSmooth2x2;		break;
				case VIDEOMODE_SUPEREAGLE:	blitFn = S9xBlitPixSuperEagle16;	break;
				case VIDEOMODE_2XSAI:		blitFn = S9xBlitPix2xSaI16;			break;
				case VIDEOMODE_SUPER2XSAI:	blitFn = S9xBlitPixSuper2xSaI16;	break;
				case VIDEOMODE_EPX:			blitFn = S9xBlitPixEPX16;			break;
				case VIDEOMODE_HQ2X:		blitFn = S9xBlitPixHQ2x16;			break;
			}
		}
	}
	else
	if (height <= SNES_HEIGHT_EXTENDED)
	{
		copyWidth  = width;
		copyHeight = height * 2;

		switch (GUI.video_mode)
		{
			default:					blitFn = S9xBlitPixSimple1x2;	break;
			case VIDEOMODE_TV:			blitFn = S9xBlitPixTV1x2;		break;
		}
	}
	else
	{
		copyWidth  = width;
		copyHeight = height;
		blitFn = S9xBlitPixSimple1x1;
	}
	blitFn((uint8 *) GFX.Screen, GFX.Pitch, GUI.blit_screen, GUI.blit_screen_pitch, width, height);

	if (height < prevHeight)
	{
		int	p = GUI.blit_screen_pitch >> 2;
		for (int y = SNES_HEIGHT * 2; y < SNES_HEIGHT_EXTENDED * 2; y++)
		{
			uint32	*d = (uint32 *) (GUI.blit_screen + y * GUI.blit_screen_pitch);
			for (int x = 0; x < p; x++)
				*d++ = 0;
		}
	}

#ifdef USE_XVIDEO
	// Adjust source blit region if SNES would only fill half the screen.
	if (height <= SNES_HEIGHT_EXTENDED)
		GUI.imageHeight = height * 2;

	if (GUI.use_xvideo && (GUI.xv_format == FOURCC_YUY2))
	{
		uint16 *s = (uint16 *)GUI.blit_screen;
		uint8 *d = (uint8 *)GUI.image->data;

		// convert GUI.blit_screen and copy to XV image
		for (int y = 0; y < copyHeight; y++)
		{
			for (int x = 0; x < SNES_WIDTH * 2; x += 2)
			{
				// Read two RGB pxls
				//  Now that we are RGB565 throughout, need to drop the Green LSB
				unsigned short rgb1 = (*s & 0xFFC0) >> 1 | (*s & 0x001F); s++;
				unsigned short rgb2 = (*s & 0xFFC0) >> 1 | (*s & 0x001F); s++;

				// put two YUYV pxls
				// lum1
				*d = GUI.y_table[rgb1]; d++;
				// U
				*d = (GUI.u_table[rgb1] + GUI.u_table[rgb2]) / 2; d++;
				// lum2
				*d = GUI.y_table[rgb2]; d++;
				// V
				*d = (GUI.v_table[rgb1] + GUI.v_table[rgb2]) / 2; d++;
			}
		}
	} else if (GUI.use_xvideo && (GUI.xv_format == FOURCC_I420)) {
        uint8 *s = (uint8 *)GUI.blit_screen;
        uint8 *d = (uint8 *)GUI.image->data;

        int tw = SNES_WIDTH * 2;
        int th = SNES_HEIGHT_EXTENDED * 2;

#ifdef USE_LIBYUV
        libyuv::RGB565ToI420(
            s, tw*2,
            d, tw,
            d + tw*th, tw/2,
            d + tw*th + tw*th/4, tw/2,
            tw, th);
#else
        uint8 *ns = (uint8 *) calloc(tw*th*3, 1);
        //convert to RGB24
        rgb565ToRgb24(ns, s, tw, th);
        //convert to I420
        rgb24ToI420(d, ns, tw, th);
        free(ns);
#endif
        GUI.need_convert =0;
    }
	else
#endif
	if (GUI.need_convert)
	{
		if (GUI.bytes_per_pixel == 3)
			Convert16To24Packed(copyWidth, copyHeight);
		else
			Convert16To24(copyWidth, copyHeight);
	}

	Repaint(TRUE);

	prevWidth  = width;
	prevHeight = height;
}

static void Convert16To24 (int width, int height)
{
	if (GUI.pixel_format == 565)
	{
		for (int y = 0; y < height; y++)
		{
			uint16	*s = (uint16 *) (GUI.blit_screen + y * GUI.blit_screen_pitch);
			uint32	*d = (uint32 *) (GUI.image->data + y * GUI.image->bytes_per_line);

			for (int x = 0; x < width; x++)
			{
				uint32	pixel = *s++;
				*d++ = (((pixel >> 11) & 0x1f) << (GUI.red_shift + 3)) | (((pixel >> 6) & 0x1f) << (GUI.green_shift + 3)) | ((pixel & 0x1f) << (GUI.blue_shift + 3));
			}
		}
	}
	else
	{
		for (int y = 0; y < height; y++)
		{
			uint16	*s = (uint16 *) (GUI.blit_screen + y * GUI.blit_screen_pitch);
			uint32	*d = (uint32 *) (GUI.image->data + y * GUI.image->bytes_per_line);

			for (int x = 0; x < width; x++)
			{
				uint32	pixel = *s++;
				*d++ = (((pixel >> 10) & 0x1f) << (GUI.red_shift + 3)) | (((pixel >> 5) & 0x1f) << (GUI.green_shift + 3)) | ((pixel & 0x1f) << (GUI.blue_shift + 3));
			}
		}
	}
}

static void Convert16To24Packed (int width, int height)
{
	if (GUI.pixel_format == 565)
	{
		for (int y = 0; y < height; y++)
		{
			uint16	*s = (uint16 *) (GUI.blit_screen + y * GUI.blit_screen_pitch);
			uint8	*d = (uint8 *)  (GUI.image->data + y * GUI.image->bytes_per_line);

		#ifdef LSB_FIRST
			if (GUI.red_shift < GUI.blue_shift)
		#else
			if (GUI.red_shift > GUI.blue_shift)
		#endif
			{
				for (int x = 0; x < width; x++)
				{
					uint32	pixel = *s++;
					*d++ = (pixel >> (11 - 3)) & 0xf8;
					*d++ = (pixel >>  (6 - 3)) & 0xf8;
					*d++ = (pixel & 0x1f) << 3;
				}
			}
			else
			{
				for (int x = 0; x < width; x++)
				{
					uint32	pixel = *s++;
					*d++ = (pixel & 0x1f) << 3;
					*d++ = (pixel >>  (6 - 3)) & 0xf8;
					*d++ = (pixel >> (11 - 3)) & 0xf8;
				}
			}
		}
	}
	else
	{
		for (int y = 0; y < height; y++)
		{
			uint16	*s = (uint16 *) (GUI.blit_screen + y * GUI.blit_screen_pitch);
			uint8	*d = (uint8 *)  (GUI.image->data + y * GUI.image->bytes_per_line);

		#ifdef LSB_FIRST
			if (GUI.red_shift < GUI.blue_shift)
		#else
			if (GUI.red_shift > GUI.blue_shift)
		#endif
			{
				for (int x = 0; x < width; x++)
				{
					uint32	pixel = *s++;
					*d++ = (pixel >> (10 - 3)) & 0xf8;
					*d++ = (pixel >>  (5 - 3)) & 0xf8;
					*d++ = (pixel & 0x1f) << 3;
				}
			}
			else
			{
				for (int x = 0; x < width; x++)
				{
					uint32	pixel = *s++;
					*d++ = (pixel & 0x1f) << 3;
					*d++ = (pixel >>  (5 - 3)) & 0xf8;
					*d++ = (pixel >> (10 - 3)) & 0xf8;
				}
			}
		}
	}
}

static void Repaint (bool8 isFrameBoundry)
{
#ifdef USE_XVIDEO
	if (GUI.use_xvideo)
	{
#ifdef MITSHM
		if (GUI.use_shared_memory)
		{
			XvShmPutImage(GUI.display, GUI.xv_port, GUI.window, GUI.gc, GUI.image->xvimage,
				0, 0, SNES_WIDTH * 2, GUI.imageHeight,
				GUI.x_offset, GUI.y_offset, GUI.scale_w, GUI.scale_h, False);
		}
		else
#endif
		XvPutImage(GUI.display, GUI.xv_port, GUI.window, GUI.gc, GUI.image->xvimage,
			0, 0, SNES_WIDTH * 2, GUI.imageHeight,
			GUI.x_offset, GUI.y_offset, GUI.scale_w, GUI.scale_h);
	}
	else
#endif
#ifdef MITSHM
	if (GUI.use_shared_memory)
	{
		XShmPutImage(GUI.display, GUI.window, GUI.gc, GUI.image->ximage, 0, 0, GUI.x_offset, GUI.y_offset, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2, False);
		XSync(GUI.display, False); // Is this double-sync?  See XQueryPointer below...
	}
	else
#endif
		XPutImage(GUI.display, GUI.window, GUI.gc, GUI.image->ximage, 0, 0, GUI.x_offset, GUI.y_offset, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2);

	Window			root, child;
	int				root_x, root_y, 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(GUI.display, GUI.window, &root, &child, &root_x, &root_y, &x, &y, &mask);

	if (x >= 0 && y >= 0 && x < SNES_WIDTH * 2 && y < SNES_HEIGHT_EXTENDED * 2)
	{
		GUI.mouse_x = x >> 1;
		GUI.mouse_y = y >> 1;

		if (mask & Mod1Mask)
		{
			if (!GUI.mod1_pressed)
			{
				GUI.mod1_pressed = TRUE;
				XDefineCursor(GUI.display, GUI.window, GUI.cross_hair_cursor);
			}
		}
		else
		if (GUI.mod1_pressed)
		{
			GUI.mod1_pressed = FALSE;
			XDefineCursor(GUI.display, GUI.window, GUI.point_cursor);
		}
	}
}

void S9xTextMode (void)
{
	SetXRepeat(TRUE);
}

void S9xGraphicsMode (void)
{
	SetXRepeat(FALSE);
}

static bool8 CheckForPendingXEvents (Display *display)
{
#ifdef SELECT_BROKEN_FOR_SIGNALS
	int	arg = 0;

	return (XEventsQueued(display, QueuedAlready) || (ioctl(ConnectionNumber(display), FIONREAD, &arg) == 0 && arg));
#else
	return (XPending(display));
#endif
}

void S9xLatchJSEvent ()
{
	// record that a JS event happened and was reported to the engine
	GUI.js_event_latch = TRUE;
}

void S9xProcessEvents (bool8 block)
{
	// Kick the screensaver if a joystick event occurred
	if (GUI.js_event_latch == TRUE) {
		XWarpPointer(GUI.display, None, None, 0, 0, 0, 0, 0, 0);
		GUI.js_event_latch = FALSE;
	}

	// Process all other X events
	while (block || CheckForPendingXEvents(GUI.display))
	{
		XEvent	event;

		XNextEvent(GUI.display, &event);
		block = FALSE;

		switch (event.type)
		{
			case KeyPress:
			case KeyRelease:
				S9xReportButton(((event.xkey.state & (ShiftMask | Mod1Mask | ControlMask | Mod4Mask)) << 8) | event.xkey.keycode, event.type == KeyPress);
			#if 1
				{
					KeyCode	kc = XKeysymToKeycode(GUI.display, XKeycodeToKeysym(GUI.display, event.xkey.keycode, 0));
					if (event.xkey.keycode != kc)
						S9xReportButton(((event.xkey.state & (ShiftMask | Mod1Mask | ControlMask | Mod4Mask)) << 8) | kc, event.type == KeyPress);
				}
			#endif
				break;

			case FocusIn:
				SetXRepeat(FALSE);
				XFlush(GUI.display);
				break;

			case FocusOut:
				SetXRepeat(TRUE);
				XFlush(GUI.display);
				break;

			case ConfigureNotify:
				break;

			case ButtonPress:
			case ButtonRelease:
				S9xReportButton(0x40000000 | (event.xbutton.button - 1), event.type == ButtonPress);
				break;

			case Expose:
				Repaint(FALSE);
				break;
		}
	}
}

const char * S9xSelectFilename (const char *def, const char *dir1, const char *ext1, const char *title)
{
	return (NULL);
}

void S9xMessage (int type, int number, const char *message)
{
	const int	max = 36 * 3;
	static char	buffer[max + 1];

	fprintf(stdout, "%s\n", message);
	strncpy(buffer, message, max + 1);
	buffer[max] = 0;
	S9xSetInfoString(buffer);
}

const char * S9xStringInput (const char *message)
{
	static char	buffer[256];

	printf("%s: ", message);
	fflush(stdout);

	if (fgets(buffer, sizeof(buffer) - 2, stdin))
		return (buffer);

	return (NULL);
}

void S9xSetTitle (const char *string)
{
	XStoreName(GUI.display, GUI.window, string);
	XFlush(GUI.display);
}

static void SetXRepeat (bool8 state)
{
	if (state)
		XAutoRepeatOn(GUI.display);
	else
		XAutoRepeatOff(GUI.display);
}

s9xcommand_t S9xGetDisplayCommandT (const char *n)
{
	s9xcommand_t	cmd;

	cmd.type         = S9xBadMapping;
	cmd.multi_press  = 0;
	cmd.button_norpt = 0;
	cmd.port[0]      = 0xff;
	cmd.port[1]      = 0;
	cmd.port[2]      = 0;
	cmd.port[3]      = 0;

	return (cmd);
}

char * S9xGetDisplayCommandName (s9xcommand_t cmd)
{
	return (strdup("None"));
}

void S9xHandleDisplayCommand (s9xcommand_t cmd, int16 data1, int16 data2)
{
	return;
}

bool8 S9xMapDisplayInput (const char *n, s9xcommand_t *cmd)
{
	int	i, d;

	if (!isdigit(n[1]) || !isdigit(n[2]) || n[3] != ':')
		goto unrecog;

	d = ((n[1] - '0') * 10 + (n[2] - '0')) << 24;

	switch (n[0])
	{
		case 'K':
		{
			KeyCode	kc;
			KeySym	ks;

			d |= 0x00000000;

			for (i = 4; n[i] != '\0' && n[i] != '+'; i++) ;

			if (n[i] == '\0' || i == 4)
				i = 4;
			else
			{
				for (i = 4; n[i] != '+'; i++)
				{
					switch (n[i])
					{
						case 'S': d |= ShiftMask   << 8; break;
						case 'C': d |= ControlMask << 8; break;
						case 'A': d |= Mod1Mask    << 8; break;
						case 'M': d |= Mod4Mask    << 8; break;
						default:  goto unrecog;
					}
				}

				i++;
			}

			if ((ks = XStringToKeysym(n + i)) == NoSymbol)
				goto unrecog;
			if ((kc = XKeysymToKeycode(GUI.display, ks)) == 0)
				goto unrecog;

			d |= kc & 0xff;

			return (S9xMapButton(d, *cmd, false));
		}

		case 'M':
		{
			char	*c;
			int		j;

			d |= 0x40000000;

			if (!strncmp(n + 4, "Pointer", 7))
			{
				d |= 0x8000;

				if (n[11] == '\0')
					return (S9xMapPointer(d, *cmd, true));

				i = 11;
			}
			else
			if (n[4] == 'B')
				i = 5;
			else
				goto unrecog;

			d |= j = strtol(n + i, &c, 10);

			if ((c != NULL && *c != '\0') || j > 0x7fff)
				goto unrecog;

			if (d & 0x8000)
				return (S9xMapPointer(d, *cmd, true));

			return (S9xMapButton(d, *cmd, false));
		}

		default:
			break;
	}

unrecog:
	char	*err = new char[strlen(n) + 34];

	sprintf(err, "Unrecognized input device name '%s'", n);
	perror(err);
	delete [] err;

	return (false);
}

bool S9xDisplayPollButton (uint32 id, bool *pressed)
{
	return (false);
}

bool S9xDisplayPollAxis (uint32 id, int16 *value)
{
	return (false);
}

bool S9xDisplayPollPointer (uint32 id, int16 *x, int16 *y)
{
	if ((id & 0xc0008000) != 0x40008000)
		return (false);

	int	d = (id >> 24) & 0x3f,
		n = id & 0x7fff;

	if (d != 0 || n != 0)
		return (false);

	*x = GUI.mouse_x;
	*y = GUI.mouse_y;

	return (true);
}
