summaryrefslogtreecommitdiff
path: root/tutorial/mousecursors.mdwn
blob: 8109920b082d0bdcc3a35ca0f138c9263651ac32 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# 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;
		}