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