/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2023  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
 
#include "swindow/sx11/SX11Impl.h"
#include "swindow/sx11/SX11Window.h"
#include "swindow/sx11/SX11Font.h"
#include <X11/Xatom.h>
#include <X11/Xproto.h>

#ifdef HAVE_LOCALE
#include <X11/Xlocale.h>
#endif

#define XK_MISCELLANY
#define XK_LATIN1
#include <X11/keysymdef.h>

#include <stdio.h>
#include <time.h>

static SWindowListener::SKey keySymOf (KeySym sym);
static SX11Window* keyboardFocusWindow = 0;;
static SX11Window* lastKeyboardFocusWindow = 0;;
static bool isAncestor (Display * d, long root, long parent, long w);
extern "C"
{
  int handleX11Errors (Display *, XErrorEvent *);
}

/**
 * This should be 1.
 */
static bool hasJob=true;

long
SX11Impl::getAnyWindow()
{
  return (long) root;
}

/**
 * @author: Gaspar Sinai <gaspar@yudit.org>
 * @version: 2000-04-23
 * This is the abstract widget toolkit
 */

SX11Impl::SX11Impl(void) : in (SEventSource::SOCKET, 0)
{
  // This hopefully will help yudit start faster.
#ifdef HAVE_LOCALE
  setlocale (LC_CTYPE, "C");
#endif
  xdndSource = None;
  acceptedDataTypeAtom = None;
  accelPressed = false;
  clipOwnerPrimary = None;
  clipOwnerSecondary = None;
  clipBufferPrimary = "";
  clipBufferSecondary = "";
  encoder = SEncoder("iso-2022-x11");
  display = XOpenDisplay (0);
  job = new SJob();
  needFocusWhenMapped = 0;
  xa_clipboard = XInternAtom (display, "CLIPBOARD", False);
  xa_property_yudit = XInternAtom (display, "YUDIT_PROPERTY", False);
  if (display == 0)
  {
    fprintf(stderr, "Can not open display.\n");
  }
  else
  {
    XSetErrorHandler (handleX11Errors);
    screen = DefaultScreen (display);
    root = RootWindow (display, screen);
    border= 0;
    shown = 0;
    background = WhitePixel (display, screen);
    foreground = BlackPixel (display, screen);

    rectangle.x = SD_WIN_X;
    rectangle.y = SD_WIN_Y;
    rectangle.width = SD_WIN_W;
    rectangle.height = SD_WIN_H;

    wmProtocols = XInternAtom (display, "WM_PROTOCOLS", False);
    wmDeleteWindow = XInternAtom (display, "WM_DELETE_WINDOW", False);
    SInputStream is(SEventSource::SOCKET, XConnectionNumber (display));
    in = is;
    //fprintf (stderr, "X11 socket=%d\n",  XConnectionNumber (display));
    SEventHandler::addInput (&in, this);
    SEventHandler::addJob (job, this);

    visual = DefaultVisual (display, screen);
    colormap = XDefaultColormap (display, screen);
    cellCount = XDisplayCells (display, screen);
    XFlush (display);

    Window root_return;
    int x_return, y_return;
    unsigned int width_return, height_return;
    unsigned int border_width_return;
    unsigned int depth_return;
    rootWidth = 0;
    rootHeight = 0;
    if (XGetGeometry (display, root, 
       &root_return, &x_return, &y_return, 
       &width_return, &height_return, &border_width_return,&depth_return))
    {
       rootWidth = width_return;
       rootHeight = height_return;
    }
  }
}

SX11Impl::~SX11Impl()
{
  if (clipOwnerPrimary != None)
  {
    XSetSelectionOwner (display, XA_PRIMARY, None, CurrentTime);
  }
  if (clipOwnerSecondary != None)
  {
    XSetSelectionOwner (display, xa_clipboard, None, CurrentTime);
  }
  if (display != 0)
  {
    XCloseDisplay (display);
    delete job;
  }
}

bool
SX11Impl::isOK()
{
  return (display != 0);
}


/**
 * The SEventTarget
 */
int
SX11Impl::readable(const SEventSource* s)
{
  if (display == 0)
  {
     return 0;
  }
  if (!hasJob)
  {
//fprintf (stderr, "Adding job..\n");
//    SEventHandler::addJob (job, this);
  }
  return 0;
}

/**
 * Process X11 events in a loop
 * @return true if at least one event was found.
 */
bool 
SX11Impl::doXLoop()
{
  if (display == 0)
  {
     return false;
  }
  XFlush (display);
  bool done1 = false;
  while (doX())
  {
     done1 = true;
  } 
  return  done1;
}

/**
 * Process one  X11 event
 * @return true if at least one event was found.
 */
bool 
SX11Impl::doX()
{
  if (display == 0)
  {
     return false;
  }
  if (!XPending(display)) 
  {
     return false;
  }

  /* block - true */
  {
    /* we dont process things in a nomral socket event loop. */
    /* everything is moved to job - done. */
    XEvent event;
    /* NextEvent sucks in all events from socket, and pass the first queued. */
    XNextEvent (display, &event);

    /* XIC needs this  */

    if (keyboardFocusWindow != 0 && 
        (event.type == KeyPress || event.type == KeyRelease))
    {
      event.xkey.window = (Window)keyboardFocusWindow->getID();
      event.xkey.subwindow = None;
      /* This precaution is because if input method dies....*/
      if (!keyboardFocusWindow->isAsciiInput())
      {
        if (XFilterEvent (&event, event.xkey.window))
        {
          return true;
        }
      }
    }
    else if (XFilterEvent(&event, None))
    {
      return true;
    }

    /* Pixmap Copy */
    if (event.xany.type == NoExpose) return true;

    /* event handlers are our first priority */
    SString ekey((long)event.xany.window);
    ekey.append (SString ((long) event.xany.type));
    
    /* process all drag and drop thingy here */
    if (event.xany.type == PropertyNotify || event.xany.type==ClientMessage)
    {
      if (dndEvent (event))
      {
        return true;
      }
    }

    SXEventHandler*h = eventHandlerTable.get (ekey);
    if (h != 0)
    {
      /* if false is returned we need to process this event */
      if (h->handleEvent (event))
      {
         return true;
      }
    }

    Window w = event.xany.window;
    SX11Window* sw = (SX11Window*) windowHashtable.get((long)w);
    XEvent peekEvent;
    XEvent lastEvent;
    int butt = 0;
    /**
     * --------------------------------------------------------------------
     *     Nothing is perfect. This is my if block... 
     *     TODO: major cleanup.
     * --------------------------------------------------------------------
     */
    if (event.type == KeyPress || event.type == KeyRelease)
    {
      if (keyboardFocusWindow != 0)
      {
         sw = keyboardFocusWindow; 
      }
      else
      {
         sw = 0;
      }
    }
    /* ConfigureNotify window is false. we need the window that changed. */
    else if (event.type == ConfigureNotify)
    {
      sw = (SX11Window*) windowHashtable.get((long) event.xconfigure.window);
    }
    /**
     * Map and unmap has fuxxy modal processing. TODO: cleanup 
     */
    else if (event.type == UnmapNotify || event.type ==  MapNotify)
    {
/*
bool debug = (sw->getWidth() == 20 && sw->getHeight() == 359);
debug = debug || (sw->getWidth() == 10 && sw->getHeight() == 200);
if (debug && event.type == UnmapNotify) fprintf(stderr, "UnmapNotify\n");
if (debug && event.type == MapNotify) fprintf(stderr, "MapNotify\n");
*/

      sw = (SX11Window*) windowHashtable.get((long) event.xunmap.window);
      if (sw!=0)
      {
         //TODO remove mapNotified totally
        sw->mapNotified = (event.type ==  MapNotify);

        /* for child windows we dont get redraw, only Map. */
        if (sw->mapNotified && !sw->isChild)
        {
           addRedrawChildrenEvent (sw->getID(),
             SRedrawEvent (false, 0,  0,  sw->getWidth(), sw->getHeight()));
        }
      }

      /**
       *--------------------------------------------------------------------
       *      Modal Checks
       *--------------------------------------------------------------------
       */
      if (sw !=0 && sw->isModal())
      {
         /* add to modal stack, or remove from it */
         unsigned int modalIndex=0;
         /* search foir this modal box... */
         for (modalIndex=0; modalIndex<modalStack.size(); modalIndex++)
         {
           if (modalStack[modalIndex] == sw->getID())
           {
             break;
           }
         }
         /* modalIndex is model index. */
         if (event.type ==  MapNotify)
         {
           SX11Window* foc = (keyboardFocusWindow==0) ?
              lastKeyboardFocusWindow : keyboardFocusWindow;
           long fid = (foc==0) ? 0 : foc->getID();
           /**
            * Not yet on stack  add to stack
            */
           if (modalIndex==modalStack.size())
           {
             modalStack.append (sw->getID());
             modalFocusStack.append (fid); 
             getKeyboardFocus (sw);
           }
         }
         else
         {
           /*
            * Remove from modal stack 
            */
           if (modalIndex<modalStack.size())
           {
             modalStack.remove (modalIndex);
             long lf = modalFocusStack[modalIndex]; 
             modalFocusStack.remove (modalIndex); 
             if (lf != 0)
             {
                SX11Window* kw = (SX11Window*) windowHashtable.get(lf);
                if (kw) getKeyboardFocus (kw);
             }
           }
         }
      }
    }
    else if (event.type == Expose)
    {
      sw = (SX11Window*) windowHashtable.get((long) event.xexpose.window);
    }

    /**
     * Check what we got...
     */
    if (sw == 0) /* nothing */
    {
      return true;
    }
    SWindowListener* l = listenerHashtable.get(sw->getID());
    if (l == 0)
    {
      return true;
    }
    SWindowListener* lf = 0;    

    /**
     * --------------------------------------------------------------------
     *                          X11 event switch
     * --------------------------------------------------------------------
     */
    switch (event.type)
    {
    case EnterNotify:
      if (l && sw)
      {
        l->enterWindow(sw);
      }
      break;
    case LeaveNotify:
      if (l && sw)
      {
        l->leaveWindow(sw);
      }
      break;
    case SelectionClear:
      processSelection (event.xselectionclear);
      break;
    case SelectionNotify:
      processSelection (event.xselection);
      break;
    case SelectionRequest:
      processSelection (event.xselectionrequest);
      break;
    case KeyPress:
    case KeyRelease:
      {
        /* events that happen during conversion that dont have sent_event
         * set should be ignored */

        /* Don't try to make this bigger because it will screw up 
           Xutf8LookupString at and above 65 (try newline in ami) */
        char asci[64]; 
        KeySym key = 0;
        XIC ic = sw->getIC();
        SString enc = sw->getICEncoding();
        Status status_return;
        int cnt = 0; 
        SString s;
        if (sw->isAsciiInput() || ic==0)
        {
          // Attempted modification to get utf-8 string instead of ascii.
//  ic is 0 segfault
// #ifdef X_HAVE_UTF8_STRING
#if 0
        if (ic==0)
        {
          cnt = Xutf8LookupString (0, &event.xkey, 
             asci, sizeof (asci), &key, &status_return);
          if (status_return==XLookupChars || XLookupBoth)
          {
             s = SString (asci, (unsigned int)cnt);
          }
        }
        else
        {
          cnt = XLookupString (&event.xkey, 
             asci, sizeof (asci), &key, 0);
          status_return = 1;
          s = SString (asci, (unsigned int)cnt);
        }
#else
          cnt = XLookupString (&event.xkey, 
             asci, sizeof (asci), &key, 0);
          status_return = 1;
          s = SString (asci, (unsigned int)cnt);
#endif
        }
        else if (enc.size()>0)
        {
          cnt = XmbLookupString (ic, &event.xkey, 
             asci, sizeof (asci), &key, &status_return);
          if (status_return==XLookupChars || XLookupBoth)
          {
             SEncoder dec (enc);
             SString str (asci, (unsigned int)cnt);
             SV_UCS4 sv = dec.decode (str);
             SEncoder enc ("utf-8-s");
             s = enc.encode (sv);
          }
        }
        else
        {
#ifdef X_HAVE_UTF8_STRING
          cnt = Xutf8LookupString (ic, &event.xkey, 
             asci, sizeof (asci), &key, &status_return);
          if (status_return==XLookupChars || XLookupBoth)
          {
             s = SString (asci, (unsigned int)cnt);
          }
#else
          cnt = XmbLookupString (ic, &event.xkey, 
             asci, sizeof (asci), &key, &status_return);
          if (status_return==XLookupChars || status_return==XLookupBoth)
          {
             s = SString (asci, (unsigned int)cnt);
          }
#endif
        }
        if (cnt == 0 && (key & 0xff000000) == 0x01000000)
        {
           SV_UCS4 ucs4;
           ucs4.append ((SS_UCS4)(key & 0x00ffffff));
           SEncoder enc ("utf-8-s");
           s = enc.encode (ucs4);
           status_return = 0;
        }
        bool ctrl = (event.xkey.state & ControlMask) ? true : false;
        bool shift = (event.xkey.state & ShiftMask) ? true : false;
        bool meta = (event.xkey.state & Mod1Mask) ? true : false;
        SWindowListener::SKey skey =  status_return
         ? keySymOf(key) : SWindowListener::Key_Undefined;
        if (event.type==KeyPress) 
        {
          /* Let kinput2 deal with the event. */
          /* FIXME: Works fine, but too naive.  Ideally, we should devise
           * a mechanism that harmonizes the key bindings of yudit with
           * those of an XIM. */
          if (sw->isKInput() && (ctrl || meta)) break;

          if (sendAcceleratorPressed ((int) skey, ctrl, shift, meta)) break;
          /* in case we forgot to get this. */
          if (sendAcceleratorReleased ()) break;
        }
        else
        {
          if (sendAcceleratorReleased ()) break;
        }
        if (!event.xkey.send_event && sw->isKInput() 
              && sw == keyboardFocusWindow)
        {
          //fprintf (stderr, "Ignoring keys.\n");
          if (event.type==KeyRelease)
          {
            l->keyReleased (sw, skey, s, ctrl, shift, meta);
          }
          break;
        }
        if (isOKToDeliver (sw->getID()))
        {
          if (event.type==KeyPress) 
          {
            l->keyPressed (sw, skey, s, ctrl, shift, meta);
          } else {
            l->keyReleased (sw, skey, s, ctrl, shift, meta);
          }
        }
      }
      break;
      
    case ConfigureNotify:
/*
      if (event.xconfigure.x == 0 &&  event.xconfigure.y == 0) {
        fprintf (stderr, "ConfigureNotify ZERO %lx  %d,%d\n", 
            event.xconfigure.window,
            event.xconfigure.width, event.xconfigure.height);
      }
*/
      // FIXME: XMoveWindow does not generate events, something else does.
      // sw->isChild fix:
      if (sw->isChild) break;
      sw->setPosition( event.xconfigure.x, event.xconfigure.y);
      sw->setSize( event.xconfigure.width, event.xconfigure.height);
      l->resized (sw, event.xconfigure.x, event.xconfigure.y,
          event.xconfigure.width, event.xconfigure.height);

      break;
    case FocusIn:
      /* Return the focus */
//fprintf (stderr, "FocusIn last=%lx now=%lx\n",
//   lastKeyboardFocusWindow, keyboardFocusWindow);
      if (lastKeyboardFocusWindow != 0 && keyboardFocusWindow == 0)
      {
          keyboardFocusWindow = lastKeyboardFocusWindow;
          lf = listenerHashtable.get(lastKeyboardFocusWindow->getID());
          if (lf != 0)
          {
             lf->gainedKeyboardFocus (lastKeyboardFocusWindow);
          }
      }
      if (lastKeyboardFocusWindow != 0)
      {
        keyboardFocusWindow = lastKeyboardFocusWindow;
      }
      if (keyboardFocusWindow != sw && keyboardFocusWindow!=0)
      {
        /* weird eh? we receive a gained focus event, but it is not us! */
/* 
Gaspar 2007-12-07 
Commented out because this can cause an infinite loop.
        if (keyboardFocusWindow->isVisible())
        {
          XSetInputFocus (display, (Window) keyboardFocusWindow->getID(), 
             RevertToNone, CurrentTime);
        }
*/
      }
      break;
    case FocusOut:
      if (keyboardFocusWindow != 0)
      {
          lf = listenerHashtable.get(keyboardFocusWindow->getID());
          if (lf != 0)
          {
             lf->lostKeyboardFocus (keyboardFocusWindow);
          }
          keyboardFocusWindow = 0;
      }
      break;
    case ClientMessage:
        if ((Atom)event.xclient.message_type == wmProtocols
          && (Atom)event.xclient.data.l[0] == wmDeleteWindow
          && event.xclient.format == 32)
        {
          if (l->windowClose (sw) == true)
          {
              delete sw;
          }
        }
      break;
    case Expose:
       //TODO remove mapNotified totally
       //if (!sw->mapNotified) return true;
       addRedrawEvent ((long) event.xexpose.window,
         SRedrawEvent (false, event.xexpose.x, 
          event.xexpose.y, event.xexpose.width, event.xexpose.height)); 
       break;
    case GraphicsExpose:
       //TODO remove mapNotified totally
       if (!sw->mapNotified) return true;
       addRedrawEvent (event.xany.window,
         SRedrawEvent (false, event.xgraphicsexpose.x, 
         event.xgraphicsexpose.y, event.xgraphicsexpose.width, 
         event.xgraphicsexpose.height)); 
       break;
    case MotionNotify:
       if (!isOKToDeliver (sw->getID())) break;
       if (event.xmotion.state & Button2MotionMask) {
          butt = 0;
       } else if (event.xmotion.state & Button2MotionMask) {
          butt = 1;
       } else if (event.xmotion.state & Button3MotionMask) {
          butt = 2;
       }
       /* Event compression */
       lastEvent = event;
       while (XEventsQueued (display, QueuedAfterFlush)) {
         XPeekEvent (display, &peekEvent); 
         if (peekEvent.type == MotionNotify
            && peekEvent.xmotion.window == event.xmotion.window
            && peekEvent.xmotion.state == event.xmotion.state) {
            XNextEvent (display, &lastEvent);
          } else {
            break;
          }
       }
       l->buttonDragged (sw, butt, lastEvent.xbutton.x, lastEvent.xbutton.y);
       break;
    case ButtonRelease:
       if (!isOKToDeliver (sw->getID())) break;
       if (event.xbutton.button == Button1) {
         l->buttonReleased (sw, 0, event.xbutton.x, event.xbutton.y);
       } else if (event.xbutton.button == Button2) {
         l->buttonReleased (sw, 1, event.xbutton.x, event.xbutton.y);
       } else if (event.xbutton.button == Button3) {
         l->buttonReleased (sw, 2, event.xbutton.x, event.xbutton.y);
       }
       break;
    case ButtonPress:
       if (!isOKToDeliver (sw->getID())) break;
       //Enable Mousewheel functions (Addition by Maarten van Gompel <proycon@anaproy.homeip.net>
       if (event.xbutton.button == Button1) {
         l->buttonPressed (sw, 0, event.xbutton.x, event.xbutton.y);
       } else if (event.xbutton.button == Button2) {
         l->buttonPressed (sw, 1, event.xbutton.x, event.xbutton.y);
       } else if (event.xbutton.button == Button3) {
         l->buttonPressed (sw, 2, event.xbutton.x, event.xbutton.y);
       } else if (event.xbutton.button == Button4) {
         l->buttonPressed (sw, 3, event.xbutton.x, event.xbutton.y);
       } else if (event.xbutton.button == Button5) {
         l->buttonPressed (sw, 4, event.xbutton.x, event.xbutton.y);
	}
       break;
    case DestroyNotify:
      break;
    }
  } 
  return true;
}

/**
 * The job is processing all events, and iafter that it processes 
 * the redrawEventTable 
 */

bool
SX11Impl::done (const SEventSource* s)
{
  hasJob = false;
  //static unsigned int ccount=0;
  doXLoop (); /* this will process readable */

  SRedrawEventTable t;
  /* redraw block */
  do {
    t.clear();
    t = redrawEventTable;
    redrawEventTable.clear();
    for (unsigned int i=0; i<t.size(); i++)
    {
      for (unsigned int j=0; j<t.size(i); j++)
      {
        const SRedrawEvent* evt = t.get (i, j);
        if (evt == 0) {
            fprintf (stderr, "NULL bucket.\n");
            break;
        }
        SString sid = t.key (i, j);
        long wid = sid.longValue();
        SX11Window* swid = (SX11Window*) windowHashtable.get(wid);
        if (swid == 0)
        {
          fprintf (stderr, "Window %ld not found.\n", wid);
          continue;
        }
        SWindowListener* li = listenerHashtable.get(swid->getID());
        if (li == 0)
        {
          fprintf (stderr, "Window listener for %ld not found.\n", wid);
          continue;
        }

         //Pixmap e = imageCache.get (i, j);
         //if (e != 0) XFreePixmap (impl->display, e);
       // Flicker free
       //Pixmap p = XCreatePixmap (display, (Window) wid, 
       //         evt->width, evt->height, depth)

        //if (!swid->mapNotified) continue;
        SX11Region region (evt->x, evt->y, evt->width, evt->height);
        if (evt->width == 0 || evt->height == 0)
        {
          region.x = 0;
          region.y = 0;
          region.width = swid->getWidth();
          region.height = swid->getHeight();
        }
        swid->beginDoubleBuffer (region);
        if (!swid->isDoubleBufferEnabled() && evt->clear)
        {
          if (evt->width != 0 && evt->height != 0)
          {
             swid->clear (evt->x, evt->y, evt->width, evt->height);
          }
        }

        /* This is the first expose. */
        if (evt->width == 0 || evt->height == 0)
        {
          li->redraw (swid, 0, 0, swid->getWidth(), swid->getHeight());
        }
        else
        {
//if (swid->getWidth() == 20)
//fprintf (stderr, "Window redraw list=%lx evt.size=%u,%u\n", 
//        (unsigned long) li, evt->width, evt->height);
          li->redraw (swid, evt->x, evt->y, evt->width, evt->height);
        }
        swid->endDoubleBuffer (region);
      }
    }
  } while (doXLoop());
  hasJob = true;
  SEventHandler::addJob (job, this);
  return false;
}

/**
 * Add a collapsing window redraw, for all children only, same event.
 * aparam id is the id of the window.
 * We assume all chidren are smaller than parent :)
 * @param evt is the event.
 */
void
SX11Impl::addRedrawChildrenEvent (long id, const SRedrawEvent& evt)
{
  /* FIXME: finish addRedrawChildrenEvent and get rid of this  */
  /* this will generate expose event from child windows. */
//fprintf (stderr, "addRedrawChildrenEvent\n");
  //XMapSubwindows (display, (Window) id);
}
/**
 * Add a collapsing window redraw.
 * aparam id is the id of the window.
 * @param evt is the event.
 */
void
SX11Impl::addRedrawEvent (long id, const SRedrawEvent& evt)
{
  SVector <SRedrawEvent> save;
  /* multiple keys */
  SRedrawEvent newEvt (evt);
  const SRedrawEvent* old = 0;
  while ((old = redrawEventTable.get (id)))
  {
    if (newEvt.merge (*old))
    {
      redrawEventTable.remove (id);
      break; // ? continue
    }
    save.append (*old);
    redrawEventTable.remove (id);
  } 
  for (unsigned int i=0; i<save.size(); i++)
  {
    //fprintf (stderr, "putback %u\n", i);
    redrawEventTable.put (id, save[i], false);
  }
  redrawEventTable.put (id, newEvt, false);
}

/**
 * If there is a redraw event associated with this id, move it.
 * @param xoffset will be added to y
 * @param yoffset will be added to x
 */
void
SX11Impl::moveRedrawEvent (long id, int xoffset, int yoffset)
{
  SVector <SRedrawEvent> save;

  const SRedrawEvent* old = 0;
  while ((old=redrawEventTable.get (id)))
  {
    save.append (*old);
    redrawEventTable.remove (id);
  }
  //fprintf (stderr, "moveRedrawEvent %u\n", save.size());
  for (unsigned int i=0; i<save.size(); i++)
  {
    SRedrawEvent newEvt (save[i]);
    newEvt.x += xoffset;
    newEvt.y += yoffset;
    redrawEventTable.put (id, newEvt, false);
  }
}

void
SX11Impl::addWindow(long id, SWindow* w)
{
  if (display == 0) return;
  windowHashtable.put(id, w);
}

void
SX11Impl::deleteWindow(long id, SWindow* w)
{
  if (display == 0) return;
  XDestroyWindow (display, (Window)id);
  XFlush(display);
  SX11Window* sw = (SX11Window*) windowHashtable.get(id);
  windowHashtable.remove(id);
  listenerHashtable.remove(id);
  if (sw->isShown()) shown--;
  if (shown==0)
  {
     SEventHandler::remove (&in);
     //fprintf (stderr, "FINISHED.\n");
  }
  if ((SWindow*)keyboardFocusWindow ==  w)
  {
    keyboardFocusWindow = 0;
    lastKeyboardFocusWindow = 0;
  }
}

void
SX11Impl::show (long id)
{
  if (display == 0) return;

  XMapRaised (display, (Window) id);
  //XFlush(display);
  SX11Window* sw = (SX11Window*) windowHashtable.get(id);
  if (sw->isShown() == false) shown++;
}

void
SX11Impl::hide (long id)
{
  if (display == 0) return;
  XUnmapWindow (display, (Window) id);
  XFlush(display);
  SX11Window* sw = (SX11Window*) windowHashtable.get(id);
  if (sw->isShown() == true) shown--;
}

/**
 * Please implement this to get a new toplvele window with
 * a title.
 */
SWindow*
SX11Impl::getWindow (SWindowListener* l, const SString& name)
{
  if (display == 0) return 0;
  Window w = XCreateSimpleWindow (display, root, rectangle.x, rectangle.y,
                rectangle.width, rectangle.height, border, foreground,
                background);
  char* n = name.cString();

  XClassHint    classHints;
  classHints.res_name =  n;
  classHints.res_class = n;

  XWMHints wmHints;
  wmHints.initial_state = NormalState;
  wmHints.input = True;
  wmHints.flags = StateHint | InputHint;
  XSetWMProperties (display, w, 0, 0, 0, 0, 0, &wmHints, &classHints);
  XSetWMProtocols (display, w, &wmDeleteWindow, 1);
  XStoreName (display, w, n);
  delete n;
  SX11Window* sx11 = new SX11Window (name, this, w);
  listenerHashtable.put ((long)w, l);
  XFlush(display);
  return sx11;
}

/**
 * Get an X11 native font.
 */
SFontNative*
SX11Impl::getFont (const SString& encoding)
{
  SX11Font* x11font = new SX11Font (this, encoding);
  return x11font;
}


bool
SX11Impl::getKeyboardFocus (SWindow *w)
{
  if ((SWindow*) keyboardFocusWindow == w) return true;
  if (!((SX11Window*)w)->isVisible())
  {
     needFocusWhenMapped = ((SX11Window*)w)->getID();
     //fprintf (stderr, "focus gaining delayed.\n");
     return false;
  }
  if (keyboardFocusWindow != 0)
  {
    SWindowListener* l = listenerHashtable.get(keyboardFocusWindow->getID());
    if (l != 0)
    {
      l->lostKeyboardFocus (keyboardFocusWindow);
    }
  }
  XSetInputFocus (display, (Window) ((SX11Window*)w)->getID(), 
     RevertToNone, CurrentTime);
  XFlush (display);
  keyboardFocusWindow = (SX11Window*) w;
  lastKeyboardFocusWindow = keyboardFocusWindow;
  return true;
}

/**
 * convert KeySym to SWindowListener::SKey
 */
static SWindowListener::SKey
keySymOf (KeySym sym)
{
  switch (sym) {
  case XK_a: return SWindowListener::Key_a;
  case XK_A: return SWindowListener::Key_A;
  case XK_b: return SWindowListener::Key_b;
  case XK_B: return SWindowListener::Key_B;
  case XK_c: return SWindowListener::Key_c;
  case XK_C: return SWindowListener::Key_C;
  case XK_d: return SWindowListener::Key_d;
  case XK_D: return SWindowListener::Key_D;
  case XK_e: return SWindowListener::Key_e;
  case XK_E: return SWindowListener::Key_E;
  case XK_f: return SWindowListener::Key_f;
  case XK_F: return SWindowListener::Key_F;
  case XK_g: return SWindowListener::Key_g;
  case XK_G: return SWindowListener::Key_G;
  case XK_h: return SWindowListener::Key_h;
  case XK_H: return SWindowListener::Key_H;
  case XK_i: return SWindowListener::Key_i;
  case XK_I: return SWindowListener::Key_I;
  case XK_j: return SWindowListener::Key_j;
  case XK_J: return SWindowListener::Key_J;
  case XK_k: return SWindowListener::Key_k;
  case XK_K: return SWindowListener::Key_K;
  case XK_l: return SWindowListener::Key_l;
  case XK_L: return SWindowListener::Key_L;
  case XK_m: return SWindowListener::Key_m;
  case XK_M: return SWindowListener::Key_M;
  case XK_n: return SWindowListener::Key_n;
  case XK_N: return SWindowListener::Key_N;
  case XK_o: return SWindowListener::Key_o;
  case XK_O: return SWindowListener::Key_O;
  case XK_p: return SWindowListener::Key_p;
  case XK_P: return SWindowListener::Key_P;
  case XK_q: return SWindowListener::Key_q;
  case XK_Q: return SWindowListener::Key_Q;
  case XK_r: return SWindowListener::Key_r;
  case XK_R: return SWindowListener::Key_R;
  case XK_s: return SWindowListener::Key_s;
  case XK_S: return SWindowListener::Key_S;
  case XK_t: return SWindowListener::Key_t;
  case XK_T: return SWindowListener::Key_T;
  case XK_u: return SWindowListener::Key_u;
  case XK_U: return SWindowListener::Key_U;
  case XK_x: return SWindowListener::Key_x;
  case XK_X: return SWindowListener::Key_X;
  case XK_y: return SWindowListener::Key_y;
  case XK_Y: return SWindowListener::Key_Y;
  case XK_v: return SWindowListener::Key_v;
  case XK_V: return SWindowListener::Key_V;
  case XK_w: return SWindowListener::Key_w;
  case XK_W: return SWindowListener::Key_W;
  case XK_z: return SWindowListener::Key_z;
  case XK_Z: return SWindowListener::Key_Z;

  case XK_Shift_L: return SWindowListener::Key_Shift_R;
  case XK_Shift_R: return SWindowListener::Key_Shift_L;
  case XK_Control_L: return SWindowListener::Key_Control_R;
  case XK_Control_R: return SWindowListener::Key_Control_L;
  case XK_Alt_L: return SWindowListener::Key_Alt_L;
  case XK_Alt_R: return SWindowListener::Key_Alt_R;
  case XK_Meta_L: return SWindowListener::Key_Meta_L;
  case XK_Meta_R: return SWindowListener::Key_Meta_R;
  case XK_Tab: return SWindowListener::Key_Tab;
  case XK_Left: return SWindowListener::Key_Left;
  case XK_Right: return SWindowListener::Key_Right;
  case XK_Up: return SWindowListener::Key_Up;
  case XK_Down: return SWindowListener::Key_Down;
  case XK_Prior: return SWindowListener::Key_Prior;
  case XK_Next: return SWindowListener::Key_Next;
  case XK_Return: return SWindowListener::Key_Return;
  case XK_KP_Enter: return SWindowListener::Key_Enter;
  case XK_Home: return SWindowListener::Key_Home;
  case XK_End: return SWindowListener::Key_End;
  case XK_Delete: return SWindowListener::Key_Delete;
  case XK_BackSpace: return SWindowListener::Key_BackSpace;
  case XK_Clear: return SWindowListener::Key_Clear;
  case XK_KP_Space: return SWindowListener::Key_Space;
  case XK_Escape: return SWindowListener::Key_Escape;
  case XK_F1: return SWindowListener::Key_F1;
  case XK_F2: return SWindowListener::Key_F2;
  case XK_F3: return SWindowListener::Key_F3;
  case XK_F4: return SWindowListener::Key_F4;
  case XK_F5: return SWindowListener::Key_F5;
  case XK_F6: return SWindowListener::Key_F6;
  case XK_F7: return SWindowListener::Key_F7;
  case XK_F8: return SWindowListener::Key_F8;
  case XK_F9: return SWindowListener::Key_F9;
  case XK_F10: return SWindowListener::Key_F10;
  case XK_F11: return SWindowListener::Key_F11;
  case XK_F12: return SWindowListener::Key_F12;
  }
  return SWindowListener::Key_Undefined;
}
void
SX11Impl::setXEventHandler (long id, long type, SXEventHandler* h)
{
  SString key(id);
  key.append (SString (type));
  eventHandlerTable.put (key, h);
}

void
SX11Impl::removeXEventHandler (long id, long type)
{
  SString key(id);
  key.append (SString (type));
  eventHandlerTable.remove (key);
}
/**
 * Send this string to a window.
 * @param id is the window id.
 * @param string is an utf8 string.
 */
void
SX11Impl::sendString (long id, const SString& s)
{
  SX11Window* sw = (SX11Window*) windowHashtable.get(id);
  if (sw == 0 || sw != keyboardFocusWindow) return;
  SWindowListener* l = listenerHashtable.get(sw->getID());
  if (l == 0) return;
  l->keyPressed (sw, SWindowListener::Key_Send, s, false, false, false);
}

/**
 * get an utf-8 string from clipboard.
 * FIXME
 */
SString
SX11Impl::getClipUTF8(long id, bool isPrimary)
{
  Window    owner;
  SEncoder utf8e;
  // https://www.uninformativ.de/blog/postings/2017-04-02/0/POSTING-en.html

  // If we own the selection ourselves, no need for interprocess
  // communication.
  Atom  clipBoard = isPrimary ? XA_PRIMARY :  xa_clipboard;

  owner=XGetSelectionOwner (display, clipBoard);
  if (isPrimary) {
    if (clipOwnerPrimary == owner) {
        return SString(clipBufferPrimary);
    }
  } else {
    if (clipOwnerSecondary == owner) {
        return SString(clipBufferSecondary);
    }
  }

  SString& clipBuffer = isPrimary ? clipBufferPrimary : clipBufferSecondary;
  clipBuffer.clear();
  // Try UTF8_STRING first.
  clipBuffer = getSelectionData (clipBoard, toAtom("UTF8_STRING"), id, CurrentTime);
  if (clipBuffer.size())
  {
    //fprintf (stderr, "SX11Impl::getClipUTF8: got UTF8_STRING\n");
    return SString(clipBuffer);
  }

  // Then try COMPOUND_TEXT.
  clipBuffer = getSelectionData (clipBoard, toAtom("COMPOUND_TEXT"), id, CurrentTime);
  if (clipBuffer.size())
  {
    //fprintf (stderr, "SX11Impl::getClipUTF8: got COMPOUND_TEXT\n");
    clipBuffer = utf8e.encode (encoder.decode(clipBuffer));
    return SString (clipBuffer);
  }

  // Finally try STRING.
  clipBuffer = getSelectionData (clipBoard, XA_STRING, id, CurrentTime);
  if (clipBuffer.size())
  {
    //fprintf (stderr, "SX11Impl::getClipUTF8: got XA_STRING\n");
    SEncoder  iso_1("iso-8859-1");
    clipBuffer = utf8e.encode (iso_1.decode(clipBuffer));
    return SString (clipBuffer);
  }
  return SString(clipBuffer);
}

void
SX11Impl::setEncoding(const SString& str)
{
  SEncoder enc = SEncoder (str);
  if (!enc.isOK())
  {
    fprintf (stderr, "SX11 clipboard encoder `%*.*s' unknown\n", 
      SSARGS(str));
  }
  else
  {
    encoder = enc;
  }
}

/**
 * This is getting data
 * using any selection. 
 * @param dataName is the selection name. for clipboard we use
 *  XA_PRIMARY and for drag and drop we use XdndSelection
 * @param dataType is the dataType.
 * @param id is the window id which asks for conversion
 * @param _time is a timestamp.
 */
SString
SX11Impl::getSelectionData (Atom dataName, Atom dataType, long id, Time _time)
{
  // use encoder to encode.
  XEvent    xevent;

  time_t  start;

  XConvertSelection (display, dataName, dataType, 
    xa_property_yudit, (Window)id, _time );

  XFlush (display);
  for (start = time (0); ; )
  {
    if (XCheckTypedWindowEvent(display, (Window)id,
      SelectionNotify, &xevent))
    {
      break;
    }
    if (time (0) - start >= 8)
    {
      fprintf (stderr, "warn: clipboard timeout.\n");
      return SString();
    }
  }
  return getPropertyData (xevent.xselection.requestor, xa_property_yudit, 8);
}

/**
 * Get the property data of the window.
 * @param window is the window that has the property
 * @param property
 * @param format - only 8 is tested.
 */
SString
SX11Impl::getPropertyData (Window window, Atom property, int format)
{
  // FIXME: This does not deal with INCR properties.
  int    propformat;
  unsigned long   propsize;
  unsigned long   rest;
  unsigned char*  propvalue;
  Atom    proptype;
  SString result;
  int     count = 0;

  /* Read the propty from the selection owner's window */
  while (Success==XGetWindowProperty (display, 
      window, property,
      count, 100000L, True, AnyPropertyType,
      &proptype,  &propformat, &propsize, &rest, &propvalue))
  {
    if (propvalue==0)
    {
      break;
    }
    if (propsize==0 || propformat != format)
    {
      XFree (propvalue);
      break;
    }
    result.append (SString((char*)propvalue, propsize));
    XFree (propvalue);
    count += propsize;
    if (rest==0) break;
  }
  return SString (result);
}

/**
 * put an utf-8 string to clipboard
 * This is using the XA_PRIMARY selection.
 */
void
SX11Impl::putClipUTF8 (long id, const SString& utf8, bool isPrimary)
{
  //fprintf (stderr, "FIXME: putClipUTF8\n");

  SWindowListener* l;
  SX11Window* sw;
  Atom  clipBoard = isPrimary ? XA_PRIMARY : xa_clipboard;

  if (clipOwnerPrimary != (Window) id && clipOwnerPrimary != None)
  {
    l = listenerHashtable.get((long)clipOwnerPrimary);
    sw = (SX11Window*) windowHashtable.get((long)clipOwnerPrimary);
    if (l && sw)
    {
      l->lostClipSelection (sw);
    }
  }
  l = listenerHashtable.get((long)id);
  sw = 0;

  if (isPrimary) {
    sw = (SX11Window*) windowHashtable.get((long)clipOwnerPrimary);
    clipOwnerPrimary = None;
  } else {
    sw = (SX11Window*) windowHashtable.get((long)clipOwnerSecondary);
    clipOwnerSecondary = None;
  }
  XSetSelectionOwner (display, clipBoard, (Window) id, CurrentTime);
  if (XGetSelectionOwner (display, clipBoard) != (Window) id)
  {
    /* Oops */
    if (isPrimary) {
        clipOwnerPrimary = None;
    } else {
        clipOwnerSecondary = None;
    }
    if (l && sw)
    {
      fprintf (stderr, "XGetSelectionOwner oops\n");
      l->lostClipSelection (sw);
    }
    return;
  }
  if (isPrimary) {
    clipOwnerPrimary = (Window) id;
    clipBufferPrimary = utf8;
  } else {
    clipOwnerSecondary = (Window) id;
    clipBufferSecondary = utf8;
  }
}

// https://www.x.org/releases/X11R7.5/doc/man/man3/XSelectionRequestEvent.3.html
void
SX11Impl::processSelection (const XSelectionRequestEvent& sel)
{
  XEvent   xevent;
  SEncoder utf8e;
  SString  str;

  SString& clipBuffer = clipBufferPrimary;
  SX11Window* sw = 0;
  if (sel.selection == xa_clipboard) 
  {
    sw = (SX11Window*) windowHashtable.get((long)clipOwnerSecondary);
    clipBuffer = clipBufferSecondary;
  }
  else if (sel.selection == XA_PRIMARY) 
  {
    sw = (SX11Window*) windowHashtable.get((long)clipOwnerPrimary);
    clipBuffer = clipBufferPrimary;
  } else {
        fprintf (stderr, "Unknown selection in XSelectionRequestEvent\n");
        return;
  }
  if (sw == 0 || sw->getID() != (long)sel.owner)
  {
    fprintf (stderr, "OOPS XSelectionRequestEvent.\n");
    return;
  }
  /* FIXME: This does not answer MULTIPLE and TIMESTAMP requests. */
  Atom property = sel.property;
  if (sel.target == XInternAtom(display,"TARGETS",False)) 
  {
      Atom* targets = new Atom[5];
      targets[0] = XInternAtom(display,"TARGETS",False);
      targets[1] = XInternAtom(display,"TEXT",False);
      targets[2] = XA_STRING;
      targets[3] = XInternAtom(display,"COMPOUND_TEXT",False);
      targets[4] = XInternAtom(display,"UTF8_STRING",False);
      XChangeProperty (sel.display,
           sel.requestor, sel.property,
           XA_ATOM, 32, PropModeReplace,
           (unsigned char *) targets, 5);
      delete[] targets;
    }
    else if (sel.target == XInternAtom(display,"UTF8_STRING",False))
    {
//fprintf (stderr, "clipevent - utf8.\n");
      // Asked for TEXT or UTF8_STRING -> answer with UTF8_STRING.
      XChangeProperty (sel.display,
           sel.requestor, sel.property,
           XInternAtom(display,"UTF8_STRING",False), 8, PropModeReplace,
           (const unsigned char*) clipBuffer.array(), clipBuffer.size());
    }
    else if (sel.target == XInternAtom(display,"TEXT",False)
       || sel.target == XInternAtom(display,"COMPOUND_TEXT",False))
    {
//fprintf (stderr, "clipevent - compound.\n");
      // Asked for COMPOUND_TEXT -> answer with COMPOUND_TEXT if convertible.
      str=encoder.encode (utf8e.decode (clipBuffer));
      XChangeProperty (sel.display,
             sel.requestor, sel.property,
             XInternAtom(display,"COMPOUND_TEXT", False), 8, PropModeReplace,
             (const unsigned char*) str.array(), str.size());
    }
    else if (sel.target == XA_STRING)
    {
//fprintf (stderr, "clipevent - string.\n");
      // Asked for STRING -> answer with STRING if convertible.
      SEncoder  iso_1("iso-8859-1");
      str=iso_1.encode (utf8e.decode (clipBuffer));
      XChangeProperty (sel.display,
          sel.requestor, sel.property,
          XA_STRING, 8, PropModeReplace,
          (const unsigned char*) str.array(), str.size());
    }
    else
    {
       property = None;
    }
    xevent.xselection.type = SelectionNotify;
    xevent.xselection.display = sel.display;
    xevent.xselection.requestor = sel.requestor;
    xevent.xselection.selection = sel.selection;
    xevent.xselection.target = sel.target;
    xevent.xselection.property = property;
    xevent.xselection.time = sel.time;
    XSendEvent (sel.display, sel.requestor, False, 0, &xevent);
}

// https://www.x.org/releases/X11R7.5/doc/man/man3/XSelectionEvent.3.html
void
SX11Impl::processSelection (const XSelectionEvent& sel)
{
// Ignore
#if 0
  if (sel.selection == xa_clipboard) 
  {
    clipOwnerSecondary = None;
  }
  else if (sel.selection == XA_PRIMARY) 
  {
    clipOwnerPrimary = None;
  } else {
        fprintf (stderr, "Unknown selection in XSelectionEvent\n");
        return;
  }
#endif
}

// https://www.x.org/releases/X11R7.5/doc/man/man3/XSelectionClearEvent.3.html
void
SX11Impl::processSelection (const XSelectionClearEvent& sel)
{
  SWindowListener* l = 0;
  SX11Window* sw = 0;
  if (sel.selection == xa_clipboard) 
  {
      l = listenerHashtable.get((long)clipOwnerSecondary);
      sw = (SX11Window*) windowHashtable.get((long)clipOwnerSecondary);
      clipOwnerSecondary = None;
  }
  else if (sel.selection == XA_PRIMARY) 
  {
      l = listenerHashtable.get((long)clipOwnerPrimary);
      sw = (SX11Window*) windowHashtable.get((long)clipOwnerPrimary);
      clipOwnerPrimary = None;
  } else {
      fprintf (stderr, "Unknown selection in XSelectionClearEvent\n");
      return;
  }
  if (sw == 0 || sw->getID() != (long)sel.window)
  {
    fprintf (stderr, "OOPS XSelectionClearEvent.\n");
    return;
  }
  if (l && sw)
  {
    l->lostClipSelection (sw);
  }
}

void
SX11Impl::addAccelerator (long id, const SAccelerator& a)
{ 
  acceleratorTable.put (a.toString(), id);
  //fprintf (stderr, "accel added %*.*s\n", SSARGS(a.toString()));
}

void
SX11Impl::removeAccelerator (long id, const SAccelerator& a)
{
  acceleratorTable.remove (a.toString());
}

bool
SX11Impl::sendAcceleratorPressed (int key, bool ctrl, bool shift, bool meta)
{
  if (accelPressed) return true;
  if (modalStack.size()!=0) return false;

  currentAccelerator = SAccelerator (key, ctrl, shift, meta);
  //fprintf (stderr, "accel pressed %*.*s\n", 
   //     SSARGS(currentAccelerator.toString()));
  long id = acceleratorTable.get (currentAccelerator.toString());
  if (id ==0) return false;
  SX11Window* sw = (SX11Window*) windowHashtable.get(id);
  if (sw ==0) return false;
  accelPressed = true;
  sw->_acceleratorPressed (currentAccelerator);

  return true;
}
bool
SX11Impl::sendAcceleratorReleased ()
{
  if (!accelPressed) return false;
  if (modalStack.size()!=0) return false;

  long id = acceleratorTable.get (currentAccelerator.toString());
  if (id ==0)
  { 
     accelPressed = false;
     return true;
  }
  SX11Window* sw = (SX11Window*) windowHashtable.get(id);
  if (sw ==0)
  {
     accelPressed = false;
     return true;
  }
  accelPressed = false;
  sw->_acceleratorReleased (currentAccelerator);
  return true;
}

/**
 * This is a really minimal implementation of xdnd target.
 */
bool 
SX11Impl::dndEvent (const XEvent& event)
{
  //char* an;
  switch (event.xany.type)
  {
  case ClientMessage:
  //fprintf (stderr, "SX11Impl::clientMessage\n");
   if (event.xclient.message_type == toAtom("XdndEnter"))
   {
     xdndEnter (event);
     return true;
   }
   if (event.xclient.message_type == toAtom("XdndPosition"))
   {
     xdndPosition (event);
     return true;
   }
   if (event.xclient.message_type == toAtom("XdndDrop"))
   {
     xdndDrop (event);
     return true;
   }
   if (event.xclient.message_type == toAtom("XdndLeave"))
   {
     xdndLeave (event);
     return true;
   }
   /* old kde */
   if (event.xclient.message_type == toAtom("DndProtocol"))
   {
     dndProtocol (event);
     return true;
   }
#if 0
   an = XGetAtomName(display,  (Atom)event.xclient.message_type);
   if (an)
   {
     fprintf (stderr, "Client Message not handled: %s\n", an);
     XFree (an);
   }
   else
   {
     fprintf (stderr, "Client Message not handled: %d\n", (int)event.xclient.message_type);
   }
#endif
   return false;
  case PropertyNotify:
  //fprintf (stderr, "SX11Impl::propertyNotify\n");
   return false;
  //default:
  //fprintf (stderr, "SX11Impl::xdndEvent unknown - %d\n", event.xany.type);
  }
  return false;
}

/**
 * The target receives XdndEnter. 
 * The ClientMessage only has space for three data types, 
 * so if the source supports more than this, the target must retrieve 
 * the property XdndTypeList from the source window in order to get the 
 * list of available types.
 */
void
SX11Impl::xdndEnter (const XEvent& event)
{
  //fprintf (stderr, "SX11Impl::xdndEnter\n");
  xdndSource = (Window) event.xclient.data.l[0];
  unsigned long enterFlags = (unsigned long) event.xclient.data.l[1];
  Atom dataType0 = event.xclient.data.l[2];
  Atom dataType1 = event.xclient.data.l[3];
  Atom dataType2 = event.xclient.data.l[4];
  //unsigned int version = (enterFlags>>24) & 0xff;
  bool hasmoreTypes = ((enterFlags & 1) != 0);
  SBinVector<Atom> allTypes;

  if (dataType0!=None) allTypes.append (dataType0);
  if (dataType1!=None) allTypes.append (dataType1);
  if (dataType2!=None) allTypes.append (dataType2);

  unsigned int i;

  /* check if we have more types */
  if (hasmoreTypes)
  {
    Atom            attributeType;
    int             format;
    unsigned long   nitems;
    unsigned long   bytesafter;
    Atom            *data;
    unsigned char   *c_data=0;

    XGetWindowProperty (
      display, xdndSource, toAtom ("XdndTypeList"),
      0L, 100L, False, XA_ATOM,
      &attributeType, &format, &nitems, &bytesafter, &c_data);

    data = (Atom*) c_data;

    if (attributeType==XA_ATOM && format==32 && nitems >0)
    {
      for (i=0; i<nitems; i++)
      {
        Atom a = data[i];
        allTypes.append (a);
      }
    } 
    if (data) XFree (data);
  }

#if 0
  SStringVector v;
  for (i=0; i<allTypes.size(); i++)
  {
    char * an = XGetAtomName(display,  allTypes[i]);
    if (an)
    {
      v.append (SString(an));
      XFree (an);
    }
  }
  fprintf (stderr, "XDND-version %u Enter[%u]: %*.*s\n", 
      version, allTypes.size(), SSARGS(v.join(",")));
#endif
  dndTypes = allTypes;
}

/**
 * The target receives XdndPosition
 * The target window must determine which widget the mouse is in and 
 * ask it whether or not it will accept the drop. For efficiency, the
 * target window should keep track of whether or not the widget will 
 * accept the drop and only ask again if the action changes or the
 * mouse enters a different part of the widget. Once the widget has 
 * said that it will accept the drop and as long as the action remains the
 * same and the mouse remains in the same part, the widget gets all the 
 * XdndPosition messages so that it can re-draw itself to show the
 * user where the data will be inserted, if appropriate. 
 *
 * To determine whether or not it can accept the drop, the target widget 
 * consults the list of types from the XdndEnter message and the
 * requested action from the XdndPosition message. 
 *
 * If it cannot perform the requested action, it can return either 
 * XdndActionCopy or XdndActionPrivate. If neither of these are possible,
 * then it should refuse the drop. 
 *
 * If it needs to look at the data itself, it calls 
 * XConvertSelection() for XdndSelection, the data type that it is 
 * interested in, and the given time stamp. (7) It can do this more than once, 
 * if necessary. 
 *
 * If it can accept the drop, it should highlight its border to notify the user. 
 * If it retrieved the data, it should cache it so it does not need to
 * be retrieved again when the actual drop occurs. 
 * ----------------------------------------------------------------------------
 * The target sends a ClientMessage of type XdndStatus. (2) This tells the 
 * source whether or not it will accept the drop, and, if so, what
 * action will be taken. It also includes a rectangle that means "don't send 
 * another XdndPosition message until the mouse moves out of here". 
 */
void
SX11Impl::xdndPosition (const XEvent& event)
{
  //fprintf (stderr, "SX11Impl::xdndPosition\n");
  if (xdndSource == None) return;
  if (xdndSource != (Window) event.xclient.data.l[0]) return;
#if 0
  unsigned long enterFlags = (unsigned long) event.xclient.data.l[1];
  unsigned long location = event.xclient.data.l[2]; /* x,y*/
  unsigned long timestamp = event.xclient.data.l[3];
  Atom action = event.xclient.data.l[4];
#endif
  bool accept = false;
  bool status = true;

  /* find the window */
  Window window = event.xclient.window;
  SX11Window* sw = (SX11Window*)windowHashtable.get((long)window);
  SWindowListener*l = 0;
  if (sw == 0)
  {
    fprintf (stderr, "SX11Impl::xdndPosition - no window\n");
    status=false; 
  }
  else
  {
    l = listenerHashtable.get(sw->getID());
  }
  if (l==0)
  {
    fprintf (stderr, "SX11Impl::xdndPosition - no listener\n");
    status=false; 
  }
  unsigned int i;
  unsigned int j;
  for (i=0; status && i<sw->targetAtoms.size() && !accept; i++)
  {
    for (j=0; j<dndTypes.size() && !accept; j++)
    {
      if (sw->targetAtoms[i] == dndTypes[j])
      {
        acceptedDataType = sw->targets[i];
        acceptedDataTypeAtom = dndTypes[j];
        accept = true;
      }
    }
  }

  /* create an xdnd status */
  XEvent cevt;
  cevt.xclient.type = ClientMessage;
  cevt.xclient.window = xdndSource;
  cevt.xclient.display = display;
  cevt.xclient.message_type = toAtom("XdndStatus"); 
  cevt.xclient.format = 32;
  cevt.xclient.data.l[0] = window;
  cevt.xclient.data.l[1] |= (accept&&status)?1:0; /* accept drop bit */
  cevt.xclient.data.l[2] = 0; /* x,y of no msg (root)*/
  cevt.xclient.data.l[3] = 0; /* x,y of no msg */
  cevt.xclient.data.l[4] = toAtom ("XdndActionCopy"); /* actions */
  XSendEvent (display, xdndSource,  False, NoEventMask, &cevt);
}

/**
 * If the mouse button is released in the window, the source waits 
 * for the last XdndStatus message (if necessary) and then sends a
 * ClientMessage of type XdndLeave or XdndDrop, depending on the "accept" 
 * flag in the last XdndStatus. 
 * If the source never received any XdndStatus messages at all, it 
 * should send XdndLeave without waiting. 
 * If the source doesn't receive the expected XdndStatus within a 
 * reasonable amount of time, it should send XdndLeave. While waiting
 * for XdndStatus, the source can block, but it must at least process 
 * SelectionRequest events so the target can examine the data. 
 */
void
SX11Impl::xdndDrop (const XEvent& event)
{
  //fprintf (stderr, "SX11Impl::xdndDrop\n");
  if (xdndSource == None) return;
  if (xdndSource != (Window) event.xclient.data.l[0]) return;
#if 0
  unsigned long flags = event.xclient.data.l[1]; 
#endif
  unsigned long timestamp = event.xclient.data.l[2];
  bool status = true;
  /* find the window */
  Window window = event.xclient.window;
  SX11Window* sw = (SX11Window*)windowHashtable.get((long)window);
  SWindowListener*l = 0;
  if (sw == 0)
  {
    fprintf (stderr, "SX11Impl::xdndPosition - no window\n");
    status=false; 
  }
  else
  {
    l = listenerHashtable.get(sw->getID());
  }
  if (l==0)
  {
    fprintf (stderr, "SX11Impl::xdndPosition - no listener\n");
    status=false; 
  }

  /* get the actual data */
  SString  data;
  if (status)
  {
    //fprintf (stderr, "Trying %*.*s...\n", SSARGS(acceptedDataType));
    data = getSelectionData (toAtom ("XdndSelection"),acceptedDataTypeAtom,
        window, timestamp);
    //fprintf (stderr, "got data: [%*.*s]\n", SSARGS(data));
  }
     
  /* create an xdnd finished */
  XEvent cevt;
  cevt.xclient.type = ClientMessage;
  cevt.xclient.window = xdndSource;
  cevt.xclient.display = display;
  cevt.xclient.message_type = toAtom("XdndFinished"); 
  cevt.xclient.format = 32;
  cevt.xclient.data.l[0] = window;
  cevt.xclient.data.l[1] = 0; /* finished flags */
  XSendEvent (display, xdndSource,  False, NoEventMask, &cevt);
  /* kde hacks */
  if  (data.size())
  {
    if (data[data.size()-1] == 0) data.truncate (data.size()-1);
  }
  while (data.size())
  {
    if (data[0] == ' ' && data[0] == '\n' && data[0] == '\r')
    {
       data.remove (0);
    }
    else
    {
      break;
    }
  }
  if (data.size()) l->drop (sw, acceptedDataType, data);
}

/**
 * if the target receives XdndLeave, it frees any cached data 
 * and forgets the whole incident. 
 */
void
SX11Impl::xdndLeave (const XEvent& event)
{
  //fprintf (stderr, "SX11Impl::xdndLeave\n");
  dndTypes.clear();
  xdndSource = None;
  acceptedDataType.clear();
  acceptedDataTypeAtom = None;
}

/**
 * COMPATIBILITY routine.
 * When a drop occurs, DND will send a client message event to the top 
 * level window of the application that receives the drop. The
 * event will have the following data: 
 *     Event.xclient.type              = ClientMessage;
 *     Event.xclient.message_type      = DndProtocol;
 *     Event.xclient.format            = 32;
 *     Event.xclient.data.l[0]         = DataType;
 *     Event.xclient.data.l[1]         = (long)event->xbutton.state;
 *     Event.xclient.data.l[2]         = (long)widget;
 *     Event.xclient.data.l[3]         = 0;
 *     Event.xclient.data.l[4]         = 0;
 *
 * So your program main event loop will need to intercept this message and 
 * take the appropriate action. Normally, this action consists
 * in getting the drop data and processing it. This can be done by getting 
 * the DndSelection property contents of the root window.
 * 
 * FIXME: I had no specifications when I wrote this hack.
 */
void
SX11Impl::dndProtocol (const XEvent& event)
{
  fprintf (stderr, "Drag and drop kack for kde < 2.0. \n");
  Atom dataType = (Atom) event.xclient.data.l[0];
  SString dataTypeString("Unknown");
  switch (dataType)
  {
  case 0: dataTypeString=SString("DndUnknown"); break;
  case 1: dataTypeString=SString("DndRawData"); break;
  case 2: dataTypeString=SString("DndFile"); break;
  case 3: dataTypeString=SString("DndFiles"); break;
  case 4: dataTypeString=SString("DndText"); break;
  case 5: dataTypeString=SString("DndDir"); break;
  case 6: dataTypeString=SString("DndLink"); break;
  case 7: dataTypeString=SString("DndExe"); break;
  case 8: dataTypeString=SString("DndURL"); break;
  case 9: dataTypeString=SString("DndMIME"); break;
  default:
     break;
  }
  //fprintf (stderr, "got dataType [%*.*s]\n", SSARGS(dataTypeString));
  Window window = (Window) event.xclient.data.l[2];
  //Window window = (Window) event.xclient.window;
  //Window window = (Window) (long)event.xclient.window;
  SX11Window* sw = (SX11Window*)windowHashtable.get((long)window);
  if (window==None) sw = 0;
  if (sw == 0)
  {
    /* ok. lets pick one */
    for (unsigned int i=0; sw == 0 && i<windowHashtable.size(); i++)
    {
      for (unsigned int j=0; sw == 0 && j<windowHashtable.size(i); j++)
      {
        SX11Window * sww = (SX11Window*) windowHashtable.get(i,j);
        if (sww==0) continue;
        if (sww->targetAtoms.size())
        {
          sw = sww;
        }
      }
    }
    if (sw == 0)
    {
      fprintf (stderr, "SX11Impl::dndProtocol - no window for %lu\n", 
        (unsigned long) window);
    }
    else
    {
      //fprintf (stderr, "SX11Impl::dndProtocol - substituted %lu with %lu\n", 
       // (unsigned long) window, (unsigned long) sw->getID());
    }
  }
  SWindowListener*l = (sw==0) ? 0 : listenerHashtable.get(sw->getID());
  if (l==0)
  {
    fprintf (stderr, "SX11Impl::dndProtocol - no listener\n");
  }

  /* get the actual data */
  if (l)
  {
    SString data = getPropertyData (root, toAtom ("DndSelection"), 8);
    /* kde hacks */
    if  (data.size())
    {
      if (data[data.size()-1] == 0) data.truncate (data.size()-1);
    }
    while (data.size())
    {
      if (data[0] == ' ' && data[0] == '\n' && data[0] == '\r')
      {
         data.remove (0);
      }
      else
      {
        break;
      }
    }
    int sz = strlen ("file:");
    if (data.size() > (unsigned int)sz 
            && strncmp (data.array(), "file:", sz)==0)
    {
      data.remove (0, (unsigned int) sz);
    }
    if  (data.size())
    {
      //fprintf (stderr, "got data[%*.*s]: [%*.*s]\n", 
       //  SSARGS (dataTypeString), SSARGS(data));
      l->drop (sw, dataTypeString, data);
    }
  }
}

Atom
SX11Impl::toAtom (const SString& str)
{
  SString a=str;
  a.append ((char)0);
  return XInternAtom (display, a.array(), False);
}
/**
 * if there is a modal window up and the ancestor is not that 
 * window return false.
 * @param is is the window to which we wish to deliver.
 */
bool
SX11Impl::isOKToDeliver (long wid)
{
 if (modalStack.size()==0) return true;
 long modal = modalStack[modalStack.size()-1];
 return isAncestor (display, root, modal, wid);
}

/**
 * check if window is ancestor
 */
static bool 
isAncestor (Display* display, long _root, long modal, long w)
{
  Window window = (Window) w;
  Window root = (Window) _root;
  while (window != None && window != (Window) root)
  {
     if (window == (Window) modal) return true;
     unsigned int nchildren;
     Window parent=None;
     Window rw=root;
     Window* children=0;
     if (!XQueryTree (display, window, &rw, &parent, &children, &nchildren))
     {
       return false;
     }
     root = rw;
     if (children) XFree (children);
     window = parent;
  }
  return false;
}
Window
SX11Impl::getTopLevelWindow (Window w)
{
  Window window = w;
  Window _root = root;
  while (window != None && window != (Window) _root)
  {
     unsigned int nchildren;
     Window parent=None;
     Window rw=_root;
     Window* children=0;
     if (!XQueryTree (display, window, &rw, &parent, &children, &nchildren))
     {
       return window;
     }
     _root = rw;
     if (children) XFree (children);
     if (parent == _root) return window;
     window = parent;
  }
  return window;
}

/**
 * does it make us more robust?
 */
extern "C"
{
  int
  handleX11Errors (Display *display, XErrorEvent *event)
  {
    char buffer_return[256];
    buffer_return[0] = 0;
    XGetErrorText (display, event->error_code, 
         buffer_return, sizeof (buffer_return)-1);
    buffer_return[255] = 0;
    if ( event->request_code == X_SetInputFocus)
    {
      //fprintf(stderr, "XSetInputFocus failed (not an error).\n");
    }
    else
    {
      fprintf(stderr, "X11Impl.cpp request_code=%d:\n%s\n", event->request_code, 
          buffer_return);
    }
    return 0;
  }
}
