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