1/*
2 * tkError.c --
3 *
4 *	This file provides a high-performance mechanism for
5 *	selectively dealing with errors that occur in talking
6 *	to the X server.  This is useful, for example, when
7 *	communicating with a window that may not exist.
8 *
9 * Copyright (c) 1990-1994 The Regents of the University of California.
10 * Copyright (c) 1994-1995 Sun Microsystems, Inc.
11 *
12 * See the file "license.terms" for information on usage and redistribution
13 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14 *
15 * RCS: @(#) $Id: tkError.c,v 1.2 1998/09/14 18:23:09 stanton Exp $
16 */
17
18#include "tkPort.h"
19#include "tkInt.h"
20
21/*
22 * The default X error handler gets saved here, so that it can
23 * be invoked if an error occurs that we can't handle.
24 */
25
26static int	(*defaultHandler) _ANSI_ARGS_((Display *display,
27		    XErrorEvent *eventPtr)) = NULL;
28
29
30/*
31 * Forward references to procedures declared later in this file:
32 */
33
34static int	ErrorProc _ANSI_ARGS_((Display *display,
35		    XErrorEvent *errEventPtr));
36
37/*
38 *--------------------------------------------------------------
39 *
40 * Tk_CreateErrorHandler --
41 *
42 *	Arrange for all a given procedure to be invoked whenever
43 *	certain errors occur.
44 *
45 * Results:
46 *	The return value is a token identifying the handler;
47 *	it must be passed to Tk_DeleteErrorHandler to delete the
48 *	handler.
49 *
50 * Side effects:
51 *	If an X error occurs that matches the error, request,
52 *	and minor arguments, then errorProc will be invoked.
53 *	ErrorProc should have the following structure:
54 *
55 *	int
56 *	errorProc(clientData, errorEventPtr)
57 *	    caddr_t clientData;
58 *	    XErrorEvent *errorEventPtr;
59 *	{
60 *	}
61 *
62 *	The clientData argument will be the same as the clientData
63 *	argument to this procedure, and errorEvent will describe
64 *	the error.  If errorProc returns 0, it means that it
65 *	completely "handled" the error:  no further processing
66 *	should be done.  If errorProc returns 1, it means that it
67 *	didn't know how to deal with the error, so we should look
68 *	for other error handlers, or invoke the default error
69 *	handler if no other handler returns zero.  Handlers are
70 *	invoked in order of age:  youngest handler first.
71 *
72 *	Note:  errorProc will only be called for errors associated
73 *	with X requests made AFTER this call, but BEFORE the handler
74 *	is deleted by calling Tk_DeleteErrorHandler.
75 *
76 *--------------------------------------------------------------
77 */
78
79Tk_ErrorHandler
80Tk_CreateErrorHandler(display, error, request, minorCode, errorProc, clientData)
81    Display *display;		/* Display for which to handle
82				 * errors. */
83    int error;			/* Consider only errors with this
84				 * error_code (-1 means consider
85				 * all errors). */
86    int request;		/* Consider only errors with this
87				 * major request code (-1 means
88				 * consider all major codes). */
89    int minorCode;		/* Consider only errors with this
90				 * minor request code (-1 means
91				 * consider all minor codes). */
92    Tk_ErrorProc *errorProc;	/* Procedure to invoke when a
93				 * matching error occurs.  NULL means
94				 * just ignore matching errors. */
95    ClientData clientData;	/* Arbitrary value to pass to
96				 * errorProc. */
97{
98    register TkErrorHandler *errorPtr;
99    register TkDisplay *dispPtr;
100
101    /*
102     * Find the display.  If Tk doesn't know about this display then
103     * it's an error:  panic.
104     */
105
106    dispPtr = TkGetDisplay(display);
107    if (dispPtr == NULL) {
108	panic("Unknown display passed to Tk_CreateErrorHandler");
109    }
110
111    /*
112     * Make sure that X calls us whenever errors occur.
113     */
114
115    if (defaultHandler == NULL) {
116	defaultHandler = XSetErrorHandler(ErrorProc);
117    }
118
119    /*
120     * Create the handler record.
121     */
122
123    errorPtr = (TkErrorHandler *) ckalloc(sizeof(TkErrorHandler));
124    errorPtr->dispPtr = dispPtr;
125    errorPtr->firstRequest = NextRequest(display);
126    errorPtr->lastRequest = (unsigned) -1;
127    errorPtr->error = error;
128    errorPtr->request = request;
129    errorPtr->minorCode = minorCode;
130    errorPtr->errorProc = errorProc;
131    errorPtr->clientData = clientData;
132    errorPtr->nextPtr = dispPtr->errorPtr;
133    dispPtr->errorPtr = errorPtr;
134
135    return (Tk_ErrorHandler) errorPtr;
136}
137
138/*
139 *--------------------------------------------------------------
140 *
141 * Tk_DeleteErrorHandler --
142 *
143 *	Do not use an error handler anymore.
144 *
145 * Results:
146 *	None.
147 *
148 * Side effects:
149 *	The handler denoted by the "handler" argument will not
150 *	be invoked for any X errors associated with requests
151 *	made after this call.  However, if errors arrive later
152 *	for requests made BEFORE this call, then the handler
153 *	will still be invoked.  Call XSync if you want to be
154 *	sure that all outstanding errors have been received
155 *	and processed.
156 *
157 *--------------------------------------------------------------
158 */
159
160void
161Tk_DeleteErrorHandler(handler)
162    Tk_ErrorHandler handler;	/* Token for handler to delete;
163				 * was previous return value from
164				 * Tk_CreateErrorHandler. */
165{
166    register TkErrorHandler *errorPtr = (TkErrorHandler *) handler;
167    register TkDisplay *dispPtr = errorPtr->dispPtr;
168
169    errorPtr->lastRequest = NextRequest(dispPtr->display) - 1;
170
171    /*
172     * Every once-in-a-while, cleanup handlers that are no longer
173     * active.  We probably won't be able to free the handler that
174     * was just deleted (need to wait for any outstanding requests to
175     * be processed by server), but there may be previously-deleted
176     * handlers that are now ready for garbage collection.  To reduce
177     * the cost of the cleanup, let a few dead handlers pile up, then
178     * clean them all at once.  This adds a bit of overhead to errors
179     * that might occur while the dead handlers are hanging around,
180     * but reduces the overhead of scanning the list to clean up
181     * (particularly if there are many handlers that stay around
182     * forever).
183     */
184
185    dispPtr->deleteCount += 1;
186    if (dispPtr->deleteCount >= 10) {
187	register TkErrorHandler *prevPtr;
188	TkErrorHandler *nextPtr;
189	int lastSerial;
190
191	dispPtr->deleteCount = 0;
192	lastSerial = LastKnownRequestProcessed(dispPtr->display);
193	errorPtr = dispPtr->errorPtr;
194	for (prevPtr = NULL; errorPtr != NULL; errorPtr = nextPtr) {
195	    nextPtr = errorPtr->nextPtr;
196	    if ((errorPtr->lastRequest != (unsigned long) -1)
197		    && (errorPtr->lastRequest <= (unsigned long) lastSerial)) {
198		if (prevPtr == NULL) {
199		    dispPtr->errorPtr = nextPtr;
200		} else {
201		    prevPtr->nextPtr = nextPtr;
202		}
203		ckfree((char *) errorPtr);
204		continue;
205	    }
206	    prevPtr = errorPtr;
207	}
208    }
209}
210
211/*
212 *--------------------------------------------------------------
213 *
214 * ErrorProc --
215 *
216 *	This procedure is invoked by the X system when error
217 *	events arrive.
218 *
219 * Results:
220 *	If it returns, the return value is zero.  However,
221 *	it is possible that one of the error handlers may
222 *	just exit.
223 *
224 * Side effects:
225 *	This procedure does two things.  First, it uses the
226 *	serial #  in the error event to eliminate handlers whose
227 *	expiration serials are now in the past.  Second, it
228 *	invokes any handlers that want to deal with the error.
229 *
230 *--------------------------------------------------------------
231 */
232
233static int
234ErrorProc(display, errEventPtr)
235    Display *display;			/* Display for which error
236					 * occurred. */
237    register XErrorEvent *errEventPtr;	/* Information about error. */
238{
239    register TkDisplay *dispPtr;
240    register TkErrorHandler *errorPtr;
241
242    /*
243     * See if we know anything about the display.  If not, then
244     * invoke the default error handler.
245     */
246
247    dispPtr = TkGetDisplay(display);
248    if (dispPtr == NULL) {
249	goto couldntHandle;
250    }
251
252    /*
253     * Otherwise invoke any relevant handlers for the error, in order.
254     */
255
256    for (errorPtr = dispPtr->errorPtr; errorPtr != NULL;
257	    errorPtr = errorPtr->nextPtr) {
258	if ((errorPtr->firstRequest > errEventPtr->serial)
259		|| ((errorPtr->error != -1)
260		    && (errorPtr->error != errEventPtr->error_code))
261		|| ((errorPtr->request != -1)
262		    && (errorPtr->request != errEventPtr->request_code))
263		|| ((errorPtr->minorCode != -1)
264		    && (errorPtr->minorCode != errEventPtr->minor_code))
265		|| ((errorPtr->lastRequest != (unsigned long) -1)
266		    && (errorPtr->lastRequest < errEventPtr->serial))) {
267	    continue;
268	}
269	if (errorPtr->errorProc == NULL) {
270	    return 0;
271	} else {
272	    if ((*errorPtr->errorProc)(errorPtr->clientData,
273		    errEventPtr) == 0) {
274		return 0;
275	    }
276	}
277    }
278
279    /*
280     * See if the error is a BadWindow error.  If so, and it refers
281     * to a window that still exists in our window table, then ignore
282     * the error.  Errors like this can occur if a window owned by us
283     * is deleted by someone externally, like a window manager.  We'll
284     * ignore the errors at least long enough to clean up internally and
285     * remove the entry from the window table.
286     *
287     * NOTE: For embedding, we must also check whether the window was
288     * recently deleted. If so, it may be that Tk generated operations on
289     * windows that were deleted by the container. Now we are getting
290     * the errors (BadWindow) after Tk already deleted the window itself.
291     */
292
293    if ((errEventPtr->error_code == BadWindow) &&
294            ((Tk_IdToWindow(display, (Window) errEventPtr->resourceid) !=
295                    NULL) ||
296                (TkpWindowWasRecentlyDeleted((Window) errEventPtr->resourceid,
297                        dispPtr)))) {
298	return 0;
299    }
300
301    /*
302     * We couldn't handle the error.  Use the default handler.
303     */
304
305    couldntHandle:
306    return (*defaultHandler)(display, errEventPtr);
307}
308