Jump to content

Xcb

From KDE Community Wiki

Introduction

Porting Xlib calls to xcb is fairly straightfoward, since most of the time it's a matter of replacing a call to an Xlib function with a call to the equivalent xcb function. This page mainly deals with exceptions to that rule, and things that might not be obvious even if you are familiar with Xlib.

Porting can be done gradually, since you can mix xcb and Xlib calls. The only exception is event processing. The event queue is managed either by xcb or Xlib, not both at the same time.

Macros

RootWindow(), DisplayWidth(), DisplayHeight(), DefaultDepth() etc.

Xlib version
    Window root = RootWindow(dpy, screen);
    int depth = DefaultDepth(dpy, screen);
    int width = DisplayWidth(dpy, screen);
    int height = DisplayHeight(dpy, screen);
xcb equivalent
    xcb_screen_t *screen = xcb_aux_get_screen(c, screen);
    xcb_window_t root = screen->root;
    int depth = screen->root_depth;
    int width = screen->width_in_pixels;
    int height = screen->height_in_pixels;


If you need the number of the default screen, call QX11Info::appScreen().

DefaultRootWindow(), DefaultVisual(), DefaultDepth(), DefaultColormap()

See the previous section and the comment about getting the number of the default screen.

Functions

XSelectInput()

XSelectInput() is a convenience function that uses the ChangeWindowAttributes request to change the event mask for a window. The xcb equivalent of XSelectInput() is thus xcb_change_window_attributes().

    // Xlib version
    XSelectInput(dpy, window, PropertyChangeMask);

    // xcb version
    uint32_t mask = XCB_EVENT_MASK_PROPERTY_CHANGE;
    xcb_change_window_attributes(c, window, XCB_CW_EVENT_MASK, &mask);


Note that this applies only to XSelectInput(), not other input selection functions added by extensions.

XSync()

The xcb equivalent of XSync() is xcb_aux_sync(), which is in xcb-utils.

The reason you won't find a sync function in libxcb is that there is no sync request in the X protocol. Calling XSync() or xcb_aux_sync() is equivalent to calling XGetInputFocus() and throwing away the reply.

XMoveWindow(), XResizeWindow(), XMoveResizeWindow(), XSetWindowBorderWidth(), XRaiseWindow(), XLowerWindow(), XRestackWindows()

These functions are Xlib convenience functions that translate to the same X request; ConfigureWindow. The xcb equivalent is thus xcb_configure_window().

XMoveWindow() example
    // Xlib version
    XMoveWindow(dpy, window, x, y);

    // xcb version
    uint32_t values[] = { x, y };
    xcb_configure_window(c, window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values);
XRaiseWindow() example
    // Xlib version
    XRaiseWindow(dpy, window);

    // xcb version
    uint32_t value = XCB_STACK_MODE_ABOVE;
    xcb_configure_window(c, window, XCB_CONFIG_STACK_MODE, &value);


Multiple calls to these functions can be combined into single call to xcb_configure_window():

    // Xlib version
    XMoveWindow(dpy, window, x, y);
    XResizeWindow(dpy, window, width, height);
    XRaiseWindow(dpy, window);

    // xcb version
    uint32_t values[] = { x, y, width, height, XCB_STACK_MODE_ABOVE };
    xcb_configure_window(c, window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
                         XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_STACK_MODE,
                         values);


Note that the order of the values in the array must match the order in the xcb_config_window_t enum, this is a tedious and error-prone task, for that reason you may want to look at the _aux versions of these functions (xcb_create_window_aux, xcb_change_window_attributes_aux, xcb_configure_window_aux).

    xcb_configure_window_value_list_t cw = {
        .x = x,
        .y = y,
        .width = width,
        .height = height,
        .stack_mode = XCB_STACK_MODE_ABOVE
    };

    xcb_configure_window_aux(c, window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
                             XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_STACK_MODE,
                             &cw);

XGetWindowAttributes()

The xcb equivalent of this function is xcb_get_window_attributes().

However you may have noticed that xcb_get_window_attributes() doesn't give you all the information returned by XGetWindowAttributes(). More specifically, it doesn't return the position, size, border width and depth of the window.

The reason xcb_get_window_attributes() doesn't return the same information is that XGetWindowAttributes() actually generates and returns the replies from two requests; GetWindowAttributes and GetGeometry.

So if you need the geometry in addition to the other window attributes, you have to call both xcb_get_window_attributes() and xcb_get_geometry().

Creating Resources

In this example we're going to look at how to create a pixmap in Xlib and in xcb, and look at the differences.

    // Xlib version
    Pixmap pixmap = XCreatePixmap(dpy, root, width, height, depth);

    // xcb version
    xcb_pixmap_t pixmap = xcb_generate_id(c);
    xcb_create_pixmap(c, depth, pixmap, root, width, height);


XCreatePixmap() creates a pixmap with the specified width, height and depth, and returns a handle to the newly created pixmap.

In the xcb version we call xcb_generate_id() to create the pixmap handle, and then pass the handle to the xcb_create_pixmap() function.

When a client connects to the X server it is given a range of unique global ID's that can be assigned to new resources. XCreatePixmap() picks the first unused ID in this range, and tells the X server to assign that ID to the pixmap when it creates it. After sending the request, XCreatePixmap() returns the ID to the caller.

Unlike Xlib, xcb doesn't hide these details from us. Whenever you create a new resource, you start by calling xcb_generate_id() to get an ID that can be assigned to the resource. The ID is always passed as a parameter to the function that creates the resource.

The advantage of this design is that creating resources is asynchronous, since the client doesn't have to wait for the X server to send back the ID after creating the resource. The disadvantage is that if resource creation fails for any reason, the ID will never become valid and we won't find out about it until later.

Another example that creates a region:

    xcb_rectangle_t rect = { 0, 0, 100, 100 };

    xcb_xfixes_region_t region = xcb_generate_id(c);
    xcb_xfixes_create_region(c, region, 1, &rect);

Checking if an Extension is Supported

The following example shows how to check if Xfixes >= 1.0 is supported.

    // Xlib version
    bool have_xfixes = false;
    int event_base, error_base;
    if (XFixesQueryExtension(dpy, &event_base, &error_base)) {
        int major, minor;
        XFixesQueryVersion(dpy, &major, &minor);
        if (major >= 1)
            have_xfixes = true;
    }

    // Xcb version
    const xcb_query_extension_reply_t *xfixes = xcb_get_extension_data(c, &xcb_xfixes_id);

    bool have_xfixes = false;
    int event_base, error_base;
    if (xfixes && xfixes->present) {
        event_base = xfixes->first_event;
        error_base = xfixes->first_error;

        xcb_xfixes_query_version_cookie_t cookie = xcb_xfixes_query_version(c, XCB_XFIXES_MAJOR_VERSION, XCB_XFIXES_MINOR_VERSION);
        xcb_xfixes_query_version_reply_t *reply = xcb_xfixes_query_version_reply(c, cookie, NULL);
        if (reply) {
            if (reply->major_version >= 1)
                have_xfixes = true;

            free(reply);
        }
    }


xcb_xfixes_id is a global variable declared in the extension header.

The version passed to xcb_xfixes_query_version() should be the highest version the client supports. It's always a good idea to use the highest version supported by xcb here. This version number determines which requests will be legal for the client to use, and may also affect the size of replies returned by the X server.

Note that if you're going to check for multiple extensions, you should call xcb_prefetch_extension_data() for each of them before you call xcb_get_extension_data():

    xcb_prefetch_extension_data(c, &xcb_xfixes_id);
    xcb_prefetch_extension_data(c, &xcb_composite_id);
    xcb_prefetch_extension_data(c, &xcb_damage_id);

    const xcb_query_extension_reply_t *xfixes    = xcb_get_extension_data(c, &xcb_xfixes_id);
    const xcb_query_extension_reply_t *composite = xcb_get_extension_data(c, &xcb_composite_id);
    // ...

KXErrorHandler

The KXErrorHandler class is used when you need to check if an asychronous Xlib call succeeded or generated an an error. The following example shows how it is typically used.

    KXerrorHandler handler;

    XMapWindow(dpy, window);

    if (handler.error(true)) {
        kDebug() << "XMapWindow() failed!";
    }


Unfortunately KXErrorHandler cannot be ported to xcb. The good news however is that with xcb you don't need KXErrorHandler to do this.

You may have noticed that xcb functions that don't generate a reply come in two versions; a regular version and a version with _checked() appended to the name:

    xcb_void_cookie_t xcb_map_window(xcb_connection_t *c, xcb_window_t window);

    xcb_void_cookie_t xcb_map_window_checked(xcb_connection_t *c, xcb_window_t window);


The xcb_void_cookie_t returned by the _checked() version of the function can be used to check if the request succeeded or not by passing the cookie to xcb_request_check().

The following example shows how this works:

    xcb_void_cookie_t cookie = xcb_map_window_checked(c, window);
    xcb_generic_error_t *error = xcb_request_check(c, cookie);

    if (error) {
        kDebug() << "xcb_map_window() failed!";
        free(error);
    }


Like KXErrorHandler, xcb_request_check() needs to synchronize with the X server to know that the X server has processed the request(s). For this reason you should only use xcb_request_check() when it is necessary to know that a request succeeded before proceeding with other calls.

Data Representation; uint32_t vs unsigned long

An important difference between Xlib and xcb is that Xlib uses long and unsigned long to represent 32-bit data, while xcb uses int32_t and uint32_t. This is important to keep in mind when porting Xlib code, since a long is 64 bits on a 64-bit system.

Consider the following example.

    // Xlib version
    unsigned long data[2];
    XChangeProperty(dpy, window, property, XA_CARDINAL, 32, PropModeReplace, (uchar *) data, 2);

    // Xcb version (wrong)
    unsigned long data[2];
    xcb_change_property(c, XCB_PROP_MODE_REPLACE, window, property, XCB_ATOM_CARDINAL, 32, 2, (const void *) data);


While it may appear at first glance that the code has been ported correctly, in reality this is not the case. Since we are uploading 32 bit data to the server, the data pointer will be interpreted as a uint32_t pointer, not an unsigned long pointer.

When ported correctly, the code should look like this:

    // Xcb version (right)
    uint32_t data[2];
    xcb_change_property(c, XCB_PROP_MODE_REPLACE, window, property, XCB_ATOM_CARDINAL, 32, 2, (const void *) data);


You should also keep in mind that XID's, such as Window, Pixmap, Drawable etc. are also unsigned longs, while the xcb equivalents are uint32_t's. This is important when you're gradually porting code and you pass an array of Windows or Pixmaps to an xcb function.