]> git.sur5r.net Git - i3/i3/commitdiff
Merge pull request #2004 from Airblader/bug-2003
authorMichael Stapelberg <stapelberg@users.noreply.github.com>
Sun, 18 Oct 2015 10:26:37 +0000 (12:26 +0200)
committerMichael Stapelberg <stapelberg@users.noreply.github.com>
Sun, 18 Oct 2015 10:26:37 +0000 (12:26 +0200)
Fix moving windows to a marked workspace by mark.

30 files changed:
docs/ipc
docs/userguide
i3bar/include/cairo_util.h [deleted file]
i3bar/include/common.h
i3bar/include/draw_util.h [new file with mode: 0644]
i3bar/include/outputs.h
i3bar/src/cairo_util.c [deleted file]
i3bar/src/child.c
i3bar/src/config.c
i3bar/src/draw_util.c [new file with mode: 0644]
i3bar/src/mode.c
i3bar/src/outputs.c
i3bar/src/workspaces.c
i3bar/src/xcb.c
include/bindings.h
include/config.h
include/config_directives.h
include/libi3.h
libi3/font.c
libi3/string.c
man/i3.man
parser-specs/config.spec
src/bindings.c
src/config_directives.c
src/manage.c
src/window.c
src/workspace.c
testcases/t/201-config-parser.t
testcases/t/242-no-focus.t
testcases/t/528-workspace-next-prev.t [new file with mode: 0644]

index 5113d79bf60abc178c798ab1f33ca30873fa7b9b..1813e53a863e2a2561599cb62be0ee1324f4e138 100644 (file)
--- a/docs/ipc
+++ b/docs/ipc
@@ -717,11 +717,15 @@ This event consists of a single serialized map containing a property
 This event consists of a single serialized map containing a property
 +change (string)+ which holds the name of current mode in use. The name
 is the same as specified in config when creating a mode. The default
-mode is simply named default.
+mode is simply named default. It contains a second property, +pango_markup+, which
+defines whether pango markup shall be used for displaying this mode.
 
 *Example:*
 ---------------------------
-{ "change": "default" }
+{
+  "change": "default",
+  "pango_markup": true
+}
 ---------------------------
 
 === window event
index c4c937964c70926964e07c3d55a2b15280bac0ab..c98c25f9d98039772c4e92c53ff1d0a518cc3d5c 100644 (file)
@@ -610,6 +610,10 @@ Note that this does not apply to all cases, e.g., when feeding data into a runni
 causing it to request being focused. To configure the behavior in such cases, refer to
 <<focus_on_window_activation>>.
 
++no_focus+ will also be ignored for the first window on a workspace as there shouldn't be
+a reason to not focus the window in this case. This allows for better usability in
+combination with +workspace_layout+.
+
 *Syntax*:
 -------------------
 no_focus <criteria>
@@ -1845,6 +1849,10 @@ container to workspace next+, +move container to workspace prev+ to move a
 container to the next/previous workspace and +move container to workspace current+
 (the last one makes sense only when used with criteria).
 
++workspace next+ cycles through either numbered or named workspaces. But when it
+reaches the last numbered/named workspace, it looks for named workspaces after
+exhausting numbered ones and looks for numbered ones after exhausting named ones.
+
 See <<move_to_outputs>> for how to move a container/workspace to a different
 RandR output.
 
diff --git a/i3bar/include/cairo_util.h b/i3bar/include/cairo_util.h
deleted file mode 100644 (file)
index 37eaa6e..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * vim:ts=4:sw=4:expandtab
- *
- * © 2015 Ingo Bürk and contributors (see also: LICENSE)
- *
- * cairo_util.h: Utility for operations using cairo.
- *
- */
-#pragma once
-
-#include <cairo/cairo-xcb.h>
-
-/* Represents a color split by color channel. */
-typedef struct color_t {
-    double red;
-    double green;
-    double blue;
-
-    /* For compatibility, we also store the colorpixel for now. */
-    uint32_t colorpixel;
-} color_t;
-
-/* A wrapper grouping an XCB drawable and both a graphics context
- * and the corresponding cairo objects representing it. */
-typedef struct surface_t {
-    /* The drawable which is being represented. */
-    xcb_drawable_t id;
-
-    // TODO remove this once i3 uses solely cairo for drawing operations
-    /* A classic XCB graphics context. This should not be used for
-     * drawing operations. */
-    xcb_gcontext_t gc;
-
-    /* A cairo surface representing the drawable. */
-    cairo_surface_t *surface;
-
-    /* The cairo object representing the drawale. In general,
-     * this is what one should use for any drawing operation. */
-    cairo_t *cr;
-} surface_t;
-
-/**
- * Initialize the cairo surface to represent the given drawable.
- *
- */
-void cairo_surface_init(surface_t *surface, xcb_drawable_t drawable, int width, int height);
-
-/**
- * Destroys the surface.
- *
- */
-void cairo_surface_free(surface_t *surface);
-
-/**
- * Parses the given color in hex format to an internal color representation.
- * Note that the input must begin with a hash sign, e.g., "#3fbc59".
- *
- */
-color_t cairo_hex_to_color(const char *color);
-
-/**
- * Set the given color as the source color on the surface.
- *
- */
-void cairo_set_source_color(surface_t *surface, color_t color);
-
-/**
- * Draw the given text using libi3.
- * This function also marks the surface dirty which is needed if other means of
- * drawing are used. This will be the case when using XCB to draw text.
- *
- */
-void cairo_draw_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width);
-
-/**
- * Draws a filled rectangle.
- * This function is a convenience wrapper and takes care of flushing the
- * surface as well as restoring the cairo state.
- * Note that the drawing is done using CAIRO_OPERATOR_SOURCE.
- *
- */
-void cairo_draw_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h);
-
-/**
- * Copies a surface onto another surface.
- * Note that the drawing is done using CAIRO_OPERATOR_SOURCE.
- *
- */
-void cairo_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y,
-                        double dest_x, double dest_y, double dest_w, double dest_h);
index 50d1c7b9b7204177af7b830352500ddefcaa93a3..4d2dbd3565f5be6ef305a120ce8c1242f8038ee3 100644 (file)
@@ -49,7 +49,7 @@ struct status_block {
 
     bool urgent;
     bool no_separator;
-    bool is_markup;
+    bool pango_markup;
 
     /* The amount of pixels necessary to render a separater after the block. */
     uint32_t sep_block_width;
@@ -80,4 +80,4 @@ TAILQ_HEAD(statusline_head, status_block) statusline_head;
 #include "config.h"
 #include "libi3.h"
 #include "parse_json_header.h"
-#include "cairo_util.h"
+#include "draw_util.h"
diff --git a/i3bar/include/draw_util.h b/i3bar/include/draw_util.h
new file mode 100644 (file)
index 0000000..9bac72c
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * © 2015 Ingo Bürk and contributors (see also: LICENSE)
+ *
+ * draw.h: Utility for drawing.
+ *
+ */
+#pragma once
+
+#ifdef I3BAR_CAIRO
+#include <cairo/cairo-xcb.h>
+#endif
+
+#ifdef I3BAR_CAIRO
+/* We need to flush cairo surfaces twice to avoid an assertion bug. See #1989
+ * and https://bugs.freedesktop.org/show_bug.cgi?id=92455. */
+#define CAIRO_SURFACE_FLUSH(surface)  \
+    do {                              \
+        cairo_surface_flush(surface); \
+        cairo_surface_flush(surface); \
+    } while (0)
+#endif
+
+/* Represents a color split by color channel. */
+typedef struct color_t {
+    double red;
+    double green;
+    double blue;
+
+    /* For compatibility, we also store the colorpixel for now. */
+    uint32_t colorpixel;
+} color_t;
+
+/* A wrapper grouping an XCB drawable and both a graphics context
+ * and the corresponding cairo objects representing it. */
+typedef struct surface_t {
+    /* The drawable which is being represented. */
+    xcb_drawable_t id;
+
+    /* A classic XCB graphics context. */
+    xcb_gcontext_t gc;
+
+    int width;
+    int height;
+
+#ifdef I3BAR_CAIRO
+    /* A cairo surface representing the drawable. */
+    cairo_surface_t *surface;
+
+    /* The cairo object representing the drawale. In general,
+     * this is what one should use for any drawing operation. */
+    cairo_t *cr;
+#endif
+} surface_t;
+
+/**
+ * Initialize the surface to represent the given drawable.
+ *
+ */
+void draw_util_surface_init(surface_t *surface, xcb_drawable_t drawable, int width, int height);
+
+/**
+ * Destroys the surface.
+ *
+ */
+void draw_util_surface_free(surface_t *surface);
+
+/**
+ * Parses the given color in hex format to an internal color representation.
+ * Note that the input must begin with a hash sign, e.g., "#3fbc59".
+ *
+ */
+color_t draw_util_hex_to_color(const char *color);
+
+/**
+ * Draw the given text using libi3.
+ * This function also marks the surface dirty which is needed if other means of
+ * drawing are used. This will be the case when using XCB to draw text.
+ *
+ */
+void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width);
+
+/**
+ * Draws a filled rectangle.
+ * This function is a convenience wrapper and takes care of flushing the
+ * surface as well as restoring the cairo state.
+ *
+ */
+void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h);
+
+/**
+ * Clears a surface with the given color.
+ *
+ */
+void draw_util_clear_surface(surface_t *surface, color_t color);
+
+/**
+ * Copies a surface onto another surface.
+ *
+ */
+void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y,
+                            double dest_x, double dest_y, double width, double height);
index 7ad3ed5052a290823cc6ecb57d089ebf85ede406..87cc023e8b8fdbc1b61964eaee7fbbdcc0c40d56 100644 (file)
@@ -13,7 +13,7 @@
 #include <cairo/cairo-xcb.h>
 
 #include "common.h"
-#include "cairo_util.h"
+#include "draw_util.h"
 
 typedef struct i3_output i3_output;
 
diff --git a/i3bar/src/cairo_util.c b/i3bar/src/cairo_util.c
deleted file mode 100644 (file)
index 52181ae..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * vim:ts=4:sw=4:expandtab
- *
- * © 2015 Ingo Bürk and contributors (see also: LICENSE)
- *
- * cairo_util.c: Utility for operations using cairo.
- *
- */
-#include <stdlib.h>
-#include <err.h>
-#include <string.h>
-#include <xcb/xcb.h>
-#include <xcb/xcb_aux.h>
-#include <cairo/cairo-xcb.h>
-
-#include "common.h"
-#include "libi3.h"
-
-xcb_connection_t *xcb_connection;
-xcb_visualtype_t *visual_type;
-
-/*
- * Initialize the cairo surface to represent the given drawable.
- *
- */
-void cairo_surface_init(surface_t *surface, xcb_drawable_t drawable, int width, int height) {
-    surface->id = drawable;
-
-    surface->gc = xcb_generate_id(xcb_connection);
-    xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(xcb_connection, surface->gc, surface->id, 0, NULL);
-    if (xcb_request_failed(gc_cookie, "Could not create graphical context"))
-        exit(EXIT_FAILURE);
-
-    surface->surface = cairo_xcb_surface_create(xcb_connection, surface->id, visual_type, width, height);
-    surface->cr = cairo_create(surface->surface);
-}
-
-/*
- * Destroys the surface.
- *
- */
-void cairo_surface_free(surface_t *surface) {
-    xcb_free_gc(xcb_connection, surface->gc);
-    cairo_surface_destroy(surface->surface);
-    cairo_destroy(surface->cr);
-}
-
-/*
- * Parses the given color in hex format to an internal color representation.
- * Note that the input must begin with a hash sign, e.g., "#3fbc59".
- *
- */
-color_t cairo_hex_to_color(const char *color) {
-    char groups[3][3] = {
-        {color[1], color[2], '\0'},
-        {color[3], color[4], '\0'},
-        {color[5], color[6], '\0'}};
-
-    return (color_t){
-        .red = strtol(groups[0], NULL, 16) / 255.0,
-        .green = strtol(groups[1], NULL, 16) / 255.0,
-        .blue = strtol(groups[2], NULL, 16) / 255.0,
-        .colorpixel = get_colorpixel(color)};
-}
-
-/*
- * Set the given color as the source color on the surface.
- *
- */
-void cairo_set_source_color(surface_t *surface, color_t color) {
-    cairo_set_source_rgb(surface->cr, color.red, color.green, color.blue);
-}
-
-/**
- * Draw the given text using libi3.
- * This function also marks the surface dirty which is needed if other means of
- * drawing are used. This will be the case when using XCB to draw text.
- *
- */
-void cairo_draw_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width) {
-    set_font_colors(surface->gc, fg_color.colorpixel, bg_color.colorpixel);
-    draw_text(text, surface->id, surface->gc, visual_type, x, y, max_width);
-
-    cairo_surface_mark_dirty(surface->surface);
-}
-
-/**
- * Draws a filled rectangle.
- * This function is a convenience wrapper and takes care of flushing the
- * surface as well as restoring the cairo state.
- * Note that the drawing is done using CAIRO_OPERATOR_SOURCE.
- *
- */
-void cairo_draw_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h) {
-    cairo_save(surface->cr);
-
-    /* Using the SOURCE operator will copy both color and alpha information directly
-     * onto the surface rather than blending it. This is a bit more efficient and
-     * allows better color control for the user when using opacity. */
-    cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE);
-    cairo_set_source_color(surface, color);
-
-    cairo_rectangle(surface->cr, x, y, w, h);
-    cairo_fill(surface->cr);
-
-    /* Make sure we flush the surface for any text drawing operations that could follow.
-     * Since we support drawing text via XCB, we need this. */
-    cairo_surface_flush(surface->surface);
-
-    cairo_restore(surface->cr);
-}
-
-/**
- * Copies a surface onto another surface.
- * Note that the drawing is done using CAIRO_OPERATOR_SOURCE.
- *
- */
-void cairo_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y,
-                        double dest_x, double dest_y, double dest_w, double dest_h) {
-    cairo_save(dest->cr);
-
-    /* Using the SOURCE operator will copy both color and alpha information directly
-     * onto the surface rather than blending it. This is a bit more efficient and
-     * allows better color control for the user when using opacity. */
-    cairo_set_operator(dest->cr, CAIRO_OPERATOR_SOURCE);
-    cairo_set_source_surface(dest->cr, src->surface, src_x, src_y);
-
-    cairo_rectangle(dest->cr, dest_x, dest_y, dest_w, dest_h);
-    cairo_fill(dest->cr);
-
-    /* Make sure we flush the surface for any text drawing operations that could follow.
-     * Since we support drawing text via XCB, we need this. */
-    cairo_surface_flush(dest->surface);
-    cairo_restore(dest->cr);
-}
index cfc96d5f1ad2d4841f39ceb39480e235fb93cb26..2b10be492b576281510fc20ce7bed9ebe4eedaf6 100644 (file)
@@ -206,7 +206,7 @@ static int stdin_string(void *context, const unsigned char *val, size_t len) {
         return 1;
     }
     if (strcasecmp(ctx->last_map_key, "markup") == 0) {
-        ctx->block.is_markup = (len == strlen("pango") && !strncasecmp((const char *)val, "pango", strlen("pango")));
+        ctx->block.pango_markup = (len == strlen("pango") && !strncasecmp((const char *)val, "pango", strlen("pango")));
         return 1;
     }
     if (strcasecmp(ctx->last_map_key, "align") == 0) {
@@ -220,24 +220,15 @@ static int stdin_string(void *context, const unsigned char *val, size_t len) {
         return 1;
     }
     if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
-        char *copy = (char *)smalloc(len + 1);
-        strncpy(copy, (const char *)val, len);
-        copy[len] = 0;
-        ctx->block.min_width_str = copy;
+        sasprintf(&(ctx->block.min_width_str), "%.*s", len, val);
         return 1;
     }
     if (strcasecmp(ctx->last_map_key, "name") == 0) {
-        char *copy = (char *)smalloc(len + 1);
-        strncpy(copy, (const char *)val, len);
-        copy[len] = 0;
-        ctx->block.name = copy;
+        sasprintf(&(ctx->block.name), "%.*s", len, val);
         return 1;
     }
     if (strcasecmp(ctx->last_map_key, "instance") == 0) {
-        char *copy = (char *)smalloc(len + 1);
-        strncpy(copy, (const char *)val, len);
-        copy[len] = 0;
-        ctx->block.instance = copy;
+        sasprintf(&(ctx->block.instance), "%.*s", len, val);
         return 1;
     }
 
@@ -275,15 +266,15 @@ static int stdin_end_map(void *context) {
 
     if (new_block->min_width_str) {
         i3String *text = i3string_from_utf8(new_block->min_width_str);
-        i3string_set_markup(text, new_block->is_markup);
+        i3string_set_markup(text, new_block->pango_markup);
         new_block->min_width = (uint32_t)predict_text_width(text);
         i3string_free(text);
     }
 
-    i3string_set_markup(new_block->full_text, new_block->is_markup);
+    i3string_set_markup(new_block->full_text, new_block->pango_markup);
 
     if (new_block->short_text != NULL)
-        i3string_set_markup(new_block->short_text, new_block->is_markup);
+        i3string_set_markup(new_block->short_text, new_block->pango_markup);
 
     TAILQ_INSERT_TAIL(&statusline_buffer, new_block, blocks);
     return 1;
index 0e2dd05a88d338400291f931558c7f875a47223c..f3412719b7f9a52b19e46616108f590cd3d1792b 100644 (file)
@@ -30,10 +30,7 @@ static bool parsing_bindings;
  */
 static int config_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) {
     FREE(cur_key);
-
-    cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1));
-    strncpy(cur_key, (const char *)keyVal, keyLen);
-    cur_key[keyLen] = '\0';
+    sasprintf(&(cur_key), "%.*s", keyLen, keyVal);
 
     if (strcmp(cur_key, "bindings") == 0)
         parsing_bindings = true;
diff --git a/i3bar/src/draw_util.c b/i3bar/src/draw_util.c
new file mode 100644 (file)
index 0000000..e1facf3
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * © 2015 Ingo Bürk and contributors (see also: LICENSE)
+ *
+ * draw.c: Utility for drawing.
+ *
+ */
+#include <stdlib.h>
+#include <err.h>
+#include <string.h>
+#include <xcb/xcb.h>
+#include <xcb/xcb_aux.h>
+#ifdef I3BAR_CAIRO
+#include <cairo/cairo-xcb.h>
+#endif
+
+#include "common.h"
+#include "libi3.h"
+
+xcb_connection_t *xcb_connection;
+xcb_visualtype_t *visual_type;
+
+/* Forward declarations */
+static void draw_util_set_source_color(surface_t *surface, color_t color);
+
+/*
+ * Initialize the surface to represent the given drawable.
+ *
+ */
+void draw_util_surface_init(surface_t *surface, xcb_drawable_t drawable, int width, int height) {
+    surface->id = drawable;
+    surface->width = width;
+    surface->height = height;
+
+    surface->gc = xcb_generate_id(xcb_connection);
+    xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(xcb_connection, surface->gc, surface->id, 0, NULL);
+    if (xcb_request_failed(gc_cookie, "Could not create graphical context"))
+        exit(EXIT_FAILURE);
+
+#ifdef I3BAR_CAIRO
+    surface->surface = cairo_xcb_surface_create(xcb_connection, surface->id, visual_type, width, height);
+    surface->cr = cairo_create(surface->surface);
+#endif
+}
+
+/*
+ * Destroys the surface.
+ *
+ */
+void draw_util_surface_free(surface_t *surface) {
+    xcb_free_gc(xcb_connection, surface->gc);
+#ifdef I3BAR_CAIRO
+    cairo_surface_destroy(surface->surface);
+    cairo_destroy(surface->cr);
+#endif
+}
+
+/*
+ * Parses the given color in hex format to an internal color representation.
+ * Note that the input must begin with a hash sign, e.g., "#3fbc59".
+ *
+ */
+color_t draw_util_hex_to_color(const char *color) {
+    char groups[3][3] = {
+        {color[1], color[2], '\0'},
+        {color[3], color[4], '\0'},
+        {color[5], color[6], '\0'}};
+
+    return (color_t){
+        .red = strtol(groups[0], NULL, 16) / 255.0,
+        .green = strtol(groups[1], NULL, 16) / 255.0,
+        .blue = strtol(groups[2], NULL, 16) / 255.0,
+        .colorpixel = get_colorpixel(color)};
+}
+
+/*
+ * Set the given color as the source color on the surface.
+ *
+ */
+static void draw_util_set_source_color(surface_t *surface, color_t color) {
+#ifdef I3BAR_CAIRO
+    cairo_set_source_rgb(surface->cr, color.red, color.green, color.blue);
+#else
+    uint32_t colorpixel = color.colorpixel;
+    xcb_change_gc(xcb_connection, surface->gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND,
+                  (uint32_t[]){colorpixel, colorpixel});
+#endif
+}
+
+/**
+ * Draw the given text using libi3.
+ * This function also marks the surface dirty which is needed if other means of
+ * drawing are used. This will be the case when using XCB to draw text.
+ *
+ */
+void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width) {
+#ifdef I3BAR_CAIRO
+    /* Flush any changes before we draw the text as this might use XCB directly. */
+    CAIRO_SURFACE_FLUSH(surface->surface);
+#endif
+
+    set_font_colors(surface->gc, fg_color.colorpixel, bg_color.colorpixel);
+    draw_text(text, surface->id, surface->gc, visual_type, x, y, max_width);
+
+#ifdef I3BAR_CAIRO
+    /* Notify cairo that we (possibly) used another way to draw on the surface. */
+    cairo_surface_mark_dirty(surface->surface);
+#endif
+}
+
+/**
+ * Draws a filled rectangle.
+ * This function is a convenience wrapper and takes care of flushing the
+ * surface as well as restoring the cairo state.
+ *
+ */
+void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h) {
+#ifdef I3BAR_CAIRO
+    cairo_save(surface->cr);
+
+    /* Using the SOURCE operator will copy both color and alpha information directly
+     * onto the surface rather than blending it. This is a bit more efficient and
+     * allows better color control for the user when using opacity. */
+    cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE);
+    draw_util_set_source_color(surface, color);
+
+    cairo_rectangle(surface->cr, x, y, w, h);
+    cairo_fill(surface->cr);
+
+    /* Make sure we flush the surface for any text drawing operations that could follow.
+     * Since we support drawing text via XCB, we need this. */
+    CAIRO_SURFACE_FLUSH(surface->surface);
+
+    cairo_restore(surface->cr);
+#else
+    draw_util_set_source_color(surface, color);
+
+    xcb_rectangle_t rect = {x, y, w, h};
+    xcb_poly_fill_rectangle(xcb_connection, surface->id, surface->gc, 1, &rect);
+#endif
+}
+
+/**
+ * Clears a surface with the given color.
+ *
+ */
+void draw_util_clear_surface(surface_t *surface, color_t color) {
+#ifdef I3BAR_CAIRO
+    cairo_save(surface->cr);
+
+    /* Using the SOURCE operator will copy both color and alpha information directly
+     * onto the surface rather than blending it. This is a bit more efficient and
+     * allows better color control for the user when using opacity. */
+    cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE);
+    draw_util_set_source_color(surface, color);
+
+    cairo_paint(surface->cr);
+
+    /* Make sure we flush the surface for any text drawing operations that could follow.
+     * Since we support drawing text via XCB, we need this. */
+    CAIRO_SURFACE_FLUSH(surface->surface);
+
+    cairo_restore(surface->cr);
+#else
+    draw_util_set_source_color(surface, color);
+
+    xcb_rectangle_t rect = {0, 0, surface->width, surface->height};
+    xcb_poly_fill_rectangle(xcb_connection, surface->id, surface->gc, 1, &rect);
+#endif
+}
+
+/**
+ * Copies a surface onto another surface.
+ *
+ */
+void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y,
+                            double dest_x, double dest_y, double width, double height) {
+#ifdef I3BAR_CAIRO
+    cairo_save(dest->cr);
+
+    /* Using the SOURCE operator will copy both color and alpha information directly
+     * onto the surface rather than blending it. This is a bit more efficient and
+     * allows better color control for the user when using opacity. */
+    cairo_set_operator(dest->cr, CAIRO_OPERATOR_SOURCE);
+    cairo_set_source_surface(dest->cr, src->surface, dest_x - src_x, src_y);
+
+    cairo_rectangle(dest->cr, dest_x, dest_y, width, height);
+    cairo_fill(dest->cr);
+
+    /* Make sure we flush the surface for any text drawing operations that could follow.
+     * Since we support drawing text via XCB, we need this. */
+    CAIRO_SURFACE_FLUSH(src->surface);
+    CAIRO_SURFACE_FLUSH(dest->surface);
+
+    cairo_restore(dest->cr);
+#else
+    xcb_copy_area(xcb_connection, src->id, dest->id, dest->gc, (int16_t)src_x, (int16_t)src_y,
+                  (int16_t)dest_x, (int16_t)dest_y, (uint16_t)width, (uint16_t)height);
+#endif
+}
index 7f7537af7d086e6db8168df6943932fa6e5530f9..d6767786ce30bdee825d6ca46a6f0461bbd3c4c6 100644 (file)
@@ -20,6 +20,8 @@
 struct mode_json_params {
     char *json;
     char *cur_key;
+    char *name;
+    bool pango_markup;
     mode *mode;
 };
 
@@ -31,17 +33,31 @@ static int mode_string_cb(void *params_, const unsigned char *val, size_t len) {
     struct mode_json_params *params = (struct mode_json_params *)params_;
 
     if (!strcmp(params->cur_key, "change")) {
-        /* Save the name */
-        params->mode->name = i3string_from_markup_with_length((const char *)val, len);
-        /* Save its rendered width */
-        params->mode->width = predict_text_width(params->mode->name);
-
-        DLOG("Got mode change: %s\n", i3string_as_utf8(params->mode->name));
+        sasprintf(&(params->name), "%.*s", len, val);
         FREE(params->cur_key);
+        return 1;
+    }
 
+    FREE(params->cur_key);
+    return 0;
+}
+
+/*
+ * Parse a boolean.
+ *
+ */
+static int mode_boolean_cb(void *params_, int val) {
+    struct mode_json_params *params = (struct mode_json_params *)params_;
+
+    if (strcmp(params->cur_key, "pango_markup") == 0) {
+        DLOG("Setting pango_markup to %d.\n", val);
+        params->pango_markup = val;
+
+        FREE(params->cur_key);
         return 1;
     }
 
+    FREE(params->cur_key);
     return 0;
 }
 
@@ -54,10 +70,21 @@ static int mode_string_cb(void *params_, const unsigned char *val, size_t len) {
 static int mode_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) {
     struct mode_json_params *params = (struct mode_json_params *)params_;
     FREE(params->cur_key);
+    sasprintf(&(params->cur_key), "%.*s", keyLen, keyVal);
+    return 1;
+}
+
+static int mode_end_map_cb(void *params_) {
+    struct mode_json_params *params = (struct mode_json_params *)params_;
 
-    params->cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1));
-    strncpy(params->cur_key, (const char *)keyVal, keyLen);
-    params->cur_key[keyLen] = '\0';
+    /* Save the name */
+    params->mode->name = i3string_from_utf8(params->name);
+    i3string_set_markup(params->mode->name, params->pango_markup);
+    /* Save its rendered width */
+    params->mode->width = predict_text_width(params->mode->name);
+
+    DLOG("Got mode change: %s\n", i3string_as_utf8(params->mode->name));
+    FREE(params->cur_key);
 
     return 1;
 }
@@ -65,7 +92,9 @@ static int mode_map_key_cb(void *params_, const unsigned char *keyVal, size_t ke
 /* A datastructure to pass all these callbacks to yajl */
 static yajl_callbacks mode_callbacks = {
     .yajl_string = mode_string_cb,
+    .yajl_boolean = mode_boolean_cb,
     .yajl_map_key = mode_map_key_cb,
+    .yajl_end_map = mode_end_map_cb,
 };
 
 /*
index fd2b1363c827f3097eeafc6a1943ced063c5c67e..d0d175ca07ad42fb7554a883c836f64f70a104ee 100644 (file)
@@ -108,9 +108,8 @@ static int outputs_string_cb(void *params_, const unsigned char *val, size_t len
     struct outputs_json_params *params = (struct outputs_json_params *)params_;
 
     if (!strcmp(params->cur_key, "current_workspace")) {
-        char *copy = smalloc(sizeof(const unsigned char) * (len + 1));
-        strncpy(copy, (const char *)val, len);
-        copy[len] = '\0';
+        char *copy = NULL;
+        sasprintf(&copy, "%.*s", len, val);
 
         char *end;
         errno = 0;
@@ -118,7 +117,8 @@ static int outputs_string_cb(void *params_, const unsigned char *val, size_t len
         if (errno == 0 &&
             (end && *end == '\0'))
             params->outputs_walk->ws = parsed_num;
-        free(copy);
+
+        FREE(copy);
         FREE(params->cur_key);
         return 1;
     }
@@ -127,14 +127,9 @@ static int outputs_string_cb(void *params_, const unsigned char *val, size_t len
         return 0;
     }
 
-    char *name = smalloc(sizeof(const unsigned char) * (len + 1));
-    strncpy(name, (const char *)val, len);
-    name[len] = '\0';
-
-    params->outputs_walk->name = name;
+    sasprintf(&(params->outputs_walk->name), "%.*s", len, val);
 
     FREE(params->cur_key);
-
     return 1;
 }
 
@@ -228,11 +223,7 @@ static int outputs_end_map_cb(void *params_) {
 static int outputs_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) {
     struct outputs_json_params *params = (struct outputs_json_params *)params_;
     FREE(params->cur_key);
-
-    params->cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1));
-    strncpy(params->cur_key, (const char *)keyVal, keyLen);
-    params->cur_key[keyLen] = '\0';
-
+    sasprintf(&(params->cur_key), "%.*s", keyLen, keyVal);
     return 1;
 }
 
index 961a41f5da7913c4be7558204f546e1bcc91919f..77b351e8b2db163e84ab132287b18de32628fb65 100644 (file)
@@ -102,8 +102,6 @@ static int workspaces_integer_cb(void *params_, long long val) {
 static int workspaces_string_cb(void *params_, const unsigned char *val, size_t len) {
     struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
 
-    char *output_name;
-
     if (!strcmp(params->cur_key, "name")) {
         const char *ws_name = (const char *)val;
         params->workspaces_walk->canonical_name = sstrndup(ws_name, len);
@@ -147,11 +145,11 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
 
     if (!strcmp(params->cur_key, "output")) {
         /* We add the ws to the TAILQ of the output, it belongs to */
-        output_name = smalloc(sizeof(const unsigned char) * (len + 1));
-        strncpy(output_name, (const char *)val, len);
-        output_name[len] = '\0';
+        char *output_name = NULL;
+        sasprintf(&output_name, "%.*s", len, val);
+
         i3_output *target = get_output_by_name(output_name);
-        if (target) {
+        if (target != NULL) {
             params->workspaces_walk->output = target;
 
             TAILQ_INSERT_TAIL(params->workspaces_walk->output->workspaces,
@@ -201,11 +199,7 @@ static int workspaces_start_map_cb(void *params_) {
 static int workspaces_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) {
     struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
     FREE(params->cur_key);
-
-    params->cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1));
-    strncpy(params->cur_key, (const char *)keyVal, keyLen);
-    params->cur_key[keyLen] = '\0';
-
+    sasprintf(&(params->cur_key), "%.*s", keyLen, keyVal);
     return 1;
 }
 
index ac9b50f98d6e28a9b9124b750cc5c21357966dc5..63b9863af83aa2114197834d61d9aee84f4eac1e 100644 (file)
@@ -30,7 +30,6 @@
 #include <X11/Xlib.h>
 #include <X11/XKBlib.h>
 #include <X11/extensions/XKB.h>
-#include <cairo/cairo-xcb.h>
 
 #include "common.h"
 #include "libi3.h"
@@ -174,16 +173,16 @@ static void draw_separator(uint32_t x, struct status_block *block) {
     uint32_t center_x = x - sep_offset;
     if (config.separator_symbol == NULL) {
         /* Draw a classic one pixel, vertical separator. */
-        cairo_draw_rectangle(&statusline_surface, colors.sep_fg,
-                             center_x,
-                             logical_px(sep_voff_px),
-                             logical_px(1),
-                             bar_height - 2 * logical_px(sep_voff_px));
+        draw_util_rectangle(&statusline_surface, colors.sep_fg,
+                            center_x,
+                            logical_px(sep_voff_px),
+                            logical_px(1),
+                            bar_height - 2 * logical_px(sep_voff_px));
     } else {
         /* Draw a custom separator. */
         uint32_t separator_x = MAX(x - block->sep_block_width, center_x - separator_symbol_width / 2);
-        cairo_draw_text(config.separator_symbol, &statusline_surface, colors.sep_fg, colors.bar_bg,
-                        separator_x, logical_px(ws_voff_px), x - separator_x);
+        draw_util_text(config.separator_symbol, &statusline_surface, colors.sep_fg, colors.bar_bg,
+                       separator_x, logical_px(ws_voff_px), x - separator_x);
     }
 }
 
@@ -244,12 +243,7 @@ void refresh_statusline(bool use_short_text) {
         realloc_sl_buffer();
 
     /* Clear the statusline pixmap. */
-    cairo_save(statusline_surface.cr);
-    cairo_set_source_color(&statusline_surface, colors.bar_bg);
-    cairo_set_operator(statusline_surface.cr, CAIRO_OPERATOR_SOURCE);
-    cairo_paint(statusline_surface.cr);
-    cairo_surface_flush(statusline_surface.surface);
-    cairo_restore(statusline_surface.cr);
+    draw_util_clear_surface(&statusline_surface, colors.bar_bg);
 
     /* Draw the text of each block. */
     uint32_t x = 0;
@@ -263,17 +257,17 @@ void refresh_statusline(bool use_short_text) {
             fg_color = colors.urgent_ws_fg;
 
             /* Draw the background */
-            cairo_draw_rectangle(&statusline_surface, colors.urgent_ws_bg,
-                                 x - logical_px(2),
-                                 logical_px(1),
-                                 block->width + logical_px(4),
-                                 bar_height - logical_px(2));
+            draw_util_rectangle(&statusline_surface, colors.urgent_ws_bg,
+                                x - logical_px(2),
+                                logical_px(1),
+                                block->width + logical_px(4),
+                                bar_height - logical_px(2));
         } else {
-            fg_color = (block->color ? cairo_hex_to_color(block->color) : colors.bar_fg);
+            fg_color = (block->color ? draw_util_hex_to_color(block->color) : colors.bar_fg);
         }
 
-        cairo_draw_text(block->full_text, &statusline_surface, fg_color, colors.bar_bg,
-                        x + block->x_offset, logical_px(ws_voff_px), block->width);
+        draw_util_text(block->full_text, &statusline_surface, fg_color, colors.bar_bg,
+                       x + block->x_offset, logical_px(ws_voff_px), block->width);
         x += block->width + block->sep_block_width + block->x_offset + block->x_append;
 
         /* If this is not the last block, draw a separator. */
@@ -351,9 +345,9 @@ void unhide_bars(void) {
  *
  */
 void init_colors(const struct xcb_color_strings_t *new_colors) {
-#define PARSE_COLOR(name, def)                                                       \
-    do {                                                                             \
-        colors.name = cairo_hex_to_color(new_colors->name ? new_colors->name : def); \
+#define PARSE_COLOR(name, def)                                                           \
+    do {                                                                                 \
+        colors.name = draw_util_hex_to_color(new_colors->name ? new_colors->name : def); \
     } while (0)
     PARSE_COLOR(bar_fg, "#FFFFFF");
     PARSE_COLOR(bar_bg, "#000000");
@@ -372,9 +366,9 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
     PARSE_COLOR(focus_ws_border, "#4c7899");
 #undef PARSE_COLOR
 
-#define PARSE_COLOR_FALLBACK(name, fallback)                                                     \
-    do {                                                                                         \
-        colors.name = new_colors->name ? cairo_hex_to_color(new_colors->name) : colors.fallback; \
+#define PARSE_COLOR_FALLBACK(name, fallback)                                                         \
+    do {                                                                                             \
+        colors.name = new_colors->name ? draw_util_hex_to_color(new_colors->name) : colors.fallback; \
     } while (0)
 
     /* For the binding mode indicator colors, we don't hardcode a default.
@@ -993,6 +987,13 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
     }
 
     while ((event = xcb_poll_for_event(xcb_connection)) != NULL) {
+        if (event->response_type == 0) {
+            xcb_generic_error_t *error = (xcb_generic_error_t *)event;
+            DLOG("Received X11 error, sequence 0x%x, error_code = %d\n", error->sequence, error->error_code);
+            free(event);
+            continue;
+        }
+
         int type = (event->response_type & ~0x80);
 
         if (type == xkb_base && xkb_base > -1) {
@@ -1133,7 +1134,7 @@ char *init_xcb_early() {
                                                                xcb_root,
                                                                root_screen->width_in_pixels,
                                                                root_screen->height_in_pixels);
-    cairo_surface_init(&statusline_surface, statusline_id, root_screen->width_in_pixels, root_screen->height_in_pixels);
+    draw_util_surface_init(&statusline_surface, statusline_id, root_screen->width_in_pixels, root_screen->height_in_pixels);
 
     /* The various watchers to communicate with xcb */
     xcb_io = smalloc(sizeof(ev_io));
@@ -1495,7 +1496,7 @@ void realloc_sl_buffer(void) {
     DLOG("Re-allocating statusline buffer, statusline_width = %d, root_screen->width_in_pixels = %d\n",
          statusline_width, root_screen->width_in_pixels);
     xcb_free_pixmap(xcb_connection, statusline_surface.id);
-    cairo_surface_free(&statusline_surface);
+    draw_util_surface_free(&statusline_surface);
 
     xcb_pixmap_t statusline_id = xcb_generate_id(xcb_connection);
     xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection,
@@ -1504,7 +1505,7 @@ void realloc_sl_buffer(void) {
                                                                xcb_root,
                                                                MAX(root_screen->width_in_pixels, statusline_width),
                                                                bar_height);
-    cairo_surface_init(&statusline_surface, statusline_id, root_screen->width_in_pixels, root_screen->height_in_pixels);
+    draw_util_surface_init(&statusline_surface, statusline_id, root_screen->width_in_pixels, root_screen->height_in_pixels);
 
     if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline buffer"))
         exit(EXIT_FAILURE);
@@ -1655,8 +1656,8 @@ void reconfig_windows(bool redraw_bars) {
                                                                 1,
                                                                 (unsigned char *)&atoms[_NET_WM_WINDOW_TYPE_DOCK]);
 
-            cairo_surface_init(&walk->bar, bar_id, walk->rect.w, bar_height);
-            cairo_surface_init(&walk->buffer, buffer_id, walk->rect.w, bar_height);
+            draw_util_surface_init(&walk->bar, bar_id, walk->rect.w, bar_height);
+            draw_util_surface_init(&walk->buffer, buffer_id, walk->rect.w, bar_height);
 
             xcb_void_cookie_t strut_cookie = config_strut_partial(walk);
 
@@ -1736,10 +1737,10 @@ void reconfig_windows(bool redraw_bars) {
                                                                     walk->rect.w,
                                                                     bar_height);
 
-            cairo_surface_free(&(walk->bar));
-            cairo_surface_free(&(walk->buffer));
-            cairo_surface_init(&(walk->bar), walk->bar.id, walk->rect.w, bar_height);
-            cairo_surface_init(&(walk->buffer), walk->buffer.id, walk->rect.w, bar_height);
+            draw_util_surface_free(&(walk->bar));
+            draw_util_surface_free(&(walk->buffer));
+            draw_util_surface_init(&(walk->bar), walk->bar.id, walk->rect.w, bar_height);
+            draw_util_surface_init(&(walk->buffer), walk->buffer.id, walk->rect.w, bar_height);
 
             xcb_void_cookie_t map_cookie, umap_cookie;
             if (redraw_bars) {
@@ -1797,12 +1798,7 @@ void draw_bars(bool unhide) {
         }
 
         /* First things first: clear the backbuffer */
-        cairo_save(outputs_walk->buffer.cr);
-        cairo_set_source_color(&(outputs_walk->buffer), colors.bar_bg);
-        cairo_set_operator(outputs_walk->buffer.cr, CAIRO_OPERATOR_SOURCE);
-        cairo_paint(outputs_walk->buffer.cr);
-        cairo_surface_flush(outputs_walk->buffer.surface);
-        cairo_restore(outputs_walk->buffer.cr);
+        draw_util_clear_surface(&(outputs_walk->buffer), colors.bar_bg);
 
         if (!config.disable_ws) {
             i3_ws *ws_walk;
@@ -1832,23 +1828,23 @@ void draw_bars(bool unhide) {
                 }
 
                 /* Draw the border of the button. */
-                cairo_draw_rectangle(&(outputs_walk->buffer), border_color,
-                                     workspace_width,
-                                     logical_px(1),
-                                     ws_walk->name_width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
-                                     font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1));
+                draw_util_rectangle(&(outputs_walk->buffer), border_color,
+                                    workspace_width,
+                                    logical_px(1),
+                                    ws_walk->name_width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
+                                    font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1));
 
                 /* Draw the inside of the button. */
-                cairo_draw_rectangle(&(outputs_walk->buffer), bg_color,
-                                     workspace_width + logical_px(1),
-                                     2 * logical_px(1),
-                                     ws_walk->name_width + 2 * logical_px(ws_hoff_px),
-                                     font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1));
+                draw_util_rectangle(&(outputs_walk->buffer), bg_color,
+                                    workspace_width + logical_px(1),
+                                    2 * logical_px(1),
+                                    ws_walk->name_width + 2 * logical_px(ws_hoff_px),
+                                    font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1));
 
-                cairo_draw_text(ws_walk->name, &(outputs_walk->buffer), fg_color, bg_color,
-                                workspace_width + logical_px(ws_hoff_px) + logical_px(1),
-                                logical_px(ws_voff_px),
-                                ws_walk->name_width);
+                draw_util_text(ws_walk->name, &(outputs_walk->buffer), fg_color, bg_color,
+                               workspace_width + logical_px(ws_hoff_px) + logical_px(1),
+                               logical_px(ws_voff_px),
+                               ws_walk->name_width);
 
                 workspace_width += 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + ws_walk->name_width;
                 if (TAILQ_NEXT(ws_walk, tailq) != NULL)
@@ -1862,22 +1858,22 @@ void draw_bars(bool unhide) {
             color_t fg_color = colors.binding_mode_fg;
             color_t bg_color = colors.binding_mode_bg;
 
-            cairo_draw_rectangle(&(outputs_walk->buffer), colors.binding_mode_border,
-                                 workspace_width,
-                                 logical_px(1),
-                                 binding.width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
-                                 font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1));
+            draw_util_rectangle(&(outputs_walk->buffer), colors.binding_mode_border,
+                                workspace_width,
+                                logical_px(1),
+                                binding.width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
+                                font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1));
 
-            cairo_draw_rectangle(&(outputs_walk->buffer), bg_color,
-                                 workspace_width + logical_px(1),
-                                 2 * logical_px(1),
-                                 binding.width + 2 * logical_px(ws_hoff_px),
-                                 font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1));
+            draw_util_rectangle(&(outputs_walk->buffer), bg_color,
+                                workspace_width + logical_px(1),
+                                2 * logical_px(1),
+                                binding.width + 2 * logical_px(ws_hoff_px),
+                                font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1));
 
-            cairo_draw_text(binding.name, &(outputs_walk->buffer), fg_color, bg_color,
-                            workspace_width + logical_px(ws_hoff_px) + logical_px(1),
-                            logical_px(ws_voff_px),
-                            binding.width);
+            draw_util_text(binding.name, &(outputs_walk->buffer), fg_color, bg_color,
+                           workspace_width + logical_px(ws_hoff_px) + logical_px(1),
+                           logical_px(ws_voff_px),
+                           binding.width);
 
             unhide = true;
             workspace_width += 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + binding.width;
@@ -1907,8 +1903,8 @@ void draw_bars(bool unhide) {
             int x_src = (int16_t)(statusline_width - visible_statusline_width);
             int x_dest = (int16_t)(outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width);
 
-            cairo_copy_surface(&statusline_surface, &(outputs_walk->buffer), x_dest - x_src, 0,
-                               x_dest, 0, (int16_t)visible_statusline_width, (int16_t)bar_height);
+            draw_util_copy_surface(&statusline_surface, &(outputs_walk->buffer), x_src, 0,
+                                   x_dest, 0, (int16_t)visible_statusline_width, (int16_t)bar_height);
         }
 
         workspace_width = 0;
@@ -1937,8 +1933,8 @@ void redraw_bars(void) {
             continue;
         }
 
-        cairo_copy_surface(&(outputs_walk->buffer), &(outputs_walk->bar), 0, 0,
-                           0, 0, outputs_walk->rect.w, outputs_walk->rect.h);
+        draw_util_copy_surface(&(outputs_walk->buffer), &(outputs_walk->bar), 0, 0,
+                               0, 0, outputs_walk->rect.w, outputs_walk->rect.h);
         xcb_flush(xcb_connection);
     }
 }
index 88b4a6cc96ff347fb6b70a907779a1d081c1481d..e9e5dac922f40e5d80602d6cab756d83efc547ad 100644 (file)
@@ -25,7 +25,7 @@ const char *DEFAULT_BINDING_MODE;
  */
 Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code,
                            const char *release, const char *border, const char *whole_window,
-                           const char *command, const char *mode);
+                           const char *command, const char *mode, bool pango_markup);
 
 /**
  * Grab the bound keys (tell X to send us keypress events for those keycodes)
index fd6fe6c092d9511722054bbacc484080b6f1787f..6312d3d2dc13cf2873181427f6430de52ad2127d 100644 (file)
@@ -77,6 +77,7 @@ struct Variable {
  */
 struct Mode {
     char *name;
+    bool pango_markup;
     struct bindings_head *bindings;
 
     SLIST_ENTRY(Mode) modes;
index c0c70bb487fa4ed057163d54e8b1e76482e85a23..97b8b424840f3c80abfec8f9f5933db9b54ed9c9 100644 (file)
@@ -66,7 +66,7 @@ CFGFUN(new_window, const char *windowtype, const char *border, const long width)
 CFGFUN(workspace, const char *workspace, const char *output);
 CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command);
 
-CFGFUN(enter_mode, const char *mode);
+CFGFUN(enter_mode, const char *pango_markup, const char *mode);
 CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command);
 
 CFGFUN(bar_font, const char *font);
index c1e109ef12b63243c59a451e361a3f02ecb13eeb..75d3639bcfd8e7e1e0adf76f9b0767693eeced03 100644 (file)
@@ -243,7 +243,7 @@ bool i3string_is_markup(i3String *str);
 /**
  * Set whether the i3String should use Pango markup.
  */
-void i3string_set_markup(i3String *str, bool is_markup);
+void i3string_set_markup(i3String *str, bool pango_markup);
 
 /**
  * Escape pango markup characters in the given string.
index 9e808a8923bf0ec6c09f0bbe1031c72d3ae6c0f9..c90f8be03be1b40f14ada4bf4d0c017e582607cd 100644 (file)
@@ -103,7 +103,7 @@ static bool load_pango_font(i3Font *font, const char *desc) {
  */
 static void draw_text_pango(const char *text, size_t text_len,
                             xcb_drawable_t drawable, xcb_visualtype_t *visual, int x, int y,
-                            int max_width, bool is_markup) {
+                            int max_width, bool pango_markup) {
     /* Create the Pango layout */
     /* root_visual_type is cached in load_pango_font */
     cairo_surface_t *surface = cairo_xcb_surface_create(conn, drawable,
@@ -117,7 +117,7 @@ static void draw_text_pango(const char *text, size_t text_len,
     pango_layout_set_wrap(layout, PANGO_WRAP_CHAR);
     pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
 
-    if (is_markup)
+    if (pango_markup)
         pango_layout_set_markup(layout, text, text_len);
     else
         pango_layout_set_text(layout, text, text_len);
@@ -143,7 +143,7 @@ static void draw_text_pango(const char *text, size_t text_len,
  * Calculate the text width using Pango rendering.
  *
  */
-static int predict_text_width_pango(const char *text, size_t text_len, bool is_markup) {
+static int predict_text_width_pango(const char *text, size_t text_len, bool pango_markup) {
     /* Create a dummy Pango layout */
     /* root_visual_type is cached in load_pango_font */
     cairo_surface_t *surface = cairo_xcb_surface_create(conn, root_screen->root, root_visual_type, 1, 1);
@@ -154,7 +154,7 @@ static int predict_text_width_pango(const char *text, size_t text_len, bool is_m
     gint width;
     pango_layout_set_font_description(layout, savedFont->specific.pango_desc);
 
-    if (is_markup)
+    if (pango_markup)
         pango_layout_set_markup(layout, text, text_len);
     else
         pango_layout_set_text(layout, text, text_len);
index 7741fde064f0ade5ab81583d802c3e593f7cdcc1..328b41c092c07b23b241f8455c023bba3ddd945d 100644 (file)
@@ -24,7 +24,7 @@ struct _i3String {
     xcb_char2b_t *ucs2;
     size_t num_glyphs;
     size_t num_bytes;
-    bool is_markup;
+    bool pango_markup;
 };
 
 /*
@@ -52,7 +52,7 @@ i3String *i3string_from_markup(const char *from_markup) {
     i3String *str = i3string_from_utf8(from_markup);
 
     /* Set the markup flag */
-    str->is_markup = true;
+    str->pango_markup = true;
 
     return str;
 }
@@ -86,7 +86,7 @@ i3String *i3string_from_markup_with_length(const char *from_markup, size_t num_b
     i3String *str = i3string_from_utf8_with_length(from_markup, num_bytes);
 
     /* set the markup flag */
-    str->is_markup = true;
+    str->pango_markup = true;
 
     return str;
 }
@@ -118,7 +118,7 @@ i3String *i3string_from_ucs2(const xcb_char2b_t *from_ucs2, size_t num_glyphs) {
  */
 i3String *i3string_copy(i3String *str) {
     i3String *copy = i3string_from_utf8(i3string_as_utf8(str));
-    copy->is_markup = str->is_markup;
+    copy->pango_markup = str->pango_markup;
     return copy;
 }
 
@@ -178,14 +178,14 @@ size_t i3string_get_num_bytes(i3String *str) {
  * Whether the given i3String is in Pango markup.
  */
 bool i3string_is_markup(i3String *str) {
-    return str->is_markup;
+    return str->pango_markup;
 }
 
 /*
  * Set whether the i3String should use Pango markup.
  */
-void i3string_set_markup(i3String *str, bool is_markup) {
-    str->is_markup = is_markup;
+void i3string_set_markup(i3String *str, bool pango_markup) {
+    str->pango_markup = pango_markup;
 }
 
 /*
index 203b42eea26e57ab7379fd180d50c8a4651b6146..16302e08d9320a679c6f56f3a29e89b883532692 100644 (file)
@@ -77,6 +77,12 @@ i3 keeps your layout in a tree data structure.
 Window::
 An X11 window, like the Firefox browser window or a terminal emulator.
 
+Floating Window::
+A window which "floats" on top of other windows. This style is used by i3 to
+display X11 windows with type "dialog", such as the "Print" or "Open File"
+dialog boxes in many GUI applications. Use of floating windows can be
+fine-tuned with the for_window command (see HTML userguide).
+
 Split container::
 A split container contains multiple other split containers or windows.
 +
index b9542c8c3c9e14cee6c8fa690b43bc8905ca6e3e..2170ace32d6da3f6dc14c7acadd2e668d0f01c34 100644 (file)
@@ -326,8 +326,10 @@ state BINDCOMMAND:
 ################################################################################
 
 state MODENAME:
+  pango_markup = '--pango_markup'
+      ->
   modename = word
-      -> call cfg_enter_mode($modename); MODEBRACE
+      -> call cfg_enter_mode($pango_markup, $modename); MODEBRACE
 
 state MODEBRACE:
   end
index c0e010301c3cb373b40478b2ebe827f8c701f101..b5f3190146a78382131bf40ea15ebdb94a5b12c5 100644 (file)
@@ -27,7 +27,7 @@ const char *DEFAULT_BINDING_MODE = "default";
  * the list of modes.
  *
  */
-static struct Mode *mode_from_name(const char *name) {
+static struct Mode *mode_from_name(const char *name, bool pango_markup) {
     struct Mode *mode;
 
     /* Try to find the mode in the list of modes and return it */
@@ -39,6 +39,7 @@ static struct Mode *mode_from_name(const char *name) {
     /* If the mode was not found, create a new one */
     mode = scalloc(1, sizeof(struct Mode));
     mode->name = sstrdup(name);
+    mode->pango_markup = pango_markup;
     mode->bindings = scalloc(1, sizeof(struct bindings_head));
     TAILQ_INIT(mode->bindings);
     SLIST_INSERT_HEAD(&modes, mode, modes);
@@ -54,7 +55,7 @@ static struct Mode *mode_from_name(const char *name) {
  */
 Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code,
                            const char *release, const char *border, const char *whole_window,
-                           const char *command, const char *modename) {
+                           const char *command, const char *modename, bool pango_markup) {
     Binding *new_binding = scalloc(1, sizeof(Binding));
     DLOG("bindtype %s, modifiers %s, input code %s, release %s\n", bindtype, modifiers, input_code, release);
     new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS);
@@ -91,7 +92,7 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch
     if (group_bits_set > 1)
         ELOG("Keybinding has more than one Group specified, but your X server is always in precisely one group. The keybinding can never trigger.\n");
 
-    struct Mode *mode = mode_from_name(modename);
+    struct Mode *mode = mode_from_name(modename, pango_markup);
     TAILQ_INSERT_TAIL(mode->bindings, new_binding, bindings);
 
     return new_binding;
@@ -437,7 +438,8 @@ void switch_mode(const char *new_mode) {
         grab_all_keys(conn);
 
         char *event_msg;
-        sasprintf(&event_msg, "{\"change\":\"%s\"}", mode->name);
+        sasprintf(&event_msg, "{\"change\":\"%s\", \"pango_markup\":%s}",
+                  mode->name, (mode->pango_markup ? "true" : "false"));
 
         ipc_send_event("mode", I3_IPC_EVENT_MODE, event_msg);
         FREE(event_msg);
index cd0432fe06c31b6e2b068f416c1cebc77a30fba2..99b70db8998ceacad69b4d1ccc10818656870687 100644 (file)
@@ -108,7 +108,7 @@ CFGFUN(font, const char *font) {
 }
 
 CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) {
-    configure_binding(bindtype, modifiers, key, release, border, whole_window, command, DEFAULT_BINDING_MODE);
+    configure_binding(bindtype, modifiers, key, release, border, whole_window, command, DEFAULT_BINDING_MODE, false);
 }
 
 /*******************************************************************************
@@ -116,12 +116,13 @@ CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, co
  ******************************************************************************/
 
 static char *current_mode;
+static bool current_mode_pango_markup;
 
 CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) {
-    configure_binding(bindtype, modifiers, key, release, border, whole_window, command, current_mode);
+    configure_binding(bindtype, modifiers, key, release, border, whole_window, command, current_mode, current_mode_pango_markup);
 }
 
-CFGFUN(enter_mode, const char *modename) {
+CFGFUN(enter_mode, const char *pango_markup, const char *modename) {
     if (strcasecmp(modename, DEFAULT_BINDING_MODE) == 0) {
         ELOG("You cannot use the name %s for your mode\n", DEFAULT_BINDING_MODE);
         exit(1);
@@ -129,6 +130,7 @@ CFGFUN(enter_mode, const char *modename) {
     DLOG("\t now in mode %s\n", modename);
     FREE(current_mode);
     current_mode = sstrdup(modename);
+    current_mode_pango_markup = (pango_markup != NULL);
 }
 
 CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command) {
index e3769670fd0644684d3412cd8af2a3679f0f7f52..5cfe490ec2746ab4540e1dcc597940f991bc33fd 100644 (file)
@@ -524,13 +524,23 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     /* Send an event about window creation */
     ipc_send_window_event("new", nc);
 
+    if (set_focus && assignment_for(cwindow, A_NO_FOCUS) != NULL) {
+        /* The first window on a workspace should always be focused. We have to
+         * compare with == 1 because the container has already been inserted at
+         * this point. */
+        if (con_num_children(ws) == 1) {
+            DLOG("This is the first window on this workspace, ignoring no_focus.\n");
+        } else {
+            DLOG("no_focus was set for con = %p, not setting focus.\n", nc);
+            set_focus = false;
+        }
+    }
+
     /* Defer setting focus after the 'new' event has been sent to ensure the
      * proper window event sequence. */
     if (set_focus && !nc->window->doesnt_accept_focus && nc->mapped) {
-        if (assignment_for(cwindow, A_NO_FOCUS) == NULL) {
-            DLOG("Now setting focus.\n");
-            con_focus(nc);
-        }
+        DLOG("Now setting focus.\n");
+        con_focus(nc);
     }
 
     tree_render();
index 5898333f3d6490f9074f53b50df9511208b29c22..eba1563290f6d6e5b0aa439524cd766e10343e34 100644 (file)
@@ -342,7 +342,7 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
 i3String *window_parse_title_format(i3Window *win) {
     /* We need to ensure that we only escape the window title if pango
      * is used by the current font. */
-    const bool is_markup = font_is_pango();
+    const bool pango_markup = font_is_pango();
 
     char *format = win->title_format;
     if (format == NULL)
@@ -359,19 +359,19 @@ i3String *window_parse_title_format(i3Window *win) {
     for (char *walk = format; *walk != '\0'; walk++) {
         if (STARTS_WITH(walk, "%title")) {
             if (escaped_title == NULL)
-                escaped_title = win->name == NULL ? "" : i3string_as_utf8(is_markup ? i3string_escape_markup(win->name) : win->name);
+                escaped_title = win->name == NULL ? "" : i3string_as_utf8(pango_markup ? i3string_escape_markup(win->name) : win->name);
 
             buffer_len = buffer_len - strlen("%title") + strlen(escaped_title);
             walk += strlen("%title") - 1;
         } else if (STARTS_WITH(walk, "%class")) {
             if (escaped_class == NULL)
-                escaped_class = is_markup ? g_markup_escape_text(win->class_class, -1) : win->class_class;
+                escaped_class = pango_markup ? g_markup_escape_text(win->class_class, -1) : win->class_class;
 
             buffer_len = buffer_len - strlen("%class") + strlen(escaped_class);
             walk += strlen("%class") - 1;
         } else if (STARTS_WITH(walk, "%instance")) {
             if (escaped_instance == NULL)
-                escaped_instance = is_markup ? g_markup_escape_text(win->class_instance, -1) : win->class_instance;
+                escaped_instance = pango_markup ? g_markup_escape_text(win->class_instance, -1) : win->class_instance;
 
             buffer_len = buffer_len - strlen("%instance") + strlen(escaped_instance);
             walk += strlen("%instance") - 1;
@@ -403,6 +403,6 @@ i3String *window_parse_title_format(i3Window *win) {
     *outwalk = '\0';
 
     i3String *formatted = i3string_from_utf8(buffer);
-    i3string_set_markup(formatted, is_markup);
+    i3string_set_markup(formatted, pango_markup);
     return formatted;
 }
index e7a09c70e3c32e5ebe12b53c964139420f2b5ab4..dc0a596d53780ecf8345dc929f9084211aed6d4b 100644 (file)
@@ -506,34 +506,13 @@ void workspace_show_by_name(const char *num) {
  */
 Con *workspace_next(void) {
     Con *current = con_get_workspace(focused);
-    Con *next = NULL;
+    Con *next = NULL, *first = NULL, *first_opposite = NULL;
     Con *output;
 
     if (current->num == -1) {
         /* If currently a named workspace, find next named workspace. */
-        next = TAILQ_NEXT(current, nodes);
-    } else {
-        /* If currently a numbered workspace, find next numbered workspace. */
-        TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
-            /* Skip outputs starting with __, they are internal. */
-            if (con_is_internal(output))
-                continue;
-            NODES_FOREACH(output_get_content(output)) {
-                if (child->type != CT_WORKSPACE)
-                    continue;
-                if (child->num == -1)
-                    break;
-                /* Need to check child against current and next because we are
-                 * traversing multiple lists and thus are not guaranteed the
-                 * relative order between the list of workspaces. */
-                if (current->num < child->num && (!next || child->num < next->num))
-                    next = child;
-            }
-        }
-    }
-
-    /* Find next named workspace. */
-    if (!next) {
+        if ((next = TAILQ_NEXT(current, nodes)) != NULL)
+            return next;
         bool found_current = false;
         TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
             /* Skip outputs starting with __, they are internal. */
@@ -542,18 +521,20 @@ Con *workspace_next(void) {
             NODES_FOREACH(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE)
                     continue;
+                if (!first)
+                    first = child;
+                if (!first_opposite && child->num != -1)
+                    first_opposite = child;
                 if (child == current) {
-                    found_current = 1;
-                } else if (child->num == -1 && (current->num != -1 || found_current)) {
+                    found_current = true;
+                } else if (child->num == -1 && found_current) {
                     next = child;
-                    goto workspace_next_end;
+                    return next;
                 }
             }
         }
-    }
-
-    /* Find first workspace. */
-    if (!next) {
+    } else {
+        /* If currently a numbered workspace, find next numbered workspace. */
         TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
             /* Skip outputs starting with __, they are internal. */
             if (con_is_internal(output))
@@ -561,12 +542,24 @@ Con *workspace_next(void) {
             NODES_FOREACH(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE)
                     continue;
-                if (!next || (child->num != -1 && child->num < next->num))
+                if (!first)
+                    first = child;
+                if (!first_opposite && child->num == -1)
+                    first_opposite = child;
+                if (child->num == -1)
+                    break;
+                /* Need to check child against current and next because we are
+                 * traversing multiple lists and thus are not guaranteed the
+                 * relative order between the list of workspaces. */
+                if (current->num < child->num && (!next || child->num < next->num))
                     next = child;
             }
         }
     }
-workspace_next_end:
+
+    if (!next)
+        next = first_opposite ? first_opposite : first;
+
     return next;
 }
 
@@ -576,7 +569,7 @@ workspace_next_end:
  */
 Con *workspace_prev(void) {
     Con *current = con_get_workspace(focused);
-    Con *prev = NULL;
+    Con *prev = NULL, *first_opposite = NULL, *last = NULL;
     Con *output;
 
     if (current->num == -1) {
@@ -584,6 +577,28 @@ Con *workspace_prev(void) {
         prev = TAILQ_PREV(current, nodes_head, nodes);
         if (prev && prev->num != -1)
             prev = NULL;
+        if (!prev) {
+            bool found_current = false;
+            TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
+                /* Skip outputs starting with __, they are internal. */
+                if (con_is_internal(output))
+                    continue;
+                NODES_FOREACH_REVERSE(output_get_content(output)) {
+                    if (child->type != CT_WORKSPACE)
+                        continue;
+                    if (!last)
+                        last = child;
+                    if (!first_opposite && child->num != -1)
+                        first_opposite = child;
+                    if (child == current) {
+                        found_current = true;
+                    } else if (child->num == -1 && found_current) {
+                        prev = child;
+                        goto workspace_prev_end;
+                    }
+                }
+            }
+        }
     } else {
         /* If numbered workspace, find previous numbered workspace. */
         TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
@@ -591,7 +606,13 @@ Con *workspace_prev(void) {
             if (con_is_internal(output))
                 continue;
             NODES_FOREACH_REVERSE(output_get_content(output)) {
-                if (child->type != CT_WORKSPACE || child->num == -1)
+                if (child->type != CT_WORKSPACE)
+                    continue;
+                if (!last)
+                    last = child;
+                if (!first_opposite && child->num == -1)
+                    first_opposite = child;
+                if (child->num == -1)
                     continue;
                 /* Need to check child against current and previous because we
                  * are traversing multiple lists and thus are not guaranteed
@@ -602,40 +623,8 @@ Con *workspace_prev(void) {
         }
     }
 
-    /* Find previous named workspace. */
-    if (!prev) {
-        bool found_current = false;
-        TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
-            /* Skip outputs starting with __, they are internal. */
-            if (con_is_internal(output))
-                continue;
-            NODES_FOREACH_REVERSE(output_get_content(output)) {
-                if (child->type != CT_WORKSPACE)
-                    continue;
-                if (child == current) {
-                    found_current = true;
-                } else if (child->num == -1 && (current->num != -1 || found_current)) {
-                    prev = child;
-                    goto workspace_prev_end;
-                }
-            }
-        }
-    }
-
-    /* Find last workspace. */
-    if (!prev) {
-        TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
-            /* Skip outputs starting with __, they are internal. */
-            if (con_is_internal(output))
-                continue;
-            NODES_FOREACH_REVERSE(output_get_content(output)) {
-                if (child->type != CT_WORKSPACE)
-                    continue;
-                if (!prev || child->num > prev->num)
-                    prev = child;
-            }
-        }
-    }
+    if (!prev)
+        prev = first_opposite ? first_opposite : last;
 
 workspace_prev_end:
     return prev;
@@ -675,7 +664,7 @@ Con *workspace_next_on_output(void) {
             if (child->type != CT_WORKSPACE)
                 continue;
             if (child == current) {
-                found_current = 1;
+                found_current = true;
             } else if (child->num == -1 && (current->num != -1 || found_current)) {
                 next = child;
                 goto workspace_next_on_output_end;
index a2b0a3a937dab2ff2701dd73f5724146c4c4fd53..7b3a181e2ed0439fe5ee2b1519662dd610ba53b5 100644 (file)
@@ -53,7 +53,7 @@ mode "meh" {
 EOT
 
 my $expected = <<'EOT';
-cfg_enter_mode(meh)
+cfg_enter_mode((null), meh)
 cfg_mode_binding(bindsym, Mod1,Shift, x, (null), (null), (null), resize grow)
 cfg_mode_binding(bindcode, Mod1, 44, (null), (null), (null), resize shrink)
 cfg_mode_binding(bindsym, Mod1, x, --release, (null), (null), exec foo)
@@ -627,7 +627,7 @@ mode "yo" {
 EOT
 
 $expected = <<'EOT';
-cfg_enter_mode(yo)
+cfg_enter_mode((null), yo)
 cfg_mode_binding(bindsym, (null), x, (null), (null), (null), resize shrink left)
 ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', '}'
 ERROR: CONFIG: (in file <stdin>)
index 143ae5cf0bac2819c7bdaeac9866c26e909f09bf..0a7f5c93fc340e1d5267e565373c745fe9ee77ff 100644 (file)
@@ -30,13 +30,15 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 EOT
 
 $pid = launch_with_config($config);
+
 $ws = fresh_workspace;
 $first = open_window;
 $focused = get_focused($ws);
 $second = open_window;
 
+sync_with_i3;
 isnt(get_focused($ws), $focused, 'focus has changed');
+is($x->input_focus, $second->id, 'input focus has changed');
 
 exit_gracefully($pid);
 
@@ -53,13 +55,37 @@ no_focus [instance=notme]
 EOT
 
 $pid = launch_with_config($config);
+
 $ws = fresh_workspace;
 $first = open_window;
 $focused = get_focused($ws);
 $second = open_window(wm_class => 'notme');
 
+sync_with_i3;
 is(get_focused($ws), $focused, 'focus has not changed');
+is($x->input_focus, $first->id, 'input focus has not changed');
+
+exit_gracefully($pid);
+
+#####################################################################
+## 3: no_focus doesn't affect the first window opened on a workspace
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+no_focus [instance=focusme]
+EOT
+
+$pid = launch_with_config($config);
+
+$ws = fresh_workspace;
+$focused = get_focused($ws);
+$first = open_window(wm_class => 'focusme');
+
+sync_with_i3;
+is($x->input_focus, $first->id, 'input focus has changed');
 
 exit_gracefully($pid);
 
diff --git a/testcases/t/528-workspace-next-prev.t b/testcases/t/528-workspace-next-prev.t
new file mode 100644 (file)
index 0000000..79e83a0
--- /dev/null
@@ -0,0 +1,127 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Tests whether 'workspace next' works correctly.
+#
+use List::Util qw(first);
+use i3test i3_autostart => 0;
+
+sub assert_next {
+    my ($expected) = @_;
+
+    cmd 'workspace next';
+    # We need to sync after changing focus to a different output to wait for the
+    # EnterNotify to be processed, otherwise it will be processed at some point
+    # later in time and mess up our subsequent tests.
+    sync_with_i3;
+
+    is(focused_ws, $expected, "workspace $expected focused");
+}
+
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0
+EOT
+my $pid = launch_with_config($config);
+
+################################################################################
+# Setup workspaces so that they stay open (with an empty container).
+# Have only named workspaces in the 1st output and numbered + named workspaces
+# in the 2nd output.
+################################################################################
+
+sync_with_i3;
+$x->root->warp_pointer(0, 0);
+sync_with_i3;
+
+cmd 'workspace A';
+# ensure workspace A stays open
+open_window;
+
+cmd 'workspace B';
+# ensure workspace B stays open
+open_window;
+
+cmd 'workspace D';
+# ensure workspace D stays open
+open_window;
+
+cmd 'workspace E';
+# ensure workspace E stays open
+open_window;
+
+cmd 'focus output right';
+
+cmd 'workspace 1';
+# ensure workspace 1 stays open
+open_window;
+
+cmd 'workspace 2';
+# ensure workspace 2 stays open
+open_window;
+
+cmd 'workspace 3';
+# ensure workspace 3 stays open
+open_window;
+
+cmd 'workspace 4';
+# ensure workspace 4 stays open
+open_window;
+
+cmd 'workspace 5';
+# ensure workspace 5 stays open
+open_window;
+
+cmd 'workspace C';
+# ensure workspace C stays open
+open_window;
+
+cmd 'workspace F';
+# ensure workspace F stays open
+open_window;
+
+cmd 'focus output right';
+
+################################################################################
+# Use workspace next and verify the correct order.
+################################################################################
+
+# The current order should be:
+# output 1: A, B, D, E
+# output 2: 1, 2, 3, 4, 5, C, F
+
+cmd 'workspace A';
+is(focused_ws, 'A', 'back on workspace A');
+
+assert_next('B');
+assert_next('D');
+assert_next('E');
+assert_next('C');
+assert_next('F');
+assert_next('1');
+assert_next('2');
+assert_next('3');
+assert_next('4');
+assert_next('5');
+assert_next('A');
+assert_next('B');
+
+exit_gracefully($pid);
+
+done_testing;