How to catch X errors?
Categories:
Robust X Error Handling in C: A Comprehensive Guide

Learn how to effectively catch and manage X errors in C applications using Xlib and GLX, ensuring stable and reliable graphical programs on Unix-like systems.
Developing graphical applications on Unix-like systems often involves direct interaction with the X Window System via Xlib. While powerful, Xlib's asynchronous nature means that errors don't always manifest immediately at the point of the offending call. This can make debugging challenging. This article will guide you through the mechanisms provided by Xlib and GLX to catch and handle these asynchronous X errors, ensuring your applications are robust and user-friendly.
Understanding X Error Handling
The X Window System operates asynchronously. When an Xlib function is called, it typically queues a request to the X server and returns immediately, without waiting for the server to process the request or report any errors. Errors are usually detected by the X server later and then sent back to the client application. This asynchronous reporting requires a specific error handling mechanism: custom error handlers.
sequenceDiagram participant Client participant XServer Client->>XServer: Xlib_Function_Call() Note right of Client: Request queued, Client continues XServer-->>Client: (Later) X Error Event Client->>Client: Custom X Error Handler Invoked Note left of Client: Error processed asynchronously
Asynchronous X Error Reporting Flow
Implementing a Custom X Error Handler
To catch X errors, you need to register a custom error handler function using XSetErrorHandler
. This function will be called whenever an X error occurs. The handler receives a pointer to an XErrorEvent
structure, which contains details about the error. It's crucial to understand that this handler is invoked in a separate context, and you should avoid performing complex operations or calling Xlib functions that might themselves generate errors within it.
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
static int x_error_occurred = 0;
int custom_x_error_handler(Display *display, XErrorEvent *error_event) {
char error_text[256];
XGetErrorText(display, error_event->error_code, error_text, sizeof(error_text));
fprintf(stderr, "\n[X Error] Request Code: %d, Minor Code: %d\n",
(int)error_event->request_code, (int)error_event->minor_code);
fprintf(stderr, "[X Error] Error Code: %d (%s)\n",
(int)error_event->error_code, error_text);
fprintf(stderr, "[X Error] Resource ID: 0x%lx\n", error_event->resourceid);
fprintf(stderr, "[X Error] Serial Number: %lu\n", error_event->serial);
x_error_occurred = 1; // Set a flag to indicate an error occurred
return 0; // Return 0 to indicate the error was handled
}
int main() {
Display *display;
// Open the display
display = XOpenDisplay(NULL);
if (display == NULL) {
fprintf(stderr, "Cannot open display\n");
return 1;
}
// Register the custom error handler
XSetErrorHandler(custom_x_error_handler);
// --- Simulate an X error ---
// Try to destroy a non-existent window (will cause a BadWindow error)
XDestroyWindow(display, 0x12345678); // Invalid window ID
// XSync() forces all pending requests to be processed,
// ensuring any errors are reported immediately.
XSync(display, False);
if (x_error_occurred) {
fprintf(stderr, "Application detected an X error.\n");
} else {
fprintf(stdout, "No X errors detected (this might not be true without XSync).\n");
}
XCloseDisplay(display);
return 0;
}
Example of a custom X error handler and its registration.
XSync(display, False);
after performing operations that might cause an error, especially during debugging. This forces the X server to process all pending requests and report errors immediately, making them easier to catch with your custom handler.Handling GLX Errors
When working with OpenGL through GLX (OpenGL Extension to the X Window System), errors can originate from both Xlib and GLX itself. GLX errors are often reported via the same XErrorEvent
mechanism, but sometimes they might be specific to GLX. For GLX-specific errors, you can use glXGetError()
to query the last GLX error code. However, for most setup and configuration issues, the XErrorEvent
handler is still the primary mechanism.
#include <GL/glx.h>
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
static int glx_error_occurred = 0;
// A GLX-specific error handler (often combined with X error handler)
int custom_glx_error_handler(Display *display, XErrorEvent *error_event) {
char error_text[256];
XGetErrorText(display, error_event->error_code, error_text, sizeof(error_text));
fprintf(stderr, "\n[GLX/X Error] Request Code: %d, Minor Code: %d\n",
(int)error_event->request_code, (int)error_event->minor_code);
fprintf(stderr, "[GLX/X Error] Error Code: %d (%s)\n",
(int)error_event->error_code, error_text);
fprintf(stderr, "[GLX/X Error] Resource ID: 0x%lx\n", error_event->resourceid);
fprintf(stderr, "[GLX/X Error] Serial Number: %lu\n", error_event->serial);
glx_error_occurred = 1;
return 0;
}
int main() {
Display *display;
GLXFBConfig *fbc;
int fb_count;
display = XOpenDisplay(NULL);
if (display == NULL) {
fprintf(stderr, "Cannot open display\n");
return 1;
}
// Register the custom error handler for both X and GLX errors
XSetErrorHandler(custom_glx_error_handler);
// --- Simulate a GLX error ---
// Try to get GLXFBConfig for a non-existent screen or invalid attributes
// This might not always generate an XErrorEvent, but it's a common source of GLX issues.
int visual_attribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None };
fbc = glXChooseFBConfig(display, 999, visual_attribs, &fb_count); // Screen 999 is invalid
if (fbc == NULL) {
fprintf(stderr, "Failed to retrieve framebuffer configs for invalid screen.\n");
// Check for GLX-specific error if X error handler wasn't triggered
int glx_err = glXGetError();
if (glx_err != Success) {
fprintf(stderr, "GLXGetError reported: %d\n", glx_err);
}
} else {
XFree(fbc);
}
XSync(display, False);
if (glx_error_occurred) {
fprintf(stderr, "Application detected a GLX/X error.\n");
} else {
fprintf(stdout, "No GLX/X errors detected.\n");
}
XCloseDisplay(display);
return 0;
}
Example demonstrating GLX error handling, often relying on the X error handler.
XSetErrorHandler
function replaces any previously installed error handler. If you are integrating with a library that also sets its own handler, you might need to chain them or be aware of potential conflicts.Best Practices for Robust Error Handling
Effective error handling goes beyond just catching errors; it involves designing your application to recover gracefully or provide meaningful feedback. Here are some best practices:
1. Global Error Flag
Use a global or static flag (like x_error_occurred
in the examples) within your error handler. This allows your main application loop to check if an error has occurred after a series of Xlib calls, rather than trying to handle it directly within the handler.
2. Informative Error Messages
Always use XGetErrorText
to translate error codes into human-readable messages. Include relevant details from the XErrorEvent
structure (request code, resource ID, serial number) to aid debugging.
3. Graceful Shutdown
Upon detecting a critical X error, your application should attempt a graceful shutdown, releasing resources and informing the user. Avoid continuing execution in an undefined state.
4. Temporary Error Handlers
For specific operations where you expect an error (e.g., checking if a window exists by trying to query it), you can temporarily install a custom error handler, perform the operation, call XSync
, check your error flag, and then restore the original error handler. This allows you to 'catch' specific errors without terminating the application.
5. Avoid Xlib Calls in Handler
Do not make Xlib calls within your XSetErrorHandler
function, as this can lead to re-entrant errors or deadlocks. The handler should be as simple as possible: log the error, set a flag, and return.