/*
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 the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// vid_win.c -- Win32 video driver

// FIXME - broken mgraph header?
//       - needs windows.h but doesn't include it?
#include <windows.h>
#include <mgraph.h>

#include "cdaudio.h"
#include "cmd.h"
#include "common.h"
#include "console.h"
#include "d_local.h"
#include "draw.h"
#include "input.h"
#include "keys.h"
#include "menu.h"
#include "quakedef.h"
#include "screen.h"
#include "sound.h"
#include "sys.h"
#include "vid.h"
#include "view.h"
#include "wad.h"
#include "winquake.h"

#ifdef NQ_HACK
#include "host.h"
#endif
#ifdef QW_HACK
#include "client.h"
#endif

#include "resource.h"

#define MAX_MODE_LIST	80
#define VID_ROW_SIZE	3

static void VID_MenuDraw(void);
static void VID_MenuKey(int key);
static int VID_SetMode(int modenum, unsigned char *palette);

static qboolean dibonly;
static qboolean Minimized;

qboolean
window_visible(void)
{
    return !Minimized;
}

HWND mainwindow;
qboolean DDActive;

static int DIBWidth, DIBHeight;
static RECT WindowRect;
static DWORD WindowStyle, ExWindowStyle;

int window_center_x, window_center_y;

static int window_x, window_y;
static int window_width, window_height;

RECT window_rect;

static DEVMODE gdevmode;
static qboolean startwindowed = false, windowed_mode_set = false;
static int firstupdate = 1;
static qboolean vid_initialized = false, 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;
static qboolean palette_changed, pal_is_nostatic;
static qboolean syscolchg, vid_mode_set, hide_window;

static HICON hIcon;

viddef_t vid;			// global video state

#define VID_MODE_NONE			(-1)
#define VID_MODE_WINDOWED		0
#define NUM_WINDOWED_MODES		5

/* so this can be "stringified" -> (VID_MODE_WINDOWED+NUM_WINDOWED_MODES) */
#define VID_MODE_FULLSCREEN_DEFAULT	5

static cvar_t vid_mode = {
    .name = "vid_mode",
    .string = stringify(VID_MODE_WINDOWED),
    .archive = false
};
static cvar_t _vid_default_mode = {
    .name = "_vid_default_mode",
    .string = stringify(VID_MODE_WINDOWED),
    .archive = true
};
static cvar_t _vid_default_mode_win = {
    .name = "_vid_default_mode_win",
    .string = stringify(VID_MODE_FULLSCREEN_DEFAULT),
    .archive = true
};
static cvar_t vid_fullscreen_mode = {
    .name = "vid_fullscreen_mode",
    .string = stringify(VID_MODE_FULLSCREEN_DEFAULT),
    .archive = true
};
static cvar_t vid_windowed_mode = {
    .name = "vid_windowed_mode",
    .string = stringify(VID_MODE_WINDOWED),
    .archive = true
};

static cvar_t vid_wait = { "vid_wait", "0" };
static cvar_t vid_nopageflip = { "vid_nopageflip", "0", true };
static cvar_t _vid_wait_override = { "_vid_wait_override", "0", true };
static cvar_t vid_stretch_by_2 = { "vid_stretch_by_2", "0", true };
static cvar_t block_switch = { "block_switch", "0", true };
static cvar_t vid_window_x = { "vid_window_x", "0", true };
static cvar_t vid_window_y = { "vid_window_y", "0", true };

cvar_t _windowed_mouse = { "_windowed_mouse", "0", true };

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

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

static int windowed_default;
static int vid_default = VID_MODE_WINDOWED;
static int vid_modenum = VID_MODE_NONE;
static int vid_testingmode, vid_realmode;
static double vid_testendtime;

modestate_t modestate = MS_UNINIT;

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

static unsigned char vid_curpal[256 * 3];

unsigned short d_8to16table[256];
unsigned d_8to24table[256];

static int driver = grDETECT, mode;
static bool useWinDirect = false, useDirectDraw = true;
static 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;

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

static vmode_t badmode;

static byte backingbuf[48 * 24];

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

/*
================
VID_RememberWindowPos
================
*/
static 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", (float)rect.left);
	    Cvar_SetValue("vid_window_y", (float)rect.top);
	}
    }
}


/*
================
VID_CheckWindowXY
================
*/
static void
VID_CheckWindowXY(void)
{
    if (((int)vid_window_x.value > (GetSystemMetrics(SM_CXSCREEN) - 160)) ||
	((int)vid_window_y.value > (GetSystemMetrics(SM_CYSCREEN) - 120)) ||
	((int)vid_window_x.value < 0) || ((int)vid_window_y.value < 0)) {
	Cvar_SetValue("vid_window_x", 0.0);
	Cvar_SetValue("vid_window_y", 0.0);
    }
}


/*
================
VID_UpdateWindowStatus
================
*/
static 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();
}


/*
================
ClearAllStates
================
*/
static void
ClearAllStates(void)
{
    int i;

    // send an up event for each key, to make sure the server clears them all
    for (i = 0; i < 256; i++)
	Key_Event(i, false);

    Key_ClearStates();
    IN_ClearStates();
}


/*
====================
VID_CheckAdequateMem
====================
*/
static qboolean
VID_CheckAdequateMem(int width, int height)
{
    int tbuffersize;

    tbuffersize = width * height * sizeof(*d_pzbuffer);
    tbuffersize += D_SurfaceCacheForRes(width, height);

    /*
     * see if there's enough memory, allowing for the normal mode 0x13 pixel,
     * z, and surface buffers
     */
    if ((host_parms.memsize - tbuffersize + SURFCACHE_SIZE_AT_320X200 +
	 0x10000 * 3) < minimum_memory)
	return false;

    return true;
}


/*
================
VID_AllocBuffers
================
*/
static qboolean
VID_AllocBuffers(int width, int height)
{
    int tsize, tbuffersize;

    tsize = D_SurfaceCacheForRes(width, height);
    tbuffersize = width * height * sizeof(*d_pzbuffer);
    tbuffersize += tsize;

    /*
     * see if there's enough memory, allowing for the normal mode 0x13 pixel,
     * z, and surface buffers
     */
    if ((host_parms.memsize - tbuffersize + SURFCACHE_SIZE_AT_320X200 +
	 0x10000 * 3) < minimum_memory) {
	Con_SafePrintf("Not enough memory for video mode\n");
	return false;
    }

    vid_surfcachesize = tsize;

    if (d_pzbuffer) {
	D_FlushCaches();
	Hunk_FreeToHighMark(VID_highhunkmark);
	d_pzbuffer = NULL;
    }

    VID_highhunkmark = Hunk_HighMark();
    d_pzbuffer = Hunk_HighAllocName(tbuffersize, "video");
    vid_surfcache = (byte *)d_pzbuffer + width * height * sizeof(*d_pzbuffer);

    return true;
}


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


static m_int
VID_Suspend(MGLDC *dc, m_int flags)
{
    if (flags & MGL_DEACTIVATE) {
	// FIXME: this doesn't currently work on NT
	if (block_switch.value && !WinNT) {
	    return MGL_NO_DEACTIVATE;
	}

	S_BlockSound();
	S_ClearBuffer();
	IN_RestoreOriginalMouseState();
	CDAudio_Pause();

	in_mode_set = true;	/* keep WM_PAINT from trying to redraw */
	block_drawing = true;	/* don't try to draw while switched away */

	return MGL_NO_SUSPEND_APP;

    } else if (flags & MGL_REACTIVATE) {
	IN_SetQuakeMouseState();

	/*
	 * fix the 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;
    }

    Sys_Error("%s: called with invalid flags (%x)", __func__, (unsigned)flags);
}


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

    /* Register display drivers */
    if (useWinDirect) {
	MGL_registerDriver(MGL_LINEAR8NAME, LINEAR8_driver);
	if (!COM_CheckParm("-novbeaf"))
	    MGL_registerDriver(MGL_ACCEL8NAME, ACCEL8_driver);
    }
    if (useDirectDraw)
	MGL_registerDriver(MGL_DDRAW8NAME, DDRAW8_driver);
}


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


static void
VID_InitMGLFull(HINSTANCE hInstance)
{
    int i;
    int xRes, yRes, bits, lowres, curmode, temp;
    int lowstretchedres, stretchedmode, 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 (!WinNT &&
	(COM_CheckParm("-usewindirect") || COM_CheckParm("-usevesa"))) {
	useWinDirect = true;
    }

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

    // FIXME - stretched mode may be uninitialised, understand -> fix
    stretchedmode = 0;

    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
	// leave 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 = VID_MODE_FULLSCREEN_DEFAULT;
		    }
		} else {
		    curmode = nummodes++;
		}

		modelist[curmode].type = MS_FULLSCREEN;
		modelist[curmode].width = xRes;
		modelist[curmode].height = yRes;
		sprintf(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[VID_MODE_FULLSCREEN_DEFAULT] = modelist[stretchedmode];
	    modelist[VID_MODE_FULLSCREEN_DEFAULT].stretched = 1;
	    modelist[VID_MODE_FULLSCREEN_DEFAULT].width >>= 1;
	    modelist[VID_MODE_FULLSCREEN_DEFAULT].height >>= 1;
	    sprintf(modelist[VID_MODE_FULLSCREEN_DEFAULT].modedesc, "%dx%d",
		    modelist[VID_MODE_FULLSCREEN_DEFAULT].width,
		    modelist[VID_MODE_FULLSCREEN_DEFAULT].height);
	}

	vid_default = VID_MODE_FULLSCREEN_DEFAULT;

	temp = m[0];

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


/*
 * Function:     createDisplayDC
 * Returns:      Pointer to the MGL device context to use for the application
 *
 * Description:  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.
 */
static 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;
}


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

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

    /* 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 = "TyrQuake";

    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 = VID_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 = VID_MODE_WINDOWED + 1;
    modelist[1].stretched = 0;
    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 = VID_MODE_WINDOWED + 2;
    modelist[2].stretched = 0;
    modelist[2].dib = 1;
    modelist[2].fullscreen = 0;
    modelist[2].halfscreen = 0;
    modelist[2].bpp = 8;

    modelist[3].type = MS_WINDOWED;
    modelist[3].width = 1024;
    modelist[3].height = 768;
    strcpy(modelist[3].modedesc, "1024x768");
    modelist[3].mode13 = 0;
    modelist[3].modenum = VID_MODE_WINDOWED + 3;
    modelist[3].stretched = 0;
    modelist[3].dib = 1;
    modelist[3].fullscreen = 0;
    modelist[3].halfscreen = 0;
    modelist[3].bpp = 8;

    modelist[4].type = MS_WINDOWED;
    modelist[4].width = 1280;
    modelist[4].height = 960;
    strcpy(modelist[4].modedesc, "1280x960");
    modelist[4].mode13 = 0;
    modelist[4].modenum = VID_MODE_WINDOWED + 4;
    modelist[4].stretched = 0;
    modelist[4].dib = 1;
    modelist[4].fullscreen = 0;
    modelist[4].halfscreen = 0;
    modelist[4].bpp = 8;

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

    /* FIXME - what is this test for? */
    if ((GetDeviceCaps(hdc, HORZRES) > 640)
	&& !COM_CheckParm("-noautostretch")) {
	vid_default = VID_MODE_WINDOWED + 1;
    } else {
	vid_default = VID_MODE_WINDOWED;
    }

    windowed_default = vid_default;
    ReleaseDC(NULL, hdc);
    nummodes = NUM_WINDOWED_MODES;
    DDActive = 0;
}


/*
=================
VID_InitFullDIB
=================
*/
static void
VID_InitFullDIB(HINSTANCE hInstance)
{
    DEVMODE devmode;
    int i, j, modenum, existingmode, originalnummodes, lowestres;
    int numlowresmodes, bpp, done;
    int cstretch, istretch, mstretch;
    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;
		sprintf(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;
			sprintf(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_SafePrintf("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;
		    sprintf(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;
			    sprintf(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;
		sprintf(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) {
	mstretch = 0;	// FIXME - Uninitialized
	cstretch = 0;	// FIXME - Uninitialized
	for (i = originalnummodes; 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;
	    sprintf(modelist[istretch].modedesc, "%dx%d",
		    modelist[istretch].width, modelist[istretch].height);
	}
    }

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


/*
=================
VID_NumModes
=================
*/
static int
VID_NumModes(void)
{
    return nummodes;
}


/*
=================
VID_GetModePtr
=================
*/
static vmode_t *
VID_GetModePtr(int modenum)
{
    if ((modenum >= 0) && (modenum < nummodes))
	return &modelist[modenum];
    else
	return &badmode;
}


/*
======================
VID_CheckModedescFixup
======================
*/
static void
VID_CheckModedescFixup(int mode)
{
#if 0
    int x, y, stretch;

    /*
     * FIXME - assuming JC had a purpose for this?
     * Ok, so this was only done for 800x600 originally.
     * default vid_config_x == 800, vid_config_y == 600
     * These two cvars do nothing else, so... wtf?
     */
    if (mode == VID_MODE_SETTABLE_WINDOW /* VID_MODE_WINDOWED + 2 */) {
	modelist[mode].stretched = ((int)vid_stretch_by_2.value) ? 1 : 0;
	stretch = modelist[mode].stretched;

	/* Make sure vid_config_{x,y} are big enough to hold the lowest vid
	   mode with current stretch settings. wtf? */
	if (vid_config_x.value < (320 << stretch))
	    vid_config_x.value = 320 << stretch;
	if (vid_config_y.value < (200 << stretch))
	    vid_config_y.value = 200 << stretch;

	x = (int)vid_config_x.value;
	y = (int)vid_config_y.value;

	sprintf(modelist[mode].modedesc, "%dx%d", x, y);

	modelist[mode].width = x;
	modelist[mode].height = y;
    }
#endif
}


/*
=================
VID_GetModeDescriptionMemCheck
=================
*/
static 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;
    }
}


/*
=================
VID_GetModeDescription
=================
*/
static 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"
=================
*/
static 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) {
	sprintf(pinfo, "%s fullscreen", pv->modedesc);
    } else if (modelist[mode].type == MS_FULLDIB) {
	sprintf(pinfo, "%s fullscreen", pv->modedesc);
    } else {
	sprintf(pinfo, "%s windowed", pv->modedesc);
    }

    return pinfo;
}


// KJB: Added this to return the mode driver name in description for console
static 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) {
	sprintf(pinfo, "%s fullscreen %s", pv->modedesc,
		MGL_modeDriverName(pv->modenum));
    } else if (modelist[mode].type == MS_FULLDIB) {
	sprintf(pinfo, "%s fullscreen DIB", pv->modedesc);
    } else {
	sprintf(pinfo, "%s windowed", pv->modedesc);
    }

    return pinfo;
}


static 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;
    }
}


static 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;
    }
}



static 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;
    }
}


static 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 = true;
    }

    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,
				    "TyrQuake",
				    "TyrQuake",
				    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, (int)vid_window_x.value,
		 (int)vid_window_y.value, 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)ICON_BIG, (LPARAM)hIcon);
    SendMessage(mainwindow, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon);

    return true;
}


static 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 ||
				 (int)vid_nopageflip.value)) == 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)ICON_BIG, (LPARAM)hIcon);
    SendMessage(mainwindow, WM_SETICON, (WPARAM)ICON_SMALL, (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;
}


static 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;
}


static 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 = VID_MODE_NONE;

	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(VID_MODE_WINDOWED, vid_curpal);
    IN_DeactivateMouse();
}


static 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 == VID_MODE_NONE) {
	    if (modenum == vid_default) {
		modenum = windowed_default;
	    } else {
		modenum = vid_default;
	    }

	    Cvar_SetValue("vid_mode", (float)modenum);
	} else {
	    Cvar_SetValue("vid_mode", (float)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 == VID_MODE_NONE)
	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.value && 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", (float)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();

    if (!msg_suppress_1)
	Con_SafePrintf("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;

    // commented out because there are no traces of an lcd_x declaration
    //if (lcd_x.value)
	//screenwidth <<= 1;
}


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 (window_visible()) {
	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_ShiftPalette(unsigned char *palette)
{
    VID_SetPalette(palette);
}


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


/*
=================
VID_NumModes_f
=================
*/
static 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);
}


/*
=================
VID_DescribeMode_f
=================
*/
static void
VID_DescribeMode_f(void)
{
    int modenum;

    modenum = Q_atoi(Cmd_Argv(1));
    Con_Printf("%s\n", VID_GetExtModeDescription(modenum));
}


/*
=================
VID_DescribeModes_f
=================
*/
static 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");
    }
}


/*
=================
VID_TestMode_f
=================
*/
static void
VID_TestMode_f(void)
{
    int modenum;
    double testduration;

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

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


/*
=================
VID_Windowed_f
=================
*/
static void
VID_Windowed_f(void)
{
    VID_SetMode((int)vid_windowed_mode.value, vid_curpal);
}


/*
=================
VID_Fullscreen_f
=================
*/
static void
VID_Fullscreen_f(void)
{
    VID_SetMode((int)vid_fullscreen_mode.value, vid_curpal);
}


/*
=================
VID_Minimize_f
=================
*/
static 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);
}



/*
=================
VID_ForceMode_f
=================
*/
static void
VID_ForceMode_f(void)
{
    int modenum;

    if (!vid_testingmode) {
	modenum = Q_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, bestmatchmetric, t, dr, dg, db;
    int basenummodes;
    byte *ptmp;

    Cvar_RegisterVariable(&vid_mode);
    Cvar_RegisterVariable(&vid_wait);
    Cvar_RegisterVariable(&vid_nopageflip);
    Cvar_RegisterVariable(&_vid_wait_override);
    Cvar_RegisterVariable(&_vid_default_mode);
    Cvar_RegisterVariable(&_vid_default_mode_win);
    Cvar_RegisterVariable(&vid_stretch_by_2);
    Cvar_RegisterVariable(&_windowed_mouse);
    Cvar_RegisterVariable(&vid_fullscreen_mode);
    Cvar_RegisterVariable(&vid_windowed_mode);
    Cvar_RegisterVariable(&block_switch);
    Cvar_RegisterVariable(&vid_window_x);
    Cvar_RegisterVariable(&vid_window_y);

    Cmd_AddCommand("vid_testmode", VID_TestMode_f);
    Cmd_AddCommand("vid_nummodes", VID_NumModes_f);
    Cmd_AddCommand("vid_describecurrentmode", VID_DescribeCurrentMode_f);
    Cmd_AddCommand("vid_describemode", VID_DescribeMode_f);
    Cmd_AddCommand("vid_describemodes", VID_DescribeModes_f);
    Cmd_AddCommand("vid_forcemode", VID_ForceMode_f);
    Cmd_AddCommand("vid_windowed", VID_Windowed_f);
    Cmd_AddCommand("vid_fullscreen", VID_Fullscreen_f);
    Cmd_AddCommand("vid_minimize", VID_Minimize_f);

    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.colormap = host_colormap;
    vid.fullbright = 256 - LittleLong(*((int *)vid.colormap + 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;
    bestmatch = 0;		// FIXME - uninitialized -> guessing 0...

    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.colormap; i < (1 << (VID_CBITS + 8)); i++, ptmp++) {
	if (*ptmp == 0)
	    *ptmp = bestmatch;
    }

    if (COM_CheckParm("-startwindowed")) {
	startwindowed = 1;
	vid_default = windowed_default;
    }

    if (hwnd_dialog)
	DestroyWindow(hwnd_dialog);

    /* keep the window minimized until we're ready for the first mode set */
    hide_window = false;
    if (!VID_SetMode(VID_MODE_WINDOWED, palette)) {
	force_mode_set = true;
	VID_SetMode(vid_default, palette);
	force_mode_set = false;
    }

    vid_initialized = true;
    vid_realmode = vid_modenum;
    VID_SetPalette(palette);
    vid_menudrawfn = VID_MenuDraw;
    vid_menukeyfn = VID_MenuKey;
    strcpy(badmode.modedesc, "Bad mode");
}


void
VID_Shutdown(void)
{
    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 (hwnd_dialog)
	    DestroyWindow(hwnd_dialog);
	if (mainwindow)
	    DestroyWindow(mainwindow);

	MGL_exit();

	vid_testingmode = 0;
	vid_initialized = 0;
    }
}


/*
================
FlipScreen
================
*/
static 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 && host_initialized) {
	if (modestate == MS_WINDOWED) {
	    GetWindowRect(mainwindow, &trect);

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

		VID_CheckWindowXY();
		SetWindowPos(mainwindow, NULL, (int)vid_window_x.value,
			     (int)vid_window_y.value, 0, 0,
			     SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW |
			     SWP_DRAWFRAME);
	    }
	}

	if (!startwindowed ||
	    _vid_default_mode_win.value < VID_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.value < 0) ||
		(_vid_default_mode_win.value >= nummodes)) {
		Cvar_SetValue("_vid_default_mode_win", windowed_default);
	    }

	    Cvar_SetValue("vid_mode", _vid_default_mode_win.value);
	}
    }
    // 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 ((int)vid_mode.value != vid_realmode) {
	    VID_SetMode((int)vid_mode.value, vid_curpal);
	    Cvar_SetValue("vid_mode", (float)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.value) {
	    if (mouseactive) {
		IN_DeactivateMouse();
		IN_ShowMouse();
	    }
	} else if (mouseactive) {
	    if (key_dest != key_game) {
		IN_DeactivateMouse();
		IN_ShowMouse();
	    }
	} else if (key_dest == key_game && ActiveApp) {
	    IN_ActivateMouse();
	    IN_HideMouse();
	}
    }
}


/*
================
D_BeginDirectRect
================
*/
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();
    }
}


/*
================
D_EndDirectRect
================
*/
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();
    }
}


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

static 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, '*',
    K_ALT, ' ', 0, K_F1, K_F2, K_F3, K_F4, K_F5,	// 3
    K_F6, K_F7, K_F8, K_F9, K_F10, K_PAUSE, 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,	// 6
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0	// 7
};

/*
=======
MapKey

Map from windows to quake keynums
=======
*/
static int
MapKey(int key)
{
    key = (key >> 16) & 255;
    if (key > 127)
	return 0;

    return scantokey[key];
}

/*
 * Function:     AppActivate
 * Parameters:   fActive - True if app is activating
 *
 * Description:  If the application is activating, then swap the system
 *               into SYSPAL_NOSTATIC mode so that our palettes will display
 *               correctly.
 */
static 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) {
		    /* don't want to see normal mode set message */
		    msg_suppress_1 = true;
		    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.value
		       && 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;

		    /* don't want to see normal mode set message */
		    msg_suppress_1 = true;
		    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.value) {
		IN_DeactivateMouse();
		IN_ShowMouse();
	    }
	}
    }
}


/*
================
VID_HandlePause
================
*/
void
VID_HandlePause(qboolean pause)
{
#if 0
    if ((modestate == MS_WINDOWED) && _windowed_mouse.value) {
	if (pause) {
	    IN_DeactivateMouse();
	    IN_ShowMouse();
	} else {
	    IN_ActivateMouse();
	    IN_HideMouse();
	}
    }
#endif
}


/*
===================================================================
MAIN WINDOW
===================================================================
*/

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

    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((int)vid_fullscreen_mode.value, 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) && (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 the 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), true);
	break;

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

    /*
     * this is complicated because Win32 seems to pack multiple mouse
     * events into one update sometimes, so we 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, true);
	    Key_Event(K_MWHEELUP, false);
	} else {
	    Key_Event(K_MWHEELDOWN, true);
	    Key_Event(K_MWHEELDOWN, 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 = CDDrv_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;
}

static int vid_line;
static int 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];

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

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

    for (i = 0; i < NUM_WINDOWED_MODES; 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 = NUM_WINDOWED_MODES;
    lnummodes = VID_NumModes();

    dupmode = 0;	// FIXME - uninitialized -> guesssing 0

    for (i = VID_MODE_FULLSCREEN_DEFAULT; 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 = VID_MODE_FULLSCREEN_DEFAULT; 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 = VID_MODE_FULLSCREEN_DEFAULT; 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 < NUM_WINDOWED_MODES; 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 + 1) % VID_ROW_SIZE)) {
	    column = 16;
	    row += 8;
	}
    }
    /* go to next row if previous row not filled */
    if (NUM_WINDOWED_MODES % VID_ROW_SIZE)
	row += 8;

    if (vid_wmodes > NUM_WINDOWED_MODES) {
	M_Print(12 * 8, row + 8, "Fullscreen Modes");

	column = 16;
	row += 3 * 8;

	for (i = VID_MODE_FULLSCREEN_DEFAULT; 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 - NUM_WINDOWED_MODES + 1) % VID_ROW_SIZE)) {
		column = 16;
		row += 8;
	    }
	}
    }

    /* line cursor */
    if (vid_testingmode) {
	sprintf(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) {
	    sprintf(temp, "D to set default: %s", ptr);
	    M_Print(2 * 8, 36 + MODE_AREA_HEIGHT * 8 + 8 * 5, temp);
	}

	ptr = VID_GetModeDescription2((int)_vid_default_mode_win.value);
	if (ptr) {
	    sprintf(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");

	if (vid_line < NUM_WINDOWED_MODES) {
	    row = 36 + 2 * 8 + (vid_line / VID_ROW_SIZE) * 8;
	    column = 8 + (vid_line % VID_ROW_SIZE) * 13 * 8;
	} else {
	    row = 36 + (5 + (NUM_WINDOWED_MODES + 2) / VID_ROW_SIZE) * 8;
	    row += ((vid_line - NUM_WINDOWED_MODES) / VID_ROW_SIZE) * 8;
	    column = 8 + ((vid_line - NUM_WINDOWED_MODES) % VID_ROW_SIZE) *
		13 * 8;
	}
	M_DrawCharacter(column, row, 12 + ((int)(realtime * 4) & 1));
    }
}


/*
================
VID_MenuKey
================
*/
static 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");
	if (vid_line < NUM_WINDOWED_MODES) {
	    if (!vid_line) {
		vid_line = VID_ROW_SIZE - 1;
	    } else if (vid_line % VID_ROW_SIZE) {
		vid_line -= 1;
	    } else {
		vid_line += VID_ROW_SIZE - 1;
		if (vid_line >= NUM_WINDOWED_MODES)
		    vid_line = NUM_WINDOWED_MODES - 1;
	    }
	} else if ((vid_line - NUM_WINDOWED_MODES) % VID_ROW_SIZE) {
	    vid_line -= 1;
	} else {
	    vid_line += VID_ROW_SIZE - 1;
	    if (vid_line >= vid_wmodes)
		vid_line = vid_wmodes - 1;
	}
	break;

    case K_RIGHTARROW:
	S_LocalSound("misc/menu1.wav");
	if (vid_line < NUM_WINDOWED_MODES) {
	    if ((vid_line + 1) % VID_ROW_SIZE) {
		vid_line += 1;
		if (vid_line >= NUM_WINDOWED_MODES)
		    vid_line = ((NUM_WINDOWED_MODES - 1) / VID_ROW_SIZE) *
			VID_ROW_SIZE;
	    } else {
		vid_line -= VID_ROW_SIZE - 1;
	    }
	} else if ((vid_line - NUM_WINDOWED_MODES + 1) % VID_ROW_SIZE) {
	    vid_line += 1;
	    if (vid_line >= vid_wmodes)
		vid_line = ((vid_line - NUM_WINDOWED_MODES) / VID_ROW_SIZE) *
		    VID_ROW_SIZE + NUM_WINDOWED_MODES;
	} else {
	    vid_line -= VID_ROW_SIZE - 1;
	}
	break;

    case K_UPARROW:
	S_LocalSound("misc/menu1.wav");
	if (vid_line < NUM_WINDOWED_MODES + VID_ROW_SIZE &&
	    vid_line >= NUM_WINDOWED_MODES) {
	    /* Going from fullscreen section to windowed section */
	    vid_line -= NUM_WINDOWED_MODES % VID_ROW_SIZE;
	    while (vid_line >= NUM_WINDOWED_MODES)
		vid_line -= VID_ROW_SIZE;
	} else if (vid_line < VID_ROW_SIZE) {
	    /* From top to bottom */
	    vid_line += (vid_wmodes / VID_ROW_SIZE + 1) * VID_ROW_SIZE;
	    vid_line += NUM_WINDOWED_MODES % VID_ROW_SIZE;
	    while (vid_line >= vid_wmodes)
		vid_line -= VID_ROW_SIZE;
	} else {
	    vid_line -= VID_ROW_SIZE;
	}
	break;

    case K_DOWNARROW:
	S_LocalSound("misc/menu1.wav");
	if (vid_line < NUM_WINDOWED_MODES &&
	    vid_line + VID_ROW_SIZE >= NUM_WINDOWED_MODES) {
	    /* windowed to fullscreen section */
	    vid_line = NUM_WINDOWED_MODES + (vid_line % VID_ROW_SIZE);
	} else if (vid_line + VID_ROW_SIZE >= vid_wmodes) {
	    /* bottom to top */
	    vid_line = (vid_line - NUM_WINDOWED_MODES) % VID_ROW_SIZE;
	} else {
	    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;
    }
}

qboolean
VID_IsFullScreen()
{
    return VID_GetModePtr(vid_modenum)->fullscreen;
}
