summaryrefslogtreecommitdiffstats
path: root/generic/tkError.c
blob: 6ff54759a83ec4a28759ab4095304a01420b46bd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
/*
 * tkError.c --
 *
 *	This file provides a high-performance mechanism for selectively
 *	dealing with errors that occur in talking to the X server. This is
 *	useful, for example, when communicating with a window that may not
 *	exist.
 *
 * Copyright (c) 1990-1994 The Regents of the University of California.
 * Copyright (c) 1994-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.
 */

#include "tkInt.h"

/*
 * The default X error handler gets saved here, so that it can be invoked if
 * an error occurs that we can't handle.
 */

typedef int (*TkXErrorHandler)(Display *display, XErrorEvent *eventPtr);
static TkXErrorHandler	defaultHandler = NULL;

/*
 * Forward references to procedures declared later in this file:
 */

static int	ErrorProc(Display *display, XErrorEvent *errEventPtr);

/*
 *--------------------------------------------------------------
 *
 * Tk_CreateErrorHandler --
 *
 *	Arrange for all a given procedure to be invoked whenever certain
 *	errors occur.
 *
 * Results:
 *	The return value is a token identifying the handler; it must be passed
 *	to Tk_DeleteErrorHandler to delete the handler.
 *
 * Side effects:
 *	If an X error occurs that matches the error, request, and minor
 *	arguments, then errorProc will be invoked. ErrorProc should have the
 *	following structure:
 *
 *	int
 *	errorProc(caddr_t clientData, XErrorEvent *errorEventPtr) {
 *	}
 *
 *	The clientData argument will be the same as the clientData argument to
 *	this procedure, and errorEvent will describe the error. If errorProc
 *	returns 0, it means that it completely "handled" the error: no further
 *	processing should be done. If errorProc returns 1, it means that it
 *	didn't know how to deal with the error, so we should look for other
 *	error handlers, or invoke the default error handler if no other
 *	handler returns zero. Handlers are invoked in order of age: youngest
 *	handler first.
 *
 *	Note: errorProc will only be called for errors associated with X
 *	requests made AFTER this call, but BEFORE the handler is deleted by
 *	calling Tk_DeleteErrorHandler.
 *
 *--------------------------------------------------------------
 */

Tk_ErrorHandler
Tk_CreateErrorHandler(
    Display *display,		/* Display for which to handle errors. */
    int error,			/* Consider only errors with this error_code
				 * (-1 means consider all errors). */
    int request,		/* Consider only errors with this major
				 * request code (-1 means consider all major
				 * codes). */
    int minorCode,		/* Consider only errors with this minor
				 * request code (-1 means consider all minor
				 * codes). */
    Tk_ErrorProc *errorProc,	/* Procedure to invoke when a matching error
				 * occurs. NULL means just ignore matching
				 * errors. */
    ClientData clientData)	/* Arbitrary value to pass to errorProc. */
{
    TkErrorHandler *errorPtr;
    TkDisplay *dispPtr;

    /*
     * Find the display. If Tk doesn't know about this display then it's an
     * error: panic.
     */

    dispPtr = TkGetDisplay(display);
    if (dispPtr == NULL) {
	Tcl_Panic("Unknown display passed to Tk_CreateErrorHandler");
    }

    /*
     * Make sure that X calls us whenever errors occur.
     */

    if (defaultHandler == NULL) {
	defaultHandler = XSetErrorHandler(ErrorProc);
    }

    /*
     * Create the handler record.
     */

    errorPtr = (TkErrorHandler *)ckalloc(sizeof(TkErrorHandler));
    errorPtr->dispPtr = dispPtr;
    errorPtr->firstRequest = NextRequest(display);
    errorPtr->lastRequest = (unsigned) -1;
    errorPtr->error = error;
    errorPtr->request = request;
    errorPtr->minorCode = minorCode;
    errorPtr->errorProc = errorProc;
    errorPtr->clientData = clientData;
    errorPtr->nextPtr = dispPtr->errorPtr;
    dispPtr->errorPtr = errorPtr;

    return (Tk_ErrorHandler) errorPtr;
}

/*
 *--------------------------------------------------------------
 *
 * Tk_DeleteErrorHandler --
 *
 *	Do not use an error handler anymore.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The handler denoted by the "handler" argument will not be invoked for
 *	any X errors associated with requests made after this call. However,
 *	if errors arrive later for requests made BEFORE this call, then the
 *	handler will still be invoked. Call XSync if you want to be sure that
 *	all outstanding errors have been received and processed.
 *
 *--------------------------------------------------------------
 */

void
Tk_DeleteErrorHandler(
    Tk_ErrorHandler handler)	/* Token for handler to delete; was previous
				 * return value from Tk_CreateErrorHandler. */
{
    TkErrorHandler *errorPtr = (TkErrorHandler *) handler;
    TkDisplay *dispPtr = errorPtr->dispPtr;

    errorPtr->lastRequest = NextRequest(dispPtr->display) - 1;

    /*
     * Every once-in-a-while, cleanup handlers that are no longer active. We
     * probably won't be able to free the handler that was just deleted (need
     * to wait for any outstanding requests to be processed by server), but
     * there may be previously-deleted handlers that are now ready for garbage
     * collection. To reduce the cost of the cleanup, let a few dead handlers
     * pile up, then clean them all at once. This adds a bit of overhead to
     * errors that might occur while the dead handlers are hanging around, but
     * reduces the overhead of scanning the list to clean up (particularly if
     * there are many handlers that stay around forever).
     */

    dispPtr->deleteCount += 1;
    if (dispPtr->deleteCount >= 10) {
	TkErrorHandler *prevPtr;
	TkErrorHandler *nextPtr;
	int lastSerial = LastKnownRequestProcessed(dispPtr->display);

	/*
	 * Last chance to catch errors for this handler: if no event/error
	 * processing took place to follow up the end of this error handler
	 * we need a round trip with the X server now.
	 */

	if (errorPtr->lastRequest > (unsigned long) lastSerial) {
	    XSync(dispPtr->display, False);
	}
	dispPtr->deleteCount = 0;
	errorPtr = dispPtr->errorPtr;
	for (prevPtr = NULL; errorPtr != NULL; errorPtr = nextPtr) {
	    nextPtr = errorPtr->nextPtr;
	    if ((errorPtr->lastRequest != (unsigned long) -1)
		    && (errorPtr->lastRequest <= (unsigned long) lastSerial)) {
		if (prevPtr == NULL) {
		    dispPtr->errorPtr = nextPtr;
		} else {
		    prevPtr->nextPtr = nextPtr;
		}
		ckfree(errorPtr);
		continue;
	    }
	    prevPtr = errorPtr;
	}
    }
}

/*
 *--------------------------------------------------------------
 *
 * ErrorProc --
 *
 *	This procedure is invoked by the X system when error events arrive.
 *
 * Results:
 *	If it returns, the return value is zero. However, it is possible that
 *	one of the error handlers may just exit.
 *
 * Side effects:
 *	This procedure does two things. First, it uses the serial # in the
 *	error event to eliminate handlers whose expiration serials are now in
 *	the past. Second, it invokes any handlers that want to deal with the
 *	error.
 *
 *--------------------------------------------------------------
 */

static int
ErrorProc(
    Display *display,		/* Display for which error occurred. */
    XErrorEvent *errEventPtr)
				/* Information about error. */
{
    TkDisplay *dispPtr;
    TkErrorHandler *errorPtr;

    /*
     * See if we know anything about the display. If not, then invoke the
     * default error handler.
     */

    dispPtr = TkGetDisplay(display);
    if (dispPtr == NULL) {
	goto couldntHandle;
    }

    /*
     * Otherwise invoke any relevant handlers for the error, in order.
     */

    for (errorPtr = dispPtr->errorPtr; errorPtr != NULL;
	    errorPtr = errorPtr->nextPtr) {
	if ((errorPtr->firstRequest > errEventPtr->serial)
		|| ((errorPtr->error != -1)
		    && (errorPtr->error != errEventPtr->error_code))
		|| ((errorPtr->request != -1)
		    && (errorPtr->request != errEventPtr->request_code))
		|| ((errorPtr->minorCode != -1)
		    && (errorPtr->minorCode != errEventPtr->minor_code))
		|| ((errorPtr->lastRequest != (unsigned long) -1)
		    && (errorPtr->lastRequest < errEventPtr->serial))) {
	    continue;
	}
	if (errorPtr->errorProc == NULL ||
		errorPtr->errorProc(errorPtr->clientData, errEventPtr) == 0) {
	    return 0;
	}
    }

    /*
     * See if the error is a BadWindow error. If so, and it refers to a window
     * that still exists in our window table, then ignore the error. Errors
     * like this can occur if a window owned by us is deleted by someone
     * externally, like a window manager. We'll ignore the errors at least
     * long enough to clean up internally and remove the entry from the window
     * table.
     *
     * NOTE: For embedding, we must also check whether the window was recently
     * deleted. If so, it may be that Tk generated operations on windows that
     * were deleted by the container. Now we are getting the errors
     * (BadWindow) after Tk already deleted the window itself.
     */

    if (errEventPtr->error_code == BadWindow) {
	Window w = (Window) errEventPtr->resourceid;

	if (Tk_IdToWindow(display, w) != NULL) {
	    return 0;
	}
    }

    /*
     * We couldn't handle the error. Use the default handler.
     */

  couldntHandle:
    return defaultHandler(display, errEventPtr);
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */