/*1* tkUnixXId.c --2*3* This file provides a replacement function for the default X4* resource allocator (_XAllocID). The problem with the default5* allocator is that it never re-uses ids, which causes long-lived6* applications to crash when X resource identifiers wrap around.7* The replacement functions in this file re-use old identifiers8* to prevent this problem.9*10* The code in this file is based on similar implementations by11* George C. Kaplan and Michael Hoegeman.12*13* Copyright (c) 1993 The Regents of the University of California.14* Copyright (c) 1994-1995 Sun Microsystems, Inc.15*16* See the file "license.terms" for information on usage and redistribution17* of this file, and for a DISCLAIMER OF ALL WARRANTIES.18*19* SCCS: @(#) tkUnixXId.c 1.17 96/07/23 16:56:3920*/2122/*23* The definition below is needed on some systems so that we can access24* the resource_alloc field of Display structures in order to replace25* the resource allocator.26*/2728#define XLIB_ILLEGAL_ACCESS 12930#include "tkInt.h"3132/*33* A structure of the following type is used to hold one or more34* available resource identifiers. There is a list of these structures35* for each display.36*/3738#define IDS_PER_STACK 1039typedef struct TkIdStack {40XID ids[IDS_PER_STACK]; /* Array of free identifiers. */41int numUsed; /* Indicates how many of the entries42* in ids are currently in use. */43TkDisplay *dispPtr; /* Display to which ids belong. */44struct TkIdStack *nextPtr; /* Next bunch of free identifiers45* for the same display. */46} TkIdStack;4748/*49* Forward declarations for procedures defined in this file:50*/5152static XID AllocXId _ANSI_ARGS_((Display *display));53static Tk_RestrictAction CheckRestrictProc _ANSI_ARGS_((54ClientData clientData, XEvent *eventPtr));55static void WindowIdCleanup _ANSI_ARGS_((ClientData clientData));56static void WindowIdCleanup2 _ANSI_ARGS_((ClientData clientData));5758/*59*----------------------------------------------------------------------60*61* TkInitXId --62*63* This procedure is called to initialize the id allocator for64* a given display.65*66* Results:67* None.68*69* Side effects:70* The official allocator for the display is set up to be Tk_AllocXID.71*72*----------------------------------------------------------------------73*/7475void76TkInitXId(dispPtr)77TkDisplay *dispPtr; /* Tk's information about the78* display. */79{80dispPtr->idStackPtr = NULL;81dispPtr->defaultAllocProc = dispPtr->display->resource_alloc;82dispPtr->display->resource_alloc = AllocXId;83dispPtr->windowStackPtr = NULL;84dispPtr->idCleanupScheduled = 0;85}8687/*88*----------------------------------------------------------------------89*90* AllocXId --91*92* This procedure is invoked by Xlib as the resource allocator93* for a display.94*95* Results:96* The return value is an X resource identifier that isn't currently97* in use.98*99* Side effects:100* The identifier is removed from the stack of free identifiers,101* if it was previously on the stack.102*103*----------------------------------------------------------------------104*/105106static XID107AllocXId(display)108Display *display; /* Display for which to allocate. */109{110TkDisplay *dispPtr;111TkIdStack *stackPtr;112113/*114* Find Tk's information about the display.115*/116117dispPtr = TkGetDisplay(display);118119/*120* If the topmost chunk on the stack is empty then free it. Then121* check for a free id on the stack and return it if it exists.122*/123124stackPtr = dispPtr->idStackPtr;125if (stackPtr != NULL) {126while (stackPtr->numUsed == 0) {127dispPtr->idStackPtr = stackPtr->nextPtr;128ckfree((char *) stackPtr);129stackPtr = dispPtr->idStackPtr;130if (stackPtr == NULL) {131goto defAlloc;132}133}134stackPtr->numUsed--;135return stackPtr->ids[stackPtr->numUsed];136}137138/*139* No free ids in the stack: just get one from the default140* allocator.141*/142143defAlloc:144return (*dispPtr->defaultAllocProc)(display);145}146147/*148*----------------------------------------------------------------------149*150* Tk_FreeXId --151*152* This procedure is called to indicate that an X resource identifier153* is now free.154*155* Results:156* None.157*158* Side effects:159* The identifier is added to the stack of free identifiers for its160* display, so that it can be re-used.161*162*----------------------------------------------------------------------163*/164165void166Tk_FreeXId(display, xid)167Display *display; /* Display for which xid was168* allocated. */169XID xid; /* Identifier that is no longer170* in use. */171{172TkDisplay *dispPtr;173TkIdStack *stackPtr;174175/*176* Find Tk's information about the display.177*/178179dispPtr = TkGetDisplay(display);180181/*182* Add a new chunk to the stack if the current chunk is full.183*/184185stackPtr = dispPtr->idStackPtr;186if ((stackPtr == NULL) || (stackPtr->numUsed >= IDS_PER_STACK)) {187stackPtr = (TkIdStack *) ckalloc(sizeof(TkIdStack));188stackPtr->numUsed = 0;189stackPtr->dispPtr = dispPtr;190stackPtr->nextPtr = dispPtr->idStackPtr;191dispPtr->idStackPtr = stackPtr;192}193194/*195* Add the id to the current chunk.196*/197198stackPtr->ids[stackPtr->numUsed] = xid;199stackPtr->numUsed++;200}201202/*203*----------------------------------------------------------------------204*205* TkFreeWindowId --206*207* This procedure is invoked instead of TkFreeXId for window ids.208* See below for the reason why.209*210* Results:211* None.212*213* Side effects:214* The id given by w will eventually be freed, so that it can be215* reused for other resources.216*217* Design:218* Freeing window ids is very tricky because there could still be219* events pending for a window in the event queue (or even in the220* server) at the time the window is destroyed. If the window221* id were to get reused immediately for another window, old222* events could "drop in" on the new window, causing unexpected223* behavior.224*225* Thus we have to wait to re-use a window id until we know that226* there are no events left for it. Right now this is done in227* two steps. First, we wait until we know that the server228* has seen the XDestroyWindow request, so we can be sure that229* it won't generate more events for the window and that any230* existing events are in our queue. Second, we make sure that231* there are no events whatsoever in our queue (this is conservative232* but safe).233*234* The first step is done by remembering the request id of the235* XDestroyWindow request and using LastKnownRequestProcessed to236* see what events the server has processed. If multiple windows237* get destroyed at about the same time, we just remember the238* most recent request number for any of them (again, conservative239* but safe).240*241* There are a few other complications as well. When Tk destroys a242* sub-tree of windows, it only issues a single XDestroyWindow call,243* at the very end for the root of the subtree. We can't free any of244* the window ids until the final XDestroyWindow call. To make sure245* that this happens, we have to keep track of deletions in progress,246* hence the need for the "destroyCount" field of the display.247*248* One final problem. Some servers, like Sun X11/News servers still249* seem to have problems with ids getting reused too quickly. I'm250* not completely sure why this is a problem, but delaying the251* recycling of ids appears to eliminate it. Therefore, we wait252* an additional few seconds, even after "the coast is clear"253* before reusing the ids.254*255*----------------------------------------------------------------------256*/257258void259TkFreeWindowId(dispPtr, w)260TkDisplay *dispPtr; /* Display that w belongs to. */261Window w; /* X identifier for window on dispPtr. */262{263TkIdStack *stackPtr;264265/*266* Put the window id on a separate stack of window ids, rather267* than the main stack, so it won't get reused right away. Add268* a new chunk to the stack if the current chunk is full.269*/270271stackPtr = dispPtr->windowStackPtr;272if ((stackPtr == NULL) || (stackPtr->numUsed >= IDS_PER_STACK)) {273stackPtr = (TkIdStack *) ckalloc(sizeof(TkIdStack));274stackPtr->numUsed = 0;275stackPtr->dispPtr = dispPtr;276stackPtr->nextPtr = dispPtr->windowStackPtr;277dispPtr->windowStackPtr = stackPtr;278}279280/*281* Add the id to the current chunk.282*/283284stackPtr->ids[stackPtr->numUsed] = w;285stackPtr->numUsed++;286287/*288* Schedule a call to WindowIdCleanup if one isn't already289* scheduled.290*/291292if (!dispPtr->idCleanupScheduled) {293dispPtr->idCleanupScheduled = 1;294Tcl_CreateTimerHandler(100, WindowIdCleanup, (ClientData *) dispPtr);295}296}297298/*299*----------------------------------------------------------------------300*301* WindowIdCleanup --302*303* See if we can now free up all the accumulated ids of304* deleted windows.305*306* Results:307* None.308*309* Side effects:310* If it's safe to move the window ids back to the main free311* list, we schedule this to happen after a few mores seconds312* of delay. If it's not safe to move them yet, a timer handler313* gets invoked to try again later.314*315*----------------------------------------------------------------------316*/317318static void319WindowIdCleanup(clientData)320ClientData clientData; /* Pointer to TkDisplay for display */321{322TkDisplay *dispPtr = (TkDisplay *) clientData;323int anyEvents, delta;324Tk_RestrictProc *oldProc;325ClientData oldData;326327dispPtr->idCleanupScheduled = 0;328329/*330* See if it's safe to recycle the window ids. It's safe if:331* (a) no deletions are in progress.332* (b) the server has seen all of the requests up to the last333* XDestroyWindow request.334* (c) there are no events in the event queue; the only way to335* test for this right now is to create a restrict proc that336* will filter the events, then call Tcl_DoOneEvent to see if337* the procedure gets invoked.338*/339340if (dispPtr->destroyCount > 0) {341goto tryAgain;342}343delta = LastKnownRequestProcessed(dispPtr->display)344- dispPtr->lastDestroyRequest;345if (delta < 0) {346XSync(dispPtr->display, False);347}348anyEvents = 0;349oldProc = Tk_RestrictEvents(CheckRestrictProc, (ClientData) &anyEvents,350&oldData);351Tcl_DoOneEvent(TCL_DONT_WAIT|TCL_WINDOW_EVENTS);352Tk_RestrictEvents(oldProc, oldData, &oldData);353if (anyEvents) {354goto tryAgain;355}356357/*358* These ids look safe to recycle, but we still need to delay a bit359* more (see comments for TkFreeWindowId). Schedule the final freeing.360*/361362if (dispPtr->windowStackPtr != NULL) {363Tcl_CreateTimerHandler(5000, WindowIdCleanup2,364(ClientData) dispPtr->windowStackPtr);365dispPtr->windowStackPtr = NULL;366}367return;368369/*370* It's still not safe to free up the ids. Try again a bit later.371*/372373tryAgain:374dispPtr->idCleanupScheduled = 1;375Tcl_CreateTimerHandler(500, WindowIdCleanup, (ClientData *) dispPtr);376}377378/*379*----------------------------------------------------------------------380*381* WindowIdCleanup2 --382*383* This procedure is the last one in the chain that recycles384* window ids. It takes all of the ids indicated by its385* argument and adds them back to the main id free list.386*387* Results:388* None.389*390* Side effects:391* Window ids get added to the main free list for their display.392*393*----------------------------------------------------------------------394*/395396static void397WindowIdCleanup2(clientData)398ClientData clientData; /* Pointer to TkIdStack list. */399{400TkIdStack *stackPtr = (TkIdStack *) clientData;401TkIdStack *lastPtr;402403lastPtr = stackPtr;404while (lastPtr->nextPtr != NULL) {405lastPtr = lastPtr->nextPtr;406}407lastPtr->nextPtr = stackPtr->dispPtr->idStackPtr;408stackPtr->dispPtr->idStackPtr = stackPtr;409}410411/*412*----------------------------------------------------------------------413*414* CheckRestrictProc --415*416* This procedure is a restrict procedure, called by Tcl_DoOneEvent417* to filter X events. All it does is to set a flag to indicate418* that there are X events present.419*420* Results:421* Sets the integer pointed to by the argument, then returns422* TK_DEFER_EVENT.423*424* Side effects:425* None.426*427*----------------------------------------------------------------------428*/429430static Tk_RestrictAction431CheckRestrictProc(clientData, eventPtr)432ClientData clientData; /* Pointer to flag to set. */433XEvent *eventPtr; /* Event to filter; not used. */434{435int *flag = (int *) clientData;436*flag = 1;437return TK_DEFER_EVENT;438}439440/*441*----------------------------------------------------------------------442*443* Tk_GetPixmap --444*445* Same as the XCreatePixmap procedure except that it manages446* resource identifiers better.447*448* Results:449* Returns a new pixmap.450*451* Side effects:452* None.453*454*----------------------------------------------------------------------455*/456457Pixmap458Tk_GetPixmap(display, d, width, height, depth)459Display *display; /* Display for new pixmap. */460Drawable d; /* Drawable where pixmap will be used. */461int width, height; /* Dimensions of pixmap. */462int depth; /* Bits per pixel for pixmap. */463{464return XCreatePixmap(display, d, (unsigned) width, (unsigned) height,465(unsigned) depth);466}467468/*469*----------------------------------------------------------------------470*471* Tk_FreePixmap --472*473* Same as the XFreePixmap procedure except that it also marks474* the resource identifier as free.475*476* Results:477* None.478*479* Side effects:480* The pixmap is freed in the X server and its resource identifier481* is saved for re-use.482*483*----------------------------------------------------------------------484*/485486void487Tk_FreePixmap(display, pixmap)488Display *display; /* Display for which pixmap was allocated. */489Pixmap pixmap; /* Identifier for pixmap. */490{491XFreePixmap(display, pixmap);492Tk_FreeXId(display, (XID) pixmap);493}494495496