]> git.sur5r.net Git - i3/i3/blob - i3bar/src/xcb.c
Add declaration, accidentally lost in last commit
[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             draw_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;
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 (reply == NULL) {
193         printf("ERROR: Could not get text extents!");
194         return 7;
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 cookie;
232     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                                            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         atoms[name] = reply->atom; \
303         free(reply);
304
305     #include "xcb_atoms.def"
306     printf("Got Atoms\n");
307 }
308
309 /*
310  * Destroy the bar of the specified output
311  *
312  */
313 void destroy_window(i3_output *output) {
314     if (output == NULL) {
315         return;
316     }
317     if (output->bar == XCB_NONE) {
318         return;
319     }
320     xcb_destroy_window(xcb_connection, output->bar);
321     output->bar = XCB_NONE;
322 }
323
324 /*
325  * Reconfigure all bars and create new for newly activated outputs
326  *
327  */
328 void reconfig_windows() {
329     uint32_t mask;
330     uint32_t values[4];
331
332     xcb_generic_error_t *err;
333
334     i3_output *walk;
335     SLIST_FOREACH(walk, outputs, slist) {
336         if (!walk->active) {
337             /* If an output is not active, we destroy it's bar */
338             /* FIXME: Maybe we rather want to unmap? */
339             printf("Destroying window for output %s\n", walk->name);
340             destroy_window(walk);
341             continue;
342         }
343         if (walk->bar == XCB_NONE) {
344             printf("Creating Window for output %s\n", walk->name);
345
346             walk->bar = xcb_generate_id(xcb_connection);
347             walk->buffer = xcb_generate_id(xcb_connection);
348             mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
349             /* Black background */
350             values[0] = xcb_screens->black_pixel;
351             /* The events we want to receive */
352             values[1] = XCB_EVENT_MASK_EXPOSURE |
353                         XCB_EVENT_MASK_BUTTON_PRESS;
354
355             xcb_void_cookie_t win_cookie = xcb_create_window_checked(xcb_connection,
356                                                                      xcb_screens->root_depth,
357                                                                      walk->bar,
358                                                                      xcb_root,
359                                                                      walk->rect.x, walk->rect.y,
360                                                                      walk->rect.w, font_height + 6,
361                                                                      1,
362                                                                      XCB_WINDOW_CLASS_INPUT_OUTPUT,
363                                                                      xcb_screens->root_visual,
364                                                                      mask,
365                                                                      values);
366             
367             xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection,
368                                                                     xcb_screens->root_depth,
369                                                                     walk->buffer,
370                                                                     walk->bar,
371                                                                     walk->rect.w,
372                                                                     walk->rect.h);
373
374             /* We want dock-windows (for now) */
375             xcb_void_cookie_t prop_cookie = xcb_change_property(xcb_connection,
376                                                                 XCB_PROP_MODE_REPLACE,
377                                                                 walk->bar,
378                                                                 atoms[_NET_WM_WINDOW_TYPE],
379                                                                 atoms[ATOM],
380                                                                 32,
381                                                                 1,
382                                                                 (unsigned char*) &atoms[_NET_WM_WINDOW_TYPE_DOCK]);
383
384             /* We also want a graphics-context (the "canvas" on which we draw) */
385             walk->bargc = xcb_generate_id(xcb_connection);
386             mask = XCB_GC_FONT;
387             values[0] = xcb_font;
388
389             xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(xcb_connection,
390                                                                 walk->bargc,
391                                                                 walk->bar,
392                                                                 mask,
393                                                                 values);
394
395             /* We finally map the bar (display it on screen) */
396             xcb_void_cookie_t map_cookie = xcb_map_window_checked(xcb_connection, walk->bar);
397
398             if ((err = xcb_request_check(xcb_connection, win_cookie)) != NULL) {
399                 printf("ERROR: Could not create Window. XCB-errorcode: %d\n", err->error_code);
400                 exit(EXIT_FAILURE);
401             }
402
403             if ((err = xcb_request_check(xcb_connection, pm_cookie)) != NULL) {
404                 printf("ERROR: Could not create Pixmap. XCB-errorcode: %d\n", err->error_code);
405                 exit(EXIT_FAILURE);
406             }
407  
408             if ((err = xcb_request_check(xcb_connection, prop_cookie)) != NULL) {
409                 printf("ERROR: Could not set dock mode. XCB-errorcode: %d\n", err->error_code);
410                 exit(EXIT_FAILURE);
411             }
412
413             if ((err = xcb_request_check(xcb_connection, gc_cookie)) != NULL) {
414                 printf("ERROR: Could not create graphical context. XCB-errorcode: %d\n", err->error_code);
415                 exit(EXIT_FAILURE);
416             }
417
418             if ((err = xcb_request_check(xcb_connection, map_cookie)) != NULL) {
419                 printf("ERROR: Could not map window. XCB-errorcode: %d\n", err->error_code);
420                 exit(EXIT_FAILURE);
421             }
422         } else {
423             /* We already have a bar, so we just reconfigure it */
424             mask = XCB_CONFIG_WINDOW_X |
425                    XCB_CONFIG_WINDOW_Y |
426                    XCB_CONFIG_WINDOW_WIDTH |
427                    XCB_CONFIG_WINDOW_HEIGHT;
428             values[0] = walk->rect.x;
429             values[1] = walk->rect.y + walk->rect.h - font_height - 6;
430             values[2] = walk->rect.w;
431             values[3] = font_height + 6;
432             printf("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]);
433
434             xcb_void_cookie_t cfg_cookie = xcb_configure_window_checked(xcb_connection,
435                                                                         walk->bar,
436                                                                         mask,
437                                                                         values);
438
439             xcb_free_pixmap(xcb_connection, walk->buffer);
440             walk->buffer = xcb_generate_id(xcb_connection);
441
442             xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection,
443                                                                     xcb_screens->root_depth,
444                                                                     walk->buffer,
445                                                                     walk->bar,
446                                                                     walk->rect.w,
447                                                                     walk->rect.h);
448
449             if ((err = xcb_request_check(xcb_connection, cfg_cookie)) != NULL) {
450                 printf("ERROR: Could not reconfigure window. XCB-errorcode: %d\n", err->error_code);
451                 exit(EXIT_FAILURE);
452             }
453
454             if ((err = xcb_request_check(xcb_connection, pm_cookie)) != NULL) {
455                 printf("ERROR: Could not create Pixmap. XCB-errorcode: %d\n", err->error_code);
456                 exit(EXIT_FAILURE);
457             }
458          }
459     }
460 }
461
462 /*
463  * Render the bars, with buttons and statusline
464  *
465  */
466 void draw_bars() {
467     printf("Drawing Bars...\n");
468     int i = 0;
469     i3_output *outputs_walk;
470     SLIST_FOREACH(outputs_walk, outputs, slist) {
471         if (!outputs_walk->active) {
472             printf("Output %s inactive, skipping...\n", outputs_walk->name);
473             continue;
474         }
475         if (outputs_walk->bar == XCB_NONE) {
476             reconfig_windows();
477         }
478         uint32_t color = get_colorpixel("000000");
479         xcb_change_gc(xcb_connection,
480                       outputs_walk->bargc,
481                       XCB_GC_FOREGROUND,
482                       &color);
483         xcb_rectangle_t rect = { 0, 0, outputs_walk->rect.w, font_height + 6 };
484         xcb_poly_fill_rectangle(xcb_connection,
485                                 outputs_walk->buffer,
486                                 outputs_walk->bargc,
487                                 1,
488                                 &rect);
489         if (statusline != NULL) {
490             printf("Printing statusline!\n");
491             xcb_change_gc(xcb_connection,
492                           outputs_walk->bargc,
493                           XCB_GC_BACKGROUND,
494                           &color);
495             color = get_colorpixel("FFFFFF");
496             xcb_change_gc(xcb_connection,
497                           outputs_walk->bargc,
498                           XCB_GC_FOREGROUND,
499                           &color);
500
501             int glyph_count;
502             xcb_char2b_t *text = (xcb_char2b_t*) convert_utf8_to_ucs2(statusline, &glyph_count);
503
504             xcb_void_cookie_t cookie;
505             cookie = xcb_image_text_16(xcb_connection,
506                                        glyph_count,
507                                        outputs_walk->buffer,
508                                        outputs_walk->bargc,
509                                        outputs_walk->rect.w - get_string_width(text, glyph_count) - 4,
510                                        font_height + 1,
511                                        (xcb_char2b_t*) text);
512
513             xcb_generic_error_t *err = xcb_request_check(xcb_connection, cookie);
514
515             if (err != NULL) {
516                 printf("XCB-Error: %d\n", err->error_code);
517             }
518         }
519         i3_ws *ws_walk;
520         TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) {
521             printf("Drawing Button for WS %s at x = %d\n", ws_walk->name, i);
522             uint32_t color = get_colorpixel("240000");
523             if (ws_walk->visible) {
524                 color = get_colorpixel("480000");
525             }
526             if (ws_walk->urgent) {
527                 printf("WS %s is urgent!\n", ws_walk->name);
528                 color = get_colorpixel("002400");
529             }
530             xcb_change_gc(xcb_connection,
531                           outputs_walk->bargc,
532                           XCB_GC_FOREGROUND,
533                           &color);
534             xcb_change_gc(xcb_connection,
535                           outputs_walk->bargc,
536                           XCB_GC_BACKGROUND,
537                           &color);
538             xcb_rectangle_t rect = { i + 1, 1, ws_walk->name_width + 8, font_height + 4 };
539             xcb_poly_fill_rectangle(xcb_connection,
540                                     outputs_walk->buffer,
541                                     outputs_walk->bargc,
542                                     1,
543                                     &rect);
544             color = get_colorpixel("FFFFFF");
545             xcb_change_gc(xcb_connection,
546                           outputs_walk->bargc,
547                           XCB_GC_FOREGROUND,
548                           &color);
549             xcb_image_text_16(xcb_connection,
550                               ws_walk->name_glyphs,
551                               outputs_walk->buffer,
552                               outputs_walk->bargc,
553                               i + 5, font_height + 1,
554                               ws_walk->ucs2_name);
555             i += 10 + ws_walk->name_width;
556         }
557
558         xcb_copy_area(xcb_connection,
559                       outputs_walk->buffer,
560                       outputs_walk->bar,
561                       outputs_walk->bargc,
562                       0, 0,
563                       0, 0,
564                       outputs_walk->rect.w,
565                       outputs_walk->rect.h);
566
567         i = 0;
568     }
569 }