+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
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).
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
-# 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 "$@"
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.
*Example*:
-------------------------------------------------
-status_command i3status --config ~/.i3status.conf
+bar {
+ status_command i3status --config ~/.i3status.conf
+}
-------------------------------------------------
=== Display mode
*Example*:
----------------
-mode hide
+bar {
+ mode hide
+}
----------------
=== Position
*Example*:
---------------------
-position top
+bar {
+ position top
+}
---------------------
=== Output(s)
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>
-------------------------------
# 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
+ }
}
-------------------------------
*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
*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
*Example*:
--------------------
-workspace_buttons no
+bar {
+ workspace_buttons no
+}
--------------------
=== 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
+ }
}
--------------------------------------
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
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?");
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");
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>");
}
extern xcb_window_t root;
-char *convert_ucs_to_utf8(char *input);
-char *convert_utf8_to_ucs2(char *input, int *real_strlen);
-
#endif
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;
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) {
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);
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)
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);
symbols = xcb_key_symbols_alloc(conn);
font = load_font(pattern, true);
+ set_font(&font);
/* Open an input window */
win = xcb_generate_id(conn);
* 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);
+++ /dev/null
-/*
- * 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;
-}
-
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);
};
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;
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;
}
}
font = load_font(pattern, true);
+ set_font(&font);
/* Open an input window */
win = xcb_generate_id(conn);
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);
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;
}
}
#
# 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 "$@"
#
# 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 "$@"
# 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 "$@"
#include "workspaces.h"
#include "trayclients.h"
#include "xcb.h"
-#include "ucs2_to_utf8.h"
#include "config.h"
#include "libi3.h"
+++ /dev/null
-/*
- * 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
*/
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
+++ /dev/null
-/*
- * 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;
-}
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,
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;
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);
}
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,
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,
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:
* 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,
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);
ELOG("Cannot open display\n");
exit(EXIT_FAILURE);
}
+ conn = xcb_connection;
DLOG("Connected to xcb\n");
/* We have to request the atoms we need */
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,
*
*/
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);
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);
}
/*
FREE(xcb_chk);
FREE(xcb_prep);
FREE(xcb_io);
- FREE(font_info);
}
/*
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,
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);
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,
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;
/* 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;
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);
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,
/* 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)
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) {
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;
}
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;
*
*/
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
* 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
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
*/
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
*
--- /dev/null
+/*
+ * 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;
+}
+++ /dev/null
-/*
- * 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;
-}
--- /dev/null
+/*
+ * 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;
+}
TOKFONT STR
{
config.font = load_font($2, true);
+ set_font(&config.font);
printf("font %s\n", $2);
FREE(font_pattern);
font_pattern = $2;
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
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");
}
/* 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];
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 */
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 */
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);
#include <sys/wait.h>
#include <stdarg.h>
-#include <iconv.h>
#if defined(__OpenBSD__)
#include <sys/cdefs.h>
#endif
#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);
}
}
}
-/*
- * 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
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);
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);
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;
}
//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);
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
*
'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;
+}
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;
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
my $result = GetOptions(
"coverage-testing" => \$coverage_testing,
"valgrind" => \$valgrind,
+ "strace" => \$strace,
"display=s" => \@displays,
"parallel=i" => \$parallel,
"help|?" => \$help,
# 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.
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);
display => $display,
configfile => $tmpfile,
outdir => $outdir,
- logpath => $logpath,
+ testname => $basename,
valgrind => $valgrind,
+ strace => $strace,
cv => $activate_cv
);
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,
});
undef $_ for @watchers;
if (@done == $num) {
- $cv->send;
+ $cv->end;
} else {
take_job($display);
}
=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>
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;
# 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},
'..',
$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;
$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);
display => $ENV{DISPLAY},
configfile => $tmpfile,
outdir => $ENV{OUTDIR},
- logpath => $ENV{LOGPATH},
+ testname => $ENV{TESTNAME},
valgrind => $ENV{VALGRIND},
+ strace => $ENV{STRACE},
cv => $cv,
);
# 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');
# 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');
# 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');
#
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';
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};
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');
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;
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');
# 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
--- /dev/null
+#!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;