/*
 *  Wine Clipboard Server
 *
 *      Copyright 1999  Noel Borthwick
 *
 * USAGE:
 *       wineclipsrv [selection_mask] [debugClass_mask] [clearAllSelections]
 *
 * The optional selection-mask argument is a bit mask of the selection
 * types to be acquired. Currently two selections are supported:
 *   1. PRIMARY (mask value 1)
 *   2. CLIPBOARD (mask value 2).
 *
 * debugClass_mask is a bit mask of all debugging classes for which messages
 * are to be output. The standard Wine debug class set FIXME(1), ERR(2),
 * WARN(4) and TRACE(8) are supported.
 *
 * If clearAllSelections == 1 *all* selections are lost whenever a SelectionClear
 * event is received.
 *
 * If no arguments are supplied the server aquires all selections. (mask value 3)
 * and defaults to output of only FIXME(1) and ERR(2) messages. The default for
 * clearAllSelections is 0.
 *
 * NOTES:
 *
 *    The Wine Clipboard Server is a standalone XLib application whose 
 * purpose is to manage the X selection when Wine exits.
 * The server itself is started automatically with the appropriate
 * selection masks, whenever Wine exits after acquiring the PRIMARY and/or
 * CLIPBOARD selection. (See X11DRV_CLIPBOARD_ResetOwner)
 * When the server starts, it first proceeds to capture the selection data from
 * Wine and then takes over the selection ownership. It does this by querying
 * the current selection owner(of the specified selections) for the TARGETS
 * selection target. It then proceeds to cache all the formats exposed by
 * TARGETS. If the selection does not support the TARGETS target, or if no
 * target formats are exposed, the server simply exits.
 * Once the cache has been filled, the server then actually acquires ownership
 * of the respective selection and begins fielding selection requests.
 * Selection requests are serviced from the cache. If a selection is lost the
 * server flushes its internal cache, destroying all data previously saved.
 * Once ALL selections have been lost the server terminates.
 *
 * TODO:
 */

#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>

#include "config.h"

/*
 *  Lightweight debug definitions for Wine Clipboard Server.
 *  The standard FIXME, ERR, WARN & TRACE classes are supported
 *  without debug channels.
 *  The standard defines NO_TRACE_MSGS and NO_DEBUG_MSGS will compile out
 *  TRACE, WARN and ERR and FIXME message displays.
 */

/* Internal definitions (do not use these directly) */

enum __DEBUG_CLASS { __DBCL_FIXME, __DBCL_ERR, __DBCL_WARN, __DBCL_TRACE, __DBCL_COUNT };

extern char __debug_msg_enabled[__DBCL_COUNT];

extern const char * const debug_cl_name[__DBCL_COUNT];

#define DEBUG_CLASS_COUNT __DBCL_COUNT

#define __GET_DEBUGGING(dbcl)    (__debug_msg_enabled[(dbcl)])
#define __SET_DEBUGGING(dbcl,on) (__debug_msg_enabled[(dbcl)] = (on))


#define __DPRINTF(dbcl) \
    (!__GET_DEBUGGING(dbcl) || \
    (printf("%s:%s:%s ", debug_cl_name[(dbcl)], progname, __FUNCTION__),0)) \
    ? 0 : printf

#define __DUMMY_DPRINTF 1 ? (void)0 : (void)((int (*)(char *, ...)) NULL)

/* use configure to allow user to compile out debugging messages */
#ifndef NO_TRACE_MSGS
  #define TRACE        __DPRINTF(__DBCL_TRACE)
#else
  #define TRACE        __DUMMY_DPRINTF
#endif /* NO_TRACE_MSGS */

#ifndef NO_DEBUG_MSGS
  #define WARN         __DPRINTF(__DBCL_WARN)
  #define FIXME        __DPRINTF(__DBCL_FIXME)
#else
  #define WARN         __DUMMY_DPRINTF
  #define FIXME        __DUMMY_DPRINTF
#endif /* NO_DEBUG_MSGS */

/* define error macro regardless of what is configured */
#define ERR        __DPRINTF(__DBCL_ERR)


#define TRUE 1
#define FALSE 0
typedef int BOOL;

/* Internal definitions for debugging messages(do not use these directly) */
const char * const debug_cl_name[] = { "fixme", "err", "warn", "trace" };
char __debug_msg_enabled[DEBUG_CLASS_COUNT] = {1, 1, 0, 0};


/* Selection masks */

#define S_NOSELECTION    0
#define S_PRIMARY        1
#define S_CLIPBOARD      2

/* Debugging class masks */

#define C_FIXME          1
#define C_ERR            2
#define C_WARN           4
#define C_TRACE          8

/*
 * 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 int  g_selectionToAcquire = 0;          /* Masks for the selection to be acquired */
static int  g_selectionAcquired = 0;           /* Contains the current selection masks */
static int  g_clearAllSelections = 0;          /* If TRUE *all* selections are lost on SelectionClear */
    
/* 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);


int main(int argc, char **argv)
{
    XEvent event;

    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);

    TRACE("Clipboard server running...\n");
    
    /* 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;

    /* Set the debugging class state from the command line argument */
    if (argc > 2)
    {
        int dbgClasses = atoi(argv[2]);
        
        __SET_DEBUGGING(__DBCL_FIXME, dbgClasses & C_FIXME);
        __SET_DEBUGGING(__DBCL_ERR, dbgClasses & C_ERR);
        __SET_DEBUGGING(__DBCL_WARN, dbgClasses & C_WARN);
        __SET_DEBUGGING(__DBCL_TRACE, dbgClasses & C_TRACE);
    }
        
    /* Set the "ClearSelections" state from the command line argument */
    if (argc > 3)
        g_clearAllSelections = atoi(argv[3]);
    
    return TRUE;
}


/**************************************************************************
 *		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 );
        TRACE("Cached %ld formats...\n", g_cPrimaryTargets);
    }
    if (g_selectionToAcquire & S_CLIPBOARD)
    {
        TRACE("Acquiring CLIPBOARD selection...\n");
        g_cClipboardTargets = CacheDataFormats( xaClipboard, &g_pClipboardCache );
        TRACE("Cached %ld formats...\n", g_cClipboardTargets);
    }

    /*
     * Now that we have cached the data, we proceed to acquire the selections
     */
    if (g_cPrimaryTargets)
    {
        /* Acquire the PRIMARY selection */
        while (XGetSelectionOwner(g_display,XA_PRIMARY) != g_win)
            XSetSelectionOwner(g_display, XA_PRIMARY, g_win, CurrentTime);
        
        g_selectionAcquired |= S_PRIMARY;
    }
    else
        TRACE("No PRIMARY targets - ownership not acquired.\n");
    
    if (g_cClipboardTargets)
    {
        /* Acquire the CLIPBOARD selection */
        while (XGetSelectionOwner(g_display,xaClipboard) != g_win)
            XSetSelectionOwner(g_display, xaClipboard, g_win, CurrentTime);

        g_selectionAcquired |= S_CLIPBOARD;
    }
    else
        TRACE("No CLIPBOARD targets - ownership not acquired.\n");

    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!\n");

              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)
    {
        TRACE("\tOwner failed to convert selection!\n");
        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 (%p)...\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;
  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...\n", 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 we're losing the CLIPBOARD selection, or if the preferences in .winerc
   * dictate that *all* selections should be cleared on loss of a selection,
   * we must give up all the selections we own.
   */
  if ( g_clearAllSelections || (event->selection == xaClipboard) )
  {
      TRACE("Lost CLIPBOARD (+PRIMARY) selection\n");
      
      /* We really lost CLIPBOARD but want to voluntarily lose PRIMARY */
      if ( (event->selection == xaClipboard)
           && (g_selectionAcquired & S_PRIMARY) )
      {
          XSetSelectionOwner(g_display, XA_PRIMARY, None, CurrentTime);
      }
      
      /* We really lost PRIMARY but want to voluntarily lose CLIPBOARD  */
      if ( (event->selection == XA_PRIMARY)
           && (g_selectionAcquired & S_CLIPBOARD) )
      {
          XSetSelectionOwner(g_display, xaClipboard, None, CurrentTime);
      }
      
      g_selectionAcquired = S_NOSELECTION;   /* Clear the selection masks */
  }
  else if (event->selection == XA_PRIMARY)
  {
      TRACE("Lost PRIMARY selection...\n");
      g_selectionAcquired &= ~S_PRIMARY;     /* Clear the PRIMARY flag */
  }

  /* 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=%ld\n", (long)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=%ld\n", (long)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));
}