1/*
2 * tkUnixXId.c --
3 *
4 *	This file provides a replacement function for the default X
5 *	resource allocator (_XAllocID).  The problem with the default
6 *	allocator is that it never re-uses ids, which causes long-lived
7 *	applications to crash when X resource identifiers wrap around.
8 *	The replacement functions in this file re-use old identifiers
9 *	to prevent this problem.
10 *
11 *	The code in this file is based on similar implementations by
12 *	George C. Kaplan and Michael Hoegeman.
13 *
14 * Copyright (c) 1993 The Regents of the University of California.
15 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
16 *
17 * See the file "license.terms" for information on usage and redistribution
18 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
19 *
20 * RCS: @(#) $Id: tkUnixXId.c,v 1.7 2002/04/12 10:06:09 hobbs Exp $
21 */
22
23/*
24 * The definition below is needed on some systems so that we can access
25 * the resource_alloc field of Display structures in order to replace
26 * the resource allocator.
27 */
28
29#define XLIB_ILLEGAL_ACCESS 1
30
31#include "tkUnixInt.h"
32#include "tkPort.h"
33
34/*
35 * A structure of the following type is used to hold one or more
36 * available resource identifiers.  There is a list of these structures
37 * for each display.
38 */
39
40#define IDS_PER_STACK 10
41typedef struct TkIdStack {
42    XID ids[IDS_PER_STACK];		/* Array of free identifiers. */
43    int numUsed;			/* Indicates how many of the entries
44					 * in ids are currently in use. */
45    TkDisplay *dispPtr;			/* Display to which ids belong. */
46    struct TkIdStack *nextPtr;		/* Next bunch of free identifiers
47					 * for the same display. */
48} TkIdStack;
49
50/*
51 * Forward declarations for procedures defined in this file:
52 */
53
54static XID		AllocXId _ANSI_ARGS_((Display *display));
55static Tk_RestrictAction CheckRestrictProc _ANSI_ARGS_((
56			    ClientData clientData, XEvent *eventPtr));
57static void		WindowIdCleanup _ANSI_ARGS_((ClientData clientData));
58static void		WindowIdCleanup2 _ANSI_ARGS_((ClientData clientData));
59
60/*
61 *----------------------------------------------------------------------
62 *
63 * TkInitXId --
64 *
65 *	This procedure is called to initialize the id allocator for
66 *	a given display.
67 *
68 * Results:
69 *	None.
70 *
71 * Side effects:
72 *	The official allocator for the display is set up to be AllocXId.
73 *
74 *----------------------------------------------------------------------
75 */
76
77void
78TkInitXId(dispPtr)
79    TkDisplay *dispPtr;			/* Tk's information about the
80					 * display. */
81{
82    dispPtr->idStackPtr = NULL;
83    dispPtr->defaultAllocProc = (XID (*) _ANSI_ARGS_((Display *display)))
84            dispPtr->display->resource_alloc;
85    dispPtr->display->resource_alloc = AllocXId;
86    dispPtr->windowStackPtr = NULL;
87    dispPtr->idCleanupScheduled = (Tcl_TimerToken) 0;
88}
89
90/*
91 *----------------------------------------------------------------------
92 *
93 * TkFreeXId --
94 *
95 *	This procedure is called to free resources for the id allocator
96 *	for a given display.
97 *
98 * Results:
99 *	None.
100 *
101 * Side effects:
102 *	Frees the id and window stack pools.
103 *
104 *----------------------------------------------------------------------
105 */
106
107void
108TkFreeXId(dispPtr)
109    TkDisplay *dispPtr;			/* Tk's information about the
110					 * display. */
111{
112    TkIdStack *stackPtr, *freePtr;
113
114    if (dispPtr->idCleanupScheduled) {
115	Tcl_DeleteTimerHandler(dispPtr->idCleanupScheduled);
116    }
117
118    for (stackPtr = dispPtr->idStackPtr; stackPtr != NULL; ) {
119	freePtr = stackPtr;
120	stackPtr = stackPtr->nextPtr;
121	ckfree((char *) freePtr);
122    }
123    dispPtr->idStackPtr = NULL;
124
125    for (stackPtr = dispPtr->windowStackPtr; stackPtr != NULL; ) {
126	freePtr = stackPtr;
127	stackPtr = stackPtr->nextPtr;
128	ckfree((char *) freePtr);
129    }
130    dispPtr->windowStackPtr = NULL;
131}
132
133/*
134 *----------------------------------------------------------------------
135 *
136 * AllocXId --
137 *
138 *	This procedure is invoked by Xlib as the resource allocator
139 *	for a display.
140 *
141 * Results:
142 *	The return value is an X resource identifier that isn't currently
143 *	in use.
144 *
145 * Side effects:
146 *	The identifier is removed from the stack of free identifiers,
147 *	if it was previously on the stack.
148 *
149 *----------------------------------------------------------------------
150 */
151
152static XID
153AllocXId(display)
154    Display *display;			/* Display for which to allocate. */
155{
156    TkDisplay *dispPtr;
157    TkIdStack *stackPtr;
158
159    /*
160     * Find Tk's information about the display.
161     */
162
163    dispPtr = TkGetDisplay(display);
164
165    /*
166     * If the topmost chunk on the stack is empty then free it.  Then
167     * check for a free id on the stack and return it if it exists.
168     */
169
170    stackPtr = dispPtr->idStackPtr;
171    if (stackPtr != NULL) {
172	while (stackPtr->numUsed == 0) {
173	    dispPtr->idStackPtr = stackPtr->nextPtr;
174	    ckfree((char *) stackPtr);
175	    stackPtr = dispPtr->idStackPtr;
176	    if (stackPtr == NULL) {
177		goto defAlloc;
178	    }
179	}
180	stackPtr->numUsed--;
181	return stackPtr->ids[stackPtr->numUsed];
182    }
183
184    /*
185     * No free ids in the stack:  just get one from the default
186     * allocator.
187     */
188
189    defAlloc:
190    return (*dispPtr->defaultAllocProc)(display);
191}
192
193/*
194 *----------------------------------------------------------------------
195 *
196 * Tk_FreeXId --
197 *
198 *	This procedure is called to indicate that an X resource identifier
199 *	is now free.
200 *
201 * Results:
202 *	None.
203 *
204 * Side effects:
205 *	The identifier is added to the stack of free identifiers for its
206 *	display, so that it can be re-used.
207 *
208 *----------------------------------------------------------------------
209 */
210
211void
212Tk_FreeXId(display, xid)
213    Display *display;			/* Display for which xid was
214					 * allocated. */
215    XID xid;				/* Identifier that is no longer
216					 * in use. */
217{
218    TkDisplay *dispPtr;
219    TkIdStack *stackPtr;
220
221    /*
222     * Find Tk's information about the display.
223     */
224
225    dispPtr = TkGetDisplay(display);
226
227    /*
228     * Add a new chunk to the stack if the current chunk is full.
229     */
230
231    stackPtr = dispPtr->idStackPtr;
232    if ((stackPtr == NULL) || (stackPtr->numUsed >= IDS_PER_STACK)) {
233	stackPtr = (TkIdStack *) ckalloc(sizeof(TkIdStack));
234	stackPtr->numUsed = 0;
235	stackPtr->dispPtr = dispPtr;
236	stackPtr->nextPtr = dispPtr->idStackPtr;
237	dispPtr->idStackPtr = stackPtr;
238    }
239
240    /*
241     * Add the id to the current chunk.
242     */
243
244    stackPtr->ids[stackPtr->numUsed] = xid;
245    stackPtr->numUsed++;
246}
247
248/*
249 *----------------------------------------------------------------------
250 *
251 * TkFreeWindowId --
252 *
253 *	This procedure is invoked instead of TkFreeXId for window ids.
254 *	See below for the reason why.
255 *
256 * Results:
257 *	None.
258 *
259 * Side effects:
260 *	The id given by w will eventually be freed, so that it can be
261 *	reused for other resources.
262 *
263 * Design:
264 *	Freeing window ids is very tricky because there could still be
265 *	events pending for a window in the event queue (or even in the
266 *	server) at the time the window is destroyed.  If the window
267 *	id were to get reused immediately for another window, old
268 *	events could "drop in" on the new window, causing unexpected
269 *	behavior.
270 *
271 *	Thus we have to wait to re-use a window id until we know that
272 *	there are no events left for it.  Right now this is done in
273 *	two steps.  First, we wait until we know that the server
274 *	has seen the XDestroyWindow request, so we can be sure that
275 *	it won't generate more events for the window and that any
276 *	existing events are in our queue.  Second, we make sure that
277 *	there are no events whatsoever in our queue (this is conservative
278 *	but safe).
279 *
280 * 	The first step is done by remembering the request id of the
281 *	XDestroyWindow request and using LastKnownRequestProcessed to
282 *	see what events the server has processed.  If multiple windows
283 *	get destroyed at about the same time, we just remember the
284 *	most recent request number for any of them (again, conservative
285 *	but safe).
286 *
287 *	There are a few other complications as well.  When Tk destroys a
288 *	sub-tree of windows, it only issues a single XDestroyWindow call,
289 *	at the very end for the root of the subtree.  We can't free any of
290 *	the window ids until the final XDestroyWindow call.  To make sure
291 *	that this happens, we have to keep track of deletions in progress,
292 *	hence the need for the "destroyCount" field of the display.
293 *
294 *	One final problem.  Some servers, like Sun X11/News servers still
295 *	seem to have problems with ids getting reused too quickly.  I'm
296 *	not completely sure why this is a problem, but delaying the
297 *	recycling of ids appears to eliminate it.  Therefore, we wait
298 *	an additional few seconds, even after "the coast is clear"
299 *	before reusing the ids.
300 *
301 *----------------------------------------------------------------------
302 */
303
304void
305TkFreeWindowId(dispPtr, w)
306    TkDisplay *dispPtr;		/* Display that w belongs to. */
307    Window w;			/* X identifier for window on dispPtr. */
308{
309    TkIdStack *stackPtr;
310
311    /*
312     * Put the window id on a separate stack of window ids, rather
313     * than the main stack, so it won't get reused right away.  Add
314     * a new chunk to the stack if the current chunk is full.
315     */
316
317    stackPtr = dispPtr->windowStackPtr;
318    if ((stackPtr == NULL) || (stackPtr->numUsed >= IDS_PER_STACK)) {
319	stackPtr = (TkIdStack *) ckalloc(sizeof(TkIdStack));
320	stackPtr->numUsed = 0;
321	stackPtr->dispPtr = dispPtr;
322	stackPtr->nextPtr = dispPtr->windowStackPtr;
323	dispPtr->windowStackPtr = stackPtr;
324    }
325
326    /*
327     * Add the id to the current chunk.
328     */
329
330    stackPtr->ids[stackPtr->numUsed] = w;
331    stackPtr->numUsed++;
332
333    /*
334     * Schedule a call to WindowIdCleanup if one isn't already
335     * scheduled.
336     */
337
338    if (!dispPtr->idCleanupScheduled) {
339	dispPtr->idCleanupScheduled =
340	    Tcl_CreateTimerHandler(100, WindowIdCleanup, (ClientData) dispPtr);
341    }
342}
343
344/*
345 *----------------------------------------------------------------------
346 *
347 * WindowIdCleanup --
348 *
349 *	See if we can now free up all the accumulated ids of
350 *	deleted windows.
351 *
352 * Results:
353 *	None.
354 *
355 * Side effects:
356 *	If it's safe to move the window ids back to the main free
357 *	list, we schedule this to happen after a few mores seconds
358 *	of delay.  If it's not safe to move them yet, a timer handler
359 *	gets invoked to try again later.
360 *
361 *----------------------------------------------------------------------
362 */
363
364static void
365WindowIdCleanup(clientData)
366    ClientData clientData;	/* Pointer to TkDisplay for display */
367{
368    TkDisplay *dispPtr = (TkDisplay *) clientData;
369    int anyEvents, delta;
370    Tk_RestrictProc *oldProc;
371    ClientData oldData;
372    static Tcl_Time timeout = {0, 0};
373
374    dispPtr->idCleanupScheduled = (Tcl_TimerToken) 0;
375
376    /*
377     * See if it's safe to recycle the window ids.  It's safe if:
378     * (a) no deletions are in progress.
379     * (b) the server has seen all of the requests up to the last
380     *     XDestroyWindow request.
381     * (c) there are no events in the event queue; the only way to
382     *     test for this right now is to create a restrict proc that
383     *     will filter the events, then call Tcl_DoOneEvent to see if
384     *	   the procedure gets invoked.
385     */
386
387    if (dispPtr->destroyCount > 0) {
388	goto tryAgain;
389    }
390    delta = LastKnownRequestProcessed(dispPtr->display)
391	    - dispPtr->lastDestroyRequest;
392    if (delta < 0) {
393	XSync(dispPtr->display, False);
394    }
395    anyEvents = 0;
396    oldProc = Tk_RestrictEvents(CheckRestrictProc, (ClientData) &anyEvents,
397	    &oldData);
398    TkUnixDoOneXEvent(&timeout);
399    Tk_RestrictEvents(oldProc, oldData, &oldData);
400    if (anyEvents) {
401	goto tryAgain;
402    }
403
404    /*
405     * These ids look safe to recycle, but we still need to delay a bit
406     * more (see comments for TkFreeWindowId).  Schedule the final freeing.
407     */
408
409    if (dispPtr->windowStackPtr != NULL) {
410	Tcl_CreateTimerHandler(5000, WindowIdCleanup2,
411		(ClientData) dispPtr->windowStackPtr);
412	dispPtr->windowStackPtr = NULL;
413    }
414    return;
415
416    /*
417     * It's still not safe to free up the ids.  Try again a bit later.
418     */
419
420    tryAgain:
421    dispPtr->idCleanupScheduled =
422	Tcl_CreateTimerHandler(500, WindowIdCleanup, (ClientData) dispPtr);
423}
424
425/*
426 *----------------------------------------------------------------------
427 *
428 * WindowIdCleanup2 --
429 *
430 *	This procedure is the last one in the chain that recycles
431 *	window ids.  It takes all of the ids indicated by its
432 *	argument and adds them back to the main id free list.
433 *
434 * Results:
435 *	None.
436 *
437 * Side effects:
438 *	Window ids get added to the main free list for their display.
439 *
440 *----------------------------------------------------------------------
441 */
442
443static void
444WindowIdCleanup2(clientData)
445    ClientData clientData;	/* Pointer to TkIdStack list. */
446{
447    TkIdStack *stackPtr = (TkIdStack *) clientData;
448    TkIdStack *lastPtr;
449
450    lastPtr = stackPtr;
451    while (lastPtr->nextPtr != NULL) {
452	lastPtr = lastPtr->nextPtr;
453    }
454    lastPtr->nextPtr = stackPtr->dispPtr->idStackPtr;
455    stackPtr->dispPtr->idStackPtr = stackPtr;
456}
457
458/*
459 *----------------------------------------------------------------------
460 *
461 * CheckRestrictProc --
462 *
463 *	This procedure is a restrict procedure, called by Tcl_DoOneEvent
464 *	to filter X events.  All it does is to set a flag to indicate
465 *	that there are X events present.
466 *
467 * Results:
468 *	Sets the integer pointed to by the argument, then returns
469 *	TK_DEFER_EVENT.
470 *
471 * Side effects:
472 *	None.
473 *
474 *----------------------------------------------------------------------
475 */
476
477static Tk_RestrictAction
478CheckRestrictProc(clientData, eventPtr)
479    ClientData clientData;	/* Pointer to flag to set. */
480    XEvent *eventPtr;		/* Event to filter;  not used. */
481{
482    int *flag = (int *) clientData;
483    *flag = 1;
484    return TK_DEFER_EVENT;
485}
486
487/*
488 *----------------------------------------------------------------------
489 *
490 * Tk_GetPixmap --
491 *
492 *	Same as the XCreatePixmap procedure except that it manages
493 *	resource identifiers better.
494 *
495 * Results:
496 *	Returns a new pixmap.
497 *
498 * Side effects:
499 *	None.
500 *
501 *----------------------------------------------------------------------
502 */
503
504Pixmap
505Tk_GetPixmap(display, d, width, height, depth)
506    Display *display;		/* Display for new pixmap. */
507    Drawable d;			/* Drawable where pixmap will be used. */
508    int width, height;		/* Dimensions of pixmap. */
509    int depth;			/* Bits per pixel for pixmap. */
510{
511    return XCreatePixmap(display, d, (unsigned) width, (unsigned) height,
512	    (unsigned) depth);
513}
514
515/*
516 *----------------------------------------------------------------------
517 *
518 * Tk_FreePixmap --
519 *
520 *	Same as the XFreePixmap procedure except that it also marks
521 *	the resource identifier as free.
522 *
523 * Results:
524 *	None.
525 *
526 * Side effects:
527 *	The pixmap is freed in the X server and its resource identifier
528 *	is saved for re-use.
529 *
530 *----------------------------------------------------------------------
531 */
532
533void
534Tk_FreePixmap(display, pixmap)
535    Display *display;		/* Display for which pixmap was allocated. */
536    Pixmap pixmap;		/* Identifier for pixmap. */
537{
538    XFreePixmap(display, pixmap);
539    Tk_FreeXId(display, (XID) pixmap);
540}
541
542/*
543 *----------------------------------------------------------------------
544 *
545 * TkpWindowWasRecentlyDeleted --
546 *
547 *	Checks whether the window was recently deleted. This is called
548 *	by the generic error handler to detect asynchronous notification
549 *	of errors due to operations by Tk on a window that was already
550 *	deleted by the server.
551 *
552 * Results:
553 *	1 if the window was deleted recently, 0 otherwise.
554 *
555 * Side effects:
556 *	None.
557 *
558 *----------------------------------------------------------------------
559 */
560
561int
562TkpWindowWasRecentlyDeleted(win, dispPtr)
563    Window win;		/* The window to check for. */
564    TkDisplay *dispPtr;	/* The window belongs to this display. */
565{
566    TkIdStack *stackPtr;
567    int i;
568
569    for (stackPtr = dispPtr->windowStackPtr;
570         stackPtr != NULL;
571         stackPtr = stackPtr->nextPtr) {
572        for (i = 0; i < stackPtr->numUsed; i++) {
573            if ((Window) stackPtr->ids[i] == win) {
574                return 1;
575            }
576        }
577    }
578    return 0;
579}
580
581/*
582 *----------------------------------------------------------------------
583 *
584 * TkpScanWindowId --
585 *
586 *	Given a string, produce the corresponding Window Id.
587 *
588 * Results:
589 *      The return value is normally TCL_OK;  in this case *idPtr
590 *      will be set to the Window value equivalent to string.  If
591 *      string is improperly formed then TCL_ERROR is returned and
592 *      an error message will be left in the interp's result.
593 *
594 * Side effects:
595 *	None.
596 *
597 *----------------------------------------------------------------------
598 */
599
600int
601TkpScanWindowId(interp, string, idPtr)
602    Tcl_Interp *interp;
603    CONST char *string;
604    Window *idPtr;
605{
606    int value;
607    if (Tcl_GetInt(interp, string, &value) != TCL_OK) {
608	return TCL_ERROR;
609    }
610    *idPtr = (Window) value;
611    return TCL_OK;
612}
613
614