/*
 * 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

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

#define MAX_MODE_LIST	30
#define VID_ROW_SIZE	3

qboolean      dibonly;

extern int    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;
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 windowed_mouse = true;
static qboolean palette_changed, syscolchg, vid_mode_set, hide_window, pal_is_nostatic;
static HICON  hIcon;
extern qboolean mouseactive;           // from in_win.c

#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 = { "vid_mode", "0" };
// Note that 0 is MODE_WINDOWED
cvar_t        _vid_default_mode = { "_vid_default_mode", "0", CVAR_ARCHIVE };
// Note that 3 is MODE_FULLSCREEN_DEFAULT
cvar_t        _vid_default_mode_win = { "_vid_default_mode_win", "3", CVAR_ARCHIVE };
cvar_t        vid_wait = { "vid_wait", "0" };
cvar_t        vid_nopageflip = { "vid_nopageflip", "0", CVAR_ARCHIVE };
cvar_t        _vid_wait_override = { "_vid_wait_override", "0", CVAR_ARCHIVE };
cvar_t        vid_config_x = { "vid_config_x", "800", CVAR_ARCHIVE };
cvar_t        vid_config_y = { "vid_config_y", "600", CVAR_ARCHIVE };
cvar_t        vid_stretch_by_2 = { "vid_stretch_by_2", "1", CVAR_ARCHIVE };
cvar_t        _windowed_mouse = { "_windowed_mouse", "1", CVAR_ARCHIVE };
cvar_t        vid_fullscreen_mode = { "vid_fullscreen_mode", "3", CVAR_ARCHIVE };
cvar_t        vid_windowed_mode = { "vid_windowed_mode", "0", CVAR_ARCHIVE };
cvar_t        block_switch = { "block_switch", "0", CVAR_ARCHIVE };
cvar_t        vid_window_x = { "vid_window_x", "0", CVAR_ARCHIVE };
cvar_t        vid_window_y = { "vid_window_y", "0", CVAR_ARCHIVE };
cvar_t        vid_resetonswitch = { "vid_resetonswitch", "0", CVAR_ARCHIVE };
cvar_t        vid_displayfrequency = { "vid_displayfrequency", "0" };

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];

int           driver = grDETECT, mode;
bool          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[17];
    int           frequency;
} vmode_t;

static vmode_t modelist[MAX_MODE_LIST];
static int    nummodes;
static vmode_t *pcurrentmode;

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

/*
 * VID_RememberWindowPos
 */
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
 */
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
 */
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
 */
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
 */
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;                  // not enough memory for mode

    return true;
}

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

    tbuffersize = width * height * sizeof (*d_pzbuffer);

    tsize = D_SurfaceCacheForRes (width, height);

    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_Printf ("Not enough memory for video mode\n");
        return false;                  // not enough memory for mode
    }

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

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

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

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

        block_drawing = true;          // so we 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;
    }

    return 0;
}

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

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

static void AddModeDesc (vmode_t * mode) {
    if (mode->frequency)
        Q_snprintfz (mode->modedesc, sizeof (mode->modedesc), "%dx%d@%d", mode->width, mode->height, mode->frequency);
    else
        Q_snprintfz (mode->modedesc, sizeof (mode->modedesc), "%dx%d", mode->width, mode->height);
}

void VID_InitMGLFull (HINSTANCE hInstance) {
    int           i, xRes, yRes, bits, lowres, curmode, temp, 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 (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;
                AddModeDesc (&modelist[curmode]);

                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;
            AddModeDesc (&modelist[MODE_FULLSCREEN_DEFAULT]);
        }

        vid_default = MODE_FULLSCREEN_DEFAULT;

        temp = m[0];

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

    MGL_setSuspendAppCallback (VID_Suspend);
}

MGLDC        *createDisplayDC (int forcemem)
/****************************************************************************
*
* 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.
*
****************************************************************************/
{
    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)))
        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_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 = ENGINE_NAME;

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

/*
 * VID_InitFullDIB
 */
void VID_InitFullDIB (HINSTANCE hInstance) {
    DEVMODE       devmode;
    int           i, j, modenum, existingmode, originalnummodes, lowestres;
    int           numlowresmodes, bpp, done, 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 | DM_DISPLAYFREQUENCY;

            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;
                modelist[nummodes].frequency = devmode.dmDisplayFrequency;

                AddModeDesc (&modelist[nummodes]);

                // 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;
                        AddModeDesc (&modelist[nummodes]);
                    }
                }

                for (i = originalnummodes, existingmode = 0; i < nummodes; i++) {
                    if (modelist[nummodes].width == modelist[i].width && modelist[nummodes].height == modelist[i].height
                        && modelist[nummodes].frequency == modelist[i].frequency) {
                        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 | DM_DISPLAYFREQUENCY;

                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;
                    modelist[nummodes].frequency = devmode.dmDisplayFrequency;

                    AddModeDesc (&modelist[nummodes]);

                    // 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;
                            AddModeDesc (&modelist[nummodes]);
                        }
                    }

                    for (i = originalnummodes, existingmode = 0; i < nummodes; i++) {
                        if (modelist[nummodes].width == modelist[i].width
                            && modelist[nummodes].height == modelist[i].height
                            && modelist[nummodes].frequency == modelist[i].frequency) {
                            // 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 | DM_DISPLAYFREQUENCY;

    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 | DM_DISPLAYFREQUENCY;

            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;
                modelist[nummodes].frequency = devmode.dmDisplayFrequency;

                AddModeDesc (&modelist[nummodes]);

                // 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
                        && modelist[nummodes].frequency == modelist[i].frequency) {
                        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;
            AddModeDesc (&modelist[istretch]);
        }
    }

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

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

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

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

    if (mode == MODE_SETTABLE_WINDOW) {
        modelist[mode].stretched = (int) vid_stretch_by_2.value;
        stretch = modelist[mode].stretched;

        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;
        AddModeDesc (&modelist[mode]);
    }
}

/*
 * VID_GetModeDescriptionMemCheck
 */
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
 */
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)
        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

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

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

    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, ENGINE_NAME, ENGINE_NAME, 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)))
        MGL_fatalError ("Unable to create Windowed DC!");

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

    MGL_makeCurrentDC (dibdc);

    vid.buffer = vid.direct = dibdc->surface;
    vid.rowbytes = 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);

    // Tonik: this is a workaround for the bug with garbaged
    // screen on Riva TNT
    if (lastmodestate == MS_FULLSCREEN && vid_resetonswitch.value)
        ChangeDisplaySettings (NULL, CDS_RESET);

    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 || (int) vid_nopageflip.value)))
        return false;

    modestate = MS_FULLSCREEN;
    vid_fulldib_on_focus_mode = 0;

    vid.buffer = 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) {
    int           lastmodestate, freq;
    HDC           hdc;
    pixel_format_t pf;

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

    freq =
        modelist[modenum].frequency > 0 ? modelist[modenum].frequency : vid_displayfrequency.value >
        0 ? vid_displayfrequency.value : 0;
    if (freq) {
        gdevmode.dmDisplayFrequency = modelist[modenum].frequency;
        gdevmode.dmFields |= DM_DISPLAYFREQUENCY;
    }

    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)))
        MGL_fatalError ("Unable to create Fullscreen DIB DC!");

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

    MGL_makeCurrentDC (dibdc);

    vid.buffer = vid.direct = dibdc->surface;
    vid.rowbytes = 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, (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 == 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.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_Printf ("%s\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.direct = memdc->surface;
        vid.rowbytes = memdc->mi.bytesPerLine;
    } else if (mgldc) {                // Update surface pointer for linear
        // access modes
        vid.buffer = vid.direct = mgldc->surface;
        vid.rowbytes = 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;

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

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

/*
 * VID_NumModes_f
 */
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
 */
void VID_DescribeMode_f (void) {
    int           modenum;

    modenum = Q_atoi (Cmd_Argv (1));

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

/*
 * VID_DescribeModes_f
 */
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
 */
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
 */
void VID_Windowed_f (void) {
    VID_SetMode ((int) vid_windowed_mode.value, vid_curpal);
}

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

/*
 * VID_Minimize_f
 */
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
 */
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, basenummodes, t, dr, dg, db;
    byte         *ptmp;

    Cvar_Register (&vid_mode);
    Cvar_Register (&vid_wait);
    Cvar_Register (&vid_nopageflip);
    Cvar_Register (&_vid_wait_override);
    Cvar_Register (&_vid_default_mode);
    Cvar_Register (&_vid_default_mode_win);
    Cvar_Register (&vid_config_x);
    Cvar_Register (&vid_config_y);
    Cvar_Register (&vid_stretch_by_2);
    Cvar_Register (&_windowed_mouse);
    Cvar_Register (&vid_fullscreen_mode);
    Cvar_Register (&vid_windowed_mode);
    Cvar_Register (&block_switch);
    Cvar_Register (&vid_window_x);
    Cvar_Register (&vid_window_y);
    Cvar_Register (&vid_resetonswitch);
    Cvar_Register (&vid_displayfrequency);

    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;

    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 ("-window") || COM_CheckParm ("-startwindowed")) {
        startwindowed = 1;
        vid_default = windowed_default;
    }
    // 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;

    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_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 (mainwindow)
            DestroyWindow (mainwindow);

        MGL_exit ();

        vid_testingmode = 0;
        vid_initialized = 0;

        // Tonik: this is a workaround for the bug with garbaged
        // screen on Riva TNT
        if (modestate == MS_FULLSCREEN && vid_resetonswitch.value)
            ChangeDisplaySettings (NULL, CDS_RESET);
    }
}

/*
 * FlipScreen
 */
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 != (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 ((_vid_default_mode_win.value != vid_default)
            && (!startwindowed || (_vid_default_mode_win.value < 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 (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 ();
            }
        }
    }
}

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

void AppActivate (BOOL fActive, BOOL minimize)
/****************************************************************************
*
* 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.
*
****************************************************************************/
{
    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.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;
                    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.value) {
                IN_DeactivateMouse ();
                IN_ShowMouse ();
            }
        }
    }
}

/*
 *
 *
 * MAIN WINDOW
 *
 *
 */

LONG          CDAudio_MessageHandler (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int           IN_MapKey (int key);
void          MW_Hook_Message (long buttons);

#define WM_MWHOOK (WM_USER + 1)

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

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

            // Tonik: this is a workaround for the bug with garbaged screen on
            // Riva TNT
            if (fActive == WA_INACTIVE && modestate == MS_FULLSCREEN && vid_resetonswitch.value)
                ChangeDisplaySettings (NULL, CDS_RESET);

            // 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 (IN_MapKey (lParam), true);
            break;

        case WM_KEYUP:
        case WM_SYSKEYUP:
            if (!in_mode_set)
                Key_Event (IN_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;

        case WM_MWHOOK:
            MW_Hook_Message (lParam);
            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 (!cl_confirmquit.value
                    || 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, mpic_t * pic);
extern void   M_DrawPic (int x, int y, mpic_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];

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

    p = Draw_CachePic ("gfx/vidmodes.lmp");
    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")) {
                    k = dup ? dupmode : 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) {
        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");

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

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