Added clipboard server.

diff --git a/Makefile.in b/Makefile.in
index 9ca6f59..3ddf0e8 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -258,6 +258,8 @@
 	    $(INSTALL_DATA) $(LIB_TARGET) $(libdir); \
 	    if [ $(LIB_TARGET) = libwine.so.1.0 ]; then $(LDCONFIG); fi \
 	fi
+	[ -d $(bindir) ] || $(MKDIR) $(bindir)
+	$(INSTALL_PROGRAM) windows/x11drv/wineclipsrv $(bindir)/wineclipsrv
 
 uninstall_lib: dummy
 	cd $(libdir); $(RM) $(LIB_TARGET) libwine.a libwine.so wine.sym
diff --git a/windows/x11drv/.cvsignore b/windows/x11drv/.cvsignore
index f3c7a7c..4f940bf 100644
--- a/windows/x11drv/.cvsignore
+++ b/windows/x11drv/.cvsignore
@@ -1 +1,2 @@
 Makefile
+wineclipsrv
diff --git a/windows/x11drv/Makefile.in b/windows/x11drv/Makefile.in
index e037ca3..afc90d3 100644
--- a/windows/x11drv/Makefile.in
+++ b/windows/x11drv/Makefile.in
@@ -16,6 +16,13 @@
 	mouse.c \
 	wnd.c
 
+PROGRAMS = wineclipsrv
+
+all: $(MODULE).o $(PROGRAMS)
+
+wineclipsrv: wineclipsrv.c
+	$(CC) $(ALLCFLAGS) -o wineclipsrv $(SRCDIR)/wineclipsrv.c $(X_LIBS) $(XLIB) $(LIBS)
+
 all: $(MODULE).o
 
 @MAKE_RULES@
diff --git a/windows/x11drv/wineclipsrv.c b/windows/x11drv/wineclipsrv.c
new file mode 100644
index 0000000..63276f7
--- /dev/null
+++ b/windows/x11drv/wineclipsrv.c
@@ -0,0 +1,985 @@
+/*
+ *  Wine Clipboard Server
+ *
+ *      Copyright 1999  Noel Borthwick
+ *
+ * NOTES:
+ *    This file contains the implementation for the Clipboard server
+ *
+ * TODO:
+ *
+ */
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xos.h>
+#include <X11/Xatom.h>
+#include <stdio.h>
+
+/*  Lightweight debug definitions */
+
+#define __DPRINTF(dbname) (printf("%s:%s:%s ", dbname, progname, __FUNCTION__),0) ? 0 : printf
+#define __DUMMY_DPRINTF 1 ? (void)0 : (void)((int (*)(char *, ...)) NULL)
+
+#ifndef NO_TRACE_MSGS
+  #define TRACE        __DPRINTF("TRACE")
+#else
+  #define TRACE        __DUMMY_DPRINTF
+#endif /* NO_TRACE_MSGS */
+
+#ifndef NO_DEBUG_MSGS
+  #define WARN         __DPRINTF("WARN")
+  #define FIXME        __DPRINTF("FIXME")
+#else
+  #define WARN         __DUMMY_DPRINTF
+  #define FIXME        __DUMMY_DPRINTF
+#endif /* NO_DEBUG_MSGS */
+
+#define ERR        __DPRINTF("ERROR")
+
+
+#define TRUE 1
+#define FALSE 0
+typedef int BOOL;
+
+/* Selection masks */
+
+#define S_NOSELECTION    0
+#define S_PRIMARY        1
+#define S_CLIPBOARD      2
+
+
+/*
+ * Global variables 
+ */
+
+static Display *g_display = NULL;
+static int screen_num;
+static char *progname;                 /* name this program was invoked by */
+static Window g_win = 0;               /* the hidden clipboard server window */
+static GC g_gc = 0;
+
+static char *g_szOutOfMemory = "Insufficient memory!\n";
+
+/* X selection context info */
+static char _CLIPBOARD[] = "CLIPBOARD";        /* CLIPBOARD atom name */
+static char FMT_PREFIX[] = "<WCF>";            /* Prefix for windows specific formats */
+static int  g_selectionToAcquire = 0;          /* Masks for the selection to be acquired */
+static int  g_selectionAcquired = 0;           /* Contains the current selection masks */
+
+/* Selection cache */
+typedef struct tag_CACHEENTRY
+{
+    Atom target;
+    Atom type;
+    int nFormat;
+    int nElements;
+    void *pData;
+} CACHEENTRY, *PCACHEENTRY;
+
+static PCACHEENTRY g_pPrimaryCache = NULL;     /* Primary selection cache */
+static PCACHEENTRY g_pClipboardCache = NULL;   /* Clipboard selection cache */
+static unsigned long g_cPrimaryTargets = 0;    /* Number of TARGETS reported by PRIMARY selection */
+static unsigned long g_cClipboardTargets = 0;  /* Number of TARGETS reported by CLIPBOARD selection */
+
+/* Event names */
+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"
+};
+
+
+/*
+ * Prototypes 
+ */
+
+BOOL Init(int argc, char **argv);
+void TerminateServer( int ret );
+int AcquireSelection();
+int CacheDataFormats( Atom SelectionSrc, PCACHEENTRY *ppCache );
+void EmptyCache(PCACHEENTRY pCache, int nItems);
+BOOL FillCacheEntry( Atom SelectionSrc, Atom target, PCACHEENTRY pCacheEntry );
+BOOL LookupCacheItem( Atom selection, Atom target, PCACHEENTRY *ppCacheEntry );
+void EVENT_ProcessEvent( XEvent *event );
+Atom EVENT_SelectionRequest_MULTIPLE( XSelectionRequestEvent *pevent );
+void EVENT_SelectionRequest( XSelectionRequestEvent *event, BOOL bIsMultiple );
+void EVENT_SelectionClear( XSelectionClearEvent *event );
+void EVENT_PropertyNotify( XPropertyEvent *event );
+Pixmap DuplicatePixmap(Pixmap pixmap);
+void TextOut(Window win, GC gc, char *pStr);
+void getGC(Window win, GC *gc);
+
+
+void main(int argc, char **argv)
+{
+    XEvent event;
+    unsigned int width, height;	/* window size */
+
+    if ( !Init(argc, argv) )
+        exit(0);
+    
+    /* Acquire the selection after retrieving all clipboard data
+     * owned by the current selection owner. If we were unable to
+     * Acquire any selection, terminate right away.
+     */
+    if ( AcquireSelection() == S_NOSELECTION )
+        TerminateServer(0);
+    
+    /* Start an X event loop */
+    while (1)
+    {
+        XNextEvent(g_display, &event);
+        
+        EVENT_ProcessEvent( &event );
+    }
+}
+
+
+/**************************************************************************
+ *		Init()
+ *  Initialize the clipboard server
+ */
+BOOL Init(int argc, char **argv)
+{
+    unsigned int width, height;	/* window size */
+    unsigned int border_width = 4;	/* four pixels */
+    unsigned int display_width, display_height;
+    char *window_name = "Wine Clipboard Server";
+    XSizeHints *size_hints = NULL;
+    XWMHints *wm_hints = NULL;
+    XClassHint *class_hints = NULL;
+    XTextProperty windowName;
+    char *display_name = NULL;
+    
+    progname = argv[0];
+    
+    if (!(size_hints = XAllocSizeHints()))
+    {
+        ERR(g_szOutOfMemory);
+        return 0;
+    }
+    if (!(wm_hints = XAllocWMHints()))
+    {
+        ERR(g_szOutOfMemory);
+        return 0;
+    }
+    if (!(class_hints = XAllocClassHint()))
+    {
+        ERR(g_szOutOfMemory);
+        return 0;
+    }
+    
+    /* connect to X server */
+    if ( (g_display=XOpenDisplay(display_name)) == NULL )
+    {
+        ERR( "cannot connect to X server %s\n", XDisplayName(display_name));
+        return 0;
+    }
+    
+    /* get screen size from display structure macro */
+    screen_num = DefaultScreen(g_display);
+    display_width = DisplayWidth(g_display, screen_num);
+    display_height = DisplayHeight(g_display, screen_num);
+    
+    /* size window with enough room for text */
+    width = display_width/3, height = display_height/4;
+    
+    /* create opaque window */
+    g_win = XCreateSimpleWindow(g_display, RootWindow(g_display,screen_num),
+                    0, 0, width, height, border_width, BlackPixel(g_display,
+                    screen_num), WhitePixel(g_display,screen_num));
+    
+    
+    /* Set size hints for window manager.  The window manager may
+     * override these settings. */
+    
+    /* x, y, width, and height hints are now taken from
+     * the actual settings of the window when mapped. Note
+     * that PPosition and PSize must be specified anyway. */
+    
+    size_hints->flags = PPosition | PSize | PMinSize;
+    size_hints->min_width = 300;
+    size_hints->min_height = 200;
+    
+    /* These calls store window_name into XTextProperty structures
+     * and sets the other fields properly. */
+    if (XStringListToTextProperty(&window_name, 1, &windowName) == 0)
+    {
+       ERR( "structure allocation for windowName failed.\n");
+       TerminateServer(-1);
+    }
+            
+    wm_hints->initial_state = NormalState;
+    wm_hints->input = True;
+    wm_hints->flags = StateHint | InputHint;
+    
+    class_hints->res_name = progname;
+    class_hints->res_class = "WineClipSrv";
+
+    XSetWMProperties(g_display, g_win, &windowName, NULL, 
+                    argv, argc, size_hints, wm_hints, 
+                    class_hints);
+
+    /* Select event types wanted */
+    XSelectInput(g_display, g_win, ExposureMask | KeyPressMask | 
+                    ButtonPressMask | StructureNotifyMask | PropertyChangeMask );
+    
+    /* create GC for text and drawing */
+    getGC(g_win, &g_gc);
+    
+    /* Display window */
+    /* XMapWindow(g_display, g_win); */
+
+    /* Set the selections to be acquired from the command line argument.
+     * If none specified, default to all selections we understand.
+     */
+    if (argc > 1)
+        g_selectionToAcquire = atoi(argv[1]);
+    else
+        g_selectionToAcquire = S_PRIMARY | S_CLIPBOARD;
+        
+    TRACE("Clipboard server running...\n");
+}
+
+
+/**************************************************************************
+ *		TerminateServer()
+ */
+void TerminateServer( int ret )
+{
+    TRACE("Terminating Wine clipboard server...\n");
+    
+    /* Free Primary and Clipboard selection caches */
+    EmptyCache(g_pPrimaryCache, g_cPrimaryTargets);
+    EmptyCache(g_pClipboardCache, g_cClipboardTargets);
+
+    if (g_gc)
+        XFreeGC(g_display, g_gc);
+
+    if (g_display)
+        XCloseDisplay(g_display);
+
+    exit(ret);
+}
+
+
+/**************************************************************************
+ *		AcquireSelection()
+ *
+ * Acquire the selection after retrieving all clipboard data owned by 
+ * the current selection owner.
+ */
+int AcquireSelection()
+{
+    Atom xaClipboard = XInternAtom(g_display, _CLIPBOARD, False);
+
+    /*
+     *  For all selections we need to acquire, get a list of all targets
+     *  supplied by the current selection owner.
+     */
+    if (g_selectionToAcquire & S_PRIMARY)
+    {
+        TRACE("Acquiring PRIMARY selection...\n");
+        g_cPrimaryTargets = CacheDataFormats( XA_PRIMARY, &g_pPrimaryCache );
+        if (g_cPrimaryTargets)
+            XSetSelectionOwner(g_display, XA_PRIMARY, g_win, CurrentTime);
+        else
+            TRACE("No PRIMARY targets - ownership not acquired.\n");
+    }
+    if (g_selectionToAcquire & S_CLIPBOARD)
+    {
+        TRACE("Acquiring CLIPBOARD selection...\n");
+        g_cClipboardTargets = CacheDataFormats( xaClipboard, &g_pClipboardCache );
+        
+        if (g_cClipboardTargets)
+            XSetSelectionOwner(g_display, xaClipboard, g_win, CurrentTime);
+        else
+            TRACE("No CLIPBOARD targets - ownership not acquired.\n");
+    }
+
+    /* Remember the acquired selections */
+    if( XGetSelectionOwner(g_display,XA_PRIMARY) == g_win )
+        g_selectionAcquired |= S_PRIMARY;
+    if( XGetSelectionOwner(g_display,xaClipboard) == g_win )
+        g_selectionAcquired |= S_CLIPBOARD;
+
+    return g_selectionAcquired;
+}
+
+/**************************************************************************
+ *		CacheDataFormats
+ *
+ * Allocates and caches the list of data formats available from the current selection.
+ * This queries the selection owner for the TARGETS property and saves all
+ * reported property types.
+ */
+int CacheDataFormats( Atom SelectionSrc, PCACHEENTRY *ppCache )
+{
+    XEvent         xe;
+    Atom           aTargets;
+    Atom           atype=AnyPropertyType;
+    int		   aformat;
+    unsigned long  remain;
+    unsigned long  cSelectionTargets = 0;
+    Atom*	   targetList=NULL;
+    Window         ownerSelection = 0;
+
+    if (!ppCache)
+        return 0;
+    *ppCache = NULL;
+
+    /* Get the selection owner */
+    ownerSelection = XGetSelectionOwner(g_display, SelectionSrc);
+    if ( ownerSelection == None )
+        return cSelectionTargets;
+
+    /*
+     * Query the selection owner for the TARGETS property
+     */
+    aTargets = XInternAtom(g_display, "TARGETS", False);
+
+    TRACE("Requesting TARGETS selection for '%s' (owner=%08x)...\n",
+          XGetAtomName(g_display, SelectionSrc), (unsigned)ownerSelection );
+          
+    XConvertSelection(g_display, SelectionSrc, aTargets,
+                    XInternAtom(g_display, "SELECTION_DATA", False),
+                    g_win, CurrentTime);
+
+    /*
+     * Wait until SelectionNotify is received
+     */
+    while( TRUE )
+    {
+       if( XCheckTypedWindowEvent(g_display, g_win, SelectionNotify, &xe) )
+           if( xe.xselection.selection == SelectionSrc )
+               break;
+    }
+
+    /* Verify that the selection returned a valid TARGETS property */
+    if ( (xe.xselection.target != aTargets)
+          || (xe.xselection.property == None) )
+    {
+        TRACE("\tCould not retrieve TARGETS\n");
+        return cSelectionTargets;
+    }
+
+    /* Read the TARGETS property contents */
+    if(XGetWindowProperty(g_display, xe.xselection.requestor, xe.xselection.property,
+                            0, 0x3FFF, True, AnyPropertyType/*XA_ATOM*/, &atype, &aformat,
+                            &cSelectionTargets, &remain, (unsigned char**)&targetList) != Success)
+        TRACE("\tCouldn't read TARGETS property\n");
+    else
+    {
+       TRACE("\tType %s,Format %d,nItems %ld, Remain %ld\n",
+             XGetAtomName(g_display,atype),aformat,cSelectionTargets, remain);
+       /*
+        * The TARGETS property should have returned us a list of atoms
+        * corresponding to each selection target format supported.
+        */
+       if( (atype == XA_ATOM || atype == aTargets) && aformat == 32 )
+       {
+          int i;
+
+          /* Allocate the selection cache */
+          *ppCache = (PCACHEENTRY)calloc(cSelectionTargets, sizeof(CACHEENTRY));
+          
+          /* Cache these formats in the selection cache */
+          for (i = 0; i < cSelectionTargets; i++)
+          {
+              char *itemFmtName = XGetAtomName(g_display, targetList[i]);
+          
+              TRACE("\tAtom# %d: '%s'\n", i, itemFmtName);
+              
+              /* Populate the cache entry */
+              if (!FillCacheEntry( SelectionSrc, targetList[i], &((*ppCache)[i])))
+                  ERR("Failed to fill cache entry!");
+
+              XFree(itemFmtName);
+          }
+       }
+
+       /* Free the list of targets */
+       XFree(targetList);
+    }
+    
+    return cSelectionTargets;
+}
+
+/***********************************************************************
+ *           FillCacheEntry
+ *
+ *   Populates the specified cache entry
+ */
+BOOL FillCacheEntry( Atom SelectionSrc, Atom target, PCACHEENTRY pCacheEntry )
+{
+    XEvent            xe;
+    Window            w;
+    Atom              prop, reqType;
+    Atom	      atype=AnyPropertyType;
+    int		      aformat;
+    unsigned long     nitems,remain,itemSize;
+    long              lRequestLength;
+    unsigned char*    val=NULL;
+    BOOL              bRet = FALSE;
+
+    TRACE("Requesting %s selection from %s...\n",
+          XGetAtomName(g_display, target),
+          XGetAtomName(g_display, SelectionSrc) );
+
+    /* Ask the selection owner to convert the selection to the target format */
+    XConvertSelection(g_display, SelectionSrc, target,
+                    XInternAtom(g_display, "SELECTION_DATA", False),
+                    g_win, CurrentTime);
+
+    /* wait until SelectionNotify is received */
+    while( TRUE )
+    {
+       if( XCheckTypedWindowEvent(g_display, g_win, SelectionNotify, &xe) )
+           if( xe.xselection.selection == SelectionSrc )
+               break;
+    }
+
+    /* Now proceed to retrieve the actual converted property from
+     * the SELECTION_DATA atom */
+
+    w = xe.xselection.requestor;
+    prop = xe.xselection.property;
+    reqType = xe.xselection.target;
+    
+    if(prop == None)
+        return bRet;
+
+    TRACE("\tretrieving property %s from window %ld into %s\n",
+          XGetAtomName(g_display,reqType), (long)w, XGetAtomName(g_display,prop) );
+
+    /*
+     * First request a zero length in order to figure out the request size.
+     */
+    if(XGetWindowProperty(g_display,w,prop,0,0,False, AnyPropertyType/*reqType*/,
+                            &atype, &aformat, &nitems, &itemSize, &val) != Success)
+    {
+        WARN("\tcouldn't get property size\n");
+        return bRet;
+    }
+
+    /* Free zero length return data if any */
+    if ( val )
+    {
+       XFree(val);
+       val = NULL;
+    }
+    
+    TRACE("\tretrieving %ld bytes...\n", itemSize * aformat/8);
+    lRequestLength = (itemSize * aformat/8)/4  + 1;
+    
+    /*
+     * Retrieve the actual property in the required X format.
+     */
+    if(XGetWindowProperty(g_display,w,prop,0,lRequestLength,False,AnyPropertyType/*reqType*/,
+                          &atype, &aformat, &nitems, &remain, &val) != Success)
+    {
+        WARN("\tcouldn't read property\n");
+        return bRet;
+    }
+
+    TRACE("\tType %s,Format %d,nitems %ld,remain %ld,value %s\n",
+          atype ? XGetAtomName(g_display,atype) : NULL, aformat,nitems,remain,val);
+    
+    if (remain)
+    {
+        WARN("\tCouldn't read entire property- selection may be too large! Remain=%ld\n", remain);
+        goto END;
+    }
+
+    /*
+     * Populate the cache entry
+     */
+    pCacheEntry->target = target;
+    pCacheEntry->type = atype;
+    pCacheEntry->nFormat = aformat;
+    pCacheEntry->nElements = nitems;
+
+    if (atype == XA_PIXMAP)
+    {
+        Pixmap *pPixmap = (Pixmap *)val;
+        Pixmap newPixmap = DuplicatePixmap( *pPixmap );
+        pPixmap = (Pixmap*)calloc(1, sizeof(Pixmap));
+        *pPixmap = newPixmap;
+        pCacheEntry->pData = pPixmap;
+    }
+    else
+        pCacheEntry->pData = val;
+
+END:
+    /* Delete the property on the window now that we are done
+     * This will send a PropertyNotify event to the selection owner. */
+    XDeleteProperty(g_display,w,prop);
+    
+    return TRUE;
+}
+
+
+/***********************************************************************
+ *           LookupCacheItem
+ *
+ *   Lookup a target atom in the cache and get the matching cache entry
+ */
+BOOL LookupCacheItem( Atom selection, Atom target, PCACHEENTRY *ppCacheEntry )
+{
+    int i;
+    int             nCachetargets = 0;
+    PCACHEENTRY     pCache = NULL;
+    Atom            xaClipboard = XInternAtom(g_display, _CLIPBOARD, False);
+
+    /* Locate the cache to be used based on the selection type */
+    if ( selection == XA_PRIMARY )
+    {
+        pCache = g_pPrimaryCache;
+        nCachetargets = g_cPrimaryTargets;
+    }
+    else if ( selection == xaClipboard )
+    {
+        pCache = g_pClipboardCache;
+        nCachetargets = g_cClipboardTargets;
+    }
+
+    if (!pCache || !ppCacheEntry)
+        return FALSE;
+
+    *ppCacheEntry = NULL;
+    
+    /* Look for the target item in the cache */
+    for (i = 0; i < nCachetargets; i++)
+    {
+        if (pCache[i].target == target)
+        {
+            *ppCacheEntry = &pCache[i];
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+
+/***********************************************************************
+ *           EmptyCache
+ *
+ *   Empties the specified cache
+ */
+void EmptyCache(PCACHEENTRY pCache, int nItems)
+{
+    int i;
+    
+    if (!pCache)
+        return;
+
+    /* Release all items in the cache */
+    for (i = 0; i < nItems; i++)
+    {
+        if (pCache[i].target && pCache[i].pData)
+        {
+            /* If we have a Pixmap, free it first */
+            if (pCache[i].target == XA_PIXMAP || pCache[i].target == XA_BITMAP)
+            {
+                Pixmap *pPixmap = (Pixmap *)pCache[i].pData;
+                
+                TRACE("Freeing %s (handle=%ld)...\n",
+                      XGetAtomName(g_display, pCache[i].target), *pPixmap);
+                
+                XFreePixmap(g_display, *pPixmap);
+
+                /* Free the cached data item (allocated by us) */
+                free(pCache[i].pData);
+            }
+            else
+            {
+                TRACE("Freeing %s (0x%x)...\n",
+                      XGetAtomName(g_display, pCache[i].target), pCache[i].pData);
+            
+                /* Free the cached data item (allocated by X) */
+                XFree(pCache[i].pData);
+            }
+        }
+    }
+
+    /* Destroy the cache */
+    free(pCache);
+}
+
+
+/***********************************************************************
+ *           EVENT_ProcessEvent
+ *
+ * Process an X event.
+ */
+void EVENT_ProcessEvent( XEvent *event )
+{
+//  TRACE(" event %s for Window %08lx\n", event_names[event->type], event->xany.window );
+
+  switch (event->type)
+  {
+      case Expose:
+          /* don't draw the window */
+          if (event->xexpose.count != 0)
+                  break;
+
+          /* Output something */
+          TextOut(g_win, g_gc, "Click here to terminate");
+          break;
+          
+      case ConfigureNotify:
+          break;
+          
+      case ButtonPress:
+              /* fall into KeyPress (no break) */
+      case KeyPress:
+          TerminateServer(1);
+          break;
+
+      case SelectionRequest:
+          EVENT_SelectionRequest( (XSelectionRequestEvent *)event, FALSE );
+          break;
+  
+      case SelectionClear:
+          EVENT_SelectionClear( (XSelectionClearEvent*)event );
+          break;
+        
+      case PropertyNotify:
+          EVENT_PropertyNotify( (XPropertyEvent *)event );
+          break;
+
+      default: /* ignore all other events */
+          break;
+          
+  } /* end switch */
+
+}
+
+
+/***********************************************************************
+ *           EVENT_SelectionRequest_MULTIPLE
+ *  Service a MULTIPLE selection request event
+ *  rprop contains a list of (target,property) atom pairs.
+ *  The first atom names a target and the second names a property.
+ *  The effect is as if we have received a sequence of SelectionRequest events
+ *  (one for each atom pair) except that:
+ *  1. We reply with a SelectionNotify only when all the requested conversions
+ *  have been performed.
+ *  2. If we fail to convert the target named by an atom in the MULTIPLE property,
+ *  we replace the atom in the property by None.
+ */
+Atom EVENT_SelectionRequest_MULTIPLE( XSelectionRequestEvent *pevent )
+{
+    Atom           rprop;
+    Atom           atype=AnyPropertyType;
+    int		   aformat;
+    unsigned long  remain;
+    Atom*	   targetPropList=NULL;
+    unsigned long  cTargetPropList = 0;
+/*  Atom           xAtomPair = XInternAtom(g_display, "ATOM_PAIR", False); */
+    
+   /* If the specified property is None the requestor is an obsolete client.
+    * We support these by using the specified target atom as the reply property.
+    */
+    rprop = pevent->property;
+    if( rprop == None ) 
+        rprop = pevent->target;
+    if (!rprop)
+        goto END;
+
+    /* Read the MULTIPLE property contents. This should contain a list of
+     * (target,property) atom pairs.
+     */
+    if(XGetWindowProperty(g_display, pevent->requestor, rprop,
+                            0, 0x3FFF, False, AnyPropertyType, &atype, &aformat,
+                            &cTargetPropList, &remain, (unsigned char**)&targetPropList) != Success)
+        TRACE("\tCouldn't read MULTIPLE property\n");
+    else
+    {
+       TRACE("\tType %s,Format %d,nItems %ld, Remain %ld\n",
+                     XGetAtomName(g_display,atype),aformat,cTargetPropList,remain);
+
+       /*
+        * Make sure we got what we expect.
+        * NOTE: According to the X-ICCCM Version 2.0 documentation the property sent
+        * in a MULTIPLE selection request should be of type ATOM_PAIR.
+        * However some X apps(such as XPaint) are not compliant with this and return
+        * a user defined atom in atype when XGetWindowProperty is called.
+        * The data *is* an atom pair but is not denoted as such.
+        */
+       if(aformat == 32 /* atype == xAtomPair */ )
+       {
+          int i;
+          
+          /* Iterate through the ATOM_PAIR list and execute a SelectionRequest
+           * for each (target,property) pair */
+
+          for (i = 0; i < cTargetPropList; i+=2)
+          {
+              char *targetName = XGetAtomName(g_display, targetPropList[i]);
+              char *propName = XGetAtomName(g_display, targetPropList[i+1]);
+              XSelectionRequestEvent event;
+
+              TRACE("MULTIPLE(%d): Target='%s' Prop='%s'\n", i/2, targetName, propName);
+              XFree(targetName);
+              XFree(propName);
+              
+              /* We must have a non "None" property to service a MULTIPLE target atom */
+              if ( !targetPropList[i+1] )
+              {
+                  TRACE("\tMULTIPLE(%d): Skipping target with empty property!", i);
+                  continue;
+              }
+              
+              /* Set up an XSelectionRequestEvent for this (target,property) pair */
+              memcpy( &event, pevent, sizeof(XSelectionRequestEvent) );
+              event.target = targetPropList[i];
+              event.property = targetPropList[i+1];
+                  
+              /* Fire a SelectionRequest, informing the handler that we are processing
+               * a MULTIPLE selection request event.
+               */
+              EVENT_SelectionRequest( &event, TRUE );
+          }
+       }
+
+       /* Free the list of targets/properties */
+       XFree(targetPropList);
+    }
+
+END:
+    return rprop;
+}
+
+
+/***********************************************************************
+ *           EVENT_SelectionRequest
+ *  Process an event selection request event.
+ *  The bIsMultiple flag is used to signal when EVENT_SelectionRequest is called
+ *  recursively while servicing a "MULTIPLE" selection target.
+ *
+ */
+void EVENT_SelectionRequest( XSelectionRequestEvent *event, BOOL bIsMultiple )
+{
+  XSelectionEvent result;
+  Atom 	          rprop = None;
+  Window          request = event->requestor;
+  BOOL            couldOpen = FALSE;
+  Atom            xaMultiple = XInternAtom(g_display, "MULTIPLE", False);
+  PCACHEENTRY     pCacheEntry = NULL;
+  void            *pData = NULL;
+  Pixmap          pixmap;
+
+  /* If the specified property is None the requestor is an obsolete client.
+   * We support these by using the specified target atom as the reply property.
+   */
+  rprop = event->property;
+  if( rprop == None ) 
+      rprop = event->target;
+
+  TRACE("Request for %s in selection %s\n",
+        XGetAtomName(g_display, event->target), XGetAtomName(g_display, event->selection));
+
+  /* Handle MULTIPLE requests -  rprop contains a list of (target, property) atom pairs */
+  if(event->target == xaMultiple)
+  {
+      /* MULTIPLE selection request - will call us back recursively */
+      rprop = EVENT_SelectionRequest_MULTIPLE( event );
+      goto END;
+  }
+
+  /* Lookup the requested target property in the cache */
+  if ( !LookupCacheItem(event->selection, event->target, &pCacheEntry) )
+  {
+      TRACE("Item not available in cache!\n");
+      goto END;
+  }
+
+  /* Update the X property */
+  TRACE("\tUpdating property %s...", XGetAtomName(g_display, rprop));
+
+  /* If we have a request for a pixmap, return a duplicate */
+  
+  if(event->target == XA_PIXMAP || event->target == XA_BITMAP)
+  {
+    Pixmap *pPixmap = (Pixmap *)pCacheEntry->pData;
+    pixmap = DuplicatePixmap( *pPixmap );
+    pData = &pixmap;
+  }
+  else
+    pData = pCacheEntry->pData;
+  
+  XChangeProperty(g_display, request, rprop,
+                    pCacheEntry->type, pCacheEntry->nFormat, PropModeReplace,
+                    (unsigned char *)pData, pCacheEntry->nElements);
+
+END:
+  if( rprop == None) 
+      TRACE("\tRequest ignored\n");
+
+  /* reply to sender 
+   * SelectionNotify should be sent only at the end of a MULTIPLE request
+   */
+  if ( !bIsMultiple )
+  {
+    result.type = SelectionNotify;
+    result.display = g_display;
+    result.requestor = request;
+    result.selection = event->selection;
+    result.property = rprop;
+    result.target = event->target;
+    result.time = event->time;
+    TRACE("Sending SelectionNotify event...\n");
+    XSendEvent(g_display,event->requestor,False,NoEventMask,(XEvent*)&result);
+  }
+}
+
+
+/***********************************************************************
+ *           EVENT_SelectionClear
+ *   We receive this event when another client grabs the X selection.
+ *   If we lost both PRIMARY and CLIPBOARD we must terminate.
+ */
+void EVENT_SelectionClear( XSelectionClearEvent *event )
+{
+  Atom xaClipboard = XInternAtom(g_display, _CLIPBOARD, False);
+    
+  TRACE("()\n");
+
+  if (event->selection == XA_PRIMARY)
+  {
+      g_selectionAcquired &= ~S_PRIMARY;     /* Clear the PRIMARY flag */
+      TRACE("Lost PRIMARY selection...\n");
+  }
+  else if (event->selection == xaClipboard)
+  {
+      g_selectionAcquired &= ~S_CLIPBOARD;   /* Clear the CLIPBOARD flag */
+      TRACE("Lost CLIPBOARD selection...\n");
+  }
+  
+  /* Once we lose all our selections we have nothing more to do */
+  if (g_selectionAcquired == S_NOSELECTION)
+      TerminateServer(1);
+}
+
+/***********************************************************************
+ *           EVENT_PropertyNotify
+ *   We use this to release resources like Pixmaps when a selection
+ *   client no longer needs them.
+ */
+void EVENT_PropertyNotify( XPropertyEvent *event )
+{
+  TRACE("()\n");
+
+  /* Check if we have any resources to free */
+
+  switch(event->state)
+  {
+    case PropertyDelete:
+    {
+      TRACE("\tPropertyDelete for atom %s on window %ld\n",
+                    XGetAtomName(event->display, event->atom), (long)event->window);
+      
+      /* FreeResources( event->atom ); */
+      break;
+    }
+
+    case PropertyNewValue:
+    {
+      TRACE("\tPropertyNewValue for atom %s on window %ld\n\n",
+                    XGetAtomName(event->display, event->atom), (long)event->window);
+      break;
+    }
+    
+    default:
+      break;
+  }
+}
+
+/***********************************************************************
+ *           DuplicatePixmap
+ */
+Pixmap DuplicatePixmap(Pixmap pixmap)
+{
+    Pixmap newPixmap;
+    XImage *xi;
+    Window root;
+    int x,y;               /* Unused */
+    unsigned border_width; /* Unused */
+    unsigned int depth, width, height;
+
+    TRACE("\t() Pixmap=%ul\n", pixmap);
+          
+    /* Get the Pixmap dimensions and bit depth */
+    if ( 0 == XGetGeometry(g_display, pixmap, &root, &x, &y, &width, &height,
+                             &border_width, &depth) )
+        return 0;
+
+    TRACE("\tPixmap properties: width=%d, height=%d, depth=%d\n",
+          width, height, depth);
+    
+    newPixmap = XCreatePixmap(g_display, g_win, width, height, depth);
+        
+    xi = XGetImage(g_display, pixmap, 0, 0, width, height, AllPlanes, XYPixmap);
+
+    XPutImage(g_display, newPixmap, g_gc, xi, 0, 0, 0, 0, width, height);
+
+    XDestroyImage(xi);
+    
+    TRACE("\t() New Pixmap=%ul\n", newPixmap);
+    return newPixmap;
+}
+
+/***********************************************************************
+ *           getGC
+ * Get a GC to use for drawing
+ */
+void getGC(Window win, GC *gc)
+{
+	unsigned long valuemask = 0; /* ignore XGCvalues and use defaults */
+	XGCValues values;
+	unsigned int line_width = 6;
+	int line_style = LineOnOffDash;
+	int cap_style = CapRound;
+	int join_style = JoinRound;
+	int dash_offset = 0;
+	static char dash_list[] = {12, 24};
+	int list_length = 2;
+
+	/* Create default Graphics Context */
+	*gc = XCreateGC(g_display, win, valuemask, &values);
+
+	/* specify black foreground since default window background is 
+	 * white and default foreground is undefined. */
+	XSetForeground(g_display, *gc, BlackPixel(g_display,screen_num));
+
+	/* set line attributes */
+	XSetLineAttributes(g_display, *gc, line_width, line_style, 
+			cap_style, join_style);
+
+	/* set dashes */
+	XSetDashes(g_display, *gc, dash_offset, dash_list, list_length);
+}
+
+
+/***********************************************************************
+ *           TextOut
+ */
+void TextOut(Window win, GC gc, char *pStr)
+{
+	int y_offset, x_offset;
+
+	y_offset = 10;
+	x_offset = 2;
+
+	/* output text, centered on each line */
+	XDrawString(g_display, win, gc, x_offset, y_offset, pStr, 
+			strlen(pStr));
+}