Annotation of micropolis/src/tk/tkgrab.c, revision 1.1.1.1

1.1       root        1: /* 
                      2:  * tkGrab.c --
                      3:  *
                      4:  *     This file provides procedures that implement grabs for Tk.
                      5:  *
                      6:  * Copyright 1992 Regents of the University of California.
                      7:  * Permission to use, copy, modify, and distribute this
                      8:  * software and its documentation for any purpose and without
                      9:  * fee is hereby granted, provided that the above copyright
                     10:  * notice appear in all copies.  The University of California
                     11:  * makes no representations about the suitability of this
                     12:  * software for any purpose.  It is provided "as is" without
                     13:  * express or implied warranty.
                     14:  */
                     15: 
                     16: #ifndef lint
                     17: static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkGrab.c,v 1.18 92/08/07 09:55:31 ouster Exp $ SPRITE (Berkeley)";
                     18: #endif
                     19: 
                     20: #include "tkconfig.h"
                     21: #include "tkint.h"
                     22: 
                     23: /*
                     24:  *-------------------------------------------------------------------
                     25:  * Problems with current grab implementation (8/7/92):
                     26:  *
                     27:  * 1. In a local grab the synthesized events are always placed at the
                     28:  *    front of the event queue.  If there are several grabs and ungrabs
                     29:  *    in a row, the groups of events for the different grabs/ungrabs
                     30:  *    end up in backwards order.
                     31:  * 2. The variables serverWinPtr and pointerWinPtr are hardly used at
                     32:  *    all and should probably be eliminated.
                     33:  * 3. The fact that grabWinPtr is set at the time a grab is set or
                     34:  *    released, rather than when its events are processed, means that
                     35:  *    it can get out of sync with the event queue if there's a rapid
                     36:  *    sequence of grabs or ungrabs.  The only solution I can think of
                     37:  *    is to keep a parallel queue to the event queue to update grabWinPtr
                     38:  *    (or, synthesize an event to change the pointer?).
                     39:  *-------------------------------------------------------------------
                     40:  */
                     41: 
                     42: /*
                     43:  * Bit definitions for grabFlags field of TkDisplay structures:
                     44:  *
                     45:  * GRAB_GLOBAL                 1 means this is a global grab (we grabbed via
                     46:  *                             the server so all applications are locked out.
                     47:  *                             0 means this is a local grab that affects
                     48:  *                             only this application.
                     49:  * GRAB_BUTTON_RELEASE         1 means that a button-release event just
                     50:  *                             occurred and we're in the middle of a sequence
                     51:  *                             of Enter and Leave events with NotifyUngrab
                     52:  *                             mode.
                     53:  */
                     54: 
                     55: #define GRAB_GLOBAL            1
                     56: #define GRAB_BUTTON_RELEASE    2
                     57: 
                     58: /*
                     59:  * Forward declarations for procedures declared later in this file:
                     60:  */
                     61: 
                     62: static void            ChangeEventWindow _ANSI_ARGS_((XEvent *eventPtr,
                     63:                            TkWindow *winPtr));
                     64: static void            MovePointer _ANSI_ARGS_((XEvent *eventPtr,
                     65:                            TkWindow *sourcePtr, TkWindow *destPtr));
                     66: static void            MovePointer2 _ANSI_ARGS_((TkWindow *sourcePtr,
                     67:                            TkWindow *destPtr, int mode));
                     68: 
                     69: /*
                     70:  *----------------------------------------------------------------------
                     71:  *
                     72:  * Tk_GrabCmd --
                     73:  *
                     74:  *     This procedure is invoked to process the "grab" Tcl command.
                     75:  *     See the user documentation for details on what it does.
                     76:  *
                     77:  * Results:
                     78:  *     A standard Tcl result.
                     79:  *
                     80:  * Side effects:
                     81:  *     See the user documentation.
                     82:  *
                     83:  *----------------------------------------------------------------------
                     84:  */
                     85: 
                     86:        /* ARGSUSED */
                     87: int
                     88: Tk_GrabCmd(clientData, interp, argc, argv)
                     89:     ClientData clientData;     /* Main window associated with
                     90:                                 * interpreter. */
                     91:     Tcl_Interp *interp;                /* Current interpreter. */
                     92:     int argc;                  /* Number of arguments. */
                     93:     char **argv;               /* Argument strings. */
                     94: {
                     95:     TkWindow *winPtr = (TkWindow *) clientData;
                     96:     int length, lockScreen;
                     97:     char *window;
                     98: 
                     99:     if (argc > 3) {
                    100:        badArgs:
                    101:        Tcl_AppendResult(interp, "wrong # args: should be \"",
                    102:                argv[0], " ?-global? ?window?\"", (char *) NULL);
                    103:        return TCL_ERROR;
                    104:     }
                    105:     if (argc == 1) {
                    106:        if ((winPtr->dispPtr->grabWinPtr != NULL)
                    107:                && (winPtr->dispPtr->grabWinPtr->mainPtr
                    108:                == winPtr->mainPtr)) {
                    109:            interp->result = Tk_PathName(winPtr->dispPtr->grabWinPtr);
                    110:        } else {
                    111:            interp->result = "none";
                    112:        }
                    113:        return TCL_OK;
                    114:     }
                    115:     if (argc == 3) {
                    116:        length = strlen(argv[1]);
                    117:        if (strncmp(argv[1], "-off", length) == 0) {
                    118:            lockScreen = -1;
                    119:        } else {
                    120:            if ((strncmp(argv[1], "-global", length) != 0) || (length < 2)) {
                    121:                goto badArgs;
                    122:            }
                    123:            lockScreen = 1;
                    124:        }
                    125:        window = argv[2];
                    126:     } else {
                    127:        lockScreen = 0;
                    128:        window = argv[1];
                    129:     }
                    130:     if ((window[0] == '\0')
                    131:            || (strncmp(window, "none", strlen(window)) == 0)) {
                    132:        Tk_Ungrab((Tk_Window) winPtr);  
                    133:     } else {
                    134:        Tk_Window tkwin;
                    135: 
                    136:        tkwin = Tk_NameToWindow(interp, window, (Tk_Window) winPtr);
                    137:        if (tkwin == NULL) {
                    138:            return TCL_ERROR;
                    139:        }
                    140:        if (lockScreen < 0) {
                    141:            Tk_Ungrab(tkwin);
                    142:        } else {
                    143:            return Tk_Grab(interp, tkwin, lockScreen);
                    144:        }
                    145:     }
                    146:     return TCL_OK;
                    147: }
                    148: 
                    149: /*
                    150:  *----------------------------------------------------------------------
                    151:  *
                    152:  * Tk_Grab --
                    153:  *
                    154:  *     Grabs the pointer and keyboard, so that mouse-related events are
                    155:  *     only reported relative to a given window and its descendants.
                    156:  *
                    157:  * Results:
                    158:  *     A standard Tcl result is returned.  TCL_OK is the normal return
                    159:  *     value;  if the grab could not be set then TCL_ERROR is returned
                    160:  *     and interp->result will hold an error message.
                    161:  *
                    162:  * Side effects:
                    163:  *     Once this call completes successfully, no window outside the
                    164:  *     tree rooted at tkwin will receive pointer- or keyboard-related
                    165:  *     events until the next call to Tk_Ungrab.  If a previous grab was
                    166:  *     in effect within this application, then it is replaced with a new
                    167:  *     one.
                    168:  *
                    169:  *----------------------------------------------------------------------
                    170:  */
                    171: 
                    172: int
                    173: Tk_Grab(interp, tkwin, grabGlobal)
                    174:     Tcl_Interp *interp;                        /* Used for error reporting. */
                    175:     Tk_Window tkwin;                   /* Window on whose behalf the pointer
                    176:                                         * is to be grabbed. */
                    177:     int grabGlobal;                    /* Non-zero means issue a grab to the
                    178:                                         * server so that no other application
                    179:                                         * gets mouse or keyboard events.
                    180:                                         * Zero means the grab only applies
                    181:                                         * within this application. */
                    182: {
                    183:     int grabResult;
                    184:     TkWindow *winPtr = (TkWindow *) tkwin;
                    185:     TkDisplay *dispPtr = winPtr->dispPtr;
                    186:     int grabRequest, inSequence, ignoring, numEvents, i, diff;
                    187:     XEvent *events, *eventPtr;
                    188:     TkWindow *winPtr2;
                    189: 
                    190:     if (dispPtr->grabWinPtr != NULL) {
                    191:        if ((dispPtr->grabWinPtr == winPtr)
                    192:                && (grabGlobal == ((dispPtr->grabFlags & GRAB_GLOBAL) != 0))) {
                    193:            return TCL_OK;
                    194:        }
                    195:        if (dispPtr->grabWinPtr->mainPtr != winPtr->mainPtr) {
                    196:            alreadyGrabbed:
                    197:            interp->result = "grab failed: another application has grab";
                    198:            return TCL_ERROR;
                    199:        }
                    200:        Tk_Ungrab(tkwin);
                    201:     }
                    202: 
                    203:     if (grabGlobal) {
                    204:        grabRequest = NextRequest(dispPtr->display);
                    205:        grabResult = XGrabPointer(dispPtr->display, Tk_WindowId(tkwin),
                    206:                True, ButtonPressMask|ButtonReleaseMask|ButtonMotionMask|PointerMotionMask,
                    207:                GrabModeAsync, GrabModeAsync, None, None,
                    208:                TkCurrentTime(dispPtr));
                    209:        if (grabResult != 0) {
                    210:            grabError:
                    211:            if (grabResult == GrabNotViewable) {
                    212:                interp->result = "grab failed: window not viewable";
                    213:            } else if (grabResult == AlreadyGrabbed) {
                    214:                goto alreadyGrabbed;
                    215:            } else if (grabResult == GrabFrozen) {
                    216:                interp->result = "grab failed: keyboard or pointer frozen";
                    217:            } else if (grabResult == GrabInvalidTime) {
                    218:                interp->result = "grab failed: invalid time";
                    219:            } else {
                    220:                char msg[100];
                    221:        
                    222:                sprintf(msg, "grab failed for unknown reason (code %d)",
                    223:                        grabResult);
                    224:                Tcl_AppendResult(interp, msg, (char *) NULL);
                    225:            }
                    226:            return TCL_ERROR;
                    227:        }
                    228:        grabResult = XGrabKeyboard(dispPtr->display, Tk_WindowId(tkwin),
                    229:                False, GrabModeAsync, GrabModeAsync, TkCurrentTime(dispPtr));
                    230:        if (grabResult != 0) {
                    231:            XUngrabPointer(dispPtr->display, TkCurrentTime(dispPtr));
                    232:            goto grabError;
                    233:        }
                    234:        dispPtr->grabFlags |= GRAB_GLOBAL;
                    235:     } else {
                    236:        /*
                    237:         * The call to XUngrabPointer below is needed to release any
                    238:         * existing auto-grab due to a button press.  This is needed
                    239:         * so that local grabs behave the same as global grabs (the
                    240:         * button grab is released by the X server in a global grab).
                    241:         */
                    242: 
                    243:        XUngrabPointer(dispPtr->display, TkCurrentTime(dispPtr));
                    244:        grabRequest = LastKnownRequestProcessed(dispPtr->display);
                    245:        dispPtr->grabFlags &= ~GRAB_GLOBAL;
                    246: 
                    247:        /*
                    248:         * Since we're not telling the server about the grab, we have
                    249:         * to generate Leave and Enter events to move the pointer from
                    250:         * its current window to the grab window.
                    251:         */
                    252: 
                    253:        MovePointer2(dispPtr->pointerWinPtr, winPtr, NotifyGrab);
                    254:     }
                    255:     dispPtr->grabWinPtr = winPtr;
                    256: 
                    257:     /*
                    258:      * When a grab occurs, X generates Enter and Leave events to move
                    259:      * the pointer from its current window to the grab window, even if
                    260:      * the current window is in the grab tree.  We don't want these
                    261:      * events getting through to the application if the current window
                    262:      * is in the grab tree.  In order to eliminate the bogus events,
                    263:      * process all pending events and filter out the bogus ones.
                    264:      *
                    265:      * Also, filter out the final enter event into the grab window in
                    266:      * any case:  this event shouldn't be delivered until the mouse really
                    267:      * moves into that window.
                    268:      *
                    269:      * The code below reads in all the pending events, filters out the bad
                    270:      * ones, and then pushes back all the events that weren't filtered.
                    271:      * Another alternative would be to simply process the events
                    272:      * immediately rather than pushing them back again.  However, this
                    273:      * tends to interfere with scripts since it causes pending events
                    274:      * to be processed during the "grab" command.  The "grab" command
                    275:      * might have been invoked in the middle of some computation where
                    276:      * it's a bad idea to process new events.
                    277:      */
                    278: 
                    279:     XSync(dispPtr->display, False);
                    280:     numEvents = QLength(dispPtr->display);
                    281:     if (numEvents == 0) {
                    282:        return TCL_OK;
                    283:     }
                    284:     events = (XEvent *) ckalloc((unsigned) (numEvents * sizeof(XEvent)));
                    285:     for (i = 0; i < numEvents; i++) {
                    286:        XNextEvent(dispPtr->display, &events[i]);
                    287:     }
                    288:     inSequence = ignoring = 0;
                    289:     for (i = numEvents-1, eventPtr = events; i >= 0; i--, eventPtr++) {
                    290:        if (((eventPtr->type != EnterNotify)
                    291:                && (eventPtr->type != LeaveNotify))
                    292:                || (eventPtr->xcrossing.mode != NotifyGrab)) {
                    293:            continue;
                    294:        }
                    295: 
                    296:        /*
                    297:         * The diff caculcation below is trickier than you might think,
                    298:         * due to the fact that the event serial number is unsigned and
                    299:         * serial numbers can wrap around.
                    300:         */
                    301: 
                    302:        diff = eventPtr->xcrossing.serial;
                    303:        diff -= grabRequest;
                    304:        if (!inSequence && (diff >= 0)) {
                    305:            /*
                    306:             * This is the first event of the grab sequence.  See if its
                    307:             * window is in the grab tree and ignore the sequence if it is.
                    308:             */
                    309: 
                    310:            inSequence = 1;
                    311:            if (XFindContext(dispPtr->display, eventPtr->xcrossing.window,
                    312:                    tkWindowContext, (void *) &winPtr2) == 0) {
                    313:                for ( ; winPtr2 != NULL; winPtr2 = winPtr2->parentPtr) {
                    314:                    if (winPtr2 == dispPtr->grabWinPtr) {
                    315:                        ignoring = 1;
                    316:                        break;
                    317:                    }
                    318:                }
                    319:            }
                    320:        }
                    321:        if (ignoring) {
                    322:            eventPtr->type = 0;
                    323:        }
                    324:        if (inSequence && (eventPtr->type == EnterNotify)
                    325:                && (dispPtr->grabWinPtr->window
                    326:                == eventPtr->xcrossing.window)) {
                    327:            eventPtr->type = 0;
                    328:            break;
                    329:        }
                    330:     }
                    331:     for (i = numEvents-1, eventPtr = &events[i]; i >= 0; i--, eventPtr--) {
                    332:        if (eventPtr->type != 0) {
                    333:            XPutBackEvent(dispPtr->display, eventPtr);
                    334:        }
                    335:     }
                    336:     ckfree((char *) events);
                    337:     return TCL_OK;
                    338: }
                    339: 
                    340: /*
                    341:  *----------------------------------------------------------------------
                    342:  *
                    343:  * Tk_Ungrab --
                    344:  *
                    345:  *     Releases a grab on the mouse pointer and keyboard.
                    346:  *
                    347:  * Results:
                    348:  *     None.
                    349:  *
                    350:  * Side effects:
                    351:  *     Pointer and keyboard events will start being delivered to other
                    352:  *     windows again.
                    353:  *
                    354:  *----------------------------------------------------------------------
                    355:  */
                    356: 
                    357: void
                    358: Tk_Ungrab(tkwin)
                    359:     Tk_Window tkwin;                   /* Window that identifies display
                    360:                                         * for grab to be released. */
                    361: {
                    362:     TkDisplay *dispPtr = ((TkWindow *) tkwin)->dispPtr;
                    363:     int inSequence, ignoring, ungrabRequest, numEvents, i, j, diff;
                    364:     TkWindow *grabWinPtr, *winPtr;
                    365:     XEvent *events, *eventPtr, *eventPtr2;
                    366: 
                    367:     grabWinPtr = dispPtr->grabWinPtr;
                    368:     if (grabWinPtr == NULL) {
                    369:        return;
                    370:     }
                    371:     dispPtr->grabWinPtr = NULL;
                    372:     dispPtr->buttonWinPtr = NULL;
                    373:     if (dispPtr->grabFlags & GRAB_GLOBAL) {
                    374:        ungrabRequest = NextRequest(dispPtr->display);
                    375:        XUngrabPointer(dispPtr->display, TkCurrentTime(dispPtr));
                    376:        XUngrabKeyboard(dispPtr->display, TkCurrentTime(dispPtr));
                    377:        XSync(dispPtr->display, False);
                    378:     } else {
                    379:        ungrabRequest = LastKnownRequestProcessed(dispPtr->display);
                    380:        if ((dispPtr->ungrabWinPtr != NULL)
                    381:                && (dispPtr->ungrabWinPtr->mainPtr != grabWinPtr->mainPtr)) {
                    382: 
                    383:            /*
                    384:             * Don't report entries down into a window of a different
                    385:             * application, since it's already seen those entries earlier.
                    386:             */
                    387: 
                    388:            dispPtr->ungrabWinPtr = NULL;
                    389:        }
                    390:        MovePointer2(grabWinPtr, dispPtr->ungrabWinPtr, NotifyUngrab);
                    391:     }
                    392: 
                    393:     /*
                    394:      * We have to filter all the pending events in a fashion similar to
                    395:      * Tk_Grab. As with grabs, the X server generates an Enter-Leave event
                    396:      * sequence to move the pointer from the grab window back to its
                    397:      * current window.  We need to ignore this sequence if the pointer
                    398:      * is being moved to a window that's already in the grab tree.
                    399:      */
                    400: 
                    401:     numEvents = QLength(dispPtr->display);
                    402:     if (numEvents == 0) {
                    403:        return;
                    404:     }
                    405:     events = (XEvent *) ckalloc((unsigned) (numEvents * sizeof(XEvent)));
                    406:     for (i = 0; i < numEvents; i++) {
                    407:        XNextEvent(dispPtr->display, &events[i]);
                    408:     }
                    409:     inSequence = ignoring = 0;
                    410:     for (i = numEvents-1, eventPtr = events; i >= 0; i--, eventPtr++) {
                    411:        if (((eventPtr->type != EnterNotify)
                    412:                && (eventPtr->type != LeaveNotify))
                    413:                || (eventPtr->xcrossing.mode != NotifyUngrab)) {
                    414:            continue;
                    415:        }
                    416:        diff = eventPtr->xcrossing.serial;
                    417:        diff -= ungrabRequest;
                    418:        if (!inSequence && (diff >= 0)) {
                    419: 
                    420:            /*
                    421:             * This is the first event of the ungrab sequence.  Scan forward
                    422:             * looking for the final Enter event in the sequence.  Then see
                    423:             * if that event's window is in the grab tree.
                    424:             */
                    425: 
                    426:            inSequence = 1;
                    427:            for (j = i, eventPtr2 = eventPtr; j >= 0; j--, eventPtr2++) {
                    428:                if (eventPtr2->type == EnterNotify) {
                    429:                    if (eventPtr2->xcrossing.mode != NotifyUngrab) {
                    430:                        break;
                    431:                    }
                    432:                    if ((eventPtr2->xcrossing.detail != NotifyAncestor)
                    433:                            && (eventPtr2->xcrossing.detail != NotifyInferior)
                    434:                            && (eventPtr2->xcrossing.detail
                    435:                                    != NotifyNonlinear)) {
                    436:                        continue;
                    437:                    }
                    438:                    if (XFindContext(dispPtr->display,
                    439:                            eventPtr2->xcrossing.window,
                    440:                            tkWindowContext, (void *) &winPtr) == 0) {
                    441:                        for ( ; winPtr != NULL; winPtr = winPtr->parentPtr) {
                    442:                            if (winPtr == grabWinPtr) {
                    443:                                ignoring = 1;
                    444:                                break;
                    445:                            }
                    446:                        }
                    447:                    }
                    448:                    break;
                    449:                } else if ((eventPtr2->type != LeaveNotify)
                    450:                        || (eventPtr2->xcrossing.mode != NotifyUngrab)) {
                    451:                    break;
                    452:                }
                    453:            }
                    454:        }
                    455:        if (ignoring) {
                    456:            eventPtr->type = 0;
                    457:        }
                    458:     }
                    459:     for (i = numEvents-1, eventPtr = &events[i]; i >= 0; i--, eventPtr--) {
                    460:        if (eventPtr->type != 0) {
                    461:            XPutBackEvent(dispPtr->display, eventPtr);
                    462:        }
                    463:     }
                    464:     ckfree((char *) events);
                    465: }
                    466: 
                    467: /*
                    468:  *----------------------------------------------------------------------
                    469:  *
                    470:  * TkPointerEvent --
                    471:  *
                    472:  *     This procedure is called for each pointer-related event, before
                    473:  *     the event has been processed.  It does various things to make
                    474:  *     grabs work correctly.
                    475:  *
                    476:  * Results:
                    477:  *     If the return value is 1 it means the event should be processed
                    478:  *     (event handlers should be invoked).  If the return value is 0
                    479:  *     it means the event should be ignored in order to make grabs
                    480:  *     work correctly.  Note:  the event may be modified by this procedure.
                    481:  *
                    482:  * Side effects:
                    483:  *     Grab state information may be updated.
                    484:  *
                    485:  *----------------------------------------------------------------------
                    486:  */
                    487: 
                    488: int
                    489: TkPointerEvent(eventPtr, winPtr)
                    490:     register XEvent *eventPtr;         /* Pointer to the event. */
                    491:     TkWindow *winPtr;                  /* Tk's information for window
                    492:                                         * where event was reported. */
                    493: {
                    494:     register TkWindow *winPtr2;
                    495:     TkDisplay *dispPtr = winPtr->dispPtr;
                    496:     int outsideGrabTree = 0;
                    497:     int originalFlags;
                    498:     int appGrabbed = 0;                        /* Non-zero means event is being
                    499:                                         * reported to an application that is
                    500:                                         * affected by the grab. */
                    501: #define ALL_BUTTONS \
                    502:        (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask)
                    503:     static unsigned int state[] = {
                    504:        Button1Mask, Button2Mask, Button3Mask, Button4Mask, Button5Mask
                    505:     };
                    506: 
                    507:     /*
                    508:      * Don't do any filtering on events generated by the event-sharing code.
                    509:      */
                    510: 
                    511:     if (eventPtr == tkShareEventPtr) {
                    512:        return 1;
                    513:     }
                    514: 
                    515:     /*
                    516:      * If a grab is in effect, see if the event is being reported to
                    517:      * a window in the grab tree.  Also see if the event is being reported
                    518:      * to an application that is affected by the grab.
                    519:      */
                    520: 
                    521:     if (dispPtr->grabWinPtr != NULL) {
                    522:        if ((winPtr->mainPtr == dispPtr->grabWinPtr->mainPtr)
                    523:                || (dispPtr->grabFlags & GRAB_GLOBAL)) {
                    524:            appGrabbed = 1;
                    525:        }
                    526:        for (winPtr2 = winPtr; winPtr2 != dispPtr->grabWinPtr;
                    527:                winPtr2 = winPtr2->parentPtr) {
                    528:            if (winPtr2 == NULL) {
                    529:                outsideGrabTree = 1;
                    530:                break;
                    531:            }
                    532:        }
                    533:     }
                    534: 
                    535:     originalFlags = dispPtr->grabFlags;
                    536:     dispPtr->grabFlags &= ~GRAB_BUTTON_RELEASE;
                    537:     if ((eventPtr->type == EnterNotify) || (eventPtr->type == LeaveNotify)) {
                    538:        if ((eventPtr->type == EnterNotify)
                    539:                && (eventPtr->xcrossing.detail != NotifyVirtual)
                    540:                && (eventPtr->xcrossing.detail != NotifyNonlinearVirtual)) {
                    541:            if ((dispPtr->grabWinPtr == NULL)
                    542:                    || (dispPtr->grabWinPtr->mainPtr == winPtr->mainPtr)) {
                    543:                dispPtr->ungrabWinPtr = winPtr;
                    544:            }
                    545:            dispPtr->serverWinPtr = winPtr;
                    546:        } else {
                    547:            dispPtr->serverWinPtr = NULL;
                    548:        }
                    549:        if (dispPtr->grabWinPtr != NULL) {
                    550:            if (eventPtr->xcrossing.mode == NotifyNormal) {
                    551:                /*
                    552:                 * When a grab is active, X continues to report enter and
                    553:                 * leave events for windows outside the tree of the grab
                    554:                 * window.  Detect these events and ignore them.
                    555:                 */
                    556: 
                    557:                if (outsideGrabTree && appGrabbed) {
                    558:                    return 0;
                    559:                }
                    560:     
                    561:                /*
                    562:                 * Make buttons have the same grab-like behavior inside a grab
                    563:                 * as they do outside a grab:  do this by ignoring enter and
                    564:                 * leave events except for the window in which the button was
                    565:                 * pressed.
                    566:                 */
                    567: 
                    568:                if ((dispPtr->buttonWinPtr != NULL)
                    569:                        && (winPtr != dispPtr->buttonWinPtr)) {
                    570:                    return 0;
                    571:                }
                    572:            } else if (eventPtr->xcrossing.mode == NotifyUngrab) {
                    573:                /*
                    574:                 * Keep the GRAB_BUTTON_RELEASE flag on if it used to be on.
                    575:                 */
                    576: 
                    577:                dispPtr->grabFlags = originalFlags;
                    578:                if (outsideGrabTree && appGrabbed
                    579:                        && (dispPtr->grabFlags & GRAB_BUTTON_RELEASE)) {
                    580:                    /*
                    581:                     * The only way we get here is if a button was pressed,
                    582:                     * then moved to a different window and released.  Enter
                    583:                     * and leave events were deferred while the button was
                    584:                     * down, but now we're getting them to move the pointer
                    585:                     * back to the right window, and this particular event
                    586:                     * is for a window outside the grab tree.  Ignore it.
                    587:                     */
                    588:     
                    589:                    return 0;
                    590:                }
                    591:            }
                    592:        }
                    593: 
                    594:        /*
                    595:         * Keep track of the window containing the mouse, in order to
                    596:         * detect various bogus event sequences.
                    597:         */
                    598: 
                    599:        dispPtr->pointerWinPtr = dispPtr->serverWinPtr;
                    600:        return 1;
                    601:     }
                    602:     if ((dispPtr->grabWinPtr == NULL) || !appGrabbed) {
                    603:        return 1;
                    604:     }
                    605: 
                    606:     if (eventPtr->type == MotionNotify) {
                    607:        /*
                    608:         * When grabs are active, X reports motion events relative to the
                    609:         * window under the pointer.  Instead, it should report the events
                    610:         * relative to the window the button went down in, if there is a
                    611:         * button down.  Otherwise, if the pointer window is outside the
                    612:         * subtree of the grab window, the events should be reported
                    613:         * relative to the grab window.  Otherwise, the event should be
                    614:         * reported to the pointer window.
                    615:         */
                    616: 
                    617:        winPtr2 = winPtr;
                    618:        if (dispPtr->buttonWinPtr != NULL) {
                    619:            winPtr2 = dispPtr->buttonWinPtr;
                    620:        } else if (outsideGrabTree || (dispPtr->serverWinPtr == NULL)) {
                    621:            winPtr2 = dispPtr->grabWinPtr;
                    622:        }
                    623:        if (winPtr2 != winPtr) {
                    624:            XEvent newEvent;
                    625: 
                    626:            newEvent = *eventPtr;
                    627:            ChangeEventWindow(&newEvent, winPtr2);
                    628:            XPutBackEvent(winPtr2->display, &newEvent);
                    629:            return 0;
                    630:        }
                    631:        return 1;
                    632:     }
                    633: 
                    634:     /*
                    635:      * Process ButtonPress and ButtonRelease events:
                    636:      * 1. Keep track of whether a button is down and what window it
                    637:      *    went down in.
                    638:      * 2. If the first button goes down outside the grab tree, pretend
                    639:      *    it went down in the grab window.  Note: it's important to
                    640:      *    redirect events to the grab window like this in order to make
                    641:      *    things like menus work, where button presses outside the
                    642:      *    grabbed menu need to be seen.  An application can always
                    643:      *    ignore the events if they occur outside its window.
                    644:      * 3. If a button press or release occurs outside the window where
                    645:      *    the first button was pressed, retarget the event so it's reported
                    646:      *    to the window where the first button was pressed.
                    647:      * 4. If the last button is released in a window different than where
                    648:      *    the first button was pressed, generate Enter/Leave events to
                    649:      *    move the mouse from the button window to its current window.
                    650:      * 5. If the grab is set at a time when a button is already down, or
                    651:      *    if the window where the button was pressed was deleted, then
                    652:      *    dispPtr->buttonWinPtr will stay NULL.  Just forget about the
                    653:      *    auto-grab for the button press;  events will go to whatever
                    654:      *    window contains the pointer.  If this window isn't in the grab
                    655:      *    tree then redirect events to the grab window.
                    656:      */
                    657: 
                    658:     if ((eventPtr->type == ButtonPress) || (eventPtr->type == ButtonRelease)) {
                    659:        winPtr2 = dispPtr->buttonWinPtr;
                    660:        if (winPtr2 == NULL) {
                    661:            if (outsideGrabTree) {
                    662:                winPtr2 = dispPtr->grabWinPtr;                  /* Note 5. */
                    663:            } else {
                    664:                winPtr2 = winPtr;                               /* Note 5. */
                    665:            }
                    666:        }
                    667:        if (eventPtr->type == ButtonPress) {
                    668:            if ((eventPtr->xbutton.state & ALL_BUTTONS) == 0) {
                    669:                if (outsideGrabTree) {
                    670:                    XEvent newEvent;
                    671: 
                    672:                    newEvent = *eventPtr;
                    673:                    ChangeEventWindow(&newEvent, dispPtr->grabWinPtr);
                    674:                    XPutBackEvent(dispPtr->display, &newEvent);
                    675:                    return 0;                                   /* Note 2. */
                    676:                }
                    677:                dispPtr->buttonWinPtr = winPtr;
                    678:                return 1;
                    679:            }
                    680:        } else {
                    681:            if ((eventPtr->xbutton.state & ALL_BUTTONS)
                    682:                    == state[eventPtr->xbutton.button - Button1]) {
                    683:                if ((dispPtr->buttonWinPtr != winPtr)
                    684:                        && (dispPtr->buttonWinPtr != NULL)) {
                    685:                    XEvent newEvent;                            /* Note 4. */
                    686: 
                    687:                    /*
                    688:                     * If the button release is made with pointer outside
                    689:                     * all applications, X reports it relative to the grab
                    690:                     * window.   Change the current window to NULL to
                    691:                     * reflect that the pointer's outside everything.  Do
                    692:                     * the same if the pointer's in a window that's not
                    693:                     * part of the grab tree.
                    694:                     */
                    695: 
                    696:                    if (outsideGrabTree || (dispPtr->serverWinPtr == NULL)) {
                    697:                        winPtr = NULL;
                    698:                    }
                    699:                    newEvent = *eventPtr;
                    700:                    newEvent.xcrossing.mode = NotifyUngrab;
                    701:                    newEvent.xcrossing.focus = False;
                    702:                    newEvent.xcrossing.state =
                    703:                            eventPtr->xbutton.state & ~ALL_BUTTONS;
                    704:                    MovePointer(&newEvent, dispPtr->buttonWinPtr, winPtr);
                    705:                }
                    706:                dispPtr->buttonWinPtr = NULL;
                    707:                dispPtr->grabFlags |= GRAB_BUTTON_RELEASE;
                    708:            }
                    709:        }
                    710:        if (winPtr2 != winPtr) {
                    711:            XEvent newEvent;
                    712: 
                    713:            newEvent = *eventPtr;
                    714:            ChangeEventWindow(&newEvent, winPtr2);
                    715:            XPutBackEvent(dispPtr->display, &newEvent);
                    716:            return 0;                                           /* Note 3. */
                    717:        }
                    718:     }
                    719: 
                    720:     return 1;
                    721: }
                    722: 
                    723: /*
                    724:  *----------------------------------------------------------------------
                    725:  *
                    726:  * ChangeEventWindow --
                    727:  *
                    728:  *     Given an event and a new window to which the event should be
                    729:  *     retargeted, modify fields of the event so that the event is
                    730:  *     properly retargeted to the new window.
                    731:  *
                    732:  * Results:
                    733:  *     The following fields of eventPtr are modified:  window,
                    734:  *     subwindow, x, y, same_screen.
                    735:  *
                    736:  * Side effects:
                    737:  *     None.
                    738:  *
                    739:  *----------------------------------------------------------------------
                    740:  */
                    741: 
                    742: static void
                    743: ChangeEventWindow(eventPtr, winPtr)
                    744:     register XEvent *eventPtr; /* Event to retarget.  Must have
                    745:                                 * type ButtonPress, ButtonRelease, KeyPress,
                    746:                                 * KeyRelease, MotionNotify, EnterNotify,
                    747:                                 * or LeaveNotify. */
                    748:     TkWindow *winPtr;          /* New target window for event. */
                    749: {
                    750:     int x, y, sameScreen, bd;
                    751:     register TkWindow *childPtr;
                    752: 
                    753:     eventPtr->xmotion.window = Tk_WindowId(winPtr);
                    754:     if (eventPtr->xmotion.root ==
                    755:            RootWindow(winPtr->display, winPtr->screenNum)) {
                    756:        Tk_GetRootCoords((Tk_Window) winPtr, &x, &y);
                    757:        eventPtr->xmotion.x = eventPtr->xmotion.x_root - x;
                    758:        eventPtr->xmotion.y = eventPtr->xmotion.y_root - y;
                    759:        eventPtr->xmotion.subwindow = None;
                    760:        for (childPtr = winPtr->childList; childPtr != NULL;
                    761:                childPtr = childPtr->nextPtr) {
                    762:            if (childPtr->flags & TK_TOP_LEVEL) {
                    763:                continue;
                    764:            }
                    765:            x = eventPtr->xmotion.x - childPtr->changes.x;
                    766:            y = eventPtr->xmotion.y - childPtr->changes.y;
                    767:            bd = childPtr->changes.border_width;
                    768:            if ((x >= -bd) && (y >= -bd)
                    769:                    && (x < (childPtr->changes.width + bd))
                    770:                    && (y < (childPtr->changes.width + bd))) {
                    771:                eventPtr->xmotion.subwindow = childPtr->window;
                    772:            }
                    773:        }
                    774:        sameScreen = 1;
                    775:     } else {
                    776:        eventPtr->xmotion.x = 0;
                    777:        eventPtr->xmotion.y = 0;
                    778:        eventPtr->xmotion.subwindow = None;
                    779:        sameScreen = 0;
                    780:     }
                    781:     if (eventPtr->type == MotionNotify) {
                    782:        eventPtr->xmotion.same_screen = sameScreen;
                    783:     } else {
                    784:        eventPtr->xbutton.same_screen = sameScreen;
                    785:     }
                    786: }
                    787: 
                    788: /*
                    789:  *----------------------------------------------------------------------
                    790:  *
                    791:  * MovePointer --
                    792:  *
                    793:  *     This procedure synthesizes EnterNotify and LeaveNotify events
                    794:  *     to correctly transfer the pointer from one window to another.
                    795:  *
                    796:  * Results:
                    797:  *     None.
                    798:  *
                    799:  * Side effects:
                    800:  *     Synthesized events may be pushed back onto the event queue.
                    801:  *     The event pointed to by eventPtr is modified.
                    802:  *
                    803:  *----------------------------------------------------------------------
                    804:  */
                    805: 
                    806: static void
                    807: MovePointer(eventPtr, sourcePtr, destPtr)
                    808:     XEvent *eventPtr;          /* A template X event.  Must have all fields
                    809:                                 * properly set for EnterNotify and LeaveNotify
                    810:                                 * events except window, subwindow, x, y,
                    811:                                 * detail, and same_screen.  (x_root and y_root
                    812:                                 * must be valid, even though x and y needn't
                    813:                                 * be valid). */
                    814:     TkWindow *sourcePtr;       /* Window currently containing pointer (NULL
                    815:                                 * means it's not one managed by this
                    816:                                 * process). */
                    817:     TkWindow *destPtr;         /* Window that is to end up containing the
                    818:                                 * pointer (NULL means it's not one managed
                    819:                                 * by this process). */
                    820: {
                    821:     TkDisplay *dispPtr;
                    822:     register TkWindow *ancestorPtr;    /* Lowest ancestor shared between
                    823:                                         * sourcePtr and destPtr, or
                    824:                                         * sourcePtr's top-level window if no
                    825:                                         * shared ancestor. */
                    826:     register TkWindow *winPtr;
                    827:     int upLevels, downLevels, i, j;
                    828: 
                    829:     /*
                    830:      * There are four possible cases to deal with:
                    831:      *
                    832:      * 1. SourcePtr and destPtr are the same.  There's nothing to do in
                    833:      *    this case.
                    834:      * 2. SourcePtr is an ancestor of destPtr in the same top-level
                    835:      *    window.  Must generate events down the window tree from source
                    836:      *    to dest.
                    837:      * 3. DestPtr is an ancestor of sourcePtr in the same top-level
                    838:      *    window.  Must generate events up the window tree from sourcePtr
                    839:      *    to destPtr.
                    840:      * 4. All other cases.  Must first generate events up the window tree
                    841:      *    from sourcePtr to its top-level, then down from destPtr's
                    842:      *    top-level to destPtr. This form is called "non-linear."
                    843:      *
                    844:      * The code below separates these four cases and decides how many levels
                    845:      * up and down events have to be generated for.
                    846:      */
                    847: 
                    848:     if (sourcePtr == destPtr) {
                    849:        return;
                    850:     }
                    851: 
                    852:     /*
                    853:      * Mark destPtr and all of its ancestors with a special flag bit.
                    854:      */
                    855: 
                    856:     if (destPtr != NULL) {
                    857:        dispPtr = destPtr->dispPtr;
                    858:        for (winPtr = destPtr; ; winPtr = winPtr->parentPtr) {
                    859:            winPtr->flags |= TK_GRAB_FLAG;
                    860:            if (winPtr->flags & TK_TOP_LEVEL) {
                    861:                break;
                    862:            }
                    863:        }
                    864:     } else {
                    865:        dispPtr = sourcePtr->dispPtr;
                    866:     }
                    867: 
                    868:     /*
                    869:      * Search upwards from sourcePtr until an ancestor of destPtr is
                    870:      * found or a top-level window is reached.  Remember if we pass out
                    871:      * of the grab tree along the way, since this means we'll have to
                    872:      * skip some of the events that would otherwise be generated.
                    873:      */
                    874: 
                    875:     ancestorPtr = sourcePtr;
                    876:     upLevels = 0;
                    877:     if (sourcePtr != NULL) {
                    878:        for (; ; upLevels++, ancestorPtr = ancestorPtr->parentPtr) {
                    879:            if (ancestorPtr->flags & TK_GRAB_FLAG) {
                    880:                break;
                    881:            }
                    882:            if (ancestorPtr->flags & TK_TOP_LEVEL)  {
                    883:                upLevels++;
                    884:                break;
                    885:            }
                    886:        }
                    887:     }
                    888: 
                    889:     /*
                    890:      * Search upwards from destPtr again, clearing the flag bits and
                    891:      * remembering how many levels up we had to go.
                    892:      */
                    893: 
                    894:     if (destPtr == NULL) {
                    895:        downLevels = 0;
                    896:     } else {
                    897:        downLevels = -1;
                    898:        for (i = 0, winPtr = destPtr; ; i++, winPtr = winPtr->parentPtr) {
                    899:            winPtr->flags &= ~TK_GRAB_FLAG;
                    900:            if (winPtr == ancestorPtr) {
                    901:                downLevels = i;
                    902:            }
                    903:            if (winPtr->flags & TK_TOP_LEVEL) {
                    904:                if (downLevels == -1) {
                    905:                    downLevels = i+1;
                    906:                }
                    907:                break;
                    908:            }
                    909:        }
                    910:     }
                    911: 
                    912:     /*
                    913:      * Generate enter/leave events and push them back onto the event
                    914:      * queue.  This has to be done backwards, since the last event
                    915:      * pushed will be the first one processed.
                    916:      */
                    917: 
                    918: #define PUSH_EVENT(w, t, d)                    \
                    919:     if (w->window != None) {                   \
                    920:        eventPtr->type = t;                     \
                    921:        eventPtr->xcrossing.detail = d;         \
                    922:        ChangeEventWindow(eventPtr, w);         \
                    923:        XPutBackEvent(w->display, eventPtr);    \
                    924:     }
                    925: 
                    926:     if (downLevels == 0) {
                    927:     
                    928:        /*
                    929:         * SourcePtr is an inferior of destPtr.
                    930:         */
                    931: 
                    932:        if (destPtr != NULL) {
                    933:            PUSH_EVENT(destPtr, EnterNotify, NotifyInferior);
                    934:        }
                    935:        for (i = upLevels-1; i > 0; i--) {
                    936:            for (winPtr = sourcePtr, j = 0; j < i;
                    937:                    winPtr = winPtr->parentPtr, j++) {
                    938:                if (winPtr == dispPtr->grabWinPtr) {
                    939:                    goto nextIteration;
                    940:                }
                    941:            }
                    942:            PUSH_EVENT(winPtr, LeaveNotify, NotifyVirtual);
                    943:            nextIteration: continue;
                    944:        }
                    945:        PUSH_EVENT(sourcePtr, LeaveNotify, NotifyAncestor);
                    946:     } else if (upLevels == 0) {
                    947: 
                    948:        /*
                    949:         * DestPtr is an inferior of sourcePtr.
                    950:         */
                    951: 
                    952:        if (destPtr != NULL) {
                    953:            PUSH_EVENT(destPtr, EnterNotify, NotifyAncestor);
                    954:        }
                    955:        for (winPtr = destPtr->parentPtr, i = downLevels-1; i > 0;
                    956:                winPtr = winPtr->parentPtr, i--) {
                    957:            PUSH_EVENT(winPtr, EnterNotify, NotifyVirtual);
                    958:        }
                    959:        if (sourcePtr != NULL) {
                    960:            PUSH_EVENT(sourcePtr, LeaveNotify, NotifyInferior);
                    961:        }
                    962:     } else {
                    963: 
                    964:        /*
                    965:         * Non-linear:  neither window is an inferior of the other.
                    966:         */
                    967: 
                    968:        if (destPtr != NULL) {
                    969:            PUSH_EVENT(destPtr, EnterNotify, NotifyNonlinear);
                    970:        }
                    971:        if (destPtr != dispPtr->grabWinPtr) {
                    972:            for (winPtr = destPtr->parentPtr, i = downLevels-1; i > 0;
                    973:                    winPtr = winPtr->parentPtr, i--) {
                    974:                PUSH_EVENT(winPtr, EnterNotify, NotifyNonlinearVirtual);
                    975:                if (winPtr == dispPtr->grabWinPtr) {
                    976:                    break;
                    977:                }
                    978:            }
                    979:        }
                    980:        for (i = upLevels-1; i > 0; i--) {
                    981:            for (winPtr = sourcePtr, j = 0; j < i;
                    982:                    winPtr = winPtr->parentPtr, j++) {
                    983:                if (winPtr == dispPtr->grabWinPtr) {
                    984:                    goto nextWindow;
                    985:                }
                    986:            }
                    987:            PUSH_EVENT(winPtr, LeaveNotify, NotifyNonlinearVirtual);
                    988:            nextWindow: continue;
                    989:        }
                    990:        PUSH_EVENT(sourcePtr, LeaveNotify, NotifyNonlinear);
                    991:     }
                    992: }
                    993: 
                    994: /*
                    995:  *----------------------------------------------------------------------
                    996:  *
                    997:  * MovePointer2 --
                    998:  *
                    999:  *     This procedure synthesizes  EnterNotify and LeaveNotify events
                   1000:  *     to correctly transfer the pointer from one window to another.
                   1001:  *     It is different from MovePointer in that no template X event
                   1002:  *     needs to be supplied;  this procedure generates the template
                   1003:  *     event and calls MovePointer.
                   1004:  *
                   1005:  * Results:
                   1006:  *     None.
                   1007:  *
                   1008:  * Side effects:
                   1009:  *     Synthesized events may be pushed back onto the event queue.
                   1010:  *
                   1011:  *----------------------------------------------------------------------
                   1012:  */
                   1013: 
                   1014: static void
                   1015: MovePointer2(sourcePtr, destPtr, mode)
                   1016:     TkWindow *sourcePtr;       /* Window currently containing pointer (NULL
                   1017:                                 * means it's not one managed by this
                   1018:                                 * process). */
                   1019:     TkWindow *destPtr;         /* Window that is to end up containing the
                   1020:                                 * pointer (NULL means it's not one managed
                   1021:                                 * by this process). */
                   1022:     int mode;                  /* Mode for enter/leave events, such as
                   1023:                                 * NotifyNormal or NotifyUngrab. */
                   1024: {
                   1025:     XEvent event;
                   1026:     Window dummy1, dummy2;
                   1027:     int dummy3, dummy4;
                   1028:     TkWindow *winPtr;
                   1029: 
                   1030:     winPtr = sourcePtr;
                   1031:     if ((winPtr == NULL) || (winPtr->window == None)) {
                   1032:        winPtr = destPtr;
                   1033:        if ((winPtr == NULL) || (winPtr->window == None)) {
                   1034:            return;
                   1035:        }
                   1036:     }
                   1037: 
                   1038:     event.xcrossing.serial = LastKnownRequestProcessed(winPtr->display);
                   1039:     event.xcrossing.send_event = False;
                   1040:     event.xcrossing.display = winPtr->display;
                   1041:     event.xcrossing.root = RootWindow(winPtr->display, winPtr->screenNum);
                   1042:     event.xcrossing.time = TkCurrentTime(winPtr->dispPtr);
                   1043:     XQueryPointer(winPtr->display, winPtr->window, &dummy1, &dummy2,
                   1044:            &event.xcrossing.x_root, &event.xcrossing.y_root,
                   1045:            &dummy3, &dummy4, &event.xcrossing.state);
                   1046:     event.xcrossing.mode = mode;
                   1047:     event.xcrossing.focus = False;
                   1048:     MovePointer(&event, sourcePtr, destPtr);
                   1049: }
                   1050: 
                   1051: /*
                   1052:  *----------------------------------------------------------------------
                   1053:  *
                   1054:  * TkGrabDeadWindow --
                   1055:  *
                   1056:  *     This procedure is invoked whenever a window is deleted, so that
                   1057:  *     grab-related cleanup can be performed.
                   1058:  *
                   1059:  * Results:
                   1060:  *     None.
                   1061:  *
                   1062:  * Side effects:
                   1063:  *     Various cleanups happen, such as generating events to move the
                   1064:  *     pointer back to its "natural" window as if an ungrab had been
                   1065:  *     done.  See the code.
                   1066:  *
                   1067:  *----------------------------------------------------------------------
                   1068:  */
                   1069: 
                   1070: void
                   1071: TkGrabDeadWindow(winPtr)
                   1072:     register TkWindow *winPtr;         /* Window that is in the process
                   1073:                                         * of being deleted. */
                   1074: {
                   1075:     TkDisplay *dispPtr = winPtr->dispPtr;
                   1076: 
                   1077:     if (dispPtr->grabWinPtr == winPtr) {
                   1078:        dispPtr->grabWinPtr = NULL;
                   1079:        if (!(dispPtr->grabFlags & GRAB_GLOBAL)) {
                   1080:            /*
                   1081:             * Must generate enter/leave events to move back to the window
                   1082:             * that contains the mouse pointer.  We needn't filter events
                   1083:             * here like we do in Tk_Ungrab because there are no children
                   1084:             * of the grab window left in existence.
                   1085:             */
                   1086: 
                   1087:            movePointerBack:
                   1088:            if ((dispPtr->ungrabWinPtr != NULL)
                   1089:                    && (dispPtr->ungrabWinPtr->mainPtr != winPtr->mainPtr)) {
                   1090:                dispPtr->ungrabWinPtr = NULL;
                   1091:            }
                   1092:            MovePointer2(winPtr, dispPtr->ungrabWinPtr, NotifyUngrab);
                   1093:        }
                   1094:     } else if (dispPtr->buttonWinPtr == winPtr) {
                   1095:        /*
                   1096:         * The window in which a button was pressed was deleted.  Simulate
                   1097:         * dropping the button auto-grab by generating Enter and Leave
                   1098:         * events to move the pointer back to the window it's really on
                   1099:         * top of.
                   1100:         */
                   1101: 
                   1102:        dispPtr->buttonWinPtr = NULL;
                   1103:        goto movePointerBack;
                   1104:     }
                   1105:     if (dispPtr->ungrabWinPtr == winPtr) {
                   1106:        dispPtr->ungrabWinPtr = NULL;
                   1107:     }
                   1108:     if (dispPtr->pointerWinPtr == winPtr) {
                   1109:        dispPtr->pointerWinPtr = NULL;
                   1110:     }
                   1111:     if (dispPtr->serverWinPtr == winPtr) {
                   1112:        dispPtr->serverWinPtr = NULL;
                   1113:     }
                   1114: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.