]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'master' into next
authorMichael Stapelberg <michael@stapelberg.de>
Mon, 21 Nov 2011 23:10:09 +0000 (23:10 +0000)
committerMichael Stapelberg <michael@stapelberg.de>
Mon, 21 Nov 2011 23:10:09 +0000 (23:10 +0000)
46 files changed:
debian/changelog
debian/control
debian/i3-wm.docs
debian/patches/use-x-terminal-emulator.patch
docs/userguide
i3-config-wizard/main.c
i3-input/i3-input.h
i3-input/main.c
i3-input/ucs2_to_utf8.c [deleted file]
i3-nagbar/main.c
i3-sensible-editor
i3-sensible-pager
i3-sensible-terminal
i3bar/include/common.h
i3bar/include/ucs2_to_utf8.h [deleted file]
i3bar/include/xcb.h
i3bar/src/ucs2_to_utf8.c [deleted file]
i3bar/src/workspaces.c
i3bar/src/xcb.c
include/data.h
include/libi3.h
include/util.h
include/xcb.h
libi3/font.c [new file with mode: 0644]
libi3/load_font.c [deleted file]
libi3/ucs2_conversion.c [new file with mode: 0644]
src/cfgparse.y
src/config.c
src/main.c
src/render.c
src/sighandler.c
src/util.c
src/window.c
src/x.c
src/xcb.c
testcases/Makefile.PL
testcases/complete-run.pl
testcases/lib/SocketActivation.pm
testcases/lib/i3test.pm
testcases/t/135-floating-focus.t
testcases/t/144-regress-floating-resize.t
testcases/t/145-flattening.t
testcases/t/147-regress-floatingmove.t
testcases/t/165-for_window.t
testcases/t/170-force_focus_wrapping.t
testcases/t/180-fd-leaks.t [new file with mode: 0644]

index be459af507f92a103d9a2c1b42f616358210e5f3..349911075937184436ce0e2cca77fae7afd44b48 100644 (file)
@@ -1,3 +1,9 @@
+i3-wm (4.1.1-0) unstable; urgency=low
+
+  * NOT YET RELEASED
+
+ -- Michael Stapelberg <michael@stapelberg.de>  Fri, 11 Nov 2011 23:00:04 +0000
+
 i3-wm (4.1-1) unstable; urgency=low
 
   * Switch to dpkg-source 3.0 (quilt) and compat level 7
index e3786cfb4f19854133381b873192cdd4b91f7cd1..1119d69d3bfa9722f555de79b7212986edc461b9 100644 (file)
@@ -10,8 +10,8 @@ Homepage: http://i3wm.org/
 Package: i3
 Architecture: any
 Section: x11
-Depends: i3-wm, ${misc:Depends}
-Recommends: i3lock, suckless-tools, i3status
+Depends: i3-wm (=${binary:Version}), ${misc:Depends}
+Recommends: i3lock (>= 2.2), suckless-tools, i3status (>= 2.3)
 Description: metapackage (i3 window manager, screen locker, menu, statusbar)
  This metapackage installs the i3 window manager (i3-wm), the i3lock screen
  locker, i3status (for system information) and suckless-tools (for dmenu).
index e5896855a0c396d0e49fc3741669a71d0da233d3..8d2662aaadb311c5f57bf9444f987d3c4106e87f 100644 (file)
@@ -17,3 +17,9 @@ docs/keyboard-layer2.png
 docs/testsuite.html
 docs/i3-sync-working.png
 docs/i3-sync.png
+docs/tree-layout1.png
+docs/tree-layout2.png
+docs/tree-shot1.png
+docs/tree-shot2.png
+docs/tree-shot3.png
+docs/tree-shot4.png
index fd515c168d84a4f6c7e30271622a428d776ceeba..28e9200df49c85caf2fc785e62ffdef694f79cb8 100644 (file)
@@ -11,7 +11,7 @@
 -# distribution-specific mechanism to find the preferred terminal emulator. On
 -# Debian, there is the x-terminal-emulator symlink for example.
 -# Please don't touch the first line, though:
- which $TERMINAL >/dev/null && exec $TERMINAL "$@"
[ -n "$TERMINAL" ] && which $TERMINAL >/dev/null && exec $TERMINAL "$@"
  
 +# Debian-specific: use x-terminal-emulator
 +which x-terminal-emulator >/dev/null && exec x-terminal-emulator "$@"
index 3d78d16f83020e1f838df9cd119dd7d24eb7bdd1..79c97b1268796e7160dce4efe355a37496fec39e 100644 (file)
@@ -174,7 +174,7 @@ Floating windows are always on top of tiling windows.
 i3 stores all information about the X11 outputs, workspaces and layout of the
 windows on them in a tree. The root node is the X11 root window, followed by
 the X11 outputs, then dock areas and a content container, then workspaces and
-finally the windows themselve. In previous versions of i3 we had multiple lists
+finally the windows themselves. In previous versions of i3 we had multiple lists
 (of outputs, workspaces) and a table for each workspace. That approach turned
 out to be complicated to use (snapping), understand and implement.
 
@@ -812,7 +812,9 @@ status_command command
 
 *Example*:
 -------------------------------------------------
-status_command i3status --config ~/.i3status.conf
+bar {
+    status_command i3status --config ~/.i3status.conf
+}
 -------------------------------------------------
 
 === Display mode
@@ -834,7 +836,9 @@ mode <dock|hide>
 
 *Example*:
 ----------------
-mode hide
+bar {
+    mode hide
+}
 ----------------
 
 === Position
@@ -850,7 +854,9 @@ position <top|bottom>
 
 *Example*:
 ---------------------
-position top
+bar {
+    position top
+}
 ---------------------
 
 === Output(s)
@@ -859,6 +865,9 @@ You can restrict i3bar to one or more outputs (monitors). The default is to
 handle all outputs. Restricting the outputs is useful for using different
 options for different outputs by using multiple 'bar' blocks.
 
+To make a particular i3bar instance handle multiple outputs, specify the output
+directive multiple times.
+
 *Syntax*:
 ---------------
 output <output>
@@ -868,18 +877,20 @@ output <output>
 -------------------------------
 # big monitor: everything
 bar {
-       output HDMI2
-       status_command i3status
+    # The display is connected either via HDMI or via DisplayPort
+    output HDMI2
+    output DP2
+    status_command i3status
 }
 
 # laptop monitor: bright colors and i3status with less modules.
 bar {
-       output LVDS1
-       status_command i3status --config ~/.i3status-small.conf
-       colors {
-               background #000000
-               statusline #ffffff
-       }
+    output LVDS1
+    status_command i3status --config ~/.i3status-small.conf
+    colors {
+        background #000000
+        statusline #ffffff
+    }
 }
 -------------------------------
 
@@ -899,10 +910,14 @@ tray_output <none|output>
 *Example*:
 -------------------------
 # disable system tray
-tray_output none
+bar {
+    tray_output none
+}
 
 # show tray icons on the big monitor
-tray_output HDMI2
+bar {
+    tray_output HDMI2
+}
 -------------------------
 
 === Font
@@ -917,7 +932,9 @@ font <font>
 
 *Example*:
 --------------------------------------------------------------
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+bar {
+    font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+}
 --------------------------------------------------------------
 
 === Workspace buttons
@@ -934,7 +951,9 @@ workspace_buttons <yes|no>
 
 *Example*:
 --------------------
-workspace_buttons no
+bar {
+    workspace_buttons no
+}
 --------------------
 
 === Colors
@@ -974,14 +993,16 @@ colors {
 
 *Example*:
 --------------------------------------
-colors {
-    background #000000
-    statusline #ffffff
-
-    focused_workspace  #ffffff #285577
-    active_workspace   #ffffff #333333
-    inactive_workspace #888888 #222222
-    urgent_workspace   #ffffff #900000
+bar {
+    colors {
+        background #000000
+        statusline #ffffff
+
+        focused_workspace  #ffffff #285577
+        active_workspace   #ffffff #333333
+        inactive_workspace #888888 #222222
+        urgent_workspace   #ffffff #900000
+    }
 }
 --------------------------------------
 
@@ -1063,7 +1084,7 @@ exec [--no-startup-id] command
 bindsym mod+g exec gimp
 
 # Start the terminal emulator urxvt which is not yet startup-notification-aware
-bindsym mod+enter exec --no-startup-id urxvt
+bindsym mod+Return exec --no-startup-id urxvt
 ------------------------------
 
 The +--no-startup-id+ parameter disables startup-notification support for this
index cdce0653be25f34053274615d5b68666a27406e3..84a7f77ee22f3f76b688c89aed0e8d0c7cfa9a1b 100644 (file)
@@ -112,13 +112,15 @@ static int handle_expose() {
     xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#000000") });
     xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
 
-    xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
+    set_font(&font);
 
-#define txt(x, row, text) xcb_image_text_8(conn, strlen(text), pixmap, pixmap_gc, x, (row * font.height) + 2, text)
+#define txt(x, row, text) \
+    draw_text(text, strlen(text), false, pixmap, pixmap_gc,\
+            x, (row - 1) * font.height + 4, 300 - x * 2)
 
     if (current_step == STEP_WELCOME) {
         /* restore font color */
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
+        set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
 
         txt(10, 2, "You have not configured i3 yet.");
         txt(10, 3, "Do you want me to generate ~/.i3/config?");
@@ -126,16 +128,16 @@ static int handle_expose() {
         txt(85, 7, "No, I will use the defaults");
 
         /* green */
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#00FF00") });
+        set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000"));
         txt(25, 5, "<Enter>");
 
         /* red */
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FF0000") });
+        set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000"));
         txt(31, 7, "<ESC>");
     }
 
     if (current_step == STEP_GENERATE) {
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
+        set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
 
         txt(10, 2, "Please choose either:");
         txt(85, 4, "Win as default modifier");
@@ -150,19 +152,19 @@ static int handle_expose() {
         else txt(31, 4, "<Win>");
 
         /* the selected modifier */
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ bold_font.id });
+        set_font(&bold_font);
+        set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
         if (modifier == MOD_Mod4)
             txt(31, 4, "<Win>");
         else txt(31, 5, "<Alt>");
 
         /* green */
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_FONT,
-                      (uint32_t[]) { get_colorpixel("#00FF00"), font.id });
-
+        set_font(&font);
+        set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000"));
         txt(25, 9, "<Enter>");
 
         /* red */
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FF0000") });
+        set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000"));
         txt(31, 10, "<ESC>");
     }
 
index d97807d1749decec42982bf935e50fb37d6028f3..f494cbd56704c260c4806e486a17fd301948c4b8 100644 (file)
@@ -14,7 +14,4 @@ while (0)
 
 extern xcb_window_t root;
 
-char *convert_ucs_to_utf8(char *input);
-char *convert_utf8_to_ucs2(char *input, int *real_strlen);
-
 #endif
index def6848177464d7a86eec2aa8b91ef3ad4b7f2f7..25ccceaac7208ea41ed85e6c79ca649f16b7bf51 100644 (file)
@@ -50,7 +50,7 @@ static char *glyphs_utf8[512];
 static int input_position;
 static i3Font font;
 static char *prompt;
-static int prompt_len;
+static size_t prompt_len;
 static int limit;
 xcb_window_t root;
 xcb_connection_t *conn;
@@ -94,7 +94,9 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t
     xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
 
     /* restore font color */
-    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
+    set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
+
+    /* draw the text */
     uint8_t *con = concat_strings(glyphs_ucs, input_position);
     char *full_text = (char*)con;
     if (prompt != NULL) {
@@ -104,8 +106,8 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t
         memcpy(full_text, prompt, prompt_len * 2);
         memcpy(full_text + (prompt_len * 2), con, input_position * 2);
     }
-    xcb_image_text_16(conn, input_position + prompt_len, pixmap, pixmap_gc, 4 /* X */,
-                      font.height + 2 /* Y = baseline of font */, (xcb_char2b_t*)full_text);
+    if (input_position + prompt_len != 0)
+        draw_text(full_text, input_position + prompt_len, true, pixmap, pixmap_gc, 4, 4, 492);
 
     /* Copy the contents of the pixmap to the real window */
     xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font.height + 8);
@@ -260,14 +262,14 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
 
     printf("inp[0] = %02x, inp[1] = %02x, inp[2] = %02x\n", inp[0], inp[1], inp[2]);
     /* convert it to UTF-8 */
-    char *out = convert_ucs_to_utf8((char*)inp);
+    char *out = convert_ucs2_to_utf8((xcb_char2b_t*)inp, 1);
     printf("converted to %s\n", out);
 
     glyphs_ucs[input_position] = malloc(3 * sizeof(uint8_t));
     if (glyphs_ucs[input_position] == NULL)
         err(EXIT_FAILURE, "malloc() failed\n");
     memcpy(glyphs_ucs[input_position], inp, 3);
-    glyphs_utf8[input_position] = strdup(out);
+    glyphs_utf8[input_position] = out;
     input_position++;
 
     if (input_position == limit)
@@ -348,7 +350,7 @@ int main(int argc, char *argv[]) {
     sockfd = ipc_connect(socket_path);
 
     if (prompt != NULL)
-        prompt = convert_utf8_to_ucs2(prompt, &prompt_len);
+        prompt = (char*)convert_utf8_to_ucs2(prompt, &prompt_len);
 
     int screens;
     conn = xcb_connect(NULL, &screens);
@@ -361,6 +363,7 @@ int main(int argc, char *argv[]) {
     symbols = xcb_key_symbols_alloc(conn);
 
     font = load_font(pattern, true);
+    set_font(&font);
 
     /* Open an input window */
     win = xcb_generate_id(conn);
@@ -393,9 +396,6 @@ int main(int argc, char *argv[]) {
      * this for us) */
     xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
 
-    /* Create graphics context */
-    xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
-
     /* Grab the keyboard to get all input */
     xcb_flush(conn);
 
diff --git a/i3-input/ucs2_to_utf8.c b/i3-input/ucs2_to_utf8.c
deleted file mode 100644 (file)
index df112ee..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * vim:ts=4:sw=4:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- * Â© 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
- *
- * ucs2_to_utf8.c: Converts between UCS-2 and UTF-8, both of which are used in
- *                 different contexts in X11.
- *
- */
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <err.h>
-#include <iconv.h>
-
-#include "libi3.h"
-
-static iconv_t conversion_descriptor = 0;
-static iconv_t conversion_descriptor2 = 0;
-
-/*
- * Returns the input string, but converted from UCS-2 to UTF-8. Memory will be
- * allocated, thus the caller has to free the output.
- *
- */
-char *convert_ucs_to_utf8(char *input) {
-    size_t input_size = 2;
-    /* UTF-8 may consume up to 4 byte */
-    int buffer_size = 8;
-
-    char *buffer = scalloc(buffer_size);
-    size_t output_size = buffer_size;
-    /* We need to use an additional pointer, because iconv() modifies it */
-    char *output = buffer;
-
-    /* We convert the input into UCS-2 big endian */
-    if (conversion_descriptor == 0) {
-        conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
-        if (conversion_descriptor == 0)
-            errx(EXIT_FAILURE, "Error opening the conversion context");
-    }
-
-    /* Get the conversion descriptor back to original state */
-    iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
-
-    /* Convert our text */
-    int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
-    if (rc == (size_t)-1) {
-        free(buffer);
-        perror("Converting to UCS-2 failed");
-        return NULL;
-    }
-
-    return buffer;
-}
-
-/*
- * Converts the given string to UCS-2 big endian for use with
- * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
- * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
- * returned. It has to be freed when done.
- *
- */
-char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
-    size_t input_size = strlen(input) + 1;
-    /* UCS-2 consumes exactly two bytes for each glyph */
-    int buffer_size = input_size * 2;
-
-    char *buffer = smalloc(buffer_size);
-    size_t output_size = buffer_size;
-    /* We need to use an additional pointer, because iconv() modifies it */
-    char *output = buffer;
-
-    /* We convert the input into UCS-2 big endian */
-    if (conversion_descriptor2 == 0) {
-        conversion_descriptor2 = iconv_open("UCS-2BE", "UTF-8");
-        if (conversion_descriptor2 == 0)
-            errx(EXIT_FAILURE, "Error opening the conversion context");
-    }
-
-    /* Get the conversion descriptor back to original state */
-    iconv(conversion_descriptor2, NULL, NULL, NULL, NULL);
-
-    /* Convert our text */
-    int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size);
-    if (rc == (size_t)-1) {
-        perror("Converting to UCS-2 failed");
-        free(buffer);
-        if (real_strlen != NULL)
-            *real_strlen = 0;
-        return NULL;
-    }
-
-    if (real_strlen != NULL)
-        *real_strlen = ((buffer_size - output_size) / 2) - 1;
-
-    return buffer;
-}
-
index 4d4e253a496a0cdabdd1a18a7cb3077bdfe98ccf..742039e2693e95611c7dadaae8273c81b0b95441 100644 (file)
@@ -131,17 +131,15 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
     xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect);
 
     /* restore font color */
-    uint32_t values[3];
-    values[0] = color_text;
-    values[1] = color_background;
-    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values);
-    xcb_image_text_8(conn, strlen(prompt), pixmap, pixmap_gc, 4 + 4/* X */,
-                      font.height + 2 + 4 /* Y = baseline of font */, prompt);
+    set_font_colors(pixmap_gc, color_text, color_background);
+    draw_text(prompt, strlen(prompt), false, pixmap, pixmap_gc,
+            4 + 4, 4 + 4, rect.width - 4 - 4);
 
     /* render close button */
     int line_width = 4;
     int w = 20;
     int y = rect.width;
+    uint32_t values[3];
     values[0] = color_button_background;
     values[1] = line_width;
     xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
@@ -159,12 +157,10 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
     };
     xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points);
 
-    values[0] = color_text;
-    values[1] = color_button_background;
-    values[2] = 1;
-    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_LINE_WIDTH, values);
-    xcb_image_text_8(conn, strlen("x"), pixmap, pixmap_gc, y - w - line_width + (w / 2) - 4/* X */,
-                      font.height + 2 + 4 - 1/* Y = baseline of font */, "X");
+    values[0] = 1;
+    set_font_colors(pixmap_gc, color_text, color_button_background);
+    draw_text("X", 1, false, pixmap, pixmap_gc, y - w - line_width + w / 2 - 4,
+            4 + 4 - 1, rect.width - y + w + line_width - w / 2 + 4);
     y -= w;
 
     y -= 20;
@@ -193,9 +189,9 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
 
         values[0] = color_text;
         values[1] = color_button_background;
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values);
-        xcb_image_text_8(conn, strlen(buttons[c].label), pixmap, pixmap_gc, y - w - line_width + 6/* X */,
-                          font.height + 2 + 3/* Y = baseline of font */, buttons[c].label);
+        set_font_colors(pixmap_gc, color_text, color_button_background);
+        draw_text(buttons[c].label, strlen(buttons[c].label), false, pixmap, pixmap_gc,
+                y - w - line_width + 6, 4 + 3, rect.width - y + w + line_width - 6);
 
         y -= w;
     }
@@ -304,6 +300,7 @@ int main(int argc, char *argv[]) {
     }
 
     font = load_font(pattern, true);
+    set_font(&font);
 
     /* Open an input window */
     win = xcb_generate_id(conn);
@@ -387,9 +384,6 @@ int main(int argc, char *argv[]) {
     xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font.height + 8);
     xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
 
-    /* Create graphics context */
-    xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
-
     /* Grab the keyboard to get all input */
     xcb_flush(conn);
 
@@ -431,9 +425,6 @@ int main(int argc, char *argv[]) {
 
                 xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, rect.width, rect.height);
                 xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
-
-                /* Create graphics context */
-                xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
                 break;
             }
         }
index dffe00d59360eaffb04b7ba3b51671d8b54a8745..d71a440bb1fe0c5c408ca8e18883f8ed114bb592 100755 (executable)
@@ -4,8 +4,8 @@
 #
 # Distributions/packagers can enhance this script with a
 # distribution-specific mechanism to find the preferred pager.
-which $VISUAL >/dev/null && exec $VISUAL "$@"
-which $EDITOR >/dev/null && exec $EDITOR "$@"
+[ -n "$VISUAL" ] && which $VISUAL >/dev/null && exec $VISUAL "$@"
+[ -n "$EDITOR" ] && which $EDITOR >/dev/null && exec $EDITOR "$@"
 
 # Hopefully one of these is installed (no flamewars about preference please!):
 which nano >/dev/null && exec nano "$@"
index 5af8d6b4e56be752aeac38a6ecbf005b37fb3f58..32f30aff895e670d39a3755e07870bf287e5358f 100755 (executable)
@@ -4,7 +4,7 @@
 #
 # Distributions/packagers can enhance this script with a
 # distribution-specific mechanism to find the preferred pager.
-which $PAGER >/dev/null && exec $PAGER "$@"
+[ -n "$PAGER" ] && which $PAGER >/dev/null && exec $PAGER "$@"
 
 # Hopefully one of these is installed:
 which most >/dev/null && exec most "$@"
index 28e60623a2f29f2d4db5c40ae0cb728463cf56e1..e5bf2718be1c3afa8623f6d3b577a6373bffad7b 100755 (executable)
@@ -6,7 +6,7 @@
 # distribution-specific mechanism to find the preferred terminal emulator. On
 # Debian, there is the x-terminal-emulator symlink for example.
 # Please don't touch the first line, though:
-which $TERMINAL >/dev/null && exec $TERMINAL "$@"
+[ -n "$TERMINAL" ] && which $TERMINAL >/dev/null && exec $TERMINAL "$@"
 
 # Hopefully one of these is installed:
 which xterm >/dev/null && exec xterm "$@"
index 3b6967faf347cdace0c353c9b30680ac032eb82f..bce31a4df22cd5c267d3bf9626b97025d424f2cb 100644 (file)
@@ -31,7 +31,6 @@ struct rect_t {
 #include "workspaces.h"
 #include "trayclients.h"
 #include "xcb.h"
-#include "ucs2_to_utf8.h"
 #include "config.h"
 #include "libi3.h"
 
diff --git a/i3bar/include/ucs2_to_utf8.h b/i3bar/include/ucs2_to_utf8.h
deleted file mode 100644 (file)
index a77ed20..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * vim:ts=4:sw=4:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- * Â© 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
- *
- * ucs2_to_utf8.c: Converts between UCS-2 and UTF-8, both of which are used in
- *                 different contexts in X11.
- */
-#ifndef _UCS2_TO_UTF8
-#define _UCS2_TO_UTF8
-
-char *convert_utf8_to_ucs2(char *input, int *real_strlen);
-
-#endif
index 8067a19382919a24805a0018c5ac2ec35d741ec9..f82c71159d7a10abb6023e54374ebe1cdddffe4c 100644 (file)
@@ -103,12 +103,4 @@ void draw_bars();
  */
 void redraw_bars();
 
-/*
- * Predicts the length of text based on cached data.
- * The string has to be encoded in ucs2 and glyph_len has to be the length
- * of the string (in glyphs).
- *
- */
-uint32_t predict_text_extents(xcb_char2b_t *text, uint32_t length);
-
 #endif
diff --git a/i3bar/src/ucs2_to_utf8.c b/i3bar/src/ucs2_to_utf8.c
deleted file mode 100644 (file)
index 642a72f..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * vim:ts=4:sw=4:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- * Â© 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
- *
- * ucs2_to_utf8.c: Converts between UCS-2 and UTF-8, both of which are used in
- *                 different contexts in X11.
- */
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <err.h>
-#include <iconv.h>
-
-#include "libi3.h"
-
-static iconv_t conversion_descriptor = 0;
-static iconv_t conversion_descriptor2 = 0;
-
-/*
- * Returns the input string, but converted from UCS-2 to UTF-8. Memory will be
- * allocated, thus the caller has to free the output.
- *
- */
-char *convert_ucs_to_utf8(char *input) {
-    size_t input_size = 2;
-    /* UTF-8 may consume up to 4 byte */
-    int buffer_size = 8;
-
-    char *buffer = scalloc(buffer_size);
-    size_t output_size = buffer_size;
-    /* We need to use an additional pointer, because iconv() modifies it */
-    char *output = buffer;
-
-    /* We convert the input into UCS-2 big endian */
-    if (conversion_descriptor == 0) {
-        conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
-        if (conversion_descriptor == 0) {
-            fprintf(stderr, "error opening the conversion context\n");
-            exit(1);
-        }
-    }
-
-    /* Get the conversion descriptor back to original state */
-    iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
-
-    /* Convert our text */
-    int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
-    if (rc == (size_t)-1) {
-        perror("Converting to UCS-2 failed");
-        free(buffer);
-        return NULL;
-    }
-
-    return buffer;
-}
-
-/*
- * Converts the given string to UCS-2 big endian for use with
- * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
- * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
- * returned. It has to be freed when done.
- *
- */
-char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
-    size_t input_size = strlen(input) + 1;
-    /* UCS-2 consumes exactly two bytes for each glyph */
-    int buffer_size = input_size * 2;
-
-    char *buffer = smalloc(buffer_size);
-    size_t output_size = buffer_size;
-    /* We need to use an additional pointer, because iconv() modifies it */
-    char *output = buffer;
-
-    /* We convert the input into UCS-2 big endian */
-    if (conversion_descriptor2 == 0) {
-        conversion_descriptor2 = iconv_open("UCS-2BE", "UTF-8");
-        if (conversion_descriptor2 == 0) {
-            fprintf(stderr, "error opening the conversion context\n");
-            exit(1);
-        }
-    }
-
-    /* Get the conversion descriptor back to original state */
-    iconv(conversion_descriptor2, NULL, NULL, NULL, NULL);
-
-    /* Convert our text */
-    int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size);
-    if (rc == (size_t)-1) {
-        perror("Converting to UCS-2 failed");
-        free(buffer);
-        if (real_strlen != NULL)
-            *real_strlen = 0;
-        return NULL;
-    }
-
-    if (real_strlen != NULL)
-        *real_strlen = ((buffer_size - output_size) / 2) - 1;
-
-    return buffer;
-}
index 7cfbeffd4e3e4ba912250611392bc7b5260021af..5df1899f33a408f4a8429a52acaa3a2b060c8f7a 100644 (file)
@@ -119,13 +119,13 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, unsigne
             params->workspaces_walk->name[len] = '\0';
 
             /* Convert the name to ucs2, save its length in glyphs and calculate its rendered width */
-            int ucs2_len;
+            size_t ucs2_len;
             xcb_char2b_t *ucs2_name = (xcb_char2b_t*) convert_utf8_to_ucs2(params->workspaces_walk->name, &ucs2_len);
             params->workspaces_walk->ucs2_name = ucs2_name;
             params->workspaces_walk->name_glyphs = ucs2_len;
             params->workspaces_walk->name_width =
-                predict_text_extents(params->workspaces_walk->ucs2_name,
-                params->workspaces_walk->name_glyphs);
+                predict_text_width((char *)params->workspaces_walk->ucs2_name,
+                params->workspaces_walk->name_glyphs, true);
 
             DLOG("Got Workspace %s, name_width: %d, glyphs: %d\n",
                  params->workspaces_walk->name,
index 29ffe1c41cfe96e0d9fa7a28ebee2f81f00ac00c..4a5ff69a9d4a291638fa6682a34c8d21ed289102 100644 (file)
@@ -48,12 +48,12 @@ xcb_connection_t *xcb_connection;
 int              screen;
 xcb_screen_t     *xcb_screen;
 xcb_window_t     xcb_root;
-xcb_font_t       xcb_font;
 
-/* We need to cache some data to speed up text-width-prediction */
-xcb_query_font_reply_t *font_info;
-int                    font_height;
-xcb_charinfo_t         *font_table;
+/* This is needed for integration with libi3 */
+xcb_connection_t *conn;
+
+/* The font we'll use */
+static i3Font font;
 
 /* These are only relevant for XKB, which we only need for grabbing modifiers */
 Display          *xkb_dpy;
@@ -99,98 +99,31 @@ int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) {
     return 0;
 }
 
-/*
- * Predicts the length of text based on cached data.
- * The string has to be encoded in ucs2 and glyph_len has to be the length
- * of the string (in glyphs).
- *
- */
-uint32_t predict_text_extents(xcb_char2b_t *text, uint32_t length) {
-    /* If we don't have per-character data, return the maximum width */
-    if (font_table == NULL) {
-        return (font_info->max_bounds.character_width * length);
-    }
-
-    uint32_t width = 0;
-    uint32_t i;
-
-    for (i = 0; i < length; i++) {
-        xcb_charinfo_t *info;
-        int row = text[i].byte1;
-        int col = text[i].byte2;
-
-        if (row < font_info->min_byte1 || row > font_info->max_byte1 ||
-            col < font_info->min_char_or_byte2 || col > font_info->max_char_or_byte2) {
-            continue;
-        }
-
-        /* Don't you ask me, how this one works… */
-        info = &font_table[((row - font_info->min_byte1) *
-                            (font_info->max_char_or_byte2 - font_info->min_char_or_byte2 + 1)) +
-                           (col - font_info->min_char_or_byte2)];
-
-        if (info->character_width != 0 ||
-            (info->right_side_bearing |
-             info->left_side_bearing |
-             info->ascent |
-             info->descent) != 0) {
-            width += info->character_width;
-        }
-    }
-
-    return width;
-}
-
-/*
- * Draws text given in UCS-2-encoding to a given drawable and position
- *
- */
-void draw_text(xcb_drawable_t drawable, xcb_gcontext_t ctx, int16_t x, int16_t y,
-               xcb_char2b_t *text, uint32_t glyph_count) {
-    int offset = 0;
-    int16_t pos_x = x;
-    int16_t font_ascent = font_info->font_ascent;
-
-    while (glyph_count > 0) {
-        uint8_t chunk_size = MIN(255, glyph_count);
-        uint32_t chunk_width = predict_text_extents(text + offset, chunk_size);
-
-        xcb_image_text_16(xcb_connection,
-                          chunk_size,
-                          drawable,
-                          ctx,
-                          pos_x, y + font_ascent,
-                          text + offset);
-
-        offset += chunk_size;
-        pos_x += chunk_width;
-        glyph_count -= chunk_size;
-    }
-}
-
 /*
  * Redraws the statusline to the buffer
  *
  */
 void refresh_statusline() {
-    int glyph_count;
+    size_t glyph_count;
 
     if (statusline == NULL) {
         return;
     }
 
-    xcb_char2b_t *text = (xcb_char2b_t*) convert_utf8_to_ucs2(statusline, &glyph_count);
+    xcb_char2b_t *text = (xcb_char2b_t*)convert_utf8_to_ucs2(statusline, &glyph_count);
     uint32_t old_statusline_width = statusline_width;
-    statusline_width = predict_text_extents(text, glyph_count);
+    statusline_width = predict_text_width((char*)text, glyph_count, true);
     /* If the statusline is bigger than our screen we need to make sure that
      * the pixmap provides enough space, so re-allocate if the width grew */
     if (statusline_width > xcb_screen->width_in_pixels &&
         statusline_width > old_statusline_width)
         realloc_sl_buffer();
 
-    xcb_rectangle_t rect = { 0, 0, xcb_screen->width_in_pixels, font_height };
+    xcb_rectangle_t rect = { 0, 0, xcb_screen->width_in_pixels, font.height };
     xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect);
-    draw_text(statusline_pm, statusline_ctx, 0, 0, text, glyph_count);
+    set_font_colors(statusline_ctx, colors.bar_fg, colors.bar_bg);
+    draw_text((char*)text, glyph_count, true, statusline_pm, statusline_ctx,
+            0, 0, xcb_screen->width_in_pixels);
 
     FREE(text);
 }
@@ -242,9 +175,9 @@ void unhide_bars() {
         values[0] = walk->rect.x;
         if (config.position == POS_TOP)
             values[1] = walk->rect.y;
-        else values[1] = walk->rect.y + walk->rect.h - font_height - 6;
+        else values[1] = walk->rect.y + walk->rect.h - font.height - 6;
         values[2] = walk->rect.w;
-        values[3] = font_height + 6;
+        values[3] = font.height + 6;
         values[4] = XCB_STACK_MODE_ABOVE;
         DLOG("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]);
         cookie = xcb_configure_window_checked(xcb_connection,
@@ -378,8 +311,8 @@ static void configure_trayclients() {
             clients++;
 
             DLOG("Configuring tray window %08x to x=%d\n",
-                 trayclient->win, output->rect.w - (clients * (font_height + 2)));
-            uint32_t x = output->rect.w - (clients * (font_height + 2));
+                 trayclient->win, output->rect.w - (clients * (font.height + 2)));
+            uint32_t x = output->rect.w - (clients * (font.height + 2));
             xcb_configure_window(xcb_connection,
                                  trayclient->win,
                                  XCB_CONFIG_WINDOW_X,
@@ -465,7 +398,7 @@ static void handle_client_message(xcb_client_message_event_t* event) {
             xcb_reparent_window(xcb_connection,
                                 client,
                                 output->bar,
-                                output->rect.w - font_height - 2,
+                                output->rect.w - font.height - 2,
                                 2);
             /* We reconfigure the window to use a reasonable size. The systray
              * specification explicitly says:
@@ -473,8 +406,8 @@ static void handle_client_message(xcb_client_message_event_t* event) {
              *   should do their best to cope with any size effectively
              */
             mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
-            values[0] = font_height;
-            values[1] = font_height;
+            values[0] = font.height;
+            values[1] = font.height;
             xcb_configure_window(xcb_connection,
                                  client,
                                  mask,
@@ -649,10 +582,10 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
                 continue;
 
             xcb_rectangle_t rect;
-            rect.x = output->rect.w - (clients * (font_height + 2));
+            rect.x = output->rect.w - (clients * (font.height + 2));
             rect.y = 2;
-            rect.width = font_height;
-            rect.height = font_height;
+            rect.width = font.height;
+            rect.height = font.height;
 
             DLOG("This is a tray window. x = %d\n", rect.x);
             fake_configure_notify(xcb_connection, rect, event->window, 0);
@@ -778,6 +711,7 @@ char *init_xcb_early() {
         ELOG("Cannot open display\n");
         exit(EXIT_FAILURE);
     }
+    conn = xcb_connection;
     DLOG("Connected to xcb\n");
 
     /* We have to request the atoms we need */
@@ -799,14 +733,12 @@ char *init_xcb_early() {
                                                                mask,
                                                                vals);
 
-    mask |= XCB_GC_BACKGROUND;
-    vals[0] = colors.bar_fg;
     statusline_ctx = xcb_generate_id(xcb_connection);
     xcb_void_cookie_t sl_ctx_cookie = xcb_create_gc_checked(xcb_connection,
                                                             statusline_ctx,
                                                             xcb_root,
-                                                            mask,
-                                                            vals);
+                                                            0,
+                                                            NULL);
 
     statusline_pm = xcb_generate_id(xcb_connection);
     xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection,
@@ -869,29 +801,13 @@ char *init_xcb_early() {
  *
  */
 void init_xcb_late(char *fontname) {
-    if (fontname == NULL) {
-        /* XXX: font fallback to 'misc' like i3 does it would be good. */
+    if (fontname == NULL)
         fontname = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
-    }
 
-    /* We load and allocate the font */
-    xcb_font = xcb_generate_id(xcb_connection);
-    xcb_void_cookie_t open_font_cookie;
-    open_font_cookie = xcb_open_font_checked(xcb_connection,
-                                             xcb_font,
-                                             strlen(fontname),
-                                             fontname);
-
-    /* We need to save info about the font, because we need the font's height and
-     * information about the width of characters */
-    xcb_query_font_cookie_t query_font_cookie;
-    query_font_cookie = xcb_query_font(xcb_connection,
-                                       xcb_font);
-
-    xcb_change_gc(xcb_connection,
-                  statusline_ctx,
-                  XCB_GC_FONT,
-                  (uint32_t[]){ xcb_font });
+    /* Load the font */
+    font = load_font(fontname, true);
+    set_font(&font);
+    DLOG("Calculated Font-height: %d\n", font.height);
 
     xcb_flush(xcb_connection);
 
@@ -936,25 +852,6 @@ void init_xcb_late(char *fontname) {
         ev_io_start(main_loop, xkb_io);
         XFlush(xkb_dpy);
     }
-
-    /* Now we save the font-infos */
-    font_info = xcb_query_font_reply(xcb_connection,
-                                     query_font_cookie,
-                                     NULL);
-
-    if (xcb_request_failed(open_font_cookie, "Could not open font")) {
-        exit(EXIT_FAILURE);
-    }
-
-    font_height = font_info->font_ascent + font_info->font_descent;
-
-    if (xcb_query_font_char_infos_length(font_info) == 0) {
-        font_table = NULL;
-    } else {
-        font_table = xcb_query_font_char_infos(font_info);
-    }
-
-    DLOG("Calculated Font-height: %d\n", font_height);
 }
 
 /*
@@ -1084,7 +981,6 @@ void clean_xcb() {
     FREE(xcb_chk);
     FREE(xcb_prep);
     FREE(xcb_io);
-    FREE(font_info);
 }
 
 /*
@@ -1137,7 +1033,7 @@ void realloc_sl_buffer() {
                                                                xcb_screen->height_in_pixels);
 
     uint32_t mask = XCB_GC_FOREGROUND;
-    uint32_t vals[3] = { colors.bar_bg, colors.bar_bg, xcb_font };
+    uint32_t vals[2] = { colors.bar_bg, colors.bar_bg };
     xcb_free_gc(xcb_connection, statusline_clear);
     statusline_clear = xcb_generate_id(xcb_connection);
     xcb_void_cookie_t clear_ctx_cookie = xcb_create_gc_checked(xcb_connection,
@@ -1146,7 +1042,7 @@ void realloc_sl_buffer() {
                                                                mask,
                                                                vals);
 
-    mask |= XCB_GC_BACKGROUND | XCB_GC_FONT;
+    mask |= XCB_GC_BACKGROUND;
     vals[0] = colors.bar_fg;
     statusline_ctx = xcb_generate_id(xcb_connection);
     xcb_free_gc(xcb_connection, statusline_ctx);
@@ -1207,8 +1103,8 @@ void reconfig_windows() {
                                                                      xcb_screen->root_depth,
                                                                      walk->bar,
                                                                      xcb_root,
-                                                                     walk->rect.x, walk->rect.y + walk->rect.h - font_height - 6,
-                                                                     walk->rect.w, font_height + 6,
+                                                                     walk->rect.x, walk->rect.y + walk->rect.h - font.height - 6,
+                                                                     walk->rect.w, font.height + 6,
                                                                      1,
                                                                      XCB_WINDOW_CLASS_INPUT_OUTPUT,
                                                                      xcb_screen->root_visual,
@@ -1282,12 +1178,12 @@ void reconfig_windows() {
                 case POS_NONE:
                     break;
                 case POS_TOP:
-                    strut_partial.top = font_height + 6;
+                    strut_partial.top = font.height + 6;
                     strut_partial.top_start_x = walk->rect.x;
                     strut_partial.top_end_x = walk->rect.x + walk->rect.w;
                     break;
                 case POS_BOT:
-                    strut_partial.bottom = font_height + 6;
+                    strut_partial.bottom = font.height + 6;
                     strut_partial.bottom_start_x = walk->rect.x;
                     strut_partial.bottom_end_x = walk->rect.x + walk->rect.w;
                     break;
@@ -1304,13 +1200,11 @@ void reconfig_windows() {
             /* We also want a graphics-context for the bars (it defines the properties
              * with which we draw to them) */
             walk->bargc = xcb_generate_id(xcb_connection);
-            mask = XCB_GC_FONT;
-            values[0] = xcb_font;
             xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(xcb_connection,
                                                                 walk->bargc,
                                                                 walk->bar,
-                                                                mask,
-                                                                values);
+                                                                0,
+                                                                NULL);
 
             /* We finally map the bar (display it on screen), unless the modifier-switch is on */
             xcb_void_cookie_t map_cookie;
@@ -1343,9 +1237,9 @@ void reconfig_windows() {
                    XCB_CONFIG_WINDOW_HEIGHT |
                    XCB_CONFIG_WINDOW_STACK_MODE;
             values[0] = walk->rect.x;
-            values[1] = walk->rect.y + walk->rect.h - font_height - 6;
+            values[1] = walk->rect.y + walk->rect.h - font.height - 6;
             values[2] = walk->rect.w;
-            values[3] = font_height + 6;
+            values[3] = font.height + 6;
             values[4] = XCB_STACK_MODE_ABOVE;
 
             DLOG("Destroying buffer for output %s", walk->name);
@@ -1401,7 +1295,7 @@ void draw_bars() {
                       outputs_walk->bargc,
                       XCB_GC_FOREGROUND,
                       &color);
-        xcb_rectangle_t rect = { 0, 0, outputs_walk->rect.w, font_height + 6 };
+        xcb_rectangle_t rect = { 0, 0, outputs_walk->rect.w, font.height + 6 };
         xcb_poly_fill_rectangle(xcb_connection,
                                 outputs_walk->buffer,
                                 outputs_walk->bargc,
@@ -1422,7 +1316,7 @@ void draw_bars() {
                 /* We assume the tray icons are quadratic (we use the font
                  * *height* as *width* of the icons) because we configured them
                  * like this. */
-                traypx += font_height + 2;
+                traypx += font.height + 2;
             }
             /* Add 2px of padding if there are any tray icons */
             if (traypx > 0)
@@ -1433,7 +1327,7 @@ void draw_bars() {
                           outputs_walk->bargc,
                           MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + 4)), 0,
                           MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - 4)), 3,
-                          MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font_height);
+                          MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font.height);
         }
 
         if (config.disable_ws) {
@@ -1467,22 +1361,15 @@ void draw_bars() {
                           outputs_walk->bargc,
                           mask,
                           vals);
-            xcb_rectangle_t rect = { i + 1, 1, ws_walk->name_width + 8, font_height + 4 };
+            xcb_rectangle_t rect = { i + 1, 1, ws_walk->name_width + 8, font.height + 4 };
             xcb_poly_fill_rectangle(xcb_connection,
                                     outputs_walk->buffer,
                                     outputs_walk->bargc,
                                     1,
                                     &rect);
-            xcb_change_gc(xcb_connection,
-                          outputs_walk->bargc,
-                          XCB_GC_FOREGROUND,
-                          &fg_color);
-            xcb_image_text_16(xcb_connection,
-                              ws_walk->name_glyphs,
-                              outputs_walk->buffer,
-                              outputs_walk->bargc,
-                              i + 5, font_info->font_ascent + 2,
-                              ws_walk->ucs2_name);
+            set_font_colors(outputs_walk->bargc, fg_color, bg_color);
+            draw_text((char*)ws_walk->ucs2_name, ws_walk->name_glyphs, true,
+                    outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, ws_walk->name_width);
             i += 10 + ws_walk->name_width;
         }
 
index 740278aecd12eb4bae72c0848d5ec25c474bf28d..3bc425d9c792f4161115c3d6a34ded12642859ac 100644 (file)
@@ -285,7 +285,7 @@ struct Window {
     char *name_json;
 
     /** The length of the name in glyphs (not bytes) */
-    int name_len;
+    size_t name_len;
 
     /** Whether the application used _NET_WM_NAME */
     bool uses_net_wm_name;
index 71fba764540f41ab5c4ff8e2c1e6e50a48373ba0..17f8a5eb99260bae09340ebff96c54929a7d8352 100644 (file)
@@ -27,10 +27,17 @@ typedef struct Font i3Font;
  *
  */
 struct Font {
-    /** The height of the font, built from font_ascent + font_descent */
-    int height;
     /** The xcb-id for the font */
     xcb_font_t id;
+
+    /** Font information gathered from the server */
+    xcb_query_font_reply_t *info;
+
+    /** Font table for this font (may be NULL) */
+    xcb_charinfo_t *table;
+
+    /** The height of the font, built from font_ascent + font_descent */
+    int height;
 };
 
 /* Since this file also gets included by utilities which don’t use the i3 log
@@ -177,6 +184,51 @@ uint32_t get_mod_mask_for(uint32_t keysym,
  * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting.
  *
  */
-i3Font load_font(const char *pattern, bool fallback);
+i3Font load_font(const char *pattern, const bool fallback);
+
+/**
+ * Converts the given string to UTF-8 from UCS-2 big endian. The return value
+ * must be freed after use.
+ *
+ */
+char *convert_ucs2_to_utf8(xcb_char2b_t *text, size_t num_glyphs);
+
+/**
+ * Converts the given string to UCS-2 big endian for use with
+ * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
+ * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
+ * returned. It has to be freed when done.
+ *
+ */
+xcb_char2b_t *convert_utf8_to_ucs2(char *input, size_t *real_strlen);
+
+/**
+ * Defines the font to be used for the forthcoming draw_text and
+ * predict_text_width calls.
+ *
+ */
+void set_font(i3Font *font);
+
+/**
+ * Defines the colors to be used for the forthcoming draw_text calls.
+ *
+ */
+void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background);
+
+/**
+ * Draws text onto the specified X drawable (normally a pixmap) at the
+ * specified coordinates (from the top left corner of the leftmost, uppermost
+ * glyph) and using the provided gc. Text can be specified as UCS-2 or UTF-8.
+ *
+ */
+void draw_text(char *text, size_t text_len, bool is_ucs2, xcb_drawable_t drawable,
+        xcb_gcontext_t gc, int x, int y, int max_width);
+
+/**
+ * Predict the text width in pixels for the given text. Text can be specified
+ * as UCS-2 or UTF-8.
+ *
+ */
+int predict_text_width(char *text, size_t text_len, bool is_ucs2);
 
 #endif
index 4a5920d2fbfa692627f17a09bc34a3fe5e8d1b97..cd88863c282a90b976bd9ba02fb6b4c3c58ef3ca 100644 (file)
@@ -91,15 +91,6 @@ void exec_i3_utility(char *name, char *argv[]);
 void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie,
                  char *err_message);
 
-/**
- * Converts the given string to UCS-2 big endian for use with
- * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen, a
- * buffer containing the UCS-2 encoded string (16 bit per glyph) is
- * returned. It has to be freed when done.
- *
- */
-char *convert_utf8_to_ucs2(char *input, int *real_strlen);
-
 /**
  * This function resolves ~ in pathnames.
  * It may resolve wildcards in the first part of the path, but if no match
index 01e2b66728ec28b022e6eaa7b5308b78b9dd2f5e..8c7d5422b79c083ccbd312c05176c00b2f45db30 100644 (file)
@@ -94,13 +94,6 @@ void send_take_focus(xcb_window_t window);
  */
 void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window);
 
-/**
- * Calculate the width of the given text (16-bit characters, UCS) with given
- * real length (amount of glyphs) using the given font.
- *
- */
-int predict_text_width(char *text, int length);
-
 /**
  * Configures the given window to have the size/position specified by given rect
  *
diff --git a/libi3/font.c b/libi3/font.c
new file mode 100644 (file)
index 0000000..045972d
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * Â© 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <err.h>
+
+#include "libi3.h"
+
+extern xcb_connection_t *conn;
+static const i3Font *savedFont = NULL;
+
+/*
+ * Loads a font for usage, also getting its metrics. If fallback is true,
+ * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting.
+ *
+ */
+i3Font load_font(const char *pattern, const bool fallback) {
+    i3Font font;
+
+    /* Send all our requests first */
+    font.id = xcb_generate_id(conn);
+    xcb_void_cookie_t font_cookie = xcb_open_font_checked(conn, font.id,
+            strlen(pattern), pattern);
+    xcb_query_font_cookie_t info_cookie = xcb_query_font(conn, font.id);
+
+    /* Check for errors. If errors, fall back to default font. */
+    xcb_generic_error_t *error;
+    error = xcb_request_check(conn, font_cookie);
+
+    /* If we fail to open font, fall back to 'fixed' */
+    if (fallback && error != NULL) {
+        ELOG("Could not open font %s (X error %d). Trying fallback to 'fixed'.\n",
+             pattern, error->error_code);
+        pattern = "fixed";
+        font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
+        info_cookie = xcb_query_font(conn, font.id);
+
+        /* Check if we managed to open 'fixed' */
+        error = xcb_request_check(conn, font_cookie);
+
+        /* Fall back to '-misc-*' if opening 'fixed' fails. */
+        if (error != NULL) {
+            ELOG("Could not open fallback font 'fixed', trying with '-misc-*'.\n");
+            pattern = "-misc-*";
+            font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
+            info_cookie = xcb_query_font(conn, font.id);
+
+            if ((error = xcb_request_check(conn, font_cookie)) != NULL)
+                errx(EXIT_FAILURE, "Could open neither requested font nor fallbacks "
+                     "(fixed or -misc-*): X11 error %d", error->error_code);
+        }
+    }
+
+    /* Get information (height/name) for this font */
+    if (!(font.info = xcb_query_font_reply(conn, info_cookie, NULL)))
+        errx(EXIT_FAILURE, "Could not load font \"%s\"", pattern);
+
+    /* Get the font table, if possible */
+    font.table = xcb_query_font_char_infos(font.info);
+
+    /* Calculate the font height */
+    font.height = font.info->font_ascent + font.info->font_descent;
+
+    return font;
+}
+
+/*
+ * Defines the font to be used for the forthcoming draw_text and
+ * predict_text_width calls.
+ *
+ */
+void set_font(i3Font *font) {
+    savedFont = font;
+}
+
+/*
+ * Defines the colors to be used for the forthcoming draw_text calls.
+ *
+ */
+void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background) {
+    assert(savedFont != NULL);
+    uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
+    uint32_t values[] = { foreground, background, savedFont->id };
+    xcb_change_gc(conn, gc, mask, values);
+}
+
+/*
+ * Draws text onto the specified X drawable (normally a pixmap) at the
+ * specified coordinates (from the top left corner of the leftmost, uppermost
+ * glyph) and using the provided gc. Text can be specified as UCS-2 or UTF-8.
+ *
+ */
+void draw_text(char *text, size_t text_len, bool is_ucs2, xcb_drawable_t drawable,
+               xcb_gcontext_t gc, int x, int y, int max_width) {
+    assert(savedFont != NULL);
+    assert(text_len != 0);
+
+    /* X11 coordinates for fonts start at the baseline */
+    int pos_y = y + savedFont->info->font_ascent;
+
+    /* As an optimization, check if we can bypass conversion */
+    if (!is_ucs2 && text_len <= 255) {
+        xcb_image_text_8(conn, text_len, drawable, gc, x, pos_y, text);
+        return;
+    }
+
+    /* Convert the text into UCS-2 so we can do basic pointer math */
+    char *input = (is_ucs2 ? text : (char*)convert_utf8_to_ucs2(text, &text_len));
+
+    /* The X11 protocol limits text drawing to 255 chars, so we may need
+     * multiple calls */
+    int pos_x = x;
+    int offset = 0;
+    for (;;) {
+        /* Calculate the size of this chunk */
+        int chunk_size = (text_len > 255 ? 255 : text_len);
+        xcb_char2b_t *chunk = (xcb_char2b_t*)input + offset;
+
+        /* Draw it */
+        xcb_image_text_16(conn, chunk_size, drawable, gc, pos_x, pos_y, chunk);
+
+        /* Advance the offset and length of the text to draw */
+        offset += chunk_size;
+        text_len -= chunk_size;
+
+        /* Check if we're done */
+        if (text_len == 0)
+            break;
+
+        /* Advance pos_x based on the predicted text width */
+        pos_x += predict_text_width((char*)chunk, chunk_size, true);
+    }
+
+    /* If we had to convert, free the converted string */
+    if (!is_ucs2)
+        free(input);
+}
+
+static int xcb_query_text_width(xcb_char2b_t *text, size_t text_len) {
+    /* Make the user know we’re using the slow path, but only once. */
+    static bool first_invocation = true;
+    if (first_invocation) {
+        fprintf(stderr, "Using slow code path for text extents\n");
+        first_invocation = false;
+    }
+
+    /* Query the text width */
+    xcb_generic_error_t *error;
+    xcb_query_text_extents_cookie_t cookie = xcb_query_text_extents(conn,
+            savedFont->id, text_len, (xcb_char2b_t*)text);
+    xcb_query_text_extents_reply_t *reply = xcb_query_text_extents_reply(conn,
+            cookie, &error);
+    if (reply == NULL) {
+        /* We return a safe estimate because a rendering error is better than
+         * a crash. Plus, the user will see the error in his log. */
+        fprintf(stderr, "Could not get text extents (X error code %d)\n",
+                error->error_code);
+        return savedFont->info->max_bounds.character_width * text_len;
+    }
+
+    int width = reply->overall_width;
+    free(reply);
+    return width;
+}
+
+/*
+ * Predict the text width in pixels for the given text. Text can be specified
+ * as UCS-2 or UTF-8.
+ *
+ */
+int predict_text_width(char *text, size_t text_len, bool is_ucs2) {
+    /* Convert the text into UTF-16 so we can do basic pointer math */
+    xcb_char2b_t *input;
+    if (is_ucs2)
+        input = (xcb_char2b_t*)text;
+    else
+        input = convert_utf8_to_ucs2(text, &text_len);
+
+    int width;
+    if (savedFont->table == NULL) {
+        /* If we don't have a font table, fall back to querying the server */
+        width = xcb_query_text_width(input, text_len);
+    } else {
+        /* Save some pointers for convenience */
+        xcb_query_font_reply_t *font_info = savedFont->info;
+        xcb_charinfo_t *font_table = savedFont->table;
+
+        /* Calculate the width using the font table */
+        width = 0;
+        for (size_t i = 0; i < text_len; i++) {
+            xcb_charinfo_t *info;
+            int row = input[i].byte1;
+            int col = input[i].byte2;
+
+            if (row < font_info->min_byte1 ||
+                row > font_info->max_byte1 ||
+                col < font_info->min_char_or_byte2 ||
+                col > font_info->max_char_or_byte2)
+                continue;
+
+            /* Don't you ask me, how this one works… (Merovius) */
+            info = &font_table[((row - font_info->min_byte1) *
+                    (font_info->max_char_or_byte2 - font_info->min_char_or_byte2 + 1)) +
+                (col - font_info->min_char_or_byte2)];
+
+            if (info->character_width != 0 ||
+                    (info->right_side_bearing |
+                     info->left_side_bearing |
+                     info->ascent |
+                     info->descent) != 0) {
+                width += info->character_width;
+            }
+        }
+    }
+
+    /* If we had to convert, free the converted string */
+    if (!is_ucs2)
+        free(input);
+
+    return width;
+}
diff --git a/libi3/load_font.c b/libi3/load_font.c
deleted file mode 100644 (file)
index acb52c0..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * vim:ts=4:sw=4:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- * Â© 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
- *
- */
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdbool.h>
-#include <err.h>
-
-#include "libi3.h"
-
-extern xcb_connection_t *conn;
-
-/*
- * Loads a font for usage, also getting its height. If fallback is true,
- * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting.
- *
- */
-i3Font load_font(const char *pattern, bool fallback) {
-    i3Font font;
-    xcb_void_cookie_t font_cookie;
-    xcb_list_fonts_with_info_cookie_t info_cookie;
-    xcb_list_fonts_with_info_reply_t *info_reply;
-    xcb_generic_error_t *error;
-
-    /* Send all our requests first */
-    font.id = xcb_generate_id(conn);
-    font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
-    info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
-    /* Check for errors. If errors, fall back to default font. */
-    error = xcb_request_check(conn, font_cookie);
-
-    /* If we fail to open font, fall back to 'fixed' */
-    if (fallback && error != NULL) {
-        ELOG("Could not open font %s (X error %d). Trying fallback to 'fixed'.\n",
-             pattern, error->error_code);
-        pattern = "fixed";
-        font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
-        info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
-        /* Check if we managed to open 'fixed' */
-        error = xcb_request_check(conn, font_cookie);
-
-        /* Fall back to '-misc-*' if opening 'fixed' fails. */
-        if (error != NULL) {
-            ELOG("Could not open fallback font 'fixed', trying with '-misc-*'.\n");
-            pattern = "-misc-*";
-            font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
-            info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
-            if ((error = xcb_request_check(conn, font_cookie)) != NULL)
-                errx(EXIT_FAILURE, "Could open neither requested font nor fallbacks "
-                     "(fixed or -misc-*): X11 error %d", error->error_code);
-        }
-    }
-
-    /* Get information (height/name) for this font */
-    if (!(info_reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL)))
-        errx(EXIT_FAILURE, "Could not load font \"%s\"", pattern);
-
-    font.height = info_reply->font_ascent + info_reply->font_descent;
-
-    free(info_reply);
-
-    return font;
-}
diff --git a/libi3/ucs2_conversion.c b/libi3/ucs2_conversion.c
new file mode 100644 (file)
index 0000000..6f7cf28
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * Â© 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <err.h>
+#include <errno.h>
+#include <iconv.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libi3.h"
+
+static iconv_t utf8_conversion_descriptor = (iconv_t)-1;
+static iconv_t ucs2_conversion_descriptor = (iconv_t)-1;
+
+/*
+ * Converts the given string to UTF-8 from UCS-2 big endian. The return value
+ * must be freed after use.
+ *
+ */
+char *convert_ucs2_to_utf8(xcb_char2b_t *text, size_t num_glyphs) {
+    /* Allocate the output buffer (UTF-8 is at most 4 bytes per glyph) */
+    size_t buffer_size = num_glyphs * 4 * sizeof(char) + 1;
+    char *buffer = scalloc(buffer_size * sizeof(char));
+
+    /* We need to use an additional pointer, because iconv() modifies it */
+    char *output = buffer;
+    size_t output_size = buffer_size - 1;
+
+    if (utf8_conversion_descriptor == (iconv_t)-1) {
+        /* Get a new conversion descriptor */
+        utf8_conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
+        if (utf8_conversion_descriptor == (iconv_t)-1)
+            err(EXIT_FAILURE, "Error opening the conversion context");
+    } else {
+        /* Reset the existing conversion descriptor */
+        iconv(utf8_conversion_descriptor, NULL, NULL, NULL, NULL);
+    }
+
+    /* Do the conversion */
+    size_t input_len = num_glyphs * sizeof(xcb_char2b_t);
+    size_t rc = iconv(utf8_conversion_descriptor, (char**)&text,
+            &input_len, &output, &output_size);
+    if (rc == (size_t)-1) {
+        perror("Converting to UTF-8 failed");
+        free(buffer);
+        return NULL;
+    }
+
+    return buffer;
+}
+
+/*
+ * Converts the given string to UCS-2 big endian for use with
+ * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
+ * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
+ * returned. It has to be freed when done.
+ *
+ */
+xcb_char2b_t *convert_utf8_to_ucs2(char *input, size_t *real_strlen) {
+    /* Calculate the input buffer size (UTF-8 is strlen-safe) */
+    size_t input_size = strlen(input);
+
+    /* Calculate the output buffer size and allocate the buffer */
+    size_t buffer_size = input_size * sizeof(xcb_char2b_t);
+    xcb_char2b_t *buffer = smalloc(buffer_size);
+
+    /* We need to use an additional pointer, because iconv() modifies it */
+    size_t output_size = buffer_size;
+    xcb_char2b_t *output = buffer;
+
+    if (ucs2_conversion_descriptor == (iconv_t)-1) {
+        /* Get a new conversion descriptor */
+        ucs2_conversion_descriptor = iconv_open("UCS-2BE", "UTF-8");
+        if (ucs2_conversion_descriptor == (iconv_t)-1)
+            err(EXIT_FAILURE, "Error opening the conversion context");
+    } else {
+        /* Reset the existing conversion descriptor */
+        iconv(ucs2_conversion_descriptor, NULL, NULL, NULL, NULL);
+    }
+
+    /* Do the conversion */
+    size_t rc = iconv(ucs2_conversion_descriptor, (char**)&input,
+            &input_size, (char**)&output, &output_size);
+    if (rc == (size_t)-1) {
+        perror("Converting to UCS-2 failed");
+        free(buffer);
+        if (real_strlen != NULL)
+            *real_strlen = 0;
+        return NULL;
+    }
+
+    /* Return the resulting string's length */
+    if (real_strlen != NULL)
+        *real_strlen = (buffer_size - output_size) / sizeof(xcb_char2b_t);
+
+    return buffer;
+}
index 79da317d2e19a3b9cc668aee4918d5af043d76e3..0d2c69771814c2ad0ced926efe26d12648e1b6a2 100644 (file)
@@ -1546,6 +1546,7 @@ font:
     TOKFONT STR
     {
         config.font = load_font($2, true);
+        set_font(&config.font);
         printf("font %s\n", $2);
         FREE(font_pattern);
         font_pattern = $2;
index 8efb491ece9657ec22465da7d873323d0ef38092..2d7fb3bf6e4d12aa0f1d848c94fd0a62dc0c95b9 100644 (file)
@@ -371,6 +371,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
     if (config.font.id == 0) {
         ELOG("You did not specify required configuration option \"font\"\n");
         config.font = load_font("fixed", true);
+        set_font(&config.font);
     }
 
 #if 0
index 062a48626d8765d1a99bd803847fe952b8fa424f..7a7c20ac437d3415392d93d61ad2b30076307846 100644 (file)
@@ -615,7 +615,9 @@ int main(int argc, char *argv[]) {
         ev_io_start(main_loop, ipc_io);
     }
 
-    /* Also handle the UNIX domain sockets passed via socket activation */
+    /* Also handle the UNIX domain sockets passed via socket activation. The
+     * parameter 1 means "remove the environment variables", we don’t want to
+     * pass these to child processes. */
     int fds = sd_listen_fds(1);
     if (fds < 0)
         ELOG("socket activation: Error in sd_listen_fds\n");
index 2905356c9d29a6f7c846c73123a02f532166ca5c..759c351fefe9364f5e50c719958d080531a6d4b9 100644 (file)
@@ -193,7 +193,9 @@ void render_con(Con *con, bool render_fullscreen) {
     }
 
     /* find the height for the decorations */
-    int deco_height = config.font.height + 5;
+    int deco_height = config.font.height + 4;
+    if (config.font.height & 0x01)
+        ++deco_height;
 
     /* precalculate the sizes to be able to correct rounding errors */
     int sizes[children];
index a029422bf778645f1396773b49122ad585f132ad..ca74813d36e2e0f215a4969e3740724368faf6ab 100644 (file)
@@ -47,15 +47,11 @@ static int sig_draw_window(xcb_window_t win, int width, int height, int font_hei
     xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
 
     /* restore font color */
-    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
+    set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
 
     for (int i = 0; i < sizeof(crash_text) / sizeof(char*); i++) {
-        int text_len = strlen(crash_text[i]);
-        char *full_text = convert_utf8_to_ucs2(crash_text[i], &text_len);
-        xcb_image_text_16(conn, text_len, pixmap, pixmap_gc, 8 /* X */,
-                          3 + (i + 1) * font_height /* Y = baseline of font */,
-                          (xcb_char2b_t*)full_text);
-        free(full_text);
+        draw_text(crash_text[i], strlen(crash_text[i]), false, pixmap, pixmap_gc,
+                8, 3 + (i - 1) * font_height, width - 16);
     }
 
     /* Copy the contents of the pixmap to the real window */
@@ -150,9 +146,9 @@ void handle_signal(int sig, siginfo_t *info, void *data) {
     int height = 13 + (crash_text_num * config.font.height);
 
     /* calculate width for longest text */
-    int text_len = strlen(crash_text[crash_text_longest]);
-    char *longest_text = convert_utf8_to_ucs2(crash_text[crash_text_longest], &text_len);
-    int font_width = predict_text_width(longest_text, text_len);
+    size_t text_len = strlen(crash_text[crash_text_longest]);
+    xcb_char2b_t *longest_text = convert_utf8_to_ucs2(crash_text[crash_text_longest], &text_len);
+    int font_width = predict_text_width((char *)longest_text, text_len, true);
     int width = font_width + 20;
 
     /* Open a popup window on each virtual screen */
@@ -169,9 +165,6 @@ void handle_signal(int sig, siginfo_t *info, void *data) {
         xcb_create_pixmap(conn, root_depth, pixmap, win, width, height);
         xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
 
-        /* Create graphics context */
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ config.font.id });
-
         /* Grab the keyboard to get all input */
         xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
 
index 72146bff7aa7c1769a1d1d341255a738f1dfc62f..57bfa89bb77051a53a5964ee4c3f7520079391ff 100644 (file)
@@ -12,7 +12,6 @@
 
 #include <sys/wait.h>
 #include <stdarg.h>
-#include <iconv.h>
 #if defined(__OpenBSD__)
 #include <sys/cdefs.h>
 #endif
@@ -24,8 +23,6 @@
 #define SN_API_NOT_YET_FROZEN 1
 #include <libsn/sn-launcher.h>
 
-static iconv_t conversion_descriptor = 0;
-
 int min(int a, int b) {
     return (a < b ? a : b);
 }
@@ -120,51 +117,6 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes
     }
 }
 
-/*
- * Converts the given string to UCS-2 big endian for use with
- * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
- * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
- * returned. It has to be freed when done.
- *
- */
-char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
-    size_t input_size = strlen(input) + 1;
-    /* UCS-2 consumes exactly two bytes for each glyph */
-    int buffer_size = input_size * 2;
-
-    char *buffer = smalloc(buffer_size);
-    size_t output_size = buffer_size;
-    /* We need to use an additional pointer, because iconv() modifies it */
-    char *output = buffer;
-
-    /* We convert the input into UCS-2 big endian */
-    if (conversion_descriptor == 0) {
-        conversion_descriptor = iconv_open("UCS-2BE", "UTF-8");
-        if (conversion_descriptor == 0) {
-            fprintf(stderr, "error opening the conversion context\n");
-            exit(1);
-        }
-    }
-
-    /* Get the conversion descriptor back to original state */
-    iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
-
-    /* Convert our text */
-    int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
-    if (rc == (size_t)-1) {
-        perror("Converting to UCS-2 failed");
-        FREE(buffer);
-        if (real_strlen != NULL)
-            *real_strlen = 0;
-        return NULL;
-    }
-
-    if (real_strlen != NULL)
-        *real_strlen = ((buffer_size - output_size) / 2) - 1;
-
-    return buffer;
-}
-
 /*
  * This function resolves ~ in pathnames.
  * It may resolve wildcards in the first part of the path, but if no match
index 30957a4b81678e3f629d12acb6303b17812fa8f2..270314eabb6cdca2a5d00a1809ee5323a09b5365 100644 (file)
@@ -68,8 +68,8 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo
         return;
     }
     /* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */
-    int len;
-    char *ucs2_name = convert_utf8_to_ucs2(new_name, &len);
+    size_t len;
+    xcb_char2b_t *ucs2_name = convert_utf8_to_ucs2(new_name, &len);
     if (ucs2_name == NULL) {
         LOG("Could not convert _NET_WM_NAME to UCS-2, ignoring new hint\n");
         FREE(new_name);
@@ -79,7 +79,7 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo
     FREE(win->name_x);
     FREE(win->name_json);
     win->name_json = new_name;
-    win->name_x = ucs2_name;
+    win->name_x = (char*)ucs2_name;
     win->name_len = len;
     win->name_x_changed = true;
     LOG("_NET_WM_NAME changed to \"%s\"\n", win->name_json);
diff --git a/src/x.c b/src/x.c
index 61824d58bc57145a644988c007bcd18279b093ff..aaa5b188e50691bf753295fefc087a45d52cd008 100644 (file)
--- a/src/x.c
+++ b/src/x.c
@@ -408,25 +408,17 @@ void x_draw_decoration(Con *con) {
     xcb_poly_segment(conn, parent->pixmap, parent->pm_gc, 2, segments);
 
     /* 6: draw the title */
-    uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
-    uint32_t values[] = { p->color->text, p->color->background, config.font.id };
-    xcb_change_gc(conn, parent->pm_gc, mask, values);
-    int text_offset_y = config.font.height + (con->deco_rect.height - config.font.height) / 2 - 1;
+    set_font_colors(parent->pm_gc, p->color->text, p->color->background);
+    int text_offset_y = (con->deco_rect.height - config.font.height) / 2;
 
     struct Window *win = con->window;
     if (win == NULL || win->name_x == NULL) {
         /* this is a non-leaf container, we need to make up a good description */
         // TODO: use a good description instead of just "another container"
-        xcb_image_text_8(
-            conn,
-            strlen("another container"),
-            parent->pixmap,
-            parent->pm_gc,
-            con->deco_rect.x + 2,
-            con->deco_rect.y + text_offset_y,
-            "another container"
-        );
-
+        draw_text("another container", strlen("another container"), false,
+                parent->pixmap, parent->pm_gc,
+                con->deco_rect.x + 2, con->deco_rect.y + text_offset_y,
+                con->deco_rect.width - 2);
         goto copy_pixmaps;
     }
 
@@ -447,26 +439,10 @@ void x_draw_decoration(Con *con) {
     //DLOG("indent_level = %d, indent_mult = %d\n", indent_level, indent_mult);
     int indent_px = (indent_level * 5) * indent_mult;
 
-    if (win->uses_net_wm_name)
-        xcb_image_text_16(
-            conn,
-            win->name_len,
-            parent->pixmap,
-            parent->pm_gc,
-            con->deco_rect.x + 2 + indent_px,
-            con->deco_rect.y + text_offset_y,
-            (xcb_char2b_t*)win->name_x
-        );
-    else
-        xcb_image_text_8(
-            conn,
-            win->name_len,
-            parent->pixmap,
-            parent->pm_gc,
-            con->deco_rect.x + 2 + indent_px,
-            con->deco_rect.y + text_offset_y,
-            win->name_x
-        );
+    draw_text(win->name_x, win->name_len, win->uses_net_wm_name,
+            parent->pixmap, parent->pm_gc,
+            con->deco_rect.x + 2 + indent_px, con->deco_rect.y + text_offset_y,
+            con->deco_rect.width - 2 - indent_px);
 
 copy_pixmaps:
     xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height);
index aa761cac74679873f32f0347d3675cce708de934..48906a26148145ffe3cbaa62ad2ef87b97bc20a9 100644 (file)
--- a/src/xcb.c
+++ b/src/xcb.c
@@ -130,32 +130,6 @@ void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) {
     xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values);
 }
 
-/*
- * Query the width of the given text (16-bit characters, UCS) with given real
- * length (amount of glyphs) using the given font.
- *
- */
-int predict_text_width(char *text, int length) {
-    xcb_query_text_extents_cookie_t cookie;
-    xcb_query_text_extents_reply_t *reply;
-    xcb_generic_error_t *error;
-    int width;
-
-    cookie = xcb_query_text_extents(conn, config.font.id, length, (xcb_char2b_t*)text);
-    if ((reply = xcb_query_text_extents_reply(conn, cookie, &error)) == NULL) {
-        ELOG("Could not get text extents (X error code %d)\n",
-             error->error_code);
-        /* We return the rather safe guess of 7 pixels, because a
-         * rendering error is better than a crash. Plus, the user will
-         * see the error in his log. */
-        return 7;
-    }
-
-    width = reply->overall_width;
-    free(reply);
-    return width;
-}
-
 /*
  * Configures the given window to have the size/position specified by given rect
  *
index 11385f763f631aa57e22d9868ce1f0e71230cf93..fd7b5e0d0cca229c66ae9cc2ea7548e734136d91 100755 (executable)
@@ -15,11 +15,19 @@ WriteMakefile(
        'EV'           => 0,
        'Inline'       => 0,
     },
-    # don't install any files from this directory
-    PM => {},
+    PM => {}, # do not install any files from this directory
     clean => {
-        FILES => 'testsuite-* latest'
+        FILES => 'testsuite-* latest i3-cfg-for-*',
     }
 );
-# and don't run the tests while installing
-sub MY::test { }
+
+package MY;
+sub test { } # do not run the tests while installing
+
+# do not rename the Makefile
+sub clean {
+    my $section = shift->SUPER::clean(@_);
+    $section =~ s/^\t\Q$_\E\n$//m for
+        '- $(MV) $(FIRST_MAKEFILE) $(MAKEFILE_OLD) $(DEV_NULL)';
+    $section;
+}
index c43fbf07b49cc4553a879ef79eee768ee40aec68..c62623f10245ae29ceab38165ed1c01156f62d49 100755 (executable)
@@ -24,10 +24,15 @@ use StartXDummy;
 use StatusLine;
 # the following modules are not shipped with Perl
 use AnyEvent;
+use AnyEvent::Util;
 use AnyEvent::Handle;
 use AnyEvent::I3 qw(:all);
 use X11::XCB;
 
+# Close superfluous file descriptors which were passed by running in a VIM
+# subshell or situations like that.
+AnyEvent::Util::close_all_fds_except(0, 1, 2);
+
 # We actually use AnyEvent to make sure it loads an event loop implementation.
 # Afterwards, we overwrite SIGCHLD:
 my $cv = AnyEvent->condvar;
@@ -51,6 +56,7 @@ sub Log { say $log "@_" }
 
 my $coverage_testing = 0;
 my $valgrind = 0;
+my $strace = 0;
 my $help = 0;
 # Number of tests to run in parallel. Important to know how many Xdummy
 # instances we need to start (unless @displays are given). Defaults to
@@ -62,6 +68,7 @@ my @childpids = ();
 my $result = GetOptions(
     "coverage-testing" => \$coverage_testing,
     "valgrind" => \$valgrind,
+    "strace" => \$strace,
     "display=s" => \@displays,
     "parallel=i" => \$parallel,
     "help|?" => \$help,
@@ -132,7 +139,7 @@ status_init(displays => \@wdisplays, tests => $num);
 
 # We start tests concurrently: For each display, one test gets started. Every
 # test starts another test after completing.
-take_job($_) for @wdisplays;
+for (@wdisplays) { $cv->begin; take_job($_) }
 
 #
 # Takes a test from the beginning of @testfiles and runs it.
@@ -147,8 +154,8 @@ take_job($_) for @wdisplays;
 sub take_job {
     my ($display) = @_;
 
-    my $test = shift @testfiles;
-    return unless $test;
+    my $test = shift @testfiles
+        or return $cv->end;
 
     my $dont_start = (slurp($test) =~ /# !NO_I3_INSTANCE!/);
     my $basename = basename($test);
@@ -171,8 +178,9 @@ sub take_job {
             display => $display,
             configfile => $tmpfile,
             outdir => $outdir,
-            logpath => $logpath,
+            testname => $basename,
             valgrind => $valgrind,
+            strace => $strace,
             cv => $activate_cv
         );
 
@@ -239,7 +247,7 @@ sub take_job {
         my $output;
         open(my $spool, '>', \$output);
         my $parser = TAP::Parser->new({
-            exec => [ 'sh', '-c', qq|DISPLAY=$display LOGPATH="$logpath" OUTDIR="$outdir" VALGRIND=$valgrind /usr/bin/perl -Ilib $test| ],
+            exec => [ 'sh', '-c', qq|DISPLAY=$display TESTNAME="$basename" OUTDIR="$outdir" VALGRIND=$valgrind STRACE=$strace /usr/bin/perl -Ilib $test| ],
             spool => $spool,
             merge => 1,
         });
@@ -280,7 +288,7 @@ sub take_job {
 
                         undef $_ for @watchers;
                         if (@done == $num) {
-                            $cv->send;
+                            $cv->end;
                         } else {
                             take_job($display);
                         }
@@ -354,7 +362,12 @@ complete-run.pl will start (num_cores * 2) Xdummy instances.
 =item B<--valgrind>
 
 Runs i3 under valgrind to find memory problems. The output will be available in
-C<latest/valgrind.log>.
+C<latest/valgrind-for-$test.log>.
+
+=item B<--strace>
+
+Runs i3 under strace to trace system calls. The output will be available in
+C<latest/strace-for-$test.log>.
 
 =item B<--coverage-testing>
 
index e0b08d746fc3bde846b502c159f8e06b94cdd095..da1dda33b3eb2db5a7dfe9e74e9b8d8a6e0af665 100644 (file)
@@ -5,8 +5,9 @@ use strict;
 use warnings;
 use IO::Socket::UNIX; # core
 use Cwd qw(abs_path); # core
-use POSIX (); # core
+use POSIX qw(:fcntl_h); # core
 use AnyEvent::Handle; # not core
+use AnyEvent::Util; # not core
 use Exporter 'import';
 use v5.10;
 
@@ -38,11 +39,6 @@ sub activate_i3 {
     # remove the old unix socket
     unlink($args{unix_socket_path});
 
-    # pass all file descriptors up to three to the children.
-    # we need to set this flag before opening the socket.
-    open(my $fdtest, '<', '/dev/null');
-    $^F = fileno($fdtest);
-    close($fdtest);
     my $socket = IO::Socket::UNIX->new(
         Listen => 1,
         Local => $args{unix_socket_path},
@@ -65,31 +61,57 @@ sub activate_i3 {
             '..',
             $ENV{PATH}
         );
-        # Only pass file descriptors 0 (stdin), 1 (stdout), 2 (stderr) and
-        # 3 (socket) to the child.
-        $^F = 3;
+
+        # We are about to exec, but we did not modify $^F to include $socket
+        # when creating the socket (because the file descriptor could have a
+        # number != 3 which would lead to i3 leaking a file descriptor). This
+        # caused Perl to set the FD_CLOEXEC flag, which would close $socket on
+        # exec(), effectively *NOT* passing $socket to the new process.
+        # Therefore, we explicitly clear FD_CLOEXEC (the only flag right now)
+        # by setting the flags to 0.
+        POSIX::fcntl($socket, F_SETFD, 0) or die "Could not clear fd flags: $!";
 
         # If the socket does not use file descriptor 3 by chance already, we
         # close fd 3 and dup2() the socket to 3.
         if (fileno($socket) != 3) {
             POSIX::close(3);
             POSIX::dup2(fileno($socket), 3);
+            POSIX::close(fileno($socket));
         }
 
+        # Make sure no file descriptors are open. Strangely, I got an open file
+        # descriptor pointing to AnyEvent/Impl/EV.pm when testing.
+        AnyEvent::Util::close_all_fds_except(0, 1, 2, 3);
+
         # Construct the command to launch i3. Use maximum debug level, disable
         # the interactive signalhandler to make it crash immediately instead.
         my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
 
+        # For convenience:
+        my $outdir = $args{outdir};
+        my $test = $args{testname};
+
         if ($args{valgrind}) {
             $i3cmd =
-                qq|valgrind -v --log-file="$args{outdir}/valgrind.log" | .
+                qq|valgrind -v --log-file="$outdir/valgrind-for-$test.log" | .
                 qq|--leak-check=full --track-origins=yes --num-callers=20 | .
                 qq|--tool=memcheck -- $i3cmd|;
         }
 
-        # Append to $args{logpath} instead of overwriting because i3 might be
+        my $logfile = "$outdir/i3-log-for-$test";
+        # Append to $logfile instead of overwriting because i3 might be
         # run multiple times in one testcase.
-        my $cmd = "exec $i3cmd -c $args{configfile} >>$args{logpath} 2>&1";
+        my $cmd = "exec $i3cmd -c $args{configfile} >>$logfile 2>&1";
+
+        if ($args{strace}) {
+            my $out = "$outdir/strace-for-$test.log";
+
+            # We overwrite LISTEN_PID with the correct process ID to make
+            # socket activation work (LISTEN_PID has to match getpid(),
+            # otherwise the LISTEN_FDS will be treated as a left-over).
+            $cmd = qq|strace -fF -s2048 -v -o "$out" -- | .
+                     'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"';
+        }
 
         # We need to use the shell due to using output redirections.
         exec '/bin/sh', '-c', $cmd;
index 7473b61757ae9c86037f2ecb3df70cf8307bc977..591064c43ef1e196de6a1829776cec3fc7cf1b68 100644 (file)
@@ -423,7 +423,7 @@ sub launch_with_config {
         $tmp_socket_path = File::Temp::tempnam('/tmp', 'i3-test-socket-');
     }
 
-    my ($fh, $tmpfile) = tempfile('i3-test-config-XXXXX', UNLINK => 1);
+    my ($fh, $tmpfile) = tempfile('/tmp/i3-test-config-XXXXX', UNLINK => 1);
     say $fh $config;
     say $fh "ipc-socket $tmp_socket_path" unless $dont_add_socket_path;
     close($fh);
@@ -434,8 +434,9 @@ sub launch_with_config {
         display => $ENV{DISPLAY},
         configfile => $tmpfile,
         outdir => $ENV{OUTDIR},
-        logpath => $ENV{LOGPATH},
+        testname => $ENV{TESTNAME},
         valgrind => $ENV{VALGRIND},
+        strace => $ENV{STRACE},
         cv => $cv,
     );
 
index a1ab211e501d59531bb50e8cb8d86a4bf91cc008..b43f0f7ffbfbffd93298a4100ec8e45e8a207d3d 100644 (file)
@@ -49,8 +49,7 @@ cmd 'floating enable';
 # now kill the third one (it's floating). focus should stay unchanged
 cmd '[id="' . $third->id . '"] kill';
 
-# TODO: wait for unmapnotify
-sync_with_i3($x);
+wait_for_unmap($x);
 
 is($x->input_focus, $second->id, 'second con still focused after killing third');
 
@@ -81,15 +80,12 @@ cmd 'floating enable';
 # now kill the second one. focus should fall back to the third one, which is
 # also floating
 cmd 'kill';
-
-# TODO: wait for unmapnotify
-sync_with_i3($x);
+wait_for_unmap($x);
 
 is($x->input_focus, $third->id, 'third con focused');
 
 cmd 'kill';
-# TODO: wait for unmapnotify
-sync_with_i3($x);
+wait_for_unmap($x);
 
 is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
 
@@ -122,15 +118,12 @@ sync_with_i3($x);
 # now kill the second one. focus should fall back to the third one, which is
 # also floating
 cmd 'kill';
-
-# TODO: wait for unmapnotify
-sync_with_i3($x);
+wait_for_unmap($x);
 
 is($x->input_focus, $third->id, 'third con focused');
 
 cmd 'kill';
-# TODO: wait for unmapnotify
-sync_with_i3($x);
+wait_for_unmap($x);
 
 is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
 
index de33eeb3961e76f581d08c9a7555ca937a81aea9..44a67bc3e4996c22e5660c2e8bec0aa9108a6d72 100644 (file)
@@ -8,13 +8,17 @@
 #
 use i3test;
 use List::Util qw(sum);
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
 
 my $tmp = fresh_workspace;
 
 cmd 'exec /usr/bin/urxvt';
-sleep 0.5;
+wait_for_map $x;
 cmd 'exec /usr/bin/urxvt';
-sleep 0.5;
+wait_for_map $x;
+
 my ($nodes, $focus) = get_ws_content($tmp);
 my $old_sum = sum map { $_->{rect}->{width} } @{$nodes};
 #cmd 'open';
@@ -22,12 +26,13 @@ cmd 'resize grow left 10 px or 25 ppt';
 cmd 'split v';
 #cmd 'open';
 cmd 'exec /usr/bin/urxvt';
-sleep 0.5;
+wait_for_map $x;
+
 cmd 'mode toggle';
-sleep 0.5;
-cmd 'kill';
+sync_with_i3 $x;
 
-sleep 0.5;
+cmd 'kill';
+wait_for_unmap $x;
 
 ($nodes, $focus) = get_ws_content($tmp);
 my $new_sum = sum map { $_->{rect}->{width} } @{$nodes};
index 904252e74c16fcf503e15144892d308bdddfca95..6810f479fdef2baf2bf900b18883ffbe1bafebe6 100644 (file)
@@ -21,8 +21,8 @@ my $left = open_window($x);
 my $mid = open_window($x);
 my $right = open_window($x);
 
-cmd 'move before v';
-cmd 'move after h';
+cmd 'move up';
+cmd 'move right';
 my $ws = get_ws($tmp);
 
 is($ws->{orientation}, 'horizontal', 'workspace orientation is horizontal');
index 771ace320f59f6b1f7b9754b646ec63e41d4d15c..5f14741b732902dcce6b95dbab5832d3ad5ac9b0 100644 (file)
@@ -29,14 +29,14 @@ cmd 'mode toggle';
 sleep 0.25;
 
 # move the con outside the floating con
-cmd 'move before v';
+cmd 'move up';
 sleep 0.25;
 
 does_i3_live;
 
 # move another con outside
 cmd '[id="' . $mid->id . '"] focus';
-cmd 'move before v';
+cmd 'move up';
 sleep 0.25;
 
 does_i3_live;
index cc100132f8c76723bab38ae14ab42ee17f5fa37d..6e921cef484339b7d72eb3744e611d6498c8bcfe 100644 (file)
@@ -237,6 +237,9 @@ cmd 'kill';
 wait_for_unmap $x;
 $window->destroy;
 
+# give i3 a chance to delete the window from its tree
+sync_with_i3 $x;
+
 @content = @{get_ws_content($tmp)};
 cmp_ok(@content, '==', 0, 'no nodes on this workspace now');
 
index 8a990f23e96dc649880f306de312a2438aaf1f80..48869c1081c75981aa59bd0368a6f50bbb61c588 100644 (file)
@@ -5,10 +5,20 @@
 # Tests if the 'force_focus_wrapping' config directive works correctly.
 #
 use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
 
-my $x = X11::XCB::Connection->new;
+{
+    package i3test::X11;
+    use parent 'X11::XCB::Connection';
+
+    sub input_focus {
+        my $self = shift;
+        i3test::sync_with_i3($self);
+
+        return $self->SUPER::input_focus(@_);
+    }
+}
+
+my $x = i3test::X11->new;
 
 #####################################################################
 # 1: test the wrapping behaviour without force_focus_wrapping
diff --git a/testcases/t/180-fd-leaks.t b/testcases/t/180-fd-leaks.t
new file mode 100644 (file)
index 0000000..487803c
--- /dev/null
@@ -0,0 +1,49 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Verifies that i3 does not leak any file descriptors in 'exec'.
+#
+use i3test;
+use POSIX qw(mkfifo);
+use File::Temp qw(:POSIX tempfile);
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = tmpnam();
+mkfifo($tmp, 0600) or die "Could not create FIFO in $tmp";
+my ($outfh, $outname) = tempfile('/tmp/i3-ls-output.XXXXXX', UNLINK => 1);
+
+cmd qq|exec ls -l /proc/self/fd >$outname && echo done >$tmp|;
+
+open(my $fh, '<', $tmp);
+# Block on the FIFO, this will return exactly when the command is done.
+<$fh>;
+close($fh);
+unlink($tmp);
+
+# Get the ls /proc/self/fd output
+my $output;
+{
+    local $/;
+    $output = <$outfh>;
+}
+close($outfh);
+
+# Split lines, keep only those which are symlinks.
+my @lines = grep { /->/ } split("\n", $output);
+
+my %fds = map { /([0-9]+) -> (.+)$/; ($1, $2) } @lines;
+
+# Filter out 0, 1, 2 (stdin, stdout, stderr).
+delete $fds{0};
+delete $fds{1};
+delete $fds{2};
+
+# Filter out the fd which is caused by ls calling readdir().
+for my $fd (keys %fds) {
+    delete $fds{$fd} if $fds{$fd} =~ m,^/proc/\d+/fd$,;
+}
+
+is(scalar keys %fds, 0, 'No file descriptors leaked');
+
+done_testing;