1 /*
2  * tclXtNotify.c --
3  *
4  *	This file contains the notifier driver implementation for the Xt
5  *	intrinsics.
6  *
7  * Copyright (c) 1997 by Sun Microsystems, Inc.
8  *
9  * See the file "license.terms" for information on usage and redistribution of
10  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
11  */
12 
13 #ifndef USE_TCL_STUBS
14 #   define USE_TCL_STUBS
15 #endif
16 #include <X11/Intrinsic.h>
17 #include "tclInt.h"
18 
19 /*
20  * This structure is used to keep track of the notifier info for a a
21  * registered file.
22  */
23 
24 typedef struct FileHandler {
25     int fd;
26     int mask;			/* Mask of desired events: TCL_READABLE,
27 				 * etc. */
28     int readyMask;		/* Events that have been seen since the last
29 				 * time FileHandlerEventProc was called for
30 				 * this file. */
31     XtInputId read;		/* Xt read callback handle. */
32     XtInputId write;		/* Xt write callback handle. */
33     XtInputId except;		/* Xt exception callback handle. */
34     Tcl_FileProc *proc;		/* Procedure to call, in the style of
35 				 * Tcl_CreateFileHandler. */
36     ClientData clientData;	/* Argument to pass to proc. */
37     struct FileHandler *nextPtr;/* Next in list of all files we care about. */
38 } FileHandler;
39 
40 /*
41  * The following structure is what is added to the Tcl event queue when file
42  * handlers are ready to fire.
43  */
44 
45 typedef struct FileHandlerEvent {
46     Tcl_Event header;		/* Information that is standard for all
47 				 * events. */
48     int fd;			/* File descriptor that is ready. Used to find
49 				 * the FileHandler structure for the file
50 				 * (can't point directly to the FileHandler
51 				 * structure because it could go away while
52 				 * the event is queued). */
53 } FileHandlerEvent;
54 
55 /*
56  * The following static structure contains the state information for the Xt
57  * based implementation of the Tcl notifier.
58  */
59 
60 static struct NotifierState {
61     XtAppContext appContext;	/* The context used by the Xt notifier. Can be
62 				 * set with TclSetAppContext. */
63     int appContextCreated;	/* Was it created by us? */
64     XtIntervalId currentTimeout;/* Handle of current timer. */
65     FileHandler *firstFileHandlerPtr;
66 				/* Pointer to head of file handler list. */
67 } notifier;
68 
69 /*
70  * The following static indicates whether this module has been initialized.
71  */
72 
73 static int initialized = 0;
74 
75 /*
76  * Static routines defined in this file.
77  */
78 
79 static int		FileHandlerEventProc(Tcl_Event *evPtr, int flags);
80 static void		FileProc(XtPointer clientData, int *source,
81 			    XtInputId *id);
82 static void		NotifierExitHandler(ClientData clientData);
83 static void		TimerProc(XtPointer clientData, XtIntervalId *id);
84 static void		CreateFileHandler(int fd, int mask,
85 			    Tcl_FileProc *proc, ClientData clientData);
86 static void		DeleteFileHandler(int fd);
87 static void		SetTimer(const Tcl_Time * timePtr);
88 static int		WaitForEvent(const Tcl_Time * timePtr);
89 
90 /*
91  * Functions defined in this file for use by users of the Xt Notifier:
92  */
93 
94 MODULE_SCOPE void InitNotifier(void);
95 MODULE_SCOPE XtAppContext TclSetAppContext(XtAppContext ctx);
96 
97 /*
98  *----------------------------------------------------------------------
99  *
100  * TclSetAppContext --
101  *
102  *	Set the notifier application context.
103  *
104  * Results:
105  *	None.
106  *
107  * Side effects:
108  *	Sets the application context used by the notifier. Panics if the
109  *	context is already set when called.
110  *
111  *----------------------------------------------------------------------
112  */
113 
114 XtAppContext
TclSetAppContext(XtAppContext appContext)115 TclSetAppContext(
116     XtAppContext appContext)
117 {
118     if (!initialized) {
119 	InitNotifier();
120     }
121 
122     /*
123      * If we already have a context we check whether we were asked to set a
124      * new context. If so, we panic because we try to prevent switching
125      * contexts by mistake. Otherwise, we return the one we have.
126      */
127 
128     if (notifier.appContext != NULL) {
129 	if (appContext != NULL) {
130 	    /*
131 	     * We already have a context. We do not allow switching contexts
132 	     * after initialization, so we panic.
133 	     */
134 
135 	    Tcl_Panic("TclSetAppContext:  multiple application contexts");
136 	}
137     } else {
138 	/*
139 	 * If we get here we have not yet gotten a context, so either create
140 	 * one or use the one supplied by our caller.
141 	 */
142 
143 	if (appContext == NULL) {
144 	    /*
145 	     * We must create a new context and tell our caller what it is, so
146 	     * she can use it too.
147 	     */
148 
149 	    notifier.appContext = XtCreateApplicationContext();
150 	    notifier.appContextCreated = 1;
151 	} else {
152 	    /*
153 	     * Otherwise we remember the context that our caller gave us and
154 	     * use it.
155 	     */
156 
157 	    notifier.appContextCreated = 0;
158 	    notifier.appContext = appContext;
159 	}
160     }
161 
162     return notifier.appContext;
163 }
164 
165 /*
166  *----------------------------------------------------------------------
167  *
168  * InitNotifier --
169  *
170  *	Initializes the notifier state.
171  *
172  * Results:
173  *	None.
174  *
175  * Side effects:
176  *	Creates a new exit handler.
177  *
178  *----------------------------------------------------------------------
179  */
180 
181 void
InitNotifier(void)182 InitNotifier(void)
183 {
184     Tcl_NotifierProcs np;
185 
186     /*
187      * Only reinitialize if we are not in exit handling. The notifier can get
188      * reinitialized after its own exit handler has run, because of exit
189      * handlers for the I/O and timer sub-systems (order dependency).
190      */
191 
192     if (TclInExit()) {
193 	return;
194     }
195 
196     np.createFileHandlerProc = CreateFileHandler;
197     np.deleteFileHandlerProc = DeleteFileHandler;
198     np.setTimerProc = SetTimer;
199     np.waitForEventProc = WaitForEvent;
200     np.initNotifierProc = Tcl_InitNotifier;
201     np.finalizeNotifierProc = Tcl_FinalizeNotifier;
202     np.alertNotifierProc = Tcl_AlertNotifier;
203     np.serviceModeHookProc = Tcl_ServiceModeHook;
204     Tcl_SetNotifier(&np);
205 
206     /*
207      * DO NOT create the application context yet; doing so would prevent
208      * external applications from setting it for us to their own ones.
209      */
210 
211     initialized = 1;
212     memset(&np, 0, sizeof(np));
213     Tcl_CreateExitHandler(NotifierExitHandler, NULL);
214 }
215 
216 /*
217  *----------------------------------------------------------------------
218  *
219  * NotifierExitHandler --
220  *
221  *	This function is called to cleanup the notifier state before Tcl is
222  *	unloaded.
223  *
224  * Results:
225  *	None.
226  *
227  * Side effects:
228  *	Destroys the notifier window.
229  *
230  *----------------------------------------------------------------------
231  */
232 
233 static void
NotifierExitHandler(ClientData clientData)234 NotifierExitHandler(
235     ClientData clientData)	/* Not used. */
236 {
237     if (notifier.currentTimeout != 0) {
238 	XtRemoveTimeOut(notifier.currentTimeout);
239     }
240     for (; notifier.firstFileHandlerPtr != NULL; ) {
241 	Tcl_DeleteFileHandler(notifier.firstFileHandlerPtr->fd);
242     }
243     if (notifier.appContextCreated) {
244 	XtDestroyApplicationContext(notifier.appContext);
245 	notifier.appContextCreated = 0;
246 	notifier.appContext = NULL;
247     }
248     initialized = 0;
249 }
250 
251 /*
252  *----------------------------------------------------------------------
253  *
254  * SetTimer --
255  *
256  *	This procedure sets the current notifier timeout value.
257  *
258  * Results:
259  *	None.
260  *
261  * Side effects:
262  *	Replaces any previous timer.
263  *
264  *----------------------------------------------------------------------
265  */
266 
267 static void
SetTimer(const Tcl_Time * timePtr)268 SetTimer(
269     const Tcl_Time *timePtr)		/* Timeout value, may be NULL. */
270 {
271     long timeout;
272 
273     if (!initialized) {
274 	InitNotifier();
275     }
276 
277     TclSetAppContext(NULL);
278     if (notifier.currentTimeout != 0) {
279 	XtRemoveTimeOut(notifier.currentTimeout);
280     }
281     if (timePtr) {
282 	timeout = timePtr->sec * 1000 + timePtr->usec / 1000;
283 	notifier.currentTimeout = XtAppAddTimeOut(notifier.appContext,
284 		(unsigned long) timeout, TimerProc, NULL);
285     } else {
286 	notifier.currentTimeout = 0;
287     }
288 }
289 
290 /*
291  *----------------------------------------------------------------------
292  *
293  * TimerProc --
294  *
295  *	This procedure is the XtTimerCallbackProc used to handle timeouts.
296  *
297  * Results:
298  *	None.
299  *
300  * Side effects:
301  *	Processes all queued events.
302  *
303  *----------------------------------------------------------------------
304  */
305 
306 static void
TimerProc(XtPointer clientData,XtIntervalId * id)307 TimerProc(
308     XtPointer clientData, /* Not used. */
309     XtIntervalId *id)
310 {
311     if (*id != notifier.currentTimeout) {
312 	return;
313     }
314     notifier.currentTimeout = 0;
315 
316     Tcl_ServiceAll();
317 }
318 
319 /*
320  *----------------------------------------------------------------------
321  *
322  * CreateFileHandler --
323  *
324  *	This procedure registers a file handler with the Xt notifier.
325  *
326  * Results:
327  *	None.
328  *
329  * Side effects:
330  *	Creates a new file handler structure and registers one or more input
331  *	procedures with Xt.
332  *
333  *----------------------------------------------------------------------
334  */
335 
336 static void
CreateFileHandler(int fd,int mask,Tcl_FileProc * proc,ClientData clientData)337 CreateFileHandler(
338     int fd,			/* Handle of stream to watch. */
339     int mask,			/* OR'ed combination of TCL_READABLE,
340 				 * TCL_WRITABLE, and TCL_EXCEPTION: indicates
341 				 * conditions under which proc should be
342 				 * called. */
343     Tcl_FileProc *proc,		/* Procedure to call for each selected
344 				 * event. */
345     ClientData clientData)	/* Arbitrary data to pass to proc. */
346 {
347     FileHandler *filePtr;
348 
349     if (!initialized) {
350 	InitNotifier();
351     }
352 
353     TclSetAppContext(NULL);
354 
355     for (filePtr = notifier.firstFileHandlerPtr; filePtr != NULL;
356 	    filePtr = filePtr->nextPtr) {
357 	if (filePtr->fd == fd) {
358 	    break;
359 	}
360     }
361     if (filePtr == NULL) {
362 	filePtr = ckalloc(sizeof(FileHandler));
363 	filePtr->fd = fd;
364 	filePtr->read = 0;
365 	filePtr->write = 0;
366 	filePtr->except = 0;
367 	filePtr->readyMask = 0;
368 	filePtr->mask = 0;
369 	filePtr->nextPtr = notifier.firstFileHandlerPtr;
370 	notifier.firstFileHandlerPtr = filePtr;
371     }
372     filePtr->proc = proc;
373     filePtr->clientData = clientData;
374 
375     /*
376      * Register the file with the Xt notifier, if it hasn't been done yet.
377      */
378 
379     if (mask & TCL_READABLE) {
380 	if (!(filePtr->mask & TCL_READABLE)) {
381 	    filePtr->read = XtAppAddInput(notifier.appContext, fd,
382 		    INT2PTR(XtInputReadMask), FileProc, filePtr);
383 	}
384     } else {
385 	if (filePtr->mask & TCL_READABLE) {
386 	    XtRemoveInput(filePtr->read);
387 	}
388     }
389     if (mask & TCL_WRITABLE) {
390 	if (!(filePtr->mask & TCL_WRITABLE)) {
391 	    filePtr->write = XtAppAddInput(notifier.appContext, fd,
392 		    INT2PTR(XtInputWriteMask), FileProc, filePtr);
393 	}
394     } else {
395 	if (filePtr->mask & TCL_WRITABLE) {
396 	    XtRemoveInput(filePtr->write);
397 	}
398     }
399     if (mask & TCL_EXCEPTION) {
400 	if (!(filePtr->mask & TCL_EXCEPTION)) {
401 	    filePtr->except = XtAppAddInput(notifier.appContext, fd,
402 		    INT2PTR(XtInputExceptMask), FileProc, filePtr);
403 	}
404     } else {
405 	if (filePtr->mask & TCL_EXCEPTION) {
406 	    XtRemoveInput(filePtr->except);
407 	}
408     }
409     filePtr->mask = mask;
410 }
411 
412 /*
413  *----------------------------------------------------------------------
414  *
415  * DeleteFileHandler --
416  *
417  *	Cancel a previously-arranged callback arrangement for a file.
418  *
419  * Results:
420  *	None.
421  *
422  * Side effects:
423  *	If a callback was previously registered on file, remove it.
424  *
425  *----------------------------------------------------------------------
426  */
427 
428 static void
DeleteFileHandler(int fd)429 DeleteFileHandler(
430     int fd)			/* Stream id for which to remove callback
431 				 * procedure. */
432 {
433     FileHandler *filePtr, *prevPtr;
434 
435     if (!initialized) {
436 	InitNotifier();
437     }
438 
439     TclSetAppContext(NULL);
440 
441     /*
442      * Find the entry for the given file (and return if there isn't one).
443      */
444 
445     for (prevPtr = NULL, filePtr = notifier.firstFileHandlerPtr; ;
446 	    prevPtr = filePtr, filePtr = filePtr->nextPtr) {
447 	if (filePtr == NULL) {
448 	    return;
449 	}
450 	if (filePtr->fd == fd) {
451 	    break;
452 	}
453     }
454 
455     /*
456      * Clean up information in the callback record.
457      */
458 
459     if (prevPtr == NULL) {
460 	notifier.firstFileHandlerPtr = filePtr->nextPtr;
461     } else {
462 	prevPtr->nextPtr = filePtr->nextPtr;
463     }
464     if (filePtr->mask & TCL_READABLE) {
465 	XtRemoveInput(filePtr->read);
466     }
467     if (filePtr->mask & TCL_WRITABLE) {
468 	XtRemoveInput(filePtr->write);
469     }
470     if (filePtr->mask & TCL_EXCEPTION) {
471 	XtRemoveInput(filePtr->except);
472     }
473     ckfree(filePtr);
474 }
475 
476 /*
477  *----------------------------------------------------------------------
478  *
479  * FileProc --
480  *
481  *	These procedures are called by Xt when a file becomes readable,
482  *	writable, or has an exception.
483  *
484  * Results:
485  *	None.
486  *
487  * Side effects:
488  *	Makes an entry on the Tcl event queue if the event is interesting.
489  *
490  *----------------------------------------------------------------------
491  */
492 
493 static void
FileProc(XtPointer clientData,int * fd,XtInputId * id)494 FileProc(
495     XtPointer clientData,
496     int *fd,
497     XtInputId *id)
498 {
499     FileHandler *filePtr = (FileHandler *)clientData;
500     FileHandlerEvent *fileEvPtr;
501     int mask = 0;
502 
503     /*
504      * Determine which event happened.
505      */
506 
507     if (*id == filePtr->read) {
508 	mask = TCL_READABLE;
509     } else if (*id == filePtr->write) {
510 	mask = TCL_WRITABLE;
511     } else if (*id == filePtr->except) {
512 	mask = TCL_EXCEPTION;
513     }
514 
515     /*
516      * Ignore unwanted or duplicate events.
517      */
518 
519     if (!(filePtr->mask & mask) || (filePtr->readyMask & mask)) {
520 	return;
521     }
522 
523     /*
524      * This is an interesting event, so put it onto the event queue.
525      */
526 
527     filePtr->readyMask |= mask;
528     fileEvPtr = ckalloc(sizeof(FileHandlerEvent));
529     fileEvPtr->header.proc = FileHandlerEventProc;
530     fileEvPtr->fd = filePtr->fd;
531     Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL);
532 
533     /*
534      * Process events on the Tcl event queue before returning to Xt.
535      */
536 
537     Tcl_ServiceAll();
538 }
539 
540 /*
541  *----------------------------------------------------------------------
542  *
543  * FileHandlerEventProc --
544  *
545  *	This procedure is called by Tcl_ServiceEvent when a file event reaches
546  *	the front of the event queue. This procedure is responsible for
547  *	actually handling the event by invoking the callback for the file
548  *	handler.
549  *
550  * Results:
551  *	Returns 1 if the event was handled, meaning it should be removed from
552  *	the queue. Returns 0 if the event was not handled, meaning it should
553  *	stay on the queue. The only time the event isn't handled is if the
554  *	TCL_FILE_EVENTS flag bit isn't set.
555  *
556  * Side effects:
557  *	Whatever the file handler's callback procedure does.
558  *
559  *----------------------------------------------------------------------
560  */
561 
562 static int
FileHandlerEventProc(Tcl_Event * evPtr,int flags)563 FileHandlerEventProc(
564     Tcl_Event *evPtr,		/* Event to service. */
565     int flags)			/* Flags that indicate what events to handle,
566 				 * such as TCL_FILE_EVENTS. */
567 {
568     FileHandler *filePtr;
569     FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) evPtr;
570     int mask;
571 
572     if (!(flags & TCL_FILE_EVENTS)) {
573 	return 0;
574     }
575 
576     /*
577      * Search through the file handlers to find the one whose handle matches
578      * the event. We do this rather than keeping a pointer to the file handler
579      * directly in the event, so that the handler can be deleted while the
580      * event is queued without leaving a dangling pointer.
581      */
582 
583     for (filePtr = notifier.firstFileHandlerPtr; filePtr != NULL;
584 	    filePtr = filePtr->nextPtr) {
585 	if (filePtr->fd != fileEvPtr->fd) {
586 	    continue;
587 	}
588 
589 	/*
590 	 * The code is tricky for two reasons:
591 	 * 1. The file handler's desired events could have changed since the
592 	 *    time when the event was queued, so AND the ready mask with the
593 	 *    desired mask.
594 	 * 2. The file could have been closed and re-opened since the time
595 	 *    when the event was queued. This is why the ready mask is stored
596 	 *    in the file handler rather than the queued event: it will be
597 	 *    zeroed when a new file handler is created for the newly opened
598 	 *    file.
599 	 */
600 
601 	mask = filePtr->readyMask & filePtr->mask;
602 	filePtr->readyMask = 0;
603 	if (mask != 0) {
604 	    filePtr->proc(filePtr->clientData, mask);
605 	}
606 	break;
607     }
608     return 1;
609 }
610 
611 /*
612  *----------------------------------------------------------------------
613  *
614  * WaitForEvent --
615  *
616  *	This function is called by Tcl_DoOneEvent to wait for new events on
617  *	the message queue. If the block time is 0, then Tcl_WaitForEvent just
618  *	polls without blocking.
619  *
620  * Results:
621  *	Returns 1 if an event was found, else 0. This ensures that
622  *	Tcl_DoOneEvent will return 1, even if the event is handled by non-Tcl
623  *	code.
624  *
625  * Side effects:
626  *	Queues file events that are detected by the select.
627  *
628  *----------------------------------------------------------------------
629  */
630 
631 static int
WaitForEvent(const Tcl_Time * timePtr)632 WaitForEvent(
633     const Tcl_Time *timePtr)		/* Maximum block time, or NULL. */
634 {
635     int timeout;
636 
637     if (!initialized) {
638 	InitNotifier();
639     }
640 
641     TclSetAppContext(NULL);
642 
643     if (timePtr) {
644 	timeout = timePtr->sec * 1000 + timePtr->usec / 1000;
645 	if (timeout == 0) {
646 	    if (XtAppPending(notifier.appContext)) {
647 		goto process;
648 	    } else {
649 		return 0;
650 	    }
651 	} else {
652 	    Tcl_SetTimer(timePtr);
653 	}
654     }
655 
656   process:
657     XtAppProcessEvent(notifier.appContext, XtIMAll);
658     return 1;
659 }
660 
661 /*
662  * Local Variables:
663  * mode: c
664  * c-basic-offset: 4
665  * fill-column: 78
666  * End:
667  */
668