/*
 * X11 event driver
 *
 * Copyright 1993 Alexandre Julliard
 */

#include "config.h"

#ifndef X_DISPLAY_MISSING

#include <X11/Xatom.h>
#include <X11/keysym.h>
#include "ts_xlib.h"
#include "ts_xresource.h"
#include "ts_xutil.h"

#include <assert.h>
#include <string.h>
#include "callback.h"
#include "clipboard.h"
#include "dce.h"
#include "debugtools.h"
#include "drive.h"
#include "heap.h"
#include "keyboard.h"
#include "message.h"
#include "mouse.h"
#include "options.h"
#include "queue.h"
#include "shell.h"
#include "winpos.h"
#include "services.h"
#include "file.h"
#include "windef.h"
#include "x11drv.h"

DECLARE_DEBUG_CHANNEL(event)
DECLARE_DEBUG_CHANNEL(win)
  
/* X context to associate a hwnd to an X window */
extern XContext winContext;

extern Atom wmProtocols;
extern Atom wmDeleteWindow;
extern Atom dndProtocol;
extern Atom dndSelection;

extern void X11DRV_KEYBOARD_UpdateState(void);
extern void X11DRV_KEYBOARD_HandleEvent(WND *pWnd, XKeyEvent *event);

#define NB_BUTTONS      3     /* Windows can handle 3 buttons */

#define DndNotDnd       -1    /* OffiX drag&drop */
#define DndUnknown      0
#define DndRawData      1
#define DndFile         2
#define DndFiles        3
#define DndText         4
#define DndDir          5
#define DndLink         6
#define DndExe          7

#define DndEND          8

#define DndURL          128   /* KDE drag&drop */

/* The last X window which had the focus */
static Window glastXFocusWin = 0;

static const char * const event_names[] =
{
  "", "", "KeyPress", "KeyRelease", "ButtonPress", "ButtonRelease",
  "MotionNotify", "EnterNotify", "LeaveNotify", "FocusIn", "FocusOut",
  "KeymapNotify", "Expose", "GraphicsExpose", "NoExpose", "VisibilityNotify",
  "CreateNotify", "DestroyNotify", "UnmapNotify", "MapNotify", "MapRequest",
  "ReparentNotify", "ConfigureNotify", "ConfigureRequest", "GravityNotify",
  "ResizeRequest", "CirculateNotify", "CirculateRequest", "PropertyNotify",
  "SelectionClear", "SelectionRequest", "SelectionNotify", "ColormapNotify",
  "ClientMessage", "MappingNotify"
};

static void CALLBACK EVENT_Flush( ULONG_PTR arg );
static void CALLBACK EVENT_ProcessAllEvents( ULONG_PTR arg );
static void EVENT_ProcessEvent( XEvent *event );

  /* Event handlers */
static void EVENT_Key( HWND hWnd, XKeyEvent *event );
static void EVENT_ButtonPress( HWND hWnd, XButtonEvent *event );
static void EVENT_ButtonRelease( HWND hWnd, XButtonEvent *event );
static void EVENT_MotionNotify( HWND hWnd, XMotionEvent *event );
static void EVENT_FocusIn( HWND hWnd, XFocusChangeEvent *event );
static void EVENT_FocusOut( HWND hWnd, XFocusChangeEvent *event );
static void EVENT_Expose( HWND hWnd, XExposeEvent *event );
static void EVENT_GraphicsExpose( HWND hWnd, XGraphicsExposeEvent *event );
static void EVENT_ConfigureNotify( HWND hWnd, XConfigureEvent *event );
static void EVENT_SelectionRequest( HWND hWnd, XSelectionRequestEvent *event);
static void EVENT_SelectionClear( HWND hWnd, XSelectionClearEvent *event);
static void EVENT_ClientMessage( HWND hWnd, XClientMessageEvent *event );
static void EVENT_MapNotify( HWND pWnd, XMapEvent *event );
static void EVENT_UnmapNotify( HWND pWnd, XUnmapEvent *event );

/* Usable only with OLVWM - compile option perhaps?
static void EVENT_EnterNotify( HWND hWnd, XCrossingEvent *event );
*/

static void EVENT_GetGeometry( Window win, int *px, int *py,
                               unsigned int *pwidth, unsigned int *pheight );


static BOOL bUserRepaintDisabled = TRUE;


/***********************************************************************
 *           EVENT_Init
 */
BOOL X11DRV_EVENT_Init(void)
{
    /* Install the X event processing callback */
    SERVICE_AddObject( FILE_DupUnixHandle( ConnectionNumber(display), 
                                           GENERIC_READ | SYNCHRONIZE ),
                       EVENT_ProcessAllEvents, 0 );

    /* Install the XFlush timer callback */
    if ( Options.synchronous ) 
        TSXSynchronize( display, True );
    else
        SERVICE_AddTimer( 200000L, EVENT_Flush, 0 );

    return TRUE;
}

/***********************************************************************
 *           EVENT_Flush
 */
static void CALLBACK EVENT_Flush( ULONG_PTR arg )
{
    TSXFlush( display );
}

/***********************************************************************
 *           EVENT_ProcessAllEvents
 */
static void CALLBACK EVENT_ProcessAllEvents( ULONG_PTR arg )
{
    XEvent event;
  
    TRACE_(event)( "called.\n" );

    EnterCriticalSection( &X11DRV_CritSection );
    while ( XPending( display ) )
    {
        XNextEvent( display, &event );
      
        LeaveCriticalSection( &X11DRV_CritSection );
        EVENT_ProcessEvent( &event );
        EnterCriticalSection( &X11DRV_CritSection );
    }
    LeaveCriticalSection( &X11DRV_CritSection );
}

/***********************************************************************
 *           EVENT_Synchronize
 *
 * Synchronize with the X server. Should not be used too often.
 */
void X11DRV_EVENT_Synchronize( void )
{
    TSXSync( display, False );
    EVENT_ProcessAllEvents( 0 );
}

/***********************************************************************
 *           EVENT_UserRepaintDisable
 */
void X11DRV_EVENT_UserRepaintDisable( BOOL bDisabled )
{
    bUserRepaintDisabled = bDisabled;
}

/***********************************************************************
 *           EVENT_ProcessEvent
 *
 * Process an X event.
 */
static void EVENT_ProcessEvent( XEvent *event )
{
  HWND hWnd;

  TRACE_(event)( "called.\n" );

  switch (event->type)
  {
    case SelectionNotify: /* all of these should be caught by XCheckTypedWindowEvent() */
	 FIXME_(event)("Got SelectionNotify - must not happen!\n");
	 /* fall through */

      /* We get all these because of StructureNotifyMask.
         This check is placed here to avoid getting error messages below,
         as X might send some of these even for windows that have already
         been deleted ... */
    case CirculateNotify:
    case CreateNotify:
    case DestroyNotify:
    case GravityNotify:
    case ReparentNotify:
      return;
  }
      
  if ( TSXFindContext( display, event->xany.window, winContext,
		       (char **)&hWnd ) != 0) {
    if ( event->type == ClientMessage) {
      /* query window (drag&drop event contains only drag window) */
      Window   	root, child;
      int      	root_x, root_y, child_x, child_y;
      unsigned	u;
      TSXQueryPointer( display, X11DRV_GetXRootWindow(), &root, &child,
		       &root_x, &root_y, &child_x, &child_y, &u);
      if (TSXFindContext( display, child, winContext, (char **)&hWnd ) != 0)
	return;
    } else {
      hWnd = 0;  /* Not for a registered window */
    }
  }

  if ( !hWnd && event->xany.window != X11DRV_GetXRootWindow() )
      ERR_(event)("Got event %s for unknown Window %08lx\n",
           event_names[event->type], event->xany.window );
  else
      TRACE_(event)("Got event %s for hwnd %04x\n",
	     event_names[event->type], hWnd );

  
  switch(event->type)
    {
    case KeyPress:
    case KeyRelease:
      EVENT_Key( hWnd, (XKeyEvent*)event );
      break;
      
    case ButtonPress:
      EVENT_ButtonPress( hWnd, (XButtonEvent*)event );
      break;
      
    case ButtonRelease:
      EVENT_ButtonRelease( hWnd, (XButtonEvent*)event );
      break;
      
    case MotionNotify:
      /* Wine between two fast machines across the overloaded campus
	 ethernet gets very boged down in MotionEvents. The following
	 simply finds the last motion event in the queue and drops
	 the rest. On a good link events are servered before they build
	 up so this doesn't take place. On a slow link this may cause
	 problems if the event order is important. I'm not yet seen
	 of any problems. Jon 7/6/96.
      */
      while (TSXCheckTypedWindowEvent(display,((XAnyEvent *)event)->window,
				      MotionNotify, event));    
      EVENT_MotionNotify( hWnd, (XMotionEvent*)event );
      break;
      
    case FocusIn:
    {
      WND *pWndLastFocus = 0;
      XWindowAttributes win_attr;
      BOOL bIsDisabled;
      XFocusChangeEvent *xfocChange = (XFocusChangeEvent*)event;

      if (!hWnd || bUserRepaintDisabled) return;

      bIsDisabled = GetWindowLongA( hWnd, GWL_STYLE ) & WS_DISABLED;

      /* If the window has been disabled and we are in managed mode,
       * revert the X focus back to the last focus window. This is to disallow
       * the window manager from switching focus away while the app is
       * in a modal state.
       */
      if ( Options.managed && bIsDisabled && glastXFocusWin)
      {
        /* Change focus only if saved focus window is registered and viewable */
        if ( TSXFindContext( xfocChange->display, glastXFocusWin, winContext,
                             (char **)&pWndLastFocus ) == 0 )
        {
          if ( TSXGetWindowAttributes( display, glastXFocusWin, &win_attr ) &&
                 (win_attr.map_state == IsViewable) )
          {
            TSXSetInputFocus( xfocChange->display, glastXFocusWin, RevertToParent, CurrentTime );
            EVENT_Synchronize();
      break;
          }
        }
      }
       
      EVENT_FocusIn( hWnd, xfocChange );
      break;
    }
      
    case FocusOut:
    {
      /* Save the last window which had the focus */
      XFocusChangeEvent *xfocChange = (XFocusChangeEvent*)event;
      glastXFocusWin = xfocChange->window;
      if (!hWnd || bUserRepaintDisabled) return;
      EVENT_FocusOut( hWnd, (XFocusChangeEvent*)event );
      break;
    }
      
    case Expose:
      if (bUserRepaintDisabled) return;
      EVENT_Expose( hWnd, (XExposeEvent *)event );
      break;
      
    case GraphicsExpose:
      if (bUserRepaintDisabled) return;
      EVENT_GraphicsExpose( hWnd, (XGraphicsExposeEvent *)event );
      break;
      
    case ConfigureNotify:
      if (!hWnd || bUserRepaintDisabled) return;
      EVENT_ConfigureNotify( hWnd, (XConfigureEvent*)event );
      break;

    case SelectionRequest:
      if (!hWnd || bUserRepaintDisabled) return;
      EVENT_SelectionRequest( hWnd, (XSelectionRequestEvent *)event );
      break;

    case SelectionClear:
      if (!hWnd || bUserRepaintDisabled) return;
      EVENT_SelectionClear( hWnd, (XSelectionClearEvent*) event );
      break;
      
    case ClientMessage:
      if (!hWnd || bUserRepaintDisabled) return;
      EVENT_ClientMessage( hWnd, (XClientMessageEvent *) event );
      break;

#if 0
    case EnterNotify:
      EVENT_EnterNotify( hWnd, (XCrossingEvent *) event );
      break;
#endif

    case NoExpose:
      break;
      
    case MapNotify:
      if (!hWnd || bUserRepaintDisabled) return;
      EVENT_MapNotify( hWnd, (XMapEvent *)event );
      break;

    case UnmapNotify:
      if (!hWnd || bUserRepaintDisabled) return;
      EVENT_UnmapNotify( hWnd, (XUnmapEvent *)event );
      break;

    default:    
      WARN_(event)("Unprocessed event %s for hwnd %04x\n",
	   event_names[event->type], hWnd );
      break;
    }
}

/***********************************************************************
 *           EVENT_QueryZOrder
 *
 * Synchronize internal z-order with the window manager's.
 */
static BOOL __check_query_condition( WND** pWndA, WND** pWndB )
{
  /* return TRUE if we have at least two managed windows */
  
  for( *pWndB = NULL; *pWndA; *pWndA = (*pWndA)->next )
    if( (*pWndA)->flags & WIN_MANAGED &&
	(*pWndA)->dwStyle & WS_VISIBLE ) break;
  if( *pWndA )
    for( *pWndB = (*pWndA)->next; *pWndB; *pWndB = (*pWndB)->next )
      if( (*pWndB)->flags & WIN_MANAGED &&
	  (*pWndB)->dwStyle & WS_VISIBLE ) break;
  return ((*pWndB) != NULL);
}

static Window __get_common_ancestor( Window A, Window B,
                                     Window** children, unsigned* total )
{
    /* find the real root window */
  
    Window      root, *childrenB;
    unsigned    totalB;
  
    do
    {
      TSXQueryTree( display, A, &root, &A, children, total );
      TSXQueryTree( display, B, &root, &B, &childrenB, &totalB );
      if( childrenB ) TSXFree( childrenB );
      if( *children ) TSXFree( *children ), *children = NULL;
    } while( A != B && A && B );

    if( A && B )
    {
	TSXQueryTree( display, A, &root, &B, children, total );
	return A;
    }
    return 0 ;
}

static Window __get_top_decoration( Window w, Window ancestor )
{
  Window*     children, root, prev = w, parent = w;
  unsigned    total;
  
  do
    {
      w = parent;
      TSXQueryTree( display, w, &root, &parent, &children, &total );
      if( children ) TSXFree( children );
    } while( parent && parent != ancestor );
  TRACE_(event)("\t%08x -> %08x\n", (unsigned)prev, (unsigned)w );
  return ( parent ) ? w : 0 ;
}

static unsigned __td_lookup( Window w, Window* list, unsigned max )
{
  unsigned    i;
  for( i = max - 1; i >= 0; i-- ) if( list[i] == w ) break;
  return i;
}

static HWND EVENT_QueryZOrder( HWND hWndCheck)
{
  HWND      hwndInsertAfter = HWND_TOP;
  WND      *pWndCheck = WIN_FindWndPtr(hWndCheck);
  WND      *pDesktop = WIN_GetDesktop();
  WND      *pWnd, *pWndZ = WIN_LockWndPtr(pDesktop->child);
  Window      w, parent, *children = NULL;
  unsigned    total, check, pos, best;
  
  if( !__check_query_condition(&pWndZ, &pWnd) )
  {
      WIN_ReleaseWndPtr(pWndCheck);
      WIN_ReleaseWndPtr(pDesktop->child);
      WIN_ReleaseDesktop();
      return hwndInsertAfter;
  }
  WIN_LockWndPtr(pWndZ);
  WIN_LockWndPtr(pWnd);
  WIN_ReleaseWndPtr(pDesktop->child);
  WIN_ReleaseDesktop();
  
  parent = __get_common_ancestor( X11DRV_WND_GetXWindow(pWndZ), 
				  X11DRV_WND_GetXWindow(pWnd),
				  &children, &total );
  if( parent && children )
  {
      /* w is the ancestor if pWndCheck that is a direct descendant of 'parent' */

      w = __get_top_decoration( X11DRV_WND_GetXWindow(pWndCheck), parent );

      if( w != children[total-1] ) /* check if at the top */
      {
	  /* X child at index 0 is at the bottom, at index total-1 is at the top */
	  check = __td_lookup( w, children, total );
	  best = total;

          for( WIN_UpdateWndPtr(&pWnd,pWndZ); pWnd;WIN_UpdateWndPtr(&pWnd,pWnd->next))
          {
	      /* go through all windows in Wine z-order... */

	      if( pWnd != pWndCheck )
              {
		  if( !(pWnd->flags & WIN_MANAGED) ||
		      !(w = __get_top_decoration( X11DRV_WND_GetXWindow(pWnd), parent )) )
		    continue;
		  pos = __td_lookup( w, children, total );
		  if( pos < best && pos > check )
                  {
		      /* find a nearest Wine window precedes 
		       * pWndCheck in the real z-order... */
		      best = pos;
		      hwndInsertAfter = pWnd->hwndSelf;
                  }
		  if( best - check == 1 ) break;
              }
          }
      }
  }
  if( children ) TSXFree( children );
  WIN_ReleaseWndPtr(pWnd);
  WIN_ReleaseWndPtr(pWndZ);
  WIN_ReleaseWndPtr(pWndCheck);
  return hwndInsertAfter;
}

/***********************************************************************
 *           EVENT_XStateToKeyState
 *
 * Translate a X event state (Button1Mask, ShiftMask, etc...) to
 * a Windows key state (MK_SHIFT, MK_CONTROL, etc...)
 */
static WORD EVENT_XStateToKeyState( int state )
{
  int kstate = 0;
  
  if (state & Button1Mask) kstate |= MK_LBUTTON;
  if (state & Button2Mask) kstate |= MK_MBUTTON;
  if (state & Button3Mask) kstate |= MK_RBUTTON;
  if (state & ShiftMask)   kstate |= MK_SHIFT;
  if (state & ControlMask) kstate |= MK_CONTROL;
  return kstate;
}

/***********************************************************************
 *           X11DRV_EVENT_QueryPointer
 */
BOOL X11DRV_EVENT_QueryPointer(DWORD *posX, DWORD *posY, DWORD *state)
{
  Window root, child;
  int rootX, rootY, winX, winY;
  unsigned int xstate;
  
  if (!TSXQueryPointer( display, X11DRV_GetXRootWindow(), &root, &child,
			&rootX, &rootY, &winX, &winY, &xstate )) 
    return FALSE;
  
  if(posX)
    *posX  = (DWORD)winX;
  if(posY)
    *posY  = (DWORD)winY;
  if(state)
    *state = EVENT_XStateToKeyState( xstate );
  
  return TRUE;
}

/***********************************************************************
 *           EVENT_Expose
 */
static void EVENT_Expose( HWND hWnd, XExposeEvent *event )
{
  RECT rect;

  WND *pWnd = WIN_FindWndPtr(hWnd);
  /* Make position relative to client area instead of window */
  rect.left   = event->x - (pWnd? (pWnd->rectClient.left - pWnd->rectWindow.left) : 0);
  rect.top    = event->y - (pWnd? (pWnd->rectClient.top - pWnd->rectWindow.top) : 0);
  rect.right  = rect.left + event->width;
  rect.bottom = rect.top + event->height;
  WIN_ReleaseWndPtr(pWnd);
 
  Callout.RedrawWindow( hWnd, &rect, 0,
                          RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN | RDW_ERASE |
                          (event->count ? 0 : RDW_ERASENOW) );
}


/***********************************************************************
 *           EVENT_GraphicsExpose
 *
 * This is needed when scrolling area is partially obscured
 * by non-Wine X window.
 */
static void EVENT_GraphicsExpose( HWND hWnd, XGraphicsExposeEvent *event )
{
  RECT rect;
  WND *pWnd = WIN_FindWndPtr(hWnd);
  
  /* Make position relative to client area instead of window */
  rect.left   = event->x - (pWnd? (pWnd->rectClient.left - pWnd->rectWindow.left) : 0);
  rect.top    = event->y - (pWnd? (pWnd->rectClient.top - pWnd->rectWindow.top) : 0);
  rect.right  = rect.left + event->width;
  rect.bottom = rect.top + event->height;
  
  WIN_ReleaseWndPtr(pWnd);
  
  Callout.RedrawWindow( hWnd, &rect, 0,
                          RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_ERASE |
                          (event->count ? 0 : RDW_ERASENOW) );
}


/***********************************************************************
 *           EVENT_Key
 *
 * Handle a X key event
 */
static void EVENT_Key( HWND hWnd, XKeyEvent *event )
{
    WND *pWnd = WIN_FindWndPtr(hWnd);
  X11DRV_KEYBOARD_HandleEvent( pWnd, event );
    WIN_ReleaseWndPtr(pWnd);

}


/***********************************************************************
 *           EVENT_MotionNotify
 */
static void EVENT_MotionNotify( HWND hWnd, XMotionEvent *event )
{
  WND *pWnd = WIN_FindWndPtr(hWnd);
  int xOffset = pWnd? pWnd->rectWindow.left : 0;
  int yOffset = pWnd? pWnd->rectWindow.top  : 0;
  WIN_ReleaseWndPtr(pWnd);

  MOUSE_SendEvent( MOUSEEVENTF_MOVE, 
		   xOffset + event->x, yOffset + event->y,
		   EVENT_XStateToKeyState( event->state ), 
		   event->time - MSG_WineStartTicks,
		   hWnd);
}


/***********************************************************************
 *           EVENT_ButtonPress
 */
static void EVENT_ButtonPress( HWND hWnd, XButtonEvent *event )
{
  static WORD statusCodes[NB_BUTTONS] = 
  { MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_RIGHTDOWN };
  int buttonNum = event->button - 1;
  
  WND *pWnd = WIN_FindWndPtr(hWnd);
  int xOffset = pWnd? pWnd->rectWindow.left : 0;
  int yOffset = pWnd? pWnd->rectWindow.top  : 0;
  WORD keystate;
  
  WIN_ReleaseWndPtr(pWnd);

  if (buttonNum >= NB_BUTTONS) return;
  
  /*
   * Get the compatible keystate
   */
  keystate = EVENT_XStateToKeyState( event->state );
  
  /*
   * Make sure that the state of the button that was just 
   * pressed is "down".
   */
  switch (buttonNum)
  {
    case 0:
      keystate |= MK_LBUTTON;
      break;
    case 1:
      keystate |= MK_MBUTTON;
      break;
    case 2:
      keystate |= MK_RBUTTON;
      break;
  }
  
  MOUSE_SendEvent( statusCodes[buttonNum], 
		   xOffset + event->x, yOffset + event->y,
		   keystate, 
		   event->time - MSG_WineStartTicks,
		   hWnd);
}


/***********************************************************************
 *           EVENT_ButtonRelease
 */
static void EVENT_ButtonRelease( HWND hWnd, XButtonEvent *event )
{
  static WORD statusCodes[NB_BUTTONS] = 
  { MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MIDDLEUP, MOUSEEVENTF_RIGHTUP };
  int buttonNum = event->button - 1;
  WND *pWnd = WIN_FindWndPtr(hWnd);
  int xOffset = pWnd? pWnd->rectWindow.left : 0;
  int yOffset = pWnd? pWnd->rectWindow.top  : 0;
  WORD keystate;
  
  WIN_ReleaseWndPtr(pWnd);
  
  if (buttonNum >= NB_BUTTONS) return;    
  
  /*
   * Get the compatible keystate
   */
  keystate = EVENT_XStateToKeyState( event->state );

  /*
   * Make sure that the state of the button that was just 
   * released is "up".
   */
  switch (buttonNum)
  {
    case 0:
      keystate &= ~MK_LBUTTON;
      break;
    case 1:
      keystate &= ~MK_MBUTTON;
      break;
    case 2:
      keystate &= ~MK_RBUTTON;
      break;
  }

  MOUSE_SendEvent( statusCodes[buttonNum], 
		   xOffset + event->x, yOffset + event->y,
		   keystate, 
		   event->time - MSG_WineStartTicks,
		   hWnd);
}


/**********************************************************************
 *              EVENT_FocusIn
 */
static void EVENT_FocusIn( HWND hWnd, XFocusChangeEvent *event )
{
    if (event->detail != NotifyPointer)
        if (hWnd != GetForegroundWindow())
        {
            SetForegroundWindow( hWnd );
	    X11DRV_KEYBOARD_UpdateState();
        }
}


/**********************************************************************
 *              EVENT_FocusOut
 *
 * Note: only top-level override-redirect windows get FocusOut events.
 */
static void EVENT_FocusOut( HWND hWnd, XFocusChangeEvent *event )
{
    if (event->detail != NotifyPointer)
        if (hWnd == GetForegroundWindow())
	{
	    SendMessageA( hWnd, WM_CANCELMODE, 0, 0 );
	    SetForegroundWindow( 0 );
	}
}

/**********************************************************************
 *              X11DRV_EVENT_CheckFocus
 */
BOOL X11DRV_EVENT_CheckFocus(void)
{
  HWND   hWnd;
  Window xW;
  int	   state;
  
  TSXGetInputFocus(display, &xW, &state);
    if( xW == None ||
        TSXFindContext(display, xW, winContext, (char **)&hWnd) ) 
      return FALSE;
    return TRUE;
}

/**********************************************************************
 *              EVENT_GetGeometry
 *
 * Helper function for ConfigureNotify handling.
 * Get the new geometry of a window relative to the root window.
 */
static void EVENT_GetGeometry( Window win, int *px, int *py,
                               unsigned int *pwidth, unsigned int *pheight )
{
    Window root, top;
    int x, y, width, height, border, depth;

    EnterCriticalSection( &X11DRV_CritSection );

    /* Get the geometry of the window */
    XGetGeometry( display, win, &root, &x, &y, &width, &height,
                  &border, &depth );

    /* Translate the window origin to root coordinates */
    XTranslateCoordinates( display, win, root, 0, 0, &x, &y, &top );

    LeaveCriticalSection( &X11DRV_CritSection );

    *px = x;
    *py = y;
    *pwidth = width;
    *pheight = height;
}

/**********************************************************************
 *              EVENT_ConfigureNotify
 *
 * The ConfigureNotify event is only selected on top-level windows
 * when the -managed flag is used.
 */
static void EVENT_ConfigureNotify( HWND hWnd, XConfigureEvent *event )
{
    RECT rectWindow;
    int x, y, flags = 0;
    unsigned int width, height;
    HWND newInsertAfter, oldInsertAfter;
  
    /* Get geometry and Z-order according to X */

    EVENT_GetGeometry( event->window, &x, &y, &width, &height );
    newInsertAfter = EVENT_QueryZOrder( hWnd );

    /* Get geometry and Z-order according to Wine */

    GetWindowRect( hWnd, &rectWindow );
    oldInsertAfter = GetWindow( hWnd, GW_HWNDPREV );
    if ( !oldInsertAfter ) oldInsertAfter = HWND_TOP;

    /* Compare what has changed */

    if ( rectWindow.left == x && rectWindow.top == y )
        flags |= SWP_NOMOVE;
    else
        TRACE_(win)( "%04x moving from (%d,%d) to (%d,%d)\n", hWnd, 
	             rectWindow.left, rectWindow.top, x, y );

    if (    rectWindow.right - rectWindow.left == width
         && rectWindow.bottom - rectWindow.top == height )
        flags |= SWP_NOSIZE;
    else
        TRACE_(win)( "%04x resizing from (%d,%d) to (%d,%d)\n", hWnd, 
                     rectWindow.right - rectWindow.left, 
                     rectWindow.bottom - rectWindow.top, width, height );

    if ( newInsertAfter == oldInsertAfter )
        flags |= SWP_NOZORDER;
    else
        TRACE_(win)( "%04x restacking from after %04x to after %04x\n", hWnd, 
                     oldInsertAfter, newInsertAfter );

    /* If anything changed, call SetWindowPos */

    if ( flags != (SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER) )
        SetWindowPos( hWnd, newInsertAfter, x, y, width, height, 
                            flags | SWP_NOACTIVATE | SWP_WINE_NOHOSTMOVE );
}


/***********************************************************************
 *           EVENT_SelectionRequest
 */
static void EVENT_SelectionRequest( HWND hWnd, XSelectionRequestEvent *event )
{
  XSelectionEvent result;
  Atom 	    rprop = None;
  Window    request = event->requestor;

  if(event->target == XA_STRING)
  {
      HANDLE16 hText;
      LPSTR  text;
      int    size,i,j;
      
      rprop = event->property;
      
      if( rprop == None ) 
	  rprop = event->target;
      
      if( event->selection != XA_PRIMARY ) 
	  rprop = None;
      else 
      if( !CLIPBOARD_IsPresent(CF_OEMTEXT) ) 
	  rprop = None;
      else
      {
	  /* open to make sure that clipboard is available */

	  BOOL couldOpen = OpenClipboard( hWnd );
	  char* lpstr = 0;
	  
	  hText = GetClipboardData16(CF_TEXT);
	  text = GlobalLock16(hText);
	  size = GlobalSize16(hText);
	  
	  /* remove carriage returns */
	  
	  lpstr = (char*)HEAP_xalloc( GetProcessHeap(), 0, size-- );
	  for(i=0,j=0; i < size && text[i]; i++ )
	  {
	      if( text[i] == '\r' && 
		  (text[i+1] == '\n' || text[i+1] == '\0') ) continue;
	      lpstr[j++] = text[i];
	  }
	  lpstr[j]='\0';
	  
	  TSXChangeProperty(display, request, rprop, 
			    XA_STRING, 8, PropModeReplace, 
			    lpstr, j);
	  HeapFree( GetProcessHeap(), 0, lpstr );
	  
	  /* close only if we opened before */
	  
	  if(couldOpen) CloseClipboard();
      }
  }
  
  if( rprop == None) 
      TRACE_(event)("Request for %s ignored\n", TSXGetAtomName(display,event->target));

  /* reply to sender */
  
  result.type = SelectionNotify;
  result.display = display;
  result.requestor = request;
  result.selection = event->selection;
  result.property = rprop;
  result.target = event->target;
  result.time = event->time;
  TSXSendEvent(display,event->requestor,False,NoEventMask,(XEvent*)&result);
}

/***********************************************************************
 *           EVENT_SelectionClear
 */
static void EVENT_SelectionClear( HWND hWnd, XSelectionClearEvent *event )
{
  if (event->selection != XA_PRIMARY) return;
  X11DRV_CLIPBOARD_ReleaseSelection( event->window, hWnd );
}


/**********************************************************************
 *           EVENT_DropFromOffix
 *
 * don't know if it still works (last Changlog is from 96/11/04)
 */
static void EVENT_DropFromOffiX( HWND hWnd, XClientMessageEvent *event )
{
  unsigned long		data_length;
  unsigned long		aux_long;
  unsigned char*	p_data = NULL;
  union {
    Atom		atom_aux;
    struct {
      int x;
      int y;
    } pt_aux;
    int		i;
  }		u;
  int			x, y;
  BOOL16	        bAccept;
  HGLOBAL16		hDragInfo = GlobalAlloc16( GMEM_SHARE | GMEM_ZEROINIT, sizeof(DRAGINFO));
  LPDRAGINFO            lpDragInfo = (LPDRAGINFO) GlobalLock16(hDragInfo);
  SEGPTR		spDragInfo = (SEGPTR) WIN16_GlobalLock16(hDragInfo);
  Window		w_aux_root, w_aux_child;
  WND*			pDropWnd;
  WND*                  pWnd;
  
  if( !lpDragInfo || !spDragInfo ) return;
  
  pWnd = WIN_FindWndPtr(hWnd);
  
  TSXQueryPointer( display, X11DRV_WND_GetXWindow(pWnd), &w_aux_root, &w_aux_child, 
                   &x, &y, (int *) &u.pt_aux.x, (int *) &u.pt_aux.y,
                   (unsigned int*)&aux_long);
  
  lpDragInfo->hScope = hWnd;
  lpDragInfo->pt.x = (INT16)x; lpDragInfo->pt.y = (INT16)y;
  
  /* find out drop point and drop window */
  if( x < 0 || y < 0 ||
      x > (pWnd->rectWindow.right - pWnd->rectWindow.left) ||
      y > (pWnd->rectWindow.bottom - pWnd->rectWindow.top) )
    {   bAccept = pWnd->dwExStyle & WS_EX_ACCEPTFILES; x = y = 0; }
  else
    {
      bAccept = DRAG_QueryUpdate( hWnd, spDragInfo, TRUE );
      x = lpDragInfo->pt.x; y = lpDragInfo->pt.y;
    }
  pDropWnd = WIN_FindWndPtr( lpDragInfo->hScope );
  WIN_ReleaseWndPtr(pWnd);
  
  GlobalFree16( hDragInfo );
  
  if( bAccept )
    {
      TSXGetWindowProperty( display, DefaultRootWindow(display),
			    dndSelection, 0, 65535, FALSE,
			    AnyPropertyType, &u.atom_aux, (int *) &u.pt_aux.y,
			    &data_length, &aux_long, &p_data);
      
      if( !aux_long && p_data)	/* don't bother if > 64K */
	{
	  char *p = (char*) p_data;
	  char *p_drop;
	  
	  aux_long = 0; 
	  while( *p )	/* calculate buffer size */
	    {
	      p_drop = p;
	      if((u.i = *p) != -1 ) 
		u.i = DRIVE_FindDriveRoot( (const char **)&p_drop );
	      if( u.i == -1 ) *p = -1;	/* mark as "bad" */
	      else
		{
		  INT len = GetShortPathNameA( p, NULL, 0 );
		  if (len) aux_long += len + 1;
		  else *p = -1;
		}
	      p += strlen(p) + 1;
	    }
	  if( aux_long && aux_long < 65535 )
	    {
	      HDROP16                 hDrop;
	      LPDROPFILESTRUCT16        lpDrop;
	      
	      aux_long += sizeof(DROPFILESTRUCT16) + 1; 
	      hDrop = (HDROP16)GlobalAlloc16( GMEM_SHARE, aux_long );
	      lpDrop = (LPDROPFILESTRUCT16) GlobalLock16( hDrop );
	      
	      if( lpDrop )
		{
		  lpDrop->wSize = sizeof(DROPFILESTRUCT16);
		  lpDrop->ptMousePos.x = (INT16)x;
		  lpDrop->ptMousePos.y = (INT16)y;
		  lpDrop->fInNonClientArea = (BOOL16) 
		    ( x < (pDropWnd->rectClient.left - pDropWnd->rectWindow.left)  ||
		      y < (pDropWnd->rectClient.top - pDropWnd->rectWindow.top)    ||
		      x > (pDropWnd->rectClient.right - pDropWnd->rectWindow.left) ||
		      y > (pDropWnd->rectClient.bottom - pDropWnd->rectWindow.top) );
		  p_drop = ((char*)lpDrop) + sizeof(DROPFILESTRUCT16);
		  p = p_data;
		  while(*p)
		    {
		      if( *p != -1 )	/* use only "good" entries */
			{
                          GetShortPathNameA( p, p_drop, 65535 );
                          p_drop += strlen( p_drop ) + 1;
			}
		      p += strlen(p) + 1;
		    }
		  *p_drop = '\0';
		  PostMessage16( hWnd, WM_DROPFILES,
				 (WPARAM16)hDrop, 0L );
		}
	    }
	}
      if( p_data ) TSXFree(p_data);  
      
    } /* WS_EX_ACCEPTFILES */

  WIN_ReleaseWndPtr(pDropWnd);
}

/**********************************************************************
 *           EVENT_DropURLs
 *
 * drop items are separated by \n 
 * each item is prefixed by its mime type
 *
 * event->data.l[3], event->data.l[4] contains drop x,y position
 */
static void EVENT_DropURLs( HWND hWnd, XClientMessageEvent *event )
{
  WND           *pDropWnd;
  WND           *pWnd;
  unsigned long	data_length;
  unsigned long	aux_long, drop_len = 0;
  unsigned char	*p_data = NULL; /* property data */
  char		*p_drop = NULL;
  char          *p, *next;
  int		x, y, drop32 = FALSE ;
  union {
    Atom	atom_aux;
    int         i;
    Window      w_aux;
  }		u; /* unused */
  union {
    HDROP16     h16;
    HDROP     h32;
  } hDrop;

  pWnd = WIN_FindWndPtr(hWnd);
  drop32 = pWnd->flags & WIN_ISWIN32;

  if (!(pWnd->dwExStyle & WS_EX_ACCEPTFILES))
  {
    WIN_ReleaseWndPtr(pWnd);
    return;
  }
  WIN_ReleaseWndPtr(pWnd);

  TSXGetWindowProperty( display, DefaultRootWindow(display),
			dndSelection, 0, 65535, FALSE,
			AnyPropertyType, &u.atom_aux, &u.i,
			&data_length, &aux_long, &p_data);
  if (aux_long)
    WARN_(event)("property too large, truncated!\n");
  TRACE_(event)("urls=%s\n", p_data);

  if( !aux_long && p_data) {	/* don't bother if > 64K */
    /* calculate length */
    p = p_data;
    next = strchr(p, '\n');
    while (p) {
      if (next) *next=0;
      if (strncmp(p,"file:",5) == 0 ) {
	INT len = GetShortPathNameA( p+5, NULL, 0 );
	if (len) drop_len += len + 1;
      }
      if (next) { 
	*next = '\n'; 
	p = next + 1;
	next = strchr(p, '\n');
      } else {
	p = NULL;
      }
    }
    
    if( drop_len && drop_len < 65535 ) {
      TSXQueryPointer( display, X11DRV_GetXRootWindow(), &u.w_aux, &u.w_aux, 
		       &x, &y, &u.i, &u.i, &u.i);

      pDropWnd = WIN_FindWndPtr( hWnd );
      
      if (drop32) {
	LPDROPFILESTRUCT        lpDrop;
	drop_len += sizeof(DROPFILESTRUCT) + 1; 
	hDrop.h32 = (HDROP)GlobalAlloc( GMEM_SHARE, drop_len );
	lpDrop = (LPDROPFILESTRUCT) GlobalLock( hDrop.h32 );
	
	if( lpDrop ) {
	  lpDrop->lSize = sizeof(DROPFILESTRUCT);
	  lpDrop->ptMousePos.x = (INT)x;
	  lpDrop->ptMousePos.y = (INT)y;
	  lpDrop->fInNonClientArea = (BOOL) 
	    ( x < (pDropWnd->rectClient.left - pDropWnd->rectWindow.left)  ||
	      y < (pDropWnd->rectClient.top - pDropWnd->rectWindow.top)    ||
	      x > (pDropWnd->rectClient.right - pDropWnd->rectWindow.left) ||
	      y > (pDropWnd->rectClient.bottom - pDropWnd->rectWindow.top) );
	  lpDrop->fWideChar = FALSE;
	  p_drop = ((char*)lpDrop) + sizeof(DROPFILESTRUCT);
	}
      } else {
	LPDROPFILESTRUCT16        lpDrop;
	drop_len += sizeof(DROPFILESTRUCT16) + 1; 
	hDrop.h16 = (HDROP16)GlobalAlloc16( GMEM_SHARE, drop_len );
	lpDrop = (LPDROPFILESTRUCT16) GlobalLock16( hDrop.h16 );
	
	if( lpDrop ) {
	  lpDrop->wSize = sizeof(DROPFILESTRUCT16);
	  lpDrop->ptMousePos.x = (INT16)x;
	  lpDrop->ptMousePos.y = (INT16)y;
	  lpDrop->fInNonClientArea = (BOOL16) 
	    ( x < (pDropWnd->rectClient.left - pDropWnd->rectWindow.left)  ||
	      y < (pDropWnd->rectClient.top - pDropWnd->rectWindow.top)    ||
	      x > (pDropWnd->rectClient.right - pDropWnd->rectWindow.left) ||
	      y > (pDropWnd->rectClient.bottom - pDropWnd->rectWindow.top) );
	  p_drop = ((char*)lpDrop) + sizeof(DROPFILESTRUCT16);
	}
      }
      
      /* create message content */
      if (p_drop) {
	p = p_data;
	next = strchr(p, '\n');
	while (p) {
	  if (next) *next=0;
	  if (strncmp(p,"file:",5) == 0 ) {
	    INT len = GetShortPathNameA( p+5, p_drop, 65535 );
	    if (len) {
	      TRACE_(event)("drop file %s as %s\n", p+5, p_drop);
	      p_drop += len+1;
	    } else {
	      WARN_(event)("can't convert file %s to dos name \n", p+5);
	    }
	  } else {
	    WARN_(event)("unknown mime type %s\n", p);
	  }
	  if (next) { 
	    *next = '\n'; 
	    p = next + 1;
	    next = strchr(p, '\n');
	  } else {
	    p = NULL;
	  }
	  *p_drop = '\0';
	}

	if (drop32) {
	  /* can not use PostMessage32A because it is currently based on 
	   * PostMessage16 and WPARAM32 would be truncated to WPARAM16
	   */
	  GlobalUnlock(hDrop.h32);
	  SendMessageA( hWnd, WM_DROPFILES,
			  (WPARAM)hDrop.h32, 0L );
	} else {
	  GlobalUnlock16(hDrop.h16);
	  PostMessage16( hWnd, WM_DROPFILES,
			 (WPARAM16)hDrop.h16, 0L );
	}
      }
      WIN_ReleaseWndPtr(pDropWnd);
    }
    if( p_data ) TSXFree(p_data);  
  }
}

/**********************************************************************
 *           EVENT_ClientMessage
 */
static void EVENT_ClientMessage( HWND hWnd, XClientMessageEvent *event )
{
  if (event->message_type != None && event->format == 32) {
    if ((event->message_type == wmProtocols) && 
	(((Atom) event->data.l[0]) == wmDeleteWindow))
    {
      /* Ignore the delete window request if the window has been disabled
       * and we are in managed mode. This is to disallow applications from
       * being closed by the window manager while in a modal state.
       */
      BOOL bIsDisabled;
      bIsDisabled = GetWindowLongA( hWnd, GWL_STYLE ) & WS_DISABLED;

      if ( !Options.managed || !bIsDisabled )
          PostMessage16( hWnd, WM_SYSCOMMAND, SC_CLOSE, 0 );
    }
    else if ( event->message_type == dndProtocol &&
	      (event->data.l[0] == DndFile || event->data.l[0] == DndFiles) )
      EVENT_DropFromOffiX(hWnd, event);
    else if ( event->message_type == dndProtocol &&
	      event->data.l[0] == DndURL )
      EVENT_DropURLs(hWnd, event);
    else {
#if 0
      /* enable this if you want to see the message */
      unsigned char* p_data = NULL;
      union {
	unsigned long	l;
	int            	i;
	Atom		atom;
      } u; /* unused */
      TSXGetWindowProperty( display, DefaultRootWindow(display),
			    dndSelection, 0, 65535, FALSE,
			    AnyPropertyType, &u.atom, &u.i,
			    &u.l, &u.l, &p_data);
      TRACE_(event)("message_type=%ld, data=%ld,%ld,%ld,%ld,%ld, msg=%s\n",
	    event->message_type, event->data.l[0], event->data.l[1], 
	    event->data.l[2], event->data.l[3], event->data.l[4],
	    p_data);
#endif
      TRACE_(event)("unrecognized ClientMessage\n" );
    }
  }
}

/**********************************************************************
 *           EVENT_EnterNotify
 *
 * Install colormap when Wine window is focused in
 * self-managed mode with private colormap
 */
#if 0
void EVENT_EnterNotify( HWND hWnd, XCrossingEvent *event )
{
  if( !Options.managed && X11DRV_GetXRootWindow() == DefaultRootWindow(display) &&
      (COLOR_GetSystemPaletteFlags() & COLOR_PRIVATE) && GetFocus() )
    TSXInstallColormap( display, X11DRV_PALETTE_GetColormap() );
}
#endif

/**********************************************************************
 *		EVENT_MapNotify
 */
void EVENT_MapNotify( HWND hWnd, XMapEvent *event )
{
  HWND hwndFocus = GetFocus();
  WND *wndFocus = WIN_FindWndPtr(hwndFocus);
  WND *pWnd = WIN_FindWndPtr(hWnd);
  if (pWnd->flags & WIN_MANAGED)
      pWnd->dwStyle &= ~WS_MINIMIZE;
  WIN_ReleaseWndPtr(pWnd);

  if (hwndFocus && IsChild( hWnd, hwndFocus ))
      X11DRV_WND_SetFocus(wndFocus);

  WIN_ReleaseWndPtr(wndFocus);
  
  return;
}


/**********************************************************************
 *              EVENT_MapNotify
 */
void EVENT_UnmapNotify( HWND hWnd, XUnmapEvent *event )
{
  WND *pWnd = WIN_FindWndPtr(hWnd);
  if (pWnd->flags & WIN_MANAGED)
  {
      EndMenu();
      if( pWnd->dwStyle & WS_VISIBLE )
	  pWnd->dwStyle |= WS_MINIMIZE;
  }
  WIN_ReleaseWndPtr(pWnd);
}


#endif /* !defined(X_DISPLAY_MISSING) */
