Fixed an entry in the keyboard translation table for Ctrl-Break.
[wine] / windows / x11drv / wineclipsrv.c
1 /*
2  *  Wine Clipboard Server
3  *
4  *      Copyright 1999  Noel Borthwick
5  *
6  * NOTES:
7  *    This file contains the implementation for the Clipboard server
8  *
9  * TODO:
10  *
11  */
12 #include <X11/Xlib.h>
13 #include <X11/Xutil.h>
14 #include <X11/Xos.h>
15 #include <X11/Xatom.h>
16 #include <stdio.h>
17
18 /*  Lightweight debug definitions */
19
20 #define __DPRINTF(dbname) (printf("%s:%s:%s ", dbname, progname, __FUNCTION__),0) ? 0 : printf
21 #define __DUMMY_DPRINTF 1 ? (void)0 : (void)((int (*)(char *, ...)) NULL)
22
23 #ifndef NO_TRACE_MSGS
24   #define TRACE        __DPRINTF("TRACE")
25 #else
26   #define TRACE        __DUMMY_DPRINTF
27 #endif /* NO_TRACE_MSGS */
28
29 #ifndef NO_DEBUG_MSGS
30   #define WARN         __DPRINTF("WARN")
31   #define FIXME        __DPRINTF("FIXME")
32 #else
33   #define WARN         __DUMMY_DPRINTF
34   #define FIXME        __DUMMY_DPRINTF
35 #endif /* NO_DEBUG_MSGS */
36
37 #define ERR        __DPRINTF("ERROR")
38
39
40 #define TRUE 1
41 #define FALSE 0
42 typedef int BOOL;
43
44 /* Selection masks */
45
46 #define S_NOSELECTION    0
47 #define S_PRIMARY        1
48 #define S_CLIPBOARD      2
49
50
51 /*
52  * Global variables 
53  */
54
55 static Display *g_display = NULL;
56 static int screen_num;
57 static char *progname;                 /* name this program was invoked by */
58 static Window g_win = 0;               /* the hidden clipboard server window */
59 static GC g_gc = 0;
60
61 static char *g_szOutOfMemory = "Insufficient memory!\n";
62
63 /* X selection context info */
64 static char _CLIPBOARD[] = "CLIPBOARD";        /* CLIPBOARD atom name */
65 static char FMT_PREFIX[] = "<WCF>";            /* Prefix for windows specific formats */
66 static int  g_selectionToAcquire = 0;          /* Masks for the selection to be acquired */
67 static int  g_selectionAcquired = 0;           /* Contains the current selection masks */
68
69 /* Selection cache */
70 typedef struct tag_CACHEENTRY
71 {
72     Atom target;
73     Atom type;
74     int nFormat;
75     int nElements;
76     void *pData;
77 } CACHEENTRY, *PCACHEENTRY;
78
79 static PCACHEENTRY g_pPrimaryCache = NULL;     /* Primary selection cache */
80 static PCACHEENTRY g_pClipboardCache = NULL;   /* Clipboard selection cache */
81 static unsigned long g_cPrimaryTargets = 0;    /* Number of TARGETS reported by PRIMARY selection */
82 static unsigned long g_cClipboardTargets = 0;  /* Number of TARGETS reported by CLIPBOARD selection */
83
84 /* Event names */
85 static const char * const event_names[] =
86 {
87   "", "", "KeyPress", "KeyRelease", "ButtonPress", "ButtonRelease",
88   "MotionNotify", "EnterNotify", "LeaveNotify", "FocusIn", "FocusOut",
89   "KeymapNotify", "Expose", "GraphicsExpose", "NoExpose", "VisibilityNotify",
90   "CreateNotify", "DestroyNotify", "UnmapNotify", "MapNotify", "MapRequest",
91   "ReparentNotify", "ConfigureNotify", "ConfigureRequest", "GravityNotify",
92   "ResizeRequest", "CirculateNotify", "CirculateRequest", "PropertyNotify",
93   "SelectionClear", "SelectionRequest", "SelectionNotify", "ColormapNotify",
94   "ClientMessage", "MappingNotify"
95 };
96
97
98 /*
99  * Prototypes 
100  */
101
102 BOOL Init(int argc, char **argv);
103 void TerminateServer( int ret );
104 int AcquireSelection();
105 int CacheDataFormats( Atom SelectionSrc, PCACHEENTRY *ppCache );
106 void EmptyCache(PCACHEENTRY pCache, int nItems);
107 BOOL FillCacheEntry( Atom SelectionSrc, Atom target, PCACHEENTRY pCacheEntry );
108 BOOL LookupCacheItem( Atom selection, Atom target, PCACHEENTRY *ppCacheEntry );
109 void EVENT_ProcessEvent( XEvent *event );
110 Atom EVENT_SelectionRequest_MULTIPLE( XSelectionRequestEvent *pevent );
111 void EVENT_SelectionRequest( XSelectionRequestEvent *event, BOOL bIsMultiple );
112 void EVENT_SelectionClear( XSelectionClearEvent *event );
113 void EVENT_PropertyNotify( XPropertyEvent *event );
114 Pixmap DuplicatePixmap(Pixmap pixmap);
115 void TextOut(Window win, GC gc, char *pStr);
116 void getGC(Window win, GC *gc);
117
118
119 void main(int argc, char **argv)
120 {
121     XEvent event;
122     unsigned int width, height; /* window size */
123
124     if ( !Init(argc, argv) )
125         exit(0);
126     
127     /* Acquire the selection after retrieving all clipboard data
128      * owned by the current selection owner. If we were unable to
129      * Acquire any selection, terminate right away.
130      */
131     if ( AcquireSelection() == S_NOSELECTION )
132         TerminateServer(0);
133     
134     /* Start an X event loop */
135     while (1)
136     {
137         XNextEvent(g_display, &event);
138         
139         EVENT_ProcessEvent( &event );
140     }
141 }
142
143
144 /**************************************************************************
145  *              Init()
146  *  Initialize the clipboard server
147  */
148 BOOL Init(int argc, char **argv)
149 {
150     unsigned int width, height; /* window size */
151     unsigned int border_width = 4;      /* four pixels */
152     unsigned int display_width, display_height;
153     char *window_name = "Wine Clipboard Server";
154     XSizeHints *size_hints = NULL;
155     XWMHints *wm_hints = NULL;
156     XClassHint *class_hints = NULL;
157     XTextProperty windowName;
158     char *display_name = NULL;
159     
160     progname = argv[0];
161     
162     if (!(size_hints = XAllocSizeHints()))
163     {
164         ERR(g_szOutOfMemory);
165         return 0;
166     }
167     if (!(wm_hints = XAllocWMHints()))
168     {
169         ERR(g_szOutOfMemory);
170         return 0;
171     }
172     if (!(class_hints = XAllocClassHint()))
173     {
174         ERR(g_szOutOfMemory);
175         return 0;
176     }
177     
178     /* connect to X server */
179     if ( (g_display=XOpenDisplay(display_name)) == NULL )
180     {
181         ERR( "cannot connect to X server %s\n", XDisplayName(display_name));
182         return 0;
183     }
184     
185     /* get screen size from display structure macro */
186     screen_num = DefaultScreen(g_display);
187     display_width = DisplayWidth(g_display, screen_num);
188     display_height = DisplayHeight(g_display, screen_num);
189     
190     /* size window with enough room for text */
191     width = display_width/3, height = display_height/4;
192     
193     /* create opaque window */
194     g_win = XCreateSimpleWindow(g_display, RootWindow(g_display,screen_num),
195                     0, 0, width, height, border_width, BlackPixel(g_display,
196                     screen_num), WhitePixel(g_display,screen_num));
197     
198     
199     /* Set size hints for window manager.  The window manager may
200      * override these settings. */
201     
202     /* x, y, width, and height hints are now taken from
203      * the actual settings of the window when mapped. Note
204      * that PPosition and PSize must be specified anyway. */
205     
206     size_hints->flags = PPosition | PSize | PMinSize;
207     size_hints->min_width = 300;
208     size_hints->min_height = 200;
209     
210     /* These calls store window_name into XTextProperty structures
211      * and sets the other fields properly. */
212     if (XStringListToTextProperty(&window_name, 1, &windowName) == 0)
213     {
214        ERR( "structure allocation for windowName failed.\n");
215        TerminateServer(-1);
216     }
217             
218     wm_hints->initial_state = NormalState;
219     wm_hints->input = True;
220     wm_hints->flags = StateHint | InputHint;
221     
222     class_hints->res_name = progname;
223     class_hints->res_class = "WineClipSrv";
224
225     XSetWMProperties(g_display, g_win, &windowName, NULL, 
226                     argv, argc, size_hints, wm_hints, 
227                     class_hints);
228
229     /* Select event types wanted */
230     XSelectInput(g_display, g_win, ExposureMask | KeyPressMask | 
231                     ButtonPressMask | StructureNotifyMask | PropertyChangeMask );
232     
233     /* create GC for text and drawing */
234     getGC(g_win, &g_gc);
235     
236     /* Display window */
237     /* XMapWindow(g_display, g_win); */
238
239     /* Set the selections to be acquired from the command line argument.
240      * If none specified, default to all selections we understand.
241      */
242     if (argc > 1)
243         g_selectionToAcquire = atoi(argv[1]);
244     else
245         g_selectionToAcquire = S_PRIMARY | S_CLIPBOARD;
246         
247     TRACE("Clipboard server running...\n");
248 }
249
250
251 /**************************************************************************
252  *              TerminateServer()
253  */
254 void TerminateServer( int ret )
255 {
256     TRACE("Terminating Wine clipboard server...\n");
257     
258     /* Free Primary and Clipboard selection caches */
259     EmptyCache(g_pPrimaryCache, g_cPrimaryTargets);
260     EmptyCache(g_pClipboardCache, g_cClipboardTargets);
261
262     if (g_gc)
263         XFreeGC(g_display, g_gc);
264
265     if (g_display)
266         XCloseDisplay(g_display);
267
268     exit(ret);
269 }
270
271
272 /**************************************************************************
273  *              AcquireSelection()
274  *
275  * Acquire the selection after retrieving all clipboard data owned by 
276  * the current selection owner.
277  */
278 int AcquireSelection()
279 {
280     Atom xaClipboard = XInternAtom(g_display, _CLIPBOARD, False);
281
282     /*
283      *  For all selections we need to acquire, get a list of all targets
284      *  supplied by the current selection owner.
285      */
286     if (g_selectionToAcquire & S_PRIMARY)
287     {
288         TRACE("Acquiring PRIMARY selection...\n");
289         g_cPrimaryTargets = CacheDataFormats( XA_PRIMARY, &g_pPrimaryCache );
290         if (g_cPrimaryTargets)
291             XSetSelectionOwner(g_display, XA_PRIMARY, g_win, CurrentTime);
292         else
293             TRACE("No PRIMARY targets - ownership not acquired.\n");
294     }
295     if (g_selectionToAcquire & S_CLIPBOARD)
296     {
297         TRACE("Acquiring CLIPBOARD selection...\n");
298         g_cClipboardTargets = CacheDataFormats( xaClipboard, &g_pClipboardCache );
299         
300         if (g_cClipboardTargets)
301             XSetSelectionOwner(g_display, xaClipboard, g_win, CurrentTime);
302         else
303             TRACE("No CLIPBOARD targets - ownership not acquired.\n");
304     }
305
306     /* Remember the acquired selections */
307     if( XGetSelectionOwner(g_display,XA_PRIMARY) == g_win )
308         g_selectionAcquired |= S_PRIMARY;
309     if( XGetSelectionOwner(g_display,xaClipboard) == g_win )
310         g_selectionAcquired |= S_CLIPBOARD;
311
312     return g_selectionAcquired;
313 }
314
315 /**************************************************************************
316  *              CacheDataFormats
317  *
318  * Allocates and caches the list of data formats available from the current selection.
319  * This queries the selection owner for the TARGETS property and saves all
320  * reported property types.
321  */
322 int CacheDataFormats( Atom SelectionSrc, PCACHEENTRY *ppCache )
323 {
324     XEvent         xe;
325     Atom           aTargets;
326     Atom           atype=AnyPropertyType;
327     int            aformat;
328     unsigned long  remain;
329     unsigned long  cSelectionTargets = 0;
330     Atom*          targetList=NULL;
331     Window         ownerSelection = 0;
332
333     if (!ppCache)
334         return 0;
335     *ppCache = NULL;
336
337     /* Get the selection owner */
338     ownerSelection = XGetSelectionOwner(g_display, SelectionSrc);
339     if ( ownerSelection == None )
340         return cSelectionTargets;
341
342     /*
343      * Query the selection owner for the TARGETS property
344      */
345     aTargets = XInternAtom(g_display, "TARGETS", False);
346
347     TRACE("Requesting TARGETS selection for '%s' (owner=%08x)...\n",
348           XGetAtomName(g_display, SelectionSrc), (unsigned)ownerSelection );
349           
350     XConvertSelection(g_display, SelectionSrc, aTargets,
351                     XInternAtom(g_display, "SELECTION_DATA", False),
352                     g_win, CurrentTime);
353
354     /*
355      * Wait until SelectionNotify is received
356      */
357     while( TRUE )
358     {
359        if( XCheckTypedWindowEvent(g_display, g_win, SelectionNotify, &xe) )
360            if( xe.xselection.selection == SelectionSrc )
361                break;
362     }
363
364     /* Verify that the selection returned a valid TARGETS property */
365     if ( (xe.xselection.target != aTargets)
366           || (xe.xselection.property == None) )
367     {
368         TRACE("\tCould not retrieve TARGETS\n");
369         return cSelectionTargets;
370     }
371
372     /* Read the TARGETS property contents */
373     if(XGetWindowProperty(g_display, xe.xselection.requestor, xe.xselection.property,
374                             0, 0x3FFF, True, AnyPropertyType/*XA_ATOM*/, &atype, &aformat,
375                             &cSelectionTargets, &remain, (unsigned char**)&targetList) != Success)
376         TRACE("\tCouldn't read TARGETS property\n");
377     else
378     {
379        TRACE("\tType %s,Format %d,nItems %ld, Remain %ld\n",
380              XGetAtomName(g_display,atype),aformat,cSelectionTargets, remain);
381        /*
382         * The TARGETS property should have returned us a list of atoms
383         * corresponding to each selection target format supported.
384         */
385        if( (atype == XA_ATOM || atype == aTargets) && aformat == 32 )
386        {
387           int i;
388
389           /* Allocate the selection cache */
390           *ppCache = (PCACHEENTRY)calloc(cSelectionTargets, sizeof(CACHEENTRY));
391           
392           /* Cache these formats in the selection cache */
393           for (i = 0; i < cSelectionTargets; i++)
394           {
395               char *itemFmtName = XGetAtomName(g_display, targetList[i]);
396           
397               TRACE("\tAtom# %d: '%s'\n", i, itemFmtName);
398               
399               /* Populate the cache entry */
400               if (!FillCacheEntry( SelectionSrc, targetList[i], &((*ppCache)[i])))
401                   ERR("Failed to fill cache entry!");
402
403               XFree(itemFmtName);
404           }
405        }
406
407        /* Free the list of targets */
408        XFree(targetList);
409     }
410     
411     return cSelectionTargets;
412 }
413
414 /***********************************************************************
415  *           FillCacheEntry
416  *
417  *   Populates the specified cache entry
418  */
419 BOOL FillCacheEntry( Atom SelectionSrc, Atom target, PCACHEENTRY pCacheEntry )
420 {
421     XEvent            xe;
422     Window            w;
423     Atom              prop, reqType;
424     Atom              atype=AnyPropertyType;
425     int               aformat;
426     unsigned long     nitems,remain,itemSize;
427     long              lRequestLength;
428     unsigned char*    val=NULL;
429     BOOL              bRet = FALSE;
430
431     TRACE("Requesting %s selection from %s...\n",
432           XGetAtomName(g_display, target),
433           XGetAtomName(g_display, SelectionSrc) );
434
435     /* Ask the selection owner to convert the selection to the target format */
436     XConvertSelection(g_display, SelectionSrc, target,
437                     XInternAtom(g_display, "SELECTION_DATA", False),
438                     g_win, CurrentTime);
439
440     /* wait until SelectionNotify is received */
441     while( TRUE )
442     {
443        if( XCheckTypedWindowEvent(g_display, g_win, SelectionNotify, &xe) )
444            if( xe.xselection.selection == SelectionSrc )
445                break;
446     }
447
448     /* Now proceed to retrieve the actual converted property from
449      * the SELECTION_DATA atom */
450
451     w = xe.xselection.requestor;
452     prop = xe.xselection.property;
453     reqType = xe.xselection.target;
454     
455     if(prop == None)
456         return bRet;
457
458     TRACE("\tretrieving property %s from window %ld into %s\n",
459           XGetAtomName(g_display,reqType), (long)w, XGetAtomName(g_display,prop) );
460
461     /*
462      * First request a zero length in order to figure out the request size.
463      */
464     if(XGetWindowProperty(g_display,w,prop,0,0,False, AnyPropertyType/*reqType*/,
465                             &atype, &aformat, &nitems, &itemSize, &val) != Success)
466     {
467         WARN("\tcouldn't get property size\n");
468         return bRet;
469     }
470
471     /* Free zero length return data if any */
472     if ( val )
473     {
474        XFree(val);
475        val = NULL;
476     }
477     
478     TRACE("\tretrieving %ld bytes...\n", itemSize * aformat/8);
479     lRequestLength = (itemSize * aformat/8)/4  + 1;
480     
481     /*
482      * Retrieve the actual property in the required X format.
483      */
484     if(XGetWindowProperty(g_display,w,prop,0,lRequestLength,False,AnyPropertyType/*reqType*/,
485                           &atype, &aformat, &nitems, &remain, &val) != Success)
486     {
487         WARN("\tcouldn't read property\n");
488         return bRet;
489     }
490
491     TRACE("\tType %s,Format %d,nitems %ld,remain %ld,value %s\n",
492           atype ? XGetAtomName(g_display,atype) : NULL, aformat,nitems,remain,val);
493     
494     if (remain)
495     {
496         WARN("\tCouldn't read entire property- selection may be too large! Remain=%ld\n", remain);
497         goto END;
498     }
499
500     /*
501      * Populate the cache entry
502      */
503     pCacheEntry->target = target;
504     pCacheEntry->type = atype;
505     pCacheEntry->nFormat = aformat;
506     pCacheEntry->nElements = nitems;
507
508     if (atype == XA_PIXMAP)
509     {
510         Pixmap *pPixmap = (Pixmap *)val;
511         Pixmap newPixmap = DuplicatePixmap( *pPixmap );
512         pPixmap = (Pixmap*)calloc(1, sizeof(Pixmap));
513         *pPixmap = newPixmap;
514         pCacheEntry->pData = pPixmap;
515     }
516     else
517         pCacheEntry->pData = val;
518
519 END:
520     /* Delete the property on the window now that we are done
521      * This will send a PropertyNotify event to the selection owner. */
522     XDeleteProperty(g_display,w,prop);
523     
524     return TRUE;
525 }
526
527
528 /***********************************************************************
529  *           LookupCacheItem
530  *
531  *   Lookup a target atom in the cache and get the matching cache entry
532  */
533 BOOL LookupCacheItem( Atom selection, Atom target, PCACHEENTRY *ppCacheEntry )
534 {
535     int i;
536     int             nCachetargets = 0;
537     PCACHEENTRY     pCache = NULL;
538     Atom            xaClipboard = XInternAtom(g_display, _CLIPBOARD, False);
539
540     /* Locate the cache to be used based on the selection type */
541     if ( selection == XA_PRIMARY )
542     {
543         pCache = g_pPrimaryCache;
544         nCachetargets = g_cPrimaryTargets;
545     }
546     else if ( selection == xaClipboard )
547     {
548         pCache = g_pClipboardCache;
549         nCachetargets = g_cClipboardTargets;
550     }
551
552     if (!pCache || !ppCacheEntry)
553         return FALSE;
554
555     *ppCacheEntry = NULL;
556     
557     /* Look for the target item in the cache */
558     for (i = 0; i < nCachetargets; i++)
559     {
560         if (pCache[i].target == target)
561         {
562             *ppCacheEntry = &pCache[i];
563             return TRUE;
564         }
565     }
566
567     return FALSE;
568 }
569
570
571 /***********************************************************************
572  *           EmptyCache
573  *
574  *   Empties the specified cache
575  */
576 void EmptyCache(PCACHEENTRY pCache, int nItems)
577 {
578     int i;
579     
580     if (!pCache)
581         return;
582
583     /* Release all items in the cache */
584     for (i = 0; i < nItems; i++)
585     {
586         if (pCache[i].target && pCache[i].pData)
587         {
588             /* If we have a Pixmap, free it first */
589             if (pCache[i].target == XA_PIXMAP || pCache[i].target == XA_BITMAP)
590             {
591                 Pixmap *pPixmap = (Pixmap *)pCache[i].pData;
592                 
593                 TRACE("Freeing %s (handle=%ld)...\n",
594                       XGetAtomName(g_display, pCache[i].target), *pPixmap);
595                 
596                 XFreePixmap(g_display, *pPixmap);
597
598                 /* Free the cached data item (allocated by us) */
599                 free(pCache[i].pData);
600             }
601             else
602             {
603                 TRACE("Freeing %s (0x%x)...\n",
604                       XGetAtomName(g_display, pCache[i].target), pCache[i].pData);
605             
606                 /* Free the cached data item (allocated by X) */
607                 XFree(pCache[i].pData);
608             }
609         }
610     }
611
612     /* Destroy the cache */
613     free(pCache);
614 }
615
616
617 /***********************************************************************
618  *           EVENT_ProcessEvent
619  *
620  * Process an X event.
621  */
622 void EVENT_ProcessEvent( XEvent *event )
623 {
624 //  TRACE(" event %s for Window %08lx\n", event_names[event->type], event->xany.window );
625
626   switch (event->type)
627   {
628       case Expose:
629           /* don't draw the window */
630           if (event->xexpose.count != 0)
631                   break;
632
633           /* Output something */
634           TextOut(g_win, g_gc, "Click here to terminate");
635           break;
636           
637       case ConfigureNotify:
638           break;
639           
640       case ButtonPress:
641               /* fall into KeyPress (no break) */
642       case KeyPress:
643           TerminateServer(1);
644           break;
645
646       case SelectionRequest:
647           EVENT_SelectionRequest( (XSelectionRequestEvent *)event, FALSE );
648           break;
649   
650       case SelectionClear:
651           EVENT_SelectionClear( (XSelectionClearEvent*)event );
652           break;
653         
654       case PropertyNotify:
655           EVENT_PropertyNotify( (XPropertyEvent *)event );
656           break;
657
658       default: /* ignore all other events */
659           break;
660           
661   } /* end switch */
662
663 }
664
665
666 /***********************************************************************
667  *           EVENT_SelectionRequest_MULTIPLE
668  *  Service a MULTIPLE selection request event
669  *  rprop contains a list of (target,property) atom pairs.
670  *  The first atom names a target and the second names a property.
671  *  The effect is as if we have received a sequence of SelectionRequest events
672  *  (one for each atom pair) except that:
673  *  1. We reply with a SelectionNotify only when all the requested conversions
674  *  have been performed.
675  *  2. If we fail to convert the target named by an atom in the MULTIPLE property,
676  *  we replace the atom in the property by None.
677  */
678 Atom EVENT_SelectionRequest_MULTIPLE( XSelectionRequestEvent *pevent )
679 {
680     Atom           rprop;
681     Atom           atype=AnyPropertyType;
682     int            aformat;
683     unsigned long  remain;
684     Atom*          targetPropList=NULL;
685     unsigned long  cTargetPropList = 0;
686 /*  Atom           xAtomPair = XInternAtom(g_display, "ATOM_PAIR", False); */
687     
688    /* If the specified property is None the requestor is an obsolete client.
689     * We support these by using the specified target atom as the reply property.
690     */
691     rprop = pevent->property;
692     if( rprop == None ) 
693         rprop = pevent->target;
694     if (!rprop)
695         goto END;
696
697     /* Read the MULTIPLE property contents. This should contain a list of
698      * (target,property) atom pairs.
699      */
700     if(XGetWindowProperty(g_display, pevent->requestor, rprop,
701                             0, 0x3FFF, False, AnyPropertyType, &atype, &aformat,
702                             &cTargetPropList, &remain, (unsigned char**)&targetPropList) != Success)
703         TRACE("\tCouldn't read MULTIPLE property\n");
704     else
705     {
706        TRACE("\tType %s,Format %d,nItems %ld, Remain %ld\n",
707                      XGetAtomName(g_display,atype),aformat,cTargetPropList,remain);
708
709        /*
710         * Make sure we got what we expect.
711         * NOTE: According to the X-ICCCM Version 2.0 documentation the property sent
712         * in a MULTIPLE selection request should be of type ATOM_PAIR.
713         * However some X apps(such as XPaint) are not compliant with this and return
714         * a user defined atom in atype when XGetWindowProperty is called.
715         * The data *is* an atom pair but is not denoted as such.
716         */
717        if(aformat == 32 /* atype == xAtomPair */ )
718        {
719           int i;
720           
721           /* Iterate through the ATOM_PAIR list and execute a SelectionRequest
722            * for each (target,property) pair */
723
724           for (i = 0; i < cTargetPropList; i+=2)
725           {
726               char *targetName = XGetAtomName(g_display, targetPropList[i]);
727               char *propName = XGetAtomName(g_display, targetPropList[i+1]);
728               XSelectionRequestEvent event;
729
730               TRACE("MULTIPLE(%d): Target='%s' Prop='%s'\n", i/2, targetName, propName);
731               XFree(targetName);
732               XFree(propName);
733               
734               /* We must have a non "None" property to service a MULTIPLE target atom */
735               if ( !targetPropList[i+1] )
736               {
737                   TRACE("\tMULTIPLE(%d): Skipping target with empty property!", i);
738                   continue;
739               }
740               
741               /* Set up an XSelectionRequestEvent for this (target,property) pair */
742               memcpy( &event, pevent, sizeof(XSelectionRequestEvent) );
743               event.target = targetPropList[i];
744               event.property = targetPropList[i+1];
745                   
746               /* Fire a SelectionRequest, informing the handler that we are processing
747                * a MULTIPLE selection request event.
748                */
749               EVENT_SelectionRequest( &event, TRUE );
750           }
751        }
752
753        /* Free the list of targets/properties */
754        XFree(targetPropList);
755     }
756
757 END:
758     return rprop;
759 }
760
761
762 /***********************************************************************
763  *           EVENT_SelectionRequest
764  *  Process an event selection request event.
765  *  The bIsMultiple flag is used to signal when EVENT_SelectionRequest is called
766  *  recursively while servicing a "MULTIPLE" selection target.
767  *
768  */
769 void EVENT_SelectionRequest( XSelectionRequestEvent *event, BOOL bIsMultiple )
770 {
771   XSelectionEvent result;
772   Atom            rprop = None;
773   Window          request = event->requestor;
774   BOOL            couldOpen = FALSE;
775   Atom            xaMultiple = XInternAtom(g_display, "MULTIPLE", False);
776   PCACHEENTRY     pCacheEntry = NULL;
777   void            *pData = NULL;
778   Pixmap          pixmap;
779
780   /* If the specified property is None the requestor is an obsolete client.
781    * We support these by using the specified target atom as the reply property.
782    */
783   rprop = event->property;
784   if( rprop == None ) 
785       rprop = event->target;
786
787   TRACE("Request for %s in selection %s\n",
788         XGetAtomName(g_display, event->target), XGetAtomName(g_display, event->selection));
789
790   /* Handle MULTIPLE requests -  rprop contains a list of (target, property) atom pairs */
791   if(event->target == xaMultiple)
792   {
793       /* MULTIPLE selection request - will call us back recursively */
794       rprop = EVENT_SelectionRequest_MULTIPLE( event );
795       goto END;
796   }
797
798   /* Lookup the requested target property in the cache */
799   if ( !LookupCacheItem(event->selection, event->target, &pCacheEntry) )
800   {
801       TRACE("Item not available in cache!\n");
802       goto END;
803   }
804
805   /* Update the X property */
806   TRACE("\tUpdating property %s...", XGetAtomName(g_display, rprop));
807
808   /* If we have a request for a pixmap, return a duplicate */
809   
810   if(event->target == XA_PIXMAP || event->target == XA_BITMAP)
811   {
812     Pixmap *pPixmap = (Pixmap *)pCacheEntry->pData;
813     pixmap = DuplicatePixmap( *pPixmap );
814     pData = &pixmap;
815   }
816   else
817     pData = pCacheEntry->pData;
818   
819   XChangeProperty(g_display, request, rprop,
820                     pCacheEntry->type, pCacheEntry->nFormat, PropModeReplace,
821                     (unsigned char *)pData, pCacheEntry->nElements);
822
823 END:
824   if( rprop == None) 
825       TRACE("\tRequest ignored\n");
826
827   /* reply to sender 
828    * SelectionNotify should be sent only at the end of a MULTIPLE request
829    */
830   if ( !bIsMultiple )
831   {
832     result.type = SelectionNotify;
833     result.display = g_display;
834     result.requestor = request;
835     result.selection = event->selection;
836     result.property = rprop;
837     result.target = event->target;
838     result.time = event->time;
839     TRACE("Sending SelectionNotify event...\n");
840     XSendEvent(g_display,event->requestor,False,NoEventMask,(XEvent*)&result);
841   }
842 }
843
844
845 /***********************************************************************
846  *           EVENT_SelectionClear
847  *   We receive this event when another client grabs the X selection.
848  *   If we lost both PRIMARY and CLIPBOARD we must terminate.
849  */
850 void EVENT_SelectionClear( XSelectionClearEvent *event )
851 {
852   Atom xaClipboard = XInternAtom(g_display, _CLIPBOARD, False);
853     
854   TRACE("()\n");
855
856   if (event->selection == XA_PRIMARY)
857   {
858       g_selectionAcquired &= ~S_PRIMARY;     /* Clear the PRIMARY flag */
859       TRACE("Lost PRIMARY selection...\n");
860   }
861   else if (event->selection == xaClipboard)
862   {
863       g_selectionAcquired &= ~S_CLIPBOARD;   /* Clear the CLIPBOARD flag */
864       TRACE("Lost CLIPBOARD selection...\n");
865   }
866   
867   /* Once we lose all our selections we have nothing more to do */
868   if (g_selectionAcquired == S_NOSELECTION)
869       TerminateServer(1);
870 }
871
872 /***********************************************************************
873  *           EVENT_PropertyNotify
874  *   We use this to release resources like Pixmaps when a selection
875  *   client no longer needs them.
876  */
877 void EVENT_PropertyNotify( XPropertyEvent *event )
878 {
879   TRACE("()\n");
880
881   /* Check if we have any resources to free */
882
883   switch(event->state)
884   {
885     case PropertyDelete:
886     {
887       TRACE("\tPropertyDelete for atom %s on window %ld\n",
888                     XGetAtomName(event->display, event->atom), (long)event->window);
889       
890       /* FreeResources( event->atom ); */
891       break;
892     }
893
894     case PropertyNewValue:
895     {
896       TRACE("\tPropertyNewValue for atom %s on window %ld\n\n",
897                     XGetAtomName(event->display, event->atom), (long)event->window);
898       break;
899     }
900     
901     default:
902       break;
903   }
904 }
905
906 /***********************************************************************
907  *           DuplicatePixmap
908  */
909 Pixmap DuplicatePixmap(Pixmap pixmap)
910 {
911     Pixmap newPixmap;
912     XImage *xi;
913     Window root;
914     int x,y;               /* Unused */
915     unsigned border_width; /* Unused */
916     unsigned int depth, width, height;
917
918     TRACE("\t() Pixmap=%ul\n", pixmap);
919           
920     /* Get the Pixmap dimensions and bit depth */
921     if ( 0 == XGetGeometry(g_display, pixmap, &root, &x, &y, &width, &height,
922                              &border_width, &depth) )
923         return 0;
924
925     TRACE("\tPixmap properties: width=%d, height=%d, depth=%d\n",
926           width, height, depth);
927     
928     newPixmap = XCreatePixmap(g_display, g_win, width, height, depth);
929         
930     xi = XGetImage(g_display, pixmap, 0, 0, width, height, AllPlanes, XYPixmap);
931
932     XPutImage(g_display, newPixmap, g_gc, xi, 0, 0, 0, 0, width, height);
933
934     XDestroyImage(xi);
935     
936     TRACE("\t() New Pixmap=%ul\n", newPixmap);
937     return newPixmap;
938 }
939
940 /***********************************************************************
941  *           getGC
942  * Get a GC to use for drawing
943  */
944 void getGC(Window win, GC *gc)
945 {
946         unsigned long valuemask = 0; /* ignore XGCvalues and use defaults */
947         XGCValues values;
948         unsigned int line_width = 6;
949         int line_style = LineOnOffDash;
950         int cap_style = CapRound;
951         int join_style = JoinRound;
952         int dash_offset = 0;
953         static char dash_list[] = {12, 24};
954         int list_length = 2;
955
956         /* Create default Graphics Context */
957         *gc = XCreateGC(g_display, win, valuemask, &values);
958
959         /* specify black foreground since default window background is 
960          * white and default foreground is undefined. */
961         XSetForeground(g_display, *gc, BlackPixel(g_display,screen_num));
962
963         /* set line attributes */
964         XSetLineAttributes(g_display, *gc, line_width, line_style, 
965                         cap_style, join_style);
966
967         /* set dashes */
968         XSetDashes(g_display, *gc, dash_offset, dash_list, list_length);
969 }
970
971
972 /***********************************************************************
973  *           TextOut
974  */
975 void TextOut(Window win, GC gc, char *pStr)
976 {
977         int y_offset, x_offset;
978
979         y_offset = 10;
980         x_offset = 2;
981
982         /* output text, centered on each line */
983         XDrawString(g_display, win, gc, x_offset, y_offset, pStr, 
984                         strlen(pStr));
985 }