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