XCB out of Windows

-jatin golani (aka jeetu aka topcat) jeetu.golani@gmail.com

XCB has now been ported to the Windows (32 bit) platform. This mess shall soon be unleashed on the world so here is the headsup on it :)

Background


The Windows operating system no longer includes the POSIX subsystem out of the box. Using POSIX/Unix applications under Windows therefore requires an additional layer such as Microsoft's Interix Posix/Unix subsystem or the Cygwin libraries. Cygwin consists of a library that implements the POSIX system call API in terms of Win32 system calls. Unfortunately, an additional translation layer causes performance degradation. While there are a few X Servers that have been ported to Windows, there has been no port of the X Client library (Xlib or XCB), thereby preventing writing native Windows based X clients. The Win32 XCB port aims at allowing native Windows X Clients to be developed. These native X applications would provide performance superior to applications using libraries such as Cygwin.

What's changed between the *nix and Windows ports?


Essentially the following:

-- The following header files used under *nix development environments are absent or redundant in Windows.

  • sys/socket.h
  • netinet/in.h
  • sys/un.h
  • sys/select.h
  • sys/uio.h
  • netdb.h

Our code has been changed to skip inclusion of these header files when compiled under WIN32.

-- Source code files effected:

  • xcb_auth.c

  • xcb_conn.c

    • set_fd_flags() : Win32 doesn't have file descriptors and the fcntl function. Made changes that set the socket in non-blocking mode.
    • write_vec() : Win32 version using the send() function instead of writev.
  • xcb_in.c

    • read_block() : Win32 version using the recv() function instead of read.
    • _xcb_in_read() : Win32 version using the recv() function instead of read.
  • xcb_util.c

    • _xcb_open_unix() : Preprocessor directives prevent this from being compiled while building for Win32.
    • _xcb_open() : Does not call xcbopen_unix for Win32 build.
    • _xcb_open_tcp()
  • xcb.h

  • windefs.h (new header created specifically for the Win32 port)

-- Windefs.h

windefs.h defines all headers and other structures,datatypes,etc required for win32. A key point to note within this header file is the following define:

#define WINVER 0x0501 required since getaddrinfo and freeaddrinfo are defined only for WinXP and above. Without this the linker complains of undefined references.

Therefore, currently the code may not compile on versions prior to XP (untested)

Creating the XCB DLL


The following lists the process I used to compile XCB under MinGW on my Debian system :

-- compiled and installed x11proto under standard linux ./autogen && make && make install....then copied the headers from /usr/local/include/X11 to /use/i586-mingw32msvc/include physically.

-- Compile the DLL version of libXau (you'll need libtool installed):

  • ./autogen.sh --host=i586-mingw32msvc

  • make

  • sudo make install

-- Compiled and installed the pthread-win32 library (http://sourceware.org/pthreads-win32/)

  • make CC=i586-mingw32msvc-gcc \

     RC=i586-mingw32msvc-windres \
    
    
     RANLIB=i586-mingw32msvc-ranlib \
    
    
     DLLTOOL=i586-mingw32msvc-dlltool \
    
    
     clean GC
    

-- Made the following changes (thanks Vincent) -

  • in configure.ac, before AC_PROG_LIBTOOL add:

    AC_LIBTOOL_WIN32_DLL

  • add, in src/Makefile.am, the value -no-undefined to *_LDFLAGS:

    libxcb_la_LDFLAGS = -no-undefined -version-info 1:0:0

-- set the environment variable LIBS to include the ws2_32 and pthreadGC2 library and the CFLAGS to point to appropriate headers:

  • export LIBS="-L/full/path/to/pthreads-w32-libs -lws2_32 -lpthreadGC2"

  • export CFLAGS="-I/full/install/path/pthreads-w32-include-files"

-- configure (you'll need package xcb-proto installed):

  • ./autogen.sh --host=i586-mingw32msvc

-- build (you'll need package python-xcbgen installed):

  • make

If all goes well the DLL is created within the src/.libs directory.

Where can I get this from now?


Until this port is merged into the main tree, I have set up a git branch of XCB with changes to support a Win32 build. Get it using :

 git clone git://github.com/topcat/xcb-win32.git

...and stand far far far away :)

This has been updated last in April 2010.

Using XCB within Windows


The XCB DLL has been tested predominantly under Windows XP (32 bit). The development environment has been Linux (Debian) using MinGW. However there is no reason you can't use Visual Studio or other IDEs. There are plenty of online resources that show how to create the required .def and .lib files and that speak of differences in name decoration of functions generated by MinGW as opposed to other compilers.

Apart from the XCB DLL (e.g. libxcb-1.dll) you will also need the DLL for libXau (e.g. libXau-6.dll) and Pthreads (e.g. pthreadGC2.dll) to be in your path or along with your source code.

The simplest way to use the DLL under MinGW is to include it during compiling your code. For e.g. :

 i586-mingw32msvc-gcc xcbclient.c libxcb-1.dll -o xcbclient.exe -lws2_32

NOTE: Initialization ( using WSAStarup() ) and Cleanup ( WSACleanup() ) of the Windows Socket Library needs to be performed by the calling application prior to using this library.

X Servers


I have tested the port using the following X Servers within Windows. My source code has been connecting locally to these X Servers. I have also connected remotely to my X.org server on Linux.

Test Code


The following is a test code from the XCB tutorial. Copy all of the XCB headers within your source code directory or somewhere in your path. If all goes well this should work.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "xcb/xcb.h"

#define WIDTH 300
#define HEIGHT 150

int initWSA(void);

static xcb_gc_t
    getFontGC (xcb_connection_t *connection,
           xcb_screen_t     *screen,
           xcb_window_t      window,
           const char       *fontName );
static void
    testCookie (xcb_void_cookie_t cookie,
            xcb_connection_t *connection,
            char *errMessage )
    {
    xcb_generic_error_t *error = xcb_request_check (connection, cookie);
    if (error) 
       {
       fprintf (stderr, "ERROR: %s : %d\n", errMessage , error->error_code);
       xcb_disconnect (connection);
       exit (-1);
       }
    }


static void
drawButton (xcb_connection_t *connection,
            xcb_screen_t     *screen,
            xcb_window_t      window,
            int16_t           x1,
            int16_t           y1,
            const char       *label )
{
    uint8_t length = strlen (label);
    int16_t inset = 2;
    int16_t width = 7 * length + 2 * (inset + 1);
    int16_t height = 13 + 2 * (inset + 1);

    xcb_point_t points[5];
    points[0].x = x1;
    points[0].y = y1;
    points[1].x = x1 + width;
    points[1].y = y1;
    points[2].x = x1 + width;
    points[2].y = y1 - height;
    points[3].x = x1;
    points[3].y = y1 - height;
    points[4].x = x1;
    points[4].y = y1;


    xcb_gcontext_t gc = getFontGC (connection, screen, window, "7x13");


    xcb_void_cookie_t lineCookie = xcb_poly_line_checked (connection,
                                                          XCB_COORD_MODE_ORIGIN,
                                                          window,
                                                          gc,
                                                          5,
                                                          points );
    testCookie (lineCookie, connection, "can't draw lines");


    xcb_void_cookie_t textCookie = xcb_image_text_8_checked (connection,
                                                             length,
                                                             window,
                                                             gc,
                                                             x1 + inset + 1,
                                                             y1 - inset - 1,
                                                             label );
    testCookie (textCookie, connection, "can't paste text");


    xcb_void_cookie_t gcCookie = xcb_free_gc (connection, gc);
    testCookie (gcCookie, connection, "can't free gc");
}



static void
drawText (xcb_connection_t *connection,
          xcb_screen_t     *screen,
          xcb_window_t      window,
          int16_t           x1,
          int16_t           y1,
          const char       *label )
{

    xcb_gcontext_t gc = getFontGC (connection, screen, window, "7x13");


    xcb_void_cookie_t textCookie = xcb_image_text_8_checked (connection,
                                                             strlen (label),
                                                             window,
                                                             gc,
                                                             x1,
                                                             y1,
                                                             label );
    testCookie(textCookie, connection, "can't paste text");


    xcb_void_cookie_t gcCookie = xcb_free_gc (connection, gc);
    testCookie (gcCookie, connection, "can't free gc");
}


static xcb_gc_t
getFontGC (xcb_connection_t *connection,
           xcb_screen_t     *screen,
           xcb_window_t      window,
           const char       *fontName )
{

    xcb_font_t font = xcb_generate_id (connection);
    xcb_void_cookie_t fontCookie = xcb_open_font_checked (connection,
                                                          font,
                                                          strlen (fontName),
                                                          fontName );
    testCookie (fontCookie, connection, "can't open font");


    xcb_gcontext_t gc = xcb_generate_id (connection);
    uint32_t  mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
    uint32_t value_list[3];
    value_list[0] = screen->black_pixel;
    value_list[1] = screen->white_pixel;
    value_list[2] = font;


    xcb_void_cookie_t gcCookie = xcb_create_gc_checked (connection,
                                                        gc,
                                                        window,
                                                        mask,
                                                        value_list );
    testCookie (gcCookie, connection, "can't create gc");


    fontCookie = xcb_close_font_checked (connection, font);
    testCookie (fontCookie, connection, "can't close font");

    return gc;
}


static void
setCursor (xcb_connection_t *connection,
            xcb_screen_t     *screen,
            xcb_window_t      window,
            int               cursorId )
{
    uint32_t mask;
xcb_font_t font = xcb_generate_id (connection);
    xcb_void_cookie_t fontCookie = xcb_open_font_checked (connection,
                                                          font,
                                                          strlen ("cursor"),
                                                          "cursor" );
    testCookie (fontCookie, connection, "can't open font");


    xcb_cursor_t cursor = xcb_generate_id (connection);
    xcb_create_glyph_cursor (connection,
                             cursor,
                             font,
                             font,
                             cursorId,
                             cursorId + 1,
                             0, 0, 0, 0, 0, 0 );


    xcb_gcontext_t gc = xcb_generate_id (connection);

    mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
    uint32_t values_list[3];
    values_list[0] = screen->black_pixel;
    values_list[1] = screen->white_pixel;
    values_list[2] = font;

    xcb_void_cookie_t gcCookie = xcb_create_gc_checked (connection, gc, window, mask, values_list);
    testCookie (gcCookie, connection, "can't create gc");


    mask = XCB_CW_CURSOR;
    uint32_t value_list = cursor;
    xcb_change_window_attributes (connection, window, mask, &value_list);


    xcb_free_cursor (connection, cursor);



    fontCookie = xcb_close_font_checked (connection, font);
    testCookie (fontCookie, connection, "can't close font");
}

int 
main ()
{


    int screenNum,i;
    int rc;

    rc = initWSA();
    if(rc != 0)
      {
      fprintf(stderr,"Unable to load Winsock: %d\n",rc);
      return -1;
      }

    xcb_connection_t *connection = xcb_connect ("127.0.0.1:0.0", &screenNum);
    if (!connection) {
        fprintf (stderr, "ERROR: can't connect to an X server\n");
        return -1;
    }




    xcb_screen_iterator_t iter = xcb_setup_roots_iterator (xcb_get_setup (connection));


    for (i = 0; i < screenNum; ++i) {
        xcb_screen_next (&iter);
    }

    xcb_screen_t *screen = iter.data;

    if (!screen) {
        fprintf (stderr, "ERROR: can't get the current screen\n");
        xcb_disconnect (connection);
        return -1;
    }




    xcb_window_t window = xcb_generate_id (connection);
    uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
    uint32_t values[2];
    values[0] = screen->white_pixel;
    values[1] = XCB_EVENT_MASK_KEY_RELEASE |
                XCB_EVENT_MASK_BUTTON_PRESS |
                XCB_EVENT_MASK_EXPOSURE |
                XCB_EVENT_MASK_POINTER_MOTION;


    xcb_void_cookie_t windowCookie = xcb_create_window_checked (connection,
                                              screen->root_depth,
                                              window,
                                              screen->root,
                                              20, 200, WIDTH, HEIGHT,
                                              0,
                                              XCB_WINDOW_CLASS_INPUT_OUTPUT,
                                              screen->root_visual,
                                              mask, values );
    testCookie (windowCookie, connection, "can't create window");


    xcb_void_cookie_t mapCookie = xcb_map_window_checked (connection, window);
    testCookie (mapCookie, connection, "can't map window");


    setCursor (connection, screen, window, 68);

    xcb_flush(connection);




    uint8_t isHand = 0;

    while (1) {
        xcb_generic_event_t *event = xcb_poll_for_event (connection);
        if (event) {
            switch (event->response_type & ~0x80) {
                case XCB_EXPOSE: {
                    char *text = "click here to change cursor";
                    drawButton (connection, 
                                screen,
                                window,
                                (WIDTH - 7 * strlen(text)) / 2,
                                (HEIGHT - 16) / 2,
                                text );

                    text = "Press ESC key to exit...";
                    drawText (connection,
                              screen,
                              window,
                              10,
                              HEIGHT - 10,
                              text );
                    break;
                }
                case XCB_BUTTON_PRESS: {
                    xcb_button_press_event_t *press = (xcb_button_press_event_t *)event;

                    int length = strlen ("click here to change cursor");
                    if ((press->event_x >= (WIDTH - 7 * length) / 2) &&
                            (press->event_x <= ((WIDTH - 7 * length) / 2 + 7 * length + 6)) &&
                            (press->event_y >= (HEIGHT - 16) / 2 - 19) &&
                            (press->event_y <= ((HEIGHT - 16) / 2))) {
                        isHand = 1 - isHand;

                    }

                    if (isHand) {
                        setCursor (connection, screen, window, 58);
                    }
                    else {
                        setCursor (connection, screen, window, 68);

                    }

                }
                case XCB_KEY_RELEASE: {

                    xcb_key_release_event_t *kr = (xcb_key_release_event_t *)event;

                    switch (kr->detail) {

                        case 8:
                            free (event);
                            xcb_disconnect (connection);
                            return 0;
                    }
                }
            }
            free (event);
        }
    }

    return 0;
}

int initWSA(void)
{
    WSADATA wsd;
    int rc;

    rc = WSAStartup(MAKEWORD(2,2),&wsd);

    return rc;
}