i3 - improved tiling WM


Allow configuration of colors with alpha channel

Patch status: superseded

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/62/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;

b/i3bar/src/xcb.c

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

b/include/libi3.h

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

b/libi3/font.c

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

b/libi3/get_colorpixel.c

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

b/src/config_directives.c

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

b/src/sighandler.c

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

b/src/x.c

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