Xcb: Difference between revisions

From KDE Community Wiki
No edit summary
m (add note for _aux versions of some xcb functions)
 
(5 intermediate revisions by 2 users not shown)
Line 26: Line 26:
     xcb_window_t root = screen->root;
     xcb_window_t root = screen->root;
     int depth = screen->root_depth;
     int depth = screen->root_depth;
     int width = screen->width;
     int width = screen->width_in_pixels;
     int height = screen->height;
     int height = screen->height_in_pixels;
</syntaxhighlight>
</syntaxhighlight>
<br/>


If you need a pointer to the xcb_screen_t for the default screen, call QPlatformNativeInterface::nativeResourceForWindow("screen", 0))
 
and cast the returned pointer to xcb_screen_t *.
If you need the number of the default screen, call QX11Info::appScreen().


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


See the previous section and the comment about obtaining a pointer
See the previous section and the comment about getting the number of the
to the xcb_screen_t for the default screen.
default screen.


==Functions==
==Functions==
Line 55: Line 54:
     xcb_change_window_attributes(c, window, XCB_CW_EVENT_MASK, &mask);
     xcb_change_window_attributes(c, window, XCB_CW_EVENT_MASK, &mask);
</syntaxhighlight>
</syntaxhighlight>
<br/>
 


Note that this applies only to XSelectInput(), not other input selection functions added by extensions.
Note that this applies only to XSelectInput(), not other input selection functions added by extensions.
Line 92: Line 91:
     xcb_configure_window(c, window, XCB_CONFIG_STACK_MODE, &value);
     xcb_configure_window(c, window, XCB_CONFIG_STACK_MODE, &value);
</syntaxhighlight>
</syntaxhighlight>
<br/>
 


Multiple calls to these functions can be combined into single call to xcb_configure_window():
Multiple calls to these functions can be combined into single call to xcb_configure_window():
Line 108: Line 107:
                         values);
                         values);
</syntaxhighlight>
</syntaxhighlight>
<br/>


Note that the order of the values in the values array must match the order in the xcb_config_window_t enum.
 
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).
 
<syntaxhighlight lang="cpp">
    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);
</syntaxhighlight>


=== XGetWindowAttributes() ===
=== XGetWindowAttributes() ===
Line 118: Line 131:
However you may have noticed that xcb_get_window_attributes() doesn't give you all the information returned by
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.
XGetWindowAttributes(). More specifically, it doesn't return the position, size, border width and depth of the window.
This is the reason why this function gets a special mention in this section.


It's not a bug that xcb_get_window_attributes() doesn't return the same information. It's because XGetWindowAttributes()
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.
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()
So if you need the geometry in addition to the other window attributes, you have to call both xcb_get_window_attributes()
Line 134: Line 145:


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


XCreatePixmap() creates a pixmap with the specified width, height and depth,
XCreatePixmap() creates a pixmap with the specified width, height and depth,
Line 143: Line 154:


In the xcb version we call xcb_generate_id() to create the pixmap handle,
In the xcb version we call xcb_generate_id() to create the pixmap handle,
and then we pass the handle to the xcb_create_pixmap() function.
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
When a client connects to the X server it is given a range of unique global ID's that can be assigned
Line 164: Line 175:
     xcb_rectangle_t rect = { 0, 0, 100, 100 };
     xcb_rectangle_t rect = { 0, 0, 100, 100 };


     xcb_xfixes_region_t region = xcb_generate_id();
     xcb_xfixes_region_t region = xcb_generate_id(c);
     xcb_xfixes_create_region(c, region, 1, &rect);
     xcb_xfixes_create_region(c, region, 1, &rect);
</syntaxhighlight>
</syntaxhighlight>


== Checking if an Extension is Supported ==
== Checking if an Extension is Supported ==
The following example shows how to check if Xfixes >= 1.0 is supported.


<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
Line 182: Line 195:


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


     bool have_xfixes = false;
     bool have_xfixes = false;
Line 206: Line 219:
The version passed to xcb_xfixes_query_version() should be the highest version the client supports.
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.
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()
Note that if you're going to check for multiple extensions, you should call xcb_prefetch_extension_data()
Line 215: Line 230:
     xcb_prefetch_extension_data(c, &xcb_damage_id);
     xcb_prefetch_extension_data(c, &xcb_damage_id);


     xcb_extension_data_t *xfixes    = xcb_get_extension_data(c, &xcb_xfixes_id);
     const xcb_query_extension_reply_t *xfixes    = xcb_get_extension_data(c, &xcb_xfixes_id);
     xcb_extension_data_t *composite = xcb_get_extension_data(c, &xcb_composite_id);
     const xcb_query_extension_reply_t *composite = xcb_get_extension_data(c, &xcb_composite_id);
     // ...
     // ...
</syntaxhighlight>
</syntaxhighlight>
Line 234: Line 249:
     }
     }
</syntaxhighlight>
</syntaxhighlight>
<br/>
 


Unfortunately KXErrorHandler cannot be ported to xcb. The good news however is that with xcb you don't
Unfortunately KXErrorHandler cannot be ported to xcb. The good news however is that with xcb you don't
Line 247: Line 262:
     xcb_void_cookie_t xcb_map_window_checked(xcb_connection_t *c, xcb_window_t window);
     xcb_void_cookie_t xcb_map_window_checked(xcb_connection_t *c, xcb_window_t window);
</syntaxhighlight>
</syntaxhighlight>
<br/>
 


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


The following example shows how this works:
The following example shows how this works:
Line 263: Line 278:
     }
     }
</syntaxhighlight>
</syntaxhighlight>
<br/>
 


Like KXErrorHandler, xcb_request_check() needs to synchronize with the X server to know
Like KXErrorHandler, xcb_request_check() needs to synchronize with the X server to know
Line 269: Line 284:
xcb_request_check() when it is necessary to know that a request succeeded before
xcb_request_check() when it is necessary to know that a request succeeded before
proceeding with other calls.
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.
<syntaxhighlight lang="cpp">
    // 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);
</syntaxhighlight>
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:
<syntaxhighlight lang="cpp">
    // 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);
</syntaxhighlight>
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.

Latest revision as of 04:09, 2 September 2022

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.