/*
 * DirectDraw driver interface
 *
 * Copyright 2001-2003 TransGaming Technologies, Inc.
 */

#include "config.h"

#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/poll.h>

#include "ts_xlib.h"
#include "x11drv.h"
#include "x11ddraw.h"
#include "xvidmode.h"
#include "xrandr.h"
#include "dga2.h"
#include "glx.h"
#include "wine/wine_gl.h"
#include "osmesa.h"

#include "windef.h"
#include "wingdi.h"
#include "ddrawi.h"
#include "bitmap.h"
#include "dce.h"
#include "wine/debug.h"
#include "options.h"
#include "mwm.h"

WINE_DEFAULT_DEBUG_CHANNEL(x11drv);

extern XContext winContext;
extern Atom wmState;
extern Atom mwmHints;
extern Atom netWMState;
extern Atom netWMStateStaysOnTop;
extern Atom netWMStateFullScreen;
extern Atom winWMLayer;

extern void X11DRV_ButtonPress( HWND hwnd, XButtonEvent *event );
extern void X11DRV_ButtonRelease( HWND hwnd, XButtonEvent *event );
extern void X11DRV_MotionNotify( HWND hwnd, XMotionEvent *event );

extern Bool EVENT_CheckEvent( Display *display, XEvent *event_return );
extern Bool EVENT_CheckIsEvent( Display *display, int type, Window window );
 
extern int dxgrab;
extern int X11DRV_NoCursor;
extern unsigned int videoram;

extern int app_resolution_change;

HHOOK x11drv_mouse_hhook;

LPDDRAWI_DDRAWSURFACE_LCL X11DRV_DD_Primary;
LPDDRAWI_DDRAWSURFACE_GBL X11DRV_DD_PrimaryGbl;
HWND X11DRV_DD_DeviceWnd;
Window X11DRV_DD_DeviceWindow;
BOOL X11DRV_DD_DeviceDesktop;
HBITMAP X11DRV_DD_PrimaryDIB;
Drawable X11DRV_DD_PrimaryDrawable;
BOOL X11DRV_DD_IsDirect;
Window X11DRV_DD_PrimaryFrame, X11DRV_DD_PrimaryClient;
HANDLE X11DRV_DD_Thread, X11DRV_DD_SyncH;
HANDLE X11DRV_DD_ThreadExit;
DWORD X11DRV_DD_ThreadID;
int X11DRV_DD_Pipe[2];
DWORD X11DRV_DD_Busy, X11DRV_DD_Paint, X11DRV_DD_Lock, X11DRV_DD_RefCount;
DWORD X11DRV_DD_DeviceWndOK;
RECT X11DRV_DD_OldWindowRect, X11DRV_DD_OldClientRect;
Window X11DRV_DD_OldRootWindow;
DWORD X11DRV_DDHAL_LockFlags;

X11DRV_MODE_SWITCH X11DRV_DD_ModeSwitch;

#define FAKE_REFRESH_RATE 60

/* this is a list of fake modes used when XF86VidMode, DGA2,
 * or future equivalent, is not available. */
static DDHALMODEINFO default_mode_info[] = {
  {0}, /* dummy mode for destroying ddraw frame window */
  {  512,  384, 0, 0, 0, FAKE_REFRESH_RATE },
  {  640,  400, 0, 0, 0, FAKE_REFRESH_RATE },
  {  640,  480, 0, 0, 0, FAKE_REFRESH_RATE },
  {  800,  600, 0, 0, 0, FAKE_REFRESH_RATE },
  { 1024,  768, 0, 0, 0, FAKE_REFRESH_RATE },
  { 1152,  864, 0, 0, 0, FAKE_REFRESH_RATE },
  { 1280, 1024, 0, 0, 0, FAKE_REFRESH_RATE },
  { 1600, 1200, 0, 0, 0, FAKE_REFRESH_RATE },
  { 2048, 1536, 0, 0, 0, FAKE_REFRESH_RATE },
  {0}
};

int get_dib_width_bytes( int width, int depth )
{
    int words;

    switch(depth)
    {
	case 1:  words = (width + 31) / 32; break;
	case 4:  words = (width + 7) / 8; break;
	case 8:  words = (width + 3) / 4; break;
	case 15:
	case 16: words = (width + 1) / 2; break;
	case 24: words = (width * 3 + 3)/4; break;

	default:
            WARN("(%d): Unsupported depth\n", depth );
	/* fall through */
	case 32:
	        words = width;
    }
    return 4 * words;
}

static int X11DRV_DD_GetCurrentMode(void)
{
  FIXME( ":stub\n" );
  /* return highest resolution */
  return X11DRV_DD_ModeSwitch.NumModes-1;
}

static void X11DRV_DD_SetCurrentMode(int mode)
{
  FIXME( ":stub\n" );
}

static int X11DRV_DD_GetSystemMode(void)
{
  /* return highest resolution */
  return X11DRV_DD_ModeSwitch.NumModes-1;
}

void X11DRV_DD_DefaultModeSetup(void)
{
  DWORD modes;

  /* initialize default mode list */
  modes = 1;
  while (default_mode_info[modes].dwWidth &&
	 default_mode_info[modes].dwWidth <= default_screen_width)
    modes++;

  if (modes > 1) {
    X11DRV_DD_ModeSwitch.Modes    = default_mode_info;
    X11DRV_DD_ModeSwitch.NumModes = modes;

    X11DRV_DD_ModeSwitch.get_current_mode = X11DRV_DD_GetCurrentMode;
    X11DRV_DD_ModeSwitch.set_current_mode = X11DRV_DD_SetCurrentMode;
    X11DRV_DD_ModeSwitch.get_system_mode = X11DRV_DD_GetSystemMode;
  }

}

void X11DRV_DD_Init(void)
{
 X11DRV_DD_DefaultModeSetup();

#ifdef HAVE_LIBXXF86VM
  /* initialize XVidMode */
  X11DRV_XF86VM_Init();
#endif
#ifdef HAVE_LIBXXF86DGA2
  /* initialize DGA2 */
  X11DRV_XF86DGA2_Init();
#endif
#ifdef HAVE_LIBXRANDR
  X11DRV_XF86RR_Init();
#endif
#ifdef HAVE_OPENGL
#ifndef HAVE_OSMESA
  /* initialize GLX */
  X11DRV_GLX_Init();
#else
  X11DRV_OSMesa_Init();
#endif
#endif
}

void X11DRV_DD_Cleanup(void)
{
#ifdef HAVE_OPENGL
#ifndef HAVE_OSMESA
  /* cleanup GLX */
  X11DRV_GLX_Cleanup();
#else
  X11DRV_OSMesa_Cleanup();
#endif
#endif
#ifdef HAVE_LIBXXF86DGA2
  /* cleanup DGA2 */
  X11DRV_XF86DGA2_Cleanup();
#endif
#ifdef HAVE_LIBXXF86VM
  /* cleanup XVidMode */
  X11DRV_XF86VM_Cleanup();
#endif
#ifdef HAVE_LIBXRANDR
  X11DRV_XF86RR_Cleanup();
#endif
}

void X11DRV_DD_Detach(void)
{
#ifdef HAVE_OPENGL
#ifndef HAVE_OSMESA
  X11DRV_GLX_Detach();
#endif
#endif
}

#define DDMSG_QUIT    0
#define DDMSG_SYNC    1
#define DDMSG_UPDATE  2
#define DDMSG_GRAB    3
#define DDMSG_UNGRAB  4

/*
 * Window manager issues:
 *  KDE/KWIN: to get above panel, window must be decorationless and
 *            have the same geometry as the desktop
 *  or else:  the _NET_WM_STATE property must be set to _NET_WM_STATE_STAYS_ON_TOP
 *  GNOME:    the _WIN_LAYER property must be set to ABOVE_DOCK
 *
 * FIXME: create window using desktop thread, not app thread
 */
static void X11DRV_DD_CreateWindow(unsigned int width, unsigned int height)
{
  Display *display = thread_display();
  char *name = "Wine DirectDraw HAL";
  XSizeHints *size_hints;
  XWMHints   *wm_hints;
  XClassHint *class_hints;
  XSetWindowAttributes win_attr;
  XTextProperty window_name;
  Window frame, client;
  unsigned int fwidth, fheight;

  wine_tsx11_lock();

  /* Create (full-screen) frame window */
  win_attr.background_pixel = BlackPixel(display, 0);
  win_attr.event_mask = /* ExposureMask | */ KeyPressMask | KeyReleaseMask |
    PointerMotionMask | ButtonPressMask | ButtonReleaseMask |
    StructureNotifyMask | FocusChangeMask | KeymapStateMask |
    EnterWindowMask | LeaveWindowMask;
  win_attr.cursor = X11DRV_GetCursor(display, X11DRV_NoCursor ? NULL : GlobalLock16(GetCursor()));
  win_attr.colormap = X11DRV_PALETTE_PaletteXColormap;
  win_attr.override_redirect = !Options.managed;

  fwidth = default_screen_width;
  fheight = default_screen_height;

  frame = XCreateWindow( display, root_window,
			 0, 0, fwidth, fheight, 0, screen_depth, InputOutput, visual,
			 CWBackPixel | CWEventMask | CWCursor | CWColormap |
			 CWOverrideRedirect, &win_attr );

  /* Set window manager properties */
  size_hints  = XAllocSizeHints();
  wm_hints    = XAllocWMHints();
  class_hints = XAllocClassHint();
  if (!size_hints || !wm_hints || !class_hints) {
    MESSAGE("Not enough memory for window manager hints.\n" );
    ExitProcess(1);
  }


  /* From XFree86 4.2.1 Man pages (and perhaps earlier): The x, y, width, and height
   * members are now obsolete and are left solely for compatibility reasons. */
  size_hints->x = 0;
  size_hints->y = 0;
  size_hints->width = size_hints->min_width = size_hints->max_width = fwidth;
  size_hints->height = size_hints->min_height = size_hints->max_height = fheight;
  size_hints->flags = PMinSize | PMaxSize | USPosition | USSize;

  wm_hints->flags = InputHint | StateHint;
  wm_hints->input = True; /* accept focus */
  wm_hints->initial_state = NormalState;
  class_hints->res_name  = "wine";
  class_hints->res_class = "Wine";

  XStringListToTextProperty( &name, 1, &window_name );
  XSetWMProperties( display, frame, &window_name, &window_name,
		    NULL, 0, size_hints, wm_hints, class_hints );
  if (win_attr.cursor != None) XFreeCursor(display, win_attr.cursor);
  XFree( size_hints );
  XFree( wm_hints );
  XFree( class_hints );

  /* no decorations! */
  if (mwmHints != None) {
    MwmHints mwm_hints;
    mwm_hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS;
    mwm_hints.functions = 0;
    mwm_hints.decorations = 0;
    XChangeProperty( display, frame, mwmHints, mwmHints, 32,
		     PropModeReplace, (char*)&mwm_hints, sizeof(mwm_hints)/sizeof(long) );
  }

  /* tell KDE-compliant WMs that we want to stay on top
   * (though this does not really have any effect on fullscreen windows),
   * and freedesktop WM 1.2-compliant WMs that we're fullscreen */
  if (netWMState != None) {
    Atom state[2] = {netWMStateStaysOnTop, netWMStateFullScreen};
    XChangeProperty( display, frame, netWMState,
		     XA_ATOM, 32,
		     PropModeReplace, (char*)&state, sizeof(state)/sizeof(long));
  }

  /* tell GNOME-compliant WMs that we want to stay on top */
  if (winWMLayer != None) {
    long layer = 10; /* ABOVE_DOCK */
    XChangeProperty( display, frame, winWMLayer,
		     XA_CARDINAL, 32,
		     PropModeReplace, (char*)&layer, 1);
  }

  /* Create real ddraw window as a child of the frame window,
   * sized at the requested resolution; this is done so that
   * 1) the frame window gets above all desktop panels
   *    (in KDE 2 it must be fullscreen to do this)
   * 2) the child window gets the benefit of the frame window,
   *    but a mouse grab on it will limit mouse movement to
   *    the resolution the app really wanted
   * then we can use XF86VidMode to only display the child
   * window, and the resolution should then be perfectly faked,
   * and no GNOME/KDE desktop stuff in the way */
  win_attr.background_pixel = BlackPixel(display, 0);
  win_attr.event_mask = ExposureMask /* | KeyPressMask | KeyReleaseMask |
    PointerMotionMask | ButtonPressMask | ButtonReleaseMask */ ;
  win_attr.cursor = None; /* inherit frame cursor */
  win_attr.colormap = X11DRV_PALETTE_PaletteXColormap;

  client = XCreateWindow( display, frame,
			  0, 0, width, height, 0, screen_depth, InputOutput, visual,
			  CWBackPixel | CWEventMask | CWCursor | CWColormap, &win_attr );

  X11DRV_DD_PrimaryFrame = frame;
  X11DRV_DD_PrimaryClient = client;

  TRACE("mapping fullscreen windows: frame=%08lx, client=%08lx\n", frame, client);
  XMapWindow( display, client );
  XMapWindow( display, frame );

  XFlush( display );

  wine_tsx11_unlock();
}

static void X11DRV_DD_DestroyWindow(void)
{
  Display *display = thread_display();

  wine_tsx11_lock();
    XSync( gdi_display, False );
    XUnmapWindow( display, X11DRV_DD_PrimaryFrame );
    XUnmapWindow( display, X11DRV_DD_PrimaryClient );
    XDestroyWindow( display, X11DRV_DD_PrimaryClient );
    X11DRV_DD_PrimaryClient = 0;
    XDestroyWindow( display, X11DRV_DD_PrimaryFrame );
    X11DRV_DD_PrimaryFrame = 0;
    XFlush( display );
  wine_tsx11_unlock();
}

static void X11DRV_DD_UpdateWindow(BOOL flip_sync)
{
  Display *display = gdi_display;
  Window win = X11DRV_DD_PrimaryClient;
  LPDDRAWI_DDRAWSURFACE_GBL gbl = X11DRV_DD_PrimaryGbl;
  HBITMAP hBmp = GET_LPDDRAWSURFACE_GBL_MORE(gbl)->hKernelSurface;
  BITMAPOBJ *bmp;
  X11DRV_DIBSECTION *dib;

  int state;

  if (!hBmp) {
    ERR("no DIB in surface\n");
    return;
  }
  bmp = (BITMAPOBJ *)GDI_GetObjPtr(hBmp, BITMAP_MAGIC);
  dib = (X11DRV_DIBSECTION *)bmp->dib;
  wine_tsx11_lock();
  state = X11DRV_DIB_Lock(bmp, DIB_Status_None, FALSE);

  /* FIXME: delegate this to another thread, for speed */

  switch (state) {
  case DIB_Status_AppMod:
    X11DRV_DIB_DrawDIBSection(bmp, win, 0, 0, 0, 0,
			      gbl->wWidth, gbl->wHeight);
    break;
  case DIB_Status_InSync:
  case DIB_Status_GdiMod:
    XCopyArea(display, (Drawable)bmp->physBitmap,
	      win, BITMAP_GC(bmp), 0, 0,
	      gbl->wWidth, gbl->wHeight,
	      0, 0);
    XSync(display, False);
    break;
  case DIB_Status_AuxMod:
    /* if this ever happens, there's probably no updating to do,
     * the sub-driver should take care of the updates */
    break;
  }

  X11DRV_DIB_Unlock(bmp, TRUE);
  wine_tsx11_unlock();
  GDI_ReleaseObj(hBmp);
}

void X11DRV_DD_Update(void);
void X11DRV_DD_InstallNewRoot(void);
void X11DRV_DD_ContinueReparent(Window win);

static BOOL has_focus, has_grab, want_grab, propagate;

LRESULT CALLBACK x11drv_mouse_hook( int code, WPARAM wparam, LPARAM lparam )
{
  if ((code != HC_ACTION) || (GetCurrentThreadId() != X11DRV_DD_ThreadID))
    return CallNextHookEx( x11drv_mouse_hhook, code, wparam, lparam );
  /* can't stuff the event into the fullscreen thread's message queue since
   * it'll never be read there, we'll have to let the caller send it to the
   * right place */
  propagate = TRUE;
  return 1; /* ignore message */
}

void X11DRV_DD_ThreadEvent(Display *display, XEvent *event)
{
  Window target;

  if (event->type == ConfigureNotify) {
    /* this can be sent from the window manager, must be exempted
     * from the send_event check below */
    TRACE("ConfigureNotify(%lx): %d,%d %dx%d\n",
          event->xconfigure.window,
          event->xconfigure.x, event->xconfigure.y,
          event->xconfigure.width, event->xconfigure.height);
    if (event->xconfigure.x != 0 || event->xconfigure.y != 0) {
      ERR("crappy WM in use: fullscreen window mispositioned: %d,%d\n",
          event->xconfigure.x, event->xconfigure.y );
      /* seems to be no way to override this */
    }
    return;
  }

  if (event->type == MapNotify) {
    /* this can be sent from the window manager, must be exempted
     * from the send_event check below */
    TRACE("MapNotify(%lx)\n",
          event->xmap.window);
    /* fullscreen windows are now ready, initiate root window change */
    X11DRV_DD_InstallNewRoot();
    /* and let the app know we're ready */
    SetEvent(X11DRV_DD_SyncH);
    return;
  }

  if(event->xany.send_event) {
    /* don't process event if it doesn't come straight from
     * the X server, since we might be getting an infinite
     * loop otherwise if we forwarded the event to ourselves
     * (safeguard for now until we can get the wrong forwards
     * sorted out) */
    TRACE("ignored sent event %d\n", event->type);
    return;
  }

  switch (event->type) {
  case KeyPress:
    /* cut down on autorepeat (assumes detectable autorepeat);
     * games rarely care about it, and it slows us down */
    {
      XEvent nevent;
      if (EVENT_CheckEvent(display, &nevent) &&
          nevent.xany.type == KeyPress &&
          nevent.xkey.window == event->xkey.window &&
          nevent.xkey.keycode == event->xkey.keycode)
        break;
    }
    /* fall through */
  case KeyRelease:
#if 1
    event->xany.window = root_window;
#else
    event->xany.window = X11DRV_DD_DeviceWindow;
#endif
    wine_tsx11_lock();
    target = X11DRV_DD_DeviceWindow;
    if (!target) {
      int rev;
      XGetInputFocus(display, &target, &rev);
    }
    XSendEvent(display, target, True, KeyPressMask | KeyReleaseMask, event);
    wine_tsx11_unlock();
    break;
  case MotionNotify:
  case ButtonPress:
  case ButtonRelease:
    event->xany.window = root_window;
    /* see if dinput takes the event */
    propagate = FALSE;
    switch (event->type) {
    case MotionNotify:
      X11DRV_MotionNotify(0, (XMotionEvent*)event);
      break;
    case ButtonPress:
      X11DRV_ButtonPress(0, (XButtonEvent*)event);
      break;
    case ButtonRelease:
      X11DRV_ButtonRelease(0, (XButtonEvent*)event);
      break;
    }
    if (!propagate) break;
    /* dinput didn't take it, propagate event to a window */
    if (event->type == MotionNotify) {
      /* cut down on motion events, they slow us down */
      if (EVENT_CheckIsEvent(display, MotionNotify, event->xany.window))
        break;
    }
    wine_tsx11_lock();
    target = X11DRV_DD_DeviceWindow;
    if (!target) {
      int dx, dy;
      XTranslateCoordinates(display, X11DRV_DD_PrimaryFrame, X11DRV_DD_PrimaryClient,
                            event->xmotion.x, event->xmotion.y,
                            &dx, &dy, &target);
      event->xmotion.x = dx;
      event->xmotion.y = dy;
    }
    if (target) {
      TRACE("forwarding mouse event %d to %08lx, pos (%d,%d)\n",
            event->xany.type, target, event->xmotion.x, event->xmotion.y);
      XSendEvent(display, target, True, PointerMotionMask, event);
    } else {
      TRACE("ignoring mouse event %d, pos (%d,%d)\n",
            event->xany.type, event->xmotion.x, event->xmotion.y);
    }
    wine_tsx11_unlock();
    break;
  case KeymapNotify:
    event->xany.window = root_window;
    wine_tsx11_lock();
    target = X11DRV_DD_DeviceWindow;
    if (!target) {
      int rev;
      XGetInputFocus(display, &target, &rev);
    }

    TRACE("forwarding keyboard event %d to %08lx\n",
          event->xany.type, target);
    XSendEvent(display, target, True, KeymapStateMask, event);
    wine_tsx11_unlock();
    break;
  case Expose:
    X11DRV_DD_Update();
    break;
  /* mode: 0=NotifyNormal, 1=NotifyGrab, 2=NotifyUngrab, 3=NotifyWhileGrabbed */
  /* detail: 0=NotifyAncestor, 1=NotifyVirtual, 2=NotifyInferior, */
  /*         3=NotifyNonlinear, 4=NotifyNonlinearVirtual, */
  /*         5=NotifyPointer, 6=NotifyPointerRoot, 7=NotifyDetailNone */
  case FocusIn:
    TRACE("acquired focus: %08lx %d %d (%d %d)\n",
          event->xfocus.window, event->xfocus.mode, event->xfocus.detail,
          has_focus, want_grab);
    if (event->xfocus.mode == NotifyNormal) {
      has_focus = TRUE;
      if (want_grab) X11DRV_EnableGrabs();
    }
    break;
  case FocusOut:
    TRACE("lost focus: %08lx %d %d (%d %d)\n",
          event->xfocus.window, event->xfocus.mode, event->xfocus.detail,
          has_focus, want_grab);
    if (event->xfocus.mode == NotifyNormal) {
      if( has_grab ) {
          X11DRV_DisableGrabs();
          want_grab = TRUE;
      }
      has_focus = FALSE;
    }
    break;
  case EnterNotify:
    TRACE("acquired pointer: %08lx %d %d (%d %d)\n",
          event->xcrossing.window, event->xcrossing.mode, event->xcrossing.detail,
          has_focus, want_grab);
    if (event->xcrossing.mode == NotifyGrab)
      has_grab = TRUE;
    else if ( event->xcrossing.mode == NotifyNormal && want_grab ) {
      X11DRV_EnableGrabs();
    }
    break;
  case LeaveNotify:
    TRACE("lost pointer: %08lx %d %d (%d %d)\n",
          event->xcrossing.window, event->xcrossing.mode, event->xcrossing.detail,
          has_focus, want_grab);
    if (event->xcrossing.mode == NotifyUngrab)
      has_grab = FALSE;
    else if ( event->xcrossing.mode == NotifyNormal && has_grab ) {
      /* For some reason this doesn't seem to generate a LeaveNotify with mode = NotifyUngrab.
         As a result just set has_grab to FALSE */
      has_grab = FALSE;
      X11DRV_DisableGrabs();
      want_grab = TRUE;
    }
    break;
  case PropertyNotify:
    if (event->xproperty.atom == wmState) {
      TRACE("property WM_STATE changed in %08lx\n", event->xproperty.window);
      /* if the property was changed, not deleted, it should have changed to WithdrawnState.
       * Let's assume this is always the case for now. */
      X11DRV_DD_ContinueReparent(event->xproperty.window);
    }
    break;
  }
}

static BOOL X11DRV_DD_ThreadCmd(Display *display, int cmd)
{
  TRACE( "Received cmd %d for display %p\n", cmd, display );
  switch (cmd) {
  case DDMSG_QUIT:
    return TRUE;
  case DDMSG_SYNC:
    SetEvent(X11DRV_DD_SyncH);
    break;
  case DDMSG_UPDATE:
    if (!X11DRV_DD_Paint) break;
    X11DRV_DD_Busy++;
    X11DRV_DD_Paint = 0;
    if (X11DRV_DD_PrimaryDIB)
      X11DRV_DD_UpdateWindow(FALSE);
    X11DRV_DD_Busy--;
    break;
  case DDMSG_GRAB:
    TSXGrabPointer(display, X11DRV_DD_PrimaryFrame, True,
		   PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
		   GrabModeAsync, GrabModeAsync,
		   X11DRV_DD_PrimaryClient, None, CurrentTime);
    break;
  case DDMSG_UNGRAB:
    TSXUngrabPointer(display, CurrentTime);
    break;
  default:
    ERR("unknown command\n");
    return TRUE;
  }
  return FALSE;
}

static DWORD CALLBACK X11DRV_DD_ThreadProc(LPVOID arg)
{
  int *input = arg;
  Display *display = thread_display();
  int conn = ConnectionNumber(display);
  unsigned int width, height;
  struct pollfd fds[2];

  read(*input, &width, sizeof(width));
  read(*input, &height, sizeof(height));
  X11DRV_DD_CreateWindow(width, height);
  fds[0].fd = conn;
  fds[0].events = POLLIN;
  fds[1].fd = *input;
  fds[1].events = POLLIN;
  do {
    fds[0].revents = 0;
    if (poll(fds, 2, -1) < 0)
    {
      if( errno != EINTR )
      {
        ERR( "poll returned %d:%s\n", errno, strerror( errno ) );
        break;
      }
      
      /* Let's try again we were just interrupted while waiting */
      continue;
    }

    if (fds[0].revents & POLLIN) {
      /* read X events */
      XEvent event;
      wine_tsx11_lock();
      while (XPending(display)) {
	XNextEvent(display, &event);
	wine_tsx11_unlock();
	X11DRV_DD_ThreadEvent(display, &event);
	wine_tsx11_lock();
      }
      wine_tsx11_unlock();
    }
    if (fds[1].revents & POLLIN) {
      /* read command */
      int cmd;
      read(*input, &cmd, sizeof(cmd));
      if (X11DRV_DD_ThreadCmd(display, cmd)) break;
    }
  } while (TRUE);
  X11DRV_DD_DestroyWindow();
  SetEvent(X11DRV_DD_ThreadExit);
  return 0;
}

void X11DRV_DD_PostCmd(int cmd)
{
  write(X11DRV_DD_Pipe[1], &cmd, sizeof(cmd));
}

HRESULT X11DRV_DD_Sync(BOOL wait)
{
  if (X11DRV_DD_Paint || X11DRV_DD_Busy) {
    if (wait) {
      X11DRV_DD_PostCmd(DDMSG_SYNC);
      WaitForSingleObject(X11DRV_DD_SyncH, INFINITE);
    }
    else
      return DDERR_WASSTILLDRAWING;
  }
  return DD_OK;
}

void X11DRV_DD_Update(void)
{
  if (X11DRV_DD_Paint) return;
  if (!X11DRV_DD_PrimaryDIB) return;
  InterlockedIncrement(&X11DRV_DD_Paint);
  X11DRV_DD_PostCmd(DDMSG_UPDATE);
}

BOOL CALLBACK X11DRV_DD_Reparent(HWND hwnd, LPARAM lParam)
{
  WND *wnd = WIN_GetPtr(hwnd);
  if (!wnd) return TRUE;
  if (wnd == WND_OTHER_PROCESS) return TRUE; /* huh? */
  TRACE("hwnd=%08x, parent=%08x\n", hwnd, wnd->parent);
  if (!wnd->parent || wnd->parent == GetDesktopWindow()) {
    Display *display = thread_display();
    struct x11drv_win_data *data = wnd->pDriverData;
    Window win = data->whole_window;
    unsigned x, y;
    TRACE("reparenting %08lx to %08lx\n", win, root_window);
    wine_tsx11_lock();
    if (root_window == DefaultRootWindow(gdi_display)) {
      /* turn on WS_EX_MANAGED? */
      /* fix window sizes? */
      x = data->whole_rect.left;
      y = data->whole_rect.top;
    }
    else {
      /* turn off WS_EX_MANAGED? */
      /* fix window sizes? */
      x = wnd->rectWindow.left;
      y = wnd->rectWindow.top;
    }
    XReparentWindow(display, win, root_window,
                    wnd->rectWindow.left,
                    wnd->rectWindow.top);
    wine_tsx11_unlock();
  }
  WIN_ReleasePtr(wnd);
  return TRUE;
}

BOOL CALLBACK X11DRV_DD_Recursor(HWND hwnd, LPARAM lParam)
{
  WND *wnd = WIN_GetPtr(hwnd);
  if (!wnd) return TRUE;
  if (wnd == WND_OTHER_PROCESS) return TRUE; /* huh? */
  TRACE("hwnd=%08x\n", hwnd);
  if (!wnd->parent || wnd->parent == GetDesktopWindow()) {
    Display *display = thread_display();
    struct x11drv_win_data *data = wnd->pDriverData;
    Window win = data->whole_window;
    TRACE("recursoring %08lx\n", win);
    wine_tsx11_lock();
    if (root_window == DefaultRootWindow(gdi_display)) {
      XDefineCursor(display, win, X11DRV_GetCursor(display, X11DRV_NoCursor ? NULL : GlobalLock16(GetCursor())));
    }
    else {
      XUndefineCursor(display, win);
    }
    wine_tsx11_unlock();
  }
  WIN_ReleasePtr(wnd);
  return TRUE;
}

BOOL CALLBACK X11DRV_DD_Remap(HWND hwnd, LPARAM lParam)
{
  WND *wnd = WIN_GetPtr(hwnd);
  if (!wnd) return TRUE;
  if (wnd == WND_OTHER_PROCESS) return TRUE; /* huh? */
  TRACE("hwnd=%08x, parent=%08x\n", hwnd, wnd->parent);
  if (!wnd->parent || wnd->parent == GetDesktopWindow()) {
    if ((wnd->dwStyle & WS_VISIBLE) && !IsRectEmpty(&wnd->rectWindow)) {
      Display *display = thread_display();
      struct x11drv_win_data *data = wnd->pDriverData;
      Window win = data->whole_window;
      TRACE("%s %08lx\n", lParam ? "mapping" : "unmapping", win);
      wine_tsx11_lock();
#ifdef HAVE_OPENGL
      if (data->client_window == glXGetCurrentDrawable()) {
        TRACE("OpenGL context bound to window, ignoring\n");
      } else
#endif
      if (lParam) {
        XMapWindow(display, win);
      }
      else
        XUnmapWindow(display, win);
      wine_tsx11_unlock();
    }
  }
  WIN_ReleasePtr(wnd);
  return TRUE;
}

BOOL CALLBACK X11DRV_DD_StartReparent(HWND hwnd, LPARAM lParam)
{
  Display *display = thread_display();
  WND *wnd = WIN_GetPtr(hwnd);
  struct x11drv_win_data *data;
  Window win;

  if (!wnd) return TRUE;
  if (wnd == WND_OTHER_PROCESS) return TRUE; /* huh? */

  TRACE("hwnd=%08x, parent=%08x\n", hwnd, wnd->parent);
  data = wnd->pDriverData;
  if (!data) goto end;

  win = data->whole_window;

  if (!wnd->parent || wnd->parent == GetDesktopWindow()) {
    if ((wnd->dwStyle & WS_VISIBLE) && !IsRectEmpty(&wnd->rectWindow)) {
      Atom type;
      int fmt;
      unsigned long items, remain;
      unsigned char *props;

      if (lParam != (LPARAM)0 && lParam != (LPARAM)-1 && lParam != win) {
        TRACE("skipping %08lx (wants %08lx)\n", win, lParam);
        WIN_ReleasePtr(wnd);
        return TRUE;
      }

      wine_tsx11_lock();
      if (lParam == (LPARAM)0 &&
          XGetWindowProperty(display, win, wmState, 0, sizeof(long), False, wmState,
                             &type, &fmt, &items, &remain, &props) == Success &&
          type != None) {
        /* WM_STATE property exists, so we can use its removal to see when the window
         * manager have finished processing the unmap we're about to do.
         * Why? Well, KDE's crappy window manager gives me all the more reason to
         * hate KDE than I already do, and it does not properly handle reparenting;
         * it will respond to the initial UnmapNotify generated by XReparentWindow
         * by unmapping the window *again*, for example, and neither the ReparentNotify
         * nor the MapNotify that would follow is enough to make it change its mind.
         * We just have to wait for the second unmap before it's safe to reparent,
         * and the removal of WM_STATE is a good indicator of when this has happened. */
        TRACE("unmapping %08lx\n", win);
        XSelectInput(display, win, PropertyChangeMask);
        XUnmapWindow(display, win);
      }
      else {
        /* WM_STATE property doesn't exist (now), can reparent window immediately. */
        TRACE("reparenting %08lx\n", win);
        if (lParam && lParam == win) {
          /* we waited for this, stop waiting */
          XSelectInput(display, win, 0);
        }
        /* ensure window is unmapped, since we want to do a proper WM-friendly
         * map afterwards if we're currently exiting fullscreen. */
        XUnmapWindow(display, win);
        wine_tsx11_unlock();
        X11DRV_DD_Reparent(hwnd, 0);
        wine_tsx11_lock();
        if (lParam != (LPARAM)0 && lParam != (LPARAM)-1) {
          /* So we got here while entering fullscreen by unmapping the window and waiting
           * for WM_STATE to go away. However, metacity sets the property to WithdrawnState
           * instead of deleting it, so let's make sure the property is really gone
           * (otherwise exiting fullscreen may not work properly). */
          XDeleteProperty(display, win, wmState);
        }
        /* if X11DRV_DD_DeviceDesktop has become TRUE by now, that means we're currently
         * in the process of unmapping windows, and it would thus not be nice to remap
         * the window here - check for that first. */
        if (!X11DRV_DD_DeviceDesktop) {
          /* it's now safe to remap the window. */
          XMapWindow(display, win);
        }
      }
      wine_tsx11_unlock();
    }
    else {
      /* window is not mapped, reparent window immediately. */
      if (lParam == (LPARAM)0 || lParam == (LPARAM)-1) {
        X11DRV_DD_Reparent(hwnd, 0);
      }
      if (lParam && lParam == win) {
        /* we waited for this, stop waiting */
        wine_tsx11_lock();
        XSelectInput(display, win, 0);
        wine_tsx11_unlock();
      }
    }
  }
  else {
    if (lParam && lParam == win) {
      /* should never happen, but... */
      wine_tsx11_lock();
      XSelectInput(display, win, 0);
      wine_tsx11_unlock();
    }
  }
end:
  WIN_ReleasePtr(wnd);
  return TRUE;
}

void X11DRV_DD_ContinueReparent(Window win)
{
  /* somewhat inefficient, but OK. Fullscreen apps don't have that many top-level windows. */
  EnumWindows(X11DRV_DD_StartReparent, win);
}

void X11DRV_DD_InstallNewRoot(void)
{
  HWND desktop = GetDesktopWindow();
  /* install new root window */
  X11DRV_DD_OldRootWindow = root_window;
  /* if (root_window == DefaultRootWindow(gdi_display)) */ {
    WND *deskwnd = WIN_GetPtr(desktop);
    struct x11drv_win_data *deskdata = deskwnd->pDriverData;
    root_window = X11DRV_DD_PrimaryClient;
    deskdata->whole_window = deskdata->client_window = root_window;
    WIN_ReleasePtr(deskwnd);

    /* Reparent all top level windows to our new root window */
    TSXSync(gdi_display, False);
    EnumWindows(X11DRV_DD_StartReparent, (LPARAM)0);
    TSXSync(thread_display(), False);
  }
}

void X11DRV_DD_InstallOldRoot(void)
{
  HWND desktop = GetDesktopWindow();
  /* restore old root window */
  root_window = X11DRV_DD_OldRootWindow;
  /* if (root_window == DefaultRootWindow(gdi_display)) */ {
    WND *deskwnd = WIN_GetPtr(desktop);
    struct x11drv_win_data *deskdata = deskwnd->pDriverData;
    deskdata->whole_window = deskdata->client_window = root_window;
    WIN_ReleasePtr(deskwnd);

    /* Reparent all top level windows to our new root window */
    TSXSync(gdi_display, False);
    EnumWindows(X11DRV_DD_StartReparent, (LPARAM)-1);
    TSXSync(thread_display(), False);
  }
  if (root_window != DefaultRootWindow(gdi_display)) {
    FIXME("can't resize desktop window yet\n");
  }
  X11DRV_DD_OldRootWindow = 0;
}

void X11DRV_DD_InstallCursor(void)
{
  EnumWindows(X11DRV_DD_Recursor, 0);
  TSXSync(thread_display(), False);
}

void X11DRV_DD_CreateDesktop(unsigned int width, unsigned int height)
{
  RECT rect;
  TRACE("(%d,%d)\n", width, height);
  if (!X11DRV_DD_RefCount++) {
    if (X11DRV_DD_Thread) {
      ERR("thread already exists\n");
      return;
    }

    /* initialize new desktop thread */
    if (pipe(X11DRV_DD_Pipe) < 0) {
      ERR("pipe failed, error=%s\n", strerror(errno));
      return;
    }
    X11DRV_DD_SyncH = CreateEventA(NULL, FALSE, FALSE, NULL);
    X11DRV_DD_Thread = CreateThread(NULL, 0,
				    X11DRV_DD_ThreadProc,
				    X11DRV_DD_Pipe, 0, &X11DRV_DD_ThreadID);
    X11DRV_DD_ThreadExit = CreateEventA(NULL, FALSE, FALSE, NULL);
    write(X11DRV_DD_Pipe[1], &width, sizeof(width));
    write(X11DRV_DD_Pipe[1], &height, sizeof(height));
    WaitForSingleObject(X11DRV_DD_SyncH, INFINITE);
    X11DRV_DD_InstallCursor();
  }
  else if (screen_width != width || screen_height != height) {
      if (root_window != DefaultRootWindow(gdi_display))
      {
          /* Probably due to the dummy mode for destroying ddraw frame window.
           * (See X11DRV_XF86VM_Init.)
           */
          if (width > 0 && height > 0)
              TSXResizeWindow(thread_display(), root_window, width, height);
      }
      else
      {
          /* Either root_window was already not the DefaultRootWindow,
           * or the first call to DD_CreateDesktop should have set it
           * to X11DRV_DD_PrimaryClient. */
          FIXME("I don't think this should happen.\n");
      }
  }

  /* change system metrics */
  screen_width = width;
  screen_height = height;
  /* change desktop window size */
  SetRect(&rect, 0, 0, screen_width, screen_height);
  WIN_SetRectangles(GetDesktopWindow(), &rect, &rect);

  if (X11DRV_DD_DeviceDesktop) {
    /* FIXME: should probably unmap all top-level windows here,
     * but for now, only unmap windows owned by current thread */
    TSXSync(gdi_display, False);
    EnumThreadWindows(GetCurrentThread(), X11DRV_DD_Remap, FALSE);
    TSXSync(thread_display(), False);
  }
}

void X11DRV_DD_DestroyDesktop(void)
{
  if (!X11DRV_DD_Thread) {
    ERR("thread doesn't exist\n");
    return;
  }
  TRACE("ref = %ld\n", X11DRV_DD_RefCount);

  if (X11DRV_DD_DeviceDesktop) {
    if (X11DRV_DD_DeviceWnd) {
      WND *win = WIN_GetPtr( X11DRV_DD_DeviceWnd );

      if (win && win != WND_OTHER_PROCESS) {
        /* restore old cooperative window position */
        if (X11DRV_DD_DeviceWndOK)
          WIN_SetRectangles(X11DRV_DD_DeviceWnd,
                            &X11DRV_DD_OldWindowRect,
                            &X11DRV_DD_OldClientRect);
        X11DRV_DD_DeviceWndOK = 0;

        WIN_ReleasePtr(win);
      }
    }

    /* FIXME: should probably map all top-level windows here,
     * but for now, only unmap windows owned by current thread */
    TSXSync(gdi_display, False);
    EnumThreadWindows(GetCurrentThread(), X11DRV_DD_Remap, TRUE);
    TSXSync(thread_display(), False);
  }

  if (!--X11DRV_DD_RefCount) {
    HWND desktop = GetDesktopWindow();
    RECT rect;

    /* change system metrics */
    screen_width = default_screen_width;
    screen_height = default_screen_height;
    /* change desktop window size */
    SetRect(&rect, 0, 0, screen_width, screen_height);
    WIN_SetRectangles(desktop, &rect, &rect);

    X11DRV_DD_InstallOldRoot();
    X11DRV_DD_InstallCursor();

    /* kill desktop thread */
    X11DRV_DD_PostCmd(DDMSG_QUIT);
    WaitForSingleObject(X11DRV_DD_ThreadExit, INFINITE);
    X11DRV_DD_ThreadID = 0;
    CloseHandle(X11DRV_DD_Thread);
    X11DRV_DD_Thread = 0;
    CloseHandle(X11DRV_DD_ThreadExit);
    X11DRV_DD_ThreadExit = 0;
    CloseHandle(X11DRV_DD_SyncH);
    close(X11DRV_DD_Pipe[1]);
    close(X11DRV_DD_Pipe[0]);
  }
}

void X11DRV_DD_UnmapNotify(HWND hwnd, XUnmapEvent *event)
{
  if (!hwnd) return;
  if (hwnd == X11DRV_DD_DeviceWnd && X11DRV_DD_PrimaryGbl) {
    if (!X11DRV_DD_DeviceWndOK) {
      RECT rect;

      /* save old cooperative window position */
      WIN_GetRectangles(X11DRV_DD_DeviceWnd,
                        &X11DRV_DD_OldWindowRect,
                        &X11DRV_DD_OldClientRect);
      X11DRV_DD_DeviceWndOK = 1;

      /* make the cooperative window "fullscreen" */
      rect.left = 0; rect.right = X11DRV_DD_PrimaryGbl->wWidth;
      rect.top = 0; rect.bottom = X11DRV_DD_PrimaryGbl->wHeight;
      WIN_SetRectangles(X11DRV_DD_DeviceWnd, &rect, &rect);
    }
  }
}

static void SetPrimaryDIB(HBITMAP hBmp)
{
  X11DRV_DD_PrimaryDIB = hBmp;
  if (hBmp) {
    BITMAPOBJ *bmp;
    bmp = (BITMAPOBJ *)GDI_GetObjPtr( hBmp, BITMAP_MAGIC );
    if (!bmp->physBitmap) X11DRV_CreateBitmap(hBmp);
    X11DRV_DD_PrimaryDrawable = (Pixmap)bmp->physBitmap;
    GDI_ReleaseObj( hBmp );
  } else {
    X11DRV_DD_PrimaryDrawable = 0;
  }
  TRACE("primary DIB: %08x\n", hBmp);
}

static void ClearDCE(void)
{
  RECT rect;
  if (!X11DRV_DD_DeviceWnd) return;
  GetWindowRect(X11DRV_DD_DeviceWnd, &rect);
  DCE_InvalidateDCE(X11DRV_DD_DeviceWnd, &rect);
}

void X11DRV_EnableGrabs(void)
{
  TRACE("(%ld)\n", X11DRV_DD_PrimaryFrame);
  if (!dxgrab) return;
  want_grab = TRUE;
  if (X11DRV_DD_PrimaryFrame) {
    X11DRV_DD_PostCmd(DDMSG_GRAB);
    return;
  }
}

void X11DRV_DisableGrabs(void)
{
  TRACE("(%ld)\n", X11DRV_DD_PrimaryFrame);
  if (!dxgrab) return;
  want_grab = FALSE;
  if (X11DRV_DD_PrimaryFrame) {
    X11DRV_DD_PostCmd(DDMSG_UNGRAB);
    return;
  }
}

void X11DRV_RetargetGrabs(void)
{
  TRACE("\n");
  if (!dxgrab) return;
  if (X11DRV_DD_PrimaryFrame) return;
  /* not needed anymore */
}

static DDHAL_DDCALLBACKS hal_ddcallbacks;

HRESULT X11DRV_DD_CreateDIB(LPDDRAWI_DDRAWSURFACE_LCL lcl, LPDDSURFACEDESC desc)
{
  LPDDRAWI_DDRAWSURFACE_GBL gbl = lcl->lpGbl;
  LPDDRAWI_DDRAWSURFACE_GBL_MORE gmore = GET_LPDDRAWSURFACE_GBL_MORE(gbl);
  BITMAPINFO* b_info;
  UINT usage;
  HDC ddc;
  LPVOID bitmap_data;
  DWORD pitch;

  if (gmore->hKernelSurface) return DD_OK; /* already allocated */

  switch (desc->ddpfPixelFormat.u1.dwRGBBitCount) {
  case 16:
  case 32:
    /* Allocate extra space to store the RGB bit masks. */
    b_info = (BITMAPINFO*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
				    sizeof(BITMAPINFOHEADER)
				    + 3 * sizeof(DWORD));
    break;

  case 24:
    b_info = (BITMAPINFO*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
				    sizeof(BITMAPINFOHEADER));
    break;

  default:
    /* Allocate extra space for a palette. */
    b_info = (BITMAPINFO*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
				    sizeof(BITMAPINFOHEADER)
				    + sizeof(RGBQUAD)
				    * (1 << desc->ddpfPixelFormat.u1.dwRGBBitCount));
    break;
  }

  b_info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  b_info->bmiHeader.biWidth = desc->dwWidth;
  b_info->bmiHeader.biHeight = -desc->dwHeight;
  b_info->bmiHeader.biPlanes = 1;
  b_info->bmiHeader.biBitCount = desc->ddpfPixelFormat.u1.dwRGBBitCount;

  if ((desc->ddpfPixelFormat.u1.dwRGBBitCount != 16)
      && (desc->ddpfPixelFormat.u1.dwRGBBitCount != 32))
    b_info->bmiHeader.biCompression = BI_RGB;
  else
    b_info->bmiHeader.biCompression = BI_BITFIELDS;

  b_info->bmiHeader.biSizeImage
    = (desc->ddpfPixelFormat.u1.dwRGBBitCount / 8)
    * desc->dwWidth * desc->dwHeight;

  b_info->bmiHeader.biXPelsPerMeter = 0;
  b_info->bmiHeader.biYPelsPerMeter = 0;
  b_info->bmiHeader.biClrUsed = 0;
  b_info->bmiHeader.biClrImportant = 0;

  switch (desc->ddpfPixelFormat.u1.dwRGBBitCount) {
  case 16:
  case 32:
    {
      DWORD *masks = (DWORD *) &(b_info->bmiColors);

      usage = 0;
      masks[0] = desc->ddpfPixelFormat.u2.dwRBitMask;
      masks[1] = desc->ddpfPixelFormat.u3.dwGBitMask;
      masks[2] = desc->ddpfPixelFormat.u4.dwBBitMask;
    }
    break;

  case 24:
    /* Nothing to do */
    usage = DIB_RGB_COLORS;
    break;

  default:
    /* Don't know palette */
    usage = 0;
    break;
  }

  ddc = CreateDCA("DISPLAY", NULL, NULL, NULL);
  if (ddc == 0) {
    HeapFree(GetProcessHeap(), 0, b_info);
    return HRESULT_FROM_WIN32(GetLastError());
  }

  pitch = (desc->dwFlags & DDSD_PITCH) ? desc->u1.lPitch : 0;
  gmore->hKernelSurface
    = DIB_CreateDIBSection(ddc, b_info, usage, &bitmap_data, 0,
			   (desc->dwFlags & DDSD_LPSURFACE) ?
                           (DWORD)desc->lpSurface : 0, pitch);
  DeleteDC(ddc);
  if (!gmore->hKernelSurface) {
    ERR("CreateDIBSection failed!\n");
    HeapFree(GetProcessHeap(), 0, b_info);
    return HRESULT_FROM_WIN32(GetLastError());
  }

  TRACE("DIBSection at : %p\n", bitmap_data);

  gbl->fpVidMem = (FLATPTR)bitmap_data;
  gbl->u4.lPitch = pitch ? pitch : get_dib_width_bytes(b_info->bmiHeader.biWidth,
							b_info->bmiHeader.biBitCount);

  HeapFree(GetProcessHeap(), 0, b_info);

  return S_OK;
}

static DWORD PASCAL X11DRV_DDHAL_DestroyDriver(LPDDHAL_DESTROYDRIVERDATA data)
{
  if (X11DRV_DD_Primary) {
    /* DestroySurface stuff */
    USER_Lock();
    ClearDCE();
    SetPrimaryDIB(0);

    if (X11DRV_DD_DeviceDesktop) {
      X11DRV_DD_DestroyDesktop();
      X11DRV_DD_DeviceDesktop = FALSE;
    }

    X11DRV_DD_DeviceWnd = 0;
    X11DRV_DD_DeviceWindow = 0;
    X11DRV_DD_Primary = NULL;
    X11DRV_DD_PrimaryGbl = NULL;
    USER_Unlock();
  }

  if (data->lpDD->dwFlags & DDRAWI_MODECHANGED) {
    DDHAL_SETMODEDATA mdata;

    /* restore default display mode */
    mdata.lpDD = data->lpDD;
    mdata.dwModeIndex = data->lpDD->dwModeIndexOrig;
    mdata.ddRVal = 0;
    mdata.SetMode = hal_ddcallbacks.SetMode;
    mdata.inexcl = 0;
    mdata.useRefreshRate = FALSE;
    if (mdata.SetMode)
      mdata.SetMode(&mdata);
  }
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static DWORD PASCAL X11DRV_DDHAL_CreateSurface(LPDDHAL_CREATESURFACEDATA data)
{
  BOOL dib = TRUE, force_dib = FALSE;
  DWORD ret = DDHAL_DRIVER_NOTHANDLED;

  TRACE("surface=%p\n", *data->lplpSList);
  TRACE("caps=%lx\n", data->lpDDSurfaceDesc->ddsCaps.dwCaps);

  if (data->lpDDSurfaceDesc->ddsCaps.dwCaps & DDSCAPS_EXECUTEBUFFER)
    dib = FALSE;
  else if (data->lpDDSurfaceDesc->ddpfPixelFormat.dwFlags & DDPF_FOURCC)
    dib = FALSE;

  if (data->lpDDSurfaceDesc->dwFlags & DDSD_LPSURFACE)
    /* the HEL shouldn't have to relocate client memory if it needs a DIB */
    force_dib = TRUE;
  else if (data->lpDDSurfaceDesc->ddsCaps.dwCaps & (DDSCAPS_PRIMARYSURFACE | DDSCAPS_BACKBUFFER))
    /* our update thread needs a DIB section */
    force_dib = TRUE;
  else if (data->lpDDSurfaceDesc->ddsCaps.dwCaps & (DDSCAPS_3DDEVICE | DDSCAPS_ZBUFFER))
    /* 3D stuff needs a DIB section */
    force_dib = TRUE;

  if (dib && force_dib) {
    DWORD c;
    for (c=0; c<data->dwSCnt; c++)
      X11DRV_DD_CreateDIB(data->lplpSList[c], data->lpDDSurfaceDesc);
  }

  if (data->lpDDSurfaceDesc->ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE) {
    USER_Lock();
    if (X11DRV_DD_Primary) {
      ERR("created multiple primary surfaces\n");
      X11DRV_DisableGrabs();

      if (X11DRV_DD_DeviceDesktop) {
	X11DRV_DD_DestroyDesktop();
	X11DRV_DD_DeviceDesktop = FALSE;
      }
    }
    X11DRV_DD_Primary = *data->lplpSList;
    X11DRV_DD_PrimaryGbl = X11DRV_DD_Primary->lpGbl;
    TRACE("primary=%p\n", X11DRV_DD_Primary);

    /* get the cooperative window */
    if (data->lpDD->lpExclusiveOwner)
    {
      X11DRV_DD_DeviceWnd = data->lpDD->lpExclusiveOwner->hWnd;
      X11DRV_DD_DeviceWindow = X11DRV_get_client_window(X11DRV_DD_DeviceWnd);
    }
    else
    {
      X11DRV_DD_DeviceWnd = 0;
      X11DRV_DD_DeviceWindow = 0;
    }

    if (X11DRV_DD_DeviceWnd) {
      X11DRV_DD_DeviceDesktop = TRUE;
      SetPrimaryDIB(GET_LPDDRAWSURFACE_GBL_MORE(X11DRV_DD_PrimaryGbl)->hKernelSurface);
      X11DRV_DD_CreateDesktop(data->lpDDSurfaceDesc->dwWidth,
			      data->lpDDSurfaceDesc->dwHeight);
    }
    else SetPrimaryDIB(0);

    ClearDCE();

    X11DRV_EnableGrabs();
    USER_Unlock();
  }

  data->ddRVal = DD_OK;
  return ret;
}

static DWORD PASCAL X11DRV_DDHAL_SetMode(LPDDHAL_SETMODEDATA data)
{
  X11DRV_DDHAL_SwitchMode(data->dwModeIndex, NULL, NULL, TRUE);
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static DWORD PASCAL X11DRV_DDHAL_CreatePalette(LPDDHAL_CREATEPALETTEDATA data)
{
  Display *display = gdi_display;
  TRACE("\n");
  if (visual->class == PseudoColor) {
    data->lpDDPalette->u1.dwReserved1 = TSXCreateColormap(display, root_window,
                                                          visual, AllocAll);
  }
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static DDHAL_DDCALLBACKS hal_ddcallbacks = {
  sizeof(DDHAL_DDCALLBACKS),
  0x3ff, /* all callbacks are 32-bit */
  X11DRV_DDHAL_DestroyDriver,
  X11DRV_DDHAL_CreateSurface,
  NULL, /* SetColorKey */
  X11DRV_DDHAL_SetMode,
  NULL, /* WaitForVerticalBlank */
  NULL, /* CanCreateSurface */
  X11DRV_DDHAL_CreatePalette,
  NULL, /* GetScanLine */
  NULL, /* SetExclusiveMode */
  NULL  /* FlipToGDISurface */
};

static DWORD PASCAL X11DRV_DDHAL_DestroySurface(LPDDHAL_DESTROYSURFACEDATA data)
{
  LPDDRAWI_DDRAWSURFACE_GBL gbl = data->lpDDSurface->lpGbl;
  LPDDRAWI_DDRAWSURFACE_GBL_MORE gmore = GET_LPDDRAWSURFACE_GBL_MORE(gbl);

  TRACE("surface=%p\n", data->lpDDSurface);
  if (data->lpDDSurface == X11DRV_DD_Primary) {
    USER_Lock();
    X11DRV_DisableGrabs();
    ClearDCE();
    SetPrimaryDIB(0);
    if (X11DRV_DD_DeviceDesktop) {
      X11DRV_DD_DestroyDesktop();
      X11DRV_DD_DeviceDesktop = FALSE;
    }
    X11DRV_DD_DeviceWnd = 0;
    X11DRV_DD_DeviceWindow = 0;
    X11DRV_DD_Primary = NULL;
    X11DRV_DD_PrimaryGbl = NULL;
    USER_Unlock();
  }
  if (gmore->hKernelSurface) {
    /* DeleteObject(gmore->hKernelSurface); */
    /* gmore->hKernelSurface = 0; */
  }
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static DWORD PASCAL X11DRV_DDHAL_Flip(LPDDHAL_FLIPDATA data)
{
  TRACE("surface=%p\n", data->lpSurfCurr);
  if (data->lpSurfCurr == X11DRV_DD_Primary) {
    /* data already flipped by ddraw, just need to update display */
    SetPrimaryDIB(GET_LPDDRAWSURFACE_GBL_MORE(X11DRV_DD_PrimaryGbl)->hKernelSurface);
    data->ddRVal = X11DRV_DD_Sync(data->dwFlags & DDFLIP_WAIT);
    if (data->ddRVal == DD_OK)
      X11DRV_DD_Update();
    return DDHAL_DRIVER_HANDLED;
  }
  return DDHAL_DRIVER_NOTHANDLED;
}

static DWORD PASCAL X11DRV_DDHAL_Lock(LPDDHAL_LOCKDATA data)
{
  TRACE("surface=%p\n", data->lpDDSurface);

  /* flush any GDI operations here, since it's possible that this
   * lock is for copying from a DIB section to a ddraw surface,
   * and in that case we need the GDI operations on the DIB sections
   * to be completed first, particularly if ShmPixmaps are in use,
   * as this may be the only chance to do it before it's too late */
  wine_tsx11_lock();
  /* a full sync is somewhat expensive, try to only do it if there
   * might be unprocessed GDI requests at this point */
  if (NextRequest(gdi_display) != (LastKnownRequestProcessed(gdi_display)+1))
  {
    TRACE("performing GDI sync\n");
    XSync(gdi_display, False);
  }
  wine_tsx11_unlock();

  if (data->lpDDSurface == X11DRV_DD_Primary) {
#if 1
    data->ddRVal = DD_OK;
#else
    data->ddRVal = X11DRV_DD_Sync(data->dwFlags & DDLOCK_WAIT);
#endif
    if (data->ddRVal == DD_OK)
    {
      if( InterlockedIncrement(&X11DRV_DD_Lock) == 1 )
      {
        /* Stash the flags used for the lock so we can see them on unlock */
        X11DRV_DDHAL_LockFlags = data->dwFlags;
      }
      else
      {
        /* There seem to be mulitiple nested locks going on - too complicated
         * to get right. Just clear the flags and we'll always do an update.
         */
        X11DRV_DDHAL_LockFlags = 0;
      }
    }

    return DDHAL_DRIVER_HANDLED;
  }
  return DDHAL_DRIVER_NOTHANDLED;
}

static DWORD PASCAL X11DRV_DDHAL_Unlock(LPDDHAL_UNLOCKDATA data)
{
  TRACE("surface=%p\n", data->lpDDSurface);
  if (data->lpDDSurface == X11DRV_DD_Primary) {
  
    /* If we were only locking for reading, there is no need 
     * to update the primary surface.
     */
    InterlockedDecrement(&X11DRV_DD_Lock);
    if( !(X11DRV_DDHAL_LockFlags & DDLOCK_READONLY) )
    {
      X11DRV_DD_Update();
    }

    data->ddRVal = DD_OK;
    return DDHAL_DRIVER_HANDLED;
  }
  return DDHAL_DRIVER_NOTHANDLED;
}

static DWORD PASCAL X11DRV_DDHAL_SetPalette(LPDDHAL_SETPALETTEDATA data)
{
  Colormap colormap = None;

  TRACE("surface=%p palette=%p\n", data->lpDDSurface, data->lpDDPalette);
  if (data->lpDDPalette)
    colormap = data->lpDDPalette->u1.dwReserved1;
  if (!colormap)
    colormap = X11DRV_PALETTE_PaletteXColormap;
  if (X11DRV_DD_Primary && data->lpDDSurface == X11DRV_DD_Primary) {
    if (visual->class == PseudoColor) {
      wine_tsx11_lock();
      XSetWindowColormap(gdi_display, X11DRV_DD_PrimaryClient, colormap);
      XSetWindowColormap(gdi_display, X11DRV_DD_PrimaryFrame, colormap);
      XFlush(gdi_display); /* update display immediately */
      wine_tsx11_unlock();
    } else
      X11DRV_DD_Update();
  }
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static DDHAL_DDSURFACECALLBACKS hal_ddsurfcallbacks = {
  sizeof(DDHAL_DDSURFACECALLBACKS),
  0x3fff, /* all callbacks are 32-bit */
  X11DRV_DDHAL_DestroySurface,
  X11DRV_DDHAL_Flip,
  NULL, /* SetClipList */
  X11DRV_DDHAL_Lock,
  X11DRV_DDHAL_Unlock,
  NULL, /* Blt */
  NULL, /* SetColorKey */
  NULL, /* AddAttachedSurface */
  NULL, /* GetBltStatus */
  NULL, /* GetFlipStatus */
  NULL, /* UpdateOverlay */
  NULL, /* SetOverlayPosition */
  NULL, /* reserved4 */
  X11DRV_DDHAL_SetPalette
};

static DWORD PASCAL X11DRV_DDHAL_DestroyPalette(LPDDHAL_DESTROYPALETTEDATA data)
{
  Colormap pal = data->lpDDPalette->u1.dwReserved1;
  TRACE("palette=%p\n", data->lpDDPalette);
  if (pal) TSXFreeColormap(gdi_display, pal);
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static DWORD PASCAL X11DRV_DDHAL_SetPaletteEntries(LPDDHAL_SETENTRIESDATA data)
{
  TRACE("palette=%p\n", data->lpDDPalette);
  X11DRV_DDHAL_SetPalEntries(data->lpDDPalette->u1.dwReserved1,
			     data->dwBase, data->dwNumEntries,
			     data->lpEntries);
  if (data->lpDDPalette->dwFlags & DDPCAPS_PRIMARYSURFACE) {
    if (!data->lpDDPalette->u1.dwReserved1)
      X11DRV_DD_Update();
  }
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static DDHAL_DDPALETTECALLBACKS hal_ddpalcallbacks = {
  sizeof(DDHAL_DDPALETTECALLBACKS),
  0x3, /* all callbacks are 32-bit */
  X11DRV_DDHAL_DestroyPalette,
  X11DRV_DDHAL_SetPaletteEntries
};

static X11DEVICE x11device = {
  NULL
};

static DWORD PASCAL X11DRV_DDHAL_GetDriverInfo(LPDDHAL_GETDRIVERINFODATA data)
{
  LPX11DRIVERINFO info = x11device.lpInfo;
  while (info) {
    if (IsEqualGUID(&data->guidInfo, info->lpGuid)) {
      DWORD dwSize = info->dwSize;
      data->dwActualSize = dwSize;
      if (data->dwExpectedSize < dwSize) dwSize = data->dwExpectedSize;
      memcpy(data->lpvData, info->lpvData, dwSize);
      data->ddRVal = DD_OK;
      return DDHAL_DRIVER_HANDLED;
    }
    info = info->lpNext;
  }
  data->ddRVal = DDERR_CURRENTLYNOTAVAIL;
  return DDHAL_DRIVER_HANDLED;
}

static DDHALINFO hal_info = {
  sizeof(DDHALINFO),
  &hal_ddcallbacks,
  &hal_ddsurfcallbacks,
  &hal_ddpalcallbacks,
  {	/* vmiData */
   0	 /* fpPrimary */
  },
  {	/* ddCaps (only stuff the HAL implements here) */
   sizeof(DDCORECAPS),							/* dwSize */
   DDCAPS_GDI | DDCAPS_PALETTE,						/* dwCaps */
   DDCAPS2_CERTIFIED | DDCAPS2_NONLOCALVIDMEM | DDCAPS2_NOPAGELOCKREQUIRED |
   DDCAPS2_WIDESURFACES | DDCAPS2_FLIPNOVSYNC,				/* dwCaps2 */
   0,									/* dwCKeyCaps */
   0,									/* dwFXCaps */
   0,									/* dwFXAlphaCaps */
   DDPCAPS_8BIT | DDPCAPS_PRIMARYSURFACE,				/* dwPalCaps */
   0,									/* dwSVCaps */
   0,									/* dwAlphaBltConstBitDepths */
   0,									/* dwAlphaBltPixelBitDepths */
   0,									/* dwAlphaBltSurfaceBitDepths */
   0,									/* dwAlphaOverlayBltConstBitDepths */
   0,									/* dwAlphaOverlayBltPixelBitDepths */
   0,									/* dwAlphaOverlayBltSurfaceBitDepths */
   0,									/* dwZBufferBitDepths */
   0,									/* dwVidMemTotal */
   0,									/* dwVidMemFree */
   0,									/* dwMaxVisibleOverlays */
   0,									/* dwCurrVisibleOverlays */
   0,									/* dwNumFourCCCodes */
   0,									/* dwAlignBoundarySrc */
   0,									/* dwAlignSizeSrc */
   0,									/* dwAlignBoundaryDest */
   0,									/* dwAlignSizeDest */
   0,									/* dwAlignStrideAlign */
   {0},									/* dwRops */
   {									/* ddsCaps */
    DDSCAPS_BACKBUFFER | DDSCAPS_FLIP | DDSCAPS_FRONTBUFFER |
    DDSCAPS_OFFSCREENPLAIN | DDSCAPS_PALETTE | DDSCAPS_PRIMARYSURFACE |
    DDSCAPS_SYSTEMMEMORY | DDSCAPS_TEXTURE | DDSCAPS_VIDEOMEMORY |
    DDSCAPS_VISIBLE | DDSCAPS_LOCALVIDMEM | DDSCAPS_NONLOCALVIDMEM	/* dwCaps */
   },
   0,									/* dwMinOverlayStretch */
   0,									/* dwMaxOverlayStretch */
   0,									/* dwMinLiveVideoStretch */
   0,									/* dwMaxLiveVideoStretch */
   0,									/* dwMinHwCodecStretch */
   0,									/* dwMaxHwCodecStretch */
   0,									/* dwReserved1 */
   0,									/* dwReserved2 */
   0,									/* dwReserved2 */
   0,									/* dwSVBCaps */
   0,									/* dwSVBCKeyCaps */
   0,									/* dwSVBFXCaps */
   {0},									/* dwSVBRops */
   0,									/* dwVSBCaps */
   0,									/* dwVSBCKeyCaps */
   0,									/* dwVSBFXCaps */
   {0},									/* dwVSBRops */
   0,									/* dwSSBCaps */
   0,									/* dwSSBCKeyCaps */
   0,									/* dwSSBFXCaps */
   {0},									/* dwSSBRops */
   0,									/* dwMaxVideoPorts */
   0,									/* dwCurrVideoPorts */
   0									/* dwSVBCaps */
  },
  0,	/* dwMonitorFrequency */
  X11DRV_DDHAL_GetDriverInfo,
  0,	/* dwModeIndex */
  NULL,	/* lpdwFourCC */
  0,	/* dwNumModes */
  NULL,	/* lpModeInfo */
  DDHALINFO_ISPRIMARYDISPLAY | DDHALINFO_MODEXILLEGAL | DDHALINFO_GETDRIVERINFOSET, /* dwFlags */
  &x11device,
  0,	/* hInstance */
  0,	/* lpD3DGlobalDriverData */
  0,	/* lpD3DHALCallbacks */
  NULL	/* lpDDExeBufCallbacks */
};

static LPDDHALDDRAWFNS ddraw_fns;
static DWORD ddraw_ver;
static BOOL driver_created;

static inline void X11DRV_DDHAL_SetInfo(void)
{
  (ddraw_fns->lpSetInfo)(&hal_info, FALSE);
}

extern int __wine_hook_user_signal( void (*func)(CONTEXT*) );

static void X11DRV_UserSignal(CONTEXT*ctx)
{
  /* we could generalize this later */
#ifdef HAVE_OPENGL
#ifndef HAVE_OSMESA
  X11DRV_GLX_UserSignal(ctx);
#endif
#endif
}

void X11DRV_DD_PrimaryLock( BOOL readonly )
{
  DDHAL_LOCKDATA data;
  if (!X11DRV_DD_Primary) return;
  memset(&data, 0, sizeof(data));
  data.lpDDSurface = X11DRV_DD_Primary;
  if( readonly ) data.dwFlags = DDLOCK_READONLY;
  hal_info.lpDDSurfaceCallbacks->Lock(&data);
}

void X11DRV_DD_PrimaryUnlock(void)
{
  DDHAL_UNLOCKDATA data;
  if (!X11DRV_DD_Primary) return;
  memset(&data, 0, sizeof(data));
  data.lpDDSurface = X11DRV_DD_Primary;
  hal_info.lpDDSurfaceCallbacks->Unlock(&data);
}

void X11DRV_DD_PrimaryReset(void)
{
  if (!X11DRV_DD_Primary) return;
  SetPrimaryDIB(GET_LPDDRAWSURFACE_GBL_MORE(X11DRV_DD_PrimaryGbl)->hKernelSurface);
}

INT X11DRV_DCICommand(INT cbInput, const DCICMD *lpCmd, LPVOID lpOutData)
{
  TRACE("(%d,(%ld,%ld,%ld),%p)\n", cbInput, lpCmd->dwCommand,
	lpCmd->dwParam1, lpCmd->dwParam2, lpOutData);

  switch (lpCmd->dwCommand) {
  case DDNEWCALLBACKFNS:
    ddraw_fns = (LPDDHALDDRAWFNS)lpCmd->dwParam1;
    return TRUE;
  case DDVERSIONINFO:
    {
      LPDDVERSIONDATA lpVer = (LPDDVERSIONDATA)lpOutData;
      ddraw_ver = lpCmd->dwParam1;
      if (!lpVer) break;
      /* well, whatever... the DDK says so */
      lpVer->dwHALVersion = DD_RUNTIME_VERSION;
    }
    return TRUE;
  case DDGET32BITDRIVERNAME:
    {
      LPDD32BITDRIVERDATA lpData = (LPDD32BITDRIVERDATA)lpOutData;
      /* here, we could ask ddraw to load a separate DLL, that
       * would contain the 32-bit ddraw HAL */
      strcpy(lpData->szName,"x11drv");
      /* the entry point named here should initialize our hal_info
       * with 32-bit entry points (ignored for now) */
      strcpy(lpData->szEntryPoint,"DriverInit");
      lpData->dwContext = 0;
    }
    return TRUE;
  case DDCREATEDRIVEROBJECT:
    {
      LPDWORD lpInstance = (LPDWORD)lpOutData;

      /* FIXME: get x11drv's hInstance */

      if (!driver_created) {
        __wine_hook_user_signal(X11DRV_UserSignal);

      hal_info.lpModeInfo = X11DRV_DD_ModeSwitch.Modes;
      hal_info.dwNumModes = X11DRV_DD_ModeSwitch.NumModes;
      hal_info.ddCaps.dwVidMemTotal = videoram * 1024 * 1024;
      hal_info.ddCaps.dwVidMemFree = videoram * 1024 * 1024;

#ifdef HAVE_LIBXXF86DGA2
	X11DRV_XF86DGA2_CreateDriver(&hal_info);
#endif
#ifdef HAVE_LIBXXF86VM
	  X11DRV_XF86VM_CreateDriver(&hal_info);
#endif
#ifdef HAVE_LIBXRANDR
	  X11DRV_XF86RR_CreateDriver(&hal_info);
#endif

#ifdef HAVE_OPENGL
#ifndef HAVE_OSMESA
	X11DRV_GLX_CreateDriver(&hal_info);
#else
	X11DRV_OSMesa_CreateDriver(&hal_info);
#endif
#endif
        driver_created = TRUE;
      }

      X11DRV_DDHAL_SetInfo();
      *lpInstance = hal_info.hInstance;
    }
    return TRUE;
  }
  return 0;
}

void X11DRV_DDHAL_SwitchMode(DWORD dwModeIndex, LPVOID fb_addr, LPVIDMEM fb_mem, BOOL desktop)
{
  LPDDHALMODEINFO info = &hal_info.lpModeInfo[dwModeIndex];

  if (desktop) {
    /* get out of previous mode, if any. */
    if (X11DRV_DD_Thread) {
      screen_width = default_screen_width;
      screen_height = default_screen_height;
      X11DRV_DisableGrabs();
      X11DRV_DD_DestroyDesktop();
    }
    /* initialize new mode, if any */
    if (dwModeIndex) {
      X11DRV_DD_CreateDesktop(info->dwWidth, info->dwHeight);
      X11DRV_EnableGrabs();
    }
  }

  hal_info.dwModeIndex        = dwModeIndex;
  hal_info.dwMonitorFrequency = info->wRefreshRate;
  hal_info.vmiData.fpPrimary  = (FLATPTR)fb_addr;
  hal_info.vmiData.dwDisplayWidth  = info->dwWidth;
  hal_info.vmiData.dwDisplayHeight = info->dwHeight;
  hal_info.vmiData.lDisplayPitch   = info->lPitch;
  hal_info.vmiData.ddpfDisplay.dwSize = info->dwBPP ? sizeof(hal_info.vmiData.ddpfDisplay) : 0;
  hal_info.vmiData.ddpfDisplay.dwFlags = (info->wFlags & DDMODEINFO_PALETTIZED) ? DDPF_PALETTEINDEXED8 : 0;
  hal_info.vmiData.ddpfDisplay.u1.dwRGBBitCount = (info->dwBPP > 24) ? 24 : info->dwBPP;
  hal_info.vmiData.ddpfDisplay.u2.dwRBitMask = info->dwRBitMask;
  hal_info.vmiData.ddpfDisplay.u3.dwGBitMask = info->dwGBitMask;
  hal_info.vmiData.ddpfDisplay.u4.dwBBitMask = info->dwBBitMask;
  hal_info.vmiData.dwNumHeaps = fb_mem ? 1 : 0;
  hal_info.vmiData.pvmList = fb_mem;

  X11DRV_DDHAL_SetInfo();
}

void X11DRV_DDHAL_SetPalEntries(Colormap pal, DWORD dwBase, DWORD dwNumEntries,
				LPPALETTEENTRY lpEntries)
{
  XColor c;
  int n;

  if (pal) {
    wine_tsx11_lock();
    c.flags = DoRed|DoGreen|DoBlue;
    c.pixel = dwBase;
    for (n=0; n<dwNumEntries; n++,c.pixel++) {
      c.red   = lpEntries[n].peRed   << 8;
      c.green = lpEntries[n].peGreen << 8;
      c.blue  = lpEntries[n].peBlue  << 8;
      XStoreColor(gdi_display, pal, &c);
    }
    XFlush(gdi_display); /* update display immediately */
    wine_tsx11_unlock();
  }
}
