|  | /* | 
|  | *  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) */ | 
|  |  | 
|  | #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)); | 
|  | } |