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