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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <xcb/xcb.h>
#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;
}