Skip to content

Messing with the mouse cursor

It it possible to modify the shape of the mouse pointer (also called the X pointer) when in certain states, as we otfen see in programs. For example, a busy application would often display the sand clock over its main window, to give the user a visual hint that he should wait. Let's see how we can change the mouse cursor of our windows.

  1. Creating and destroying a mouse cursor

    There are two methods for creating cursors. One of them is by using a set of predefined cursors, that are supplied by the X server, the other is by using a user-supplied bitmap.

    In the first method, we use a special font named "cursor", and the function xcb_create_glyph_cursor:

    xcb_void_cookie_t xcb_create_glyph_cursor (xcb_connection_t *c,
                                               xcb_cursor_t      cid,
                                               xcb_font_t        source_font, /* font for the source glyph */
                                               xcb_font_t        mask_font,   /* font for the mask glyph or XCB_NONE */
                                               uint16_t          source_char, /* character glyph for the source */
                                               uint16_t          mask_char,   /* character glyph for the mask */
                                               uint16_t          fore_red,    /* red value for the foreground of the source */
                                               uint16_t          fore_green,  /* green value for the foreground of the source */
                                               uint16_t          fore_blue,   /* blue value for the foreground of the source */
                                               uint16_t          back_red,    /* red value for the background of the source */
                                               uint16_t          back_green,  /* green value for the background of the source */
                                               uint16_t          back_blue)   /* blue value for the background of the source */
    xcb_void_cookie_t xcb_create_glyph_cursor (xcb_connection_t *c,
                                               xcb_cursor_t      cid,
                                               xcb_font_t        source_font, /* font for the source glyph */
                                               xcb_font_t        mask_font,   /* font for the mask glyph or XCB_NONE */
                                               uint16_t          source_char, /* character glyph for the source */
                                               uint16_t          mask_char,   /* character glyph for the mask */
                                               uint16_t          fore_red,    /* red value for the foreground of the source */
                                               uint16_t          fore_green,  /* green value for the foreground of the source */
                                               uint16_t          fore_blue,   /* blue value for the foreground of the source */
                                               uint16_t          back_red,    /* red value for the background of the source */
                                               uint16_t          back_green,  /* green value for the background of the source */
                                               uint16_t          back_blue)   /* blue value for the background of the source */

    TODO: Describe source_char and mask_char, for example by giving an example on how to get the values. There is a list there: X Font Cursors

    So we first open that font (see Loading a Font) and create the new cursor. As for every X ressource, we have to ask for an X id with xcb_generate_id first:

    xcb_font_t           font;
    xcb_cursor_t         cursor;
    
    /* The connection is set */
    
    font = xcb_generate_id (conn);
    xcb_open_font (conn, font, strlen ("cursor"), "cursor");
    
    cursor = xcb_generate_id (conn);
    xcb_create_glyph_cursor (conn, cursor, font, font,
                             58, 58 + 1,
                             0, 0, 0,
                             0, 0, 0);
    xcb_font_t           font;
    xcb_cursor_t         cursor;
    
    /* The connection is set */
    
    font = xcb_generate_id (conn);
    xcb_open_font (conn, font, strlen ("cursor"), "cursor");
    
    cursor = xcb_generate_id (conn);
    xcb_create_glyph_cursor (conn, cursor, font, font,
                             58, 58 + 1,
                             0, 0, 0,
                             0, 0, 0);

    We have created the cursor "right hand" by specifying 58 to the source_font argument and 58 + 1 to the mask_font.

    The cursor is destroyed by using the function

    xcb_void_cookie_t xcb_free_cursor (xcb_connection_t *c,
                                       xcb_cursor_t      cursor);
    xcb_void_cookie_t xcb_free_cursor (xcb_connection_t *c,
                                       xcb_cursor_t      cursor);

    In the second method, we create a new cursor by using a pair of pixmaps, with depth of one (that is, two colors pixmaps). One pixmap defines the shape of the cursor, while the other works as a mask, specifying which pixels of the cursor will be actually drawn. The rest of the pixels will be transparent.

    TODO: give an example.

  2. Setting a window's mouse cursor

    Once the cursor is created, we can modify the cursor of our window by using xcb_change_window_attributes and using the XCB_CWCURSOR attribute:

    uint32_t mask;
    uint32_t value_list;
    
    /* The connection and window are set */
    /* The cursor is already created */
    
    mask = XCB_CWCURSOR;
    value_list = cursor;
    xcb_change_window_attributes (conn, window, mask, &value_list);
    uint32_t mask;
    uint32_t value_list;
    
    /* The connection and window are set */
    /* The cursor is already created */
    
    mask = XCB_CWCURSOR;
    value_list = cursor;
    xcb_change_window_attributes (conn, window, mask, &value_list);

    Of course, the cursor and the font must be freed.

  3. Complete example

    The following example displays a window with a button. When entering the window, the window cursor is changed to an arrow. When clicking once on the button, the cursor is changed to a hand. When clicking again on the button, the cursor window gets back to the arrow. The Esc key exits the application.

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    
    #include <xcb/xcb.h>
    
    #define WIDTH 300
    #define HEIGHT 150
    
    
    
    static xcb_gc_t gc_font_get (xcb_connection_t *c,
                                 xcb_screen_t     *screen,
                                 xcb_window_t      window,
                                 const char       *font_name);
    
    static void button_draw (xcb_connection_t *c,
                             xcb_screen_t     *screen,
                             xcb_window_t      window,
                             int16_t           x1,
                             int16_t           y1,
                             const char       *label);
    
    static void text_draw (xcb_connection_t *c,
                           xcb_screen_t     *screen,
                           xcb_window_t      window,
                           int16_t           x1,
                           int16_t           y1,
                           const char       *label);
    
    static void cursor_set (xcb_connection_t *c,
                            xcb_screen_t     *screen,
                            xcb_window_t      window,
                            int               cursor_id);
    
    
    static void
    button_draw (xcb_connection_t *c,
                 xcb_screen_t     *screen,
                 xcb_window_t      window,
                 int16_t           x1,
                 int16_t           y1,
                 const char       *label)
    {
      xcb_point_t          points[5];
      xcb_void_cookie_t    cookie_gc;
      xcb_void_cookie_t    cookie_line;
      xcb_void_cookie_t    cookie_text;
      xcb_generic_error_t *error;
      xcb_gcontext_t       gc;
      int16_t              width;
      int16_t              height;
      uint8_t              length;
      int16_t              inset;
    
      length = strlen (label);
      inset = 2;
    
      gc = gc_font_get(c, screen, window, "7x13");
    
      width = 7 * length + 2 * (inset + 1);
      height = 13 + 2 * (inset + 1);
      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;
      cookie_line = xcb_poly_line_checked (c, XCB_COORD_MODE_ORIGIN,
                                           window, gc, 5, points);
    
      error = xcb_request_check (c, cookie_line);
      if (error) {
        fprintf (stderr, "ERROR: can't draw lines : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    
      cookie_text = xcb_image_text_8_checked (c, length, window, gc,
                                              x1 + inset + 1,
                                              y1 - inset - 1, label);
      error = xcb_request_check (c, cookie_text);
      if (error) {
        fprintf (stderr, "ERROR: can't paste text : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    
      cookie_gc = xcb_free_gc (c, gc);
      error = xcb_request_check (c, cookie_gc);
      if (error) {
        fprintf (stderr, "ERROR: can't free gc : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    }
    
    static void
    text_draw (xcb_connection_t *c,
               xcb_screen_t     *screen,
               xcb_window_t      window,
               int16_t           x1,
               int16_t           y1,
               const char       *label)
    {
      xcb_void_cookie_t    cookie_gc;
      xcb_void_cookie_t    cookie_text;
      xcb_generic_error_t *error;
      xcb_gcontext_t       gc;
      uint8_t              length;
    
      length = strlen (label);
    
      gc = gc_font_get(c, screen, window, "7x13");
    
      cookie_text = xcb_image_text_8_checked (c, length, window, gc,
                                              x1,
                                              y1, label);
      error = xcb_request_check (c, cookie_text);
      if (error) {
        fprintf (stderr, "ERROR: can't paste text : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    
      cookie_gc = xcb_free_gc (c, gc);
      error = xcb_request_check (c, cookie_gc);
      if (error) {
        fprintf (stderr, "ERROR: can't free gc : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    }
    
    static xcb_gc_t
    gc_font_get (xcb_connection_t *c,
                 xcb_screen_t     *screen,
                 xcb_window_t      window,
                 const char       *font_name)
    {
      uint32_t             value_list[3];
      xcb_void_cookie_t    cookie_font;
      xcb_void_cookie_t    cookie_gc;
      xcb_generic_error_t *error;
      xcb_font_t           font;
      xcb_gcontext_t       gc;
      uint32_t             mask;
    
      font = xcb_generate_id (c);
      cookie_font = xcb_open_font_checked (c, font,
                                           strlen (font_name),
                                           font_name);
    
      error = xcb_request_check (c, cookie_font);
      if (error) {
        fprintf (stderr, "ERROR: can't open font : %d\n", error->error_code);
        xcb_disconnect (c);
        return -1;
      }
    
      gc = xcb_generate_id (c);
      mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
      value_list[0] = screen->black_pixel;
      value_list[1] = screen->white_pixel;
      value_list[2] = font;
      cookie_gc = xcb_create_gc_checked (c, gc, window, mask, value_list);
      error = xcb_request_check (c, cookie_gc);
      if (error) {
        fprintf (stderr, "ERROR: can't create gc : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    
      cookie_font = xcb_close_font_checked (c, font);
      error = xcb_request_check (c, cookie_font);
      if (error) {
        fprintf (stderr, "ERROR: can't close font : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    
      return gc;
    }
    
    static void
    cursor_set (xcb_connection_t *c,
                xcb_screen_t     *screen,
                xcb_window_t      window,
                int               cursor_id)
    {
      uint32_t             values_list[3];
      xcb_void_cookie_t    cookie_font;
      xcb_void_cookie_t    cookie_gc;
      xcb_generic_error_t *error;
      xcb_font_t           font;
      xcb_cursor_t         cursor;
      xcb_gcontext_t       gc;
      uint32_t             mask;
      uint32_t             value_list;
    
      font = xcb_generate_id (c);
      cookie_font = xcb_open_font_checked (c, font,
                                           strlen ("cursor"),
                                           "cursor");
      error = xcb_request_check (c, cookie_font);
      if (error) {
        fprintf (stderr, "ERROR: can't open font : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    
      cursor = xcb_generate_id (c);
      xcb_create_glyph_cursor (c, cursor, font, font,
                               cursor_id, cursor_id + 1,
                               0, 0, 0,
                               0, 0, 0);
    
      gc = xcb_generate_id (c);
      mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
      values_list[0] = screen->black_pixel;
      values_list[1] = screen->white_pixel;
      values_list[2] = font;
      cookie_gc = xcb_create_gc_checked (c, gc, window, mask, values_list);
      error = xcb_request_check (c, cookie_gc);
      if (error) {
        fprintf (stderr, "ERROR: can't create gc : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    
      mask = XCB_CW_CURSOR;
      value_list = cursor;
      xcb_change_window_attributes (c, window, mask, &value_list);
    
      xcb_free_cursor (c, cursor);
    
      cookie_font = xcb_close_font_checked (c, font);
      error = xcb_request_check (c, cookie_font);
      if (error) {
        fprintf (stderr, "ERROR: can't close font : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    }
    
    int main ()
    {
      xcb_screen_iterator_t screen_iter;
      xcb_connection_t     *c;
      const xcb_setup_t    *setup;
      xcb_screen_t         *screen;
      xcb_generic_event_t  *e;
      xcb_generic_error_t  *error;
      xcb_void_cookie_t     cookie_window;
      xcb_void_cookie_t     cookie_map;
      xcb_window_t          window;
      uint32_t              mask;
      uint32_t              values[2];
      int                   screen_number;
      uint8_t               is_hand = 0;
    
      /* getting the connection */
      c = xcb_connect (NULL, &screen_number);
      if (!c) {
        fprintf (stderr, "ERROR: can't connect to an X server\n");
        return -1;
      }
    
      /* getting the current screen */
      setup = xcb_get_setup (c);
    
      screen = NULL;
      screen_iter = xcb_setup_roots_iterator (setup);
      for (; screen_iter.rem != 0; --screen_number, xcb_screen_next (&screen_iter))
        if (screen_number == 0)
          {
            screen = screen_iter.data;
            break;
          }
      if (!screen) {
        fprintf (stderr, "ERROR: can't get the current screen\n");
        xcb_disconnect (c);
        return -1;
      }
    
      /* creating the window */
      window = xcb_generate_id (c);
      mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
      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;
      cookie_window = xcb_create_window_checked (c,
                                                 screen->root_depth,
                                                 window, screen->root,
                                                 20, 200, WIDTH, HEIGHT,
                                                 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
                                                 screen->root_visual,
                                                 mask, values);
      cookie_map = xcb_map_window_checked (c, window);
    
      /* error managing */
      error = xcb_request_check (c, cookie_window);
      if (error) {
        fprintf (stderr, "ERROR: can't create window : %d\n", error->error_code);
        xcb_disconnect (c);
        return -1;
      }
      error = xcb_request_check (c, cookie_map);
      if (error) {
        fprintf (stderr, "ERROR: can't map window : %d\n", error->error_code);
        xcb_disconnect (c);
        return -1;
      }
    
      cursor_set (c, screen, window, 68);
    
      xcb_flush(c);
    
      while (1) {
        e = xcb_poll_for_event(c);
        if (e) {
          switch (e->response_type & ~0x80) {
          case XCB_EXPOSE: {
            char *text;
    
            text = "click here to change cursor";
            button_draw (c, screen, window,
                         (WIDTH - 7 * strlen(text)) / 2,
                         (HEIGHT - 16) / 2, text);
    
            text = "Press ESC key to exit...";
            text_draw (c, screen, window, 10, HEIGHT - 10, text);
            break;
          }
          case XCB_BUTTON_PRESS: {
            xcb_button_press_event_t *ev;
            int                       length;
    
            ev = (xcb_button_press_event_t *)e;
            length = strlen ("click here to change cursor");
    
            if ((ev->event_x >= (WIDTH - 7 * length) / 2) &&
                (ev->event_x <= ((WIDTH - 7 * length) / 2 + 7 * length + 6)) &&
                (ev->event_y >= (HEIGHT - 16) / 2 - 19) &&
                (ev->event_y <= ((HEIGHT - 16) / 2)))
              is_hand = 1 - is_hand;
    
            is_hand ? cursor_set (c, screen, window, 58) : cursor_set (c, screen, window, 68);
          }
          case XCB_KEY_RELEASE: {
            xcb_key_release_event_t *ev;
    
            ev = (xcb_key_release_event_t *)e;
    
            switch (ev->detail) {
              /* ESC */
            case 9:
              free (e);
              xcb_disconnect (c);
              return 0;
            }
          }
          }
          free (e);
        }
      }
    
      return 0;
    }
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    
    #include <xcb/xcb.h>
    
    #define WIDTH 300
    #define HEIGHT 150
    
    
    
    static xcb_gc_t gc_font_get (xcb_connection_t *c,
                                 xcb_screen_t     *screen,
                                 xcb_window_t      window,
                                 const char       *font_name);
    
    static void button_draw (xcb_connection_t *c,
                             xcb_screen_t     *screen,
                             xcb_window_t      window,
                             int16_t           x1,
                             int16_t           y1,
                             const char       *label);
    
    static void text_draw (xcb_connection_t *c,
                           xcb_screen_t     *screen,
                           xcb_window_t      window,
                           int16_t           x1,
                           int16_t           y1,
                           const char       *label);
    
    static void cursor_set (xcb_connection_t *c,
                            xcb_screen_t     *screen,
                            xcb_window_t      window,
                            int               cursor_id);
    
    
    static void
    button_draw (xcb_connection_t *c,
                 xcb_screen_t     *screen,
                 xcb_window_t      window,
                 int16_t           x1,
                 int16_t           y1,
                 const char       *label)
    {
      xcb_point_t          points[5];
      xcb_void_cookie_t    cookie_gc;
      xcb_void_cookie_t    cookie_line;
      xcb_void_cookie_t    cookie_text;
      xcb_generic_error_t *error;
      xcb_gcontext_t       gc;
      int16_t              width;
      int16_t              height;
      uint8_t              length;
      int16_t              inset;
    
      length = strlen (label);
      inset = 2;
    
      gc = gc_font_get(c, screen, window, "7x13");
    
      width = 7 * length + 2 * (inset + 1);
      height = 13 + 2 * (inset + 1);
      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;
      cookie_line = xcb_poly_line_checked (c, XCB_COORD_MODE_ORIGIN,
                                           window, gc, 5, points);
    
      error = xcb_request_check (c, cookie_line);
      if (error) {
        fprintf (stderr, "ERROR: can't draw lines : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    
      cookie_text = xcb_image_text_8_checked (c, length, window, gc,
                                              x1 + inset + 1,
                                              y1 - inset - 1, label);
      error = xcb_request_check (c, cookie_text);
      if (error) {
        fprintf (stderr, "ERROR: can't paste text : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    
      cookie_gc = xcb_free_gc (c, gc);
      error = xcb_request_check (c, cookie_gc);
      if (error) {
        fprintf (stderr, "ERROR: can't free gc : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    }
    
    static void
    text_draw (xcb_connection_t *c,
               xcb_screen_t     *screen,
               xcb_window_t      window,
               int16_t           x1,
               int16_t           y1,
               const char       *label)
    {
      xcb_void_cookie_t    cookie_gc;
      xcb_void_cookie_t    cookie_text;
      xcb_generic_error_t *error;
      xcb_gcontext_t       gc;
      uint8_t              length;
    
      length = strlen (label);
    
      gc = gc_font_get(c, screen, window, "7x13");
    
      cookie_text = xcb_image_text_8_checked (c, length, window, gc,
                                              x1,
                                              y1, label);
      error = xcb_request_check (c, cookie_text);
      if (error) {
        fprintf (stderr, "ERROR: can't paste text : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    
      cookie_gc = xcb_free_gc (c, gc);
      error = xcb_request_check (c, cookie_gc);
      if (error) {
        fprintf (stderr, "ERROR: can't free gc : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    }
    
    static xcb_gc_t
    gc_font_get (xcb_connection_t *c,
                 xcb_screen_t     *screen,
                 xcb_window_t      window,
                 const char       *font_name)
    {
      uint32_t             value_list[3];
      xcb_void_cookie_t    cookie_font;
      xcb_void_cookie_t    cookie_gc;
      xcb_generic_error_t *error;
      xcb_font_t           font;
      xcb_gcontext_t       gc;
      uint32_t             mask;
    
      font = xcb_generate_id (c);
      cookie_font = xcb_open_font_checked (c, font,
                                           strlen (font_name),
                                           font_name);
    
      error = xcb_request_check (c, cookie_font);
      if (error) {
        fprintf (stderr, "ERROR: can't open font : %d\n", error->error_code);
        xcb_disconnect (c);
        return -1;
      }
    
      gc = xcb_generate_id (c);
      mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
      value_list[0] = screen->black_pixel;
      value_list[1] = screen->white_pixel;
      value_list[2] = font;
      cookie_gc = xcb_create_gc_checked (c, gc, window, mask, value_list);
      error = xcb_request_check (c, cookie_gc);
      if (error) {
        fprintf (stderr, "ERROR: can't create gc : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    
      cookie_font = xcb_close_font_checked (c, font);
      error = xcb_request_check (c, cookie_font);
      if (error) {
        fprintf (stderr, "ERROR: can't close font : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    
      return gc;
    }
    
    static void
    cursor_set (xcb_connection_t *c,
                xcb_screen_t     *screen,
                xcb_window_t      window,
                int               cursor_id)
    {
      uint32_t             values_list[3];
      xcb_void_cookie_t    cookie_font;
      xcb_void_cookie_t    cookie_gc;
      xcb_generic_error_t *error;
      xcb_font_t           font;
      xcb_cursor_t         cursor;
      xcb_gcontext_t       gc;
      uint32_t             mask;
      uint32_t             value_list;
    
      font = xcb_generate_id (c);
      cookie_font = xcb_open_font_checked (c, font,
                                           strlen ("cursor"),
                                           "cursor");
      error = xcb_request_check (c, cookie_font);
      if (error) {
        fprintf (stderr, "ERROR: can't open font : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    
      cursor = xcb_generate_id (c);
      xcb_create_glyph_cursor (c, cursor, font, font,
                               cursor_id, cursor_id + 1,
                               0, 0, 0,
                               0, 0, 0);
    
      gc = xcb_generate_id (c);
      mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
      values_list[0] = screen->black_pixel;
      values_list[1] = screen->white_pixel;
      values_list[2] = font;
      cookie_gc = xcb_create_gc_checked (c, gc, window, mask, values_list);
      error = xcb_request_check (c, cookie_gc);
      if (error) {
        fprintf (stderr, "ERROR: can't create gc : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    
      mask = XCB_CW_CURSOR;
      value_list = cursor;
      xcb_change_window_attributes (c, window, mask, &value_list);
    
      xcb_free_cursor (c, cursor);
    
      cookie_font = xcb_close_font_checked (c, font);
      error = xcb_request_check (c, cookie_font);
      if (error) {
        fprintf (stderr, "ERROR: can't close font : %d\n", error->error_code);
        xcb_disconnect (c);
        exit (-1);
      }
    }
    
    int main ()
    {
      xcb_screen_iterator_t screen_iter;
      xcb_connection_t     *c;
      const xcb_setup_t    *setup;
      xcb_screen_t         *screen;
      xcb_generic_event_t  *e;
      xcb_generic_error_t  *error;
      xcb_void_cookie_t     cookie_window;
      xcb_void_cookie_t     cookie_map;
      xcb_window_t          window;
      uint32_t              mask;
      uint32_t              values[2];
      int                   screen_number;
      uint8_t               is_hand = 0;
    
      /* getting the connection */
      c = xcb_connect (NULL, &screen_number);
      if (!c) {
        fprintf (stderr, "ERROR: can't connect to an X server\n");
        return -1;
      }
    
      /* getting the current screen */
      setup = xcb_get_setup (c);
    
      screen = NULL;
      screen_iter = xcb_setup_roots_iterator (setup);
      for (; screen_iter.rem != 0; --screen_number, xcb_screen_next (&screen_iter))
        if (screen_number == 0)
          {
            screen = screen_iter.data;
            break;
          }
      if (!screen) {
        fprintf (stderr, "ERROR: can't get the current screen\n");
        xcb_disconnect (c);
        return -1;
      }
    
      /* creating the window */
      window = xcb_generate_id (c);
      mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
      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;
      cookie_window = xcb_create_window_checked (c,
                                                 screen->root_depth,
                                                 window, screen->root,
                                                 20, 200, WIDTH, HEIGHT,
                                                 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
                                                 screen->root_visual,
                                                 mask, values);
      cookie_map = xcb_map_window_checked (c, window);
    
      /* error managing */
      error = xcb_request_check (c, cookie_window);
      if (error) {
        fprintf (stderr, "ERROR: can't create window : %d\n", error->error_code);
        xcb_disconnect (c);
        return -1;
      }
      error = xcb_request_check (c, cookie_map);
      if (error) {
        fprintf (stderr, "ERROR: can't map window : %d\n", error->error_code);
        xcb_disconnect (c);
        return -1;
      }
    
      cursor_set (c, screen, window, 68);
    
      xcb_flush(c);
    
      while (1) {
        e = xcb_poll_for_event(c);
        if (e) {
          switch (e->response_type & ~0x80) {
          case XCB_EXPOSE: {
            char *text;
    
            text = "click here to change cursor";
            button_draw (c, screen, window,
                         (WIDTH - 7 * strlen(text)) / 2,
                         (HEIGHT - 16) / 2, text);
    
            text = "Press ESC key to exit...";
            text_draw (c, screen, window, 10, HEIGHT - 10, text);
            break;
          }
          case XCB_BUTTON_PRESS: {
            xcb_button_press_event_t *ev;
            int                       length;
    
            ev = (xcb_button_press_event_t *)e;
            length = strlen ("click here to change cursor");
    
            if ((ev->event_x >= (WIDTH - 7 * length) / 2) &&
                (ev->event_x <= ((WIDTH - 7 * length) / 2 + 7 * length + 6)) &&
                (ev->event_y >= (HEIGHT - 16) / 2 - 19) &&
                (ev->event_y <= ((HEIGHT - 16) / 2)))
              is_hand = 1 - is_hand;
    
            is_hand ? cursor_set (c, screen, window, 58) : cursor_set (c, screen, window, 68);
          }
          case XCB_KEY_RELEASE: {
            xcb_key_release_event_t *ev;
    
            ev = (xcb_key_release_event_t *)e;
    
            switch (ev->detail) {
              /* ESC */
            case 9:
              free (e);
              xcb_disconnect (c);
              return 0;
            }
          }
          }
          free (e);
        }
      }
    
      return 0;
    }