i3
src/xcb.c
Go to the documentation of this file.
00001 /*
00002  * vim:ts=4:sw=4:expandtab
00003  *
00004  * i3 - an improved dynamic tiling window manager
00005  *
00006  * © 2009-2010 Michael Stapelberg and contributors
00007  *
00008  * See file LICENSE for license information.
00009  *
00010  * xcb.c: Helper functions for easier usage of XCB
00011  *
00012  */
00013 
00014 #include "all.h"
00015 
00016 TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts);
00017 unsigned int xcb_numlock_mask;
00018 
00019 /*
00020  * Loads a font for usage, also getting its height. If fallback is true,
00021  * i3 loads 'fixed' or '-misc-*' if the font cannot be found instead of
00022  * exiting.
00023  *
00024  */
00025 i3Font load_font(const char *pattern, bool fallback) {
00026     i3Font new;
00027     xcb_void_cookie_t font_cookie;
00028     xcb_list_fonts_with_info_cookie_t info_cookie;
00029 
00030     /* Send all our requests first */
00031     new.id = xcb_generate_id(conn);
00032     font_cookie = xcb_open_font_checked(conn, new.id, strlen(pattern), pattern);
00033     info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
00034 
00035     /* Check for errors. If errors, fall back to default font. */
00036     xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
00037 
00038     /* If we fail to open font, fall back to 'fixed'. If opening 'fixed' fails fall back to '-misc-*' */
00039     if (error != NULL) {
00040         ELOG("Could not open font %s (X error %d). Reverting to backup font.\n", pattern, error->error_code);
00041         pattern = "fixed";
00042         font_cookie = xcb_open_font_checked(conn, new.id, strlen(pattern), pattern);
00043         info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
00044 
00045         /* Check if we managed to open 'fixed' */
00046         xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
00047 
00048         /* Fall back to '-misc-*' if opening 'fixed' fails. */
00049         if (error != NULL) {
00050             ELOG("Could not open fallback font '%s', trying with '-misc-*'\n",pattern);
00051             pattern = "-misc-*";
00052             font_cookie = xcb_open_font_checked(conn, new.id, strlen(pattern), pattern);
00053             info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
00054 
00055             check_error(conn, font_cookie, "Could open neither requested font nor fallback (fixed or -misc-*");
00056         }
00057     }
00058 
00059     /* Get information (height/name) for this font */
00060     xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL);
00061     exit_if_null(reply, "Could not load font \"%s\"\n", pattern);
00062 
00063     new.height = reply->font_ascent + reply->font_descent;
00064 
00065     free(reply);
00066 
00067     return new;
00068 }
00069 
00070 /*
00071  * Returns the colorpixel to use for the given hex color (think of HTML).
00072  *
00073  * The hex_color has to start with #, for example #FF00FF.
00074  *
00075  * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
00076  * This has to be done by the caller.
00077  *
00078  */
00079 uint32_t get_colorpixel(char *hex) {
00080     char strgroups[3][3] = {{hex[1], hex[2], '\0'},
00081                             {hex[3], hex[4], '\0'},
00082                             {hex[5], hex[6], '\0'}};
00083     uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
00084                          (strtol(strgroups[1], NULL, 16)),
00085                          (strtol(strgroups[2], NULL, 16))};
00086 
00087     return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
00088 }
00089 
00090 /*
00091  * Convenience wrapper around xcb_create_window which takes care of depth, generating an ID and checking
00092  * for errors.
00093  *
00094  */
00095 xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_class,
00096         enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values) {
00097     xcb_window_t result = xcb_generate_id(conn);
00098 
00099     /* If the window class is XCB_WINDOW_CLASS_INPUT_ONLY, depth has to be 0 */
00100     uint16_t depth = (window_class == XCB_WINDOW_CLASS_INPUT_ONLY ? 0 : XCB_COPY_FROM_PARENT);
00101 
00102     xcb_create_window(conn,
00103             depth,
00104             result, /* the window id */
00105             root, /* parent == root */
00106             dims.x, dims.y, dims.width, dims.height, /* dimensions */
00107             0, /* border = 0, we draw our own */
00108             window_class,
00109             XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
00110             mask,
00111             values);
00112 
00113     /* Set the cursor */
00114     if (xcursor_supported) {
00115         mask = XCB_CW_CURSOR;
00116         values[0] = xcursor_get_cursor(cursor);
00117         xcb_change_window_attributes(conn, result, mask, values);
00118     } else {
00119         xcb_cursor_t cursor_id = xcb_generate_id(conn);
00120         i3Font cursor_font = load_font("cursor", false);
00121         int xcb_cursor = xcursor_get_xcb_cursor(cursor);
00122         xcb_create_glyph_cursor(conn, cursor_id, cursor_font.id, cursor_font.id,
00123                 xcb_cursor, xcb_cursor + 1, 0, 0, 0, 65535, 65535, 65535);
00124         xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
00125         xcb_free_cursor(conn, cursor_id);
00126     }
00127 
00128     /* Map the window (= make it visible) */
00129     if (map)
00130         xcb_map_window(conn, result);
00131 
00132     return result;
00133 }
00134 
00135 /*
00136  * Changes a single value in the graphic context (so one doesn’t have to define an array of values)
00137  *
00138  */
00139 void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) {
00140     xcb_change_gc(conn, gc, mask, &value);
00141 }
00142 
00143 /*
00144  * Draws a line from x,y to to_x,to_y using the given color
00145  *
00146  */
00147 void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc,
00148                    uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t to_x, uint32_t to_y) {
00149     xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, colorpixel);
00150     xcb_point_t points[] = {{x, y}, {to_x, to_y}};
00151     xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, drawable, gc, 2, points);
00152 }
00153 
00154 /*
00155  * Draws a rectangle from x,y with width,height using the given color
00156  *
00157  */
00158 void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc,
00159                    uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
00160     xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, colorpixel);
00161     xcb_rectangle_t rect = {x, y, width, height};
00162     xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
00163 }
00164 
00165 /*
00166  * Generates a configure_notify event and sends it to the given window
00167  * Applications need this to think they’ve configured themselves correctly.
00168  * The truth is, however, that we will manage them.
00169  *
00170  */
00171 void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window) {
00172     /* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes.
00173      * In order to properly initialize these bytes, we allocate 32 bytes even
00174      * though we only need less for an xcb_configure_notify_event_t */
00175     void *event = scalloc(32);
00176     xcb_configure_notify_event_t *generated_event = event;
00177 
00178     generated_event->event = window;
00179     generated_event->window = window;
00180     generated_event->response_type = XCB_CONFIGURE_NOTIFY;
00181 
00182     generated_event->x = r.x;
00183     generated_event->y = r.y;
00184     generated_event->width = r.width;
00185     generated_event->height = r.height;
00186 
00187     generated_event->border_width = 0;
00188     generated_event->above_sibling = XCB_NONE;
00189     generated_event->override_redirect = false;
00190 
00191     xcb_send_event(conn, false, window, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)generated_event);
00192     xcb_flush(conn);
00193 
00194     free(event);
00195 }
00196 
00197 /*
00198  * Generates a configure_notify_event with absolute coordinates (relative to the X root
00199  * window, not to the client’s frame) for the given client.
00200  *
00201  */
00202 void fake_absolute_configure_notify(Con *con) {
00203     Rect absolute;
00204     if (con->window == NULL)
00205         return;
00206 
00207     absolute.x = con->rect.x + con->window_rect.x;
00208     absolute.y = con->rect.y + con->window_rect.y;
00209     absolute.width = con->window_rect.width;
00210     absolute.height = con->window_rect.height;
00211 
00212     DLOG("fake rect = (%d, %d, %d, %d)\n", absolute.x, absolute.y, absolute.width, absolute.height);
00213 
00214     fake_configure_notify(conn, absolute, con->window->id);
00215 }
00216 
00217 /*
00218  * Sends the WM_TAKE_FOCUS ClientMessage to the given window
00219  *
00220  */
00221 void send_take_focus(xcb_window_t window) {
00222     /* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes.
00223      * In order to properly initialize these bytes, we allocate 32 bytes even
00224      * though we only need less for an xcb_configure_notify_event_t */
00225     void *event = scalloc(32);
00226     xcb_client_message_event_t *ev = event;
00227 
00228     ev->response_type = XCB_CLIENT_MESSAGE;
00229     ev->window = window;
00230     ev->type = A_WM_PROTOCOLS;
00231     ev->format = 32;
00232     ev->data.data32[0] = A_WM_TAKE_FOCUS;
00233     ev->data.data32[1] = XCB_CURRENT_TIME;
00234 
00235     DLOG("Sending WM_TAKE_FOCUS to the client\n");
00236     xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char*)ev);
00237     free(event);
00238 }
00239 
00240 /*
00241  * Finds out which modifier mask is the one for numlock, as the user may change this.
00242  *
00243  */
00244 void xcb_get_numlock_mask(xcb_connection_t *conn) {
00245     xcb_key_symbols_t *keysyms;
00246     xcb_get_modifier_mapping_cookie_t cookie;
00247     xcb_get_modifier_mapping_reply_t *reply;
00248     xcb_keycode_t *modmap;
00249     int mask, i;
00250     const int masks[8] = { XCB_MOD_MASK_SHIFT,
00251                            XCB_MOD_MASK_LOCK,
00252                            XCB_MOD_MASK_CONTROL,
00253                            XCB_MOD_MASK_1,
00254                            XCB_MOD_MASK_2,
00255                            XCB_MOD_MASK_3,
00256                            XCB_MOD_MASK_4,
00257                            XCB_MOD_MASK_5 };
00258 
00259     /* Request the modifier map */
00260     cookie = xcb_get_modifier_mapping(conn);
00261 
00262     /* Get the keysymbols */
00263     keysyms = xcb_key_symbols_alloc(conn);
00264 
00265     if ((reply = xcb_get_modifier_mapping_reply(conn, cookie, NULL)) == NULL) {
00266         xcb_key_symbols_free(keysyms);
00267         return;
00268     }
00269 
00270     modmap = xcb_get_modifier_mapping_keycodes(reply);
00271 
00272     /* Get the keycode for numlock */
00273 #ifdef OLD_XCB_KEYSYMS_API
00274     xcb_keycode_t numlock = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
00275 #else
00276     /* For now, we only use the first keysymbol. */
00277     xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
00278     if (numlock_syms == NULL)
00279         return;
00280     xcb_keycode_t numlock = *numlock_syms;
00281     free(numlock_syms);
00282 #endif
00283 
00284     /* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */
00285     for (mask = 0; mask < 8; mask++)
00286         for (i = 0; i < reply->keycodes_per_modifier; i++)
00287             if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock)
00288                 xcb_numlock_mask = masks[mask];
00289 
00290     xcb_key_symbols_free(keysyms);
00291     free(reply);
00292 }
00293 
00294 /*
00295  * Raises the given window (typically client->frame) above all other windows
00296  *
00297  */
00298 void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) {
00299     uint32_t values[] = { XCB_STACK_MODE_ABOVE };
00300     xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values);
00301 }
00302 
00303 /*
00304  * Query the width of the given text (16-bit characters, UCS) with given real
00305  * length (amount of glyphs) using the given font.
00306  *
00307  */
00308 int predict_text_width(char *text, int length) {
00309     xcb_query_text_extents_cookie_t cookie;
00310     xcb_query_text_extents_reply_t *reply;
00311     xcb_generic_error_t *error;
00312     int width;
00313 
00314     cookie = xcb_query_text_extents(conn, config.font.id, length, (xcb_char2b_t*)text);
00315     if ((reply = xcb_query_text_extents_reply(conn, cookie, &error)) == NULL) {
00316         ELOG("Could not get text extents (X error code %d)\n",
00317              error->error_code);
00318         /* We return the rather safe guess of 7 pixels, because a
00319          * rendering error is better than a crash. Plus, the user will
00320          * see the error in his log. */
00321         return 7;
00322     }
00323 
00324     width = reply->overall_width;
00325     free(reply);
00326     return width;
00327 }
00328 
00329 /*
00330  * Configures the given window to have the size/position specified by given rect
00331  *
00332  */
00333 void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r) {
00334     xcb_void_cookie_t cookie;
00335     cookie = xcb_configure_window(conn, window,
00336                          XCB_CONFIG_WINDOW_X |
00337                          XCB_CONFIG_WINDOW_Y |
00338                          XCB_CONFIG_WINDOW_WIDTH |
00339                          XCB_CONFIG_WINDOW_HEIGHT,
00340                          &(r.x));
00341     /* ignore events which are generated because we configured a window */
00342     add_ignore_event(cookie.sequence, -1);
00343 }
00344 
00345 /*
00346  * Returns true if the given reply contains the given atom.
00347  *
00348  */
00349 bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom) {
00350     if (prop == NULL || xcb_get_property_value_length(prop) == 0)
00351         return false;
00352 
00353     xcb_atom_t *atoms;
00354     if ((atoms = xcb_get_property_value(prop)) == NULL)
00355         return false;
00356 
00357     for (int i = 0; i < xcb_get_property_value_length(prop) / (prop->format / 8); i++)
00358         if (atoms[i] == atom)
00359             return true;
00360 
00361     return false;
00362 
00363 }