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