/*
	vid_mgl.c

	Win32 Scitech MGL video driver

	Copyright (C) 1996-1997  Id Software, Inc.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program 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 General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to:

		Free Software Foundation, Inc.
		59 Temple Place - Suite 330
		Boston, MA  02111-1307, USA

	$Id$
*/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdlib.h>

#include "QF/cmd.h"
#include "QF/cdaudio.h"
#include "QF/console.h"
#include "QF/draw.h"
#include "QF/keys.h"
#include "QF/qargs.h"
#include "QF/qendian.h"
#include "QF/screen.h"
#include "QF/sound.h"
#include "QF/sys.h"
#include "QF/va.h"
#include "QF/wad.h"

#include "d_local.h"
#include "in_win.h"
#include "resource.h"
#include "winquake.h"

#define	MINIMUM_MEMORY	0x550000

#define MAX_MODE_LIST	30
#define VID_ROW_SIZE	3

extern void (*vid_menudrawfn) (void);
extern void (*vid_menukeyfn) (int);

/* Unused */
int			VGA_width, VGA_height, VGA_rowbytes, VGA_bufferrowbytes,
			VGA_planar;
byte       *VGA_pagebase;

qboolean    dibonly;

extern qboolean Minimized;

HWND        mainwindow;

HWND WINAPI InitializeWindow (HINSTANCE hInstance, int nCmdShow);

int         DIBWidth, DIBHeight;
qboolean    DDActive;
RECT        WindowRect;
DWORD       WindowStyle, ExWindowStyle;

int         window_center_x, window_center_y, window_x, window_y, window_width,
			window_height;
RECT        window_rect;

static DEVMODE	gdevmode;
static qboolean startwindowed = 0, windowed_mode_set = 0;
static int		firstupdate = 1;
static qboolean vid_palettized;
static int		lockcount;
static int		vid_fulldib_on_focus_mode;
static qboolean force_minimized, in_mode_set, is_mode0x13, force_mode_set;
static int		vid_stretched, windowed_mouse;
static qboolean palette_changed, syscolchg, vid_mode_set, hide_window,
				pal_is_nostatic;
static HICON hIcon;

extern viddef_t vid;					// global video state

#define MODE_WINDOWED			0
#define MODE_SETTABLE_WINDOW	2
#define NO_MODE					(MODE_WINDOWED - 1)
#define MODE_FULLSCREEN_DEFAULT	(MODE_WINDOWED + 3)

// Note that 0 is MODE_WINDOWED
cvar_t     *vid_mode;

// Note that 0 is MODE_WINDOWED
// Note that 3 is MODE_FULLSCREEN_DEFAULT
cvar_t     *_vid_default_mode_win;
cvar_t     *vid_nopageflip;
cvar_t     *vid_config_x;
cvar_t     *vid_config_y;
cvar_t     *vid_stretch_by_2;
cvar_t     *_windowed_mouse;
cvar_t     *vid_fullscreen_mode;
cvar_t     *vid_windowed_mode;
cvar_t     *block_switch;
cvar_t     *vid_window_x;
cvar_t     *vid_window_y;

typedef struct {
	int         width;
	int         height;
} lmode_t;

lmode_t     lowresmodes[] = {
	{320, 200},
	{320, 240},
	{400, 300},
	{512, 384},
};

int         vid_modenum = NO_MODE;
int         vid_testingmode, vid_realmode;
double      vid_testendtime;
int         vid_default = MODE_WINDOWED;
static int  windowed_default;

modestate_t modestate = MS_UNINIT;

static byte *vid_surfcache;
static int  vid_surfcachesize;
static int  VID_highhunkmark;

unsigned char vid_curpal[256 * 3];

unsigned int d_8to24table[256];

int         driver = grDETECT, mode;
qboolean    useWinDirect = true, useDirectDraw = true;
MGLDC      *mgldc = NULL, *memdc = NULL, *dibdc = NULL, *windc = NULL;

typedef struct {
	modestate_t type;
	int         width;
	int         height;
	int         modenum;
	int         mode13;
	int         stretched;
	int         dib;
	int         fullscreen;
	int         bpp;
	int         halfscreen;
	char        modedesc[13];
} vmode_t;

static vmode_t modelist[MAX_MODE_LIST];
static int  nummodes;

int         aPage;						// Current active display page
int         vPage;						// Current visible display page
int         waitVRT = true;				// True to wait for retrace on flip

static vmode_t badmode;

static byte backingbuf[48 * 24];

void        VID_MenuDraw (void);
void        VID_MenuKey (int key);

LONG WINAPI MainWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void        AppActivate (BOOL fActive, BOOL minimize);


void
VID_RememberWindowPos (void)
{
	RECT        rect;

	if (GetWindowRect (mainwindow, &rect)) {
		if ((rect.left < GetSystemMetrics (SM_CXSCREEN)) &&
			(rect.top < GetSystemMetrics (SM_CYSCREEN)) &&
			(rect.right > 0) && (rect.bottom > 0)) {
			Cvar_SetValue (vid_window_x, rect.left);
			Cvar_SetValue (vid_window_y, rect.top);
		}
	}
}

void
VID_CheckWindowXY (void)
{

	if ((vid_window_x->int_val > (GetSystemMetrics (SM_CXSCREEN) - 160)) ||
		(vid_window_y->int_val > (GetSystemMetrics (SM_CYSCREEN) - 120)) ||
		(vid_window_x->int_val < 0) || (vid_window_y->int_val < 0)) {
		Cvar_SetValue (vid_window_x, 0.0);
		Cvar_SetValue (vid_window_y, 0.0);
	}
}

void
VID_UpdateWindowStatus (void)
{

	window_rect.left = window_x;
	window_rect.top = window_y;
	window_rect.right = window_x + window_width;
	window_rect.bottom = window_y + window_height;
	window_center_x = (window_rect.left + window_rect.right) / 2;
	window_center_y = (window_rect.top + window_rect.bottom) / 2;

	IN_UpdateClipCursor ();
}

void
ClearAllStates (void)
{
	IN_ClearStates ();
}

qboolean
VID_CheckAdequateMem (int width, int height)
{
	return true;
}

qboolean
VID_AllocBuffers (int width, int height)
{
	int         zbuffersize, cachesize;

	// Calculate the sizes we want first

	zbuffersize = width * height * sizeof (*d_pzbuffer);
	cachesize = D_SurfaceCacheForRes (width, height);

	// Free the old z-buffer
	if (d_pzbuffer) {
		free (d_pzbuffer);
		d_pzbuffer = NULL;
	}
	// Free the old surface cache
	vid_surfcache = D_SurfaceCacheAddress ();

	if (vid_surfcache) {
		D_FlushCaches ();
		free (vid_surfcache);
		vid_surfcache = NULL;
	}
	// Allocate the new z-buffer
	d_pzbuffer = calloc (zbuffersize, 1);
	if (!d_pzbuffer) {
		Sys_Error ("Not enough memory for video mode\n");
		return false;
	}
	// Allocate the new surface cache; free the z-buffer if we fail
	vid_surfcache = calloc (cachesize, 1);
	if (!vid_surfcache) {
		free (d_pzbuffer);
		d_pzbuffer = NULL;
		Sys_Error ("Not enough memory for video mode\n");
		return false;
	}
	vid_surfcachesize = cachesize;

	return true;
}

void
initFatalError (void)
{
	MGL_exit ();
	MGL_fatalError (MGL_errorMsg (MGL_result ()));
	exit (EXIT_FAILURE);
}

#if 0									// def NEW_SUSPEND

int
VID_Suspend (MGLDC * dc, int flags)
{
	int         i;

	if (flags & MGL_DEACTIVATE) {
		IN_RestoreOriginalMouseState ();
		CDAudio_Pause ();

		// keep WM_PAINT from trying to redraw
		in_mode_set = true;
		block_drawing = true;
	} else if (flags & MGL_REACTIVATE) {
		IN_SetQuakeMouseState ();
		// fix leftover Alt from any Alt-Tab or the like that switched us away
		ClearAllStates ();
		CDAudio_Resume ();
		in_mode_set = false;

		block_drawing = false;
//		vid.recalc_refdef = 1;
		force_mode_set = 1;
		i = msg_suppress_1;
		msg_suppress_1 = 1;
		VID_Fullscreen_f ();
		msg_suppress_1 = i;
		force_mode_set = 0;
	}

	return 1;
}

#else

int
VID_Suspend (MGLDC * dc, int flags)
{

	if (flags & MGL_DEACTIVATE) {
		// FIXME: this doesn't currently work on NT
		if (block_switch->int_val && !WinNT) {
			return MGL_NO_DEACTIVATE;
		}

		S_BlockSound ();
		S_ClearBuffer ();

		IN_RestoreOriginalMouseState ();
		CDAudio_Pause ();

		// keep WM_PAINT from trying to redraw
		in_mode_set = true;

		block_drawing = true;			// so we don't try to draw while
										// switched away
	} else if (flags & MGL_REACTIVATE) {
		IN_SetQuakeMouseState ();
		// fix leftover Alt from any Alt-Tab or the like that switched us away
		ClearAllStates ();
		CDAudio_Resume ();
		S_UnblockSound ();

		in_mode_set = false;

		vid.recalc_refdef = 1;

		block_drawing = false;
	}

	return MGL_NO_SUSPEND_APP;
}

#endif

void
registerAllDispDrivers (void)
{
	/* Event though these driver require WinDirect, we register * them so
	   that they will still be available even if DirectDraw * is present and
	   the user has disable the high performance * WinDirect modes. */
	MGL_registerDriver (MGL_VGA8NAME, VGA8_driver);
//	MGL_registerDriver(MGL_VGAXNAME, VGAX_driver);

	/* Register display drivers */
	if (useWinDirect) {
/* we don't want VESA 1.X drivers
		MGL_registerDriver(MGL_SVGA8NAME, SVGA8_driver);
*/
		MGL_registerDriver (MGL_LINEAR8NAME, LINEAR8_driver);

		if (!COM_CheckParm ("-novbeaf"))
			MGL_registerDriver (MGL_ACCEL8NAME, ACCEL8_driver);
	}

	if (useDirectDraw) {
		MGL_registerDriver (MGL_DDRAW8NAME, DDRAW8_driver);
	}
}

void
registerAllMemDrivers (void)
{
	/* Register memory context drivers */
	MGL_registerDriver (MGL_PACKED8NAME, PACKED8_driver);
}

void
VID_InitMGLFull (HINSTANCE hInstance)
{
	int         i, xRes, yRes, bits, lowres, curmode, temp;
	int         lowstretchedres, stretchedmode = 0, lowstretched;
	uchar      *m;

// FIXME: NT is checked for because MGL currently has a bug that causes it
// to try to use WinDirect modes even on NT
	if (COM_CheckParm ("-nowindirect") ||
		COM_CheckParm ("-nowd") || COM_CheckParm ("-novesa") || WinNT) {
		useWinDirect = false;
	}

	if (COM_CheckParm ("-nodirectdraw") || COM_CheckParm ("-noddraw")
		|| COM_CheckParm ("-nodd"))
		useDirectDraw = false;

	// Initialise the MGL
	MGL_unregisterAllDrivers ();
	registerAllDispDrivers ();
	registerAllMemDrivers ();
	MGL_detectGraph (&driver, &mode);
	m = MGL_availableModes ();

	if (m[0] != 0xFF) {
		lowres = lowstretchedres = 99999;
		lowstretched = 0;
		curmode = 0;

		// find the lowest-res mode, or a mode we can stretch up to and get
		// lowest-res that way
		for (i = 0; m[i] != 0xFF; i++) {
			MGL_modeResolution (m[i], &xRes, &yRes, &bits);

			if ((bits == 8) &&
				(xRes <= MAXWIDTH) &&
				(yRes <= MAXHEIGHT) && (curmode < MAX_MODE_LIST)) {
				if (m[i] == grVGA_320x200x256)
					is_mode0x13 = true;

				if (!COM_CheckParm ("-noforcevga")) {
					if (m[i] == grVGA_320x200x256) {
						mode = i;
						break;
					}
				}

				if (xRes < lowres) {
					lowres = xRes;
					mode = i;
				}

				if ((xRes < lowstretchedres) && ((xRes >> 1) >= 320)) {
					lowstretchedres = xRes >> 1;
					stretchedmode = i;
				}
			}

			curmode++;
		}

		// if there's a mode we can stretch by 2 up to, thereby effectively
		// getting a lower-res mode than the lowest-res real but still at least
		// 320x200, that will be our default mode
		if (lowstretchedres < lowres) {
			mode = stretchedmode;
			lowres = lowstretchedres;
			lowstretched = 1;
		}
		// build the mode list, leaving room for the low-res stretched mode,
		// if any
		nummodes++;						// leave room for default mode

		for (i = 0; m[i] != 0xFF; i++) {
			MGL_modeResolution (m[i], &xRes, &yRes, &bits);

			if ((bits == 8) &&
				(xRes <= MAXWIDTH) &&
				(yRes <= MAXHEIGHT) && (nummodes < MAX_MODE_LIST)) {
				if (i == mode) {
					if (lowstretched) {
						stretchedmode = nummodes;
						curmode = nummodes++;
					} else {
						curmode = MODE_FULLSCREEN_DEFAULT;
					}
				} else {
					curmode = nummodes++;
				}

				modelist[curmode].type = MS_FULLSCREEN;
				modelist[curmode].width = xRes;
				modelist[curmode].height = yRes;
				snprintf (modelist[curmode].modedesc,
						  sizeof (modelist[curmode].modedesc), "%dx%d", xRes,
						  yRes);

				if (m[i] == grVGA_320x200x256)
					modelist[curmode].mode13 = 1;
				else
					modelist[curmode].mode13 = 0;

				modelist[curmode].modenum = m[i];
				modelist[curmode].stretched = 0;
				modelist[curmode].dib = 0;
				modelist[curmode].fullscreen = 1;
				modelist[curmode].halfscreen = 0;
				modelist[curmode].bpp = 8;
			}
		}

		if (lowstretched) {
			modelist[MODE_FULLSCREEN_DEFAULT] = modelist[stretchedmode];
			modelist[MODE_FULLSCREEN_DEFAULT].stretched = 1;
			modelist[MODE_FULLSCREEN_DEFAULT].width >>= 1;
			modelist[MODE_FULLSCREEN_DEFAULT].height >>= 1;
			snprintf (modelist[MODE_FULLSCREEN_DEFAULT].modedesc,
					  sizeof (modelist[MODE_FULLSCREEN_DEFAULT].modedesc),
					  "%dx%d", modelist[MODE_FULLSCREEN_DEFAULT].width,
					  modelist[MODE_FULLSCREEN_DEFAULT].height);
		}

		vid_default = MODE_FULLSCREEN_DEFAULT;

		temp = m[0];

		if (!MGL_init (&driver, &temp, "")) {
			initFatalError ();
		}
	}

	MGL_setSuspendAppCallback (VID_Suspend);
}

/*
  createDisplayDC

  Returns pointer to the MGL device context to use for the application
  Initialises the MGL and creates an appropriate display device context to be
  used by the GUI. This creates and apropriate device context depending on the
  system being compile for, and should be the only place where system specific
  code is required.
*/
MGLDC      *
createDisplayDC (int forcemem)
{
	MGLDC      *dc;
	pixel_format_t pf;
	int         npages;

	// Start the specified video mode
	if (!MGL_changeDisplayMode (mode))
		initFatalError ();

	npages = MGL_availablePages (mode);

	if (npages > 3)
		npages = 3;

	if (!COM_CheckParm ("-notriplebuf")) {
		if (npages > 2) {
			npages = 2;
		}
	}

	if ((dc = MGL_createDisplayDC (npages)) == NULL)
		return NULL;

	if (!forcemem && (MGL_surfaceAccessType (dc)) == MGL_LINEAR_ACCESS
		&& (dc->mi.maxPage > 0)) {
		MGL_makeCurrentDC (dc);
		memdc = NULL;
	} else {
		// Set up for blitting from a memory buffer
		memdc = MGL_createMemoryDC (MGL_sizex (dc) + 1, MGL_sizey (dc) + 1, 8,
									&pf);
		MGL_makeCurrentDC (memdc);
	}

	// Enable page flipping even for even for blitted surfaces
	if (forcemem) {
		vid.numpages = 1;
	} else {
		vid.numpages = dc->mi.maxPage + 1;

		if (vid.numpages > 1) {
			// Set up for page flipping
			MGL_setActivePage (dc, aPage = 1);
			MGL_setVisualPage (dc, vPage = 0, false);
		}

		if (vid.numpages > 3)
			vid.numpages = 3;
	}

	if (vid.numpages == 2)
		waitVRT = true;
	else
		waitVRT = false;

	return dc;
}

void
VID_InitMGLDIB (HINSTANCE hInstance)
{
	WNDCLASS    wc;
	HDC         hdc;

	hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON1));

	/* Register the frame class */
	wc.style = 0;
	wc.lpfnWndProc = (WNDPROC) MainWndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = 0;
	wc.hCursor = LoadCursor (NULL, IDC_ARROW);
	wc.hbrBackground = NULL;
	wc.lpszMenuName = 0;
	wc.lpszClassName = "WinQuake";

	if (!RegisterClass (&wc))
		Sys_Error ("Couldn't register window class");

	/* Find the size for the DIB window */
	/* Initialise the MGL for windowed operation */
	MGL_setAppInstance (hInstance);
	registerAllMemDrivers ();
	MGL_initWindowed ("");

	modelist[0].type = MS_WINDOWED;
	modelist[0].width = 320;
	modelist[0].height = 240;
	strcpy (modelist[0].modedesc, "320x240");
	modelist[0].mode13 = 0;
	modelist[0].modenum = MODE_WINDOWED;
	modelist[0].stretched = 0;
	modelist[0].dib = 1;
	modelist[0].fullscreen = 0;
	modelist[0].halfscreen = 0;
	modelist[0].bpp = 8;

	modelist[1].type = MS_WINDOWED;
	modelist[1].width = 640;
	modelist[1].height = 480;
	strcpy (modelist[1].modedesc, "640x480");
	modelist[1].mode13 = 0;
	modelist[1].modenum = MODE_WINDOWED + 1;
	modelist[1].stretched = 1;
	modelist[1].dib = 1;
	modelist[1].fullscreen = 0;
	modelist[1].halfscreen = 0;
	modelist[1].bpp = 8;

	modelist[2].type = MS_WINDOWED;
	modelist[2].width = 800;
	modelist[2].height = 600;
	strcpy (modelist[2].modedesc, "800x600");
	modelist[2].mode13 = 0;
	modelist[2].modenum = MODE_WINDOWED + 2;
	modelist[2].stretched = 1;
	modelist[2].dib = 1;
	modelist[2].fullscreen = 0;
	modelist[2].halfscreen = 0;
	modelist[2].bpp = 8;

	// automatically stretch default mode up if > 640x480 desktop resolution
	hdc = GetDC (NULL);

	if ((GetDeviceCaps (hdc, HORZRES) > 640)
		&& !COM_CheckParm ("-noautostretch")) {
		vid_default = MODE_WINDOWED + 1;
	} else {
		vid_default = MODE_WINDOWED;
	}

	windowed_default = vid_default;

	ReleaseDC (NULL, hdc);

	nummodes = 3;						// reserve space for windowed mode

	DDActive = 0;
}

void
VID_InitFullDIB (HINSTANCE hInstance)
{
	DEVMODE     devmode;
	int         i, j, modenum, existingmode, originalnummodes, lowestres;
	int         numlowresmodes, bpp, done;
	int         cstretch, istretch, mstretch = 0;
	BOOL        stat;

	// enumerate 8 bpp modes
	originalnummodes = nummodes;
	modenum = 0;
	lowestres = 99999;

	do {
		stat = EnumDisplaySettings (NULL, modenum, &devmode);

		if ((devmode.dmBitsPerPel == 8) && (devmode.dmPelsWidth <= MAXWIDTH) &&
			(devmode.dmPelsHeight <= MAXHEIGHT) &&
			(nummodes < MAX_MODE_LIST))	{
			devmode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

			if (ChangeDisplaySettings (&devmode, CDS_TEST | CDS_FULLSCREEN) ==
				DISP_CHANGE_SUCCESSFUL) {
				modelist[nummodes].type = MS_FULLDIB;
				modelist[nummodes].width = devmode.dmPelsWidth;
				modelist[nummodes].height = devmode.dmPelsHeight;
				modelist[nummodes].modenum = 0;
				modelist[nummodes].mode13 = 0;
				modelist[nummodes].stretched = 0;
				modelist[nummodes].halfscreen = 0;
				modelist[nummodes].dib = 1;
				modelist[nummodes].fullscreen = 1;
				modelist[nummodes].bpp = devmode.dmBitsPerPel;
				snprintf (modelist[nummodes].modedesc,
						  sizeof (modelist[nummodes].modedesc), "%ldx%ld",
						  devmode.dmPelsWidth, devmode.dmPelsHeight);

				// if the width is more than twice the height, reduce it by
				// half because this
				// is probably a dual-screen monitor
				if (!COM_CheckParm ("-noadjustaspect")) {
					if (modelist[nummodes].width >
						(modelist[nummodes].height << 1)) {
						modelist[nummodes].width >>= 1;
						modelist[nummodes].halfscreen = 1;
						snprintf (modelist[nummodes].modedesc,
								  sizeof (modelist[nummodes].modedesc),
								  "%dx%d", modelist[nummodes].width,
								  modelist[nummodes].height);
					}
				}

				for (i = originalnummodes, existingmode = 0; i < nummodes;
					 i++) {
					if ((modelist[nummodes].width == modelist[i].width) &&
						(modelist[nummodes].height == modelist[i].height)) {
						existingmode = 1;
						break;
					}
				}

				if (!existingmode) {
					if (modelist[nummodes].width < lowestres)
						lowestres = modelist[nummodes].width;

					nummodes++;
				}
			}
		}

		modenum++;
	} while (stat);

// see if any of them were actually settable; if so, this is our mode list,
// else enumerate all modes; our mode list is whichever ones are settable
// with > 8 bpp
	if (nummodes == originalnummodes) {
		modenum = 0;
		lowestres = 99999;

		Con_Printf ("No 8-bpp fullscreen DIB modes found\n");

		do {
			stat = EnumDisplaySettings (NULL, modenum, &devmode);

			if ((((devmode.dmPelsWidth <= MAXWIDTH) &&
				  (devmode.dmPelsHeight <= MAXHEIGHT)) ||
				 (!COM_CheckParm ("-noadjustaspect") &&
				  (devmode.dmPelsWidth <= (MAXWIDTH * 2)) &&
				  (devmode.dmPelsWidth > (devmode.dmPelsHeight * 2)))) &&
				(nummodes < MAX_MODE_LIST) && (devmode.dmBitsPerPel > 8)) {
				devmode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH |
					DM_PELSHEIGHT;

				if (ChangeDisplaySettings (&devmode, CDS_TEST | CDS_FULLSCREEN)
					== DISP_CHANGE_SUCCESSFUL) {
					modelist[nummodes].type = MS_FULLDIB;
					modelist[nummodes].width = devmode.dmPelsWidth;
					modelist[nummodes].height = devmode.dmPelsHeight;
					modelist[nummodes].modenum = 0;
					modelist[nummodes].mode13 = 0;
					modelist[nummodes].stretched = 0;
					modelist[nummodes].halfscreen = 0;
					modelist[nummodes].dib = 1;
					modelist[nummodes].fullscreen = 1;
					modelist[nummodes].bpp = devmode.dmBitsPerPel;
					snprintf (modelist[nummodes].modedesc,
							  sizeof (modelist[nummodes].modedesc), "%ldx%ld",
							  devmode.dmPelsWidth, devmode.dmPelsHeight);

					// if the width is more than twice the height, reduce it
					// by half because this is probably a dual-screen monitor
					if (!COM_CheckParm ("-noadjustaspect")) {
						if (modelist[nummodes].width >
							(modelist[nummodes].height * 2)) {
							modelist[nummodes].width >>= 1;
							modelist[nummodes].halfscreen = 1;
							snprintf (modelist[nummodes].modedesc,
									  sizeof (modelist[nummodes].modedesc),
									  "%dx%d", modelist[nummodes].width,
									  modelist[nummodes].height);
						}
					}

					for (i = originalnummodes, existingmode = 0; i < nummodes;
						 i++) {
						if ((modelist[nummodes].width == modelist[i].width)
							&& (modelist[nummodes].height ==
								modelist[i].height)) {
							// pick the lowest available bpp
							if (modelist[nummodes].bpp < modelist[i].bpp)
								modelist[i] = modelist[nummodes];

							existingmode = 1;
							break;
						}
					}

					if (!existingmode) {
						if (modelist[nummodes].width < lowestres)
							lowestres = modelist[nummodes].width;

						nummodes++;
					}
				}
			}

			modenum++;
		} while (stat);
	}
	// see if there are any low-res modes that aren't being reported
	numlowresmodes = sizeof (lowresmodes) / sizeof (lowresmodes[0]);
	bpp = 8;
	done = 0;

	// first make sure the driver doesn't just answer yes to all tests
	devmode.dmBitsPerPel = 8;
	devmode.dmPelsWidth = 42;
	devmode.dmPelsHeight = 37;
	devmode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

	if (ChangeDisplaySettings (&devmode, CDS_TEST | CDS_FULLSCREEN) ==
		DISP_CHANGE_SUCCESSFUL) {
		done = 1;
	}

	while (!done) {
		for (j = 0; (j < numlowresmodes) && (nummodes < MAX_MODE_LIST); j++) {
			devmode.dmBitsPerPel = bpp;
			devmode.dmPelsWidth = lowresmodes[j].width;
			devmode.dmPelsHeight = lowresmodes[j].height;
			devmode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

			if (ChangeDisplaySettings (&devmode, CDS_TEST | CDS_FULLSCREEN) ==
				DISP_CHANGE_SUCCESSFUL) {
				modelist[nummodes].type = MS_FULLDIB;
				modelist[nummodes].width = devmode.dmPelsWidth;
				modelist[nummodes].height = devmode.dmPelsHeight;
				modelist[nummodes].modenum = 0;
				modelist[nummodes].mode13 = 0;
				modelist[nummodes].stretched = 0;
				modelist[nummodes].halfscreen = 0;
				modelist[nummodes].dib = 1;
				modelist[nummodes].fullscreen = 1;
				modelist[nummodes].bpp = devmode.dmBitsPerPel;
				snprintf (modelist[nummodes].modedesc,
						  sizeof (modelist[nummodes].modedesc), "%ldx%ld",
						  devmode.dmPelsWidth, devmode.dmPelsHeight);

				// we only want the lowest-bpp version of each mode
				for (i = originalnummodes, existingmode = 0; i < nummodes;
					 i++) {
					if ((modelist[nummodes].width == modelist[i].width) &&
						(modelist[nummodes].height == modelist[i].height) &&
						(modelist[nummodes].bpp >= modelist[i].bpp)) {
						existingmode = 1;
						break;
					}
				}

				if (!existingmode) {
					if (modelist[nummodes].width < lowestres)
						lowestres = modelist[nummodes].width;

					nummodes++;
				}
			}
		}

		switch (bpp) {
			case 8:
				bpp = 16;
				break;

			case 16:
				bpp = 32;
				break;

			case 32:
				done = 1;
				break;
		}
	}

	// now add the lowest stretch-by-2 pseudo-modes between 320-wide
	// (inclusive) and lowest real res (not inclusive) don't bother if we have
	// a real VGA mode 0x13 mode
	if (!is_mode0x13) {
		for (i = originalnummodes, cstretch = 0; i < nummodes; i++) {
			if (((modelist[i].width >> 1) < lowestres) &&
				((modelist[i].width >> 1) >= 320)) {
				lowestres = modelist[i].width >> 1;
				cstretch = 1;
				mstretch = i;
			}
		}

		if ((nummodes + cstretch) > MAX_MODE_LIST)
			cstretch = MAX_MODE_LIST - nummodes;

		if (cstretch > 0) {
			for (i = (nummodes - 1); i >= originalnummodes; i--)
				modelist[i + cstretch] = modelist[i];

			nummodes += cstretch;
			istretch = originalnummodes;

			modelist[istretch] = modelist[mstretch];
			modelist[istretch].width >>= 1;
			modelist[istretch].height >>= 1;
			modelist[istretch].stretched = 1;
			snprintf (modelist[istretch].modedesc,
					  sizeof (modelist[istretch].modedesc), "%dx%d",
					  modelist[istretch].width, modelist[istretch].height);
		}
	}

	if (nummodes != originalnummodes)
		vid_default = MODE_FULLSCREEN_DEFAULT;
	else
		Con_Printf ("No fullscreen DIB modes found\n");
}

int
VID_NumModes (void)
{
	return nummodes;
}

vmode_t    *
VID_GetModePtr (int modenum)
{

	if ((modenum >= 0) && (modenum < nummodes))
		return &modelist[modenum];
	else
		return &badmode;
}

void
VID_CheckModedescFixup (int mode)
{
	int         x, y, stretch;

	if (mode == MODE_SETTABLE_WINDOW) {
		modelist[mode].stretched = vid_stretch_by_2->int_val;
		stretch = modelist[mode].stretched;

		Cvar_SetValue (vid_config_x,
					   max (vid_config_x->int_val, 320 << stretch));
		Cvar_SetValue (vid_config_y,
					   max (vid_config_y->int_val, 200 << stretch));

		x = vid_config_x->int_val;
		y = vid_config_y->int_val;
		snprintf (modelist[mode].modedesc, sizeof (modelist[mode].modedesc),
				  "%dx%d", x, y);
		modelist[mode].width = x;
		modelist[mode].height = y;
	}
}

char       *
VID_GetModeDescriptionMemCheck (int mode)
{
	char       *pinfo;
	vmode_t    *pv;

	if ((mode < 0) || (mode >= nummodes))
		return NULL;

	VID_CheckModedescFixup (mode);

	pv = VID_GetModePtr (mode);
	pinfo = pv->modedesc;

	if (VID_CheckAdequateMem (pv->width, pv->height)) {
		return pinfo;
	} else {
		return NULL;
	}
}

char       *
VID_GetModeDescription (int mode)
{
	char       *pinfo;
	vmode_t    *pv;

	if ((mode < 0) || (mode >= nummodes))
		return NULL;

	VID_CheckModedescFixup (mode);

	pv = VID_GetModePtr (mode);
	pinfo = pv->modedesc;
	return pinfo;
}

/*
	VID_GetModeDescription2

	Tacks on "windowed" or "fullscreen"
*/
char       *
VID_GetModeDescription2 (int mode)
{
	static char pinfo[40];
	vmode_t    *pv;

	if ((mode < 0) || (mode >= nummodes))
		return NULL;

	VID_CheckModedescFixup (mode);

	pv = VID_GetModePtr (mode);

	if (modelist[mode].type == MS_FULLSCREEN) {
		snprintf (pinfo, sizeof (pinfo), "%s fullscreen", pv->modedesc);
	} else if (modelist[mode].type == MS_FULLDIB) {
		snprintf (pinfo, sizeof (pinfo), "%s fullscreen", pv->modedesc);
	} else {
		snprintf (pinfo, sizeof (pinfo), "%s windowed", pv->modedesc);
	}

	return pinfo;
}

// KJB: Added this to return the mode driver name in description for console
char       *
VID_GetExtModeDescription (int mode)
{
	static char pinfo[40];
	vmode_t    *pv;

	if ((mode < 0) || (mode >= nummodes))
		return NULL;

	VID_CheckModedescFixup (mode);

	pv = VID_GetModePtr (mode);
	if (modelist[mode].type == MS_FULLSCREEN) {
		snprintf (pinfo, sizeof (pinfo), "%s fullscreen %s", pv->modedesc,
				  MGL_modeDriverName (pv->modenum));
	} else if (modelist[mode].type == MS_FULLDIB) {
		snprintf (pinfo, sizeof (pinfo), "%s fullscreen DIB", pv->modedesc);
	} else {
		snprintf (pinfo, sizeof (pinfo), "%s windowed", pv->modedesc);
	}

	return pinfo;
}

void
DestroyDIBWindow (void)
{

	if (modestate == MS_WINDOWED) {
		// destroy the associated MGL DC's; the window gets reused
		if (windc)
			MGL_destroyDC (windc);
		if (dibdc)
			MGL_destroyDC (dibdc);
		windc = dibdc = NULL;
	}
}

void
DestroyFullscreenWindow (void)
{
	if (modestate == MS_FULLSCREEN) {
		// destroy the existing fullscreen mode and DC's
		if (mgldc)
			MGL_destroyDC (mgldc);
		if (memdc)
			MGL_destroyDC (memdc);
		mgldc = memdc = NULL;
	}
}

void
DestroyFullDIBWindow (void)
{
	if (modestate == MS_FULLDIB) {
		ChangeDisplaySettings (NULL, CDS_FULLSCREEN);

		// Destroy the fullscreen DIB window and associated MGL DC's
		if (windc)
			MGL_destroyDC (windc);
		if (dibdc)
			MGL_destroyDC (dibdc);
		windc = dibdc = NULL;
	}
}

qboolean
VID_SetWindowedMode (int modenum)
{
	HDC         hdc;
	pixel_format_t pf;
	qboolean    stretched;
	int         lastmodestate;

	if (!windowed_mode_set) {
		if (COM_CheckParm ("-resetwinpos")) {
			Cvar_SetValue (vid_window_x, 0.0);
			Cvar_SetValue (vid_window_y, 0.0);
		}

		windowed_mode_set = 1;
	}

	VID_CheckModedescFixup (modenum);

	DDActive = 0;
	lastmodestate = modestate;

	DestroyFullscreenWindow ();
	DestroyFullDIBWindow ();

	if (windc)
		MGL_destroyDC (windc);
	if (dibdc)
		MGL_destroyDC (dibdc);
	windc = dibdc = NULL;

	// KJB: Signal to the MGL that we are going back to windowed mode
	if (!MGL_changeDisplayMode (grWINDOWED))
		initFatalError ();

	WindowRect.top = WindowRect.left = 0;

	WindowRect.right = modelist[modenum].width;
	WindowRect.bottom = modelist[modenum].height;
	stretched = modelist[modenum].stretched;

	DIBWidth = modelist[modenum].width;
	DIBHeight = modelist[modenum].height;

	if (stretched) {
		DIBWidth >>= 1;
		DIBHeight >>= 1;
	}

	WindowStyle = WS_OVERLAPPED | WS_BORDER | WS_CAPTION | WS_SYSMENU |
		WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
	ExWindowStyle = 0;
	AdjustWindowRectEx (&WindowRect, WindowStyle, FALSE, 0);

	// the first time we're called to set the mode, create the window we'll use
	// for the rest of the session
	if (!vid_mode_set) {
		mainwindow = CreateWindowEx (ExWindowStyle, "WinQuake", PROGRAM,
									 WindowStyle, 0, 0,
									 WindowRect.right - WindowRect.left,
									 WindowRect.bottom - WindowRect.top,
									 NULL, NULL, global_hInstance, NULL);
		if (!mainwindow)
			Sys_Error ("Couldn't create DIB window");

		// tell MGL to use this window for fullscreen modes
		MGL_registerFullScreenWindow (mainwindow);

		vid_mode_set = true;
	} else {
		SetWindowLong (mainwindow, GWL_STYLE, WindowStyle | WS_VISIBLE);
		SetWindowLong (mainwindow, GWL_EXSTYLE, ExWindowStyle);
	}

	if (!SetWindowPos (mainwindow, NULL, 0, 0,
					   WindowRect.right - WindowRect.left,
					   WindowRect.bottom - WindowRect.top,
					   SWP_NOCOPYBITS | SWP_NOZORDER | SWP_HIDEWINDOW)) {
		Sys_Error ("Couldn't resize DIB window");
	}

	if (hide_window)
		return true;

	// position and show the DIB window
	VID_CheckWindowXY ();
	SetWindowPos (mainwindow, NULL, vid_window_x->int_val,
				  vid_window_y->int_val, 0, 0,
				  SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW | SWP_DRAWFRAME);

	if (force_minimized)
		ShowWindow (mainwindow, SW_MINIMIZE);
	else
		ShowWindow (mainwindow, SW_SHOWDEFAULT);

	UpdateWindow (mainwindow);

	modestate = MS_WINDOWED;
	vid_fulldib_on_focus_mode = 0;

// because we have set the background brush for the window to NULL
// (to avoid flickering when re-sizing the window on the desktop),
// we clear the window to black when created, otherwise it will be
// empty while Quake starts up.
	hdc = GetDC (mainwindow);
	PatBlt (hdc, 0, 0, WindowRect.right, WindowRect.bottom, BLACKNESS);
	ReleaseDC (mainwindow, hdc);

	/* Create the MGL window DC and the MGL memory DC */
	if ((windc = MGL_createWindowedDC (mainwindow)) == NULL)
		MGL_fatalError ("Unable to create Windowed DC!");

	if ((dibdc = MGL_createMemoryDC (DIBWidth, DIBHeight, 8, &pf)) == NULL)
		MGL_fatalError ("Unable to create Memory DC!");

	MGL_makeCurrentDC (dibdc);

	vid.buffer = vid.conbuffer = vid.direct = dibdc->surface;
	vid.rowbytes = vid.conrowbytes = dibdc->mi.bytesPerLine;
	vid.numpages = 1;
	vid.maxwarpwidth = WARP_WIDTH;
	vid.maxwarpheight = WARP_HEIGHT;
	vid.height = vid.conheight = DIBHeight;
	vid.width = vid.conwidth = DIBWidth;
	vid.aspect = ((float) vid.height / (float) vid.width) * (320.0 / 240.0);

	vid_stretched = stretched;

	SendMessage (mainwindow, WM_SETICON, (WPARAM) TRUE, (LPARAM) hIcon);
	SendMessage (mainwindow, WM_SETICON, (WPARAM) FALSE, (LPARAM) hIcon);

	return true;
}

qboolean
VID_SetFullscreenMode (int modenum)
{

	DDActive = 1;

	DestroyDIBWindow ();
	DestroyFullDIBWindow ();

	mode = modelist[modenum].modenum;

	// Destroy old DC's, resetting back to fullscreen mode
	if (mgldc)
		MGL_destroyDC (mgldc);
	if (memdc)
		MGL_destroyDC (memdc);
	mgldc = memdc = NULL;

	if ((mgldc = createDisplayDC (modelist[modenum].stretched ||
								  vid_nopageflip->int_val)) == NULL) {
		return false;
	}

	modestate = MS_FULLSCREEN;
	vid_fulldib_on_focus_mode = 0;

	vid.buffer = vid.conbuffer = vid.direct = NULL;
	vid.maxwarpwidth = WARP_WIDTH;
	vid.maxwarpheight = WARP_HEIGHT;
	DIBHeight = vid.height = vid.conheight = modelist[modenum].height;
	DIBWidth = vid.width = vid.conwidth = modelist[modenum].width;
	vid.aspect = ((float) vid.height / (float) vid.width) * (320.0 / 240.0);

	vid_stretched = modelist[modenum].stretched;

	// needed because we're not getting WM_MOVE messages fullscreen on NT
	window_x = 0;
	window_y = 0;

	// set the large icon, so the Quake icon will show up in the taskbar
	SendMessage (mainwindow, WM_SETICON, (WPARAM) 1, (LPARAM) hIcon);
	SendMessage (mainwindow, WM_SETICON, (WPARAM) 0, (LPARAM) hIcon);

	// shouldn't be needed, but Kendall needs to let us get the activation
	// message for this not to be needed on NT
	AppActivate (true, false);

	return true;
}

qboolean
VID_SetFullDIBMode (int modenum)
{
	HDC         hdc;
	pixel_format_t pf;
	int         lastmodestate;

	DDActive = 0;

	DestroyFullscreenWindow ();
	DestroyDIBWindow ();

	if (windc)
		MGL_destroyDC (windc);
	if (dibdc)
		MGL_destroyDC (dibdc);
	windc = dibdc = NULL;

	// KJB: Signal to the MGL that we are going back to windowed mode
	if (!MGL_changeDisplayMode (grWINDOWED))
		initFatalError ();

	gdevmode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
	gdevmode.dmBitsPerPel = modelist[modenum].bpp;
	gdevmode.dmPelsWidth =
		modelist[modenum].width << modelist[modenum].
		stretched << modelist[modenum].halfscreen;
	gdevmode.dmPelsHeight =
		modelist[modenum].height << modelist[modenum].stretched;
	gdevmode.dmSize = sizeof (gdevmode);

	if (ChangeDisplaySettings (&gdevmode, CDS_FULLSCREEN) !=
		DISP_CHANGE_SUCCESSFUL) Sys_Error ("Couldn't set fullscreen DIB mode");

	lastmodestate = modestate;
	modestate = MS_FULLDIB;
	vid_fulldib_on_focus_mode = modenum;

	WindowRect.top = WindowRect.left = 0;

	hdc = GetDC (NULL);

	WindowRect.right = modelist[modenum].width << modelist[modenum].stretched;
	WindowRect.bottom = modelist[modenum].height <<
		modelist[modenum].stretched;

	ReleaseDC (NULL, hdc);

	DIBWidth = modelist[modenum].width;
	DIBHeight = modelist[modenum].height;

	WindowStyle = WS_POPUP | WS_SYSMENU | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
	ExWindowStyle = 0;
	AdjustWindowRectEx (&WindowRect, WindowStyle, FALSE, 0);

	SetWindowLong (mainwindow, GWL_STYLE, WindowStyle | WS_VISIBLE);
	SetWindowLong (mainwindow, GWL_EXSTYLE, ExWindowStyle);

	if (!SetWindowPos (mainwindow, NULL, 0, 0,
					   WindowRect.right - WindowRect.left,
					   WindowRect.bottom - WindowRect.top,
					   SWP_NOCOPYBITS | SWP_NOZORDER)) {
		Sys_Error ("Couldn't resize DIB window");
	}
	// position and show the DIB window
	SetWindowPos (mainwindow, HWND_TOPMOST, 0, 0, 0, 0,
				  SWP_NOSIZE | SWP_SHOWWINDOW | SWP_DRAWFRAME);
	ShowWindow (mainwindow, SW_SHOWDEFAULT);
	UpdateWindow (mainwindow);

	// Because we have set the background brush for the window to NULL
	// (to avoid flickering when re-sizing the window on the desktop), we
	// clear the window to black when created, otherwise it will be
	// empty while Quake starts up.
	hdc = GetDC (mainwindow);
	PatBlt (hdc, 0, 0, WindowRect.right, WindowRect.bottom, BLACKNESS);
	ReleaseDC (mainwindow, hdc);

	/* Create the MGL window DC and the MGL memory DC */
	if ((windc = MGL_createWindowedDC (mainwindow)) == NULL)
		MGL_fatalError ("Unable to create Fullscreen DIB DC!");

	if ((dibdc = MGL_createMemoryDC (DIBWidth, DIBHeight, 8, &pf)) == NULL)
		MGL_fatalError ("Unable to create Memory DC!");

	MGL_makeCurrentDC (dibdc);

	vid.buffer = vid.conbuffer = vid.direct = dibdc->surface;
	vid.rowbytes = vid.conrowbytes = dibdc->mi.bytesPerLine;
	vid.numpages = 1;
	vid.maxwarpwidth = WARP_WIDTH;
	vid.maxwarpheight = WARP_HEIGHT;
	vid.height = vid.conheight = DIBHeight;
	vid.width = vid.conwidth = DIBWidth;
	vid.aspect = ((float) vid.height / (float) vid.width) * (320.0 / 240.0);

	vid_stretched = modelist[modenum].stretched;

	// needed because we're not getting WM_MOVE messages fullscreen on NT
	window_x = 0;
	window_y = 0;

	return true;
}

void
VID_RestoreOldMode (int original_mode)
{
	static qboolean inerror = false;

	if (inerror)
		return;

	in_mode_set = false;
	inerror = true;

	// make sure mode set happens (video mode changes)
	vid_modenum = original_mode - 1;

	if (!VID_SetMode (original_mode, vid_curpal)) {
		vid_modenum = MODE_WINDOWED - 1;

		if (!VID_SetMode (windowed_default, vid_curpal))
			Sys_Error ("Can't set any video mode");
	}

	inerror = false;
}

void
VID_SetDefaultMode (void)
{

	if (vid.initialized)
		VID_SetMode (0, vid_curpal);

	IN_DeactivateMouse ();
}

int
VID_SetMode (int modenum, unsigned char *palette)
{
	int         original_mode, temp;
	qboolean    stat;
	MSG         msg;
	HDC         hdc;

	while ((modenum >= nummodes) || (modenum < 0)) {
		if (vid_modenum == NO_MODE) {
			if (modenum == vid_default) {
				modenum = windowed_default;
			} else {
				modenum = vid_default;
			}

			Cvar_SetValue (vid_mode, modenum);
		} else {
			Cvar_SetValue (vid_mode, vid_modenum);
			return 0;
		}
	}

	if (!force_mode_set && (modenum == vid_modenum))
		return true;

	// so Con_Printfs don't mess us up by forcing vid and snd updates
	temp = scr_disabled_for_loading;
	scr_disabled_for_loading = true;
	in_mode_set = true;

	CDAudio_Pause ();
	S_ClearBuffer ();

	if (vid_modenum == NO_MODE)
		original_mode = windowed_default;
	else
		original_mode = vid_modenum;

	// Set either the fullscreen or windowed mode
	if (modelist[modenum].type == MS_WINDOWED) {
		if (_windowed_mouse->int_val && key_dest == key_game) {
			stat = VID_SetWindowedMode (modenum);
			IN_ActivateMouse ();
			IN_HideMouse ();
		} else {
			IN_DeactivateMouse ();
			IN_ShowMouse ();
			stat = VID_SetWindowedMode (modenum);
		}
	} else if (modelist[modenum].type == MS_FULLDIB) {
		stat = VID_SetFullDIBMode (modenum);
		IN_ActivateMouse ();
		IN_HideMouse ();
	} else {
		stat = VID_SetFullscreenMode (modenum);
		IN_ActivateMouse ();
		IN_HideMouse ();
	}

	window_width = vid.width << vid_stretched;
	window_height = vid.height << vid_stretched;
	VID_UpdateWindowStatus ();

	CDAudio_Resume ();
	scr_disabled_for_loading = temp;

	if (!stat) {
		VID_RestoreOldMode (original_mode);
		return false;
	}

	if (hide_window)
		return true;

// now we try to make sure we get the focus on the mode switch, because
// sometimes in some systems we don't.  We grab the foreground, then
// finish setting up, pump all our messages, and sleep for a little while
// to let messages finish bouncing around the system, then we put
// ourselves at the top of the z order, then grab the foreground again,
// Who knows if it helps, but it probably doesn't hurt
	if (!force_minimized)
		SetForegroundWindow (mainwindow);

	hdc = GetDC (NULL);

	if (GetDeviceCaps (hdc, RASTERCAPS) & RC_PALETTE)
		vid_palettized = true;
	else
		vid_palettized = false;

	VID_SetPalette (palette);

	ReleaseDC (NULL, hdc);

	vid_modenum = modenum;
	Cvar_SetValue (vid_mode, vid_modenum);

	if (!VID_AllocBuffers (vid.width, vid.height)) {
		// couldn't get memory for this mode; try to fall back to previous
		// mode
		VID_RestoreOldMode (original_mode);
		return false;
	}

	D_InitCaches (vid_surfcache, vid_surfcachesize);

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

	Sleep (100);

	if (!force_minimized) {
		SetWindowPos (mainwindow, HWND_TOP, 0, 0, 0, 0, SWP_DRAWFRAME |
					  SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW |
					  SWP_NOCOPYBITS);

		SetForegroundWindow (mainwindow);
	}

	// fix the leftover Alt from any Alt-Tab or the like that switched us away
	ClearAllStates ();

	Con_Printf ("Video mode %s initialized\n",
				VID_GetModeDescription (vid_modenum));

	VID_SetPalette (palette);

	in_mode_set = false;
	vid.recalc_refdef = 1;

	return true;
}

void
VID_LockBuffer (void)
{
	if (dibdc)
		return;

	lockcount++;

	if (lockcount > 1)
		return;

	MGL_beginDirectAccess ();

	if (memdc) {
		// Update surface pointer for linear access modes
		vid.buffer = vid.conbuffer = vid.direct = memdc->surface;
		vid.rowbytes = vid.conrowbytes = memdc->mi.bytesPerLine;
	} else if (mgldc) {
		// Update surface pointer for linear access modes
		vid.buffer = vid.conbuffer = vid.direct = mgldc->surface;
		vid.rowbytes = vid.conrowbytes = mgldc->mi.bytesPerLine;
	}

	if (r_dowarp)
		d_viewbuffer = r_warpbuffer;
	else
		d_viewbuffer = (void *) (byte *) vid.buffer;

	if (r_dowarp)
		screenwidth = WARP_WIDTH;
	else
		screenwidth = vid.rowbytes;
}

void
VID_UnlockBuffer (void)
{
	if (dibdc)
		return;

	lockcount--;

	if (lockcount > 0)
		return;

	if (lockcount < 0)
		Sys_Error ("Unbalanced unlock");

	MGL_endDirectAccess ();

// to turn up any unlocked accesses
	vid.buffer = vid.conbuffer = vid.direct = d_viewbuffer = NULL;
}

int
VID_ForceUnlockedAndReturnState (void)
{
	int         lk;

	if (!lockcount)
		return 0;

	lk = lockcount;

	if (dibdc) {
		lockcount = 0;
	} else {
		lockcount = 1;
		VID_UnlockBuffer ();
	}

	return lk;
}

void
VID_ForceLockState (int lk)
{
	if (!dibdc && lk) {
		lockcount = 0;
		VID_LockBuffer ();
	}

	lockcount = lk;
}

void
VID_SetPalette (unsigned char *palette)
{
	INT         i;
	palette_t   pal[256];
	HDC         hdc;

	if (!Minimized) {
		palette_changed = true;

		// make sure we have the static colors if we're the active app
		hdc = GetDC (NULL);

		if (vid_palettized && ActiveApp) {
			if (GetSystemPaletteUse (hdc) == SYSPAL_STATIC) {
				// switch to SYSPAL_NOSTATIC and remap the colors
				SetSystemPaletteUse (hdc, SYSPAL_NOSTATIC);
				syscolchg = true;
				pal_is_nostatic = true;
			}
		}

		ReleaseDC (NULL, hdc);

		// Translate the palette values to an MGL palette array and
		// set the values.
		for (i = 0; i < 256; i++) {
			pal[i].red = palette[i * 3];
			pal[i].green = palette[i * 3 + 1];
			pal[i].blue = palette[i * 3 + 2];
		}

		if (DDActive) {
			if (!mgldc)
				return;

			MGL_setPalette (mgldc, pal, 256, 0);
			MGL_realizePalette (mgldc, 256, 0, false);
			if (memdc)
				MGL_setPalette (memdc, pal, 256, 0);
		} else {
			if (!windc)
				return;

			MGL_setPalette (windc, pal, 256, 0);
			MGL_realizePalette (windc, 256, 0, false);
			if (dibdc) {
				MGL_setPalette (dibdc, pal, 256, 0);
				MGL_realizePalette (dibdc, 256, 0, false);
			}
		}
	}

	memcpy (vid_curpal, palette, sizeof (vid_curpal));

	if (syscolchg) {
		PostMessage (HWND_BROADCAST, WM_SYSCOLORCHANGE, (WPARAM) 0,
					 (LPARAM) 0);
		syscolchg = false;
	}
}

void
VID_DescribeCurrentMode_f (void)
{
	Con_Printf ("%s\n", VID_GetExtModeDescription (vid_modenum));
}

void
VID_NumModes_f (void)
{

	if (nummodes == 1)
		Con_Printf ("%d video mode is available\n", nummodes);
	else
		Con_Printf ("%d video modes are available\n", nummodes);
}

void
VID_DescribeMode_f (void)
{
	int         modenum;

	modenum = atoi (Cmd_Argv (1));

	Con_Printf ("%s\n", VID_GetExtModeDescription (modenum));
}

void
VID_DescribeModes_f (void)
{
	int         i, lnummodes;
	char       *pinfo;
	qboolean    na;
	vmode_t    *pv;

	na = false;

	lnummodes = VID_NumModes ();

	for (i = 0; i < lnummodes; i++) {
		pv = VID_GetModePtr (i);
		pinfo = VID_GetExtModeDescription (i);

		if (VID_CheckAdequateMem (pv->width, pv->height)) {
			Con_Printf ("%2d: %s\n", i, pinfo);
		} else {
			Con_Printf ("**: %s\n", pinfo);
			na = true;
		}
	}

	if (na) {
		Con_Printf ("\n[**: not enough system RAM for mode]\n");
	}
}

void
VID_TestMode_f (void)
{
	int         modenum;
	double      testduration;

	if (!vid_testingmode) {
		modenum = atoi (Cmd_Argv (1));

		if (VID_SetMode (modenum, vid_curpal)) {
			vid_testingmode = 1;
			testduration = atof (Cmd_Argv (2));
			if (testduration == 0)
				testduration = 5.0;
			vid_testendtime = realtime + testduration;
		}
	}
}

void
VID_Windowed_f (void)
{

	VID_SetMode (vid_windowed_mode->int_val, vid_curpal);
}

void
VID_Fullscreen_f (void)
{

	VID_SetMode (vid_fullscreen_mode->int_val, vid_curpal);
}

void
VID_Minimize_f (void)
{
	// we only support minimizing windows; if you're fullscreen,
	// switch to windowed first
	if (modestate == MS_WINDOWED)
		ShowWindow (mainwindow, SW_MINIMIZE);
}

void
VID_ForceMode_f (void)
{
	int         modenum;

	if (!vid_testingmode) {
		modenum = atoi (Cmd_Argv (1));

		force_mode_set = 1;
		VID_SetMode (modenum, vid_curpal);
		force_mode_set = 0;
	}
}

void
VID_Init (unsigned char *palette)
{
	int         i, bestmatch = 0, bestmatchmetric, t, dr, dg, db;
	int         basenummodes;
	byte       *ptmp;

	Cmd_AddCommand ("vid_testmode", VID_TestMode_f, "Switch to another video "
					"mode temporarily for 5-seconds to test it.\n"
					"(vid_testmode (mode))");
	Cmd_AddCommand ("vid_nummodes", VID_NumModes_f, "Reports the total number "
					"of video modes available");
	Cmd_AddCommand ("vid_describecurrentmode", VID_DescribeCurrentMode_f,
					"Report current video mode.");
	Cmd_AddCommand ("vid_describemode", VID_DescribeMode_f, "Report "
					"information on specified video mode, default is "
					"current.\n(vid_describemode (mode))");
	Cmd_AddCommand ("vid_describemodes", VID_DescribeModes_f, "Report "
					"information on all video modes.");
	Cmd_AddCommand ("vid_forcemode", VID_ForceMode_f, "Force QuakeWorld to "
					"use a certain video mode. (vid_forcemode (mode))");
	Cmd_AddCommand ("vid_windowed", VID_Windowed_f, "Switch to windowed mode");
	Cmd_AddCommand ("vid_fullscreen", VID_Fullscreen_f, "Switch to full "
					"screen mode");
	Cmd_AddCommand ("vid_minimize", VID_Minimize_f, "Minimize windowed Quake");

	if (COM_CheckParm ("-dibonly"))
		dibonly = true;

	VID_InitMGLDIB (global_hInstance);

	basenummodes = nummodes;

	if (!dibonly)
		VID_InitMGLFull (global_hInstance);

	// if there are no non-windowed modes, or only windowed and mode 0x13,
	// then use fullscreen DIBs as well
	if (((nummodes == basenummodes) || ((nummodes == (basenummodes + 1)) &&
										is_mode0x13)) &&
		!COM_CheckParm ("-nofulldib"))
	{
		VID_InitFullDIB (global_hInstance);
	}

	vid.maxwarpwidth = WARP_WIDTH;
	vid.maxwarpheight = WARP_HEIGHT;
	vid.colormap8 = vid_colormap;
	vid.fullbright = 256 - LittleLong (*((int *) vid.colormap8 + 2048));
	vid_testingmode = 0;

	// GDI doesn't let us remap palette index 0, so we'll remap color
	// mappings from that black to another one
	bestmatchmetric = 256 * 256 * 3;

	for (i = 1; i < 256; i++) {
		dr = palette[0] - palette[i * 3];
		dg = palette[1] - palette[i * 3 + 1];
		db = palette[2] - palette[i * 3 + 2];

		t = (dr * dr) + (dg * dg) + (db * db);

		if (t < bestmatchmetric) {
			bestmatchmetric = t;
			bestmatch = i;

			if (t == 0)
				break;
		}
	}

	for (i = 0, ptmp = vid.colormap8; i < (1 << (VID_CBITS + 8));
		 i++, ptmp++) {
		if (*ptmp == 0)
			*ptmp = bestmatch;
	}

	if (COM_CheckParm ("-startwindowed") || COM_CheckParm ("-window")) {
		startwindowed = 1;
		vid_default = windowed_default;
	}
#ifdef SPLASH_SCREEN
	if (hwnd_dialog)
		DestroyWindow (hwnd_dialog);
#endif

// sound initialization has to go here, preceded by a windowed mode set,
// so there's a window for DirectSound to work with but we're not yet
// fullscreen so the "hardware already in use" dialog is visible if it
// gets displayed

	// keep the window minimized until we're ready for the first real mode set
	hide_window = true;
	VID_SetMode (MODE_WINDOWED, palette);
	hide_window = false;
//	S_Init ();

	vid.initialized = true;

	force_mode_set = true;
	VID_SetMode (vid_default, palette);
	force_mode_set = false;

	vid_realmode = vid_modenum;

	VID_SetPalette (palette);

	vid_menudrawfn = VID_MenuDraw;
	vid_menukeyfn = VID_MenuKey;

	strcpy (badmode.modedesc, "Bad mode");
}

void
VID_Init_Cvars ()
{
	vid_mode = Cvar_Get ("vid_mode", "0", CVAR_NONE, NULL, "Set the video "
						 "mode");
	vid_nopageflip = Cvar_Get ("vid_nopageflip", "0", CVAR_ARCHIVE, NULL,
							   "Toggles the use of page flipping");
	_vid_default_mode_win = Cvar_Get ("_vid_default_mode_win", "3",
									  CVAR_ARCHIVE, NULL, "Default windowed "
									  "video mode");
	vid_config_x = Cvar_Get ("vid_config_x", "800", CVAR_ARCHIVE, NULL,
							 "Maximum x-axis screen size");
	vid_config_y = Cvar_Get ("vid_config_y", "600", CVAR_ARCHIVE, NULL,
							 "Maximum y-axis screen size");
	vid_stretch_by_2 = Cvar_Get ("vid_stretch_by_2", "1", CVAR_ARCHIVE, NULL,
								 "Stretch the pixles by a two fold to acheive "
								 "proper view");
	_windowed_mouse = Cvar_Get ("_windowed_mouse", "0", CVAR_ARCHIVE, NULL,
								"Have quake grab the mouse from X when you "
								"play");
	vid_fullscreen_mode = Cvar_Get ("vid_fullscreen_mode", "3", CVAR_ARCHIVE,
									NULL, "Set the full screen video mode.");
	vid_windowed_mode =	Cvar_Get ("vid_windowed_mode", "0", CVAR_ARCHIVE, NULL,
								  "Set the windowed video mode");
	block_switch = Cvar_Get ("block_switch", "0", CVAR_ARCHIVE, NULL, "If "
							 "set, won't allow you to task switch while "
							 "playing");
	vid_window_x = Cvar_Get ("vid_window_x", "0", CVAR_ARCHIVE, NULL, "The "
							 "x-axis location of the window, if windowed");
	vid_window_y = Cvar_Get ("vid_window_y", "0", CVAR_ARCHIVE, NULL, "The "
							 "y-axis location of the window, if windowed");
}

void
VID_Shutdown (void)
{
#ifdef SPLASH_SCREEN
                if (hwnd_dialog)
                        DestroyWindow (hwnd_dialog);
#endif

	if (vid.initialized) {
		if (modestate == MS_FULLDIB)
			ChangeDisplaySettings (NULL, CDS_FULLSCREEN);

		PostMessage (HWND_BROADCAST, WM_PALETTECHANGED, (WPARAM) mainwindow,
					 (LPARAM) 0);
		PostMessage (HWND_BROADCAST, WM_SYSCOLORCHANGE, (WPARAM) 0,
					 (LPARAM) 0);

		AppActivate (false, false);
		DestroyDIBWindow ();
		DestroyFullscreenWindow ();
		DestroyFullDIBWindow ();

		if (mainwindow)
			DestroyWindow (mainwindow);

		MGL_exit ();

		vid_testingmode = 0;
		vid.initialized = 0;
	}
}

void
FlipScreen (vrect_t *rects)
{
	/* Flip the surfaces */
	if (DDActive) {
		if (mgldc) {
			if (memdc) {
				while (rects) {
					if (vid_stretched) {
						MGL_stretchBltCoord (mgldc, memdc, rects->x,
											 rects->y, rects->x + rects->width,
											 rects->y + rects->height,
											 rects->x << 1, rects->y << 1,
											 (rects->x + rects->width) << 1,
											 (rects->y + rects->height) << 1);
					} else {
						MGL_bitBltCoord (mgldc, memdc, rects->x, rects->y,
										 (rects->x + rects->width),
										 (rects->y + rects->height),
										 rects->x, rects->y, MGL_REPLACE_MODE);
					}

					rects = rects->pnext;
				}
			}

			if (vid.numpages > 1) {
				// We have a flipping surface, so do a hard page flip
				aPage = (aPage + 1) % vid.numpages;
				vPage = (vPage + 1) % vid.numpages;
				MGL_setActivePage (mgldc, aPage);
				MGL_setVisualPage (mgldc, vPage, waitVRT);
			}
		}
	} else {
		HDC         hdcScreen;

		hdcScreen = GetDC (mainwindow);

		if (windc && dibdc) {
			MGL_setWinDC (windc, hdcScreen);

			while (rects) {
				if (vid_stretched) {
					MGL_stretchBltCoord (windc, dibdc, rects->x, rects->y,
										 rects->x + rects->width,
										 rects->y + rects->height,
										 rects->x << 1, rects->y << 1,
										 (rects->x + rects->width) << 1,
										 (rects->y + rects->height) << 1);
				} else {
					MGL_bitBltCoord (windc, dibdc, rects->x, rects->y,
									 rects->x + rects->width,
									 rects->y + rects->height, rects->x,
									 rects->y, MGL_REPLACE_MODE);
				}

				rects = rects->pnext;
			}
		}

		ReleaseDC (mainwindow, hdcScreen);
	}
}

void
VID_Update (vrect_t *rects)
{
	vrect_t     rect;
	RECT        trect;

	if (!vid_palettized && palette_changed) {
		palette_changed = false;
		rect.x = 0;
		rect.y = 0;
		rect.width = vid.width;
		rect.height = vid.height;
		rect.pnext = NULL;
		rects = &rect;
	}

	if (firstupdate) {
		if (modestate == MS_WINDOWED) {
			GetWindowRect (mainwindow, &trect);

			if ((trect.left != vid_window_x->int_val) ||
				(trect.top != vid_window_y->int_val)) {
				if (COM_CheckParm ("-resetwinpos")) {
					Cvar_SetValue (vid_window_x, 0.0);
					Cvar_SetValue (vid_window_y, 0.0);
				}

				VID_CheckWindowXY ();
				SetWindowPos (mainwindow, NULL, vid_window_x->int_val,
							  vid_window_y->int_val, 0, 0, SWP_NOSIZE |
							  SWP_NOZORDER | SWP_SHOWWINDOW | SWP_DRAWFRAME);
			}
		}

		if ((_vid_default_mode_win->int_val != vid_default) &&
			(!startwindowed || (_vid_default_mode_win->int_val <
								MODE_FULLSCREEN_DEFAULT))) {
			firstupdate = 0;

			if (COM_CheckParm ("-resetwinpos")) {
				Cvar_SetValue (vid_window_x, 0.0);
				Cvar_SetValue (vid_window_y, 0.0);
			}

			if ((_vid_default_mode_win->int_val < 0) ||
				(_vid_default_mode_win->int_val >= nummodes)) {
				Cvar_SetValue (_vid_default_mode_win, windowed_default);
			}

			Cvar_SetValue (vid_mode, _vid_default_mode_win->int_val);
		}
	}
	// We've drawn the frame; copy it to the screen
	FlipScreen (rects);

	if (vid_testingmode) {
		if (realtime >= vid_testendtime) {
			VID_SetMode (vid_realmode, vid_curpal);
			vid_testingmode = 0;
		}
	} else {
		if (vid_mode->int_val != vid_realmode) {
			VID_SetMode (vid_mode->int_val, vid_curpal);
			Cvar_SetValue (vid_mode, vid_modenum);
			// so if mode set fails, we don't keep on
			// trying to set that mode
			vid_realmode = vid_modenum;
		}
	}

	// handle the mouse state when windowed if that's changed
	if (modestate == MS_WINDOWED) {
		if (!_windowed_mouse->int_val) {
			if (windowed_mouse) {
				IN_DeactivateMouse ();
				IN_ShowMouse ();
			}
			windowed_mouse = false;
		} else {
			windowed_mouse = true;
			if (key_dest == key_game && !mouseactive && ActiveApp) {
				IN_ActivateMouse ();
				IN_HideMouse ();
			} else if (mouseactive && key_dest != key_game) {
				IN_DeactivateMouse ();
				IN_ShowMouse ();
			}
		}
	}
}

void
D_BeginDirectRect (int x, int y, byte * pbitmap, int width, int height)
{
	int         i, j, reps, repshift;
	vrect_t     rect;

	if (!vid.initialized)
		return;

	if (vid.aspect > 1.5) {
		reps = 2;
		repshift = 1;
	} else {
		reps = 1;
		repshift = 0;
	}

	if (vid.numpages == 1) {
		VID_LockBuffer ();

		if (!vid.direct)
			Sys_Error ("NULL vid.direct pointer");

		for (i = 0; i < (height << repshift); i += reps) {
			for (j = 0; j < reps; j++) {
				memcpy (&backingbuf[(i + j) * 24],
						vid.direct + x + ((y << repshift) + i +
										  j) * vid.rowbytes, width);
				memcpy (vid.direct + x +
						((y << repshift) + i + j) * vid.rowbytes,
						&pbitmap[(i >> repshift) * width], width);
			}
		}

		VID_UnlockBuffer ();

		rect.x = x;
		rect.y = y;
		rect.width = width;
		rect.height = height << repshift;
		rect.pnext = NULL;

		FlipScreen (&rect);
	} else {
		// unlock if locked
		if (lockcount > 0)
			MGL_endDirectAccess ();

		// set the active page to the displayed page
		MGL_setActivePage (mgldc, vPage);

		// lock the screen
		MGL_beginDirectAccess ();

		// save from and draw to screen
		for (i = 0; i < (height << repshift); i += reps) {
			for (j = 0; j < reps; j++) {
				memcpy (&backingbuf[(i + j) * 24],
						(byte *) mgldc->surface + x +
						((y << repshift) + i + j) * mgldc->mi.bytesPerLine,
						width);
				memcpy ((byte *) mgldc->surface + x +
						((y << repshift) + i + j) * mgldc->mi.bytesPerLine,
						&pbitmap[(i >> repshift) * width], width);
			}
		}

		// unlock the screen
		MGL_endDirectAccess ();

		// restore the original active page
		MGL_setActivePage (mgldc, aPage);

		// relock the screen if it was locked
		if (lockcount > 0)
			MGL_beginDirectAccess ();
	}
}

void
D_EndDirectRect (int x, int y, int width, int height)
{
	int         i, j, reps, repshift;
	vrect_t     rect;

	if (!vid.initialized)
		return;

	if (vid.aspect > 1.5) {
		reps = 2;
		repshift = 1;
	} else {
		reps = 1;
		repshift = 0;
	}

	if (vid.numpages == 1) {
		VID_LockBuffer ();

		if (!vid.direct)
			Sys_Error ("NULL vid.direct pointer");

		for (i = 0; i < (height << repshift); i += reps) {
			for (j = 0; j < reps; j++) {
				memcpy (vid.direct + x +
						((y << repshift) + i + j) * vid.rowbytes,
						&backingbuf[(i + j) * 24], width);
			}
		}

		VID_UnlockBuffer ();

		rect.x = x;
		rect.y = y;
		rect.width = width;
		rect.height = height << repshift;
		rect.pnext = NULL;

		FlipScreen (&rect);
	} else {
		// unlock if locked
		if (lockcount > 0)
			MGL_endDirectAccess ();

		// set the active page to the displayed page
		MGL_setActivePage (mgldc, vPage);

		// lock the screen
		MGL_beginDirectAccess ();

		// restore to the screen
		for (i = 0; i < (height << repshift); i += reps) {
			for (j = 0; j < reps; j++) {
				memcpy ((byte *) mgldc->surface + x +
						((y << repshift) + i + j) * mgldc->mi.bytesPerLine,
						&backingbuf[(i + j) * 24], width);
			}
		}

		// unlock the screen
		MGL_endDirectAccess ();

		// restore the original active page
		MGL_setActivePage (mgldc, aPage);

		// relock the screen if it was locked
		if (lockcount > 0)
			MGL_beginDirectAccess ();
	}
}

//==========================================================================

byte        scantokey[128] = {
//  0       1        2       3       4       5       6       7
//  8       9        A       B       C       D       E       F
	0, 27, '1', '2', '3', '4', '5', '6',
	'7', '8', '9', '0', '-', '=', K_BACKSPACE, 9,	// 0
	'q', 'w', 'e', 'r', 't', 'y', 'u', 'i',
	'o', 'p', '[', ']', 13, K_CTRL, 'a', 's',	// 1
	'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',
	'\'', '`', K_SHIFT, '\\', 'z', 'x', 'c', 'v',	// 2
	'b', 'n', 'm', ',', '.', '/', K_SHIFT, KP_MULTIPLY,
	K_ALT, ' ', K_CAPSLOCK, K_F1, K_F2, K_F3, K_F4, K_F5,	// 3
	K_F6, K_F7, K_F8, K_F9, K_F10, K_PAUSE, K_SCRLCK, KP_HOME,
	KP_UPARROW, KP_PGUP, KP_MINUS, KP_LEFTARROW, KP_5, KP_RIGHTARROW, KP_PLUS,
	KP_END,	// 4
	KP_DOWNARROW, KP_PGDN, KP_INS, KP_DEL, 0, 0, 0, K_F11,
	K_F12, 0, 0, 0, 0, 0, 0, 0,			// 5
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0
};

byte        extscantokey[128] = {
//  0       1        2       3       4       5       6       7
//  8       9        A       B       C       D       E       F
	0, 27, '1', '2', '3', '4', '5', '6',
	'7', '8', '9', '0', '-', '=', K_BACKSPACE, 9,	// 0
	'q', 'w', 'e', 'r', 't', 'y', 'u', 'i',
	'o', 'p', '[', ']', KP_ENTER, K_CTRL, 'a', 's',	// 1
	'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',
	'\'', '`', K_SHIFT, '\\', 'z', 'x', 'c', 'v',	// 2
	'b', 'n', 'm', ',', '.', KP_DIVIDE, K_SHIFT, '*',
	K_ALT, ' ', K_CAPSLOCK, K_F1, K_F2, K_F3, K_F4, K_F5,	// 3
	K_F6, K_F7, K_F8, K_F9, K_F10, KP_NUMLCK, 0, K_HOME,
	K_UPARROW, K_PGUP, '-', K_LEFTARROW, '5', K_RIGHTARROW, '+', K_END,	// 4
	K_DOWNARROW, K_PGDN, K_INS, K_DEL, 0, 0, 0, K_F11,
	K_F12, 0, 0, 0, 0, 0, 0, 0,			// 5
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0
};

/*
	MapKey

	Map from windows to quake keynums
*/
int
MapKey (int key)
{
	int         extended;

	extended = (key >> 24) & 1;

	key = (key >> 16) & 255;
	if (key > 127)
		return 0;

	if (extended)
		return extscantokey[key];
	else
		return scantokey[key];
}

/*
  AppActivate
  
  fActive - True if app is activating
  If the application is activating, then swap the system into SYSPAL_NOSTATIC
  mode so that our palettes will display correctly.
*/
void
AppActivate (BOOL fActive, BOOL minimize)
{
	HDC         hdc;
	int         i, t;
	static BOOL sound_active;

	ActiveApp = fActive;

	// messy, but it seems to work
	if (vid_fulldib_on_focus_mode) {
		Minimized = minimize;

		if (Minimized)
			ActiveApp = false;
	}

	MGL_appActivate (windc, ActiveApp);

	if (vid.initialized) {
		// yield the palette if we're losing the focus
		hdc = GetDC (NULL);

		if (GetDeviceCaps (hdc, RASTERCAPS) & RC_PALETTE) {
			if (ActiveApp) {
				if ((modestate == MS_WINDOWED) || (modestate == MS_FULLDIB)) {
					if (GetSystemPaletteUse (hdc) == SYSPAL_STATIC) {
						// switch to SYSPAL_NOSTATIC and remap the colors
						SetSystemPaletteUse (hdc, SYSPAL_NOSTATIC);
						syscolchg = true;
						pal_is_nostatic = true;
					}
				}
			} else if (pal_is_nostatic) {
				if (GetSystemPaletteUse (hdc) == SYSPAL_NOSTATIC) {
					// switch back to SYSPAL_STATIC and the old mapping
					SetSystemPaletteUse (hdc, SYSPAL_STATIC);
					syscolchg = true;
				}

				pal_is_nostatic = false;
			}
		}

		if (!Minimized)
			VID_SetPalette (vid_curpal);

		scr_fullupdate = 0;

		ReleaseDC (NULL, hdc);
	}
	// enable/disable sound on focus gain/loss
	if (!ActiveApp && sound_active) {
		S_BlockSound ();
		S_ClearBuffer ();
		sound_active = false;
	} else if (ActiveApp && !sound_active) {
		S_UnblockSound ();
		S_ClearBuffer ();
		sound_active = true;
	}
	// minimize/restore fulldib windows/mouse-capture normal windows on demand
	if (!in_mode_set) {
		if (ActiveApp) {
			if (vid_fulldib_on_focus_mode) {
				if (vid.initialized) {
					msg_suppress_1 = true;	// don't want to see normal mode
											// set message
					VID_SetMode (vid_fulldib_on_focus_mode, vid_curpal);
					msg_suppress_1 = false;

					t = in_mode_set;
					in_mode_set = true;
					AppActivate (true, false);
					in_mode_set = t;
				}

				IN_ActivateMouse ();
				IN_HideMouse ();
			}
				else if ((modestate == MS_WINDOWED) && _windowed_mouse->int_val
						 && key_dest == key_game) {
				IN_ActivateMouse ();
				IN_HideMouse ();
			}
		}

		if (!ActiveApp) {
			if (modestate == MS_FULLDIB) {
				if (vid.initialized) {
					force_minimized = true;
					i = vid_fulldib_on_focus_mode;
					msg_suppress_1 = true;	// don't want to see normal mode
											// set message
					VID_SetMode (windowed_default, vid_curpal);
					msg_suppress_1 = false;
					vid_fulldib_on_focus_mode = i;
					force_minimized = false;

					// we never seem to get WM_ACTIVATE inactive from this
					// mode set, so we'll do it manually
					t = in_mode_set;
					in_mode_set = true;
					AppActivate (false, true);
					in_mode_set = t;
				}

				IN_DeactivateMouse ();
				IN_ShowMouse ();
			} else if ((modestate == MS_WINDOWED) && _windowed_mouse->int_val
					   /* &&  mouseactive */ ) {
				IN_DeactivateMouse ();
				IN_ShowMouse ();
			}
		}
	}
}

void
VID_HandlePause (qboolean pause)
{
#if 0
	if ((modestate == MS_WINDOWED) && _windowed_mouse->int_val) {
		if (pause) {
			IN_DeactivateMouse ();
			IN_ShowMouse ();
		} else {
			IN_ActivateMouse ();
			IN_HideMouse ();
		}
	}
#endif
}

/*
  MAIN WINDOW
*/

LONG        CDAudio_MessageHandler (HWND hWnd, UINT uMsg, WPARAM wParam,
									LPARAM lParam);

/* main window procedure */
LONG WINAPI
MainWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	LONG        lRet = 0;
	int         fActive, fMinimized, temp;
	HDC         hdc;
	PAINTSTRUCT ps;
	extern unsigned int uiWheelMessage;

	if (uMsg == uiWheelMessage) {
		uMsg = WM_MOUSEWHEEL;
		wParam <<= 16;
	}

	switch (uMsg) {
		case WM_CREATE:
			break;

		case WM_SYSCOMMAND:
			// Check for maximize being hit
			switch (wParam & ~0x0F) {
			case SC_MAXIMIZE:
				// if minimized, bring up as a window before going
				// fullscreen,
				// so MGL will have the right state to restore
				if (Minimized) {
					force_mode_set = true;
					VID_SetMode (vid_modenum, vid_curpal);
					force_mode_set = false;
				}

				VID_SetMode (vid_fullscreen_mode->int_val, vid_curpal);
				break;

			case SC_SCREENSAVE:
			case SC_MONITORPOWER:
				if (modestate != MS_WINDOWED) {
					// don't call DefWindowProc() because we don't want
					// to start the screen saver fullscreen
					break;
				}

			// fall through windowed and allow the screen saver to start
			default:
				if (!in_mode_set) {
					S_BlockSound ();
					S_ClearBuffer ();
				}

				lRet = DefWindowProc (hWnd, uMsg, wParam, lParam);

				if (!in_mode_set) {
					S_UnblockSound ();
				}
			}
			break;

	case WM_MOVE:
		window_x = (int) LOWORD (lParam);
		window_y = (int) HIWORD (lParam);
		VID_UpdateWindowStatus ();

		if ((modestate == MS_WINDOWED) && !in_mode_set && !Minimized)
			VID_RememberWindowPos ();

		break;

	case WM_SIZE:
		Minimized = false;

		if (!(wParam & SIZE_RESTORED)) {
			if (wParam & SIZE_MINIMIZED)
				Minimized = true;
		}
		break;

	case WM_SYSCHAR:
		// keep Alt-Space from happening
		break;

	case WM_ACTIVATE:
		fActive = LOWORD (wParam);
		fMinimized = (BOOL) HIWORD (wParam);
		AppActivate (!(fActive == WA_INACTIVE), fMinimized);

		// fix leftover Alt from any Alt-Tab or the like that switched us away
		ClearAllStates ();

		if (!in_mode_set) {
			if (windc)
				MGL_activatePalette (windc, true);

			VID_SetPalette (vid_curpal);
		}
		break;

	case WM_PAINT:
		hdc = BeginPaint (hWnd, &ps);

		if (!in_mode_set && host_initialized)
			SCR_UpdateWholeScreen ();

		EndPaint (hWnd, &ps);
		break;

	case WM_KEYDOWN:
	case WM_SYSKEYDOWN:
		if (!in_mode_set)
			Key_Event (MapKey (lParam), -1, true);
		break;

	case WM_KEYUP:
	case WM_SYSKEYUP:
		if (!in_mode_set)
			Key_Event (MapKey (lParam), -1, false);
		break;

	// this is complicated because Win32 seems to pack multiple mouse events
	// into one update sometimes, so always check all states and look for
	// events
	case WM_LBUTTONDOWN:
	case WM_LBUTTONUP:
	case WM_RBUTTONDOWN:
	case WM_RBUTTONUP:
	case WM_MBUTTONDOWN:
	case WM_MBUTTONUP:
	case WM_MOUSEMOVE:
		if (!in_mode_set) {
			temp = 0;

			if (wParam & MK_LBUTTON)
				temp |= 1;

			if (wParam & MK_RBUTTON)
				temp |= 2;

			if (wParam & MK_MBUTTON)
				temp |= 4;

			IN_MouseEvent (temp);
		}
		break;
	// JACK: This is the mouse wheel with the Intellimouse
	// Its delta is either positive or neg, and we generate the proper Event.
	case WM_MOUSEWHEEL:
		if ((short) HIWORD (wParam) > 0) {
			Key_Event (K_MWHEELUP, -1, true);
			Key_Event (K_MWHEELUP, -1, false);
		} else {
				Key_Event (K_MWHEELDOWN, -1, true);
				Key_Event (K_MWHEELDOWN, -1, false);
		}
		break;
	// KJB: Added these new palette functions
	case WM_PALETTECHANGED:
		if ((HWND) wParam == hWnd)
			break;
	/* Fall through to WM_QUERYNEWPALETTE */
	case WM_QUERYNEWPALETTE:
		hdc = GetDC (NULL);

		if (GetDeviceCaps (hdc, RASTERCAPS) & RC_PALETTE)
			vid_palettized = true;
		else
			vid_palettized = false;

		ReleaseDC (NULL, hdc);

		scr_fullupdate = 0;

		if (vid.initialized && !in_mode_set && windc
			&& MGL_activatePalette (windc, false) && !Minimized) {
			VID_SetPalette (vid_curpal);
			InvalidateRect (mainwindow, NULL, false);

			// specifically required if WM_QUERYNEWPALETTE realizes a new 
			// palette
			lRet = TRUE;
		}
		break;

	case WM_DISPLAYCHANGE:
		if (!in_mode_set && (modestate == MS_WINDOWED)
			&& !vid_fulldib_on_focus_mode) {
			force_mode_set = true;
			VID_SetMode (vid_modenum, vid_curpal);
			force_mode_set = false;
		}
		break;

	case WM_CLOSE:
		// this causes Close in the right-click task bar menu not to work, but
		// right now bad things happen if Close is handled in that case
		// (garbage and a crash on Win95)
		if (!in_mode_set) {
			if (MessageBox
				(mainwindow, "Are you sure you want to quit?",
				 "Confirm Exit",
				 MB_YESNO | MB_SETFOREGROUND | MB_ICONQUESTION) == IDYES) {
				Sys_Quit ();
			}
		}
		break;

	case MM_MCINOTIFY:
		lRet = CDAudio_MessageHandler (hWnd, uMsg, wParam, lParam);
		break;

	default:
		/* pass all unhandled messages to DefWindowProc */
		lRet = DefWindowProc (hWnd, uMsg, wParam, lParam);
		break;
	}

	/* return 0 if handled message, 1 if not */
	return lRet;
}

extern void M_Menu_Options_f (void);
extern void M_Print (int cx, int cy, char *str);
extern void M_PrintWhite (int cx, int cy, char *str);
extern void M_DrawCharacter (int cx, int line, int num);
extern void M_DrawTransPic (int x, int y, qpic_t *pic);
extern void M_DrawPic (int x, int y, qpic_t *pic);

static int  vid_line, vid_wmodes;

typedef struct {
	int         modenum;
	char       *desc;
	int         iscur;
	int         ismode13;
	int         width;
} modedesc_t;

#define MAX_COLUMN_SIZE		5
#define MODE_AREA_HEIGHT	(MAX_COLUMN_SIZE + 6)
#define MAX_MODEDESCS		(MAX_COLUMN_SIZE*3)

static modedesc_t modedescs[MAX_MODEDESCS];

void
VID_MenuDraw (void)
{
	qpic_t     *p;
	char       *ptr;
	int         lnummodes, i, j, k, column, row, dup, dupmode = 0;
	char        temp[100];
	vmode_t    *pv;
	modedesc_t  tmodedesc;

	p = Draw_CachePic ("gfx/vidmodes.lmp", true);
	M_DrawPic ((320 - p->width) / 2, 4, p);

	for (i = 0; i < 3; i++) {
		ptr = VID_GetModeDescriptionMemCheck (i);
		modedescs[i].modenum = modelist[i].modenum;
		modedescs[i].desc = ptr;
		modedescs[i].ismode13 = 0;
		modedescs[i].iscur = 0;

		if (vid_modenum == i)
			modedescs[i].iscur = 1;
	}

	vid_wmodes = 3;
	lnummodes = VID_NumModes ();

	for (i = 3; i < lnummodes; i++) {
		ptr = VID_GetModeDescriptionMemCheck (i);
		pv = VID_GetModePtr (i);

		// we only have room for 15 fullscreen modes, so don't allow
		// 360-wide modes, because if there are 5 320-wide modes and
		// 5 360-wide modes, we'll run out of space
		if (ptr && ((pv->width != 360) || COM_CheckParm ("-allow360"))) {
			dup = 0;

			for (j = 3; j < vid_wmodes; j++) {
				if (!strcmp (modedescs[j].desc, ptr)) {
					dup = 1;
					dupmode = j;
					break;
				}
			}

			if (dup || (vid_wmodes < MAX_MODEDESCS)) {
				if (!dup || !modedescs[dupmode].ismode13
					|| COM_CheckParm ("-noforcevga")) {
					if (dup) {
						k = dupmode;
					} else {
						k = vid_wmodes;
					}

					modedescs[k].modenum = i;
					modedescs[k].desc = ptr;
					modedescs[k].ismode13 = pv->mode13;
					modedescs[k].iscur = 0;
					modedescs[k].width = pv->width;

					if (i == vid_modenum)
						modedescs[k].iscur = 1;

					if (!dup)
						vid_wmodes++;
				}
			}
		}
	}

	// sort the modes on width (to handle picking up oddball dibonly modes
	// after all the others)
	for (i = 3; i < (vid_wmodes - 1); i++) {
		for (j = (i + 1); j < vid_wmodes; j++) {
			if (modedescs[i].width > modedescs[j].width) {
				tmodedesc = modedescs[i];
				modedescs[i] = modedescs[j];
				modedescs[j] = tmodedesc;
			}
		}
	}

	M_Print (13 * 8, 36, "Windowed Modes");

	column = 16;
	row = 36 + 2 * 8;

	for (i = 0; i < 3; i++) {
		if (modedescs[i].iscur)
			M_PrintWhite (column, row, modedescs[i].desc);
		else
			M_Print (column, row, modedescs[i].desc);

		column += 13 * 8;
	}

	if (vid_wmodes > 3) {
		M_Print (12 * 8, 36 + 4 * 8, "Fullscreen Modes");

		column = 16;
		row = 36 + 6 * 8;

		for (i = 3; i < vid_wmodes; i++) {
			if (modedescs[i].iscur)
				M_PrintWhite (column, row, modedescs[i].desc);
			else
				M_Print (column, row, modedescs[i].desc);

			column += 13 * 8;

			if (((i - 3) % VID_ROW_SIZE) == (VID_ROW_SIZE - 1)) {
				column = 16;
				row += 8;
			}
		}
	}
	// line cursor
	if (vid_testingmode) {
		snprintf (temp, sizeof (temp), "TESTING %s", modedescs[vid_line].desc);
		M_Print (13 * 8, 36 + MODE_AREA_HEIGHT * 8 + 8 * 4, temp);
		M_Print (9 * 8, 36 + MODE_AREA_HEIGHT * 8 + 8 * 6,
				 "Please wait 5 seconds...");
	} else {
		M_Print (9 * 8, 36 + MODE_AREA_HEIGHT * 8 + 8,
				 "Press Enter to set mode");
		M_Print (6 * 8, 36 + MODE_AREA_HEIGHT * 8 + 8 * 3,
				 "T to test mode for 5 seconds");
		ptr = VID_GetModeDescription2 (vid_modenum);

		if (ptr) {
			snprintf (temp, sizeof (temp), "D to set default: %s", ptr);
			M_Print (2 * 8, 36 + MODE_AREA_HEIGHT * 8 + 8 * 5, temp);
		}

		ptr = VID_GetModeDescription2 (_vid_default_mode_win->int_val);

		if (ptr) {
			snprintf (temp, sizeof (temp), "Current default: %s", ptr);
			M_Print (3 * 8, 36 + MODE_AREA_HEIGHT * 8 + 8 * 6, temp);
		}

		M_Print (15 * 8, 36 + MODE_AREA_HEIGHT * 8 + 8 * 8, "Esc to exit");

		row = 36 + 2 * 8 + (vid_line / VID_ROW_SIZE) * 8;
		column = 8 + (vid_line % VID_ROW_SIZE) * 13 * 8;

		if (vid_line >= 3)
			row += 3 * 8;

		M_DrawCharacter (column, row, 12 + ((int) (realtime * 4) & 1));
	}
}

void
VID_MenuKey (int key)
{
	if (vid_testingmode)
		return;

	switch (key) {
		case K_ESCAPE:
			S_LocalSound ("misc/menu1.wav");
			M_Menu_Options_f ();
			break;

		case K_LEFTARROW:

			S_LocalSound ("misc/menu1.wav");
			vid_line = ((vid_line / VID_ROW_SIZE) * VID_ROW_SIZE) +
				((vid_line + 2) % VID_ROW_SIZE);

			if (vid_line >= vid_wmodes)
				vid_line = vid_wmodes - 1;
			break;

		case K_RIGHTARROW:
			S_LocalSound ("misc/menu1.wav");
			vid_line = ((vid_line / VID_ROW_SIZE) * VID_ROW_SIZE) +
				((vid_line + 4) % VID_ROW_SIZE);

			if (vid_line >= vid_wmodes)
				vid_line = (vid_line / VID_ROW_SIZE) * VID_ROW_SIZE;
			break;

		case K_UPARROW:
			S_LocalSound ("misc/menu1.wav");
			vid_line -= VID_ROW_SIZE;

			if (vid_line < 0) {
				vid_line += ((vid_wmodes + (VID_ROW_SIZE - 1)) /
							 VID_ROW_SIZE) * VID_ROW_SIZE;

				while (vid_line >= vid_wmodes)
					vid_line -= VID_ROW_SIZE;
			}
			break;

		case K_DOWNARROW:
			S_LocalSound ("misc/menu1.wav");
			vid_line += VID_ROW_SIZE;

			if (vid_line >= vid_wmodes) {
				vid_line -= ((vid_wmodes + (VID_ROW_SIZE - 1)) /
							 VID_ROW_SIZE) * VID_ROW_SIZE;

				while (vid_line < 0)
					vid_line += VID_ROW_SIZE;
			}
			break;

		case K_ENTER:
			S_LocalSound ("misc/menu1.wav");
			VID_SetMode (modedescs[vid_line].modenum, vid_curpal);
			break;

		case 'T':
		case 't':
			S_LocalSound ("misc/menu1.wav");
			// have to set this before setting the mode because WM_PAINT
			// happens during the mode set and does a VID_Update, which
			// checks vid_testingmode
			vid_testingmode = 1;
			vid_testendtime = realtime + 5.0;

			if (!VID_SetMode (modedescs[vid_line].modenum, vid_curpal)) {
				vid_testingmode = 0;
			}
			break;

		case 'D':
		case 'd':
			S_LocalSound ("misc/menu1.wav");
			firstupdate = 0;
			Cvar_SetValue (_vid_default_mode_win, vid_modenum);
			break;

		default:
			break;
	}
}

void
VID_SetCaption (const char *text)
{
	if (text && *text) {
		char       *temp = strdup (text);

		SetWindowText (mainwindow, (LPSTR) va ("%s %s: %s", PROGRAM, VERSION,
											   text));
		free (temp);
	} else {
		SetWindowText (mainwindow, (LPSTR) va ("%s %s", PROGRAM, VERSION));
	}
}