How to catch X errors?

Learn how to catch x errors? with practical examples, diagrams, and best practices. Covers c, unix, xlib development techniques with visual explanations.

Robust X Error Handling in C: A Comprehensive Guide

Hero image for How to catch X errors?

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.

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.

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.