| /* |
| * 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 <stdio.h> |
| #include <stdlib.h> |
| #include <X11/Xlib.h> |
| #include <X11/Xutil.h> |
| #include <X11/Xos.h> |
| #include <X11/Xatom.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; |
| } |
| |
| 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: |
| // 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)); |
| } |