/*
 * tkUnixXId.c --
 *
 *	This file provides a replacement function for the default X resource
 *	allocator (_XAllocID). The problem with the default allocator is that
 *	it never re-uses ids, which causes long-lived applications to crash
 *	when X resource identifiers wrap around. The replacement functions in
 *	this file re-use old identifiers to prevent this problem.
 *
 *	The code in this file is based on similar implementations by
 *	George C. Kaplan and Michael Hoegeman.
 *
 * Copyright (c) 1993 The Regents of the University of California.
 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: tkUnixXId.c,v 1.10 2007/12/13 15:28:51 dgp Exp $
 */

/*
 * The definition below is needed on some systems so that we can access the
 * resource_alloc field of Display structures in order to replace the resource
 * allocator.
 */

#define XLIB_ILLEGAL_ACCESS 1

#include "tkUnixInt.h"

/*
 * A structure of the following type is used to hold one or more available
 * resource identifiers. There is a list of these structures for each display.
 */

#define IDS_PER_STACK 10
typedef struct TkIdStack {
    XID ids[IDS_PER_STACK];	/* Array of free identifiers. */
    int numUsed;		/* Indicates how many of the entries in ids
				 * are currently in use. */
    TkDisplay *dispPtr;		/* Display to which ids belong. */
    struct TkIdStack *nextPtr;	/* Next bunch of free identifiers for the same
				 * display. */
} TkIdStack;

/*
 * Forward declarations for functions defined in this file:
 */

static XID		AllocXId(Display *display);
static Tk_RestrictAction CheckRestrictProc(ClientData clientData,
			    XEvent *eventPtr);
static void		WindowIdCleanup(ClientData clientData);
static void		WindowIdCleanup2(ClientData clientData);

/*
 *----------------------------------------------------------------------
 *
 * TkInitXId --
 *
 *	This function is called to initialize the id allocator for a given
 *	display.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The official allocator for the display is set up to be AllocXId.
 *
 *----------------------------------------------------------------------
 */

void
TkInitXId(
    TkDisplay *dispPtr)		/* Tk's information about the display. */
{
    dispPtr->idStackPtr = NULL;
    dispPtr->defaultAllocProc = (XID (*) _ANSI_ARGS_((Display *display)))
	    dispPtr->display->resource_alloc;
    dispPtr->display->resource_alloc = AllocXId;
    dispPtr->windowStackPtr = NULL;
    dispPtr->idCleanupScheduled = (Tcl_TimerToken) 0;
}

/*
 *----------------------------------------------------------------------
 *
 * TkFreeXId --
 *
 *	This function is called to free resources for the id allocator for a
 *	given display.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Frees the id and window stack pools.
 *
 *----------------------------------------------------------------------
 */

void
TkFreeXId(
    TkDisplay *dispPtr)		/* Tk's information about the display. */
{
    TkIdStack *stackPtr, *freePtr;

    if (dispPtr->idCleanupScheduled) {
	Tcl_DeleteTimerHandler(dispPtr->idCleanupScheduled);
    }

    for (stackPtr = dispPtr->idStackPtr; stackPtr != NULL; ) {
	freePtr = stackPtr;
	stackPtr = stackPtr->nextPtr;
	ckfree((char *) freePtr);
    }
    dispPtr->idStackPtr = NULL;

    for (stackPtr = dispPtr->windowStackPtr; stackPtr != NULL; ) {
	freePtr = stackPtr;
	stackPtr = stackPtr->nextPtr;
	ckfree((char *) freePtr);
    }
    dispPtr->windowStackPtr = NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * AllocXId --
 *
 *	This function is invoked by Xlib as the resource allocator for a
 *	display.
 *
 * Results:
 *	The return value is an X resource identifier that isn't currently in
 *	use.
 *
 * Side effects:
 *	The identifier is removed from the stack of free identifiers, if it
 *	was previously on the stack.
 *
 *----------------------------------------------------------------------
 */

static XID
AllocXId(
    Display *display)		/* Display for which to allocate. */
{
    TkDisplay *dispPtr;
    TkIdStack *stackPtr;

    /*
     * Find Tk's information about the display.
     */

    dispPtr = TkGetDisplay(display);

    /*
     * If the topmost chunk on the stack is empty then free it. Then check for
     * a free id on the stack and return it if it exists.
     */

    stackPtr = dispPtr->idStackPtr;
    if (stackPtr != NULL) {
	while (stackPtr->numUsed == 0) {
	    dispPtr->idStackPtr = stackPtr->nextPtr;
	    ckfree((char *) stackPtr);
	    stackPtr = dispPtr->idStackPtr;
	    if (stackPtr == NULL) {
		goto defAlloc;
	    }
	}
	stackPtr->numUsed--;
	return stackPtr->ids[stackPtr->numUsed];
    }

    /*
     * No free ids in the stack: just get one from the default allocator.
     */

  defAlloc:
    return (*dispPtr->defaultAllocProc)(display);
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_FreeXId --
 *
 *	This function is called to indicate that an X resource identifier is
 *	now free.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The identifier is added to the stack of free identifiers for its
 *	display, so that it can be re-used.
 *
 *----------------------------------------------------------------------
 */

void
Tk_FreeXId(
    Display *display,		/* Display for which xid was allocated. */
    XID xid)			/* Identifier that is no longer in use. */
{
    TkDisplay *dispPtr;
    TkIdStack *stackPtr;

    /*
     * Find Tk's information about the display.
     */

    dispPtr = TkGetDisplay(display);

    /*
     * Add a new chunk to the stack if the current chunk is full.
     */

    stackPtr = dispPtr->idStackPtr;
    if ((stackPtr == NULL) || (stackPtr->numUsed >= IDS_PER_STACK)) {
	stackPtr = (TkIdStack *) ckalloc(sizeof(TkIdStack));
	stackPtr->numUsed = 0;
	stackPtr->dispPtr = dispPtr;
	stackPtr->nextPtr = dispPtr->idStackPtr;
	dispPtr->idStackPtr = stackPtr;
    }

    /*
     * Add the id to the current chunk.
     */

    stackPtr->ids[stackPtr->numUsed] = xid;
    stackPtr->numUsed++;
}

/*
 *----------------------------------------------------------------------
 *
 * TkFreeWindowId --
 *
 *	This function is invoked instead of TkFreeXId for window ids. See
 *	below for the reason why.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The id given by w will eventually be freed, so that it can be reused
 *	for other resources.
 *
 * Design:
 *	Freeing window ids is very tricky because there could still be events
 *	pending for a window in the event queue (or even in the server) at the
 *	time the window is destroyed. If the window id were to get reused
 *	immediately for another window, old events could "drop in" on the new
 *	window, causing unexpected behavior.
 *
 *	Thus we have to wait to re-use a window id until we know that there
 *	are no events left for it. Right now this is done in two steps. First,
 *	we wait until we know that the server has seen the XDestroyWindow
 *	request, so we can be sure that it won't generate more events for the
 *	window and that any existing events are in our queue. Second, we make
 *	sure that there are no events whatsoever in our queue (this is
 *	conservative but safe).
 *
 *	The first step is done by remembering the request id of the
 *	XDestroyWindow request and using LastKnownRequestProcessed to see what
 *	events the server has processed. If multiple windows get destroyed at
 *	about the same time, we just remember the most recent request number
 *	for any of them (again, conservative but safe).
 *
 *	There are a few other complications as well. When Tk destroys a
 *	sub-tree of windows, it only issues a single XDestroyWindow call, at
 *	the very end for the root of the subtree. We can't free any of the
 *	window ids until the final XDestroyWindow call. To make sure that this
 *	happens, we have to keep track of deletions in progress, hence the
 *	need for the "destroyCount" field of the display.
 *
 *	One final problem. Some servers, like Sun X11/News servers still seem
 *	to have problems with ids getting reused too quickly. I'm not
 *	completely sure why this is a problem, but delaying the recycling of
 *	ids appears to eliminate it. Therefore, we wait an additional few
 *	seconds, even after "the coast is clear" before reusing the ids.
 *
 *----------------------------------------------------------------------
 */

void
TkFreeWindowId(
    TkDisplay *dispPtr,		/* Display that w belongs to. */
    Window w)			/* X identifier for window on dispPtr. */
{
    TkIdStack *stackPtr;

    /*
     * Put the window id on a separate stack of window ids, rather than the
     * main stack, so it won't get reused right away. Add a new chunk to the
     * stack if the current chunk is full.
     */

    stackPtr = dispPtr->windowStackPtr;
    if ((stackPtr == NULL) || (stackPtr->numUsed >= IDS_PER_STACK)) {
	stackPtr = (TkIdStack *) ckalloc(sizeof(TkIdStack));
	stackPtr->numUsed = 0;
	stackPtr->dispPtr = dispPtr;
	stackPtr->nextPtr = dispPtr->windowStackPtr;
	dispPtr->windowStackPtr = stackPtr;
    }

    /*
     * Add the id to the current chunk.
     */

    stackPtr->ids[stackPtr->numUsed] = w;
    stackPtr->numUsed++;

    /*
     * Schedule a call to WindowIdCleanup if one isn't already scheduled.
     */

    if (!dispPtr->idCleanupScheduled) {
	dispPtr->idCleanupScheduled = Tcl_CreateTimerHandler(100,
		WindowIdCleanup, (ClientData) dispPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * WindowIdCleanup --
 *
 *	See if we can now free up all the accumulated ids of deleted windows.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If it's safe to move the window ids back to the main free list, we
 *	schedule this to happen after a few mores seconds of delay. If it's
 *	not safe to move them yet, a timer handler gets invoked to try again
 *	later.
 *
 *----------------------------------------------------------------------
 */

static void
WindowIdCleanup(
    ClientData clientData)	/* Pointer to TkDisplay for display */
{
    TkDisplay *dispPtr = (TkDisplay *) clientData;
    int anyEvents, delta;
    Tk_RestrictProc *oldProc;
    ClientData oldData;
    static Tcl_Time timeout = {0, 0};

    dispPtr->idCleanupScheduled = (Tcl_TimerToken) 0;

    /*
     * See if it's safe to recycle the window ids. It's safe if:
     * (a) no deletions are in progress.
     * (b) the server has seen all of the requests up to the last
     *     XDestroyWindow request.
     * (c) there are no events in the event queue; the only way to test for
     *     this right now is to create a restrict proc that will filter the
     *     events, then call Tcl_DoOneEvent to see if the function gets
     *     invoked.
     */

    if (dispPtr->destroyCount > 0) {
	goto tryAgain;
    }
    delta = LastKnownRequestProcessed(dispPtr->display)
	    - dispPtr->lastDestroyRequest;
    if (delta < 0) {
	XSync(dispPtr->display, False);
    }
    anyEvents = 0;
    oldProc = Tk_RestrictEvents(CheckRestrictProc, (ClientData) &anyEvents,
	    &oldData);
    TkUnixDoOneXEvent(&timeout);
    Tk_RestrictEvents(oldProc, oldData, &oldData);
    if (anyEvents) {
	goto tryAgain;
    }

    /*
     * These ids look safe to recycle, but we still need to delay a bit more
     * (see comments for TkFreeWindowId). Schedule the final freeing.
     */

    if (dispPtr->windowStackPtr != NULL) {
	Tcl_CreateTimerHandler(5000, WindowIdCleanup2,
		(ClientData) dispPtr->windowStackPtr);
	dispPtr->windowStackPtr = NULL;
    }
    return;

    /*
     * It's still not safe to free up the ids. Try again a bit later.
     */

  tryAgain:
    dispPtr->idCleanupScheduled = Tcl_CreateTimerHandler(500,
	    WindowIdCleanup, (ClientData) dispPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * WindowIdCleanup2 --
 *
 *	This function is the last one in the chain that recycles window ids.
 *	It takes all of the ids indicated by its argument and adds them back
 *	to the main id free list.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Window ids get added to the main free list for their display.
 *
 *----------------------------------------------------------------------
 */

static void
WindowIdCleanup2(
    ClientData clientData)	/* Pointer to TkIdStack list. */
{
    TkIdStack *stackPtr = (TkIdStack *) clientData;
    TkIdStack *lastPtr;

    lastPtr = stackPtr;
    while (lastPtr->nextPtr != NULL) {
	lastPtr = lastPtr->nextPtr;
    }
    lastPtr->nextPtr = stackPtr->dispPtr->idStackPtr;
    stackPtr->dispPtr->idStackPtr = stackPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * CheckRestrictProc --
 *
 *	This function is a restrict function, called by Tcl_DoOneEvent to
 *	filter X events. All it does is to set a flag to indicate that there
 *	are X events present.
 *
 * Results:
 *	Sets the integer pointed to by the argument, then returns
 *	TK_DEFER_EVENT.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static Tk_RestrictAction
CheckRestrictProc(
    ClientData clientData,	/* Pointer to flag to set. */
    XEvent *eventPtr)		/* Event to filter; not used. */
{
    int *flag = (int *) clientData;
    *flag = 1;
    return TK_DEFER_EVENT;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_GetPixmap --
 *
 *	Same as the XCreatePixmap function except that it manages resource
 *	identifiers better.
 *
 * Results:
 *	Returns a new pixmap.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

Pixmap
Tk_GetPixmap(
    Display *display,		/* Display for new pixmap. */
    Drawable d,			/* Drawable where pixmap will be used. */
    int width, int height,	/* Dimensions of pixmap. */
    int depth)			/* Bits per pixel for pixmap. */
{
    return XCreatePixmap(display, d, (unsigned) width, (unsigned) height,
	    (unsigned) depth);
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_FreePixmap --
 *
 *	Same as the XFreePixmap function except that it also marks the
 *	resource identifier as free.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The pixmap is freed in the X server and its resource identifier is
 *	saved for re-use.
 *
 *----------------------------------------------------------------------
 */

void
Tk_FreePixmap(
    Display *display,		/* Display for which pixmap was allocated. */
    Pixmap pixmap)		/* Identifier for pixmap. */
{
    XFreePixmap(display, pixmap);
    Tk_FreeXId(display, (XID) pixmap);
}

/*
 *----------------------------------------------------------------------
 *
 * TkpWindowWasRecentlyDeleted --
 *
 *	Checks whether the window was recently deleted. This is called by the
 *	generic error handler to detect asynchronous notification of errors
 *	due to operations by Tk on a window that was already deleted by the
 *	server.
 *
 * Results:
 *	1 if the window was deleted recently, 0 otherwise.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TkpWindowWasRecentlyDeleted(
    Window win,			/* The window to check for. */
    TkDisplay *dispPtr)		/* The window belongs to this display. */
{
    TkIdStack *stackPtr;
    int i;

    for (stackPtr = dispPtr->windowStackPtr; stackPtr != NULL;
	    stackPtr = stackPtr->nextPtr) {
	for (i = 0; i < stackPtr->numUsed; i++) {
	    if ((Window) stackPtr->ids[i] == win) {
		return 1;
	    }
	}
    }
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * TkpScanWindowId --
 *
 *	Given a string, produce the corresponding Window Id.
 *
 * Results:
 *	The return value is normally TCL_OK; in this case *idPtr will be set
 *	to the Window value equivalent to string. If string is improperly
 *	formed then TCL_ERROR is returned and an error message will be left in
 *	the interp's result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TkpScanWindowId(
    Tcl_Interp *interp,
    CONST char *string,
    Window *idPtr)
{
    int value;

    if (Tcl_GetInt(interp, string, &value) != TCL_OK) {
	return TCL_ERROR;
    }
    *idPtr = (Window) value;
    return TCL_OK;
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */
