blob: 5e0b938bef8f8b0456726079b75b91ead5b52998 [file] [log] [blame]
/*
* Wine Clipboard Server
*
* Copyright 1999 Noel Borthwick
*
* 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 <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>
#include "config.h"
/*
* 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) */
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
*/
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 ( !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 );
}
}
/**************************************************************************
* 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;
}
/**************************************************************************
* 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
*/
while( TRUE )
{
if( XCheckTypedWindowEvent(g_display, g_win, SelectionNotify, &xe) )
if( xe.xselection.selection == SelectionSrc )
break;
}
/* 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 */
while( TRUE )
{
if( XCheckTypedWindowEvent(g_display, g_win, SelectionNotify, &xe) )
if( xe.xselection.selection == SelectionSrc )
break;
}
/* 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:
// EVENT_PropertyNotify( (XPropertyEvent *)event );
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));
}