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-1995 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  * SCCS: @(#) tkUnixXId.c 1.17 96/07/23 16:56:39
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 "tkInt.h"
32 
33 /*
34  * A structure of the following type is used to hold one or more
35  * available resource identifiers.  There is a list of these structures
36  * for each display.
37  */
38 
39 #define IDS_PER_STACK 10
40 typedef struct TkIdStack {
41     XID ids[IDS_PER_STACK];		/* Array of free identifiers. */
42     int numUsed;			/* Indicates how many of the entries
43 					 * in ids are currently in use. */
44     TkDisplay *dispPtr;			/* Display to which ids belong. */
45     struct TkIdStack *nextPtr;		/* Next bunch of free identifiers
46 					 * for the same display. */
47 } TkIdStack;
48 
49 /*
50  * Forward declarations for procedures defined in this file:
51  */
52 
53 static XID		AllocXId _ANSI_ARGS_((Display *display));
54 static Tk_RestrictAction CheckRestrictProc _ANSI_ARGS_((
55 			    ClientData clientData, XEvent *eventPtr));
56 static void		WindowIdCleanup _ANSI_ARGS_((ClientData clientData));
57 static void		WindowIdCleanup2 _ANSI_ARGS_((ClientData clientData));
58 
59 /*
60  *----------------------------------------------------------------------
61  *
62  * TkInitXId --
63  *
64  *	This procedure is called to initialize the id allocator for
65  *	a given display.
66  *
67  * Results:
68  *	None.
69  *
70  * Side effects:
71  *	The official allocator for the display is set up to be Tk_AllocXID.
72  *
73  *----------------------------------------------------------------------
74  */
75 
76 void
TkInitXId(dispPtr)77 TkInitXId(dispPtr)
78     TkDisplay *dispPtr;			/* Tk's information about the
79 					 * display. */
80 {
81     dispPtr->idStackPtr = NULL;
82     dispPtr->defaultAllocProc = dispPtr->display->resource_alloc;
83     dispPtr->display->resource_alloc = AllocXId;
84     dispPtr->windowStackPtr = NULL;
85     dispPtr->idCleanupScheduled = 0;
86 }
87 
88 /*
89  *----------------------------------------------------------------------
90  *
91  * AllocXId --
92  *
93  *	This procedure is invoked by Xlib as the resource allocator
94  *	for a display.
95  *
96  * Results:
97  *	The return value is an X resource identifier that isn't currently
98  *	in use.
99  *
100  * Side effects:
101  *	The identifier is removed from the stack of free identifiers,
102  *	if it was previously on the stack.
103  *
104  *----------------------------------------------------------------------
105  */
106 
107 static XID
AllocXId(display)108 AllocXId(display)
109     Display *display;			/* Display for which to allocate. */
110 {
111     TkDisplay *dispPtr;
112     TkIdStack *stackPtr;
113 
114     /*
115      * Find Tk's information about the display.
116      */
117 
118     dispPtr = TkGetDisplay(display);
119 
120     /*
121      * If the topmost chunk on the stack is empty then free it.  Then
122      * check for a free id on the stack and return it if it exists.
123      */
124 
125     stackPtr = dispPtr->idStackPtr;
126     if (stackPtr != NULL) {
127 	while (stackPtr->numUsed == 0) {
128 	    dispPtr->idStackPtr = stackPtr->nextPtr;
129 	    ckfree((char *) stackPtr);
130 	    stackPtr = dispPtr->idStackPtr;
131 	    if (stackPtr == NULL) {
132 		goto defAlloc;
133 	    }
134 	}
135 	stackPtr->numUsed--;
136 	return stackPtr->ids[stackPtr->numUsed];
137     }
138 
139     /*
140      * No free ids in the stack:  just get one from the default
141      * allocator.
142      */
143 
144     defAlloc:
145     return (*dispPtr->defaultAllocProc)(display);
146 }
147 
148 /*
149  *----------------------------------------------------------------------
150  *
151  * Tk_FreeXId --
152  *
153  *	This procedure is called to indicate that an X resource identifier
154  *	is now free.
155  *
156  * Results:
157  *	None.
158  *
159  * Side effects:
160  *	The identifier is added to the stack of free identifiers for its
161  *	display, so that it can be re-used.
162  *
163  *----------------------------------------------------------------------
164  */
165 
166 void
Tk_FreeXId(display,xid)167 Tk_FreeXId(display, xid)
168     Display *display;			/* Display for which xid was
169 					 * allocated. */
170     XID xid;				/* Identifier that is no longer
171 					 * in use. */
172 {
173     TkDisplay *dispPtr;
174     TkIdStack *stackPtr;
175 
176     /*
177      * Find Tk's information about the display.
178      */
179 
180     dispPtr = TkGetDisplay(display);
181 
182     /*
183      * Add a new chunk to the stack if the current chunk is full.
184      */
185 
186     stackPtr = dispPtr->idStackPtr;
187     if ((stackPtr == NULL) || (stackPtr->numUsed >= IDS_PER_STACK)) {
188 	stackPtr = (TkIdStack *) ckalloc(sizeof(TkIdStack));
189 	stackPtr->numUsed = 0;
190 	stackPtr->dispPtr = dispPtr;
191 	stackPtr->nextPtr = dispPtr->idStackPtr;
192 	dispPtr->idStackPtr = stackPtr;
193     }
194 
195     /*
196      * Add the id to the current chunk.
197      */
198 
199     stackPtr->ids[stackPtr->numUsed] = xid;
200     stackPtr->numUsed++;
201 }
202 
203 /*
204  *----------------------------------------------------------------------
205  *
206  * TkFreeWindowId --
207  *
208  *	This procedure is invoked instead of TkFreeXId for window ids.
209  *	See below for the reason why.
210  *
211  * Results:
212  *	None.
213  *
214  * Side effects:
215  *	The id given by w will eventually be freed, so that it can be
216  *	reused for other resources.
217  *
218  * Design:
219  *	Freeing window ids is very tricky because there could still be
220  *	events pending for a window in the event queue (or even in the
221  *	server) at the time the window is destroyed.  If the window
222  *	id were to get reused immediately for another window, old
223  *	events could "drop in" on the new window, causing unexpected
224  *	behavior.
225  *
226  *	Thus we have to wait to re-use a window id until we know that
227  *	there are no events left for it.  Right now this is done in
228  *	two steps.  First, we wait until we know that the server
229  *	has seen the XDestroyWindow request, so we can be sure that
230  *	it won't generate more events for the window and that any
231  *	existing events are in our queue.  Second, we make sure that
232  *	there are no events whatsoever in our queue (this is conservative
233  *	but safe).
234  *
235  * 	The first step is done by remembering the request id of the
236  *	XDestroyWindow request and using LastKnownRequestProcessed to
237  *	see what events the server has processed.  If multiple windows
238  *	get destroyed at about the same time, we just remember the
239  *	most recent request number for any of them (again, conservative
240  *	but safe).
241  *
242  *	There are a few other complications as well.  When Tk destroys a
243  *	sub-tree of windows, it only issues a single XDestroyWindow call,
244  *	at the very end for the root of the subtree.  We can't free any of
245  *	the window ids until the final XDestroyWindow call.  To make sure
246  *	that this happens, we have to keep track of deletions in progress,
247  *	hence the need for the "destroyCount" field of the display.
248  *
249  *	One final problem.  Some servers, like Sun X11/News servers still
250  *	seem to have problems with ids getting reused too quickly.  I'm
251  *	not completely sure why this is a problem, but delaying the
252  *	recycling of ids appears to eliminate it.  Therefore, we wait
253  *	an additional few seconds, even after "the coast is clear"
254  *	before reusing the ids.
255  *
256  *----------------------------------------------------------------------
257  */
258 
259 void
TkFreeWindowId(dispPtr,w)260 TkFreeWindowId(dispPtr, w)
261     TkDisplay *dispPtr;		/* Display that w belongs to. */
262     Window w;			/* X identifier for window on dispPtr. */
263 {
264     TkIdStack *stackPtr;
265 
266     /*
267      * Put the window id on a separate stack of window ids, rather
268      * than the main stack, so it won't get reused right away.  Add
269      * a new chunk to the stack if the current chunk is full.
270      */
271 
272     stackPtr = dispPtr->windowStackPtr;
273     if ((stackPtr == NULL) || (stackPtr->numUsed >= IDS_PER_STACK)) {
274 	stackPtr = (TkIdStack *) ckalloc(sizeof(TkIdStack));
275 	stackPtr->numUsed = 0;
276 	stackPtr->dispPtr = dispPtr;
277 	stackPtr->nextPtr = dispPtr->windowStackPtr;
278 	dispPtr->windowStackPtr = stackPtr;
279     }
280 
281     /*
282      * Add the id to the current chunk.
283      */
284 
285     stackPtr->ids[stackPtr->numUsed] = w;
286     stackPtr->numUsed++;
287 
288     /*
289      * Schedule a call to WindowIdCleanup if one isn't already
290      * scheduled.
291      */
292 
293     if (!dispPtr->idCleanupScheduled) {
294 	dispPtr->idCleanupScheduled = 1;
295 	Tcl_CreateTimerHandler(100, WindowIdCleanup, (ClientData *) dispPtr);
296     }
297 }
298 
299 /*
300  *----------------------------------------------------------------------
301  *
302  * WindowIdCleanup --
303  *
304  *	See if we can now free up all the accumulated ids of
305  *	deleted windows.
306  *
307  * Results:
308  *	None.
309  *
310  * Side effects:
311  *	If it's safe to move the window ids back to the main free
312  *	list, we schedule this to happen after a few mores seconds
313  *	of delay.  If it's not safe to move them yet, a timer handler
314  *	gets invoked to try again later.
315  *
316  *----------------------------------------------------------------------
317  */
318 
319 static void
WindowIdCleanup(clientData)320 WindowIdCleanup(clientData)
321     ClientData clientData;	/* Pointer to TkDisplay for display */
322 {
323     TkDisplay *dispPtr = (TkDisplay *) clientData;
324     int anyEvents, delta;
325     Tk_RestrictProc *oldProc;
326     ClientData oldData;
327 
328     dispPtr->idCleanupScheduled = 0;
329 
330     /*
331      * See if it's safe to recycle the window ids.  It's safe if:
332      * (a) no deletions are in progress.
333      * (b) the server has seen all of the requests up to the last
334      *     XDestroyWindow request.
335      * (c) there are no events in the event queue; the only way to
336      *     test for this right now is to create a restrict proc that
337      *     will filter the events, then call Tcl_DoOneEvent to see if
338      *	   the procedure gets invoked.
339      */
340 
341     if (dispPtr->destroyCount > 0) {
342 	goto tryAgain;
343     }
344     delta = LastKnownRequestProcessed(dispPtr->display)
345 	    - dispPtr->lastDestroyRequest;
346     if (delta < 0) {
347 	XSync(dispPtr->display, False);
348     }
349     anyEvents = 0;
350     oldProc = Tk_RestrictEvents(CheckRestrictProc, (ClientData) &anyEvents,
351 	    &oldData);
352     Tcl_DoOneEvent(TCL_DONT_WAIT|TCL_WINDOW_EVENTS);
353     Tk_RestrictEvents(oldProc, oldData, &oldData);
354     if (anyEvents) {
355 	goto tryAgain;
356     }
357 
358     /*
359      * These ids look safe to recycle, but we still need to delay a bit
360      * more (see comments for TkFreeWindowId).  Schedule the final freeing.
361      */
362 
363     if (dispPtr->windowStackPtr != NULL) {
364 	Tcl_CreateTimerHandler(5000, WindowIdCleanup2,
365 		(ClientData) dispPtr->windowStackPtr);
366 	dispPtr->windowStackPtr = NULL;
367     }
368     return;
369 
370     /*
371      * It's still not safe to free up the ids.  Try again a bit later.
372      */
373 
374     tryAgain:
375     dispPtr->idCleanupScheduled = 1;
376     Tcl_CreateTimerHandler(500, WindowIdCleanup, (ClientData *) dispPtr);
377 }
378 
379 /*
380  *----------------------------------------------------------------------
381  *
382  * WindowIdCleanup2 --
383  *
384  *	This procedure is the last one in the chain that recycles
385  *	window ids.  It takes all of the ids indicated by its
386  *	argument and adds them back to the main id free list.
387  *
388  * Results:
389  *	None.
390  *
391  * Side effects:
392  *	Window ids get added to the main free list for their display.
393  *
394  *----------------------------------------------------------------------
395  */
396 
397 static void
WindowIdCleanup2(clientData)398 WindowIdCleanup2(clientData)
399     ClientData clientData;	/* Pointer to TkIdStack list. */
400 {
401     TkIdStack *stackPtr = (TkIdStack *) clientData;
402     TkIdStack *lastPtr;
403 
404     lastPtr = stackPtr;
405     while (lastPtr->nextPtr != NULL) {
406 	lastPtr = lastPtr->nextPtr;
407     }
408     lastPtr->nextPtr = stackPtr->dispPtr->idStackPtr;
409     stackPtr->dispPtr->idStackPtr = stackPtr;
410 }
411 
412 /*
413  *----------------------------------------------------------------------
414  *
415  * CheckRestrictProc --
416  *
417  *	This procedure is a restrict procedure, called by Tcl_DoOneEvent
418  *	to filter X events.  All it does is to set a flag to indicate
419  *	that there are X events present.
420  *
421  * Results:
422  *	Sets the integer pointed to by the argument, then returns
423  *	TK_DEFER_EVENT.
424  *
425  * Side effects:
426  *	None.
427  *
428  *----------------------------------------------------------------------
429  */
430 
431 static Tk_RestrictAction
CheckRestrictProc(clientData,eventPtr)432 CheckRestrictProc(clientData, eventPtr)
433     ClientData clientData;	/* Pointer to flag to set. */
434     XEvent *eventPtr;		/* Event to filter;  not used. */
435 {
436     int *flag = (int *) clientData;
437     *flag = 1;
438     return TK_DEFER_EVENT;
439 }
440 
441 /*
442  *----------------------------------------------------------------------
443  *
444  * Tk_GetPixmap --
445  *
446  *	Same as the XCreatePixmap procedure except that it manages
447  *	resource identifiers better.
448  *
449  * Results:
450  *	Returns a new pixmap.
451  *
452  * Side effects:
453  *	None.
454  *
455  *----------------------------------------------------------------------
456  */
457 
458 Pixmap
Tk_GetPixmap(display,d,width,height,depth)459 Tk_GetPixmap(display, d, width, height, depth)
460     Display *display;		/* Display for new pixmap. */
461     Drawable d;			/* Drawable where pixmap will be used. */
462     int width, height;		/* Dimensions of pixmap. */
463     int depth;			/* Bits per pixel for pixmap. */
464 {
465     return XCreatePixmap(display, d, (unsigned) width, (unsigned) height,
466 	    (unsigned) depth);
467 }
468 
469 /*
470  *----------------------------------------------------------------------
471  *
472  * Tk_FreePixmap --
473  *
474  *	Same as the XFreePixmap procedure except that it also marks
475  *	the resource identifier as free.
476  *
477  * Results:
478  *	None.
479  *
480  * Side effects:
481  *	The pixmap is freed in the X server and its resource identifier
482  *	is saved for re-use.
483  *
484  *----------------------------------------------------------------------
485  */
486 
487 void
Tk_FreePixmap(display,pixmap)488 Tk_FreePixmap(display, pixmap)
489     Display *display;		/* Display for which pixmap was allocated. */
490     Pixmap pixmap;		/* Identifier for pixmap. */
491 {
492     XFreePixmap(display, pixmap);
493     Tk_FreeXId(display, (XID) pixmap);
494 }
495