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