/*
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 MINIMUM_MEMORY  0x550000

#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, windowed_mouse;
static qboolean palette_changed, syscolchg, vid_mode_set, hide_window, pal_is_nostatic;
static HICON  hIcon;

qboolean mouseactive; // from in_win.c

viddef_t  vid;        // global video state

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

// Note that 0 is MODE_WINDOWED
cvar_t    vid_mode = {"vid_mode","0", false};
// Note that 0 is MODE_WINDOWED
cvar_t    _vid_default_mode = {"_vid_default_mode","0", true};
// Note that 3 is MODE_FULLSCREEN_DEFAULT
cvar_t    _vid_default_mode_win = {"_vid_default_mode_win","3", true};
cvar_t    vid_wait = {"vid_wait","0"};
cvar_t    vid_nopageflip = {"vid_nopageflip","0", true};
cvar_t    _vid_wait_override = {"_vid_wait_override", "0", true};
cvar_t    vid_config_x = {"vid_config_x","800", true};
cvar_t    vid_config_y = {"vid_config_y","600", true};
cvar_t    vid_stretch_by_2 = {"vid_stretch_by_2","1", true};
cvar_t    _windowed_mouse = {"_windowed_mouse","0", true};
cvar_t    vid_fullscreen_mode = {"vid_fullscreen_mode","3", true};
cvar_t    vid_windowed_mode = {"vid_windowed_mode","0", true};
cvar_t    block_switch = {"block_switch","0", true};
cvar_t    vid_window_x = {"vid_window_x", "0", true};
cvar_t    vid_window_y = {"vid_window_y", "0", true};

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

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

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

modestate_t modestate = MS_UNINIT;

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

unsigned char vid_curpal[256*3];

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

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[13];
} 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_SafePrintf ("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);
}

#if 0 //def NEW_SUSPEND

int VID_Suspend (MGLDC *dc, int flags)
{
  int i;
  if (flags & MGL_DEACTIVATE)
  {
    IN_RestoreOriginalMouseState ();
    CDAudio_Pause ();

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

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

  return 1;
}

#else

int VID_Suspend (MGLDC *dc, int flags)
{

  if (flags & MGL_DEACTIVATE)
  {
  // FIXME: this doesn't currently work on NT
    if (block_switch.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;
  }

}
#endif


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

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

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

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


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


void VID_InitMGLFull (HINSTANCE hInstance)
{
  int     i, xRes, yRes, bits, vMode, 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 (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;
        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[MODE_FULLSCREEN_DEFAULT] = modelist[stretchedmode];
      modelist[MODE_FULLSCREEN_DEFAULT].stretched = 1;
      modelist[MODE_FULLSCREEN_DEFAULT].width >>= 1;
      modelist[MODE_FULLSCREEN_DEFAULT].height >>= 1;
      sprintf (modelist[MODE_FULLSCREEN_DEFAULT].modedesc, "%dx%d",
           modelist[MODE_FULLSCREEN_DEFAULT].width,
           modelist[MODE_FULLSCREEN_DEFAULT].height);
    }

    vid_default = MODE_FULLSCREEN_DEFAULT;

    temp = m[0];

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

  MGL_setSuspendAppCallback(VID_Suspend);
}


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)) == NULL)
    return NULL;

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

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

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

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

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

  return dc;
}


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

  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 = "WinQuake";

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

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

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

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

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

// automatically stretch 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, cmodes, 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, "%dx%d",
             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, "%dx%d",
               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, "%dx%d",
               devmode.dmPelsWidth, devmode.dmPelsHeight);

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

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

          nummodes++;
        }
      }
    }

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

      case 16:
        bpp = 32;
        break;

      case 32:
        done = 1;
        break;
    }
  }

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

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

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

      nummodes += cstretch;
      istretch = originalnummodes;

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

  if (nummodes != originalnummodes)
    vid_default = MODE_FULLSCREEN_DEFAULT;
  else
    Con_SafePrintf ("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;
    sprintf (modelist[mode].modedesc, "%dx%d", x, y);
    modelist[mode].width = x;
    modelist[mode].height = y;
  }
}


/*
=================
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;
  LONG      wlong;

  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,
       "WinQuake",
       "WinQuake",
       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)TRUE, (LPARAM)hIcon);
  SendMessage (mainwindow, WM_SETICON, (WPARAM)FALSE, (LPARAM)hIcon);

  return true;
}


qboolean VID_SetFullscreenMode (int modenum)
{

  DDActive = 1;

  DestroyDIBWindow ();
  DestroyFullDIBWindow ();

  mode = modelist[modenum].modenum;

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

  if ((mgldc = createDisplayDC (modelist[modenum].stretched ||
     (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)1, (LPARAM)hIcon);
  SendMessage (mainwindow, WM_SETICON, (WPARAM)0, (LPARAM)hIcon);

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

  return true;
}


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

  DDActive = 0;

  DestroyFullscreenWindow ();
  DestroyDIBWindow ();

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

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

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

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

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

  WindowRect.top = WindowRect.left = 0;

  hdc = GetDC(NULL);

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

  ReleaseDC(NULL,hdc);

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

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

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

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

// position and show the DIB window
  SetWindowPos (mainwindow, HWND_TOPMOST, 0, 0, 0, 0,
          SWP_NOSIZE | SWP_SHOWWINDOW | SWP_DRAWFRAME);
  ShowWindow (mainwindow, SW_SHOWDEFAULT);
  UpdateWindow (mainwindow);

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

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

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

  MGL_makeCurrentDC(dibdc);

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

  vid_stretched = modelist[modenum].stretched;

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

  return true;
}


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

  if (inerror)
    return;

  in_mode_set = false;
  inerror = true;

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

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

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

  inerror = false;
}


void VID_SetDefaultMode (void)
{

  if (vid_initialized)
    VID_SetMode (0, vid_curpal);

  IN_DeactivateMouse ();
}


int VID_SetMode (int modenum, unsigned char *palette)
{
  int       original_mode, temp, dummy;
  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_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;

  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 (!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;
  double  testduration;

  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_config_x);
  Cvar_RegisterVariable (&vid_config_y);
  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;

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

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

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

  vid_initialized = true;

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

  vid_realmode = vid_modenum;

  VID_SetPalette (palette);

  vid_menudrawfn = VID_MenuDraw;
  vid_menukeyfn = VID_MenuKey;

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


void  VID_Shutdown (void)
{
  HDC       hdc;
  int       dummy;

  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
================
*/
void FlipScreen(vrect_t *rects)
{
  HRESULT   ddrval;

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


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

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
=======
*/
int MapKey (int key)
{
  key = (key>>16)&255;
  if (key > 127)
    return 0;

  return scantokey[key];
}

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 /* && mouseactive */)
      {
        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

===================================================================
*/

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

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

  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))
      {
        if (wParam & SIZE_MINIMIZED)
          Minimized = true;
      }
      break;

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

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

    // fix 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 = CDAudio_MessageHandler (hWnd, uMsg, wParam, lParam);
      break;

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

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


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

static int  vid_line, vid_wmodes;

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

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

static modedesc_t modedescs[MAX_MODEDESCS];

/*
================
VID_MenuDraw
================
*/
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<3 ; i++)
  {
    ptr = VID_GetModeDescriptionMemCheck (i);
    modedescs[i].modenum = modelist[i].modenum;
    modedescs[i].desc = ptr;
    modedescs[i].ismode13 = 0;
    modedescs[i].iscur = 0;

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

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

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

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

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

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

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

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

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


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

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

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

    column += 13*8;
  }

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

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

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

      column += 13*8;

      if (((i - 3) % VID_ROW_SIZE) == (VID_ROW_SIZE - 1))
      {
        column = 16;
        row += 8;
      }
    }
  }

// line cursor
  if (vid_testingmode)
  {
    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;
  }
}
