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