/* * tkUnixNotify.c -- * * This file provides the platform specific event detection * facilities used by the Tk event routines. The procedures in * this file comprise the notifier interface and contain unix * specific file and timer handling code. * * Copyright (c) 1995 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ static char sccsid[] = "@(#) tkUnixNotify.c 1.22 95/10/03 13:51:51"; #include "tkPort.h" #include "tkInt.h" #include /* * The information below is used to provide read, write, and * exception masks to select during calls to Tk_DoOneEvent. */ static fd_mask ready[3*MASK_SIZE]; /* Masks passed to select and modified * by kernel to indicate which files are * actually ready. */ static fd_mask check[3*MASK_SIZE]; /* Current set of masks, built up by * Tk_NotifyFile and Tk_NotifyDisplay, * that reflects what files we should wait * for in the next select. */ static int numFdBits = 0; /* Number of valid bits in mask * arrays (this value is passed * to select). This value is only a high * water mark as long as at least one display * is registered. If there are no displays, * then it is recomputed at the end of each * call to Tk_DoOneEvent. */ /* * For each display that Tk has called Tk_NotifyDisplay, there is one * record of the following type, chained together in a list. */ typedef struct DisplayList { Display *display; /* Display from which events may arrive. */ fd_mask *readPtr; /* Pointer to word in ready array * for this display's read mask bit. */ fd_mask *checkReadPtr; /* Pointer to word in check array * for this display's read mask bit. */ fd_mask bitSelect; /* Value to AND with *readPtr etc. to * select just this file's bit. */ struct DisplayList *nextPtr; /* Next display in list, nor NULL for * end of queue. */ } DisplayList; static DisplayList *firstDisplayPtr; /* First display in list of registered */ /* displays. */ /* * The notifier has at most one pending timer. The next scheduled timer * event is recorded in the pendingTimeout structure. The * pendingTimeoutPtr is set to refer to pendingTimeout by Tk_NotifyTimer. * Once the timer fires, Tk_DoOneEvent resets the pointer to NULL. */ static Tk_Time *pendingTimeoutPtr; /* Points to current timer value. */ static Tk_Time pendingTimeout; /* Time when timer should fire. */ /* * Prototypes for procedures referenced only in this file: */ static int ServiceDisplay _ANSI_ARGS_((Display *display)); static void RefreshFdBits _ANSI_ARGS_((void)); /* *---------------------------------------------------------------------- * * ServiceDisplay -- * * This function is called by Tk_DoOneEvent whenever the notifier * detects that the Display's fd is readable. It is responsible * for retrieving and queueing any pending X events. * * Results: * The return value is 1 if the procedure found an event to * queue. If no events were found, then 0 is returned. * * Side effects: * Passes all pending X events to Tk_QueueEvent. * *---------------------------------------------------------------------- */ static int ServiceDisplay(display) Display *display; /* Display to service. */ { Tk_Event event; int numFound; /* * We should not need to do a flush of the output queues before * calling XEventsQueued because it should have been done before * we called select in Tk_DoOneEvent. */ numFound = XEventsQueued(display, QueuedAfterReading); if (numFound == 0) { /* * Things are very tricky if there aren't any events readable * at this point (after all, there was supposedly data * available on the connection). A couple of things could * have occurred: * * One possibility is that there were only error events in the * input from the server. If this happens, we should return * (we don't want to go to sleep in XNextEvent below, since * this would block out other sources of input to the * process). * * Another possibility is that our connection to the server * has been closed. This will not necessarily be detected in * XEventsQueued (!!), so if we just return then there will be * an infinite loop. To detect such an error, generate a NoOp * protocol request to exercise the connection to the server, * then return. However, must disable SIGPIPE while sending * the request, or else the process will die from the signal * and won't invoke the X error function to print a nice (?!) * message. */ void (*oldHandler)(); oldHandler = (void (*)()) signal(SIGPIPE, SIG_IGN); XNoOp(display); XFlush(display); (void) signal(SIGPIPE, oldHandler); return 0; } /* * Transfer events from the X event queue to the Tk event queue. */ event.type = TK_WINDOW_EVENTS; while (numFound > 0) { XNextEvent(display, &event.window.event); Tk_QueueEvent(&event, TK_QUEUE_TAIL); numFound--; } return 1; } /* *---------------------------------------------------------------------- * * Tk_NotifyFile -- * * Arrange for Tk_QueueEvent to be called the next time a given * I/O channel becomes readable or writable. This is a one-shot * event. * * Results: * None. * * Side effects: * The notifier will generate a file event when the I/O channel * given by fd next becomes ready in the way indicated by mask. * If fd is already registered then the old mask will be replaced * with the new one. Once the event is sent, the notifier will * not send any more events about the fd until the next call to * Tk_NotifyFile. * *---------------------------------------------------------------------- */ void Tk_NotifyFile(fd, mask) int fd; /* Integer identifier for a stream. */ int mask; /* OR'ed combination of TK_READABLE, * TK_WRITABLE, and TK_EXCEPTION: * indicates conditions under which a * new event should be queued. */ { int index; fd_mask bitSelect; if (fd >= FD_SETSIZE) { panic("Tk_NotifyFile can't handle file id %d", fd); } /* * Make sure the appropriate check bits are set for the specified file * descriptor. */ index = fd/(NBBY*sizeof(fd_mask)); bitSelect = 1 << (fd%(NBBY*sizeof(fd_mask))); if (mask & TK_READABLE) { check[index] |= bitSelect; } else { check[index] &= ~bitSelect; } if (mask & TK_WRITABLE) { check[index+MASK_SIZE] |= bitSelect; } else { check[index+MASK_SIZE] &= ~bitSelect; } if (mask & TK_EXCEPTION) { check[index+2*MASK_SIZE] |= bitSelect; } else { check[index+2*MASK_SIZE] &= ~bitSelect; } if (numFdBits <= fd) { numFdBits = fd+1; } } /* *---------------------------------------------------------------------- * * Tk_IgnoreFile -- * * Cancel an outstanding file event request. * * Results: * None. * * Side effects: * The notifier will stop looking for events on the I/O channel * given by fd. * *---------------------------------------------------------------------- */ void Tk_IgnoreFile(fd) int fd; /* Integer identifier for a stream. */ { int index; fd_mask bitSelect; /* * Turn off check bits for the specified file. */ index = fd/(NBBY*sizeof(fd_mask)); bitSelect = 1 << (fd%(NBBY*sizeof(fd_mask))); check[index] &= ~bitSelect; check[index+MASK_SIZE] &= ~bitSelect; check[index+2*MASK_SIZE] &= ~bitSelect; /* * If this fd was at the high water mark and there is a chance * that it was the last fd, then recompute the numFdBits. */ if ((firstDisplayPtr != NULL) && (fd == (numFdBits-1))) { RefreshFdBits(); } } /* *---------------------------------------------------------------------- * * Tk_NotifyDisplay -- * * Arrange for events to be generated for a particular display. * * Results: * None. * * Side effects: * From now on, all window system events directed at display will * be passed to Tk_QueueEvent. * *---------------------------------------------------------------------- */ void Tk_NotifyDisplay(display) Display *display; /* Display for which events should be * generated. */ { int index, fd; DisplayList *displayPtr; /* * Ignore duplicate displays. */ for (displayPtr = firstDisplayPtr; displayPtr != NULL; displayPtr = displayPtr->nextPtr) { if (displayPtr->display == display) { return; } } /* * Add the display to the display list. */ fd = ConnectionNumber(display); displayPtr = (DisplayList *) ckalloc(sizeof(DisplayList)); displayPtr->display = display; index = fd/(NBBY*sizeof(fd_mask)); displayPtr->readPtr = &ready[index]; displayPtr->checkReadPtr = &check[index]; displayPtr->bitSelect = 1 << (fd%(NBBY*sizeof(fd_mask))); displayPtr->nextPtr = firstDisplayPtr; firstDisplayPtr = displayPtr; check[index] |= displayPtr->bitSelect; if (numFdBits <= fd) { numFdBits = fd+1; } } /* *---------------------------------------------------------------------- * * Tk_IgnoreDisplay -- * * Arrange for events to no longer be delivered for a particular * display. * * Results: * None. * * Side effects: * From now on, the notifier will not generate events for a * particular display. * *---------------------------------------------------------------------- */ void Tk_IgnoreDisplay(display) Display *display; /* Display for which events should not * be generated. */ { int index, fd; DisplayList *displayPtr, *prevPtr; fd_mask bitSelect; for (displayPtr = firstDisplayPtr, prevPtr = NULL; displayPtr != NULL; prevPtr = displayPtr, displayPtr = displayPtr->nextPtr) { if (displayPtr->display != display) { continue; } if (prevPtr == NULL) { firstDisplayPtr = displayPtr->nextPtr; } else { prevPtr->nextPtr = displayPtr->nextPtr; } fd = ConnectionNumber(display); index = fd/(NBBY*sizeof(fd_mask)); bitSelect = 1 << (fd%(NBBY*sizeof(fd_mask))); check[index] &= ~bitSelect; ckfree((char *) displayPtr); /* * If this fd was at the high water mark and there is a chance * that it was the last fd, then recompute the numFdBits. */ if ((firstDisplayPtr != NULL) && (fd == (numFdBits-1))) { RefreshFdBits(); } return; } } /* *---------------------------------------------------------------------- * * Tk_NotifyTimer -- * * Arrange for an event to be generated at a particular time in * the future. This is a one-shot event. * * Results: * None. * * Side effects: * When the time specified by timeout is reached, a timer event * will be passed to Tk_QueueEvent. There is only one * outstanding timer, so if the previous timer has not expired, * it will be replaced with the current timeout value. Only one * timer event will be generated after the timer expires until * the next call to Tk_NotifyTimer. * *---------------------------------------------------------------------- */ void Tk_NotifyTimer(time) Tk_Time *time; /* Absolute time when the notifier should * generate a timer event. */ { pendingTimeoutPtr = &pendingTimeout; pendingTimeout = *time; } /* *---------------------------------------------------------------------- * * Tk_DoOneEvent -- * * Process a single event of some sort. If there's no work to * do, wait for an event to occur, then process it. * * Results: * The return value is 1 if the procedure actually found an event * to process. If no processing occurred, then 0 is returned. * The caller should invoke Tk_DoOneEvent repeatedly until it * returns 0 to insure that all events have been processed. * * Side effects: * May delay execution of process while waiting for an event, * unless TK_DONT_WAIT is set in the flags argument. * Tk_ServiceEvent will be called at most once. If no queued * events were processed, then the procedure checks for new * events. If TK_DONT_WAIT is set or Tk has called * Tk_NotifyIdle since the last time Tk_ServiceIdle was called, * then Tk_DoOneEvent will only poll for events, otherwise it * blocks until the next event occurs. All detected events will * be queued. If no events are detected, and Tk has called * Tk_NotifyIdle, then Tk_ServiceIdle will be called. * *---------------------------------------------------------------------- */ int Tk_DoOneEvent(flags) int flags; /* Miscellaneous flag values: may be any * combination of TK_DONT_WAIT, * TK_WINDOW_EVENTS, TK_FILE_EVENTS, * TK_TIMER_EVENTS, and TK_IDLE_EVENTS. */ { DisplayList *displayPtr; struct timeval curTime, timeout, *timeoutPtr; Tk_Event event; fd_mask fdmask, bitSelect; int index, numFound; int foundEvent = 0; int dontLoop; /* Avoid reentering select if set to 1. */ /* * No event flags is equivalent to TK_ALL_EVENTS. */ if ((flags & TK_ALL_EVENTS) == 0) { flags |= TK_ALL_EVENTS; } dontLoop = (flags & TK_DONT_WAIT); /* * Look for events until we find something to do, or no other events * are possible. */ do { /* * The first thing we do is to service any asynchronous event * handlers. */ if (Tcl_AsyncReady()) { (void) Tcl_AsyncInvoke((Tcl_Interp *) NULL, 0); return 1; } /* * Ask Tk to service a queued event. If Tk does not handle any events, * then we should look for new events. */ if (Tk_ServiceEvent(flags)) { return 1; /* Tk serviced an event so we're done. */ } /* * Skip to the idle handlers if that is all we are doing. Since * nothing else can happen, we set dontLoop to 1. */ if ((flags & TK_ALL_EVENTS) == TK_IDLE_EVENTS) { dontLoop = 1; goto doIdle; } /* * First we need to check for X events which are already queued. */ if ((flags & TK_WINDOW_EVENTS)) { for (displayPtr = firstDisplayPtr; displayPtr != NULL; displayPtr = displayPtr->nextPtr) { XFlush(displayPtr->display); if (XQLength(displayPtr->display) > 0) { if (ServiceDisplay(displayPtr->display)) { foundEvent = 1; } } } } /* * Load the select mask from the current check mask. */ memcpy((VOID *) ready, (VOID *) check, 3*MASK_SIZE*sizeof(fd_mask)); /* * Compute the current timeout value. If we have already detected an * event, if idle tasks are pending or if TK_DONT_WAIT is set, then we * should poll. If a timer is pending, then we should compute the * remaining time. Otherwise, we block. */ if (foundEvent || (flags & TK_DONT_WAIT) || ((flags & TK_IDLE_EVENTS) && Tk_IdlePending())) { timeout.tv_sec = 0; timeout.tv_usec = 0; timeoutPtr = &timeout; } else if ((pendingTimeoutPtr != NULL) && (flags & TK_TIMER_EVENTS)) { (void) gettimeofday(&curTime, (struct timezone *) NULL); /* * Check to see if the timer has expired. */ if ((pendingTimeoutPtr->sec < curTime.tv_sec) || ((pendingTimeoutPtr->sec == curTime.tv_sec) && (pendingTimeoutPtr->usec < curTime.tv_usec))) { timeout.tv_sec = 0; timeout.tv_usec = 0; } else { timeout.tv_sec = pendingTimeoutPtr->sec - curTime.tv_sec; timeout.tv_usec = pendingTimeoutPtr->usec - curTime.tv_usec; if (timeout.tv_usec < 0) { timeout.tv_sec -= 1; timeout.tv_usec += 1000000; } } timeoutPtr = &timeout; } else { timeoutPtr = NULL; } /* * If no events could be detected, return immediately to * avoid blocking forever. */ if ((timeoutPtr == NULL) && (numFdBits == 0)) { return 0; } /* * Wait until an event occurs or the timer expires. */ numFound = select(numFdBits, (SELECT_MASK *) &ready[0], (SELECT_MASK *) &ready[MASK_SIZE], (SELECT_MASK *) &ready[2*MASK_SIZE], timeoutPtr); /* * Some systems don't clear the masks after an error, so * we have to do it here. */ if (numFound == -1) { memset((VOID *) ready, 0, 3*MASK_SIZE*sizeof(fd_mask)); } /* * Check to see if the timer has expired. */ if ((pendingTimeoutPtr != NULL) && (flags & TK_TIMER_EVENTS)) { (void) gettimeofday(&curTime, (struct timezone *) NULL); if ((pendingTimeoutPtr->sec < curTime.tv_sec) || ((pendingTimeoutPtr->sec == curTime.tv_sec) && (pendingTimeoutPtr->usec < curTime.tv_usec))) { pendingTimeoutPtr = NULL; /* * Generate timer event. */ foundEvent = 1; event.type = TK_TIMER_EVENTS; event.timer.time.sec = curTime.tv_sec; event.timer.time.usec = curTime.tv_usec; Tk_QueueEvent(&event, TK_QUEUE_TAIL); } } /* * Check for window system events. Once a display has been serviced, * it is removed from the ready mask, so we don't generate a file event * for it. */ for (displayPtr = firstDisplayPtr; displayPtr != NULL; displayPtr = displayPtr->nextPtr) { if (*displayPtr->readPtr & displayPtr->bitSelect) { if (ServiceDisplay(displayPtr->display)) { foundEvent = 1; } numFound--; *displayPtr->readPtr &= ~(displayPtr->bitSelect); } } /* * Check for file events. We iterate over the ready fd_masks looking * for a mask with any bits set. By or'ing the read, write, and * exception masks together, we can make a single pass through the file * descriptors. If any event occured on a file, we need to queue a * file event with the appropriate file descriptor and event mask, and * then clear all of the check bits for that file. */ if (numFound > 0) { foundEvent = 1; event.type = TK_FILE_EVENTS; index = -1; bitSelect = 0; event.file.fd = 0; fdmask = 0; /* * Each time through the loop we shift bitSelect. When we * have tested each bit in the current mask, bitSelect becomes * zero, so we know to continue with the next mask in the set. */ while (numFound > 0) { if (!bitSelect) { /* check for wrap-around */ if (event.file.fd >= numFdBits) { /* * Somehow there aren't as many files ready as numFound * suggests. Either select lied to us or there is a * reentrancy problem here. */ panic("Tk_DoOneEvent got bogus count from select"); } bitSelect = 1; index++; fdmask = ready[index] | ready[index+MASK_SIZE] | ready[index+2*MASK_SIZE]; } if (fdmask & bitSelect) { numFound--; event.file.mask = 0; if (ready[index] & bitSelect) { event.file.mask |= TK_READABLE; } if (ready[index+MASK_SIZE] & bitSelect) { event.file.mask |= TK_WRITABLE; } if (ready[index+2*MASK_SIZE] & bitSelect) { event.file.mask |= TK_EXCEPTION; } Tk_QueueEvent(&event, TK_QUEUE_TAIL); check[index] &= ~bitSelect; check[index+MASK_SIZE] &= ~bitSelect; check[index+2*MASK_SIZE] &= ~bitSelect; } bitSelect <<= 1; event.file.fd++; } /* * If there are no displays currently registered, then we need to * recompute numFdBits since some bits have been cleared and we may * block the next time through. */ if (firstDisplayPtr == NULL) { RefreshFdBits(); } } /* * If no other events were generated, then we should try to * service pending idle handlers. */ doIdle: if (!foundEvent && (flags & TK_IDLE_EVENTS)) { foundEvent = Tk_ServiceIdle(); } } while (!foundEvent && !dontLoop); /* * No other events can be detected at this point. */ return foundEvent; } /* *---------------------------------------------------------------------- * * Tk_Sleep -- * * Delay execution for the specified number of milliseconds. * * Results: * None. * * Side effects: * Time passes. * *---------------------------------------------------------------------- */ void Tk_Sleep(ms) int ms; /* Number of milliseconds to sleep. */ { static struct timeval delay; delay.tv_sec = ms/1000; delay.tv_usec = (ms%1000)*1000; (void) select(0, (SELECT_MASK *) 0, (SELECT_MASK *) 0, (SELECT_MASK *) 0, &delay); } /* *-------------------------------------------------------------- * * Tk_MainLoop -- * * Call Tk_DoOneEvent over and over again in an infinite * loop as long as there exist any main windows. * * Results: * None. * * Side effects: * Arbitrary; depends on handlers for events. * *-------------------------------------------------------------- */ void Tk_MainLoop() { while (Tk_GetNumMainWindows() > 0) { Tk_DoOneEvent(0); } } /* *---------------------------------------------------------------------- * * RefreshFdBits -- * * This function finds the largest currently registered * file descriptor from the check mask. * * Results: * None. * * Side effects: * Updates the numFdBits static variable. * *---------------------------------------------------------------------- */ static void RefreshFdBits() { int limit, index; fd_mask fdmask; /* * We start by finding the largest element of the check array that * contains at least one set bit. We start the search with the * last known maximum index value. */ limit = (numFdBits / (NBBY*sizeof(fd_mask))); numFdBits = 0; fdmask = 0; for (index = limit; (index >= 0); index--) { fdmask = check[index] | check[index+MASK_SIZE] | check[index+2*MASK_SIZE]; if (fdmask) { break; } } /* * Now that we have the biggest mask, we need to find the most * significant bit. */ if (fdmask) { int offset; numFdBits = index*NBBY*sizeof(fd_mask); for (offset = 0; ; offset++) { fdmask &= ~(1<