]> git.sur5r.net Git - i3/i3/blob - i3bar/src/xcb.c
Add licensing information
[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_open_font(xcb_connection,
225                   xcb_font,
226                   strlen(fontname),
227                   fontname);
228
229     /* We also need the fontheight to configure our bars accordingly */
230     xcb_list_fonts_with_info_cookie_t cookie;
231     cookie = xcb_list_fonts_with_info(xcb_connection,
232                                       1,
233                                       strlen(fontname),
234                                       fontname);
235
236     /* The varios Watchers to communicate with xcb */
237     xcb_io = malloc(sizeof(ev_io));
238     xcb_prep = malloc(sizeof(ev_prepare));
239     xcb_chk = malloc(sizeof(ev_check));
240
241     ev_io_init(xcb_io, &xcb_io_cb, xcb_get_file_descriptor(xcb_connection), EV_READ);
242     ev_prepare_init(xcb_prep, &xcb_prep_cb);
243     ev_check_init(xcb_chk, &xcb_chk_cb);
244
245     ev_io_start(main_loop, xcb_io);
246     ev_prepare_start(main_loop, xcb_prep);
247     ev_check_start(main_loop, xcb_chk);
248
249     /* Now we get the atoms and save them in a nice data-structure */
250     get_atoms();
251
252     /* Now we calculate the font-height */
253     xcb_list_fonts_with_info_reply_t *reply;
254     reply = xcb_list_fonts_with_info_reply(xcb_connection,
255                                            cookie,
256                                            NULL);
257     font_height = reply->font_ascent + reply->font_descent;
258     FREE(reply);
259
260     printf("Calculated Font-height: %d\n", font_height);
261 }
262
263 /*
264  * Cleanup the xcb-stuff.
265  * Called once, before the program terminates.
266  *
267  */
268 void clean_xcb() {
269     i3_output *walk;
270     SLIST_FOREACH(walk, outputs, slist) {
271         destroy_window(walk);
272     }
273     FREE_SLIST(outputs, i3_output);
274
275     xcb_disconnect(xcb_connection);
276
277     ev_check_stop(main_loop, xcb_chk);
278     ev_prepare_stop(main_loop, xcb_prep);
279     ev_io_stop(main_loop, xcb_io);
280
281     FREE(xcb_chk);
282     FREE(xcb_prep);
283     FREE(xcb_io);
284 }
285
286 /*
287  * Get the earlier requested atoms and save them in the prepared data-structure
288  *
289  */
290 void get_atoms() {
291     xcb_intern_atom_reply_t *reply;
292     #define ATOM_DO(name) reply = xcb_intern_atom_reply(xcb_connection, atom_cookies[name], NULL); \
293         atoms[name] = reply->atom; \
294         free(reply);
295
296     #include "xcb_atoms.def"
297     printf("Got Atoms\n");
298 }
299
300 /*
301  * Destroy the bar of the specified output
302  *
303  */
304 void destroy_window(i3_output *output) {
305     if (output == NULL) {
306         return;
307     }
308     if (output->bar == XCB_NONE) {
309         return;
310     }
311     xcb_destroy_window(xcb_connection, output->bar);
312     output->bar = XCB_NONE;
313 }
314
315 /*
316  * Reconfigure all bars and create new for newly activated outputs
317  *
318  */
319 void reconfig_windows() {
320     uint32_t mask;
321     uint32_t values[4];
322
323     i3_output *walk;
324     SLIST_FOREACH(walk, outputs, slist) {
325         if (!walk->active) {
326             /* If an output is not active, we destroy it's bar */
327             /* FIXME: Maybe we rather want to unmap? */
328             printf("Destroying window for output %s\n", walk->name);
329             destroy_window(walk);
330             continue;
331         }
332         if (walk->bar == XCB_NONE) {
333             printf("Creating Window for output %s\n", walk->name);
334
335             walk->bar = xcb_generate_id(xcb_connection);
336             mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
337             /* Black background */
338             values[0] = xcb_screens->black_pixel;
339             /* The events we want to receive */
340             values[1] = XCB_EVENT_MASK_EXPOSURE |
341                         XCB_EVENT_MASK_BUTTON_PRESS;
342             xcb_create_window(xcb_connection,
343                               xcb_screens->root_depth,
344                               walk->bar,
345                               xcb_root,
346                               walk->rect.x, walk->rect.y,
347                               walk->rect.w, font_height + 6,
348                               1,
349                               XCB_WINDOW_CLASS_INPUT_OUTPUT,
350                               xcb_screens->root_visual,
351                               mask,
352                               values);
353
354             /* We want dock-windows (for now) */
355             xcb_change_property(xcb_connection,
356                                 XCB_PROP_MODE_REPLACE,
357                                 walk->bar,
358                                 atoms[_NET_WM_WINDOW_TYPE],
359                                 atoms[ATOM],
360                                 32,
361                                 1,
362                                 (unsigned char*) &atoms[_NET_WM_WINDOW_TYPE_DOCK]);
363
364             /* We also want a graphics-context (the "canvas" on which we draw) */
365             walk->bargc = xcb_generate_id(xcb_connection);
366             mask = XCB_GC_FONT;
367             values[0] = xcb_font;
368             xcb_create_gc(xcb_connection,
369                           walk->bargc,
370                           walk->bar,
371                           mask,
372                           values);
373
374             /* We finally map the bar (display it on screen) */
375             xcb_map_window(xcb_connection, walk->bar);
376         } else {
377             /* We already have a bar, so we just reconfigure it */
378             mask = XCB_CONFIG_WINDOW_X |
379                    XCB_CONFIG_WINDOW_Y |
380                    XCB_CONFIG_WINDOW_WIDTH |
381                    XCB_CONFIG_WINDOW_HEIGHT;
382             values[0] = walk->rect.x;
383             values[1] = walk->rect.y + walk->rect.h - font_height - 6;
384             values[2] = walk->rect.w;
385             values[3] = font_height + 6;
386             printf("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]);
387             xcb_configure_window(xcb_connection,
388                                  walk->bar,
389                                  mask,
390                                  values);
391         }
392     }
393 }
394
395 /*
396  * Render the bars, with buttons and statusline
397  *
398  */
399 void draw_bars() {
400     printf("Drawing Bars...\n");
401     int i = 0;
402     i3_output *outputs_walk;
403     SLIST_FOREACH(outputs_walk, outputs, slist) {
404         if (!outputs_walk->active) {
405             printf("Output %s inactive, skipping...\n", outputs_walk->name);
406             continue;
407         }
408         if (outputs_walk->bar == XCB_NONE) {
409             reconfig_windows();
410         }
411         uint32_t color = get_colorpixel("000000");
412         xcb_change_gc(xcb_connection,
413                       outputs_walk->bargc,
414                       XCB_GC_FOREGROUND,
415                       &color);
416         xcb_rectangle_t rect = { 0, 0, outputs_walk->rect.w, font_height + 6 };
417         xcb_poly_fill_rectangle(xcb_connection,
418                                 outputs_walk->bar,
419                                 outputs_walk->bargc,
420                                 1,
421                                 &rect);
422         if (statusline != NULL) {
423             printf("Printing statusline!\n");
424             xcb_change_gc(xcb_connection,
425                           outputs_walk->bargc,
426                           XCB_GC_BACKGROUND,
427                           &color);
428             color = get_colorpixel("FFFFFF");
429             xcb_change_gc(xcb_connection,
430                           outputs_walk->bargc,
431                           XCB_GC_FOREGROUND,
432                           &color);
433
434             int glyph_count;
435             xcb_char2b_t *text = (xcb_char2b_t*) convert_utf8_to_ucs2(statusline, &glyph_count);
436
437             xcb_void_cookie_t cookie;
438             cookie = xcb_image_text_16(xcb_connection,
439                                        glyph_count,
440                                        outputs_walk->bar,
441                                        outputs_walk->bargc,
442                                        outputs_walk->rect.w - get_string_width(text, glyph_count) - 4,
443                                        font_height + 1,
444                                        (xcb_char2b_t*) text);
445
446             xcb_generic_error_t *err = xcb_request_check(xcb_connection, cookie);
447
448             if (err != NULL) {
449                 printf("XCB-Error: %d\n", err->error_code);
450             }
451         }
452         i3_ws *ws_walk;
453         TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) {
454             printf("Drawing Button for WS %s at x = %d\n", ws_walk->name, i);
455             uint32_t color = get_colorpixel("240000");
456             if (ws_walk->visible) {
457                 color = get_colorpixel("480000");
458             }
459             if (ws_walk->urgent) {
460                 printf("WS %s is urgent!\n", ws_walk->name);
461                 color = get_colorpixel("002400");
462             }
463             xcb_change_gc(xcb_connection,
464                           outputs_walk->bargc,
465                           XCB_GC_FOREGROUND,
466                           &color);
467             xcb_change_gc(xcb_connection,
468                           outputs_walk->bargc,
469                           XCB_GC_BACKGROUND,
470                           &color);
471             xcb_rectangle_t rect = { i + 1, 1, ws_walk->name_width + 8, font_height + 4 };
472             xcb_poly_fill_rectangle(xcb_connection,
473                                     outputs_walk->bar,
474                                     outputs_walk->bargc,
475                                     1,
476                                     &rect);
477             color = get_colorpixel("FFFFFF");
478             xcb_change_gc(xcb_connection,
479                           outputs_walk->bargc,
480                           XCB_GC_FOREGROUND,
481                           &color);
482             xcb_image_text_16(xcb_connection,
483                               ws_walk->name_glyphs,
484                               outputs_walk->bar,
485                               outputs_walk->bargc,
486                               i + 5, font_height + 1,
487                               ws_walk->ucs2_name);
488             i += 10 + ws_walk->name_width;
489         }
490
491         i = 0;
492     }
493 }