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