# 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 often 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 they 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 *connection, xcb_cursor_t cursorId, 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 resource, we have to ask for an X id with xcb\_generate\_id first: xcb_font_t font = xcb_generate_id (connection); xcb_open_font (connection, font, strlen ("cursor"), "cursor"); cursor = xcb_generate_id (connection); xcb_create_glyph_cursor (connection, cursor, /* cursor id */ source_font, /* source glyph font */ mask_font, /* mask glyph font */ 58, /* source character glyph */ 58 + 1, /* mask character glyph */ 0, 0, 0, 0, 0, 0); /* r b g r b g */ 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 freed by using the function: xcb_void_cookie_t xcb_free_cursor (xcb_connection_t *connection, xcb_cursor_t cursor ); In the second method, we create a new cursor by using a pair of pixmaps with bit depth of one (that is, two-color pixmaps). One pixmap defines the shape of the cursor while the other works as a mask that specifies 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\_CW\_CURSOR attribute: /* ...assume cursor created here... */ uint32_t mask = XCB_CW_CURSOR; uint32_t value_list = cursor; xcb_change_window_attributes (connection, 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 #include #include #include #include #define WIDTH 300 #define HEIGHT 150 static void testCookie(xcb_void_cookie_t, xcb_connection_t*, char *); static void drawButton(xcb_connection_t*, xcb_screen_t*, xcb_window_t, int16_t, int16_t, const char*); static void drawText(xcb_connection_t*, xcb_screen_t*, xcb_window_t, int16_t, int16_t, const char*); static xcb_gc_t getFontGC(xcb_connection_t*, xcb_screen_t*, xcb_window_t, const char*); static void setCursor (xcb_connection_t*, xcb_screen_t*, xcb_window_t, int); /* */ 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 : %"PRIu8"\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, "fixed"); 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, "fixed"); 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 ) { 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); uint32_t 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 () { /* get the connection */ int screenNum; xcb_connection_t *connection = xcb_connect (NULL, &screenNum); if (!connection) { fprintf (stderr, "ERROR: can't connect to an X server\n"); return -1; } /* get the current screen */ xcb_screen_iterator_t iter = xcb_setup_roots_iterator (xcb_get_setup (connection)); /* we want the screen at index screenNum of the iterator */ for (int 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; } /* create the window */ 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); /* event loop */ 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) { /* ESC */ case 9: free (event); xcb_disconnect (connection); return 0; } } } free (event); } } return 0; }