]> git.sur5r.net Git - i3/i3/blob - i3bar/src/xcb.c
Only copy the double-buffer on expose-events
[i3/i3] / i3bar / src / xcb.c
1 /*
2  * i3bar - an xcb-based status- and ws-bar for i3
3  *
4  * © 2010 Axel Wagner and contributors
5  *
6  * See file LICNSE for license information
7  *
8  * src/xcb.c: Communicating with X
9  *
10  */
11 #include <xcb/xcb.h>
12 #include <xcb/xproto.h>
13 #include <xcb/xcb_event.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <i3/ipc.h>
18 #include <ev.h>
19
20 #include "common.h"
21
22 /* We save the Atoms in an easy to access array, indexed by an enum */
23 #define NUM_ATOMS 3
24
25 enum {
26     #define ATOM_DO(name) name,
27     #include "xcb_atoms.def"
28 };
29
30 xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
31 xcb_atom_t               atoms[NUM_ATOMS];
32
33 /* Variables, that are the same for all functions at all times */
34 xcb_connection_t *xcb_connection;
35 xcb_screen_t     *xcb_screens;
36 xcb_window_t     xcb_root;
37 xcb_font_t       xcb_font;
38
39 /* Event-Watchers, to interact with the user */
40 ev_prepare *xcb_prep;
41 ev_check   *xcb_chk;
42 ev_io      *xcb_io;
43
44 /*
45  * Converts a colorstring to a colorpixel as expected from xcb_change_gc.
46  * s is assumed to be in the format "rrggbb"
47  *
48  */
49 uint32_t get_colorpixel(const char *s) {
50     char strings[3][3] = { { s[0], s[1], '\0'} ,
51                            { s[2], s[3], '\0'} ,
52                            { s[4], s[5], '\0'} };
53     uint8_t r = strtol(strings[0], NULL, 16);
54     uint8_t g = strtol(strings[1], NULL, 16);
55     uint8_t b = strtol(strings[2], NULL, 16);
56     return (r << 16 | g << 8 | b);
57 }
58
59 /*
60  * Handle a button-press-event (i.c. a mouse click on one of our bars).
61  * We determine, wether the click occured on a ws-button or if the scroll-
62  * wheel was used and change the workspace appropriately
63  *
64  */
65 void handle_button(xcb_button_press_event_t *event) {
66     i3_ws *cur_ws;
67
68     /* Determine, which bar was clicked */
69     i3_output *walk;
70     xcb_window_t bar = event->event;
71     SLIST_FOREACH(walk, outputs, slist) {
72         if (walk->bar == bar) {
73             break;
74         }
75     }
76
77     if (walk == NULL) {
78         printf("Unknown Bar klicked!\n");
79         return;
80     }
81
82     /* TODO: Move this to exern get_ws_for_output() */
83     TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) {
84         if (cur_ws->visible) {
85             break;
86         }
87     }
88
89     if (cur_ws == NULL) {
90         printf("No Workspace active?\n");
91         return;
92     }
93
94     int32_t x = event->event_x;
95
96     printf("Got Button %d\n", event->detail);
97
98     switch (event->detail) {
99         case 1:
100             /* Left Mousbutton. We determine, which button was clicked
101              * and set cur_ws accordingly */
102             TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) {
103                 printf("x = %d\n", x);
104                 if (x < cur_ws->name_width + 10) {
105                     break;
106                 }
107                 x -= cur_ws->name_width + 10;
108             }
109             if (cur_ws == NULL) {
110                 return;
111             }
112             break;
113         case 4:
114             /* Mouse wheel down. We select the next ws */
115             if (cur_ws == TAILQ_FIRST(walk->workspaces)) {
116                 cur_ws = TAILQ_LAST(walk->workspaces, ws_head);
117             } else {
118                 cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq);
119             }
120             break;
121         case 5:
122             /* Mouse wheel up. We select the previos ws */
123             if (cur_ws == TAILQ_LAST(walk->workspaces, ws_head)) {
124                 cur_ws = TAILQ_FIRST(walk->workspaces);
125             } else {
126                 cur_ws = TAILQ_NEXT(cur_ws, tailq);
127             }
128             break;
129     }
130
131     char buffer[50];
132     snprintf(buffer, 50, "%d", cur_ws->num);
133     i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, buffer);
134 }
135
136 /*
137  * This function is called immediately bevor the main loop locks. We flush xcb
138  * then (and only then)
139  *
140  */
141 void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revenst) {
142     xcb_flush(xcb_connection);
143 }
144
145 /*
146  * This function is called immediately after the main loop locks, so when one
147  * of the watchers registered an event.
148  * We check wether an X-Event arrived and handle it.
149  *
150  */
151 void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
152     xcb_generic_event_t *event;
153     while ((event = xcb_poll_for_event(xcb_connection)) == NULL) {
154         return;
155     }
156
157     switch (event->response_type & ~0x80) {
158         case XCB_EXPOSE:
159             /* Expose-events happen, when the window needs to be redrawn */
160             redraw_bars();
161             break;
162         case XCB_BUTTON_PRESS:
163             /* Button-press-events are mouse-buttons clicked on one of our bars */
164             handle_button((xcb_button_press_event_t*) event);
165             break;
166     }
167     FREE(event);
168 }
169
170 /*
171  * Dummy Callback. We only need this, so that the Prepare- and Check-Watchers
172  * are triggered
173  *
174  */
175 void xcb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
176 }
177
178 /*
179  * Calculate the rendered width of a string with the configured font.
180  * The string has to be encoded in ucs2 and glyph_len has to be the length
181  * of the string (in width)
182  *
183  */
184 int get_string_width(xcb_char2b_t *string, int glyph_len) {
185     xcb_query_text_extents_cookie_t cookie;
186     xcb_query_text_extents_reply_t *reply;
187     xcb_generic_error_t *error = NULL;
188     int width;
189
190     cookie = xcb_query_text_extents(xcb_connection, xcb_font, glyph_len, string);
191     reply = xcb_query_text_extents_reply(xcb_connection, cookie, &error);
192     if (error != NULL) {
193         printf("ERROR: Could not get text extents! XCB-errorcode: %d\n", error->error_code);
194         exit(EXIT_FAILURE);
195     }
196
197     width = reply->overall_width;
198     free(reply);
199     return width;
200 }
201
202 /*
203  * Initialize xcb and use the specified fontname for text-rendering
204  *
205  */
206 void init_xcb(char *fontname) {
207     /* FIXME: xcb_connect leaks Memory */
208     xcb_connection = xcb_connect(NULL, NULL);
209     if (xcb_connection_has_error(xcb_connection)) {
210         printf("Cannot open display\n");
211         exit(EXIT_FAILURE);
212     }
213     printf("Connected to xcb\n");
214
215     /* We have to request the atoms we need */
216     #define ATOM_DO(name) atom_cookies[name] = xcb_intern_atom(xcb_connection, 0, strlen(#name), #name);
217     #include "xcb_atoms.def"
218
219     xcb_screens = xcb_setup_roots_iterator(xcb_get_setup(xcb_connection)).data;
220     xcb_root = xcb_screens->root;
221
222     /* We load and allocate the font */
223     xcb_font = xcb_generate_id(xcb_connection);
224     xcb_void_cookie_t open_font_cookie;
225     open_font_cookie = xcb_open_font_checked(xcb_connection,
226                                              xcb_font,
227                                              strlen(fontname),
228                                              fontname);
229
230     /* We also need the fontheight to configure our bars accordingly */
231     xcb_list_fonts_with_info_cookie_t font_info_cookie;
232     font_info_cookie = xcb_list_fonts_with_info(xcb_connection,
233                                                 1,
234                                                 strlen(fontname),
235                                                 fontname);
236
237     /* The varios Watchers to communicate with xcb */
238     xcb_io = malloc(sizeof(ev_io));
239     xcb_prep = malloc(sizeof(ev_prepare));
240     xcb_chk = malloc(sizeof(ev_check));
241
242     ev_io_init(xcb_io, &xcb_io_cb, xcb_get_file_descriptor(xcb_connection), EV_READ);
243     ev_prepare_init(xcb_prep, &xcb_prep_cb);
244     ev_check_init(xcb_chk, &xcb_chk_cb);
245
246     ev_io_start(main_loop, xcb_io);
247     ev_prepare_start(main_loop, xcb_prep);
248     ev_check_start(main_loop, xcb_chk);
249
250     /* Now we get the atoms and save them in a nice data-structure */
251     get_atoms();
252
253     xcb_generic_error_t *err = xcb_request_check(xcb_connection,
254                                                  open_font_cookie);
255
256     if (err != NULL) {
257         printf("ERROR: Could not open font! XCB-Error-Code: %d\n", err->error_code);
258         exit(EXIT_FAILURE);
259     }
260
261     /* Now we calculate the font-height */
262     xcb_list_fonts_with_info_reply_t *reply;
263     reply = xcb_list_fonts_with_info_reply(xcb_connection,
264                                            font_info_cookie,
265                                            NULL);
266     font_height = reply->font_ascent + reply->font_descent;
267     FREE(reply);
268
269     printf("Calculated Font-height: %d\n", font_height);
270 }
271
272 /*
273  * Cleanup the xcb-stuff.
274  * Called once, before the program terminates.
275  *
276  */
277 void clean_xcb() {
278     i3_output *walk;
279     SLIST_FOREACH(walk, outputs, slist) {
280         destroy_window(walk);
281     }
282     FREE_SLIST(outputs, i3_output);
283
284     xcb_disconnect(xcb_connection);
285
286     ev_check_stop(main_loop, xcb_chk);
287     ev_prepare_stop(main_loop, xcb_prep);
288     ev_io_stop(main_loop, xcb_io);
289
290     FREE(xcb_chk);
291     FREE(xcb_prep);
292     FREE(xcb_io);
293 }
294
295 /*
296  * Get the earlier requested atoms and save them in the prepared data-structure
297  *
298  */
299 void get_atoms() {
300     xcb_intern_atom_reply_t *reply;
301     #define ATOM_DO(name) reply = xcb_intern_atom_reply(xcb_connection, atom_cookies[name], NULL); \
302         if (reply == NULL) { \
303             printf("ERROR: Could not get atom %s\n", #name); \
304             exit(EXIT_FAILURE); \
305         } \
306         atoms[name] = reply->atom; \
307         free(reply);
308
309     #include "xcb_atoms.def"
310     printf("Got Atoms\n");
311 }
312
313 /*
314  * Destroy the bar of the specified output
315  *
316  */
317 void destroy_window(i3_output *output) {
318     if (output == NULL) {
319         return;
320     }
321     if (output->bar == XCB_NONE) {
322         return;
323     }
324     xcb_destroy_window(xcb_connection, output->bar);
325     output->bar = XCB_NONE;
326 }
327
328 /*
329  * Reconfigure all bars and create new for newly activated outputs
330  *
331  */
332 void reconfig_windows() {
333     uint32_t mask;
334     uint32_t values[4];
335
336     xcb_generic_error_t *err;
337
338     i3_output *walk;
339     SLIST_FOREACH(walk, outputs, slist) {
340         if (!walk->active) {
341             /* If an output is not active, we destroy it's bar */
342             /* FIXME: Maybe we rather want to unmap? */
343             printf("Destroying window for output %s\n", walk->name);
344             destroy_window(walk);
345             continue;
346         }
347         if (walk->bar == XCB_NONE) {
348             printf("Creating Window for output %s\n", walk->name);
349
350             walk->bar = xcb_generate_id(xcb_connection);
351             walk->buffer = xcb_generate_id(xcb_connection);
352             mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
353             /* Black background */
354             values[0] = xcb_screens->black_pixel;
355             /* The events we want to receive */
356             values[1] = XCB_EVENT_MASK_EXPOSURE |
357                         XCB_EVENT_MASK_BUTTON_PRESS;
358
359             xcb_void_cookie_t win_cookie = xcb_create_window_checked(xcb_connection,
360                                                                      xcb_screens->root_depth,
361                                                                      walk->bar,
362                                                                      xcb_root,
363                                                                      walk->rect.x, walk->rect.y,
364                                                                      walk->rect.w, font_height + 6,
365                                                                      1,
366                                                                      XCB_WINDOW_CLASS_INPUT_OUTPUT,
367                                                                      xcb_screens->root_visual,
368                                                                      mask,
369                                                                      values);
370             
371             xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection,
372                                                                     xcb_screens->root_depth,
373                                                                     walk->buffer,
374                                                                     walk->bar,
375                                                                     walk->rect.w,
376                                                                     walk->rect.h);
377
378             /* We want dock-windows (for now) */
379             xcb_void_cookie_t prop_cookie = xcb_change_property(xcb_connection,
380                                                                 XCB_PROP_MODE_REPLACE,
381                                                                 walk->bar,
382                                                                 atoms[_NET_WM_WINDOW_TYPE],
383                                                                 atoms[ATOM],
384                                                                 32,
385                                                                 1,
386                                                                 (unsigned char*) &atoms[_NET_WM_WINDOW_TYPE_DOCK]);
387
388             /* We also want a graphics-context (the "canvas" on which we draw) */
389             walk->bargc = xcb_generate_id(xcb_connection);
390             mask = XCB_GC_FONT;
391             values[0] = xcb_font;
392             xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(xcb_connection,
393                                                                 walk->bargc,
394                                                                 walk->bar,
395                                                                 mask,
396                                                                 values);
397
398             /* We finally map the bar (display it on screen) */
399             xcb_void_cookie_t map_cookie = xcb_map_window_checked(xcb_connection, walk->bar);
400
401             if ((err = xcb_request_check(xcb_connection, win_cookie)) != NULL) {
402                 printf("ERROR: Could not create Window. XCB-errorcode: %d\n", err->error_code);
403                 exit(EXIT_FAILURE);
404             }
405
406             if ((err = xcb_request_check(xcb_connection, pm_cookie)) != NULL) {
407                 printf("ERROR: Could not create Pixmap. XCB-errorcode: %d\n", err->error_code);
408                 exit(EXIT_FAILURE);
409             }
410  
411             if ((err = xcb_request_check(xcb_connection, prop_cookie)) != NULL) {
412                 printf("ERROR: Could not set dock mode. XCB-errorcode: %d\n", err->error_code);
413                 exit(EXIT_FAILURE);
414             }
415
416             if ((err = xcb_request_check(xcb_connection, gc_cookie)) != NULL) {
417                 printf("ERROR: Could not create graphical context. XCB-errorcode: %d\n", err->error_code);
418                 exit(EXIT_FAILURE);
419             }
420
421             if ((err = xcb_request_check(xcb_connection, map_cookie)) != NULL) {
422                 printf("ERROR: Could not map window. XCB-errorcode: %d\n", err->error_code);
423                 exit(EXIT_FAILURE);
424             }
425         } else {
426             /* We already have a bar, so we just reconfigure it */
427             mask = XCB_CONFIG_WINDOW_X |
428                    XCB_CONFIG_WINDOW_Y |
429                    XCB_CONFIG_WINDOW_WIDTH |
430                    XCB_CONFIG_WINDOW_HEIGHT;
431             values[0] = walk->rect.x;
432             values[1] = walk->rect.y + walk->rect.h - font_height - 6;
433             values[2] = walk->rect.w;
434             values[3] = font_height + 6;
435             printf("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]);
436             xcb_void_cookie_t cfg_cookie = xcb_configure_window_checked(xcb_connection,
437                                                                         walk->bar,
438                                                                         mask,
439                                                                         values);
440
441             xcb_free_pixmap(xcb_connection, walk->buffer);
442             walk->buffer = xcb_generate_id(xcb_connection);
443
444             xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection,
445                                                                     xcb_screens->root_depth,
446                                                                     walk->buffer,
447                                                                     walk->bar,
448                                                                     walk->rect.w,
449                                                                     walk->rect.h);
450
451             if ((err = xcb_request_check(xcb_connection, cfg_cookie)) != NULL) {
452                 printf("ERROR: Could not reconfigure window. XCB-errorcode: %d\n", err->error_code);
453                 exit(EXIT_FAILURE);
454             }
455
456             if ((err = xcb_request_check(xcb_connection, pm_cookie)) != NULL) {
457                 printf("ERROR: Could not create Pixmap. XCB-errorcode: %d\n", err->error_code);
458                 exit(EXIT_FAILURE);
459             }
460          }
461     }
462 }
463
464 /*
465  * Render the bars, with buttons and statusline
466  *
467  */
468 void draw_bars() {
469     printf("Drawing Bars...\n");
470     int i = 0;
471     i3_output *outputs_walk;
472     SLIST_FOREACH(outputs_walk, outputs, slist) {
473         if (!outputs_walk->active) {
474             printf("Output %s inactive, skipping...\n", outputs_walk->name);
475             continue;
476         }
477         if (outputs_walk->bar == XCB_NONE) {
478             reconfig_windows();
479         }
480         uint32_t color = get_colorpixel("000000");
481         xcb_change_gc(xcb_connection,
482                       outputs_walk->bargc,
483                       XCB_GC_FOREGROUND,
484                       &color);
485         xcb_rectangle_t rect = { 0, 0, outputs_walk->rect.w, font_height + 6 };
486         xcb_poly_fill_rectangle(xcb_connection,
487                                 outputs_walk->buffer,
488                                 outputs_walk->bargc,
489                                 1,
490                                 &rect);
491         if (statusline != NULL) {
492             printf("Printing statusline!\n");
493             xcb_change_gc(xcb_connection,
494                           outputs_walk->bargc,
495                           XCB_GC_BACKGROUND,
496                           &color);
497             color = get_colorpixel("FFFFFF");
498             xcb_change_gc(xcb_connection,
499                           outputs_walk->bargc,
500                           XCB_GC_FOREGROUND,
501                           &color);
502
503             int glyph_count;
504             xcb_char2b_t *text = (xcb_char2b_t*) convert_utf8_to_ucs2(statusline, &glyph_count);
505
506             xcb_void_cookie_t cookie;
507             cookie = xcb_image_text_16(xcb_connection,
508                                        glyph_count,
509                                        outputs_walk->buffer,
510                                        outputs_walk->bargc,
511                                        outputs_walk->rect.w - get_string_width(text, glyph_count) - 4,
512                                        font_height + 1,
513                                        (xcb_char2b_t*) text);
514
515             xcb_generic_error_t *err = xcb_request_check(xcb_connection, cookie);
516
517             if (err != NULL) {
518                 printf("XCB-Error: %d\n", err->error_code);
519             }
520         }
521         i3_ws *ws_walk;
522         TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) {
523             printf("Drawing Button for WS %s at x = %d\n", ws_walk->name, i);
524             uint32_t color = get_colorpixel("240000");
525             if (ws_walk->visible) {
526                 color = get_colorpixel("480000");
527             }
528             if (ws_walk->urgent) {
529                 printf("WS %s is urgent!\n", ws_walk->name);
530                 color = get_colorpixel("002400");
531             }
532             xcb_change_gc(xcb_connection,
533                           outputs_walk->bargc,
534                           XCB_GC_FOREGROUND,
535                           &color);
536             xcb_change_gc(xcb_connection,
537                           outputs_walk->bargc,
538                           XCB_GC_BACKGROUND,
539                           &color);
540             xcb_rectangle_t rect = { i + 1, 1, ws_walk->name_width + 8, font_height + 4 };
541             xcb_poly_fill_rectangle(xcb_connection,
542                                     outputs_walk->buffer,
543                                     outputs_walk->bargc,
544                                     1,
545                                     &rect);
546             color = get_colorpixel("FFFFFF");
547             xcb_change_gc(xcb_connection,
548                           outputs_walk->bargc,
549                           XCB_GC_FOREGROUND,
550                           &color);
551             xcb_image_text_16(xcb_connection,
552                               ws_walk->name_glyphs,
553                               outputs_walk->buffer,
554                               outputs_walk->bargc,
555                               i + 5, font_height + 1,
556                               ws_walk->ucs2_name);
557             i += 10 + ws_walk->name_width;
558         }
559
560         xcb_copy_area(xcb_connection,
561                       outputs_walk->buffer,
562                       outputs_walk->bar,
563                       outputs_walk->bargc,
564                       0, 0,
565                       0, 0,
566                       outputs_walk->rect.w,
567                       outputs_walk->rect.h);
568
569         i = 0;
570     }
571 }
572
573 /*
574  * Redraw the bars, i.e. simply copy the buffer to the barwindow
575  *
576  */
577 void redraw_bars() {
578     i3_output *outputs_walk;
579     SLIST_FOREACH(outputs_walk, outputs, slist) {
580         xcb_copy_area(xcb_connection,
581                       outputs_walk->buffer,
582                       outputs_walk->bar,
583                       outputs_walk->bargc,
584                       0, 0,
585                       0, 0,
586                       outputs_walk->rect.w,
587                       outputs_walk->rect.h);
588         xcb_flush(xcb_connection);
589     }            
590 }