i3 - improved tiling WM


Allow configuration of colors with alpha channel

Patch status: needinfo

Patch by Klemens Baum

Long description:

This commit comprises the following changes:

  Properly validate color strings with optional alpha channel
    For example #DDE83030 and #E83030 are both allowed
  Use a 32-bit visual for i3bar if available
    Note: We cannot just use the root visual because some graphics
    drivers (e.g. fglrx) do not support setting a 32-bit root visual.
  Pass the current visual to draw_text (needed for Pango)

To apply this patch, use:
curl http://cr.i3wm.org/patch/63/raw.patch | git am

b/i3-config-wizard/main.c

30
@@ -72,6 +72,7 @@ static uint32_t xcb_numlock_mask;
31
 xcb_connection_t *conn;
32
 static xcb_key_symbols_t *keysyms;
33
 xcb_screen_t *root_screen;
34
+static xcb_visualtype_t* visual_type;
35
 static xcb_get_modifier_mapping_reply_t *modmap_reply;
36
 static i3Font font;
37
 static i3Font bold_font;
38
@@ -484,7 +485,7 @@ static int handle_expose() {
39
     set_font(&font);
40
 
41
 #define txt(x, row, text) \
42
-    draw_text_ascii(text, pixmap, pixmap_gc,\
43
+    draw_text_ascii(text, pixmap, visual_type, pixmap_gc,\
44
             x, (row - 1) * font.height + 4, 300 - x * 2)
45
 
46
     if (current_step == STEP_WELCOME) {
47
@@ -814,6 +815,7 @@ int main(int argc, char *argv[]) {
48
     #undef xmacro
49
 
50
     root_screen = xcb_aux_get_screen(conn, screens);
51
+    visual_type = get_visualtype(root_screen);
52
     root = root_screen->root;
53
 
54
     if (!(modmap_reply = xcb_get_modifier_mapping_reply(conn, modmap_cookie, NULL)))

b/i3-input/main.c

59
@@ -43,6 +43,7 @@ static xcb_key_symbols_t *symbols;
60
 static bool modeswitch_active = false;
61
 static xcb_window_t win;
62
 static xcb_pixmap_t pixmap;
63
+static xcb_visualtype_t* visual_type;
64
 static xcb_gcontext_t pixmap_gc;
65
 static xcb_char2b_t glyphs_ucs[512];
66
 static char *glyphs_utf8[512];
67
@@ -137,13 +138,13 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t
68
 
69
     /* draw the prompt … */
70
     if (prompt != NULL) {
71
-        draw_text(prompt, pixmap, pixmap_gc, 4, 4, 492);
72
+        draw_text(prompt, pixmap, visual_type, pixmap_gc, 4, 4, 492);
73
     }
74
     /* … and the text */
75
     if (input_position > 0)
76
     {
77
         i3String *input = i3string_from_ucs2(glyphs_ucs, input_position);
78
-        draw_text(input, pixmap, pixmap_gc, prompt_offset + 4, 4, 492);
79
+        draw_text(input, pixmap, visual_type, pixmap_gc, prompt_offset + 4, 4, 492);
80
         i3string_free(input);
81
     }
82
 
83
@@ -385,6 +386,7 @@ int main(int argc, char *argv[]) {
84
     focus_cookie = xcb_get_input_focus(conn);
85
 
86
     root_screen = xcb_aux_get_screen(conn, screens);
87
+    visual_type = get_visualtype(root_screen);
88
     root = root_screen->root;
89
 
90
     symbols = xcb_key_symbols_alloc(conn);

b/i3-nagbar/main.c

95
@@ -41,6 +41,7 @@ typedef struct {
96
 
97
 static xcb_window_t win;
98
 static xcb_pixmap_t pixmap;
99
+static xcb_visualtype_t* visual_type;
100
 static xcb_gcontext_t pixmap_gc;
101
 static xcb_rectangle_t rect = { 0, 0, 600, 20 };
102
 static i3Font font;
103
@@ -191,7 +192,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
104
 
105
     /* restore font color */
106
     set_font_colors(pixmap_gc, color_text, color_background);
107
-    draw_text(prompt, pixmap, pixmap_gc,
108
+    draw_text(prompt, pixmap, visual_type, pixmap_gc,
109
             4 + 4, 4 + 4, rect.width - 4 - 4);
110
 
111
     /* render close button */
112
@@ -218,7 +219,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
113
 
114
     values[0] = 1;
115
     set_font_colors(pixmap_gc, color_text, color_button_background);
116
-    draw_text_ascii("X", pixmap, pixmap_gc, y - w - line_width + w / 2 - 4,
117
+    draw_text_ascii("X", pixmap, visual_type, pixmap_gc, y - w - line_width + w / 2 - 4,
118
             4 + 4 - 1, rect.width - y + w + line_width - w / 2 + 4);
119
     y -= w;
120
 
121
@@ -249,7 +250,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
122
         values[0] = color_text;
123
         values[1] = color_button_background;
124
         set_font_colors(pixmap_gc, color_text, color_button_background);
125
-        draw_text(buttons[c].label, pixmap, pixmap_gc,
126
+        draw_text(buttons[c].label, pixmap, visual_type, pixmap_gc,
127
                 y - w - line_width + 6, 4 + 3, rect.width - y + w + line_width - 6);
128
 
129
         y -= w;
130
@@ -363,6 +364,7 @@ int main(int argc, char *argv[]) {
131
     #undef xmacro
132
 
133
     root_screen = xcb_aux_get_screen(conn, screens);
134
+    visual_type = get_visualtype(root_screen);
135
     root = root_screen->root;
136
 
137
     if (bar_type == TYPE_ERROR) {

b/i3bar/src/xcb.c

142
@@ -48,6 +48,9 @@ xcb_connection_t *xcb_connection;
143
 int              screen;
144
 xcb_screen_t     *root_screen;
145
 xcb_window_t     xcb_root;
146
+xcb_visualtype_t *visual_type;
147
+uint8_t          depth;
148
+xcb_colormap_t   colormap;
149
 
150
 /* selection window for tray support */
151
 static xcb_window_t selwin = XCB_NONE;
152
@@ -173,7 +176,8 @@ void refresh_statusline(void) {
153
 
154
         uint32_t colorpixel = (block->color ? get_colorpixel(block->color) : colors.bar_fg);
155
         set_font_colors(statusline_ctx, colorpixel, colors.bar_bg);
156
-        draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, 1, block->width);
157
+        draw_text(block->full_text, statusline_pm, visual_type,
158
+                statusline_ctx, x + block->x_offset, 1, block->width);
159
         x += block->width + block->x_offset + block->x_append;
160
 
161
         if (TAILQ_NEXT(block, blocks) != NULL && !block->no_separator && block->sep_block_width > 0) {
162
@@ -261,7 +265,16 @@ void unhide_bars(void) {
163
 void init_colors(const struct xcb_color_strings_t *new_colors) {
164
 #define PARSE_COLOR(name, def) \
165
     do { \
166
-        colors.name = get_colorpixel(new_colors->name ? new_colors->name : def); \
167
+        const char* color = def; \
168
+        const char* user_color = new_colors->name; \
169
+        if (user_color) { \
170
+            if (valid_colorpixel(user_color)) { \
171
+                color = user_color; \
172
+            } else { \
173
+                ELOG("Invalid color specified for %s: %s", #name, user_color); \
174
+            } \
175
+        } \
176
+        colors.name = get_colorpixel(color); \
177
     } while  (0)
178
     PARSE_COLOR(bar_fg, "#FFFFFF");
179
     PARSE_COLOR(bar_bg, "#000000");
180
@@ -872,34 +885,55 @@ char *init_xcb_early() {
181
     root_screen = xcb_aux_get_screen(xcb_connection, screen);
182
     xcb_root = root_screen->root;
183
 
184
+    depth = root_screen->root_depth;
185
+    colormap = root_screen->default_colormap;
186
+
187
+    visual_type = xcb_aux_find_visual_by_attrs(root_screen, -1, 32);
188
+    if (visual_type) {
189
+        depth = xcb_aux_get_depth_of_visual(root_screen, visual_type->visual_id);
190
+
191
+        colormap = xcb_generate_id(xcb_connection);
192
+        xcb_void_cookie_t cm_cookie = xcb_create_colormap_checked(xcb_connection,
193
+                                                                  XCB_COLORMAP_ALLOC_NONE,
194
+                                                                  colormap,
195
+                                                                  xcb_root,
196
+                                                                  visual_type->visual_id);
197
+        if (xcb_request_failed(cm_cookie, "Could not allocate colormap")) {
198
+            exit(EXIT_FAILURE);
199
+        }
200
+    } else {
201
+        visual_type = get_visualtype(root_screen);
202
+    }
203
+
204
     /* We draw the statusline to a seperate pixmap, because it looks the same on all bars and
205
      * this way, we can choose to crop it */
206
     uint32_t mask = XCB_GC_FOREGROUND;
207
     uint32_t vals[] = { colors.bar_bg, colors.bar_bg };
208
 
209
+    statusline_pm = xcb_generate_id(xcb_connection);
210
+    xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection,
211
+                                                               depth,
212
+                                                               statusline_pm,
213
+                                                               xcb_root,
214
+                                                               root_screen->width_in_pixels,
215
+                                                               root_screen->height_in_pixels);
216
+
217
     statusline_clear = xcb_generate_id(xcb_connection);
218
     xcb_void_cookie_t clear_ctx_cookie = xcb_create_gc_checked(xcb_connection,
219
                                                                statusline_clear,
220
-                                                               xcb_root,
221
+                                                               statusline_pm,
222
                                                                mask,
223
                                                                vals);
224
 
225
+    mask |= XCB_GC_BACKGROUND;
226
+    vals[0] = colors.bar_fg;
227
     statusline_ctx = xcb_generate_id(xcb_connection);
228
     xcb_void_cookie_t sl_ctx_cookie = xcb_create_gc_checked(xcb_connection,
229
                                                             statusline_ctx,
230
-                                                            xcb_root,
231
+                                                            statusline_pm,
232
                                                             0,
233
                                                             NULL);
234
 
235
-    statusline_pm = xcb_generate_id(xcb_connection);
236
-    xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection,
237
-                                                               root_screen->root_depth,
238
-                                                               statusline_pm,
239
-                                                               xcb_root,
240
-                                                               root_screen->width_in_pixels,
241
-                                                               root_screen->height_in_pixels);
242
-
243
-
244
     /* The various Watchers to communicate with xcb */
245
     xcb_io = smalloc(sizeof(ev_io));
246
     xcb_prep = smalloc(sizeof(ev_prepare));
247
@@ -1267,7 +1301,7 @@ void realloc_sl_buffer(void) {
248
     xcb_free_pixmap(xcb_connection, statusline_pm);
249
     statusline_pm = xcb_generate_id(xcb_connection);
250
     xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection,
251
-                                                               root_screen->root_depth,
252
+                                                               depth,
253
                                                                statusline_pm,
254
                                                                xcb_root,
255
                                                                MAX(root_screen->width_in_pixels, statusline_width),
256
@@ -1279,7 +1313,7 @@ void realloc_sl_buffer(void) {
257
     statusline_clear = xcb_generate_id(xcb_connection);
258
     xcb_void_cookie_t clear_ctx_cookie = xcb_create_gc_checked(xcb_connection,
259
                                                                statusline_clear,
260
-                                                               xcb_root,
261
+                                                               statusline_pm,
262
                                                                mask,
263
                                                                vals);
264
 
265
@@ -1289,7 +1323,7 @@ void realloc_sl_buffer(void) {
266
     xcb_free_gc(xcb_connection, statusline_ctx);
267
     xcb_void_cookie_t sl_ctx_cookie = xcb_create_gc_checked(xcb_connection,
268
                                                             statusline_ctx,
269
-                                                            xcb_root,
270
+                                                            statusline_pm,
271
                                                             mask,
272
                                                             vals);
273
 
274
@@ -1324,37 +1358,42 @@ void reconfig_windows(void) {
275
 
276
             walk->bar = xcb_generate_id(xcb_connection);
277
             walk->buffer = xcb_generate_id(xcb_connection);
278
-            mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
279
+            mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT
280
+                | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
281
             /* Black background */
282
             values[0] = colors.bar_bg;
283
+            /* Border */
284
+            values[1] = root_screen->black_pixel;
285
             /* If hide_on_modifier is set, i3 is not supposed to manage our bar-windows */
286
-            values[1] = config.hide_on_modifier;
287
+            values[2] = config.hide_on_modifier;
288
             /* We enable the following EventMask fields:
289
              * EXPOSURE, to get expose events (we have to re-draw then)
290
              * SUBSTRUCTURE_REDIRECT, to get ConfigureRequests when the tray
291
              *                        child windows use ConfigureWindow
292
              * BUTTON_PRESS, to handle clicks on the workspace buttons
293
              * */
294
-            values[2] = XCB_EVENT_MASK_EXPOSURE |
295
+            values[3] = XCB_EVENT_MASK_EXPOSURE |
296
                         XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
297
             if (!config.disable_ws) {
298
-                values[2] |= XCB_EVENT_MASK_BUTTON_PRESS;
299
+                values[3] |= XCB_EVENT_MASK_BUTTON_PRESS;
300
             }
301
+            /* Colormap */
302
+            values[4] = colormap;
303
             xcb_void_cookie_t win_cookie = xcb_create_window_checked(xcb_connection,
304
-                                                                     root_screen->root_depth,
305
+                                                                     depth,
306
                                                                      walk->bar,
307
                                                                      xcb_root,
308
                                                                      walk->rect.x, walk->rect.y + walk->rect.h - font.height - 6,
309
                                                                      walk->rect.w, font.height + 6,
310
                                                                      0,
311
                                                                      XCB_WINDOW_CLASS_INPUT_OUTPUT,
312
-                                                                     root_screen->root_visual,
313
+                                                                     visual_type->visual_id,
314
                                                                      mask,
315
                                                                      values);
316
 
317
             /* The double-buffer we use to render stuff off-screen */
318
             xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection,
319
-                                                                    root_screen->root_depth,
320
+                                                                    depth,
321
                                                                     walk->buffer,
322
                                                                     walk->bar,
323
                                                                     walk->rect.w,
324
@@ -1494,7 +1533,7 @@ void reconfig_windows(void) {
325
 
326
             DLOG("Recreating buffer for output %s\n", walk->name);
327
             xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection,
328
-                                                                    root_screen->root_depth,
329
+                                                                    depth,
330
                                                                     walk->buffer,
331
                                                                     walk->bar,
332
                                                                     walk->rect.w,
333
@@ -1633,7 +1672,7 @@ void draw_bars(bool unhide) {
334
                                     1,
335
                                     &rect);
336
             set_font_colors(outputs_walk->bargc, fg_color, bg_color);
337
-            draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 3, ws_walk->name_width);
338
+            draw_text(ws_walk->name, outputs_walk->buffer, visual_type, outputs_walk->bargc, i + 5, 3, ws_walk->name_width);
339
             i += 10 + ws_walk->name_width + 1;
340
 
341
         }
342
@@ -1669,7 +1708,7 @@ void draw_bars(bool unhide) {
343
                                     &rect);
344
 
345
             set_font_colors(outputs_walk->bargc, fg_color, bg_color);
346
-            draw_text(binding.name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 3, binding.width);
347
+            draw_text(binding.name, outputs_walk->buffer, visual_type, outputs_walk->bargc, i + 5, 3, binding.width);
348
         }
349
 
350
         i = 0;

b/include/libi3.h

355
@@ -231,14 +231,25 @@ int ipc_recv_message(int sockfd, uint32_t *message_type,
356
 void fake_configure_notify(xcb_connection_t *conn, xcb_rectangle_t r, xcb_window_t window, int border_width);
357
 
358
 /**
359
+ * Checks whether the given string denotes a valid color.
360
+ *
361
+ * Color strings start with # followed by two hexadecimal digits for each
362
+ * component.
363
+ *
364
+ * RGB components must be specified while alpha is optional,
365
+ * i.e. all valid colors are of the form #RRGGBB or #AARRGGBB.
366
+ */
367
+bool valid_colorpixel(const char* hex);
368
+
369
+/**
370
  * Returns the colorpixel to use for the given hex color (think of HTML). Only
371
  * works for true-color (vast majority of cases) at the moment, avoiding a
372
  * roundtrip to X11.
373
  *
374
- * The hex_color has to start with #, for example #FF00FF.
375
+ * See valid_colorpixel() for a description of the color format.
376
  *
377
  * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
378
- * This has to be done by the caller.
379
+ * The caller should use valid_colorpixel() to sanitize user input.
380
  *
381
  * NOTE that this function may in the future rely on a global xcb_connection_t
382
  * variable called 'conn' to be present.
383
@@ -328,14 +339,14 @@ void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background
384
  * Text must be specified as an i3String.
385
  *
386
  */
387
-void draw_text(i3String *text, xcb_drawable_t drawable,
388
+void draw_text(i3String *text, xcb_drawable_t drawable, xcb_visualtype_t* visual,
389
         xcb_gcontext_t gc, int x, int y, int max_width);
390
 
391
 /**
392
  * ASCII version of draw_text to print static strings.
393
  *
394
  */
395
-void draw_text_ascii(const char *text, xcb_drawable_t drawable,
396
+void draw_text_ascii(const char *text, xcb_drawable_t drawable, xcb_visualtype_t* visual,
397
         xcb_gcontext_t gc, int x, int y, int max_width);
398
 
399
 /**

b/libi3/font.c

404
@@ -79,11 +79,11 @@ static bool load_pango_font(i3Font *font, const char *desc) {
405
  *
406
  */
407
 static void draw_text_pango(const char *text, size_t text_len,
408
-        xcb_drawable_t drawable, int x, int y, int max_width) {
409
+        xcb_drawable_t drawable, xcb_visualtype_t* visual, int x, int y, int max_width) {
410
     /* Create the Pango layout */
411
     /* root_visual_type is cached in load_pango_font */
412
     cairo_surface_t *surface = cairo_xcb_surface_create(conn, drawable,
413
-            root_visual_type, x + max_width, y + savedFont->height);
414
+            visual, x + max_width, y + savedFont->height);
415
     cairo_t *cr = cairo_create(surface);
416
     PangoLayout *layout = pango_cairo_create_layout(cr);
417
     pango_layout_set_font_description(layout, savedFont->specific.pango_desc);
418
@@ -322,7 +322,7 @@ static void draw_text_xcb(const xcb_char2b_t *text, size_t text_len, xcb_drawabl
419
  * Text must be specified as an i3String.
420
  *
421
  */
422
-void draw_text(i3String *text, xcb_drawable_t drawable,
423
+void draw_text(i3String *text, xcb_drawable_t drawable, xcb_visualtype_t* visual,
424
                xcb_gcontext_t gc, int x, int y, int max_width) {
425
     assert(savedFont != NULL);
426
 
427
@@ -338,7 +338,7 @@ void draw_text(i3String *text, xcb_drawable_t drawable,
428
         case FONT_TYPE_PANGO:
429
             /* Render the text using Pango */
430
             draw_text_pango(i3string_as_utf8(text), i3string_get_num_bytes(text),
431
-                            drawable, x, y, max_width);
432
+                            drawable, visual, x, y, max_width);
433
             return;
434
 #endif
435
         default:
436
@@ -350,7 +350,7 @@ void draw_text(i3String *text, xcb_drawable_t drawable,
437
  * ASCII version of draw_text to print static strings.
438
  *
439
  */
440
-void draw_text_ascii(const char *text, xcb_drawable_t drawable,
441
+void draw_text_ascii(const char *text, xcb_drawable_t drawable, xcb_visualtype_t* visual,
442
                xcb_gcontext_t gc, int x, int y, int max_width) {
443
     assert(savedFont != NULL);
444
 
445
@@ -364,7 +364,7 @@ void draw_text_ascii(const char *text, xcb_drawable_t drawable,
446
             if (text_len > 255) {
447
                 /* The text is too long to draw it directly to X */
448
                 i3String *str = i3string_from_utf8(text);
449
-                draw_text(str, drawable, gc, x, y, max_width);
450
+                draw_text(str, drawable, visual, gc, x, y, max_width);
451
                 i3string_free(str);
452
             } else {
453
                 /* X11 coordinates for fonts start at the baseline */
454
@@ -378,7 +378,7 @@ void draw_text_ascii(const char *text, xcb_drawable_t drawable,
455
         case FONT_TYPE_PANGO:
456
             /* Render the text using Pango */
457
             draw_text_pango(text, strlen(text),
458
-                            drawable, x, y, max_width);
459
+                            drawable, visual, x, y, max_width);
460
             return;
461
 #endif
462
         default:

b/libi3/get_colorpixel.c

467
@@ -7,9 +7,54 @@
468
  */
469
 #include <stdlib.h>
470
 #include <stdint.h>
471
+#include <string.h>
472
+#include <assert.h>
473
 
474
 #include "libi3.h"
475
 
476
+bool has_alpha(const char* hex) {
477
+    assert(strlen(hex) >= 7);
478
+
479
+    return hex[7] != '\0';
480
+}
481
+
482
+bool in_range(char val, char lower, char upper) {
483
+    return lower <= val && val <= upper;
484
+}
485
+
486
+bool is_hex(char c) {
487
+    return in_range(c, '0', '9')
488
+        || in_range(c, 'A', 'F')
489
+        || in_range(c, 'a', 'f');
490
+}
491
+
492
+bool valid_colorpixel(const char* hex) {
493
+    if (hex[0] != '#') {
494
+        return false;
495
+    }
496
+
497
+    size_t len = strlen(hex);
498
+    if (len < 7) {
499
+        return false;
500
+    }
501
+
502
+    if (has_alpha(hex)) {
503
+        if (len != 9) {
504
+            return false;
505
+        }
506
+    } else if (len != 7) {
507
+        return false;
508
+    }
509
+
510
+    while (*++hex) {
511
+        if (!is_hex(*hex)) {
512
+            return false;
513
+        }
514
+    }
515
+
516
+    return true;
517
+}
518
+
519
 /*
520
  * Returns the colorpixel to use for the given hex color (think of HTML). Only
521
  * works for true-color (vast majority of cases) at the moment, avoiding a
522
@@ -25,14 +70,23 @@
523
  *
524
  */
525
 uint32_t get_colorpixel(const char *hex) {
526
-    char strgroups[3][3] = {{hex[1], hex[2], '\0'},
527
-                            {hex[3], hex[4], '\0'},
528
-                            {hex[5], hex[6], '\0'}};
529
-    uint8_t r = strtol(strgroups[0], NULL, 16);
530
-    uint8_t g = strtol(strgroups[1], NULL, 16);
531
-    uint8_t b = strtol(strgroups[2], NULL, 16);
532
-
533
-    /* We set the first 8 bits high to have 100% opacity in case of a 32 bit
534
+    assert(valid_colorpixel(hex));
535
+
536
+    /* If specified without alpha, assume 100% opacity in case of a 32 bit
537
      * color depth visual. */
538
-    return (0xFF << 24) | (r << 16 | g << 8 | b);
539
+    char strgroups[4][3] = {{'F', 'F', '\0'}};
540
+
541
+    size_t first_component = has_alpha(hex) ? 0 : 1;
542
+    for (size_t i = first_component; i < 4; ++i) {
543
+        strgroups[i][0] = *++hex;
544
+        strgroups[i][1] = *++hex;
545
+        strgroups[i][2] = '\0';
546
+    }
547
+
548
+    uint8_t a = strtol(strgroups[0], NULL, 16);
549
+    uint8_t r = strtol(strgroups[1], NULL, 16);
550
+    uint8_t g = strtol(strgroups[2], NULL, 16);
551
+    uint8_t b = strtol(strgroups[3], NULL, 16);
552
+
553
+    return (a << 24 | r << 16 | g << 8 | b);
554
 }

b/src/config_directives.c

559
@@ -24,6 +24,18 @@
560
     y(map_close); \
561
 } while (0)
562
 
563
+// Macro to sanitize user-provided color strings
564
+#define APPLY_COLOR(classname, value) \
565
+    do { \
566
+        if (value) { \
567
+            if (valid_colorpixel(value)) { \
568
+                config.client.classname = get_colorpixel(value); \
569
+            } else { \
570
+                ELOG("Invalid color specified for %s: %s", #classname, value); \
571
+            } \
572
+        } \
573
+    } while  (0)
574
+
575
 /*******************************************************************************
576
  * Criteria functions.
577
  ******************************************************************************/
578
@@ -403,19 +415,19 @@ CFGFUN(popup_during_fullscreen, const char *value) {
579
 
580
 CFGFUN(color_single, const char *colorclass, const char *color) {
581
     /* used for client.background only currently */
582
-    config.client.background = get_colorpixel(color);
583
+    if (strcmp(colorclass, "client.background") == 0) {
584
+        APPLY_COLOR(background, color);
585
+    }
586
 }
587
 
588
 CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator) {
589
 #define APPLY_COLORS(classname) \
590
     do { \
591
         if (strcmp(colorclass, "client." #classname) == 0) { \
592
-            config.client.classname.border = get_colorpixel(border); \
593
-            config.client.classname.background = get_colorpixel(background); \
594
-            config.client.classname.text = get_colorpixel(text); \
595
-            if (indicator != NULL) { \
596
-                config.client. classname .indicator = get_colorpixel(indicator); \
597
-            } \
598
+            APPLY_COLOR(classname.border, border); \
599
+            APPLY_COLOR(classname.background, background); \
600
+            APPLY_COLOR(classname.text, text); \
601
+            APPLY_COLOR(classname.indicator, indicator); \
602
         } \
603
     } while (0)
604
 

b/src/sighandler.c

609
@@ -24,6 +24,8 @@
610
 
611
 static void open_popups(void);
612
 
613
+extern xcb_visualtype_t* visual_type;
614
+
615
 static xcb_gcontext_t pixmap_gc;
616
 static xcb_pixmap_t pixmap;
617
 static int raised_signal;
618
@@ -150,7 +152,7 @@ static int sig_draw_window(xcb_window_t win, int width, int height, int font_hei
619
         if (i == backtrace_string_index)
620
             set_font_colors(pixmap_gc, get_colorpixel(bt_colour), get_colorpixel("#000000"));
621
 
622
-        draw_text(crash_text_i3strings[i], pixmap, pixmap_gc,
623
+        draw_text(crash_text_i3strings[i], pixmap, visual_type, pixmap_gc,
624
                 8, 5 + i * font_height, width - 16);
625
 
626
         /* and reset the colour again for other lines */

b/src/x.c

631
@@ -15,6 +15,8 @@
632
 /* Stores the X11 window ID of the currently focused window */
633
 xcb_window_t focused_id = XCB_NONE;
634
 
635
+xcb_visualtype_t* visual_type;
636
+
637
 /* The bottom-to-top window stack of all windows which are managed by i3.
638
  * Used for x_get_window_stack(). */
639
 static xcb_window_t *btt_stack;
640
@@ -99,7 +101,10 @@ void x_con_init(Con *con, uint16_t depth) {
641
     if (depth != root_depth && depth != XCB_COPY_FROM_PARENT) {
642
         /* For custom visuals, we need to create a colormap before creating
643
          * this window. It will be freed directly after creating the window. */
644
-        visual = get_visualid_by_depth(depth);
645
+        visual_type = xcb_aux_find_visual_by_attrs(root_screen, -1, depth);
646
+        if (visual_type) {
647
+            visual = visual_type->visual_id;
648
+        }
649
         win_colormap = xcb_generate_id(conn);
650
         xcb_create_colormap_checked(conn, XCB_COLORMAP_ALLOC_NONE, win_colormap, root, visual);
651
 
652
@@ -136,6 +141,10 @@ void x_con_init(Con *con, uint16_t depth) {
653
         values[2] = colormap;
654
     }
655
 
656
+    if (!visual_type) {
657
+        visual_type = get_visualtype(root_screen);
658
+    }
659
+
660
     Rect dims = { -15, -15, 10, 10 };
661
     con->frame = create_window(conn, dims, depth, visual, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER, false, mask, values);
662
 
663
@@ -500,7 +509,7 @@ void x_draw_decoration(Con *con) {
664
         free(tree);
665
 
666
         draw_text_ascii(title,
667
-                parent->pixmap, parent->pm_gc,
668
+                parent->pixmap, visual_type, parent->pm_gc,
669
                 con->deco_rect.x + 2, con->deco_rect.y + text_offset_y,
670
                 con->deco_rect.width - 2);
671
         free(title);
672
@@ -529,7 +538,7 @@ void x_draw_decoration(Con *con) {
673
     int indent_px = (indent_level * 5) * indent_mult;
674
 
675
     draw_text(win->name,
676
-            parent->pixmap, parent->pm_gc,
677
+            parent->pixmap, visual_type, parent->pm_gc,
678
             con->deco_rect.x + 2 + indent_px, con->deco_rect.y + text_offset_y,
679
             con->deco_rect.width - 2 - indent_px);
680