/* ES40 emulator.
 * Copyright (C) 2007-2008 by the ES40 Emulator Project
 *
 * WWW    : http://sourceforge.net/projects/es40
 * E-mail : camiel@camicom.com
 *
 *  This file is based upon Bochs.
 *
 *  Copyright (C) 2002  MandrakeSoft S.A.
 *
 *    MandrakeSoft S.A.
 *    43, rue d'Aboukir
 *    75002 Paris - France
 *    http://www.linux-mandrake.com/
 *    http://www.mandrakesoft.com/
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 *  Much of this file was written by:
 *  David Ross
 *  dross@pobox.com
 */

 /**
  * \file
  * Win32 GUI implementation. Allows use of gfx without SDL on WIndows
  *
  * $Id$
  *
  * X-1.5        Camiel Vanderhoeven                             05-MAR-2008
  *      Multi-threading version.
  *
  * X-1.4        Camiel Vanderhoeven                             12-FEB-2008
  *      Moved keyboard code into it's own class (CKeyboard)
  *
  * X-1.3        Camiel Vanderhoeven                             22-JAN-2008
  *      Commented out mousewheel-code, doesn't work with older win32
  *      versions.
  *
  * X-1.2        Camiel Vanderhoeven                             19-JAN-2008
  *      Changed window title.
  *
  * X-1.1        Camiel Vanderhoeven                             19-JAN-2008
  *      Initial version for ES40 emulator.
  *
  **/
#include "../StdAfx.h"

#if defined(_WIN32)
#include <process.h>

#include "gui_win32_font.h"
#include "keymap.h"
#include "../Configurator.h"
#include "../VGA.h"
#include "../Keyboard.h"

#include "gui.h"

class bx_win32_gui_c : public bx_gui_c
{
public:
	bx_win32_gui_c(CConfigurator* cfg)
	{
		myCfg = cfg;
		bx_keymap = new bx_keymap_c(cfg);
	};

	virtual void  specific_init(unsigned x_tilesize, unsigned y_tilesize) override;
	virtual void  text_update(u8* old_text, u8* new_text,
		unsigned long cursor_x, unsigned long cursor_y,
		bx_vga_tminfo_t tm_info, unsigned rows) override;
	virtual void  graphics_tile_update(u8* snapshot, unsigned x, unsigned y) override;
	virtual void  handle_events(void) override;
	virtual void  flush(void) override;
	virtual void  clear_screen(void) override;
	virtual bool  palette_change(unsigned index, unsigned red, unsigned green,
		unsigned blue) override;
	virtual void  dimension_update(unsigned x, unsigned y, unsigned fheight = 0,
		unsigned fwidth = 0, unsigned bpp = 8) override;
	virtual void  mouse_enabled_changed_specific(bool val) override;
	virtual void  exit(void) override;

	virtual void  get_capabilities(u16* xres, u16* yres, u16* bpp) override;

	virtual void  graphics_frame_update(const u32* pixels, unsigned width, unsigned height) override;
private:
	CConfigurator* myCfg;
};

// declare one instance of the gui object and call macro to insert the
// plugin code
static bx_win32_gui_c* theGui = NULL;
IMPLEMENT_GUI_PLUGIN_CODE(win32)
#define EXIT_GUI_SHUTDOWN         1
#define EXIT_GMH_FAILURE          2
#define EXIT_FONT_BITMAP_ERROR    3
#define EXIT_NORMAL               4
#define EXIT_HEADER_BITMAP_ERROR  5

// Keyboard/mouse stuff
#define SCANCODE_BUFSIZE  256
#define MOUSE_PRESSED     0x20000000
#define MOUSE_MOTION      0x22000000
#define BX_SYSKEY         (KF_UP | KF_REPEAT | KF_ALTDOWN)
void  enq_key_event(u32, u32);
void  enq_mouse_event(void);

struct QueueEvent
{
	u32 key_event;
	int mouse_x;
	int mouse_y;
	int mouse_z;
	int mouse_button_state;
};
QueueEvent* deq_key_event(void);

static QueueEvent     keyevents[SCANCODE_BUFSIZE];
static unsigned       head = 0, tail = 0;
static int            mouse_button_state = 0;
static int            ms_xdelta = 0, ms_ydelta = 0, ms_zdelta = 0;
static int            ms_lastx = 0, ms_lasty = 0;
static int            ms_savedx = 0, ms_savedy = 0;
static BOOL           mouseCaptureMode, mouseCaptureNew, mouseToggleReq;
static unsigned long  workerThread = 0;
static DWORD          workerThreadID = 0;
static int            mouse_buttons = 3;

// Graphics screen stuff
static unsigned       x_tilesize = 0, y_tilesize = 0;
static BITMAPINFO* bitmap_info = (BITMAPINFO*)0;
static RGBQUAD* cmap_index; // indeces into system colormap
static HBITMAP        MemoryBitmap = NULL;
static HDC            MemoryDC = NULL;
static RECT           updated_area;
static BOOL           updated_area_valid = false;
static HWND           desktopWindow;
static RECT           desktop;
static BOOL           queryFullScreen = false;
static int            desktop_x, desktop_y;

// Text mode screen stuff
static unsigned       prev_cursor_x = 0;
static unsigned       prev_cursor_y = 0;
static HBITMAP        vgafont[256];
static int            xChar = 8, yChar = 16;
static unsigned int   text_rows = 25, text_cols = 80;
static u8             text_pal_idx[16];

static u8             h_panning = 0, v_panning = 0;
static u16            line_compare = 1023;

// Misc stuff
static unsigned       dimension_x, dimension_y, current_bpp;
static unsigned       stretched_x, stretched_y;
static unsigned       stretch_factor = 1;
static BOOL           BxTextMode = true;
static BOOL           fix_size = false;
static HWND           hotKeyReceiver = NULL;
static HWND           saveParent = NULL;

static char           szAppName[] = "ES40 Emulator";
static char           szWindowName[] = "ES40 Emulator";

typedef struct
{
	HINSTANCE         hInstance;

	CRITICAL_SECTION  drawCS;
	CRITICAL_SECTION  keyCS;
	CRITICAL_SECTION  mouseCS;

	int               kill;         // reason for terminateEmul(int)
	BOOL              UIinited;
	HWND              mainWnd;
	HWND              simWnd;
} sharedThreadInfo;

sharedThreadInfo      stInfo;

LRESULT CALLBACK      mainWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK      simWndProc(HWND, UINT, WPARAM, LPARAM);
VOID                  UIThread(PVOID);
void                  terminateEmul(int);
void                  create_vga_font(void);
static unsigned char  reverse_bitorder(unsigned char);
void                  DrawBitmap(HDC, HBITMAP, int, int, int, int, int, int,
	DWORD, unsigned char);
void                  DrawChar(HDC, unsigned char, int, int, unsigned char cColor,
	int, int);
void                  updateUpdated(int, int, int, int);

u32                   win32_to_bx_key[2][0x100] =
{
  {     /* normal-keys */
	/* 0x00 - 0x0f */
	0,
	BX_KEY_ESC,
	BX_KEY_1,
	BX_KEY_2,
	BX_KEY_3,
	BX_KEY_4,
	BX_KEY_5,
	BX_KEY_6,
	  BX_KEY_7,
	  BX_KEY_8,
	  BX_KEY_9,
	  BX_KEY_0,
	  BX_KEY_MINUS,
	  BX_KEY_EQUALS,
		BX_KEY_BACKSPACE,
		BX_KEY_TAB,

		/* 0x10 - 0x1f */
		BX_KEY_Q,
		BX_KEY_W,
		BX_KEY_E,
		BX_KEY_R,
		BX_KEY_T,
		BX_KEY_Y,
		BX_KEY_U,
		  BX_KEY_I,
		  BX_KEY_O,
		  BX_KEY_P,
		  BX_KEY_LEFT_BRACKET,
		  BX_KEY_RIGHT_BRACKET,
			BX_KEY_ENTER,
			BX_KEY_CTRL_L,
			BX_KEY_A,
			BX_KEY_S,

			/* 0x20 - 0x2f */
			BX_KEY_D,
			BX_KEY_F,
			BX_KEY_G,
			BX_KEY_H,
			BX_KEY_J,
			BX_KEY_K,
			BX_KEY_L,
			  BX_KEY_SEMICOLON,
			  BX_KEY_SINGLE_QUOTE,
			  BX_KEY_GRAVE,
			  BX_KEY_SHIFT_L,
				BX_KEY_BACKSLASH,
				BX_KEY_Z,
				BX_KEY_X,
				BX_KEY_C,
				BX_KEY_V,

				/* 0x30 - 0x3f */
				BX_KEY_B,
				BX_KEY_N,
				BX_KEY_M,
				BX_KEY_COMMA,
				BX_KEY_PERIOD,
				BX_KEY_SLASH,
				  BX_KEY_SHIFT_R,
				  BX_KEY_KP_MULTIPLY,
				  BX_KEY_ALT_L,
				  BX_KEY_SPACE,
					BX_KEY_CAPS_LOCK,
					BX_KEY_F1,
					BX_KEY_F2,
					BX_KEY_F3,
					BX_KEY_F4,
					BX_KEY_F5,

					/* 0x40 - 0x4f */
					BX_KEY_F6,
					BX_KEY_F7,
					BX_KEY_F8,
					BX_KEY_F9,
					BX_KEY_F10,
					BX_KEY_PAUSE,
					  BX_KEY_SCRL_LOCK,
					  BX_KEY_KP_HOME,
					  BX_KEY_KP_UP,
					  BX_KEY_KP_PAGE_UP,
						BX_KEY_KP_SUBTRACT,
						BX_KEY_KP_LEFT,
						BX_KEY_KP_5,
						BX_KEY_KP_RIGHT,
						  BX_KEY_KP_ADD,
						  BX_KEY_KP_END,

						  /* 0x50 - 0x5f */
						  BX_KEY_KP_DOWN,
						  BX_KEY_KP_PAGE_DOWN,
						  BX_KEY_KP_INSERT,
							BX_KEY_KP_DELETE,
							0,
							0,
							BX_KEY_LEFT_BACKSLASH,
							BX_KEY_F11,
							  BX_KEY_F12,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,

							  /* 0x60 - 0x6f */
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,

							  /* 0x70 - 0x7f */
							  0,  /* Todo: "Katakana" key ( ibm 133 ) for Japanese 106 keyboard */
							  0,
							  0,
							  0,  /* Todo: "Ro" key ( ibm 56 ) for Japanese 106 keyboard */
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,  /* Todo: "convert" key ( ibm 132 ) for Japanese 106 keyboard */
							  0,
							  0,  /* Todo: "non-convert" key ( ibm 131 ) for Japanese 106 keyboard */
							  0,
							  0,  /* Todo: "Yen" key ( ibm 14 ) for Japanese 106 keyboard */
							  0,
							  0,
							},
							{     /* extended-keys */
							  /* 0x00 - 0x0f */
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,

							  /* 0x10 - 0x1f */
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  BX_KEY_KP_ENTER,
							  BX_KEY_CTRL_R,
							  0,
							  0,

							  /* 0x20 - 0x2f */
							  0,
							  BX_KEY_POWER_CALC,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,
							  0,

							  /* 0x30 - 0x3f */
							  0,
							  0,
							  BX_KEY_INT_HOME,
							  0,
							  0,
							  BX_KEY_KP_DIVIDE,
							  0,
							  BX_KEY_PRINT,
								BX_KEY_ALT_R,
								0,
								0,
								0,
								0,
								0,
								0,
								0,

								/* 0x40 - 0x4f */
								0,
								0,
								0,
								0,
								0,
								BX_KEY_NUM_LOCK,
								0,
								BX_KEY_HOME,
								BX_KEY_UP,
								  BX_KEY_PAGE_UP,
								  0,
								  BX_KEY_LEFT,
								  0,
								  BX_KEY_RIGHT,
								  0,
								  BX_KEY_END,

								  /* 0x50 - 0x5f */
								  BX_KEY_DOWN,
								  BX_KEY_PAGE_DOWN,
								  BX_KEY_INSERT,
									BX_KEY_DELETE,
									0,
									0,
									0,
									0,
									0,
									0,
									0,
									BX_KEY_WIN_L,
									BX_KEY_WIN_R,
									  BX_KEY_MENU,
									  BX_KEY_POWER_POWER,
									  BX_KEY_POWER_SLEEP,

									  /* 0x60 - 0x6f */
									  0,
									  0,
									  0,
									  BX_KEY_POWER_WAKE,
									  0,
									  BX_KEY_INT_SEARCH,
									  BX_KEY_INT_FAV,
									  0,
										BX_KEY_INT_STOP,
										BX_KEY_INT_FORWARD,
										BX_KEY_INT_BACK,
										BX_KEY_POWER_MYCOMP,
										  BX_KEY_INT_MAIL,
										  0,
										  0,
										  0,
										}
};

/* Macro to convert WM_ button state to BX button state */
#if defined(__MINGW32__) || defined(_MSC_VER)
VOID CALLBACK         MyTimer(HWND, UINT, UINT, DWORD);
void                  alarm(int);
void                  bx_signal_handler(int);
#endif
static void processMouseXY(int x, int y, int z, int windows_state,
	int implied_state_change)
{
	int bx_state;
	int old_bx_state;
	EnterCriticalSection(&stInfo.mouseCS);
	bx_state = ((windows_state & MK_LBUTTON) ? 1 : 0) +
		((windows_state & MK_RBUTTON) ? 2 : 0) +
		((windows_state & MK_MBUTTON) ? 4 : 0);
	old_bx_state = bx_state ^ implied_state_change;
	if (old_bx_state != mouse_button_state)
	{

		/* Make up for missing message */
		BX_INFO(("&&&missing mouse state change"));
		EnterCriticalSection(&stInfo.keyCS);
		enq_mouse_event();
		mouse_button_state = old_bx_state;
		enq_key_event(mouse_button_state, MOUSE_PRESSED);
		LeaveCriticalSection(&stInfo.keyCS);
	}

	ms_ydelta = ms_savedy - y;
	ms_xdelta = x - ms_savedx;
	ms_zdelta = z;
	ms_lastx = x;
	ms_lasty = y;
	if (bx_state != mouse_button_state)
	{
		EnterCriticalSection(&stInfo.keyCS);
		enq_mouse_event();
		mouse_button_state = bx_state;
		enq_key_event(mouse_button_state, MOUSE_PRESSED);
		LeaveCriticalSection(&stInfo.keyCS);
	}

	LeaveCriticalSection(&stInfo.mouseCS);
}

static void resetDelta()
{
	EnterCriticalSection(&stInfo.mouseCS);
	ms_savedx = ms_lastx;
	ms_savedy = ms_lasty;
	ms_ydelta = ms_xdelta = ms_zdelta = 0;
	LeaveCriticalSection(&stInfo.mouseCS);
}

static void cursorWarped()
{
	EnterCriticalSection(&stInfo.mouseCS);
	EnterCriticalSection(&stInfo.keyCS);
	enq_mouse_event();
	LeaveCriticalSection(&stInfo.keyCS);
	ms_lastx = stretched_x / 2;
	ms_lasty = stretched_y / 2;
	ms_savedx = ms_lastx;
	ms_savedy = ms_lasty;
	LeaveCriticalSection(&stInfo.mouseCS);
}

// GUI thread must be dead/done in order to call terminateEmul
void terminateEmul(int reason)
{

	// We know that Critical Sections were inited when x_tilesize has been set
	// See bx_win32_gui_c::specific_init
	if (x_tilesize != 0)
	{
		DeleteCriticalSection(&stInfo.drawCS);
		DeleteCriticalSection(&stInfo.keyCS);
		DeleteCriticalSection(&stInfo.mouseCS);
	}

	x_tilesize = 0;

	if (MemoryDC)
		DeleteDC(MemoryDC);
	if (MemoryBitmap)
		DeleteObject(MemoryBitmap);

	if (bitmap_info)
		delete[](char*) bitmap_info;

	for (unsigned c = 0; c < 256; c++)
		if (vgafont[c])
			DeleteObject(vgafont[c]);

	switch (reason)
	{
	case EXIT_GUI_SHUTDOWN:
		FAILURE(Graceful, "Window closed, exiting!");
		break;

	case EXIT_GMH_FAILURE:
		FAILURE(Win32, "GetModuleHandle failure!");
		break;

	case EXIT_FONT_BITMAP_ERROR:
		FAILURE(Win32, "Font bitmap creation failure!");
		break;

	case EXIT_NORMAL:
		break;
	}
}

// ::SPECIFIC_INIT()
//
// Called from gui.cc, once upon program startup, to allow for the
// specific GUI code (X11, BeOS, ...) to be initialized.
//
// tilewidth, tileheight: for optimization, graphics_tile_update() passes
//     only updated regions of the screen to the gui code to be redrawn.

//     These define the dimensions of a region (tile).
void bx_win32_gui_c::specific_init(unsigned tilewidth, unsigned tileheight)
{
	int i;

	// prepare for possible fullscreen mode
	desktopWindow = GetDesktopWindow();
	GetWindowRect(desktopWindow, &desktop);
	desktop_x = desktop.right - desktop.left;
	desktop_y = desktop.bottom - desktop.top;
	hotKeyReceiver = stInfo.simWnd;
	BX_INFO(("Desktop Window dimensions: %d x %d", desktop_x, desktop_y));

	static RGBQUAD  black_quad = { 0, 0, 0, 0 };
	stInfo.kill = 0;
	stInfo.UIinited = false;
	InitializeCriticalSection(&stInfo.drawCS);
	InitializeCriticalSection(&stInfo.keyCS);
	InitializeCriticalSection(&stInfo.mouseCS);

	x_tilesize = tilewidth;
	y_tilesize = tileheight;

	mouseCaptureMode = false;
	mouseCaptureNew = false;
	mouseToggleReq = false;

	mouse_buttons = GetSystemMetrics(SM_CMOUSEBUTTONS);
	BX_INFO(("Number of Mouse Buttons = %d", mouse_buttons));

	stInfo.hInstance = GetModuleHandle(NULL);

	dimension_x = 640;
	dimension_y = 480;
	current_bpp = 8;
	stretched_x = dimension_x;
	stretched_y = dimension_y;
	stretch_factor = 1;

	for (unsigned c = 0; c < 256; c++)
		vgafont[c] = NULL;
	create_vga_font();

	bitmap_info = (BITMAPINFO*) new char[sizeof(BITMAPINFOHEADER) + 259 * sizeof(RGBQUAD)]; // 256 + 3 entries for 16 bpp mode
	bitmap_info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bitmap_info->bmiHeader.biWidth = x_tilesize;

	// Height is negative for top-down bitmap
	bitmap_info->bmiHeader.biHeight = -(LONG)y_tilesize;
	bitmap_info->bmiHeader.biPlanes = 1;
	bitmap_info->bmiHeader.biBitCount = 8;
	bitmap_info->bmiHeader.biCompression = BI_RGB;
	bitmap_info->bmiHeader.biSizeImage = x_tilesize * y_tilesize * 4;

	// I think these next two figures don't matter; saying 45 pixels/centimeter
	bitmap_info->bmiHeader.biXPelsPerMeter = 4500;
	bitmap_info->bmiHeader.biYPelsPerMeter = 4500;
	bitmap_info->bmiHeader.biClrUsed = 256;
	bitmap_info->bmiHeader.biClrImportant = 0;
	cmap_index = bitmap_info->bmiColors;

	// start out with all color map indeces pointing to Black
	cmap_index[0] = black_quad;
	for (i = 1; i < 259; i++)
	{
		cmap_index[i] = cmap_index[0];
	}

	if (stInfo.hInstance)
		workerThread = _beginthread(UIThread, 0, NULL);
	else
		terminateEmul(EXIT_GMH_FAILURE);

	// Wait for a window before continuing
	if ((stInfo.kill == 0) && (FindWindow(szAppName, NULL) == NULL))
		Sleep(500);

	// Now set this thread's priority to below normal because this is where
	//  the emulated CPU runs, and it hogs the real CPU
	// SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
	// load keymap tables
	if (myCfg->get_bool_value("keyboard.use_mapping"))
	{
		bx_keymap->loadKeymap(NULL);  // I have no function to convert X windows symbols
	}
}

void resize_main_window()
{
	RECT          R;
	unsigned long mainStyle;

	// stretched_x and stretched_y were set in dimension_update()
	// if we need to do any additional resizing, do it now
	if ((desktop_y > 0) && (stretched_y >= (unsigned)desktop_y))
	{
		if (!queryFullScreen)
		{
			MessageBox(NULL, "Going into fullscreen mode -- Alt-Enter to revert",
				"Going fullscreen", MB_APPLMODAL);
			queryFullScreen = true;
		}

		// hide title bar
		mainStyle = GetWindowLong(stInfo.mainWnd, GWL_STYLE);
		mainStyle &= ~(WS_CAPTION | WS_BORDER);
		SetWindowLong(stInfo.mainWnd, GWL_STYLE, mainStyle);

		// maybe need to adjust stInfo.simWnd here also?
		saveParent = SetParent(stInfo.mainWnd, desktopWindow);
		if (saveParent)
		{
			BX_DEBUG(("Saved parent window"));
			SetWindowPos(stInfo.mainWnd, HWND_TOPMOST, desktop.left, desktop.top,
				desktop.right, desktop.bottom, SWP_SHOWWINDOW);
		}
	}
	else
	{
		if (saveParent)
		{
			BX_DEBUG(("Restoring parent window"));
			SetParent(stInfo.mainWnd, saveParent);
			saveParent = NULL;
		}

		// put back the title bar, border, etc...
		mainStyle = GetWindowLong(stInfo.mainWnd, GWL_STYLE);
		mainStyle |= WS_CAPTION | WS_BORDER;
		SetWindowLong(stInfo.mainWnd, GWL_STYLE, mainStyle);
		SetRect(&R, 0, 0, stretched_x, stretched_y);

		DWORD style = GetWindowLong(stInfo.simWnd, GWL_STYLE);
		DWORD exstyle = GetWindowLong(stInfo.simWnd, GWL_EXSTYLE);
		AdjustWindowRectEx(&R, style, false, exstyle);
		style = GetWindowLong(stInfo.mainWnd, GWL_STYLE);
		AdjustWindowRect(&R, style, false);
		SetWindowPos(stInfo.mainWnd, HWND_TOP, 0, 0, R.right - R.left,
			R.bottom - R.top, SWP_NOMOVE | SWP_NOZORDER);
	}

	fix_size = false;
}

// This thread controls the GUI window.
VOID UIThread(PVOID pvoid)
{
	MSG       msg;
	HDC       hdc;
	WNDCLASS  wndclass;
	RECT      wndRect;

	workerThreadID = GetCurrentThreadId();

	GetClassInfo(NULL, WC_DIALOG, &wndclass);
	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = mainWndProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hInstance = stInfo.hInstance;
	wndclass.lpszMenuName = NULL;
	wndclass.lpszClassName = szAppName;

	RegisterClass(&wndclass);

	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = simWndProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hInstance = stInfo.hInstance;
	wndclass.hIcon = NULL;
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	wndclass.lpszMenuName = NULL;
	wndclass.lpszClassName = "SIMWINDOW";

	RegisterClass(&wndclass);

	SetRect(&wndRect, 0, 0, stretched_x, stretched_y);

	DWORD sim_style = WS_CHILD;
	DWORD sim_exstyle = WS_EX_CLIENTEDGE;
	AdjustWindowRectEx(&wndRect, sim_style, false, sim_exstyle);

	DWORD main_style = WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
	AdjustWindowRect(&wndRect, main_style, false);
	stInfo.mainWnd = CreateWindow(szAppName, szWindowName, main_style,
		CW_USEDEFAULT, CW_USEDEFAULT,
		wndRect.right - wndRect.left,
		wndRect.bottom - wndRect.top, NULL, NULL,
		stInfo.hInstance, NULL);

	if (stInfo.mainWnd)
	{

		//    InitCommonControls();
		stInfo.simWnd = CreateWindowEx(sim_exstyle, "SIMWINDOW", "", sim_style, 0,
			0, 0, 0, stInfo.mainWnd, NULL,
			stInfo.hInstance, NULL);

		/* needed for the Japanese versions of Windows */
		if (stInfo.simWnd)
		{
			HMODULE hm;
			hm = GetModuleHandle("USER32");
			if (hm)
			{
				BOOL(WINAPI * enableime) (HWND, BOOL);
				enableime = (BOOL(WINAPI*) (HWND, BOOL)) GetProcAddress(hm,
					"WINNLSEnableIME");
				if (enableime)
				{
					enableime(stInfo.simWnd, false);
					BX_INFO(("IME disabled"));
				}
			}
		}

		ShowWindow(stInfo.simWnd, SW_SHOW);
		SetFocus(stInfo.simWnd);

		ShowCursor(!mouseCaptureMode);

		POINT pt = { 0, 0 };
		ClientToScreen(stInfo.simWnd, &pt);
		SetCursorPos(pt.x + stretched_x / 2, pt.y + stretched_y / 2);
		cursorWarped();

		hdc = GetDC(stInfo.simWnd);
		MemoryBitmap = CreateCompatibleBitmap(hdc, BX_MAX_XRES, BX_MAX_YRES);
		MemoryDC = CreateCompatibleDC(hdc);
		ReleaseDC(stInfo.simWnd, hdc);

		if (MemoryBitmap && MemoryDC)
		{
			resize_main_window();
			ShowWindow(stInfo.mainWnd, SW_SHOW);
#if BX_DEBUGGER
			if (windebug)
			{
				InitDebugDialog(stInfo.mainWnd);
			}
#endif
			stInfo.UIinited = true;

			bx_gui->clear_screen();

			while (GetMessage(&msg, NULL, 0, 0))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}
	}

	stInfo.kill = EXIT_GUI_SHUTDOWN;

	_endthread();
}

LRESULT CALLBACK mainWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
	switch (iMsg)
	{
	case WM_SETFOCUS:
		SetFocus(stInfo.simWnd);
		return 0;

	case WM_CLOSE:
		SendMessage(stInfo.simWnd, WM_CLOSE, 0, 0);
		break;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	case WM_SIZE:
	{
		int   x;

		int   y;

		// now fit simWindow to mainWindow
		//      int rect_data[] = { 1, 0, 0, 0 };
		RECT  R;

		//GetEffectiveClientRect( hwnd, &R, rect_data );
		GetClientRect(hwnd, &R);
		x = R.right - R.left;
		y = R.bottom - R.top;
		MoveWindow(stInfo.simWnd, R.left, R.top, x, y, true);
		GetClientRect(stInfo.simWnd, &R);
		x = R.right - R.left;
		y = R.bottom - R.top;
		if ((x != (int)stretched_x) || (y != (int)stretched_y))
		{
			BX_ERROR(("Sim client size(%d, %d) != stretched size(%d, %d)!", x, y,
				stretched_x, stretched_y));
			if (!saveParent)
				fix_size = true;        // no fixing if fullscreen
		}
	}
	break;
	}

	return DefWindowProc(hwnd, iMsg, wParam, lParam);
}

void SetMouseCapture()
{
	POINT pt = { 0, 0 };

	if (mouseToggleReq)
	{
		mouseCaptureMode = mouseCaptureNew;
		mouseToggleReq = false;
	}
	else
	{

		//  SIM->get_param_bool(BXPN_MOUSE_ENABLED)->set(mouseCaptureMode);
	}

	ShowCursor(!mouseCaptureMode);
	ShowCursor(!mouseCaptureMode);  // somehow one didn't do the trick (win98)
	ClientToScreen(stInfo.simWnd, &pt);
	SetCursorPos(pt.x + stretched_x / 2, pt.y + stretched_y / 2);
	cursorWarped();
}

LRESULT CALLBACK simWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
	HDC         hdc;

	HDC         hdcMem;
	PAINTSTRUCT ps;
	POINT       pt;
	static BOOL mouseModeChange = false;

	switch (iMsg)
	{
	case WM_CREATE:
#if BX_USE_WINDOWS_FONTS
		InitFont();
#endif
		SetTimer(hwnd, 1, 330, NULL);
		return 0;

	case WM_TIMER:
		if (mouseToggleReq && (GetActiveWindow() == stInfo.mainWnd))
		{
			SetMouseCapture();
		}

		// If mouse escaped, bring it back
		if (mouseCaptureMode)
		{
			pt.x = 0;
			pt.y = 0;
			ClientToScreen(hwnd, &pt);
			SetCursorPos(pt.x + stretched_x / 2, pt.y + stretched_y / 2);
			cursorWarped();
		}

		if (fix_size)
		{
			resize_main_window();
		}

		return 0;

	case WM_PAINT:
		EnterCriticalSection(&stInfo.drawCS);
		hdc = BeginPaint(hwnd, &ps);

		hdcMem = CreateCompatibleDC(hdc);
		SelectObject(hdcMem, MemoryBitmap);

		if (stretch_factor == 1)
		{
			BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top,
				ps.rcPaint.right - ps.rcPaint.left + 1,
				ps.rcPaint.bottom - ps.rcPaint.top + 1, hdcMem, ps.rcPaint.left,
				ps.rcPaint.top, SRCCOPY);
		}
		else
		{
			StretchBlt(hdc, ps.rcPaint.left, ps.rcPaint.top,
				ps.rcPaint.right - ps.rcPaint.left + 1,
				ps.rcPaint.bottom - ps.rcPaint.top + 1, hdcMem,
				ps.rcPaint.left / stretch_factor, ps.rcPaint.top,
				(ps.rcPaint.right - ps.rcPaint.left + 1) / stretch_factor,
				(ps.rcPaint.bottom - ps.rcPaint.top + 1), SRCCOPY);
		}

		DeleteDC(hdcMem);
		EndPaint(hwnd, &ps);
		LeaveCriticalSection(&stInfo.drawCS);
		return 0;

	case WM_MOUSEMOVE:
		if (!mouseModeChange)
		{
			processMouseXY(LOWORD(lParam), HIWORD(lParam), 0, wParam, 0);
		}

		return 0;

		//case WM_MOUSEWHEEL:
		//  if (!mouseModeChange) {
		//    // WM_MOUSEWHEEL returns x and y relative to the main screen.
		//    // WM_MOUSEMOVE below returns x and y relative to the current view.
		//    POINT pt;
		//    pt.x = LOWORD(lParam);
		//    pt.y = HIWORD(lParam);
		//    ScreenToClient(stInfo.simWnd, &pt);
		//    processMouseXY( pt.x, pt.y, (s16) HIWORD(wParam) / 120, LOWORD(wParam), 0);
		//  }
		//  return 0;
	case WM_LBUTTONDOWN:
	case WM_LBUTTONDBLCLK:
	case WM_LBUTTONUP:
		if (mouse_buttons == 2)
		{
			if (wParam == (MK_CONTROL | MK_LBUTTON | MK_RBUTTON))
			{
				mouseCaptureMode = !mouseCaptureMode;
				SetMouseCapture();
				mouseModeChange = true;
			}
			else if (mouseModeChange && (iMsg == WM_LBUTTONUP))
			{
				mouseModeChange = false;
			}
			else
			{
				processMouseXY(LOWORD(lParam), HIWORD(lParam), 0, wParam, 1);
			}

			return 0;
		}

		processMouseXY(LOWORD(lParam), HIWORD(lParam), 0, wParam, 1);
		return 0;

	case WM_MBUTTONDOWN:
	case WM_MBUTTONDBLCLK:
	case WM_MBUTTONUP:
		if (wParam == (MK_CONTROL | MK_MBUTTON))
		{
			mouseCaptureMode = !mouseCaptureMode;
			SetMouseCapture();
			mouseModeChange = true;
		}
		else if (mouseModeChange && (iMsg == WM_MBUTTONUP))
		{
			mouseModeChange = false;
		}
		else
		{
			processMouseXY(LOWORD(lParam), HIWORD(lParam), 0, wParam, 4);
		}

		return 0;

	case WM_RBUTTONDOWN:
	case WM_RBUTTONDBLCLK:
	case WM_RBUTTONUP:
		if (mouse_buttons == 2)
		{
			if (wParam == (MK_CONTROL | MK_LBUTTON | MK_RBUTTON))
			{
				mouseCaptureMode = !mouseCaptureMode;
				SetMouseCapture();
				mouseModeChange = true;
			}
			else if (mouseModeChange && (iMsg == WM_RBUTTONUP))
			{
				mouseModeChange = false;
			}
			else
			{
				processMouseXY(LOWORD(lParam), HIWORD(lParam), 0, wParam, 2);
			}

			return 0;
		}

		processMouseXY(LOWORD(lParam), HIWORD(lParam), 0, wParam, 2);
		return 0;

	case WM_CLOSE:
		return DefWindowProc(hwnd, iMsg, wParam, lParam);

	case WM_DESTROY:
		KillTimer(hwnd, 1);
		stInfo.UIinited = false;
#if BX_USE_WINDOWS_FONTS
		DestroyFont();
#endif
		return 0;

	case WM_KEYDOWN:
	case WM_SYSKEYDOWN:
		EnterCriticalSection(&stInfo.keyCS);
		enq_key_event(HIWORD(lParam) & 0x01FF, BX_KEY_PRESSED);
		LeaveCriticalSection(&stInfo.keyCS);
		return 0;

	case WM_KEYUP:
	case WM_SYSKEYUP:

		// check if it's keyup, alt key, non-repeat
		// see http://msdn2.microsoft.com/en-us/library/ms646267.aspx
		if (wParam == VK_RETURN)
		{
			if ((HIWORD(lParam) & BX_SYSKEY) == (KF_ALTDOWN | KF_UP))
			{
				if (!saveParent)
				{
					BX_INFO(("entering fullscreen mode"));
					theGui->dimension_update(desktop_x, desktop_y, 0, 0, current_bpp);
				}
				else
				{
					BX_INFO(("leaving fullscreen mode"));
					theGui->dimension_update(dimension_x, desktop_y - 1, 0, 0, current_bpp);
				}
			}
		}
		else
		{
			EnterCriticalSection(&stInfo.keyCS);
			enq_key_event(HIWORD(lParam) & 0x01FF, BX_KEY_RELEASED);
			LeaveCriticalSection(&stInfo.keyCS);
		}

		return 0;

	case WM_SYSCHAR:

		// check if it's keydown, alt key, non-repeat
		// see http://msdn2.microsoft.com/en-us/library/ms646267.aspx
		if (wParam == VK_RETURN)
		{
			if ((HIWORD(lParam) & BX_SYSKEY) == KF_ALTDOWN)
			{
				if (!saveParent)
				{
					BX_INFO(("entering fullscreen mode"));
					theGui->dimension_update(desktop_x, desktop_y, 0, 0, current_bpp);
				}
				else
				{
					BX_INFO(("leaving fullscreen mode"));
					theGui->dimension_update(dimension_x, desktop_y - 1, 0, 0, current_bpp);
				}
			}
		}
		return 0;
		
	case WM_CHAR:
	case WM_DEADCHAR:
	case WM_SYSDEADCHAR:
		return 0;
	}

	return DefWindowProc(hwnd, iMsg, wParam, lParam);
}

void enq_key_event(u32 key, u32 press_release)
{
	static BOOL alt_pressed_l = false;
	static BOOL alt_pressed_r = false;
	static BOOL ctrl_pressed_l = false;
	static BOOL ctrl_pressed_r = false;
	static BOOL shift_pressed_l = false;
	static BOOL shift_pressed_r = false;

	// Windows generates multiple keypresses when holding down these keys
	if (press_release == BX_KEY_PRESSED)
	{
		switch (key)
		{
		case 0x1d:
			if (ctrl_pressed_l)
				return;
			ctrl_pressed_l = true;
			break;

		case 0x2a:
			if (shift_pressed_l)
				return;
			shift_pressed_l = true;
			break;

		case 0x36:
			if (shift_pressed_r)
				return;
			shift_pressed_r = true;
			break;

		case 0x38:
			if (alt_pressed_l)
				return;
			alt_pressed_l = true;
			break;

		case 0x011d:
			if (ctrl_pressed_r)
				return;
			ctrl_pressed_r = true;
			break;

		case 0x0138:
			if (alt_pressed_r)
				return;

			// This makes the "AltGr" key on European keyboards work
			if (ctrl_pressed_l)
			{
				enq_key_event(0x1d, BX_KEY_RELEASED);
			}

			alt_pressed_r = true;
			break;
		}
	}
	else
	{
		switch (key)
		{
		case 0x1d:    if (!ctrl_pressed_l) return; ctrl_pressed_l = false; break;
		case 0x2a:    shift_pressed_l = false; break;
		case 0x36:    shift_pressed_r = false; break;
		case 0x38:    alt_pressed_l = false; break;
		case 0x011d:  ctrl_pressed_r = false; break;
		case 0x0138:  alt_pressed_r = false; break;
		}
	}

	if (((tail + 1) % SCANCODE_BUFSIZE) == head)
	{
		BX_ERROR(("enq_scancode: buffer full"));
		return;
	}

	keyevents[tail].key_event = key | press_release;
	tail = (tail + 1) % SCANCODE_BUFSIZE;
}

void enq_mouse_event(void)
{
	EnterCriticalSection(&stInfo.mouseCS);
	if (ms_xdelta || ms_ydelta || ms_zdelta)
	{
		// Protect the shared ring with keyCS as well.
		EnterCriticalSection(&stInfo.keyCS);
		if (((tail + 1) % SCANCODE_BUFSIZE) == head) {
			// BX_DEBUG(("enq_scancode: buffer full, dropping mouse motion"));
			resetDelta();
			LeaveCriticalSection(&stInfo.keyCS);
			LeaveCriticalSection(&stInfo.mouseCS);
			return;
		}

		QueueEvent& current = keyevents[tail];
		current.key_event = MOUSE_MOTION;
		current.mouse_x = ms_xdelta;
		current.mouse_y = ms_ydelta;
		current.mouse_z = ms_zdelta;
		current.mouse_button_state = mouse_button_state;
		resetDelta();
		tail = (tail + 1) % SCANCODE_BUFSIZE;
		LeaveCriticalSection(&stInfo.keyCS);
	}

	LeaveCriticalSection(&stInfo.mouseCS);
}

QueueEvent* deq_key_event(void)
{
	QueueEvent* key;

	if (head == tail)
	{
		BX_ERROR(("deq_scancode: buffer empty"));
		return((QueueEvent*)0);
	}

	key = &keyevents[head];
	head = (head + 1) % SCANCODE_BUFSIZE;

	return(key);
}

// ::HANDLE_EVENTS()
//
// Called periodically (vga_update_interval in .bochsrc) so the
// the gui code can poll for keyboard, mouse, and other

// relevant events.
void bx_win32_gui_c::handle_events(void)
{
	u32 key;
	u32 key_event;

	if (stInfo.kill)
		terminateEmul(stInfo.kill);

	// Handle mouse moves
	enq_mouse_event();

	// Handle keyboard and mouse clicks
	EnterCriticalSection(&stInfo.keyCS);
	while (head != tail)
	{
		QueueEvent* queue_event = deq_key_event();
		if (!queue_event)
			break;
		key = queue_event->key_event;
		if (key == MOUSE_MOTION)
		{

			//      DEV_mouse_motion_ext( queue_event->mouse_x,
			//        queue_event->mouse_y, queue_event->mouse_z, queue_event->mouse_button_state);
		}

		// Check for mouse buttons first
		else if (key & MOUSE_PRESSED)
		{

			//      DEV_mouse_motion_ext( 0, 0, 0, LOWORD(key));
		}
		else
		{
			key_event = win32_to_bx_key[(key & 0x100) ? 1 : 0][key & 0xff];
			if (key & BX_KEY_RELEASED)
				key_event |= BX_KEY_RELEASED;

			//DEV_kbd_gen_scancode(key_event);
			theKeyboard->gen_scancode(key_event);
		}
	}

	LeaveCriticalSection(&stInfo.keyCS);
}

// ::FLUSH()
//
// Called periodically, requesting that the gui code flush all pending

// screen update requests.
void bx_win32_gui_c::flush(void)
{
	EnterCriticalSection(&stInfo.drawCS);
	if (updated_area_valid)
	{

		// slight bugfix
		updated_area.right++;
		updated_area.bottom++;
		InvalidateRect(stInfo.simWnd, &updated_area, false);
		updated_area_valid = false;
	}

	LeaveCriticalSection(&stInfo.drawCS);
}

// ::CLEAR_SCREEN()
//

// Called to request that the VGA region is cleared.
void bx_win32_gui_c::clear_screen(void)
{
	HGDIOBJ oldObj;

	if (!stInfo.UIinited)
		return;

	EnterCriticalSection(&stInfo.drawCS);

	oldObj = SelectObject(MemoryDC, MemoryBitmap);
	PatBlt(MemoryDC, 0, 0, stretched_x, stretched_y, BLACKNESS);
	SelectObject(MemoryDC, oldObj);

	updateUpdated(0, 0, dimension_x - 1, dimension_y - 1);

	LeaveCriticalSection(&stInfo.drawCS);
}

// ::TEXT_UPDATE()
//
// Called in a VGA text mode, to update the screen with
// new content.
//
// old_text: array of character/attributes making up the contents
//           of the screen from the last call.  See below
// new_text: array of character/attributes making up the current
//           contents, which should now be displayed.  See below
//
// format of old_text & new_text: each is tm_info.line_offset*text_rows
//     bytes long. Each character consists of 2 bytes.  The first by is
//     the character value, the second is the attribute byte.
//
// cursor_x: new x location of cursor
// cursor_y: new y location of cursor
// tm_info:  this structure contains information for additional
//           features in text mode (cursor shape, line offset,...)

// nrows:    number of text rows (unused here)
void bx_win32_gui_c::text_update(u8* old_text, u8* new_text,
	unsigned long cursor_x, unsigned long cursor_y,
	bx_vga_tminfo_t tm_info, unsigned nrows)
{
	HDC           hdc;
	unsigned char data[64];
	u8* old_line;
	u8* new_line;
	u8            cAttr;
	u8            cChar;
	unsigned int  curs;
	unsigned int  hchars;
	unsigned int  i;
	unsigned int  offset;
	unsigned int  rows;
	unsigned int  x;
	unsigned int  y;
	unsigned int  xc;
	unsigned int  yc;
	BOOL          forceUpdate = false;
#if !BX_USE_WINDOWS_FONTS
	u8* text_base;
	u8            cfwidth;
	u8            cfheight;
	u8            cfheight2;
	u8            font_col;
	u8            font_row;
	u8            font_row2;
	u8            split_textrow;
	u8            split_fontrows;
	unsigned int  yc2;
	unsigned int  cs_y;
	BOOL          split_screen;
#endif
	if (!stInfo.UIinited)
		return;

	EnterCriticalSection(&stInfo.drawCS);

	if (charmap_updated)
	{
		for (unsigned c = 0; c < 256; c++)
		{
			if (char_changed[c])
			{
				memset(data, 0, sizeof(data));

				BOOL  gfxchar = tm_info.line_graphics && ((c & 0xE0) == 0xC0);
				for (i = 0; i < (unsigned)yChar; i++)
				{
					data[i * 2] = vga_charmap[c * 32 + i];
					if (gfxchar)
					{
						data[i * 2 + 1] = (data[i * 2] << 7);
					}
				}

				SetBitmapBits(vgafont[c], 64, data);
				char_changed[c] = 0;
			}
		}

		forceUpdate = true;
		charmap_updated = 0;
	}

	for (i = 0; i < 16; i++)
	{
		text_pal_idx[i] = theVGA->get_actl_palette_idx(i);  // DEV_vga_get_actl_pal_idx(i);
	}

	hdc = GetDC(stInfo.simWnd);

#if !BX_USE_WINDOWS_FONTS
	if ((tm_info.h_panning != h_panning) || (tm_info.v_panning != v_panning))
	{
		forceUpdate = 1;
		h_panning = tm_info.h_panning;
		v_panning = tm_info.v_panning;
	}

	if (tm_info.line_compare != line_compare)
	{
		forceUpdate = 1;
		line_compare = tm_info.line_compare;
	}
#endif

	// first invalidate character at previous and new cursor location
	if ((prev_cursor_y < text_rows) && (prev_cursor_x < text_cols))
	{
		curs = prev_cursor_y * tm_info.line_offset + prev_cursor_x * 2;
		old_text[curs] = ~new_text[curs];
	}

	if ((tm_info.cs_start <= tm_info.cs_end) && (tm_info.cs_start < yChar)
		&& (cursor_y < text_rows) && (cursor_x < text_cols))
	{
		curs = cursor_y * tm_info.line_offset + cursor_x * 2;
		old_text[curs] = ~new_text[curs];
	}
	else
	{
		curs = 0xffff;
	}

#if !BX_USE_WINDOWS_FONTS
	rows = text_rows;
	if (v_panning)
		rows++;
	y = 0;
	cs_y = 0;
	text_base = new_text - tm_info.start_address;
	split_textrow = (line_compare + v_panning) / yChar;
	split_fontrows = ((line_compare + v_panning) % yChar) + 1;
	split_screen = 0;
	do
	{
		hchars = text_cols;
		if (h_panning)
			hchars++;
		if (split_screen)
		{
			yc = line_compare + cs_y * yChar + 1;
			font_row = 0;
			if (rows == 1)
			{
				cfheight = (dimension_y - line_compare - 1) % yChar;
				if (cfheight == 0)
					cfheight = yChar;
			}
			else
			{
				cfheight = yChar;
			}
		}
		else if (v_panning)
		{
			if (y == 0)
			{
				yc = 0;
				font_row = v_panning;
				cfheight = yChar - v_panning;
			}
			else
			{
				yc = y * yChar - v_panning;
				font_row = 0;
				if (rows == 1)
				{
					cfheight = v_panning;
				}
				else
				{
					cfheight = yChar;
				}
			}
		}
		else
		{
			yc = y * yChar;
			font_row = 0;
			cfheight = yChar;
		}

		if (!split_screen && (y == split_textrow))
		{
			if (split_fontrows < cfheight)
				cfheight = split_fontrows;
		}

		new_line = new_text;
		old_line = old_text;
		x = 0;
		offset = cs_y * tm_info.line_offset;
		do
		{
			if (h_panning)
			{
				if (hchars > text_cols)
				{
					xc = 0;
					font_col = h_panning;
					cfwidth = xChar - h_panning;
				}
				else
				{
					xc = x * xChar - h_panning;
					font_col = 0;
					if (hchars == 1)
					{
						cfwidth = h_panning;
					}
					else
					{
						cfwidth = xChar;
					}
				}
			}
			else
			{
				xc = x * xChar;
				font_col = 0;
				cfwidth = xChar;
			}

			if (forceUpdate || (old_text[0] != new_text[0])
				|| (old_text[1] != new_text[1]))
			{
				cChar = new_text[0];
				cAttr = new_text[1];
				DrawBitmap(hdc, vgafont[cChar], xc, yc, cfwidth, cfheight, font_col,
					font_row, SRCCOPY, cAttr);
				if (offset == curs)
				{
					if (font_row == 0)
					{
						yc2 = yc + tm_info.cs_start;
						font_row2 = tm_info.cs_start;
						cfheight2 = tm_info.cs_end - tm_info.cs_start + 1;
					}
					else
					{
						if (v_panning > tm_info.cs_start)
						{
							yc2 = yc;
							font_row2 = font_row;
							cfheight2 = tm_info.cs_end - v_panning + 1;
						}
						else
						{
							yc2 = yc + tm_info.cs_start - v_panning;
							font_row2 = tm_info.cs_start;
							cfheight2 = tm_info.cs_end - tm_info.cs_start + 1;
						}
					}

					cAttr = ((cAttr >> 4) & 0xF) + ((cAttr & 0xF) << 4);
					DrawBitmap(hdc, vgafont[cChar], xc, yc2, cfwidth, cfheight2, font_col,
						font_row2, SRCCOPY, cAttr);
				}
			}

			x++;
			new_text += 2;
			old_text += 2;
			offset += 2;
		} while (--hchars);
		if (!split_screen && (y == split_textrow))
		{
			new_text = text_base;
			forceUpdate = 1;
			cs_y = 0;
			if (tm_info.split_hpanning)
				h_panning = 0;
			rows = ((dimension_y - line_compare + yChar - 2) / yChar) + 1;
			split_screen = 1;
		}
		else
		{
			y++;
			cs_y++;
			new_text = new_line + tm_info.line_offset;
			old_text = old_line + tm_info.line_offset;
		}
	} while (--rows);

	h_panning = tm_info.h_panning;
#else
	rows = text_rows;
	y = 0;
	do
	{
		hchars = text_cols;
		yc = y * yChar;
		new_line = new_text;
		old_line = old_text;
		x = 0;
		offset = y * tm_info.line_offset;
		do
		{
			xc = x * xChar;
			if (forceUpdate || (old_text[0] != new_text[0])
				|| (old_text[1] != new_text[1]))
			{
				cChar = new_text[0];
				cAttr = new_text[1];
				DrawChar(hdc, cChar, xc, yc, cAttr, 1, 0);
				if (offset == curs)
				{
					DrawChar(hdc, cChar, xc, yc, cAttr, tm_info.cs_start, tm_info.cs_end);
				}
			}

			x++;
			new_text += 2;
			old_text += 2;
			offset += 2;
		} while (--hchars);
		y++;
		new_text = new_line + tm_info.line_offset;
		old_text = old_line + tm_info.line_offset;
	} while (--rows);
#endif
	prev_cursor_x = cursor_x;
	prev_cursor_y = cursor_y;

	ReleaseDC(stInfo.simWnd, hdc);

	LeaveCriticalSection(&stInfo.drawCS);
}

// ::PALETTE_CHANGE()
//
// Allocate a color in the native GUI, for this color, and put
// it in the colormap location 'index'.
// returns: 0=no screen update needed (color map change has direct effect)

//          1=screen updated needed (redraw using current colormap)
bool bx_win32_gui_c::palette_change(unsigned index, unsigned red, unsigned green,
	unsigned blue)
{
	if ((current_bpp == 16) && (index < 3))
	{
		cmap_index[256 + index].rgbRed = red;
		cmap_index[256 + index].rgbBlue = blue;
		cmap_index[256 + index].rgbGreen = green;
		return 0;
	}
	else
	{
		cmap_index[index].rgbRed = red;
		cmap_index[index].rgbBlue = blue;
		cmap_index[index].rgbGreen = green;
	}

	return(1);
}

void bx_win32_gui_c::graphics_frame_update(const u32* pixels, unsigned width, unsigned height)
{
	BITMAPINFOHEADER bih = {};
	bih.biSize = sizeof(BITMAPINFOHEADER);
	bih.biWidth = (LONG)width;
	bih.biHeight = -(LONG)height;   
	bih.biPlanes = 1;
	bih.biBitCount = 32;
	bih.biCompression = BI_RGB;

	EnterCriticalSection(&stInfo.drawCS);

	HDC hdc = GetDC(stInfo.simWnd);
	HGDIOBJ oldObj = SelectObject(MemoryDC, MemoryBitmap);

	StretchDIBits(
		MemoryDC,
		0, 0, (int)width, (int)height,    
		0, 0, (int)width, (int)height,   
		pixels,
		(const BITMAPINFO*)&bih,
		DIB_RGB_COLORS,
		SRCCOPY);

	SelectObject(MemoryDC, oldObj);
	updateUpdated(0, 0, width - 1, height - 1);

	ReleaseDC(stInfo.simWnd, hdc);
	LeaveCriticalSection(&stInfo.drawCS);
}

// ::GRAPHICS_TILE_UPDATE()
//
// Called to request that a tile of graphics be drawn to the
// screen, since info in this region has changed.
//
// tile: array of 8bit values representing a block of pixels with
//       dimension equal to the 'tilewidth' & 'tileheight' parameters to
//       ::specific_init().  Each value specifies an index into the
//       array of colors you allocated for ::palette_change()
// x0: x origin of tile
// y0: y origin of tile
//
// note: origin of tile and of window based on (0,0) being in the upper

//       left of the window.
void bx_win32_gui_c::graphics_tile_update(u8* tile, unsigned x0, unsigned y0)
{
	HDC     hdc;
	HGDIOBJ oldObj;

	EnterCriticalSection(&stInfo.drawCS);
	hdc = GetDC(stInfo.simWnd);

	oldObj = SelectObject(MemoryDC, MemoryBitmap);

	StretchDIBits(MemoryDC, x0, y0, x_tilesize, y_tilesize, 0, 0, x_tilesize,
		y_tilesize, tile, bitmap_info, DIB_RGB_COLORS, SRCCOPY);

	SelectObject(MemoryDC, oldObj);

	updateUpdated(x0, y0, x0 + x_tilesize - 1, y0 + y_tilesize - 1);

	ReleaseDC(stInfo.simWnd, hdc);
	LeaveCriticalSection(&stInfo.drawCS);
}

// ::DIMENSION_UPDATE()
//
// Called when the VGA mode changes it's X,Y dimensions.
// Resize the window to this size.
//
// x: new VGA x size
// y: new VGA y size
// fheight: new VGA character height in text mode
// fwidth : new VGA character width in text mode

// bpp : bits per pixel in graphics mode
void bx_win32_gui_c::dimension_update(unsigned x, unsigned y, unsigned fheight,
	unsigned fwidth, unsigned bpp)
{
	BxTextMode = (fheight > 0);
	if (BxTextMode)
	{
		text_cols = x / fwidth;
		text_rows = y / fheight;
		xChar = fwidth;
		yChar = fheight;
	}

	if (x == dimension_x && y == dimension_y && bpp == current_bpp)
		return;
	dimension_x = x;
	dimension_y = y;
	stretched_x = dimension_x;
	stretched_y = dimension_y;
	stretch_factor = 1;
	if (BxTextMode && (stretched_x < 400))
	{
		stretched_x *= 2;
		stretch_factor *= 2;
	}

	bitmap_info->bmiHeader.biBitCount = bpp;
	if (bpp == 16)
	{
		bitmap_info->bmiHeader.biCompression = BI_BITFIELDS;

		static RGBQUAD  red_mask = { 0x00, 0xF8, 0x00, 0x00 };
		static RGBQUAD  green_mask = { 0xE0, 0x07, 0x00, 0x00 };
		static RGBQUAD  blue_mask = { 0x1F, 0x00, 0x00, 0x00 };
		bitmap_info->bmiColors[256] = bitmap_info->bmiColors[0];
		bitmap_info->bmiColors[257] = bitmap_info->bmiColors[1];
		bitmap_info->bmiColors[258] = bitmap_info->bmiColors[2];
		bitmap_info->bmiColors[0] = red_mask;
		bitmap_info->bmiColors[1] = green_mask;
		bitmap_info->bmiColors[2] = blue_mask;
	}
	else
	{
		if (current_bpp == 16)
		{
			bitmap_info->bmiColors[0] = bitmap_info->bmiColors[256];
			bitmap_info->bmiColors[1] = bitmap_info->bmiColors[257];
			bitmap_info->bmiColors[2] = bitmap_info->bmiColors[258];
		}

		bitmap_info->bmiHeader.biCompression = BI_RGB;
		if (bpp == 15)
		{
			bitmap_info->bmiHeader.biBitCount = 16;
		}
	}

	current_bpp = bpp;

	resize_main_window();

	BX_INFO(("dimension update x=%d y=%d fontheight=%d fontwidth=%d bpp=%d", x, y,
		fheight, fwidth, bpp));

	host_xres = x;
	host_yres = y;
	host_bpp = bpp;
}

// ::EXIT()
//
// Called before bochs terminates, to allow for a graceful

// exit from the native GUI mechanism.
void bx_win32_gui_c::exit(void)
{
	printf("# In bx_win32_gui_c::exit(void)!\n");

	// kill thread first...
	PostMessage(stInfo.mainWnd, WM_CLOSE, 0, 0);

	// Wait until it dies
	while ((stInfo.kill == 0) && (workerThreadID != 0))
		Sleep(500);

	if (!stInfo.kill)
		terminateEmul(EXIT_NORMAL);
}

void create_vga_font(void)
{
	unsigned char data[64];

	// VGA font is 8 or 9 wide and up to 32 high
	for (unsigned c = 0; c < 256; c++)
	{
		vgafont[c] = CreateBitmap(9, 32, 1, 1, NULL);
		if (!vgafont[c])
			terminateEmul(EXIT_FONT_BITMAP_ERROR);
		memset(data, 0, sizeof(data));
		for (unsigned i = 0; i < 16; i++)
			data[i * 2] = reverse_bitorder(bx_vgafont[c].data[i]);
		SetBitmapBits(vgafont[c], 64, data);
	}
}

unsigned char reverse_bitorder(unsigned char b)
{
	unsigned char ret = 0;

	for (unsigned i = 0; i < 8; i++)
	{
		ret |= (b & 0x01) << (7 - i);
		b >>= 1;
	}

	return(ret);
}

COLORREF GetColorRef(unsigned char attr)
{
	u8  pal_idx = text_pal_idx[attr];
	return RGB(cmap_index[pal_idx].rgbRed, cmap_index[pal_idx].rgbGreen,
		cmap_index[pal_idx].rgbBlue);
}

void DrawBitmap(HDC hdc, HBITMAP hBitmap, int xStart, int yStart, int width,
	int height, int fcol, int frow, DWORD dwRop, unsigned char cColor)
{
	BITMAP  bm;
	HDC     hdcMem;
	POINT   ptSize;
	POINT   ptOrg;
	HGDIOBJ oldObj;

	hdcMem = CreateCompatibleDC(hdc);
	SelectObject(hdcMem, hBitmap);
	SetMapMode(hdcMem, GetMapMode(hdc));

	GetObject(hBitmap, sizeof(BITMAP), (LPVOID)&bm);

	ptSize.x = width;
	ptSize.y = height;

	DPtoLP(hdc, &ptSize, 1);

	ptOrg.x = fcol;
	ptOrg.y = frow;
	DPtoLP(hdcMem, &ptOrg, 1);

	oldObj = SelectObject(MemoryDC, MemoryBitmap);

	// The highest background bit usually means blinking characters. No idea
	// how to implement that so for now it's just implemented as color.
	// Note: it is also possible to program the VGA controller to have the
	// high bit for the foreground color enable blinking characters.
	COLORREF  crFore = SetTextColor(MemoryDC, GetColorRef((cColor >> 4) & 0xf));
	COLORREF  crBack = SetBkColor(MemoryDC, GetColorRef(cColor & 0xf));
	BitBlt(MemoryDC, xStart, yStart, ptSize.x, ptSize.y, hdcMem, ptOrg.x, ptOrg.y,
		dwRop);
	SetBkColor(MemoryDC, crBack);
	SetTextColor(MemoryDC, crFore);

	SelectObject(MemoryDC, oldObj);

	updateUpdated(xStart, yStart, ptSize.x + xStart - 1, ptSize.y + yStart - 1);

	DeleteDC(hdcMem);
}

void updateUpdated(int x1, int y1, int x2, int y2)
{
	x1 *= stretch_factor;
	x2 *= stretch_factor;
	if (!updated_area_valid)
	{
		updated_area.left = x1;
		updated_area.top = y1;
		updated_area.right = x2;
		updated_area.bottom = y2;
	}
	else
	{
		if (x1 < updated_area.left)
			updated_area.left = x1;
		if (y1 < updated_area.top)
			updated_area.top = y1;
		if (x2 > updated_area.right)
			updated_area.right = x2;
		if (y2 > updated_area.bottom)
			updated_area.bottom = y2;
	}

	updated_area_valid = true;
}

void bx_win32_gui_c::mouse_enabled_changed_specific(bool val)
{
	if ((val != (bool)mouseCaptureMode) && !mouseToggleReq)
	{
		mouseToggleReq = true;
		mouseCaptureNew = val;
	}
}

void bx_win32_gui_c::get_capabilities(u16* xres, u16* yres, u16* bpp)
{
	if (desktop_y > 0)
	{
		*xres = (u16)desktop_x;
		*yres = (u16)desktop_y;
		*bpp = 32;
	}
	else
	{
		*xres = 1024;
		*yres = 768;
		*bpp = 32;
	}
}
#endif /* if BX_WITH_WIN32 */
