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