// Filename: wdxGraphicsWindow.cxx
// Created by:  mike (09Jan00)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://www.panda3d.org/license.txt .
//
// To contact the maintainers of this program write to
// panda3d@yahoogroups.com .
//
////////////////////////////////////////////////////////////////////

#include <errno.h>
#include <time.h>
#include <math.h>
#include <tchar.h>
#include "wdxGraphicsWindow.h"
#include "wdxGraphicsPipe.h"
#include "config_wdxdisplay.h"

#include <keyboardButton.h>
#include <mouseButton.h>
#include <throw_event.h>

#ifdef DO_PSTATS
#include <pStatTimer.h>
#endif

#include <map>

////////////////////////////////////////////////////////////////////
// Static variables
////////////////////////////////////////////////////////////////////
TypeHandle wdxGraphicsWindow::_type_handle;

#define LAST_ERROR 0
#define ERRORBOX_TITLE "Panda3D Error"
#define WDX_WINDOWCLASSNAME "wdxDisplay"
#define DEFAULT_CURSOR IDC_ARROW

typedef map<HWND,wdxGraphicsWindow *> HWND_PANDAWIN_MAP;

HWND_PANDAWIN_MAP hwnd_pandawin_map;
wdxGraphicsWindow* global_wdxwinptr = NULL;  // need this for temporary windproc

#define MAX_DISPLAYS 20

extern bool dx_full_screen_antialiasing;  // defined in dxgsg_config.cxx

#define PAUSED_TIMER_ID  7   // completely arbitrary choice
#define DXREADY ((_dxgsg!=NULL)&&(_dxgsg->GetDXReady()))

LONG WINAPI static_window_proc(HWND hwnd, UINT msg, WPARAM wparam,LPARAM lparam);

// imperfect method to ID NVid? could also scan desc str, but that isnt fullproof either
#define IS_NVIDIA(DDDEVICEID) ((DDDEVICEID.dwVendorId==0x10DE) || (DDDEVICEID.dwVendorId==0x12D2))
#define IS_ATI(DDDEVICEID) (DDDEVICEID.dwVendorId==0x1002) 
#define IS_MATROX(DDDEVICEID) (DDDEVICEID.dwVendorId==0x102B)

// because we dont have access to ModifierButtons, as a hack just synchronize state of these
// keys on get/lose keybd focus
#define NUM_MODIFIER_KEYS 16
unsigned int hardcoded_modifier_buttons[NUM_MODIFIER_KEYS]={VK_SHIFT,VK_MENU,VK_CONTROL,VK_SPACE,VK_TAB,
                                         VK_UP,VK_DOWN,VK_LEFT,VK_RIGHT,VK_PRIOR,VK_NEXT,VK_HOME,VK_END,
                                         VK_INSERT,VK_DELETE,VK_ESCAPE};

static DWORD BitDepth_2_DDBDMask(DWORD iBitDepth) {
    switch(iBitDepth) {
        case 16: return DDBD_16;
        case 32: return DDBD_32;
        case 24: return DDBD_24;
        case 8: return DDBD_8;
        case 1: return DDBD_1;
        case 2: return DDBD_2;
        case 4: return DDBD_4;
    }
    return 0x0;
}
/*
typedef enum {DBGLEV_FATAL,DBGLEV_ERROR,DBGLEV_WARNING,DBGLEV_INFO,DBGLEV_DEBUG,DBGLEV_SPAM
    } DebugLevels;
 
void PrintDBGStr(DebugLevels level,HRESULT hr,const char *msgstr) {
    ostream *pstrm;
    static ostream dbg_strms[DBGLEV_SPAM+1]={wdxdisplay_cat.fatal,wdxdisplay_cat.error,
        wdxdisplay_cat.warning,wdxdisplay_cat.info,wdxdisplay_cat.debug,wdxdisplay_cat.spam};
    assert(level<=DBGLEV_SPAM);

    pstrm=dbg_strms[level];
    (*pstrm)->fatal() << "GetDisplayMode failed, result = " << ConvD3DErrorToString(hr) << endl;
}
*/
#ifdef _DEBUG
static void DebugPrintPixFmt(DDPIXELFORMAT* pddpf) {
    static int iddpfnum=0;
    ostream *dbgout = &dxgsg_cat.debug();

    *dbgout << "ZBuf DDPF[" << iddpfnum << "]: RGBBitCount:" << pddpf->dwRGBBitCount
    << " Flags:"  << (void *)pddpf->dwFlags ;

    if(pddpf->dwFlags & DDPF_STENCILBUFFER) {
        *dbgout << " StencilBits:" << (void *) pddpf->dwStencilBitDepth;
    }

    *dbgout << endl;

    iddpfnum++;
}
#endif

// pops up MsgBox w/system error msg
#define LAST_ERROR 0
void PrintErrorMessage(DWORD msgID) {
   LPTSTR pMessageBuffer;

   if(msgID==LAST_ERROR)
     msgID=GetLastError();

   FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                 NULL,msgID,  
                 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default language
                 (LPTSTR) &pMessageBuffer,  // the weird ptrptr->ptr cast is intentional, see FORMAT_MESSAGE_ALLOCATE_BUFFER
                 1024, NULL);
   MessageBox(GetDesktopWindow(),pMessageBuffer,_T(ERRORBOX_TITLE),MB_OK);
   wdxdisplay_cat.fatal() << "System error msg: " << pMessageBuffer << endl;
   LocalFree( pMessageBuffer ); 
}

//#if defined(NOTIFY_DEBUG) || defined(DO_PSTATS)
#ifdef _DEBUG
extern void dbgPrintVidMem(LPDIRECTDRAW7 pDD, LPDDSCAPS2 lpddsCaps,const char *pMsg) {
    DWORD dwTotal,dwFree;
    HRESULT hr;

 //  These Caps bits arent allowed to be specified when calling GetAvailVidMem.
 //  They don't affect surface allocation in a vram heap.

#define AVAILVIDMEM_BADCAPS  (DDSCAPS_BACKBUFFER   | \
                              DDSCAPS_FRONTBUFFER  | \
                              DDSCAPS_COMPLEX      | \
                              DDSCAPS_FLIP         | \
                              DDSCAPS_OWNDC        | \
                              DDSCAPS_PALETTE      | \
                              DDSCAPS_SYSTEMMEMORY | \
                              DDSCAPS_VISIBLE      | \
                              DDSCAPS_WRITEONLY)

    DDSCAPS2 ddsCaps = *lpddsCaps;
    ddsCaps.dwCaps &= ~(AVAILVIDMEM_BADCAPS);  // turn off the bad caps
//  ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY; done internally by DX anyway

    if(FAILED(  hr = pDD->GetAvailableVidMem(&ddsCaps,&dwTotal,&dwFree))) {
        wdxdisplay_cat.debug() << "GetAvailableVidMem failed : result = " << ConvD3DErrorToString(hr) << endl;
        exit(1);
    }

  // Write a debug message to the console reporting the texture memory.
    char tmpstr[100],tmpstr2[100];
    sprintf(tmpstr,"%.4g",dwTotal/1000000.0);
    sprintf(tmpstr2,"%.4g",dwFree/1000000.0);
    if(wdxdisplay_cat.is_debug())
       wdxdisplay_cat.debug() << "AvailableVidMem before creating "<< pMsg << ",(megs) total: " << tmpstr << "  free:" << tmpstr2 <<endl;
}
#endif

#define MAX_DX_ZBUF_FMTS 20
static int cNumZBufFmts;

HRESULT CALLBACK EnumZBufFmtsCallback( LPDDPIXELFORMAT pddpf, VOID* param )  {
    DDPIXELFORMAT *ZBufFmtsArr = (DDPIXELFORMAT *) param;
    assert(cNumZBufFmts < MAX_DX_ZBUF_FMTS);
    memcpy( &(ZBufFmtsArr[cNumZBufFmts]), pddpf, sizeof(DDPIXELFORMAT) );
    cNumZBufFmts++;
    return DDENUMRET_OK;
}

// fn exists so AtExitFn can call it without refcntr blowing up since its !=0
void wdxGraphicsWindow::DestroyMe(bool bAtExitFnCalled) {
  if(wdxdisplay_cat.is_spam())
      wdxdisplay_cat.spam() << "DestroyMe called, AtExitFnCalled=" << bAtExitFnCalled << endl;

  _exiting_window = true;  // may be needed for DestroyWindow call
  DXScreenData scrn;
  memcpy(&scrn,&_dxgsg->scrn,sizeof(DXScreenData));

  if(_dxgsg!=NULL) {
      _dxgsg->dx_cleanup(_props._fullscreen, bAtExitFnCalled);
      _dxgsg=NULL;
  }

/*
  if(scrn._hdc!=NULL) {
    ReleaseDC(scrn.hWnd,scrn._hdc);
//    _hdc = NULL;
  }
*/  
/*
  if((scrn._hOldForegroundWindow!=NULL) && (scrn.hWnd==GetForegroundWindow())) {
      SetForegroundWindow(scrn._hOldForegroundWindow);
  }
"*/
  if(scrn.hWnd!=NULL) {
/*
    if(_bLoadedCustomCursor && (_hMouseCursor!=NULL))
        DestroyCursor(_hMouseCursor);
*/      
      DestroyWindow(scrn.hWnd);
      hwnd_pandawin_map.erase(scrn.hWnd);
  }
}

void wdxGraphicsWindow::do_close_window() {
  GraphicsWindow::do_close_window();
   DestroyMe(false);
}

////////////////////////////////////////////////////////////////////
//     Function: Destructor
//       Access:
//  Description:
////////////////////////////////////////////////////////////////////
wdxGraphicsWindow::~wdxGraphicsWindow(void) {
   close_window();
}

void DestroyAllWindows(bool bAtExitFnCalled) {
   // need to go through all windows in map var and delete them
   while(!hwnd_pandawin_map.empty()) {
     // cant use a for loop cause DestroyMe erases things out from under us, so iterator is invalid
     HWND_PANDAWIN_MAP::iterator pwin = hwnd_pandawin_map.begin();
     if((*pwin).second != NULL) 
         (*pwin).second->DestroyMe(bAtExitFnCalled);
   }
}

void AtExitFn() {
#ifdef _DEBUG
    wdxdisplay_cat.spam() << "AtExitFn called\n";
#endif

     restore_global_parameters();

     DestroyAllWindows(true);
}

////////////////////////////////////////////////////////////////////
//     Function: wdxGraphicsWindow::set_window_handle
//       Access: Public
//  Description: This is called on the object once the actual Window
//               has been created.  It gives the window handle of the
//               created window so the wdxGraphicsWindow object can
//               perform any additional initialization based on this.
////////////////////////////////////////////////////////////////////
void wdxGraphicsWindow::
set_window_handle(HWND hwnd) {
  // Tell the associated dxGSG about the window handle.
  _dxgsg->scrn.hWnd = hwnd;

  // Determine the initial open status of the IME.
  _ime_open = false;
  _ime_active = false;
  HIMC hIMC = ImmGetContext(hwnd);
  if (hIMC != 0) {
    _ime_open = (ImmGetOpenStatus(hIMC) != 0);
    ImmReleaseContext(hwnd, hIMC);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: static_window_proc
//       Access:
//  Description:
////////////////////////////////////////////////////////////////////
LONG WINAPI static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
   HWND_PANDAWIN_MAP::iterator pwin;
   pwin=hwnd_pandawin_map.find(hwnd);

   if(pwin!=hwnd_pandawin_map.end()) {
      wdxGraphicsWindow *wdxwinptr=(*pwin).second;
      return wdxwinptr->window_proc(hwnd, msg, wparam, lparam);
   } else if(global_wdxwinptr!=NULL){
       // this stuff should only be used during CreateWindow()
       return global_wdxwinptr->window_proc(hwnd, msg, wparam, lparam);
   } else {
       // should never need this??  (maybe at shutdwn?)
       return DefWindowProc(hwnd, msg, wparam, lparam);
   }
}

// Note: could use _TrackMouseEvent in comctrl32.dll (part of IE 3.0+) which emulates
// TrackMouseEvent on w95, but that requires another 500K of memory to hold that DLL,
// which is lame just to support w95, which probably has other issues anyway
INLINE void wdxGraphicsWindow::
track_mouse_leaving(HWND hwnd) {
  if(_pParentWindowGroup->_pfnTrackMouseEvent==NULL)
      return;

  TRACKMOUSEEVENT tme = {sizeof(TRACKMOUSEEVENT),TME_LEAVE,hwnd,0};
  BOOL bSucceeded = _pParentWindowGroup->_pfnTrackMouseEvent(&tme);  // tell win32 to post WM_MOUSELEAVE msgs

  if((!bSucceeded) && wdxdisplay_cat.is_debug())
     wdxdisplay_cat.debug() << "TrackMouseEvent failed!, LastError=" << GetLastError() << endl;

  _tracking_mouse_leaving=true;
}

////////////////////////////////////////////////////////////////////
//     Function: window_proc
//       Access:
//  Description:
////////////////////////////////////////////////////////////////////
LONG wdxGraphicsWindow::
window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
  int button = -1;
  int x, y, width, height;

  switch(msg) {
    case WM_PAINT: {
        PAINTSTRUCT ps;
        BeginPaint(hwnd, &ps);
        if(DXREADY)
            _dxgsg->show_frame();
        EndPaint(hwnd, &ps);
        return 0;
    }
    
    case WM_MOUSEMOVE:
        if(!DXREADY)
            break;
    
        // Win32 doesn't return the same numbers as X does when the mouse
        // goes beyond the upper or left side of the window
        #define SET_MOUSE_COORD(iVal,VAL) { \
                iVal = VAL;                   \
                if(iVal & 0x8000)             \
                  iVal -= 0x10000;            \
        }
        
        if(!_tracking_mouse_leaving) {
            // need to re-call TrackMouseEvent every time mouse re-enters window
            track_mouse_leaving(hwnd);
        }
    
        SET_MOUSE_COORD(x,LOWORD(lparam));
        SET_MOUSE_COORD(y,HIWORD(lparam));
    
        handle_mouse_motion(x, y);
        return 0;
    
    // if cursor is invisible, make it visible when moving in the window bars,etc
    case WM_NCMOUSEMOVE: {
        if(!_props._bCursorIsVisible) {
            if(!_cursor_in_windowclientarea) {
                ShowCursor(true);
                _cursor_in_windowclientarea=true;
            }
        }
        break;
    }

    case WM_NCMOUSELEAVE: {
        if(!_props._bCursorIsVisible) {
            ShowCursor(false);
            _cursor_in_windowclientarea=false;
        }
        break;
    }
    
    case WM_MOUSELEAVE: {
       // wdxdisplay_cat.fatal() << "XXXXX WM_MOUSELEAVE received\n";

       _tracking_mouse_leaving=false;  
       handle_mouse_entry(false,0,0);         
       break;
    }
    
    case WM_CREATE: {
      track_mouse_leaving(hwnd);
    
      _cursor_in_windowclientarea=false;
      if(!_props._bCursorIsVisible)
          ShowCursor(false);
      break;
    }
    
    case WM_IME_NOTIFY:
      if (wparam == IMN_SETOPENSTATUS) {
        HIMC hIMC = ImmGetContext(hwnd);
        nassertr(hIMC != 0, 0);
        _ime_open = (ImmGetOpenStatus(hIMC) != 0);
        if (!_ime_open) {
          _ime_active = false;  // Sanity enforcement.
        }
        ImmReleaseContext(hwnd, hIMC);
      }
      break;
    
    case WM_IME_STARTCOMPOSITION:
      // In case we're running fullscreen mode, we have to turn on
      // explicit DX support for overlay windows now, so we'll be able
      // to see the IME window.
      _dxgsg->support_overlay_window(true);
      _ime_active = true;
      break;

    case WM_IME_ENDCOMPOSITION:
      // Turn off the support for overlay windows, since we're done
      // with the IME window for now and it just slows things down.
      _dxgsg->support_overlay_window(false);
      _ime_active = false;
      break;

    case WM_IME_COMPOSITION:
      if (lparam & GCS_RESULTSTR) {
        if (!_input_devices.empty()) {
          HIMC hIMC = ImmGetContext(hwnd);
          nassertr(hIMC != 0, 0);
          
          static const int max_ime_result = 128;
          static char ime_result[max_ime_result];

          if (_ime_composition_w) {
            // Since ImmGetCompositionStringA() doesn't seem to work
            // for Win2000 (it always returns question mark
            // characters), we have to use ImmGetCompositionStringW()
            // on this OS.  This is actually the easier of the two
            // functions to use.

            DWORD result_size =
              ImmGetCompositionStringW(hIMC, GCS_RESULTSTR,
                                       ime_result, max_ime_result);

            // Add this string into the text buffer of the application.
          
            // ImmGetCompositionStringW() returns a string, but it's
            // filled in with wstring data: every two characters defines a
            // 16-bit unicode char.  The docs aren't clear on the
            // endianness of this.  I guess it's safe to assume all Win32
            // machines are little-endian.
            for (DWORD i = 0; i < result_size; i += 2) {
              int result = 
                ((int)(unsigned char)ime_result[i + 1] << 8) | 
                (unsigned char)ime_result[i];
              _input_devices[0].keystroke(result);
            }
          } else {
            // On the other hand, ImmGetCompositionStringW() doesn't
            // work on Win95 or Win98; for these OS's we must use
            // ImmGetCompositionStringA().
            DWORD result_size =
              ImmGetCompositionStringA(hIMC, GCS_RESULTSTR,
                                       ime_result, max_ime_result);

            // ImmGetCompositionStringA() returns an encoded ANSI
            // string, which we now have to map to wide-character
            // Unicode.
            static const int max_wide_result = 128;
            static wchar_t wide_result[max_wide_result];

            int wide_size =
              MultiByteToWideChar(CP_ACP, 0,
                                  ime_result, result_size,
                                  wide_result, max_wide_result);
            if (wide_size == 0) {
              PrintErrorMessage(LAST_ERROR);
            }
            for (int i = 0; i < wide_size; i++) {
              _input_devices[0].keystroke(wide_result[i]);
            }
          }

          ImmReleaseContext(hwnd, hIMC);
        }
        return 0;
      }
      break;

    case WM_CHAR:
      // Ignore WM_CHAR messages if we have the IME open, since
      // everything will come in through WM_IME_COMPOSITION.  (It's
      // supposed to come in through WM_CHAR, too, but there seems to
      // be a bug in Win2000 in that it only sends question mark
      // characters through here.)
      if (!_ime_open && !_input_devices.empty()) {
        _input_devices[0].keystroke(wparam);
      }
      break;

    case WM_SYSKEYDOWN: {
      // Alt and F10 are sent as WM_SYSKEYDOWN instead of WM_KEYDOWN
      // want to use defwindproc on Alt syskey so std windows cmd Alt-F4 works, etc
      POINT point;
      GetCursorPos(&point);
      ScreenToClient(hwnd, &point);
      handle_keypress(lookup_key(wparam), point.x, point.y);
      if (wparam == VK_F10) {
        //bypass default windproc F10 behavior (it activates the main menu, but we have none)
        return 0;
      }
    }
    break;

    case WM_SYSCOMMAND:
        if(wparam==SC_KEYMENU) {
            // if Alt is released (alone w/o other keys), defwindproc will send 
            // this command, which will 'activate' the title bar menu (we have none)
            // and give focus to it.  we dont want this to happen, so kill this msg
            return 0;
        }
    break;

    case WM_KEYDOWN: {
      POINT point;
      GetCursorPos(&point);
      ScreenToClient(hwnd, &point);
      handle_keypress(lookup_key(wparam), point.x, point.y);

      // Handle Cntrl-V paste from clipboard.  Is there a better way
      // to detect this hotkey?
      if ((wparam=='V') && (GetKeyState(VK_CONTROL) < 0) && 
          !_input_devices.empty()) {
        HGLOBAL hglb;
        char *lptstr;

        if (!IsClipboardFormatAvailable(CF_TEXT))
          return 0;

        if (!OpenClipboard(NULL))
          return 0;
        
        // Maybe we should support CF_UNICODETEXT if it is available
        // too?
        hglb = GetClipboardData(CF_TEXT);
        if (hglb!=NULL) {
          lptstr = (char *) GlobalLock(hglb);
          if (lptstr != NULL)  {
            char *pChar;
            for(pChar=lptstr;*pChar!=NULL;pChar++) {
              _input_devices[0].keystroke((uchar)*pChar);
            }
            GlobalUnlock(hglb);
          }
        }
        CloseClipboard();
      }
      break;
    }

    case WM_SYSKEYUP:
    case WM_KEYUP:
      handle_keyrelease(lookup_key(wparam));
      break;

    case WM_LBUTTONDOWN:
        button = 0;
    case WM_MBUTTONDOWN:
        if(button < 0)
            button = 1;
    case WM_RBUTTONDOWN:
        if(!DXREADY) 
          break;
    
        if(button < 0)
            button = 2;
        SetCapture(hwnd);
        SET_MOUSE_COORD(x,LOWORD(lparam));
        SET_MOUSE_COORD(y,HIWORD(lparam));
        handle_keypress(MouseButton::button(button), x, y);
        return 0;
    
    case WM_LBUTTONUP:
        button = 0;
    case WM_MBUTTONUP:
        if(button < 0)
            button = 1;
    case WM_RBUTTONUP:
        if(!DXREADY) 
          break;
    
        if(button < 0)
            button = 2;
        ReleaseCapture();
        #if 0
           SET_MOUSE_COORD(x,LOWORD(lparam));
           SET_MOUSE_COORD(y,HIWORD(lparam));           
        #endif
        handle_keyrelease(MouseButton::button(button));
        return 0;
    
    case WM_MOVE:
        if(!DXREADY)
            break;
        handle_window_move(LOWORD(lparam), HIWORD(lparam) );
        return 0;

    case WM_EXITSIZEMOVE:
        #ifdef _DEBUG
          wdxdisplay_cat.spam()  << "WM_EXITSIZEMOVE received"  << endl;
        #endif
        
        if(_WindowAdjustingType==Resizing) {
            handle_reshape(true);
        }
    
        _WindowAdjustingType = NotAdjusting;
        return 0;
    
    case WM_ENTERSIZEMOVE: {
            if(_dxgsg!=NULL)
                _dxgsg->SetDXReady(true);   // dont disable here because I want to see pic as I resize
            _WindowAdjustingType = MovingOrResizing;
        }
        break;

    case WM_DISPLAYCHANGE: {
        #ifdef _DEBUG
            width = LOWORD(lparam);  height = HIWORD(lparam);
            DWORD newbitdepth=wparam;
            wdxdisplay_cat.spam() <<"WM_DISPLAYCHANGE received with width:" << width << "  height: " << height << " bpp: " << wparam<< endl;
        #endif

            //    unfortunately this doesnt seem to work because RestoreAllSurfaces doesn't
            //    seem to think we're back in the original displaymode even after I've received
            //    the WM_DISPLAYCHANGE msg, and returns WRONGMODE error.  So the only way I can
            //    think of to make this work is to have the timer periodically check for restored
            //    coop level

            //    if(_props._fullscreen && !_window_active) {
            //          if(_dxgsg!=NULL)
            //              _dxgsg->CheckCooperativeLevel(DO_REACTIVATE_WINDOW);
            //           else reactivate_window();
            //    }

            // does the windowed case handle displaychange properly?  no. need to recreate all devices
          }
          break;

      case WM_SIZE: {
                #ifdef _DEBUG
                {
                    width = LOWORD(lparam);  height = HIWORD(lparam);
                    wdxdisplay_cat.spam() << "WM_SIZE received with width:" << width << "  height: " << height << " flags: " <<
                    ((wparam == SIZE_MAXHIDE)? "SIZE_MAXHIDE " : "") << ((wparam == SIZE_MAXSHOW)? "SIZE_MAXSHOW " : "") <<
                    ((wparam == SIZE_MINIMIZED)? "SIZE_MINIMIZED " : "") << ((wparam == SIZE_RESTORED)? "SIZE_RESTORED " : "") <<
                    ((wparam == SIZE_MAXIMIZED)? "SIZE_MAXIMIZED " : "") << endl;
                }
                #endif
                // old comment -- added SIZE_RESTORED to handle 3dfx case
                if(dx_full_screen || ((_dxgsg==NULL) || (_dxgsg->scrn.hWnd==NULL)) || ((wparam != SIZE_RESTORED) && (wparam != SIZE_MAXIMIZED)))
                    break;

                width = LOWORD(lparam);  height = HIWORD(lparam);

                if((_props._xsize != width) || (_props._ysize != height)) {
                    _WindowAdjustingType = Resizing;

                 // for maximized,unmaximize, need to call resize code artificially
                 // since no WM_EXITSIZEMOVE is generated.
                 if(wparam==SIZE_MAXIMIZED) {
                       _bSizeIsMaximized=TRUE;
                       window_proc(hwnd, WM_EXITSIZEMOVE, 0x0,0x0);
                 } else if((wparam==SIZE_RESTORED) && _bSizeIsMaximized) {
                       _bSizeIsMaximized=FALSE;  // only want to reinit dx if restoring from maximized state
                       window_proc(hwnd, WM_EXITSIZEMOVE, 0x0,0x0);
                 }
                }

                break;
            }

        case WM_SETFOCUS: {
            if(!DXREADY) {
              break;
            }

            POINT point;
            GetCursorPos(&point);
            ScreenToClient(hwnd, &point);

            // this is a hack to make sure common modifier keys have proper state
            // since at focus loss, app may never receive key-up event corresponding to
            // a key-down. it would be better to know the exact set of ModifierButtons the
            // user is using, since we may miss some here

            int i;
            for(i=0;i<NUM_MODIFIER_KEYS;i++) {
              if(GetKeyState(hardcoded_modifier_buttons[i]) < 0) 
                handle_keypress(lookup_key(hardcoded_modifier_buttons[i]),point.x,point.y);
            }
            return 0;
        }

        case WM_KILLFOCUS: {
            if(!DXREADY) {
              break;
            }

            int i;
            for(i=0;i<NUM_MODIFIER_KEYS;i++) {
              if(GetKeyState(hardcoded_modifier_buttons[i]) < 0)
                handle_keyrelease(lookup_key(hardcoded_modifier_buttons[i]));
            }

            return 0;
        }

#if 0
        case WM_WINDOWPOSCHANGING: {
                LPWINDOWPOS pWindPos = (LPWINDOWPOS) lparam;
                wdxdisplay_cat.spam() << "WM_WINDOWPOSCHANGING received, flags 0x" << pWindPos->flags <<  endl;
                break;
            }

        case WM_GETMINMAXINFO:
            wdxdisplay_cat.spam() << "WM_GETMINMAXINFO received\n" <<  endl;
            break;
#endif

        case WM_ERASEBKGND:
        // WM_ERASEBKGND will be ignored during resizing, because 
        // we dont want to redraw as user is manually resizing window
            if(_WindowAdjustingType)
                break;
            return 0;  // dont let GDI waste time redrawing the deflt background

        case WM_TIMER:
            // 2 cases of app deactivation:
            //
            // 1) user has switched out of fullscreen mode
            //    this is first signalled when ACTIVATEAPP returns false
            //    for this case, we dont wake up until WM_SIZE returns restore or maximize
            //    and WM_TIMER just periodically reawakens app for idle processing

            //    unfortunately this doesnt seem to work because RestoreAllSurfaces doesn't
            //    seem to think we're back in the original displaymode even after I've received
            //    the WM_DISPLAYCHANGE msg, and returns WRONGMODE error.  So the only way I can
            //    think of to make this work is to have the timer periodically check for restored
            //    coop level, as it does in case 2)

            //
            // 2) windowed app has lost access to dx because another app has taken dx exclusive mode
            //    here we rely on WM_TIMER to periodically check if it is ok to reawaken app.
            //    windowed apps currently run regardless of if its window is in the foreground
            //    so we cannot rely on window messages to reawaken app

            if((wparam==_PandaPausedTimer) && (!_window_active||_active_minimized_fullscreen)) {
                assert(_dxgsg!=NULL);
                _dxgsg->CheckCooperativeLevel(DO_REACTIVATE_WINDOW);

                // wdxdisplay_cat.spam() << "periodic return of control to app\n";
                _return_control_to_app = true;
                // throw_event("PandaPaused");   
                // do we still need to do this since I return control to app periodically using timer msgs?
                // does app need to know to avoid major computation?
            }
            return 0;

        case WM_CLOSE:
         // close_window();
          delete _pParentWindowGroup;

          // BUGBUG:  right now there is no way to tell the panda app the graphics window is invalid or
          //          has been closed by the user, to prevent further methods from being called on the window.
          //          this needs to be added to panda for multiple windows to work.  in the meantime, just
          //          trigger an exit here if # windows==0, since that is the expected behavior when all 
          //          windows are closed (should be done by the app though, and it assumes you only make this
          //          type of panda gfx window)
    
          if(hwnd_pandawin_map.size()==0) {
              exit(0);
          }
          return 0;


        case WM_ACTIVATEAPP: {
            #ifdef _DEBUG
              wdxdisplay_cat.spam()  << "WM_ACTIVATEAPP(" << (bool)(wparam!=0) <<") received\n";
            #endif
            
           if((!wparam) && _props._fullscreen) {
               deactivate_window();
               return 0;
           }         // dont want to reactivate until window is actually un-minimized (see WM_SIZE)
           break;
        }
    }

    return DefWindowProc(hwnd, msg, wparam, lparam);
}

  
////////////////////////////////////////////////////////////////////
//     Function: handle_reshape
//       Access:
//  Description:
////////////////////////////////////////////////////////////////////
void wdxGraphicsWindow::handle_reshape(bool bDoDxReset) {
    
    GdiFlush();

    if(bDoDxReset && _dxgsg!=NULL) {
        HRESULT hr;

        if(_dxgsg->scrn.pddsBack==NULL) {
            //assume this is initial creation reshape and ignore this call
            return;
        }

        // Clear the back/primary surface to black
        DX_DECLARE_CLEAN(DDBLTFX, bltfx)
        bltfx.dwDDFX |= DDBLTFX_NOTEARING;
        hr = _dxgsg->scrn.pddsPrimary->Blt(NULL,NULL,NULL,DDBLT_COLORFILL | DDBLT_WAIT,&bltfx);
        if(FAILED( hr )) {
            wdxdisplay_cat.fatal() << "Blt to Black of Primary Surf failed! : result = " << ConvD3DErrorToString(hr) << endl;
            exit(1);
        }

        if(FAILED(hr = _dxgsg->scrn.pDD->TestCooperativeLevel())) {
             wdxdisplay_cat.error() << "TestCooperativeLevel failed : result = " << ConvD3DErrorToString(hr) << endl;
             return;
        }

        _dxgsg->SetDXReady(false);  // disable rendering whilst we mess with surfs

        _dxgsg->RestoreAllVideoSurfaces();

        // Want to change rendertarget size without destroying d3d device.  To save vid memory
        // (and make resizing work on memory-starved 4MB cards), we need to construct
        // a temporary mini-sized render target for the d3d device (it cannot point to a
        // NULL rendertarget) before creating the fully resized buffers.  The old
        // rendertargets will be freed when these temp targets are set, and that will give
        // us the memory to create the resized target
    
        LPDIRECTDRAWSURFACE7 pddsDummy = NULL, pddsDummyZ = NULL;
        ULONG refcnt;
    
        DX_DECLARE_CLEAN( DDSURFACEDESC2, ddsd );

        _dxgsg->scrn.pddsBack->GetSurfaceDesc(&ddsd);
        LPDIRECTDRAW7 pDD = _dxgsg->scrn.pDD;
    
        ddsd.dwFlags &= ~DDSD_PITCH;
        ddsd.dwWidth  = 1; ddsd.dwHeight = 1;
        ddsd.ddsCaps.dwCaps &= ~(DDSCAPS_COMPLEX | DDSCAPS_FLIP | DDSCAPS_FRONTBUFFER | DDSCAPS_BACKBUFFER);
    
        PRINTVIDMEM(pDD,&ddsd.ddsCaps,"dummy backbuf");
    
        if(FAILED( hr = pDD->CreateSurface( &ddsd, &pddsDummy, NULL ) )) {
            wdxdisplay_cat.fatal() << "Resize CreateSurface for temp backbuf failed : result = " << ConvD3DErrorToString(hr) << endl;
            exit(1);
        }
    
        if(_dxgsg->scrn.pddsZBuf!=NULL) {
            DX_DECLARE_CLEAN( DDSURFACEDESC2, ddsdZ );
            _dxgsg->scrn.pddsZBuf->GetSurfaceDesc(&ddsdZ);
            ddsdZ.dwFlags &= ~DDSD_PITCH;
            ddsdZ.dwWidth  = 1;   ddsdZ.dwHeight = 1;
        
            PRINTVIDMEM(pDD,&ddsdZ.ddsCaps,"dummy zbuf");
        
            if(FAILED( hr = pDD->CreateSurface( &ddsdZ, &pddsDummyZ, NULL ) )) {
                wdxdisplay_cat.fatal() << "Resize CreateSurface for temp zbuf failed : result = " << ConvD3DErrorToString(hr) << endl;
                exit(1);
            }
        
            if(FAILED( hr = pddsDummy->AddAttachedSurface( pddsDummyZ ) )) {
                wdxdisplay_cat.fatal() << "Resize AddAttachedSurf for temp zbuf failed : result = " << ConvD3DErrorToString(hr) << endl;
                exit(1);
            }
        }
    
        if(FAILED( hr = _dxgsg->scrn.pD3DDevice->SetRenderTarget( pddsDummy, 0x0 ))) {
            wdxdisplay_cat.fatal()
            << "Resize failed to set render target to temporary surface, result = " << ConvD3DErrorToString(hr) << endl;
            exit(1);
        }
        RELEASE(pddsDummyZ,wdxdisplay,"dummy resize zbuffer",false);
        RELEASE(pddsDummy,wdxdisplay,"dummy resize rendertarget buffer",false);
    }
    
    RECT view_rect;

    assert(!dx_full_screen);

    HWND hWnd=_dxgsg->scrn.hWnd;
    GetClientRect( hWnd, &view_rect );
    ClientToScreen( hWnd, (POINT*)&view_rect.left );   // translates top,left pnt
    ClientToScreen( hWnd, (POINT*)&view_rect.right );  // translates right,bottom pnt
    
    // change _props xsize,ysize
    resized((view_rect.right - view_rect.left),(view_rect.bottom - view_rect.top));
    
    _props._xorg = view_rect.left;  // _props origin should reflect upper left of view rectangle
    _props._yorg = view_rect.top;
    
    if(wdxdisplay_cat.is_spam()) {
      wdxdisplay_cat.spam() << "reshape to origin: (" << _props._xorg << "," << _props._yorg << "), size: (" << _props._xsize << "," << _props._ysize << ")\n";
    }
    
    if(_dxgsg!=NULL) {
      if(bDoDxReset)
          _dxgsg->dx_setup_after_resize(view_rect,hWnd);  // create the new resized rendertargets
      _dxgsg->SetDXReady(true);
    }
}

void wdxGraphicsWindow::deactivate_window(void) {
    // current policy is to suspend minimized or deactivated fullscreen windows, but leave
    // regular windows running normally

   if(!_window_active || _exiting_window || _active_minimized_fullscreen) {
       #ifdef _DEBUG
          if(wdxdisplay_cat.is_spam())
            wdxdisplay_cat.spam()  << "deactivate_window called, but ignored in current mode\n";
       #endif
     return;
   }

   if(bResponsive_minimized_fullscreen_window) {
       if(wdxdisplay_cat.is_spam())
           wdxdisplay_cat.spam() << "WDX fullscreen window switching to active minimized mode...\n";
       _active_minimized_fullscreen = true;
   } else {

       if(wdxdisplay_cat.is_spam())
           wdxdisplay_cat.spam() << "WDX window deactivated, waiting...\n";

       _window_active = false;
   }

   if(_props._fullscreen) {
       // make sure window is minimized

       WINDOWPLACEMENT wndpl;
       wndpl.length=sizeof(WINDOWPLACEMENT);
       if(!GetWindowPlacement(_dxgsg->scrn.hWnd,&wndpl)) {
          wdxdisplay_cat.error() << "GetWindowPlacement failed!\n";
          return;
       }

       if((wndpl.showCmd!=SW_MINIMIZE)&&(wndpl.showCmd!=SW_SHOWMINIMIZED)) {
          ShowWindow(_dxgsg->scrn.hWnd, SW_MINIMIZE);
       }

       throw_event("PandaPaused"); // right now this is used to signal python event handler to disable audio
   }

//   if(!bResponsive_minimized_fullscreen_window) {
   // need this even in responsive-mode to trigger the dxgsg check of cooplvl, i think?

       _PandaPausedTimer = SetTimer(_dxgsg->scrn.hWnd,PAUSED_TIMER_ID,500,NULL);
       if(_PandaPausedTimer!=PAUSED_TIMER_ID) {
           wdxdisplay_cat.error() << "Error in SetTimer!\n";
       }
//   }
}

void wdxGraphicsWindow::reactivate_window(void) {
    if(!_window_active) {
    
        // first see if dx cooperative level is OK for reactivation
    //    if(!_dxgsg->CheckCooperativeLevel())
    //        return;
    
        if(wdxdisplay_cat.is_spam())
            wdxdisplay_cat.spam() << "WDX window re-activated...\n";
    
        _window_active = true;
    
        if(_PandaPausedTimer!=NULL) {
            KillTimer(_dxgsg->scrn.hWnd,_PandaPausedTimer);
            _PandaPausedTimer = NULL;
        }
    
        // move window to top of zorder
    //  if(_props._fullscreen)
    //      SetWindowPos(_DisplayDataArray[0].hWnd, HWND_TOP, 0,0,0,0, SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE | SWP_NOOWNERZORDER);
        GdiFlush();

    } else if(_active_minimized_fullscreen) {
        if(wdxdisplay_cat.is_spam())
            wdxdisplay_cat.spam() << "WDX window unminimized from active-minimized state...\n";
    
        if(_PandaPausedTimer!=NULL) {
            KillTimer(_dxgsg->scrn.hWnd,_PandaPausedTimer);
            _PandaPausedTimer = NULL;
        }

        _active_minimized_fullscreen = false;
    
        // move window to top of zorder
    //  if(_props._fullscreen)
    //      SetWindowPos(_DisplayDataArray[0].hWnd, HWND_TOP, 0,0,0,0, SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE | SWP_NOOWNERZORDER);
        GdiFlush();
    }

    if(_props._fullscreen) {
        throw_event("PandaRestarted");  // right now this is used to signal python event handler to re-enable audio
    }
}

////////////////////////////////////////////////////////////////////
//     Function: Constructor
//       Access:
//  Description:
////////////////////////////////////////////////////////////////////
wdxGraphicsWindow::wdxGraphicsWindow(GraphicsPipe* pipe) : GraphicsWindow(pipe) {
   _ime_active = false;
   _pParentWindowGroup=NULL;
   _pParentWindowGroup=new wdxGraphicsWindowGroup(this);
}

////////////////////////////////////////////////////////////////////
//     Function: Constructor
//       Access:
//  Description:
////////////////////////////////////////////////////////////////////
wdxGraphicsWindow::wdxGraphicsWindow(GraphicsPipe* pipe, const GraphicsWindow::Properties& props) 
                  : GraphicsWindow(pipe, props) {
   _ime_open = false;
   _pParentWindowGroup=NULL;
   _pParentWindowGroup=new wdxGraphicsWindowGroup(this);
}

bool supports_color_cursors(DDDEVICEIDENTIFIER2 &DevID) {
    // TODO: add more cards as more testing is done
    if(IS_NVIDIA(DevID)) {    
        // all nvidia seem to support 256 color
        return true;
    } else if(IS_ATI(DevID)) {
        // radeons seem to be in the 5100 range and support color, assume anything in 6000 or above 
        // is newer than radeon and supports 256 color
        if(((DevID.dwDeviceId>=0x5100) && (DevID.dwDeviceId<=0x5200)) ||
           (DevID.dwDeviceId>=0x6000))
            return true;
    } else if IS_MATROX(DevID) {
        if(DevID.dwDeviceId==0x0525)   // G400 seems to support color cursors, havent tested other matrox
            return true;
    }

    return false;
}

void wdxGraphicsWindowGroup::CreateWindows(void) {
    HINSTANCE hProgramInstance = GetModuleHandle(NULL);
    WNDCLASS wc;
    static bool wc_registered = false;
    _hParentWindow = NULL;        

    // these fns arent defined on win95, so get dynamic ptrs to them to avoid
    // ugly DLL loader failures on w95
    HINSTANCE hUser32 = (HINSTANCE) LoadLibrary("user32.dll");
    assert(hUser32);

    _pfnGetMonitorInfo = (PFN_GETMONITORINFO) GetProcAddress(hUser32, "GetMonitorInfoA");
    _pfnTrackMouseEvent = (PFN_TRACKMOUSEEVENT) GetProcAddress(hUser32, "TrackMouseEvent");
    FreeLibrary(hUser32);

    // Clear before filling in window structure!
    ZeroMemory(&wc, sizeof(WNDCLASS));
    wc.style      = CS_HREDRAW | CS_VREDRAW; //CS_OWNDC;
    wc.lpfnWndProc    = (WNDPROC) static_window_proc;
    wc.hInstance      = hProgramInstance;

    string windows_icon_filename = get_icon_filename().to_os_specific();
    string windows_mono_cursor_filename = get_mono_cursor_filename().to_os_specific();
    string windows_color_cursor_filename = get_color_cursor_filename().to_os_specific();

    if(!windows_icon_filename.empty()) {
        // Note: LoadImage seems to cause win2k internal heap corruption (outputdbgstr warnings)
        // if icon is more than 8bpp

        // loads a .ico fmt file
        wc.hIcon = (HICON) LoadImage(NULL, windows_icon_filename.c_str(), IMAGE_ICON, 0, 0, LR_LOADFROMFILE );

        if(wc.hIcon==NULL) {
            wdxdisplay_cat.warning() << "windows icon filename '" << windows_icon_filename << "' not found!!\n";
        }
    } else {
        wc.hIcon = NULL; // use default app icon
    }

    _bLoadedCustomCursor=false;
    _hMouseCursor=NULL;

    if(!windows_color_cursor_filename.empty()) {
        // card support for full color non-black/white GDI cursors varies greatly.  if the cursor is not supported,
        // it is rendered in software by GDI, which causes a flickering effect (because it's not synced 
        // with flip?).  GDI transparently masks the lack of HW support so there is no easy way for app to detect
        // if HW cursor support exists.  alternatives are to tie cursor motion to frame rate using DDraw blts
        // or overlays (this is done automatically by DX8 runtime mouse cursor support), or to have separate thread draw cursor 
        // (sync issues?).  instead we do mono cursor  unless card is known to support 256 color cursors
        bool bSupportsColorCursor=true;
    
        /* if any card doesnt support color, dont load it*/
        for(DWORD w=0;w<_windows.size();w++)
            bSupportsColorCursor &= supports_color_cursors(_windows[w]->_dxgsg->scrn.DXDeviceID);
    
        if(bSupportsColorCursor) {
            DWORD load_flags = LR_LOADFROMFILE;
    
            if(dx_full_screen) {
                // I think cursors should use LR_CREATEDIBSECTION since they should not be mapped to the device palette (in the case of 256-color cursors)
                // since they are not going to be used on the desktop
                load_flags |= LR_CREATEDIBSECTION;
            }
    
            // Note: LoadImage seems to cause win2k internal heap corruption (outputdbgstr warnings)
            // if icon is more than 8bpp

            // loads a .cur fmt file. 
            _hMouseCursor = (HCURSOR) LoadImage(NULL, windows_color_cursor_filename.c_str(), IMAGE_CURSOR, 0, 0, load_flags );
    
            if(_hMouseCursor==NULL) {
                wdxdisplay_cat.warning() << "windows color cursor filename '" << windows_color_cursor_filename << "' not found!!\n";
                goto try_mono_cursor;

            }

/*          dont need these anymore since we are do mousestuff before window creation    
            SetClassLongPtr(_mwindow, GCLP_HCURSOR, (LONG_PTR) hNewMouseCursor);
            SetCursor(hNewMouseCursor);
    
            if(_bLoadedCustomCursor)
               DestroyCursor(_hMouseCursor);
*/             
            _bLoadedCustomCursor=true;
        }
    }

    try_mono_cursor:

    if((!_bLoadedCustomCursor) && (!windows_mono_cursor_filename.empty())) {
        // Note: LoadImage seems to cause win2k internal heap corruption (outputdbgstr warnings)
        // if icon is more than 8bpp

        DWORD load_flags = LR_LOADFROMFILE;

        if(dx_full_screen) {
            // I think cursors should use LR_CREATEDIBSECTION since they should not be mapped to the device palette (in the case of 256-color cursors)
            // since they are not going to be used on the desktop
            load_flags |= LR_CREATEDIBSECTION;
        }
        // loads a .cur fmt file. 
        _hMouseCursor = (HCURSOR) LoadImage(NULL, windows_mono_cursor_filename.c_str(), IMAGE_CURSOR, 0, 0, load_flags);

        if(_hMouseCursor==NULL) {
            wdxdisplay_cat.warning() << "windows mono cursor filename '" << windows_mono_cursor_filename << "' not found!!\n";
        } else _bLoadedCustomCursor=true;
    }

    if(!_bLoadedCustomCursor) 
      _hMouseCursor = LoadCursor(NULL, DEFAULT_CURSOR);

    if (!wc_registered) {
      // We only need to register the window class once per session.
      wc.hCursor = _hMouseCursor;
      wc.hbrBackground  = (HBRUSH)GetStockObject(BLACK_BRUSH);
      wc.lpszMenuName   = NULL;
      wc.lpszClassName  = WDX_WINDOWCLASSNAME;
      
      if(!RegisterClass(&wc)) {
        wdxdisplay_cat.error() << "could not register window class!" << endl;
      }
      wc_registered = true;
    }

    DWORD window_style = WS_POPUP | WS_SYSMENU;  // for CreateWindow

    global_wdxwinptr = _windows[0];  // for use during createwin()  bugbug look at this again

    // rect now contains the coords for the entire window, not the client
    if(dx_full_screen) {

        // extra windows must be parented to the first so app doesnt minimize when user selects them
        for(DWORD devnum=0;devnum<_windows.size();devnum++) {
            MONITORINFO minfo;
            ZeroMemory(&minfo, sizeof(MONITORINFO));
            minfo.cbSize = sizeof(MONITORINFO);
            if(_pfnGetMonitorInfo!=NULL)
                // get upper-left corner coords using GetMonitorInfo
                (*_pfnGetMonitorInfo)(_windows[devnum]->_dxgsg->scrn.hMon, &minfo);
             else {
                 minfo.rcMonitor.left = minfo.rcMonitor.top = 0;
             }

            GraphicsWindow::Properties *props = &_windows[devnum]->_props;

            wdxdisplay_cat.info() << "opening " << props->_xsize << "x" << props->_ysize << " fullscreen window\n";

            HWND hWin = CreateWindow(WDX_WINDOWCLASSNAME, props->_title.c_str(),
                                      window_style, minfo.rcMonitor.left, minfo.rcMonitor.top,
                                      props->_xsize,props->_ysize,
                                      _hParentWindow, NULL, hProgramInstance, 0);

            if(!hWin) {
                wdxdisplay_cat.fatal() << "CreateWindow failed for monitor " << devnum << "!, LastError=" << GetLastError() << endl;
                #ifdef _DEBUG
                   PrintErrorMessage(LAST_ERROR);
                #endif
                exit(1);
            }

            _windows[devnum]->set_window_handle(hWin);
            if(devnum==0) {
                _hParentWindow=hWin;
            }
        }
    } else {
        assert(_windows.size()==1);

        GraphicsWindow::Properties *props = &_windows[0]->_props;

        RECT win_rect;
        SetRect(&win_rect, props->_xorg,  props->_yorg, props->_xorg + props->_xsize,
                props->_yorg + props->_ysize);

        if(props->_border)
            window_style |= WS_OVERLAPPEDWINDOW;  // should we just use WS_THICKFRAME instead?

        AdjustWindowRect(&win_rect, window_style, FALSE);  //compute window size based on desired client area size

        // make sure origin is on screen
        if(win_rect.left < 0) {
            win_rect.right -= win_rect.left; win_rect.left = 0;
        }
        if(win_rect.top < 0) {
            win_rect.bottom -= win_rect.top; win_rect.top = 0;
        }

        UINT xsize = win_rect.right-win_rect.left;
        UINT ysize = win_rect.bottom-win_rect.top;

        wdxdisplay_cat.info() << "opening " << props->_xsize << "x" << props->_ysize << " regular window\n";

        _hParentWindow =
            CreateWindow(WDX_WINDOWCLASSNAME, props->_title.c_str(),
                         window_style, win_rect.left, win_rect.top, xsize,ysize,
                         NULL, NULL, hProgramInstance, 0);
        _windows[0]->set_window_handle(_hParentWindow);
    }

    if(_hParentWindow==NULL) {
        wdxdisplay_cat.fatal() << "CreateWindow failed!\n";
        exit(1);
    }

    for(DWORD devnum=0;devnum<_windows.size();devnum++) {
        wdxGraphicsWindow *pWDXWin = _windows[devnum];
        // for use by the window_proc
        hwnd_pandawin_map[pWDXWin->_dxgsg->scrn.hWnd] = pWDXWin;

/*
        // DC is mostly used for writing fps meter (but also old palette code, which needs updating)
        // probably only need to do this for devnum 0
        HDC hdc = GetDC(pWDXWin->_dxgsg_>scrn.hWnd);
        pWDXWin->_dxgsg->Set_HDC(hdc);
*/      
    }

    // now we can stop using the global_wdxwinptr crutch since windows are created
    global_wdxwinptr = NULL;  // get rid of any reference to this obj
}

////////////////////////////////////////////////////////////////////
//     Function: config
//       Access:
//  Description:  Set up win32 window.
////////////////////////////////////////////////////////////////////
void wdxGraphicsWindow::config_window(wdxGraphicsWindowGroup *pParentGroup) {
//    assert(_pParentWindowGroup==NULL);
    _pParentWindowGroup=pParentGroup;

    GraphicsWindow::config();

    _hdc = NULL;
    _gsg = _dxgsg = NULL;
    _exiting_window = false;
    _window_active = true;
    _return_control_to_app = false;
    _active_minimized_fullscreen = false;

    if(dx_full_screen || _props._fullscreen) {
        _props._fullscreen = dx_full_screen = true;
    }

    _WindowAdjustingType = NotAdjusting;
    _bSizeIsMaximized=FALSE;

    _gsg = _dxgsg = NULL;
    // Create a GSG to manage the graphics
    if(_gsg==NULL) {
        make_gsg();
        if(_gsg==NULL) {
            wdxdisplay_cat.error() << "make_gsg() failed!\n";
            exit(1);
        }
    }
    _dxgsg = DCAST(DXGraphicsStateGuardian, _gsg);

  // Check the version of the OS we are running.  If we are running
  // win2000, we must use ImmGetCompositionStringW() to report the
  // characters returned by the IME, since WM_CHAR and
  // ImmGetCompositionStringA() both just return question marks.
  // However, this function doesn't work for Win98; on this OS, we
  // have to use ImmGetCompositionStringA() instead, which returns an
  // encoded string in shift-jis (which we then have to decode).
  
  // For now, this is user-configurable, to allow testing of this code
  // on both OS's.  After we verify that truth of the above claim, we
  // should base this decision on GetVersionEx() or maybe
  // VerifyVersionInfo().
  _ime_composition_w = ime_composition_w;
}

void wdxGraphicsWindow::finish_window_setup(void) {
    // Now indicate that we have our keyboard/mouse device ready.
    GraphicsWindowInputDevice device = GraphicsWindowInputDevice::pointer_and_keyboard("keyboard/mouse");
    _input_devices.push_back(device);

    // move windows to top of zorder
    HWND hWin = _dxgsg->scrn.hWnd;

    SetWindowPos(hWin, HWND_TOP, 0,0,0,0, SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE);
    // call twice to override STARTUPINFO value, which may be set to hidden initially (by emacs for instance)
    ShowWindow(hWin, SW_SHOWNORMAL);
    ShowWindow(hWin, SW_SHOWNORMAL);  
    //  UpdateWindow( _mwindow );
}



HRESULT CALLBACK EnumDevicesCallback(LPSTR pDeviceDescription, LPSTR pDeviceName,
                                     LPD3DDEVICEDESC7 pD3DDeviceDesc,LPVOID pContext) {
    D3DDEVICEDESC7 *pd3ddevs = (D3DDEVICEDESC7 *)pContext;
#ifdef _DEBUG
    wdxdisplay_cat.spam() << "Enumerating Device " << pDeviceName << " : " << pDeviceDescription << endl;
#endif


#define REGHALIDX 0
#define TNLHALIDX 1
#define SWRASTIDX 2

    if(IsEqualGUID(pD3DDeviceDesc->deviceGUID,IID_IDirect3DHALDevice)) {
        CopyMemory(&pd3ddevs[REGHALIDX],pD3DDeviceDesc,sizeof(D3DDEVICEDESC7));
    } else if(IsEqualGUID(pD3DDeviceDesc->deviceGUID,IID_IDirect3DTnLHalDevice)) {
        CopyMemory(&pd3ddevs[TNLHALIDX],pD3DDeviceDesc,sizeof(D3DDEVICEDESC7));
    } else if(IsEqualGUID(pD3DDeviceDesc->deviceGUID,IID_IDirect3DRGBDevice)) {
        CopyMemory(&pd3ddevs[SWRASTIDX],pD3DDeviceDesc,sizeof(D3DDEVICEDESC7));
    }
    return DDENUMRET_OK;
}

#define MAX_DISPLAY_MODES 100  // probably dont need this much, since i already screen for width&hgt
typedef struct {
  DWORD maxWidth,maxHeight;
  DWORD supportedBitDepths;    // uses DDBD_* flags
  LPDDSURFACEDESC2 pDDSD_Arr;
  DWORD cNumSurfDescs;
} DisplayModeInfo;

HRESULT WINAPI EnumDisplayModesCallBack(LPDDSURFACEDESC2 lpDDSurfaceDesc,LPVOID lpContext) {
    DisplayModeInfo *pDMI = (DisplayModeInfo *) lpContext;

    // ddsd_search should assure this is true
    assert((lpDDSurfaceDesc->dwWidth == pDMI->maxWidth) && (lpDDSurfaceDesc->dwHeight == pDMI->maxHeight));

    // ignore refresh rates under 60Hz (and special values of 0 & 1)
    if((lpDDSurfaceDesc->dwRefreshRate>1) && (lpDDSurfaceDesc->dwRefreshRate<60))
      return DDENUMRET_OK;

    assert(pDMI->cNumSurfDescs < MAX_DISPLAY_MODES);
    memcpy( &(pDMI->pDDSD_Arr[pDMI->cNumSurfDescs]), lpDDSurfaceDesc, sizeof(DDSURFACEDESC2) );
    pDMI->cNumSurfDescs++;
    pDMI->supportedBitDepths |= BitDepth_2_DDBDMask(lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount);

    return DDENUMRET_OK;
}

/*
BOOL WINAPI DriverEnumCallback_Voodoo1( GUID* pGUID, TCHAR* strDesc,TCHAR* strName,
                                VOID *argptr, HMONITOR hm) {

    if(hm!=NULL)  // skip over non-primary and non-voodoo-type display devices
        return DDENUMRET_OK;

    GUID null_guid;
    ZeroMemory(&null_guid,sizeof(GUID));

    // primary display driver will have NULL guid
    // ignore that and save any non-null value, which
    // indicates a secondary driver, which is usually voodoo1/2
    if((pGUID!=NULL) && !IsEqualGUID(null_guid,*pGUID)) {
        memcpy(argptr,pGUID,sizeof(GUID));
    }

    return DDENUMRET_OK;
}
*/

BOOL WINAPI save_devinfo( GUID* pGUID, TCHAR* strDesc,TCHAR* strName,VOID *argptr, HMONITOR hm) {

    DXDeviceInfoVec *pDevInfoArr = (DXDeviceInfoVec *) argptr;
   
    DXDeviceInfo devinfo;
    ZeroMemory(&devinfo,sizeof(devinfo));

    // primary display driver will have NULL guid
    if(pGUID!=NULL) {
        memcpy(&devinfo.guidDeviceIdentifier,pGUID,sizeof(GUID));
    }
    if(strDesc!=NULL) {
        _tcsncpy(devinfo.szDescription,
            strDesc,
            MAX_DDDEVICEID_STRING);
    }
    if(strName!=NULL) {
        _tcsncpy(devinfo.szDriver,strName,MAX_DDDEVICEID_STRING);
    }
    devinfo.hMon=hm;

    pDevInfoArr->push_back(devinfo);
    return DDENUMRET_OK;
}

BOOL WINAPI DriverEnumCallback_MultiMon( GUID* pGUID, TCHAR* strDesc,TCHAR* strName,VOID *argptr, HMONITOR hm) {
    if(hm==NULL) {
         // skip over the 'primary' since it will duplicated later as an explicit device
        return DDENUMRET_OK;
    }

    return save_devinfo(pGUID,strDesc,strName,argptr,hm);
}

bool wdxGraphicsWindow::resize(unsigned int xsize,unsigned int ysize) {

   if (!_props._fullscreen) {
       if(wdxdisplay_cat.is_debug())
          wdxdisplay_cat.debug() << "resize("<<xsize<<","<<ysize<<") called\n";

        // is this enough?
        SetWindowPos(_dxgsg->scrn.hWnd, NULL, 0,0, xsize, ysize, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSENDCHANGING);
        // WM_ERASEBKGND will be ignored, because _WindowAdjustingType!=NotAdjusting because 
        // we dont want to redraw as user is manually resizing window, so need to force explicit
        // background clear for the programmatic resize fn call
         _WindowAdjustingType=NotAdjusting;
        
         // this doesnt seem to be working in toontown resize, so I put ddraw blackblt in handle_reshape instead
         //window_proc(_mwindow, WM_ERASEBKGND,(WPARAM)_hdc,0x0);  
        handle_reshape(true);
        return true;
   }

   if(wdxdisplay_cat.is_info())
      wdxdisplay_cat.info() << "resize("<<xsize<<","<<ysize<<") called\n";

   _dxgsg->SetDXReady(false);

   HRESULT hr;

   DX_DECLARE_CLEAN(DDSURFACEDESC2,ddsd_curmode);

   if(FAILED(hr = _dxgsg->scrn.pDD->GetDisplayMode(&ddsd_curmode))) {
       wdxdisplay_cat.fatal() << "resize() - GetDisplayMode failed, result = " << ConvD3DErrorToString(hr) << endl;
       exit(1);
   }

   DX_DECLARE_CLEAN(DDSURFACEDESC2,ddsd_search);

   ddsd_search.dwFlags = DDSD_HEIGHT | DDSD_WIDTH;
   ddsd_search.dwWidth=xsize;  ddsd_search.dwHeight=ysize;

   // not requesting same refresh rate since changing res might not support same refresh rate

   DDSURFACEDESC2 DDSD_Arr[MAX_DISPLAY_MODES];
   DisplayModeInfo DMI;
   ZeroMemory(&DDSD_Arr,sizeof(DDSD_Arr));
   ZeroMemory(&DMI,sizeof(DMI));
   DMI.maxWidth=xsize;  DMI.maxHeight=ysize;
   DMI.pDDSD_Arr=DDSD_Arr;

   if(FAILED(hr = _dxgsg->scrn.pDD->EnumDisplayModes(DDEDM_REFRESHRATES,&ddsd_search,&DMI,EnumDisplayModesCallBack))) {
       wdxdisplay_cat.fatal() << "resize() - EnumDisplayModes failed, result = " << ConvD3DErrorToString(hr) << endl;
       return false;
   }

   DMI.supportedBitDepths &= _dxgsg->scrn.D3DDevDesc.dwDeviceRenderBitDepth;

   DWORD dwFullScreenBitDepth;
   DWORD requested_bpp=ddsd_curmode.ddpfPixelFormat.dwRGBBitCount;

   // would like to match current bpp first.  if that is not possible, try 16bpp, then 32
   DWORD requested_bpp_DDBD = BitDepth_2_DDBDMask(requested_bpp);

   if(DMI.supportedBitDepths & requested_bpp_DDBD) {
       dwFullScreenBitDepth=requested_bpp;
   } else if(DMI.supportedBitDepths & DDBD_16) {
       dwFullScreenBitDepth=16;
   } else if(DMI.supportedBitDepths & DDBD_32) {
       dwFullScreenBitDepth=32;
   } else {
       wdxdisplay_cat.error()
          << "resize failed, no fullScreen resolutions at " << xsize << "x" << ysize << endl;
       return false;
   }

   if(FAILED(hr = _dxgsg->scrn.pDD->TestCooperativeLevel())) {
        wdxdisplay_cat.error() << "TestCooperativeLevel failed : result = " << ConvD3DErrorToString(hr) << endl;
        wdxdisplay_cat.error() << "Full screen app failed to get exclusive mode on resize, exiting..\n";
        return false;
   }

   _dxgsg->free_dxgsg_objects();

   // let driver choose default refresh rate (hopefully its >=60Hz)   
   if(FAILED( hr = _dxgsg->scrn.pDD->SetDisplayMode( xsize,ysize,dwFullScreenBitDepth, 0L, 0L ))) {
        wdxdisplay_cat.error() << "resize failed to reset display mode to (" << xsize <<"x"<<ysize<<"x"<<dwFullScreenBitDepth<<"): result = " << ConvD3DErrorToString(hr) << endl;
   }

   if(wdxdisplay_cat.is_debug()) {
      DX_DECLARE_CLEAN(DDSURFACEDESC2,ddsd34); 
      _dxgsg->scrn.pDD->GetDisplayMode(&ddsd34);
      wdxdisplay_cat.debug() << "set displaymode to " << ddsd34.dwWidth << "x" << ddsd34.dwHeight << " at "<< ddsd34.ddpfPixelFormat.dwRGBBitCount << "bpp, " << ddsd34.dwRefreshRate<< "Hz\n";
   }

   _dxgsg->scrn.dwRenderWidth=xsize;
   _dxgsg->scrn.dwRenderHeight=ysize;

   CreateScreenBuffersAndDevice(_dxgsg->scrn);
   _dxgsg->RecreateAllVideoSurfaces();
   _dxgsg->SetDXReady(true);
   return true;
}


// overrides of the general estimator for known working cases
bool wdxGraphicsWindow::
special_check_fullscreen_resolution(UINT xsize,UINT ysize) {
    // assert(IS_VALID_PTR(_dxgsg));

    DWORD VendorId=_dxgsg->scrn.DXDeviceID.dwVendorId;
    DWORD DeviceId=_dxgsg->scrn.DXDeviceID.dwDeviceId;
    switch(VendorId) {
        case 0x8086:  // Intel
            /*for now, just validate all the intel cards at these resolutions.
              I dont have a complete list of deviceIDs (missing 82830, 845, etc)
            // Intel i810,i815,82810
            if((DeviceId==0x7121)||(DeviceId==0x7123)||(DeviceId==0x7125)||
               (DeviceId==0x1132)) 
             */
            {
                if((xsize==640)&&(ysize==480))
                    return true;
                if((xsize==800)&&(ysize==600))
                    return true;
                if((xsize==1024)&&(ysize==768))
                    return true;
            }
            break;
    }

    return false;
}

unsigned int wdxGraphicsWindow::
verify_window_sizes(unsigned int numsizes,unsigned int *dimen) {
   DWORD num_valid_modes=0;
   HRESULT hr;

   // not requesting same refresh rate since changing res might not support same refresh rate at new size

   DDSURFACEDESC2 DDSD_Arr[MAX_DISPLAY_MODES];
   DisplayModeInfo DMI;
   DWORD i,*pCurDim=(DWORD *)dimen;

   for(i=0;i<numsizes;i++,pCurDim+=2) {
      DWORD xsize=pCurDim[0];
      DWORD ysize=pCurDim[1];

       DX_DECLARE_CLEAN(DDSURFACEDESC2,ddsd_search);
       ddsd_search.dwFlags = DDSD_HEIGHT | DDSD_WIDTH; //| DDSD_PIXELFORMAT;
       ddsd_search.dwWidth=xsize; ddsd_search.dwHeight=ysize;
    
       ZeroMemory(&DDSD_Arr,sizeof(DDSD_Arr));
       ZeroMemory(&DMI,sizeof(DMI));
       DMI.maxWidth=xsize;  DMI.maxHeight=ysize;
       DMI.pDDSD_Arr=DDSD_Arr;
    
       if(FAILED(hr = _dxgsg->scrn.pDD->EnumDisplayModes(DDEDM_REFRESHRATES,&ddsd_search,&DMI,EnumDisplayModesCallBack))) {
           wdxdisplay_cat.fatal() << "resize() - EnumDisplayModes failed, result = " << ConvD3DErrorToString(hr) << endl;
           return 0;
       }
    
       // get rid of bpp's we cant render at
       DMI.supportedBitDepths &= _dxgsg->scrn.D3DDevDesc.dwDeviceRenderBitDepth;

       bool bIsGoodMode=false;

       if(DMI.supportedBitDepths & (DDBD_16 | DDBD_24 | DDBD_32)) {
           if(special_check_fullscreen_resolution(xsize,ysize)) {
               // bypass the test below for certain cards we know have valid modes
               bIsGoodMode=true;
           } else {
               if(_dxgsg->scrn.bIsLowVidMemCard) 
                   bIsGoodMode=(((float)xsize*(float)ysize)<=(float)(640*480));
                 else {
                     // assume user is testing fullscreen, not windowed, so use the dwTotal value
                     // see if 3 scrnbufs (front/back/z)at 16bpp at xsize*ysize will fit with a few 
                     // extra megs for texmem
        
                     // 8MB Rage Pro says it has 6.8 megs Total free and will run at 1024x768, so
                     // formula makes it so that is OK
        
                     #define REQD_TEXMEM 1800000.0f  
        
                     if(_dxgsg->scrn.MaxAvailVidMem==0) {
                         //assume buggy drivers return bad val of 0 and everything will be OK
                         bIsGoodMode=true;
                     } else {
                         bIsGoodMode = ((((float)xsize*(float)ysize)*6+REQD_TEXMEM) < (float)_dxgsg->scrn.MaxAvailVidMem);
                     }
             }
           }
       }

       if(bIsGoodMode)
         num_valid_modes++;
        else {
            pCurDim[0] = 0;
            pCurDim[1] = 0;
        }
   }

   return num_valid_modes;
}

/*
void wdxGraphicsWindow::
check_for_color_cursor_support(void) {
    // card support for non-black/white GDI cursors varies greatly.  if the cursor is not supported,
    // it is rendered in software by GDI, which causes a flickering effect (because it's not synced 
    // with flip?).  GDI transparently masks what's happening so there is no easy way for app to detect
    // if HW cursor support exists.  alternatives are to tie cursor motion to frame rate using DDraw blts
    // or overlays, or to have separate thread draw cursor (sync issues?).  instead we do mono cursor 
    // unless card is known to support 256 color cursors

    string windows_color_cursor_filename = get_color_cursor_filename().to_os_specific();
    if(windows_color_cursor_filename.empty())
       return;

    bool bSupportsColorCursor=false;

    if(IS_NVIDIA(_DXDeviceID)) {    
        // all nvidia seem to support 256 color
        bSupportsColorCursor=true;
    } else if(IS_ATI(_DXDeviceID)) {
        // radeons seem to be in the 5100 range and support color, assume anything in 6000 or above 
        // is newer than radeon and supports 256 color
        if(((_DXDeviceID.dwDeviceId>=0x5100) && (_DXDeviceID.dwDeviceId<=0x5200)) ||
           (_DXDeviceID.dwDeviceId>=0x6000))
            bSupportsColorCursor=true;
    } else if IS_MATROX(_DXDeviceID) {
        if(_DXDeviceID.dwDeviceId==0x0525)   // G400 seems to support color cursors, havent tested other matrox
            bSupportsColorCursor=true;
    }

    // TODO: add more cards as more testing is done

    if(bSupportsColorCursor) {
        // Note: LoadImage seems to cause win2k internal heap corruption (outputdbgstr warnings)
        // if icon is more than 8bpp

        DWORD load_flags = LR_LOADFROMFILE;

        if(dx_full_screen) {
            // I think cursors should use LR_CREATEDIBSECTION since they should not be mapped to the device palette (in the case of 256-color cursors)
            // since they are not going to be used on the desktop
            load_flags |= LR_CREATEDIBSECTION;
        }

        // loads a .cur fmt file. 
        HCURSOR hNewMouseCursor = (HCURSOR) LoadImage(NULL, windows_color_cursor_filename.c_str(), IMAGE_CURSOR, 0, 0, load_flags );

        if(hNewMouseCursor==NULL) {
            wdxdisplay_cat.warning() << "windows color cursor filename '" << windows_color_cursor_filename << "' not found!!\n";
            return;
        }

        SetClassLongPtr(_mwindow, GCLP_HCURSOR, (LONG_PTR) hNewMouseCursor);
        SetCursor(hNewMouseCursor);

        if(_bLoadedCustomCursor)
           DestroyCursor(_hMouseCursor);
        _hMouseCursor = hNewMouseCursor;
    }
}
*/

// returns true if successful
bool wdxGraphicsWindow::search_for_device(int devnum,DXDeviceInfo *pDevinfo) {
    DWORD dwRenderWidth  = _props._xsize;
    DWORD dwRenderHeight = _props._ysize;
    LPDIRECTDRAW7 pDD=NULL;
    HRESULT hr;

    assert(_dxgsg!=NULL);

    GUID *pDDDeviceGUID;
    if(pDevinfo==NULL)
       pDDDeviceGUID=NULL;
     else pDDDeviceGUID=&pDevinfo->guidDeviceIdentifier;

    assert(_pParentWindowGroup->_pDDCreateEx!=NULL);

    // Create the Direct Draw Objects
    hr = (*(_pParentWindowGroup->_pDDCreateEx))(pDDDeviceGUID,(void **)&pDD, IID_IDirectDraw7, NULL);
    if((hr != DD_OK)||(pDD==NULL)) {
          wdxdisplay_cat.fatal() << "DirectDrawCreateEx failed for monitor("<<devnum<< "): result = " << ConvD3DErrorToString(hr) << endl;
          return false;
    }

    _dxgsg->scrn.pDD=pDD;

    //GetDeviceID bug writes an extra 4 bytes, so need xtra space
    BYTE id_arr[sizeof(DDDEVICEIDENTIFIER2)+4];
    pDD->GetDeviceIdentifier((DDDEVICEIDENTIFIER2 *)&id_arr,0x0);

    memcpy(&_dxgsg->scrn.DXDeviceID,id_arr,sizeof(DDDEVICEIDENTIFIER2));

    if(wdxdisplay_cat.is_info()) {
       DDDEVICEIDENTIFIER2 *pDevID=&_dxgsg->scrn.DXDeviceID;
       wdxdisplay_cat.info() << "GfxCard: " << pDevID->szDescription <<  "; DriverFile: '" << pDevID->szDriver  
                             << "'; VendorID: 0x" <<  (void*)pDevID->dwVendorId 
                             << "; DeviceID: 0x" <<  (void*)pDevID->dwDeviceId 
                             << "; DriverVer: " 
                             << HIWORD(pDevID->liDriverVersion.HighPart) << "." 
                             << LOWORD(pDevID->liDriverVersion.HighPart) << "."
                             << HIWORD(pDevID->liDriverVersion.LowPart) << "." 
                             << LOWORD(pDevID->liDriverVersion.LowPart) << endl;
    }

    // Query DirectDraw for access to Direct3D
    hr = pDD->QueryInterface( IID_IDirect3D7, (VOID**)&_dxgsg->scrn.pD3D);
    if(hr != DD_OK) {
        wdxdisplay_cat.fatal() << "QI for D3D failed : result = " << ConvD3DErrorToString(hr) << endl;
        goto error_exit;
    }

    D3DDEVICEDESC7 d3ddevs[3];  // put HAL in 0, TnLHAL in 1, SW rast in 2

    // just look for HAL and TnL devices right now.  I dont think
    // we have any interest in the sw rasts at this point

    ZeroMemory(d3ddevs,3*sizeof(D3DDEVICEDESC7));

    hr = _dxgsg->scrn.pD3D->EnumDevices(EnumDevicesCallback,d3ddevs);
    if(hr != DD_OK) {
       wdxdisplay_cat.fatal() << "EnumDevices failed : result = " << ConvD3DErrorToString(hr) << endl;
        goto error_exit;
    }
    
    WORD DeviceIdx;

    // select TNL if present
    if(d3ddevs[TNLHALIDX].dwDevCaps & D3DDEVCAPS_HWRASTERIZATION) {
       DeviceIdx=TNLHALIDX;
    } else if(d3ddevs[REGHALIDX].dwDevCaps & D3DDEVCAPS_HWRASTERIZATION) {
       DeviceIdx=REGHALIDX;
    } else if(dx_allow_software_renderer || dx_force_software_renderer) {
       DeviceIdx=SWRASTIDX;      
    } else {
       wdxdisplay_cat.error() << "No 3D HW present on device #"<<devnum<<", skipping it... (" << _dxgsg->scrn.DXDeviceID.szDescription<<")\n";
       goto error_exit;
    }

    if(dx_force_software_renderer) {
       DeviceIdx=SWRASTIDX; 
    }
    
    memcpy(&_dxgsg->scrn.D3DDevDesc,&d3ddevs[DeviceIdx],sizeof(D3DDEVICEDESC7));

    _dxgsg->scrn.bIsTNLDevice=(DeviceIdx==TNLHALIDX);

    // Get Current VidMem avail.  Note this is only an estimate, when we switch to fullscreen
    // mode from desktop, more vidmem will be available (typically 1.2 meg).  I dont want
    // to switch to fullscreen more than once due to the annoying monitor flicker, so try
    // to figure out optimal mode using this estimate
    DDSCAPS2 ddsGAVMCaps;
    DWORD dwVidMemTotal,dwVidMemFree;
    dwVidMemTotal=dwVidMemFree=0;
    ZeroMemory(&ddsGAVMCaps,sizeof(DDSCAPS2));
    ddsGAVMCaps.dwCaps = DDSCAPS_VIDEOMEMORY | DDSCAPS_LOCALVIDMEM;  // dont count AGP mem!
    if(FAILED(hr = pDD->GetAvailableVidMem(&ddsGAVMCaps,&dwVidMemTotal,&dwVidMemFree))) {
       wdxdisplay_cat.error() << "GetAvailableVidMem failed for device #"<<devnum<<": result = " << ConvD3DErrorToString(hr) << endl;
       // goto skip_device;
       exit(1);  // probably want to exit, since it may be my fault
    }
    
    // after SetDisplayMode, GetAvailVidMem totalmem seems to go down by 1.2 meg (contradicting above
    // comment and what I think would be correct behavior (shouldnt FS mode release the desktop vidmem?),
    // so this is the true value
    _dxgsg->scrn.MaxAvailVidMem = dwVidMemTotal;
    
    #define LOWVIDMEMTHRESHOLD 5500000
    #define CRAPPY_DRIVER_IS_LYING_VIDMEMTHRESHOLD 1000000     // every vidcard we deal with should have at least 1MB
    
    // assume buggy drivers (this means you, FireGL2) may return zero for dwVidMemTotal, so ignore value if its < CRAPPY_DRIVER_IS_LYING_VIDMEMTHRESHOLD
    _dxgsg->scrn.bIsLowVidMemCard = ((dwVidMemTotal>CRAPPY_DRIVER_IS_LYING_VIDMEMTHRESHOLD) && (dwVidMemTotal< LOWVIDMEMTHRESHOLD));   

    if(!dx_do_vidmemsize_check) {
       _dxgsg->scrn.MaxAvailVidMem = 0xFFFFFFFF;
       _dxgsg->scrn.bIsLowVidMemCard = false; 
    }

    if(DeviceIdx==SWRASTIDX) {
        // this will force 640x480x16, is this what we want for all sw rast?
        _dxgsg->scrn.bIsLowVidMemCard = true; 
        _dxgsg->scrn.bIsSWRast = true; 
        dx_force_16bpp_zbuffer = true;
    }

    if(dx_full_screen) {
        _props._xorg = _props._yorg = 0;
        
        DX_DECLARE_CLEAN(DDSURFACEDESC2,ddsd_search);
        ddsd_search.dwFlags = DDSD_HEIGHT | DDSD_WIDTH;
        ddsd_search.dwWidth=dwRenderWidth;  ddsd_search.dwHeight=dwRenderHeight;
        
        DDSURFACEDESC2 DDSD_Arr[MAX_DISPLAY_MODES];
        DisplayModeInfo DMI;
        ZeroMemory(&DDSD_Arr,sizeof(DDSD_Arr));
        ZeroMemory(&DMI,sizeof(DMI));
        DMI.maxWidth=dwRenderWidth;  DMI.maxHeight=dwRenderHeight;
        DMI.pDDSD_Arr=DDSD_Arr;
        
        if(FAILED(hr= pDD->EnumDisplayModes(DDEDM_REFRESHRATES,&ddsd_search,&DMI,EnumDisplayModesCallBack))) {
           wdxdisplay_cat.fatal() << "EnumDisplayModes failed for device #" << devnum << " (" << _dxgsg->scrn.DXDeviceID.szDescription<<"), result = " << ConvD3DErrorToString(hr) << endl;
           // goto skip_device;
           exit(1);  // probably want to exit, since it may be my fault
        }
        
        if(wdxdisplay_cat.is_info())
           wdxdisplay_cat.info() << "Before fullscreen switch: GetAvailableVidMem for device #"<<devnum<<" returns Total: " << dwVidMemTotal/1000000.0 << "  Free: " << dwVidMemFree/1000000.0 << endl;
        
        // Now we try to figure out if we can use requested screen resolution and best
        // rendertarget bpp and still have at least 2 meg of texture vidmem
        
        DMI.supportedBitDepths &= _dxgsg->scrn.D3DDevDesc.dwDeviceRenderBitDepth;

        DWORD dwFullScreenBitDepth;

        // note: this chooses 32bpp, which may not be preferred over 16 for memory & speed reasons
        if(DMI.supportedBitDepths & DDBD_32) {
           dwFullScreenBitDepth=32;              // go for 32bpp if its avail
        } else if(DMI.supportedBitDepths & DDBD_24) {
           dwFullScreenBitDepth=24;              // go for 24bpp if its avail
        } else if(DMI.supportedBitDepths & DDBD_16) {
           dwFullScreenBitDepth=16;              // do 16bpp
        } else {
           wdxdisplay_cat.fatal() << "No Supported FullScreen resolutions at " << dwRenderWidth << "x" << dwRenderHeight 
               << " for device #" << devnum << " (" << _dxgsg->scrn.DXDeviceID.szDescription<<"), skipping device...\n";
          goto error_exit;
        }
        
        if(_dxgsg->scrn.bIsLowVidMemCard) {
           {
               // hack: figuring out exactly what res to use is tricky, instead I will
               // just use 640x480 if we have < 3 meg avail
    
               dwFullScreenBitDepth=16; 
               dwRenderWidth=640;
               dwRenderHeight=480;
               dx_force_16bpptextures = true;
        
               if(wdxdisplay_cat.is_info())
                   wdxdisplay_cat.info() << "Available VidMem (" << dwVidMemFree<<") is under " << LOWVIDMEMTHRESHOLD <<", using 640x480 16bpp rendertargets to save tex vidmem.\n";
           }
        
               #if 0
                 /*
                   // cant use this method without more accurate way to estimate mem used before actually switching
                   // to that fullscrn mode.  simply computing memsize based on GetDisplayMode doesnt seem
                   // to be accurate within more than 1 meg
            
                           // we think we need to reserve at least 2 megs of vidmem for textures.
                           // to do this, reduce buffer bitdepth if possible
                   #define RESERVEDTEXVIDMEM 2000000
        
                   int rendertargetmem=dwRenderWidth*dwRenderHeight*(dwFullScreenBitDepth>>3);
                   int memleft = dwFree-rendertargetmem*2;   //*2 to handle backbuf/zbuf
        
                   if(memleft < RESERVEDTEXVIDMEM) {
                       dwFullScreenBitDepth=16;
                       wdxdisplay_cat.debug() << "using 16bpp rendertargets to save tex vidmem\n";
                       assert((DMI.supportedBitDepths & DDBD_16) && (pD3DDevDesc->dwDeviceRenderBitDepth & DDBD_16));   // probably a safe assumption
                       rendertargetmem=dwRenderWidth*dwRenderHeight*(dwFullScreenBitDepth>>3);
                       memleft = dwFree-rendertargetmem*2;
        
                        // BUGBUG:  if we still cant reserve 2 megs of vidmem, need to auto-reduce the scrn res
                       if(memleft < RESERVEDTEXVIDMEM)
                           wdxdisplay_cat.debug() << "XXXX WARNING: cant reserve 2MB of tex vidmem. only " << memleft << " bytes available. Need to rewrite wdxdisplay to try lower resolutions  XXXXXXXX\n";
                   }
                 */
               #endif
        }

        _dxgsg->scrn.dwFullScreenBitDepth=dwFullScreenBitDepth;
    }
    
    _dxgsg->scrn.dwRenderWidth=dwRenderWidth;
    _dxgsg->scrn.dwRenderHeight=dwRenderHeight;
    if(pDevinfo)
        _dxgsg->scrn.hMon=pDevinfo->hMon;
    _dxgsg->scrn.CardIDNum=devnum;  // add ID tag for dbgprint purposes

    return true;
    
    // handle errors within this for device loop
    
    error_exit:
       if(_dxgsg->scrn.pD3D!=NULL)
         _dxgsg->scrn.pD3D->Release();
       if(_dxgsg->scrn.pDD!=NULL)
         _dxgsg->scrn.pDD->Release();

       _dxgsg->scrn.pDD=NULL;
       _dxgsg->scrn.pD3D=NULL;
       return false;
}

void wdxGraphicsWindowGroup::
SetCoopLevelsAndDisplayModes(void) {
    HRESULT hr;
    DWORD SCL_FPUFlag;

    if(dx_preserve_fpu_state)
      SCL_FPUFlag = DDSCL_FPUPRESERVE;  // tell d3d to preserve the fpu state across calls.  this hurts perf, but is good for dbgging
    else SCL_FPUFlag = DDSCL_FPUSETUP;

    DXScreenData *pScrn=&_windows[0]->_dxgsg->scrn;
    // All SetCoopLevels must use the parent window

    if(!dx_full_screen) {
        if(FAILED(hr = pScrn->pDD->SetCooperativeLevel(_hParentWindow, SCL_FPUFlag | DDSCL_NORMAL))) {
            wdxdisplay_cat.fatal() << "SetCooperativeLevel failed : result = " << ConvD3DErrorToString(hr) << endl;
            exit(1);
        }
        return; 
    }
    
    DWORD SCL_FLAGS = SCL_FPUFlag | DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT;
    
    if(_windows.size()>1) {
       SCL_FLAGS |= DDSCL_SETDEVICEWINDOW;
    }

    for(DWORD devnum=0;devnum<_windows.size();devnum++) {
       DXScreenData *pScrn=&_windows[devnum]->_dxgsg->scrn;

       // need to set focus/device windows for multimon
       // focus window is primary monitor that will receive keybd input
       // all ddraw objs need to have same focus window
       if(_windows.size()>1) {    
           if(FAILED(hr = pScrn->pDD->SetCooperativeLevel(_hParentWindow, DDSCL_SETFOCUSWINDOW))) {
               wdxdisplay_cat.fatal() << "SetCooperativeLevel SetFocusWindow failed on device 0: result = " << ConvD3DErrorToString(hr) << endl;
               exit(1);
           }
       }

       // s3 savage2000 on w95 seems to set EXCLUSIVE_MODE only if you call SetCoopLevel twice.
       // so we do it, it really shouldnt be necessary if drivers werent buggy
       for(int jj=0;jj<2;jj++) {
           if(FAILED(hr = pScrn->pDD->SetCooperativeLevel(pScrn->hWnd, SCL_FLAGS))) {
               wdxdisplay_cat.fatal() << "SetCooperativeLevel failed for device #"<< devnum<<": result = " << ConvD3DErrorToString(hr) << endl;
               exit(1);
           }
       }
    
       if(FAILED(hr = pScrn->pDD->TestCooperativeLevel())) {
           wdxdisplay_cat.fatal() << "TestCooperativeLevel failed for device #"<< devnum<<": result = " << ConvD3DErrorToString(hr) << endl;
           wdxdisplay_cat.fatal() << "Full screen app failed to get exclusive mode on init, exiting..\n";
           exit(1);
       }
    
       // note: its important we call SetDisplayMode on all cards before creating surfaces on any of them
       // let driver choose default refresh rate (hopefully its >=60Hz)
       if(FAILED( hr = pScrn->pDD->SetDisplayMode( pScrn->dwRenderWidth, pScrn->dwRenderHeight,
                                            pScrn->dwFullScreenBitDepth, 0, 0 ))) {
           wdxdisplay_cat.fatal() << "SetDisplayMode failed to set ("<<pScrn->dwRenderWidth<<"x"<<pScrn->dwRenderHeight<<"x"<<pScrn->dwFullScreenBitDepth<<") on device #"<< pScrn->CardIDNum<<": result = " << ConvD3DErrorToString(hr) << endl;
           exit(1);
       }
    
       if(wdxdisplay_cat.is_debug()) {
          DX_DECLARE_CLEAN(DDSURFACEDESC2,ddsd34); 
          pScrn->pDD->GetDisplayMode(&ddsd34);
          wdxdisplay_cat.debug() << "set displaymode to " << ddsd34.dwWidth << "x" << ddsd34.dwHeight << " at "<< ddsd34.ddpfPixelFormat.dwRGBBitCount << "bpp, " << ddsd34.dwRefreshRate<< "Hz\n";
    
         /*
         #ifdef _DEBUG
          if(FAILED(hr = (*Disply).pDD->GetAvailableVidMem(&ddsGAVMCaps,&dwVidMemTotal,&dwVidMemFree))) {
              wdxdisplay_cat.debug() << "GetAvailableVidMem failed : result = " << ConvD3DErrorToString(hr) << endl;
              exit(1);
          }
          wdxdisplay_cat.debug() << "after fullscreen switch: GetAvailableVidMem returns Total: " << dwVidMemTotal/1000000.0 << "  Free: " << dwVidMemFree/1000000.0 << endl;
         #endif
         */
       }
    }
}

//return true if successful
void wdxGraphicsWindow::
CreateScreenBuffersAndDevice(DXScreenData &Display) {

    DWORD dwRenderWidth=Display.dwRenderWidth;
    DWORD dwRenderHeight=Display.dwRenderHeight;
    LPDIRECT3D7 pD3DI=Display.pD3D;
    LPDIRECTDRAW7 pDD=Display.pDD;
    D3DDEVICEDESC7 *pD3DDevDesc=&Display.D3DDevDesc;

    LPDIRECTDRAWSURFACE7 pPrimaryDDSurf,pBackDDSurf,pZDDSurf;
    LPDIRECT3DDEVICE7 pD3DDevice;
    RECT view_rect;
    int i;
    HRESULT hr;
    DX_DECLARE_CLEAN( DDSURFACEDESC2, SurfaceDesc );

    assert(pDD!=NULL);
    assert(pD3DI!=NULL);

/*
    // select the best device if the caller does not provide one
    D3DDEVICEDESC7 d3ddevs[2];  // put HAL in 0, TnLHAL in 1    

    if(pD3DDevDesc==NULL) {
        // just look for HAL and TnL devices right now.  I dont think
        // we have any interest in the sw rasts at this point

        ZeroMemory(d3ddevs,2*sizeof(D3DDEVICEDESC7));
    
        hr = pD3DI->EnumDevices(EnumDevicesCallback,d3ddevs);
        if(hr != DD_OK) {
            wdxdisplay_cat.fatal() << "EnumDevices failed : result = " << ConvD3DErrorToString(hr) << endl;
            exit(1);
        }

        WORD DeviceIdx=REGHALIDX;

        if(!(d3ddevs[DeviceIdx].dwDevCaps & D3DDEVCAPS_HWRASTERIZATION )) {
            wdxdisplay_cat.fatal() << "No 3D HW present, exiting..." << endl;
            exit(1);
        }

        // select TNL if present
        if(d3ddevs[TNLHALIDX].dwDevCaps & D3DDEVCAPS_HWRASTERIZATION) {
            DeviceIdx=TNLHALIDX;
        }

        pD3DDevDesc=&d3ddevs[DeviceIdx];
    }
*/  

   DX_DECLARE_CLEAN(DDCAPS,DDCaps);
   pDD->GetCaps(&DDCaps,NULL);

   // if window is not foreground in exclusive mode, ddraw thinks you are 'not active', so
   // it changes your WM_ACTIVATEAPP from true to false, causing us
   // to go into a 'wait-for WM_ACTIVATEAPP true' loop, and the event never comes so we hang
   // in fullscreen wait.

   SetForegroundWindow(Display.hWnd);

   if(dx_full_screen) {
        // Setup to create the primary surface w/backbuffer
        DX_DECLARE_CLEAN(DDSURFACEDESC2,ddsd)
        ddsd.dwFlags           = DDSD_CAPS|DDSD_BACKBUFFERCOUNT;
        ddsd.ddsCaps.dwCaps    = DDSCAPS_PRIMARYSURFACE | DDSCAPS_3DDEVICE |
                                 DDSCAPS_FLIP | DDSCAPS_COMPLEX;
        ddsd.dwBackBufferCount = 1;

        if(dx_full_screen_antialiasing) {
            // cant check that d3ddevice has this capability yet, so got to set it anyway.
            // hope this is OK.
            ddsd.ddsCaps.dwCaps2 |= DDSCAPS2_HINTANTIALIASING; 
        }

        PRINTVIDMEM(pDD,&ddsd.ddsCaps,"initial primary & backbuf");

        // Create the primary surface
        if(FAILED( hr = pDD->CreateSurface( &ddsd, &pPrimaryDDSurf, NULL ) )) {
            wdxdisplay_cat.fatal() << "CreateSurface failed for primary surface: result = " << ConvD3DErrorToString(hr) << endl;

            if(((hr==DDERR_OUTOFVIDEOMEMORY)||(hr==DDERR_OUTOFMEMORY)) &&
               (Display.dwFullScreenBitDepth>16)) {
                // emergency fallback to 16bpp (shouldnt have to do this unless GetAvailVidMem lied)
                // will this work for multimon?  what if surfs are already created on 1st mon?
                Display.dwFullScreenBitDepth=16;

                if(wdxdisplay_cat.info())
                    wdxdisplay_cat.info() << "GetAvailVidMem lied, not enough VidMem for 32bpp, so trying 16bpp on device #"<< Display.CardIDNum<< endl;

                if(FAILED( hr = pDD->SetDisplayMode( Display.dwRenderWidth, Display.dwRenderHeight,Display.dwFullScreenBitDepth, 0, 0 ))) {
                    wdxdisplay_cat.fatal() << "SetDisplayMode failed to set ("<<Display.dwRenderWidth<<"x"<<Display.dwRenderHeight<<"x"<<Display.dwFullScreenBitDepth<<") on device #"<< Display.CardIDNum<<": result = " << ConvD3DErrorToString(hr) << endl;
                    exit(1);
                }
                bool saved_value=dx_force_16bpp_zbuffer;
                dx_force_16bpp_zbuffer=true;
                CreateScreenBuffersAndDevice(Display);
                dx_force_16bpp_zbuffer=saved_value;
                return;
            } else exit(1);
        }

        // Clear the primary surface to black

        DX_DECLARE_CLEAN(DDBLTFX, bltfx)
        bltfx.dwDDFX |= DDBLTFX_NOTEARING;
        hr = pPrimaryDDSurf->Blt(NULL,NULL,NULL,DDBLT_COLORFILL | DDBLT_WAIT,&bltfx);

        if(FAILED(hr)) {
            wdxdisplay_cat.fatal() << "Blt to Black of Primary Surf failed! : result = " << ConvD3DErrorToString(hr) << endl;
            exit(1);
        }

        // Get the backbuffer, which was created along with the primary.
        DDSCAPS2 ddscaps = { DDSCAPS_BACKBUFFER, 0, 0, 0};
        if(FAILED( hr = pPrimaryDDSurf->GetAttachedSurface( &ddscaps, &pBackDDSurf ) )) {
            wdxdisplay_cat.fatal() << "Can't get the backbuffer: result = " << ConvD3DErrorToString(hr) << endl;
            exit(1);
        }

        if(FAILED( hr = pBackDDSurf->Blt(NULL,NULL,NULL,DDBLT_COLORFILL | DDBLT_WAIT,&bltfx))) {
            wdxdisplay_cat.fatal() << "Blt to Black of Back Surf failed! : result = " << ConvD3DErrorToString(hr) << endl;
            exit(1);
        }

        SetRect(&view_rect, 0, 0, dwRenderWidth, dwRenderHeight);
    }   // end create full screen buffers

    else {          // CREATE WINDOWED BUFFERS

        if(!(DDCaps.dwCaps2 & DDCAPS2_CANRENDERWINDOWED)) {
            wdxdisplay_cat.fatal() << "the 3D HW cannot render windowed, exiting..." << endl;
            exit(1);
        }

        if(FAILED(hr = pDD->GetDisplayMode( &SurfaceDesc ))) {
            wdxdisplay_cat.fatal() << "GetDisplayMode failed result = " << ConvD3DErrorToString(hr) << endl;
            exit(1);
        }
        if(SurfaceDesc.ddpfPixelFormat.dwRGBBitCount <= 8) {
            wdxdisplay_cat.fatal() << "Can't run windowed in an 8-bit or less display mode" << endl;
            exit(1);
        }

        if(!(BitDepth_2_DDBDMask(SurfaceDesc.ddpfPixelFormat.dwRGBBitCount) & pD3DDevDesc->dwDeviceRenderBitDepth)) {
            wdxdisplay_cat.fatal() << "3D Device doesnt support rendering at " << SurfaceDesc.ddpfPixelFormat.dwRGBBitCount << "bpp (current desktop bitdepth)" << endl;
            exit(1);
        }

     
        // Get the dimensions of the viewport and screen bounds

        GetClientRect( Display.hWnd, &view_rect );
        POINT ul,lr;
        ul.x=view_rect.left;  ul.y=view_rect.top;
        lr.x=view_rect.right;  lr.y=view_rect.bottom;
        ClientToScreen(Display.hWnd, &ul );
        ClientToScreen(Display.hWnd, &lr );
        view_rect.left=ul.x; view_rect.top=ul.y;
        view_rect.right=lr.x; view_rect.bottom=lr.y;

        dwRenderWidth  = view_rect.right - view_rect.left;
        dwRenderHeight = view_rect.bottom - view_rect.top;
        _props._xorg = view_rect.left;  // _props should reflect view rectangle
        _props._yorg = view_rect.top;
        _props._xsize = dwRenderWidth;
        _props._ysize = dwRenderHeight;

        // Initialize the description of the primary surface
        ZeroMemory( &SurfaceDesc, sizeof(DDSURFACEDESC2) );
        SurfaceDesc.dwSize         = sizeof(DDSURFACEDESC2);
        SurfaceDesc.dwFlags        = DDSD_CAPS ;
        SurfaceDesc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

        PRINTVIDMEM(pDD,&SurfaceDesc.ddsCaps,"initial primary surface");

        // Create the primary surface.  This includes all of the visible
        // window, so no need to specify height/width
        if(FAILED(hr = pDD->CreateSurface( &SurfaceDesc, &pPrimaryDDSurf, NULL ))) {
            wdxdisplay_cat.fatal()
            << "CreateSurface failed for primary surface: result = " << ConvD3DErrorToString(hr) << endl;
            exit(1);
        }

        // Create a clipper object which handles all our clipping for cases when
        // our window is partially obscured by other windows.
        LPDIRECTDRAWCLIPPER Clipper;
        if(FAILED(hr = pDD->CreateClipper( 0, &Clipper, NULL ))) {
            wdxdisplay_cat.fatal() << "CreateClipper failed : result = " << ConvD3DErrorToString(hr) << endl;
            exit(1);
        }

        // Associate the clipper with our window. Note that, afterwards, the
        // clipper is internally referenced by the primary surface, so it is safe
        // to release our local reference to it.
        Clipper->SetHWnd( 0, Display.hWnd );
        pPrimaryDDSurf->SetClipper( Clipper );
        Clipper->Release();
   
        // Clear the primary surface to black
        DX_DECLARE_CLEAN(DDBLTFX, bltfx)

        if(FAILED( hr = pPrimaryDDSurf->Blt(NULL,NULL,NULL,DDBLT_COLORFILL | DDBLT_WAIT,&bltfx))) {
            wdxdisplay_cat.fatal()
            << "Blt to Black of Primary Surf failed! : result = " << ConvD3DErrorToString(hr) << endl;
            exit(1);
        }

        // Setup a surface description to create a backbuffer.
        SurfaceDesc.dwFlags        = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;
        SurfaceDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE | DDSCAPS_VIDEOMEMORY;
        SurfaceDesc.dwWidth  = dwRenderWidth;
        SurfaceDesc.dwHeight = dwRenderHeight;

        if(dx_full_screen_antialiasing) {
            // cant check that d3ddevice has this capability yet, so got to set it anyway.
            // hope this is OK.
            SurfaceDesc.ddsCaps.dwCaps2 |= DDSCAPS2_HINTANTIALIASING; 
        }

        PRINTVIDMEM(pDD,&SurfaceDesc.ddsCaps,"initial backbuf");

        // Create the backbuffer. (might want to handle failure due to running out of video memory)
        if(FAILED(hr = pDD->CreateSurface( &SurfaceDesc, &pBackDDSurf, NULL ))) {
            wdxdisplay_cat.fatal()
            << "CreateSurface failed for backbuffer : result = " << ConvD3DErrorToString(hr) << endl;
            exit(1);
        }

        if(FAILED( hr = pBackDDSurf->Blt(NULL,NULL,NULL,DDBLT_COLORFILL | DDBLT_WAIT,&bltfx))) {
            wdxdisplay_cat.fatal()
            << "Blt to Black of Back Surf failed! : result = " << ConvD3DErrorToString(hr) << endl;
            exit(1);
        }

    }  // end create windowed buffers

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

    resized(dwRenderWidth,dwRenderHeight);  // update panda channel/display rgn info

    #ifndef NDEBUG
      if(!(_props._mask & W_DEPTH)) {
        wdxdisplay_cat.info() << "no zbuffer requested, skipping zbuffer creation\n";
      }
    #endif

    // Check if the device supports z-bufferless hidden surface removal. If so,
    // we don't really need a z-buffer
    if((!(pD3DDevDesc->dpcTriCaps.dwRasterCaps & D3DPRASTERCAPS_ZBUFFERLESSHSR )) &&
       (_props._mask & W_DEPTH)) {

        // Get z-buffer dimensions from the render target
        DX_DECLARE_CLEAN(DDSURFACEDESC2,ddsd);
        pBackDDSurf->GetSurfaceDesc( &ddsd );

        // Setup the surface desc for the z-buffer.
        ddsd.dwFlags        = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS | DDSD_PIXELFORMAT;
        ddsd.ddsCaps.dwCaps = DDSCAPS_ZBUFFER | ((_dxgsg->scrn.bIsSWRast) ?  DDSCAPS_SYSTEMMEMORY : DDSCAPS_VIDEOMEMORY);

        DDPIXELFORMAT ZBufPixFmts[MAX_DX_ZBUF_FMTS];
        cNumZBufFmts=0;

        // Get an appropiate pixel format from enumeration of the formats. On the
        // first pass, we look for a zbuffer dpeth which is equal to the frame
        // buffer depth (as some cards unfornately require this).
        if(FAILED(pD3DI->EnumZBufferFormats((Display.bIsTNLDevice ? IID_IDirect3DHALDevice : IID_IDirect3DTnLHalDevice),
                                            EnumZBufFmtsCallback,
                                            (VOID*)&ZBufPixFmts ))) {
            wdxdisplay_cat.fatal() << "EnumZBufferFormats failed" << endl;
            exit(1);
        }

#ifdef _DEBUG
        { static BOOL bPrinted=FALSE;
            if(!bPrinted) {
                for(i=0;i<cNumZBufFmts;i++) {
                    DebugPrintPixFmt(&ZBufPixFmts[i]);
                }
                bPrinted=TRUE;
            }
        }
#endif

        // int want_depth_bits = _props._want_depth_bits;  should we pay attn to these at some point?
        // int want_color_bits = _props._want_color_bits;
        bool bWantStencil = ((_props._mask & W_STENCIL)!=0);

        LPDDPIXELFORMAT pCurPixFmt,pz16=NULL,pz24=NULL,pz32=NULL;
        for(i=0,pCurPixFmt=ZBufPixFmts;i<cNumZBufFmts;i++,pCurPixFmt++) {
          if(bWantStencil==((pCurPixFmt->dwFlags & DDPF_STENCILBUFFER)!=0)) {
            switch(pCurPixFmt->dwRGBBitCount) {
                case 16:
                    pz16=pCurPixFmt;
                    break;
                case 24:
                    pz24=pCurPixFmt;
                    break;
                case 32:
                    pz32=pCurPixFmt;
                    break;
            }
          }
        }

        if((pz16==NULL)&&(pz24==NULL)&&(pz32==NULL)) {
            if(bWantStencil) 
                wdxdisplay_cat.fatal() << "stencil buffer requested, device has no stencil capability\n";
              else wdxdisplay_cat.fatal() << "failed to find adequate zbuffer capability\n";
            exit(1);
        }

        #define SET_ZBUF_DEPTH(DEPTH) { assert(pz##DEPTH != NULL); Display.depth_buffer_bitdepth=DEPTH; ddsd.ddpfPixelFormat = *pz##DEPTH;}
        
        if(_dxgsg->scrn.bIsSWRast) {
            SET_ZBUF_DEPTH(16);    // need this for fast path rasterizers
        } else
        if(IS_NVIDIA(Display.DXDeviceID)) {
           DX_DECLARE_CLEAN(DDSURFACEDESC2,ddsd_pri)
            pPrimaryDDSurf->GetSurfaceDesc(&ddsd_pri);

            // must pick zbuf depth to match primary surface depth for nvidia
            if(ddsd_pri.ddpfPixelFormat.dwRGBBitCount==16) {
                SET_ZBUF_DEPTH(16);
            } else {
                if(dx_force_16bpp_zbuffer) {
                    wdxdisplay_cat.fatal() << "'dx-force-16bpp-zbuffer #t' requires a 16bpp desktop on nvidia cards\n";
                    exit(1);
                }
                // take the smaller of 24 or 32.  (already assured to match stencil capability)
                if(pz24!=NULL) {
                    SET_ZBUF_DEPTH(24);
                } else SET_ZBUF_DEPTH(32);
            }
        } else {
            if(dx_force_16bpp_zbuffer) {
                if(pz16==NULL) {
                    wdxdisplay_cat.fatal() << "'dx-force-16bpp-zbuffer #t', but no 16bpp zbuf fmts available on this card\n";
                    exit(1);
                }

                if(wdxdisplay_cat.is_debug())
                   wdxdisplay_cat.debug() << "forcing use of 16bpp Z-Buffer\n";
                SET_ZBUF_DEPTH(16);
                ddsd.ddpfPixelFormat = *pz16;
            } else {
                // pick the highest res zbuffer format avail.  Note: this is choosing to waste vid-memory
                // and possibly perf for more accuracy, less z-fighting at long distance (std 16bpp would 
                // be smaller// maybe faster)
                // order of preference 24: (should be enough), 32: probably means 24 of Z, then 16

                if(bWantStencil && (pz32!=NULL)) {
                    // dont want to select 16/8 z/stencil over 24/8 z/stenc
                    SET_ZBUF_DEPTH(32);
                } else {
                    if(pz24!=NULL) {
                        SET_ZBUF_DEPTH(24);
                    } else if(pz32!=NULL) {
                        SET_ZBUF_DEPTH(32);
                    } else {
                        SET_ZBUF_DEPTH(16);
                    }
                }
            }
        }

        PRINTVIDMEM(pDD,&ddsd.ddsCaps,"initial zbuf");

#ifdef _DEBUG
        wdxdisplay_cat.info() << "Creating " << ddsd.ddpfPixelFormat.dwRGBBitCount << "bpp zbuffer\n";
#endif

        // Create and attach a z-buffer
        if(FAILED( hr = pDD->CreateSurface( &ddsd, &pZDDSurf, NULL ) )) {
            wdxdisplay_cat.fatal() << "CreateSurface failed for Z buffer: result = " <<  ConvD3DErrorToString(hr) << endl;

            if(((hr==DDERR_OUTOFVIDEOMEMORY)||(hr==DDERR_OUTOFMEMORY)) &&
               ((Display.dwFullScreenBitDepth>16)||(ddsd.ddpfPixelFormat.dwRGBBitCount>16))) {
                Display.dwFullScreenBitDepth=16;
                // emergency fallback to 16bpp (shouldnt have to do this unless GetAvailVidMem lied)
                // will this work for multimon?  what if surfs are already created on 1st mon?

                if(wdxdisplay_cat.info())
                    wdxdisplay_cat.info() << "GetAvailVidMem lied, not enough VidMem for 32bpp, so trying 16bpp on device #"<< Display.CardIDNum<< endl;

                ULONG refcnt;

                // free pri and back (maybe should just free pri since created as complex chain?)
                RELEASE(pBackDDSurf,wdxdisplay,"backbuffer",false);
                RELEASE(pPrimaryDDSurf,wdxdisplay,"primary surface",false);

                if(FAILED( hr = pDD->SetDisplayMode( Display.dwRenderWidth, Display.dwRenderHeight,Display.dwFullScreenBitDepth, 0, 0 ))) {
                    wdxdisplay_cat.fatal() << "SetDisplayMode failed to set ("<<Display.dwRenderWidth<<"x"<<Display.dwRenderHeight<<"x"<<Display.dwFullScreenBitDepth<<") on device #"<< Display.CardIDNum<<": result = " << ConvD3DErrorToString(hr) << endl;
                    exit(1);
                }
                bool saved_value=dx_force_16bpp_zbuffer;
                dx_force_16bpp_zbuffer=true;
                CreateScreenBuffersAndDevice(Display);
                dx_force_16bpp_zbuffer=saved_value;
                return;
            } else {
               exit(1);
            }
        }

        if(FAILED( hr = pBackDDSurf->AddAttachedSurface( pZDDSurf ) )) {
            wdxdisplay_cat.fatal() << "AddAttachedSurface failed : result = " << ConvD3DErrorToString(hr) << endl;
            exit(1);
        }
    }

    // Create the device. The device is created off of our back buffer, which
    // becomes the render target for the newly created device.
    hr = pD3DI->CreateDevice(pD3DDevDesc->deviceGUID, pBackDDSurf, &pD3DDevice );
    if(hr != DD_OK) {
        wdxdisplay_cat.fatal() << "CreateDevice failed : result = " << ConvD3DErrorToString(hr) << endl;
        exit(1);
    }

    // Create the viewport
    D3DVIEWPORT7 vp = { 0, 0, _props._xsize, _props._ysize, 0.0f, 1.0f};
    hr = pD3DDevice->SetViewport( &vp );
    if(hr != DD_OK) {
        wdxdisplay_cat.fatal() << "SetViewport failed : result = " << ConvD3DErrorToString(hr) << endl;
        exit(1);
    }

    Display.pD3DDevice=pD3DDevice;
    Display.pddsPrimary=pPrimaryDDSurf;
    Display.pddsBack=pBackDDSurf;
    Display.pddsZBuf=pZDDSurf;
    Display.view_rect = view_rect;

//pDD, pPrimaryDDSurf, pBackDDSurf, pZDDSurf, pD3DI, pD3DDevice, view_rect);
    _dxgsg->dx_init();
    // do not SetDXReady() yet since caller may want to do more work before letting rendering proceed
}

////////////////////////////////////////////////////////////////////
//     Function: setup_colormap
//       Access:
//  Description:
////////////////////////////////////////////////////////////////////
void wdxGraphicsWindow::setup_colormap(void) {
    PIXELFORMATDESCRIPTOR pfd;
    LOGPALETTE *logical;
    int n;

  // should probably do this some other way than through opengl PixelFormat calls

  /* grab the pixel format */
    memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
    DescribePixelFormat(_hdc, GetPixelFormat(_hdc),
                        sizeof(PIXELFORMATDESCRIPTOR), &pfd);

    if(!(pfd.dwFlags & PFD_NEED_PALETTE ||
         pfd.iPixelType == PFD_TYPE_COLORINDEX))
        return;

    n = 1 << pfd.cColorBits;

  /* allocate a bunch of memory for the logical palette (assume 256
     colors in a Win32 palette */
    logical = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) +
                                  sizeof(PALETTEENTRY) * n);
    memset(logical, 0, sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * n);

  /* set the entries in the logical palette */
    logical->palVersion = 0x300;
    logical->palNumEntries = n;

  /* start with a copy of the current system palette */
    GetSystemPaletteEntries(_hdc, 0, 256, &logical->palPalEntry[0]);

    if(pfd.iPixelType == PFD_TYPE_RGBA) {
        int redMask = (1 << pfd.cRedBits) - 1;
        int greenMask = (1 << pfd.cGreenBits) - 1;
        int blueMask = (1 << pfd.cBlueBits) - 1;
        int i;

    /* fill in an RGBA color palette */
        for(i = 0; i < n; ++i) {
            logical->palPalEntry[i].peRed =
            (((i >> pfd.cRedShift)   & redMask)   * 255) / redMask;
            logical->palPalEntry[i].peGreen =
            (((i >> pfd.cGreenShift) & greenMask) * 255) / greenMask;
            logical->palPalEntry[i].peBlue =
            (((i >> pfd.cBlueShift)  & blueMask)  * 255) / blueMask;
            logical->palPalEntry[i].peFlags = 0;
        }
    }

    _colormap = CreatePalette(logical);
    free(logical);

    SelectPalette(_hdc, _colormap, FALSE);
    RealizePalette(_hdc);
}

////////////////////////////////////////////////////////////////////
//     Function: handle_window_move
//       Access:
//  Description: we receive the new x and y position of the client
////////////////////////////////////////////////////////////////////
void wdxGraphicsWindow::handle_window_move(int x, int y) {
    _dxgsg->adjust_view_rect(x,y);
    _props._xorg = x;
    _props._yorg = y;
}

////////////////////////////////////////////////////////////////////
//     Function: handle_mouse_motion
//       Access:
//  Description:
////////////////////////////////////////////////////////////////////
void wdxGraphicsWindow::handle_mouse_motion(int x, int y) {
    _input_devices[0].set_pointer_in_window(x, y);
}

////////////////////////////////////////////////////////////////////
//     Function: handle_mouse_exit
//       Access:
//  Description:
////////////////////////////////////////////////////////////////////

void wdxGraphicsWindow::handle_mouse_entry(bool bEntering, int x, int y) {
    // usually 'motion' event is equivalent to entering, so
    // this will never be called w/bEntering true
    if(bEntering) {
        _input_devices[0].set_pointer_in_window(x, y);
    } else {
        _input_devices[0].set_pointer_out_of_window();
    }
}

////////////////////////////////////////////////////////////////////
//     Function: handle_keypress
//       Access:
//  Description:
////////////////////////////////////////////////////////////////////
void wdxGraphicsWindow::
handle_keypress(ButtonHandle key, int x, int y) {
/*
    if(key.has_ascii_equivalent()) {
        wdxdisplay_cat.spam() << key.get_ascii_equivalent() << endl;

        short d1 = GetKeyState(VK_CONTROL);
        short d2 = GetKeyState(VK_SHIFT);
        wdxdisplay_cat.spam().flags(ios::hex | ios::uppercase );
        wdxdisplay_cat.spam()  << " Control: " << ((d1 < 0)? "down" : "up") << "  Shift: " << ((d2 < 0)? "down" : "up") << endl;
    }
*/
    _input_devices[0].set_pointer_in_window(x, y);
    if(key != ButtonHandle::none()) {
        _input_devices[0].button_down(key);
    }
}

////////////////////////////////////////////////////////////////////
//     Function: handle_keyrelease
//       Access:
//  Description:
////////////////////////////////////////////////////////////////////
void wdxGraphicsWindow::
handle_keyrelease(ButtonHandle key) {
    if(key != ButtonHandle::none()) {
        _input_devices[0].button_up(key);
    }
}

void INLINE process_1_event(void) {
  MSG msg;

  if(!GetMessage(&msg, NULL, 0, 0)) {
      // WM_QUIT received
      DestroyAllWindows(false);
      exit(msg.wParam);  // this will invoke AtExitFn
  }

  // Translate virtual key messages
  TranslateMessage(&msg);
  // Call window_proc
  DispatchMessage(&msg);
}

void INLINE wdxGraphicsWindow::process_events(void) {
  if(!_window_active) {
      // Get 1 msg at a time until no more are left and we block and sleep,
      // or message changes _return_control_to_app or _window_active status

      while(!_window_active && (!_return_control_to_app)) {
          process_1_event();
      }
      _return_control_to_app = false;

  } else {
      MSG msg;

      // handle all msgs on queue in a row
      while(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
          process_1_event();
      }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: wdxGraphicsWindow::get_gsg_type
//       Access: Public, Virtual
//  Description: Returns the TypeHandle of the kind of GSG preferred
//               by this kind of window.
////////////////////////////////////////////////////////////////////
TypeHandle wdxGraphicsWindow::
get_gsg_type() const {
    return DXGraphicsStateGuardian::get_class_type();
}

GraphicsWindow *wdxGraphicsWindow::
make_wdxGraphicsWindow(const FactoryParams &params) {
    GraphicsWindow::WindowPipe *pipe_param;
    if(!get_param_into(pipe_param, params)) {
        wdxdisplay_cat.error()
        << "No pipe specified for window creation!" << endl;
        return NULL;
    }

    GraphicsPipe *pipe = pipe_param->get_pipe();

    GraphicsWindow::WindowProps *props_param;
    if(!get_param_into(props_param, params)) {
        return new wdxGraphicsWindow(pipe);
    } else {
        return new wdxGraphicsWindow(pipe, props_param->get_properties());
    }
}

TypeHandle wdxGraphicsWindow::get_class_type(void) {
    return _type_handle;
}

void wdxGraphicsWindow::init_type(void) {
    GraphicsWindow::init_type();
    register_type(_type_handle, "wdxGraphicsWindow",
                  GraphicsWindow::get_class_type());
}

TypeHandle wdxGraphicsWindow::get_type(void) const {
    return get_class_type();
}

////////////////////////////////////////////////////////////////////
//     Function: lookup_key
//       Access:
//  Description:
////////////////////////////////////////////////////////////////////
ButtonHandle wdxGraphicsWindow::
lookup_key(WPARAM wparam) const {
    // First, check for a few buttons that we filter out when the IME
    // window is open.
    if (!_ime_active) {
      switch(wparam) {
      case VK_BACK: return KeyboardButton::backspace();
      case VK_DELETE: return KeyboardButton::del();
      case VK_ESCAPE: return KeyboardButton::escape();
      case VK_SPACE: return KeyboardButton::space();
      case VK_UP: return KeyboardButton::up();
      case VK_DOWN: return KeyboardButton::down();
      case VK_LEFT: return KeyboardButton::left();
      case VK_RIGHT: return KeyboardButton::right();
      }
    }

    // Now check for the rest of the buttons, including the ones that
    // we allow through even when the IME window is open.
    switch(wparam) {
        case VK_TAB: return KeyboardButton::tab();
        case VK_PRIOR: return KeyboardButton::page_up();
        case VK_NEXT: return KeyboardButton::page_down();
        case VK_HOME: return KeyboardButton::home();
        case VK_END: return KeyboardButton::end();
        case VK_F1: return KeyboardButton::f1();
        case VK_F2: return KeyboardButton::f2();
        case VK_F3: return KeyboardButton::f3();
        case VK_F4: return KeyboardButton::f4();
        case VK_F5: return KeyboardButton::f5();
        case VK_F6: return KeyboardButton::f6();
        case VK_F7: return KeyboardButton::f7();
        case VK_F8: return KeyboardButton::f8();
        case VK_F9: return KeyboardButton::f9();
        case VK_F10: return KeyboardButton::f10();
        case VK_F11: return KeyboardButton::f11();
        case VK_F12: return KeyboardButton::f12();
        case VK_INSERT: return KeyboardButton::insert();
        case VK_CAPITAL: return KeyboardButton::caps_lock();
        case VK_NUMLOCK: return KeyboardButton::num_lock();
        case VK_SCROLL: return KeyboardButton::scroll_lock();
        case VK_SNAPSHOT: return KeyboardButton::print_screen();

        case VK_SHIFT:
        case VK_LSHIFT:
        case VK_RSHIFT:
            return KeyboardButton::shift();

        case VK_CONTROL:
        case VK_LCONTROL:
        case VK_RCONTROL:
            return KeyboardButton::control();

        case VK_MENU:
        case VK_LMENU:
        case VK_RMENU: {
            return KeyboardButton::alt();
        }

        default:
            int key = MapVirtualKey(wparam, 2);
            if (isascii(key) && key != 0) {
              // We used to try to remap lowercase to uppercase keys
              // here based on the state of the shift and/or caps lock
              // keys.  But that's a mistake, and doesn't allow for
              // international or user-defined keyboards; let Windows
              // do that mapping.

              // Nowadays, we make a distinction between a "button"
              // and a "keystroke".  A button corresponds to a
              // physical button on the keyboard and has a down and up
              // event associated.  A keystroke may or may not
              // correspond to a physical button, but will be some
              // Unicode character and will not have a corresponding
              // up event.
              return KeyboardButton::ascii_key(tolower(key));
            }
            break;
    }
    return ButtonHandle::none();
}

int wdxGraphicsWindow::
get_depth_bitwidth(void) {
    assert(_dxgsg!=NULL);
    if(_dxgsg->scrn.pddsZBuf!=NULL)
       return _dxgsg->scrn.depth_buffer_bitdepth;
     else return 0;

// GetSurfaceDesc is not reliable, on GF2, GetSurfDesc returns 32bpp when you created a 24bpp zbuf
// instead store the depth used at creation time

//    DX_DECLARE_CLEAN(DDSURFACEDESC2, ddsd);
//    _dxgsg->_zbuf->GetSurfaceDesc(&ddsd); 
//  return ddsd.ddpfPixelFormat.dwRGBBitCount;
}

// Global system parameters we want to modify during our run
static int iMouseTrails;
static bool bCursorShadowOn,bMouseVanish;

#ifndef SPI_GETMOUSEVANISH
// get rid of this when we upgrade to winxp winuser.h in newest platform sdk
#define SPI_GETMOUSEVANISH                  0x1020
#define SPI_SETMOUSEVANISH                  0x1021
#endif

#ifndef SPI_GETCURSORSHADOW
#error You have old windows headers, need to get latest MS Platform SDK
#endif

void set_global_parameters(void) {
  // turn off mousetrails and cursor shadow and mouse cursor vanish
  // cursor shadow causes cursor blink and reduced frame rate due to lack of driver support for 
  // cursor alpha blending

  // this is a win2k/xp only param, could use GetVersionEx to do it just for win2k,
  // but since it is just a param it will just cause a silent error on other OS's
  // that should be ok
  SystemParametersInfo(SPI_GETCURSORSHADOW,NULL,&bCursorShadowOn,NULL);
  SystemParametersInfo(SPI_SETCURSORSHADOW,NULL,(PVOID)false,NULL);

  SystemParametersInfo(SPI_GETMOUSETRAILS,NULL,&iMouseTrails,NULL);
  SystemParametersInfo(SPI_SETMOUSETRAILS,NULL,(PVOID)0,NULL);

  // this is ME/XP only feature
  SystemParametersInfo(SPI_GETMOUSEVANISH,NULL,&bMouseVanish,NULL);
  SystemParametersInfo(SPI_SETMOUSEVANISH,NULL,(PVOID)false,NULL);
}

void restore_global_parameters(void) {
  SystemParametersInfo(SPI_SETCURSORSHADOW,NULL,(PVOID)bCursorShadowOn,NULL);
  SystemParametersInfo(SPI_SETMOUSETRAILS,NULL,(PVOID)iMouseTrails,NULL);
  SystemParametersInfo(SPI_SETMOUSEVANISH,NULL,(PVOID)bMouseVanish,NULL);
}


// fns for exporting to python, which cant pass variable length arrays
wdxGraphicsWindowGroup::wdxGraphicsWindowGroup(GraphicsPipe *pipe,const GraphicsWindow::Properties &Win1Prop) {
    GraphicsWindow::Properties WinPropArr[1];
    WinPropArr[0]=Win1Prop;
    make_windows(pipe,1,WinPropArr);
}

wdxGraphicsWindowGroup::wdxGraphicsWindowGroup(GraphicsPipe *pipe,
                                               const GraphicsWindow::Properties &Win1Prop,
                                               const GraphicsWindow::Properties &Win2Prop) {
    GraphicsWindow::Properties WinPropArr[2];

    WinPropArr[0]=Win1Prop;
    WinPropArr[1]=Win2Prop;
    make_windows(pipe,2,WinPropArr);
}

wdxGraphicsWindowGroup::wdxGraphicsWindowGroup(GraphicsPipe *pipe,
                                               const GraphicsWindow::Properties &Win1Prop,
                                               const GraphicsWindow::Properties &Win2Prop,
                                               const GraphicsWindow::Properties &Win3Prop) {
    GraphicsWindow::Properties WinPropArr[3];

    WinPropArr[0]=Win1Prop;
    WinPropArr[1]=Win2Prop;
    WinPropArr[2]=Win3Prop;
    make_windows(pipe,3,WinPropArr);
}

wdxGraphicsWindowGroup::wdxGraphicsWindowGroup(wdxGraphicsWindow *OneWindow) {
    // init a 1 window group
    // called from config_window

    _windows.reserve(1);
    _windows.push_back(OneWindow);
    initWindowGroup();
}

void wdxGraphicsWindowGroup::initWindowGroup(void) {
    HRESULT hr;
    unsigned int i;

    assert(_windows.size()>0);
    _hOldForegroundWindow=GetForegroundWindow();
    _bClosingAllWindows= false;

    unsigned int num_windows=_windows.size();

    #define DDRAW_NAME "ddraw.dll"

    _hDDrawDLL = LoadLibrary(DDRAW_NAME);
    if(_hDDrawDLL == 0) {
        wdxdisplay_cat.fatal() << "can't locate " << DDRAW_NAME <<"!\n";
        exit(1);
    }

    _hMouseCursor = NULL;
    _bLoadedCustomCursor = false;

    // can only get multimon HW acceleration in fullscrn on DX7

    unsigned int numMonitors = GetSystemMetrics(SM_CMONITORS);

    if(numMonitors < num_windows) {
        if(numMonitors==0) {
             numMonitors=1;   //win95 system will fail this call
          } else {
              wdxdisplay_cat.fatal() << "system has only "<< numMonitors << " monitors attached, couldn't find enough devices to meet multi window reqmt of " << num_windows << endl;
              exit(1);
          }
    }     

    _pDDCreateEx = (LPDIRECTDRAWCREATEEX) GetProcAddress(_hDDrawDLL,"DirectDrawCreateEx");
    if(_pDDCreateEx == NULL) {
        wdxdisplay_cat.fatal() << "Panda currently requires at least DirectX 7.0!\n";
        exit(1);
    }

    if(_windows.size()>1) {
        // just do Enumeration for multimon case.
        // enumeration was reqd to select voodoo1-class devices, but I'm no longer
        // going to support them on DX7 due to lack of mouse cursor

        LPDIRECTDRAWENUMERATEEX pDDEnumEx = (LPDIRECTDRAWENUMERATEEX) GetProcAddress(_hDDrawDLL,"DirectDrawEnumerateExA");
        if(pDDEnumEx == NULL) {
            wdxdisplay_cat.fatal() << "GetProcAddr failed for DirectDrawEnumerateEx!\n";
            exit(1);
        }
    
        hr = (*pDDEnumEx)(DriverEnumCallback_MultiMon, &_DeviceInfoVec, DDENUM_ATTACHEDSECONDARYDEVICES | DDENUM_NONDISPLAYDEVICES);
        if(FAILED(hr)) {
            wdxdisplay_cat.fatal()   << "DirectDrawEnumerateEx failed : result = " << ConvD3DErrorToString(hr) << endl;
            exit(1);
        }
    
        if(_DeviceInfoVec.size() < num_windows) {
            wdxdisplay_cat.fatal() << "couldn't find enough devices attached to meet multi window reqmt of " << num_windows << endl;
            exit(1);
        }
    }

    assert(_windows[0] != NULL);

    for(i=0;i<num_windows;i++) {
        _windows[i]->config_window(this);
    }

    assert(_windows[0] != NULL);

    DWORD good_device_count=0;

    if(num_windows==1) {
        assert(_windows[0] != NULL);
        if(_windows[0]->search_for_device(0,NULL))
            good_device_count=1;
    } else {
        for(DWORD devnum=0;devnum<_DeviceInfoVec.size() && (good_device_count < num_windows);devnum++) {
            if(_windows[devnum]->search_for_device(devnum,&(_DeviceInfoVec[devnum])))
                good_device_count++;
        }
    }

    if(good_device_count < num_windows) {
      if(good_device_count==0)
         wdxdisplay_cat.fatal() << "no usable display devices, exiting...\n";
       else wdxdisplay_cat.fatal() << "multi-device request for " << num_windows << "devices, found only "<< good_device_count << endl;
      exit(1);
    }

    _DeviceInfoVec.clear();  // dont need this anymore

    CreateWindows();  // creates win32 windows  (need to do this before Setting coopLvls and display modes, 
                      // but after we have all the monitor handles needed by CreateWindow()

    SetCoopLevelsAndDisplayModes();

    if(dx_show_fps_meter)
       _windows[0]->_dxgsg->_bShowFPSMeter = true;  // just show fps on 1st mon

    for(i=0;i<num_windows;i++) {
        _windows[i]->CreateScreenBuffersAndDevice(_windows[i]->_dxgsg->scrn);
    }

    for(i=0;i<num_windows;i++) {
        _windows[i]->finish_window_setup();
    }

    for(i=0;i<num_windows;i++) {
        _windows[i]->_dxgsg->SetDXReady(true);
    }
}

wdxGraphicsWindowGroup::wdxGraphicsWindowGroup(GraphicsPipe *pipe,int num_windows,GraphicsWindow::Properties *WinPropArray) {
    make_windows(pipe,num_windows,WinPropArray);
}

void wdxGraphicsWindowGroup::
make_windows(GraphicsPipe *pipe,int num_windows,GraphicsWindow::Properties *WinPropArray) {
    _hParentWindow=NULL;
    _windows.reserve(num_windows);
    int i;

    // first make all the objs without running the dx config() stuff
    for(i=0;i<num_windows;i++) {
        wdxGraphicsWindow *pWdxWinptr= new wdxGraphicsWindow(pipe,WinPropArray[i],this);
        assert(pWdxWinptr!=NULL);
        _windows.push_back(pWdxWinptr);
    }

    initWindowGroup();  // needs _windows.size() to be valid
}

wdxGraphicsWindow::wdxGraphicsWindow(GraphicsPipe* pipe, const GraphicsWindow::Properties &props, wdxGraphicsWindowGroup *pParentGroup)
                   : GraphicsWindow(pipe, props) {
    _pParentWindowGroup=pParentGroup;
    _ime_open = false;
   // just call the GraphicsWindow constructor, have to hold off rest of init
}

wdxGraphicsWindowGroup::~wdxGraphicsWindowGroup() {
    // this fn must be called before windows are actually closed
    _bClosingAllWindows= true;

    for(DWORD i=0;i<_windows.size();i++) {
        _windows[i]->close_window();
    }

    if((_hOldForegroundWindow!=NULL) /*&& (scrn.hWnd==GetForegroundWindow())*/) {
        SetForegroundWindow(_hOldForegroundWindow);
    }

    if(_bLoadedCustomCursor && (_hMouseCursor!=NULL))
      DestroyCursor(_hMouseCursor);


    if(_hDDrawDLL != NULL) {
       FreeLibrary(_hDDrawDLL);
       _hDDrawDLL = NULL;
    }
}

