/* * Wine Clipboard Server * * Copyright 1999 Noel Borthwick * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * USAGE: * wineclipsrv [selection_mask] [debugClass_mask] [clearAllSelections] * * The optional selection-mask argument is a bit mask of the selection * types to be acquired. Currently two selections are supported: * 1. PRIMARY (mask value 1) * 2. CLIPBOARD (mask value 2). * * debugClass_mask is a bit mask of all debugging classes for which messages * are to be output. The standard Wine debug class set FIXME(1), ERR(2), * WARN(4) and TRACE(8) are supported. * * If clearAllSelections == 1 *all* selections are lost whenever a SelectionClear * event is received. * * If no arguments are supplied the server aquires all selections. (mask value 3) * and defaults to output of only FIXME(1) and ERR(2) messages. The default for * clearAllSelections is 0. * * NOTES: * * The Wine Clipboard Server is a standalone XLib application whose * purpose is to manage the X selection when Wine exits. * The server itself is started automatically with the appropriate * selection masks, whenever Wine exits after acquiring the PRIMARY and/or * CLIPBOARD selection. (See X11DRV_CLIPBOARD_ResetOwner) * When the server starts, it first proceeds to capture the selection data from * Wine and then takes over the selection ownership. It does this by querying * the current selection owner(of the specified selections) for the TARGETS * selection target. It then proceeds to cache all the formats exposed by * TARGETS. If the selection does not support the TARGETS target, or if no * target formats are exposed, the server simply exits. * Once the cache has been filled, the server then actually acquires ownership * of the respective selection and begins fielding selection requests. * Selection requests are serviced from the cache. If a selection is lost the * server flushes its internal cache, destroying all data previously saved. * Once ALL selections have been lost the server terminates. * * TODO: */ #include "config.h" #include #include #include #include #include #include /* * Lightweight debug definitions for Wine Clipboard Server. * The standard FIXME, ERR, WARN & TRACE classes are supported * without debug channels. * The standard defines NO_TRACE_MSGS and NO_DEBUG_MSGS will compile out * TRACE, WARN and ERR and FIXME message displays. */ /* Internal definitions (do not use these directly) */ #ifdef __SUNPRO_C #define __FUNCTION__ __func__ #endif enum __DEBUG_CLASS { __DBCL_FIXME, __DBCL_ERR, __DBCL_WARN, __DBCL_TRACE, __DBCL_COUNT }; extern char __debug_msg_enabled[__DBCL_COUNT]; extern const char * const debug_cl_name[__DBCL_COUNT]; #define DEBUG_CLASS_COUNT __DBCL_COUNT #define __GET_DEBUGGING(dbcl) (__debug_msg_enabled[(dbcl)]) #define __SET_DEBUGGING(dbcl,on) (__debug_msg_enabled[(dbcl)] = (on)) #define __DPRINTF(dbcl) \ (!__GET_DEBUGGING(dbcl) || \ (printf("%s:%s:%s ", debug_cl_name[(dbcl)], progname, __FUNCTION__),0)) \ ? 0 : printf #define __DUMMY_DPRINTF 1 ? (void)0 : (void)((int (*)(char *, ...)) NULL) /* use configure to allow user to compile out debugging messages */ #ifndef NO_TRACE_MSGS #define TRACE __DPRINTF(__DBCL_TRACE) #else #define TRACE __DUMMY_DPRINTF #endif /* NO_TRACE_MSGS */ #ifndef NO_DEBUG_MSGS #define WARN __DPRINTF(__DBCL_WARN) #define FIXME __DPRINTF(__DBCL_FIXME) #else #define WARN __DUMMY_DPRINTF #define FIXME __DUMMY_DPRINTF #endif /* NO_DEBUG_MSGS */ /* define error macro regardless of what is configured */ #define ERR __DPRINTF(__DBCL_ERR) #define TRUE 1 #define FALSE 0 typedef int BOOL; /* Internal definitions for debugging messages(do not use these directly) */ const char * const debug_cl_name[] = { "fixme", "err", "warn", "trace" }; char __debug_msg_enabled[DEBUG_CLASS_COUNT] = {1, 1, 0, 0}; /* Selection masks */ #define S_NOSELECTION 0 #define S_PRIMARY 1 #define S_CLIPBOARD 2 /* Debugging class masks */ #define C_FIXME 1 #define C_ERR 2 #define C_WARN 4 #define C_TRACE 8 /* * Global variables */ static Display *g_display = NULL; static int screen_num; static char *progname; /* name this program was invoked by */ static Window g_win = 0; /* the hidden clipboard server window */ static GC g_gc = 0; static char *g_szOutOfMemory = "Insufficient memory!\n"; /* X selection context info */ static char _CLIPBOARD[] = "CLIPBOARD"; /* CLIPBOARD atom name */ static int g_selectionToAcquire = 0; /* Masks for the selection to be acquired */ static int g_selectionAcquired = 0; /* Contains the current selection masks */ static int g_clearAllSelections = 0; /* If TRUE *all* selections are lost on SelectionClear */ /* Selection cache */ typedef struct tag_CACHEENTRY { Atom target; Atom type; int nFormat; int nElements; void *pData; } CACHEENTRY, *PCACHEENTRY; static PCACHEENTRY g_pPrimaryCache = NULL; /* Primary selection cache */ static PCACHEENTRY g_pClipboardCache = NULL; /* Clipboard selection cache */ static unsigned long g_cPrimaryTargets = 0; /* Number of TARGETS reported by PRIMARY selection */ static unsigned long g_cClipboardTargets = 0; /* Number of TARGETS reported by CLIPBOARD selection */ /* Event names */ static const char * const event_names[] = { "", "", "KeyPress", "KeyRelease", "ButtonPress", "ButtonRelease", "MotionNotify", "EnterNotify", "LeaveNotify", "FocusIn", "FocusOut", "KeymapNotify", "Expose", "GraphicsExpose", "NoExpose", "VisibilityNotify", "CreateNotify", "DestroyNotify", "UnmapNotify", "MapNotify", "MapRequest", "ReparentNotify", "ConfigureNotify", "ConfigureRequest", "GravityNotify", "ResizeRequest", "CirculateNotify", "CirculateRequest", "PropertyNotify", "SelectionClear", "SelectionRequest", "SelectionNotify", "ColormapNotify", "ClientMessage", "MappingNotify" }; /* * Prototypes */ int RunAsDaemon( void ); BOOL Init(int argc, char **argv); void TerminateServer( int ret ); int AcquireSelection(); int CacheDataFormats( Atom SelectionSrc, PCACHEENTRY *ppCache ); void EmptyCache(PCACHEENTRY pCache, int nItems); BOOL FillCacheEntry( Atom SelectionSrc, Atom target, PCACHEENTRY pCacheEntry ); BOOL LookupCacheItem( Atom selection, Atom target, PCACHEENTRY *ppCacheEntry ); void EVENT_ProcessEvent( XEvent *event ); Atom EVENT_SelectionRequest_MULTIPLE( XSelectionRequestEvent *pevent ); void EVENT_SelectionRequest( XSelectionRequestEvent *event, BOOL bIsMultiple ); void EVENT_SelectionClear( XSelectionClearEvent *event ); void EVENT_PropertyNotify( XPropertyEvent *event ); Pixmap DuplicatePixmap(Pixmap pixmap); void TextOut(Window win, GC gc, char *pStr); void getGC(Window win, GC *gc); int main(int argc, char **argv) { XEvent event; if ( RunAsDaemon() == -1 ) { ERR("could not run as daemon\n"); exit(1); } if ( !Init(argc, argv) ) exit(0); /* Acquire the selection after retrieving all clipboard data * owned by the current selection owner. If we were unable to * Acquire any selection, terminate right away. */ if ( AcquireSelection() == S_NOSELECTION ) TerminateServer(0); TRACE("Clipboard server running...\n"); /* Start an X event loop */ while (1) { XNextEvent(g_display, &event); EVENT_ProcessEvent( &event ); } } /************************************************************************** * RunAsDaemon() */ int RunAsDaemon( void ) { int i; /* fork child process and let parent exit ; gets rid of original PID */ switch( fork() ) { case -1: ERR("fork failed\n"); return(-1); case 0: exit(0); break; } /* below is child process w/ new PID, set as session leader */ setsid(); /* close stdin,stdout,stderr and file descriptors (overkill method) */ for ( i = 0; i < 256 ; i++ ) close(i); TRACE("now running as daemon...\n"); return 0; } /************************************************************************** * Init() * Initialize the clipboard server */ BOOL Init(int argc, char **argv) { unsigned int width, height; /* window size */ unsigned int border_width = 4; /* four pixels */ unsigned int display_width, display_height; char *window_name = "Wine Clipboard Server"; XSizeHints *size_hints = NULL; XWMHints *wm_hints = NULL; XClassHint *class_hints = NULL; XTextProperty windowName; char *display_name = NULL; progname = argv[0]; if (!(size_hints = XAllocSizeHints())) { ERR(g_szOutOfMemory); return 0; } if (!(wm_hints = XAllocWMHints())) { ERR(g_szOutOfMemory); return 0; } if (!(class_hints = XAllocClassHint())) { ERR(g_szOutOfMemory); return 0; } /* connect to X server */ if ( (g_display=XOpenDisplay(display_name)) == NULL ) { ERR( "cannot connect to X server %s\n", XDisplayName(display_name)); return 0; } /* get screen size from display structure macro */ screen_num = DefaultScreen(g_display); display_width = DisplayWidth(g_display, screen_num); display_height = DisplayHeight(g_display, screen_num); /* size window with enough room for text */ width = display_width/3, height = display_height/4; /* create opaque window */ g_win = XCreateSimpleWindow(g_display, RootWindow(g_display,screen_num), 0, 0, width, height, border_width, BlackPixel(g_display, screen_num), WhitePixel(g_display,screen_num)); /* Set size hints for window manager. The window manager may * override these settings. */ /* x, y, width, and height hints are now taken from * the actual settings of the window when mapped. Note * that PPosition and PSize must be specified anyway. */ size_hints->flags = PPosition | PSize | PMinSize; size_hints->min_width = 300; size_hints->min_height = 200; /* These calls store window_name into XTextProperty structures * and sets the other fields properly. */ if (XStringListToTextProperty(&window_name, 1, &windowName) == 0) { ERR( "structure allocation for windowName failed.\n"); TerminateServer(-1); } wm_hints->initial_state = NormalState; wm_hints->input = True; wm_hints->flags = StateHint | InputHint; class_hints->res_name = progname; class_hints->res_class = "WineClipSrv"; XSetWMProperties(g_display, g_win, &windowName, NULL, argv, argc, size_hints, wm_hints, class_hints); /* Select event types wanted */ XSelectInput(g_display, g_win, ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask | PropertyChangeMask ); /* create GC for text and drawing */ getGC(g_win, &g_gc); /* Display window */ /* XMapWindow(g_display, g_win); */ /* Set the selections to be acquired from the command line argument. * If none specified, default to all selections we understand. */ if (argc > 1) g_selectionToAcquire = atoi(argv[1]); else g_selectionToAcquire = S_PRIMARY | S_CLIPBOARD; /* Set the debugging class state from the command line argument */ if (argc > 2) { int dbgClasses = atoi(argv[2]); __SET_DEBUGGING(__DBCL_FIXME, dbgClasses & C_FIXME); __SET_DEBUGGING(__DBCL_ERR, dbgClasses & C_ERR); __SET_DEBUGGING(__DBCL_WARN, dbgClasses & C_WARN); __SET_DEBUGGING(__DBCL_TRACE, dbgClasses & C_TRACE); } /* Set the "ClearSelections" state from the command line argument */ if (argc > 3) g_clearAllSelections = atoi(argv[3]); return TRUE; } /************************************************************************** * TerminateServer() */ void TerminateServer( int ret ) { TRACE("Terminating Wine clipboard server...\n"); /* Free Primary and Clipboard selection caches */ EmptyCache(g_pPrimaryCache, g_cPrimaryTargets); EmptyCache(g_pClipboardCache, g_cClipboardTargets); if (g_gc) XFreeGC(g_display, g_gc); if (g_display) XCloseDisplay(g_display); exit(ret); } /************************************************************************** * AcquireSelection() * * Acquire the selection after retrieving all clipboard data owned by * the current selection owner. */ int AcquireSelection() { Atom xaClipboard = XInternAtom(g_display, _CLIPBOARD, False); /* * For all selections we need to acquire, get a list of all targets * supplied by the current selection owner. */ if (g_selectionToAcquire & S_PRIMARY) { TRACE("Acquiring PRIMARY selection...\n"); g_cPrimaryTargets = CacheDataFormats( XA_PRIMARY, &g_pPrimaryCache ); TRACE("Cached %ld formats...\n", g_cPrimaryTargets); } if (g_selectionToAcquire & S_CLIPBOARD) { TRACE("Acquiring CLIPBOARD selection...\n"); g_cClipboardTargets = CacheDataFormats( xaClipboard, &g_pClipboardCache ); TRACE("Cached %ld formats...\n", g_cClipboardTargets); } /* * Now that we have cached the data, we proceed to acquire the selections */ if (g_cPrimaryTargets) { /* Acquire the PRIMARY selection */ while (XGetSelectionOwner(g_display,XA_PRIMARY) != g_win) XSetSelectionOwner(g_display, XA_PRIMARY, g_win, CurrentTime); g_selectionAcquired |= S_PRIMARY; } else TRACE("No PRIMARY targets - ownership not acquired.\n"); if (g_cClipboardTargets) { /* Acquire the CLIPBOARD selection */ while (XGetSelectionOwner(g_display,xaClipboard) != g_win) XSetSelectionOwner(g_display, xaClipboard, g_win, CurrentTime); g_selectionAcquired |= S_CLIPBOARD; } else TRACE("No CLIPBOARD targets - ownership not acquired.\n"); return g_selectionAcquired; } BOOL GetSelectionEvent(Atom SelectionSrc, XEvent *xe) { time_t end_time; /* Set up a 10 second time out */ end_time=time(NULL)+10; do { struct timeval nap; if (XCheckTypedWindowEvent(g_display, g_win, SelectionNotify, xe)) { if( xe->xselection.selection == SelectionSrc ) return TRUE; } if (time(NULL)>end_time) break; /* Sleep a bit to make this busy wait less brutal */ nap.tv_sec = 0; nap.tv_usec = 10; select(0, NULL, NULL, NULL, &nap); } while (TRUE); return FALSE; } /************************************************************************** * CacheDataFormats * * Allocates and caches the list of data formats available from the current selection. * This queries the selection owner for the TARGETS property and saves all * reported property types. */ int CacheDataFormats( Atom SelectionSrc, PCACHEENTRY *ppCache ) { XEvent xe; Atom aTargets; Atom atype=AnyPropertyType; int aformat; unsigned long remain; unsigned long cSelectionTargets = 0; Atom* targetList=NULL; Window ownerSelection = 0; if (!ppCache) return 0; *ppCache = NULL; /* Get the selection owner */ ownerSelection = XGetSelectionOwner(g_display, SelectionSrc); if ( ownerSelection == None ) return cSelectionTargets; /* * Query the selection owner for the TARGETS property */ aTargets = XInternAtom(g_display, "TARGETS", False); TRACE("Requesting TARGETS selection for '%s' (owner=%08x)...\n", XGetAtomName(g_display, SelectionSrc), (unsigned)ownerSelection ); XConvertSelection(g_display, SelectionSrc, aTargets, XInternAtom(g_display, "SELECTION_DATA", False), g_win, CurrentTime); /* * Wait until SelectionNotify is received */ if (!GetSelectionEvent(SelectionSrc, &xe)) return 0; /* Verify that the selection returned a valid TARGETS property */ if ( (xe.xselection.target != aTargets) || (xe.xselection.property == None) ) { TRACE("\tCould not retrieve TARGETS\n"); return cSelectionTargets; } /* Read the TARGETS property contents */ if(XGetWindowProperty(g_display, xe.xselection.requestor, xe.xselection.property, 0, 0x3FFF, True, AnyPropertyType/*XA_ATOM*/, &atype, &aformat, &cSelectionTargets, &remain, (unsigned char**)&targetList) != Success) TRACE("\tCouldn't read TARGETS property\n"); else { TRACE("\tType %s,Format %d,nItems %ld, Remain %ld\n", XGetAtomName(g_display,atype),aformat,cSelectionTargets, remain); /* * The TARGETS property should have returned us a list of atoms * corresponding to each selection target format supported. */ if( (atype == XA_ATOM || atype == aTargets) && aformat == 32 ) { int i; /* Allocate the selection cache */ *ppCache = (PCACHEENTRY)calloc(cSelectionTargets, sizeof(CACHEENTRY)); /* Cache these formats in the selection cache */ for (i = 0; i < cSelectionTargets; i++) { char *itemFmtName = XGetAtomName(g_display, targetList[i]); TRACE("\tAtom# %d: '%s'\n", i, itemFmtName); /* Populate the cache entry */ if (!FillCacheEntry( SelectionSrc, targetList[i], &((*ppCache)[i]))) ERR("Failed to fill cache entry!\n"); XFree(itemFmtName); } } /* Free the list of targets */ XFree(targetList); } return cSelectionTargets; } /*********************************************************************** * FillCacheEntry * * Populates the specified cache entry */ BOOL FillCacheEntry( Atom SelectionSrc, Atom target, PCACHEENTRY pCacheEntry ) { XEvent xe; Window w; Atom prop, reqType; Atom atype=AnyPropertyType; int aformat; unsigned long nitems,remain,itemSize; long lRequestLength; unsigned char* val=NULL; BOOL bRet = FALSE; TRACE("Requesting %s selection from %s...\n", XGetAtomName(g_display, target), XGetAtomName(g_display, SelectionSrc) ); /* Ask the selection owner to convert the selection to the target format */ XConvertSelection(g_display, SelectionSrc, target, XInternAtom(g_display, "SELECTION_DATA", False), g_win, CurrentTime); /* wait until SelectionNotify is received */ if (!GetSelectionEvent(SelectionSrc,&xe)) return bRet; /* Now proceed to retrieve the actual converted property from * the SELECTION_DATA atom */ w = xe.xselection.requestor; prop = xe.xselection.property; reqType = xe.xselection.target; if(prop == None) { TRACE("\tOwner failed to convert selection!\n"); return bRet; } TRACE("\tretrieving property %s from window %ld into %s\n", XGetAtomName(g_display,reqType), (long)w, XGetAtomName(g_display,prop) ); /* * First request a zero length in order to figure out the request size. */ if(XGetWindowProperty(g_display,w,prop,0,0,False, AnyPropertyType/*reqType*/, &atype, &aformat, &nitems, &itemSize, &val) != Success) { WARN("\tcouldn't get property size\n"); return bRet; } /* Free zero length return data if any */ if ( val ) { XFree(val); val = NULL; } TRACE("\tretrieving %ld bytes...\n", itemSize * aformat/8); lRequestLength = (itemSize * aformat/8)/4 + 1; /* * Retrieve the actual property in the required X format. */ if(XGetWindowProperty(g_display,w,prop,0,lRequestLength,False,AnyPropertyType/*reqType*/, &atype, &aformat, &nitems, &remain, &val) != Success) { WARN("\tcouldn't read property\n"); return bRet; } TRACE("\tType %s,Format %d,nitems %ld,remain %ld,value %s\n", atype ? XGetAtomName(g_display,atype) : NULL, aformat,nitems,remain,val); if (remain) { WARN("\tCouldn't read entire property- selection may be too large! Remain=%ld\n", remain); goto END; } /* * Populate the cache entry */ pCacheEntry->target = target; pCacheEntry->type = atype; pCacheEntry->nFormat = aformat; pCacheEntry->nElements = nitems; if (atype == XA_PIXMAP) { Pixmap *pPixmap = (Pixmap *)val; Pixmap newPixmap = DuplicatePixmap( *pPixmap ); pPixmap = (Pixmap*)calloc(1, sizeof(Pixmap)); *pPixmap = newPixmap; pCacheEntry->pData = pPixmap; } else pCacheEntry->pData = val; END: /* Delete the property on the window now that we are done * This will send a PropertyNotify event to the selection owner. */ XDeleteProperty(g_display,w,prop); return TRUE; } /*********************************************************************** * LookupCacheItem * * Lookup a target atom in the cache and get the matching cache entry */ BOOL LookupCacheItem( Atom selection, Atom target, PCACHEENTRY *ppCacheEntry ) { int i; int nCachetargets = 0; PCACHEENTRY pCache = NULL; Atom xaClipboard = XInternAtom(g_display, _CLIPBOARD, False); /* Locate the cache to be used based on the selection type */ if ( selection == XA_PRIMARY ) { pCache = g_pPrimaryCache; nCachetargets = g_cPrimaryTargets; } else if ( selection == xaClipboard ) { pCache = g_pClipboardCache; nCachetargets = g_cClipboardTargets; } if (!pCache || !ppCacheEntry) return FALSE; *ppCacheEntry = NULL; /* Look for the target item in the cache */ for (i = 0; i < nCachetargets; i++) { if (pCache[i].target == target) { *ppCacheEntry = &pCache[i]; return TRUE; } } return FALSE; } /*********************************************************************** * EmptyCache * * Empties the specified cache */ void EmptyCache(PCACHEENTRY pCache, int nItems) { int i; if (!pCache) return; /* Release all items in the cache */ for (i = 0; i < nItems; i++) { if (pCache[i].target && pCache[i].pData) { /* If we have a Pixmap, free it first */ if (pCache[i].target == XA_PIXMAP || pCache[i].target == XA_BITMAP) { Pixmap *pPixmap = (Pixmap *)pCache[i].pData; TRACE("Freeing %s (handle=%ld)...\n", XGetAtomName(g_display, pCache[i].target), *pPixmap); XFreePixmap(g_display, *pPixmap); /* Free the cached data item (allocated by us) */ free(pCache[i].pData); } else { TRACE("Freeing %s (%p)...\n", XGetAtomName(g_display, pCache[i].target), pCache[i].pData); /* Free the cached data item (allocated by X) */ XFree(pCache[i].pData); } } } /* Destroy the cache */ free(pCache); } /*********************************************************************** * EVENT_ProcessEvent * * Process an X event. */ void EVENT_ProcessEvent( XEvent *event ) { /* TRACE(" event %s for Window %08lx\n", event_names[event->type], event->xany.window ); */ switch (event->type) { case Expose: /* don't draw the window */ if (event->xexpose.count != 0) break; /* Output something */ TextOut(g_win, g_gc, "Click here to terminate"); break; case ConfigureNotify: break; case ButtonPress: /* fall into KeyPress (no break) */ case KeyPress: TerminateServer(1); break; case SelectionRequest: EVENT_SelectionRequest( (XSelectionRequestEvent *)event, FALSE ); break; case SelectionClear: EVENT_SelectionClear( (XSelectionClearEvent*)event ); break; case PropertyNotify: #if 0 EVENT_PropertyNotify( (XPropertyEvent *)event ); #endif break; default: /* ignore all other events */ break; } /* end switch */ } /*********************************************************************** * EVENT_SelectionRequest_MULTIPLE * Service a MULTIPLE selection request event * rprop contains a list of (target,property) atom pairs. * The first atom names a target and the second names a property. * The effect is as if we have received a sequence of SelectionRequest events * (one for each atom pair) except that: * 1. We reply with a SelectionNotify only when all the requested conversions * have been performed. * 2. If we fail to convert the target named by an atom in the MULTIPLE property, * we replace the atom in the property by None. */ Atom EVENT_SelectionRequest_MULTIPLE( XSelectionRequestEvent *pevent ) { Atom rprop; Atom atype=AnyPropertyType; int aformat; unsigned long remain; Atom* targetPropList=NULL; unsigned long cTargetPropList = 0; /* Atom xAtomPair = XInternAtom(g_display, "ATOM_PAIR", False); */ /* If the specified property is None the requestor is an obsolete client. * We support these by using the specified target atom as the reply property. */ rprop = pevent->property; if( rprop == None ) rprop = pevent->target; if (!rprop) goto END; /* Read the MULTIPLE property contents. This should contain a list of * (target,property) atom pairs. */ if(XGetWindowProperty(g_display, pevent->requestor, rprop, 0, 0x3FFF, False, AnyPropertyType, &atype, &aformat, &cTargetPropList, &remain, (unsigned char**)&targetPropList) != Success) TRACE("\tCouldn't read MULTIPLE property\n"); else { TRACE("\tType %s,Format %d,nItems %ld, Remain %ld\n", XGetAtomName(g_display,atype),aformat,cTargetPropList,remain); /* * Make sure we got what we expect. * NOTE: According to the X-ICCCM Version 2.0 documentation the property sent * in a MULTIPLE selection request should be of type ATOM_PAIR. * However some X apps(such as XPaint) are not compliant with this and return * a user defined atom in atype when XGetWindowProperty is called. * The data *is* an atom pair but is not denoted as such. */ if(aformat == 32 /* atype == xAtomPair */ ) { int i; /* Iterate through the ATOM_PAIR list and execute a SelectionRequest * for each (target,property) pair */ for (i = 0; i < cTargetPropList; i+=2) { char *targetName = XGetAtomName(g_display, targetPropList[i]); char *propName = XGetAtomName(g_display, targetPropList[i+1]); XSelectionRequestEvent event; TRACE("MULTIPLE(%d): Target='%s' Prop='%s'\n", i/2, targetName, propName); XFree(targetName); XFree(propName); /* We must have a non "None" property to service a MULTIPLE target atom */ if ( !targetPropList[i+1] ) { TRACE("\tMULTIPLE(%d): Skipping target with empty property!\n", i); continue; } /* Set up an XSelectionRequestEvent for this (target,property) pair */ memcpy( &event, pevent, sizeof(XSelectionRequestEvent) ); event.target = targetPropList[i]; event.property = targetPropList[i+1]; /* Fire a SelectionRequest, informing the handler that we are processing * a MULTIPLE selection request event. */ EVENT_SelectionRequest( &event, TRUE ); } } /* Free the list of targets/properties */ XFree(targetPropList); } END: return rprop; } /*********************************************************************** * EVENT_SelectionRequest * Process an event selection request event. * The bIsMultiple flag is used to signal when EVENT_SelectionRequest is called * recursively while servicing a "MULTIPLE" selection target. * */ void EVENT_SelectionRequest( XSelectionRequestEvent *event, BOOL bIsMultiple ) { XSelectionEvent result; Atom rprop = None; Window request = event->requestor; Atom xaMultiple = XInternAtom(g_display, "MULTIPLE", False); PCACHEENTRY pCacheEntry = NULL; void *pData = NULL; Pixmap pixmap; /* If the specified property is None the requestor is an obsolete client. * We support these by using the specified target atom as the reply property. */ rprop = event->property; if( rprop == None ) rprop = event->target; TRACE("Request for %s in selection %s\n", XGetAtomName(g_display, event->target), XGetAtomName(g_display, event->selection)); /* Handle MULTIPLE requests - rprop contains a list of (target, property) atom pairs */ if(event->target == xaMultiple) { /* MULTIPLE selection request - will call us back recursively */ rprop = EVENT_SelectionRequest_MULTIPLE( event ); goto END; } /* Lookup the requested target property in the cache */ if ( !LookupCacheItem(event->selection, event->target, &pCacheEntry) ) { TRACE("Item not available in cache!\n"); goto END; } /* Update the X property */ TRACE("\tUpdating property %s...\n", XGetAtomName(g_display, rprop)); /* If we have a request for a pixmap, return a duplicate */ if(event->target == XA_PIXMAP || event->target == XA_BITMAP) { Pixmap *pPixmap = (Pixmap *)pCacheEntry->pData; pixmap = DuplicatePixmap( *pPixmap ); pData = &pixmap; } else pData = pCacheEntry->pData; XChangeProperty(g_display, request, rprop, pCacheEntry->type, pCacheEntry->nFormat, PropModeReplace, (unsigned char *)pData, pCacheEntry->nElements); END: if( rprop == None) TRACE("\tRequest ignored\n"); /* reply to sender * SelectionNotify should be sent only at the end of a MULTIPLE request */ if ( !bIsMultiple ) { result.type = SelectionNotify; result.display = g_display; result.requestor = request; result.selection = event->selection; result.property = rprop; result.target = event->target; result.time = event->time; TRACE("Sending SelectionNotify event...\n"); XSendEvent(g_display,event->requestor,False,NoEventMask,(XEvent*)&result); } } /*********************************************************************** * EVENT_SelectionClear * We receive this event when another client grabs the X selection. * If we lost both PRIMARY and CLIPBOARD we must terminate. */ void EVENT_SelectionClear( XSelectionClearEvent *event ) { Atom xaClipboard = XInternAtom(g_display, _CLIPBOARD, False); TRACE("()\n"); /* If we're losing the CLIPBOARD selection, or if the preferences in .winerc * dictate that *all* selections should be cleared on loss of a selection, * we must give up all the selections we own. */ if ( g_clearAllSelections || (event->selection == xaClipboard) ) { TRACE("Lost CLIPBOARD (+PRIMARY) selection\n"); /* We really lost CLIPBOARD but want to voluntarily lose PRIMARY */ if ( (event->selection == xaClipboard) && (g_selectionAcquired & S_PRIMARY) ) { XSetSelectionOwner(g_display, XA_PRIMARY, None, CurrentTime); } /* We really lost PRIMARY but want to voluntarily lose CLIPBOARD */ if ( (event->selection == XA_PRIMARY) && (g_selectionAcquired & S_CLIPBOARD) ) { XSetSelectionOwner(g_display, xaClipboard, None, CurrentTime); } g_selectionAcquired = S_NOSELECTION; /* Clear the selection masks */ } else if (event->selection == XA_PRIMARY) { TRACE("Lost PRIMARY selection...\n"); g_selectionAcquired &= ~S_PRIMARY; /* Clear the PRIMARY flag */ } /* Once we lose all our selections we have nothing more to do */ if (g_selectionAcquired == S_NOSELECTION) TerminateServer(1); } /*********************************************************************** * EVENT_PropertyNotify * We use this to release resources like Pixmaps when a selection * client no longer needs them. */ void EVENT_PropertyNotify( XPropertyEvent *event ) { TRACE("()\n"); /* Check if we have any resources to free */ switch(event->state) { case PropertyDelete: { TRACE("\tPropertyDelete for atom %s on window %ld\n", XGetAtomName(event->display, event->atom), (long)event->window); /* FreeResources( event->atom ); */ break; } case PropertyNewValue: { TRACE("\tPropertyNewValue for atom %s on window %ld\n\n", XGetAtomName(event->display, event->atom), (long)event->window); break; } default: break; } } /*********************************************************************** * DuplicatePixmap */ Pixmap DuplicatePixmap(Pixmap pixmap) { Pixmap newPixmap; XImage *xi; Window root; int x,y; /* Unused */ unsigned border_width; /* Unused */ unsigned int depth, width, height; TRACE("\t() Pixmap=%ld\n", (long)pixmap); /* Get the Pixmap dimensions and bit depth */ if ( 0 == XGetGeometry(g_display, pixmap, &root, &x, &y, &width, &height, &border_width, &depth) ) return 0; TRACE("\tPixmap properties: width=%d, height=%d, depth=%d\n", width, height, depth); newPixmap = XCreatePixmap(g_display, g_win, width, height, depth); xi = XGetImage(g_display, pixmap, 0, 0, width, height, AllPlanes, XYPixmap); XPutImage(g_display, newPixmap, g_gc, xi, 0, 0, 0, 0, width, height); XDestroyImage(xi); TRACE("\t() New Pixmap=%ld\n", (long)newPixmap); return newPixmap; } /*********************************************************************** * getGC * Get a GC to use for drawing */ void getGC(Window win, GC *gc) { unsigned long valuemask = 0; /* ignore XGCvalues and use defaults */ XGCValues values; unsigned int line_width = 6; int line_style = LineOnOffDash; int cap_style = CapRound; int join_style = JoinRound; int dash_offset = 0; static char dash_list[] = {12, 24}; int list_length = 2; /* Create default Graphics Context */ *gc = XCreateGC(g_display, win, valuemask, &values); /* specify black foreground since default window background is * white and default foreground is undefined. */ XSetForeground(g_display, *gc, BlackPixel(g_display,screen_num)); /* set line attributes */ XSetLineAttributes(g_display, *gc, line_width, line_style, cap_style, join_style); /* set dashes */ XSetDashes(g_display, *gc, dash_offset, dash_list, list_length); } /*********************************************************************** * TextOut */ void TextOut(Window win, GC gc, char *pStr) { int y_offset, x_offset; y_offset = 10; x_offset = 2; /* output text, centered on each line */ XDrawString(g_display, win, gc, x_offset, y_offset, pStr, strlen(pStr)); }