]> git.sur5r.net Git - i3/i3/blobdiff - src/commands.c
Close empty workspaces after cross-output move (+test) (Thanks chrysn)
[i3/i3] / src / commands.c
index 6c36c256fe8758aad00895e3d89368c7a6240967..2bbde9bff08c75560c5c57ed13a054643dfad161 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "commands.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
 
 #include "all.h"
 
+// Macros to make the YAJL API a bit easier to use.
+#define y(x, ...) yajl_gen_ ## x (cmd_output->json_gen, ##__VA_ARGS__)
+#define ystr(str) yajl_gen_string(cmd_output->json_gen, (unsigned char*)str, strlen(str))
+#define ysuccess(success) do { \
+    y(map_open); \
+    ystr("success"); \
+    y(bool, success); \
+    y(map_close); \
+} while (0)
+
 /** When the command did not include match criteria (!), we use the currently
- * focused command. Do not confuse this case with a command which included
+ * focused container. Do not confuse this case with a command which included
  * criteria but which did not match any windows. This macro has to be called in
  * every command.
  */
@@ -45,19 +57,19 @@ static Output *get_output_from_string(Output *current_output, const char *output
     Output *output;
 
     if (strcasecmp(output_str, "left") == 0) {
-        output = get_output_next(D_LEFT, current_output);
+        output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT);
         if (!output)
             output = get_output_most(D_RIGHT, current_output);
     } else if (strcasecmp(output_str, "right") == 0) {
-        output = get_output_next(D_RIGHT, current_output);
+        output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT);
         if (!output)
             output = get_output_most(D_LEFT, current_output);
     } else if (strcasecmp(output_str, "up") == 0) {
-        output = get_output_next(D_UP, current_output);
+        output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT);
         if (!output)
             output = get_output_most(D_DOWN, current_output);
     } else if (strcasecmp(output_str, "down") == 0) {
-        output = get_output_next(D_DOWN, current_output);
+        output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT);
         if (!output)
             output = get_output_most(D_UP, current_output);
     } else output = get_output_by_name(output_str);
@@ -65,6 +77,51 @@ static Output *get_output_from_string(Output *current_output, const char *output
     return output;
 }
 
+/*
+ * Checks whether we switched to a new workspace and returns false in that case,
+ * signaling that further workspace switching should be done by the calling function
+ * If not, calls workspace_back_and_forth() if workspace_auto_back_and_forth is set
+ * and return true, signaling that no further workspace switching should occur in the calling function.
+ *
+ */
+static bool maybe_back_and_forth(struct CommandResult *cmd_output, char *name) {
+    Con *ws = con_get_workspace(focused);
+
+    /* If we switched to a different workspace, do nothing */
+    if (strcmp(ws->name, name) != 0)
+        return false;
+
+    DLOG("This workspace is already focused.\n");
+    if (config.workspace_auto_back_and_forth) {
+        workspace_back_and_forth();
+        cmd_output->needs_tree_render = true;
+    }
+    return true;
+}
+
+/*
+ * Return the passed workspace unless it is the current one and auto back and
+ * forth is enabled, in which case the back_and_forth workspace is returned.
+ */
+static Con *maybe_auto_back_and_forth_workspace(Con *workspace) {
+    Con *current, *baf;
+
+    if (!config.workspace_auto_back_and_forth)
+        return workspace;
+
+    current = con_get_workspace(focused);
+
+    if (current == workspace) {
+        baf = workspace_back_and_forth_get();
+        if (baf != NULL) {
+            DLOG("Substituting workspace with back_and_forth, as it is focused.\n");
+            return baf;
+        }
+    }
+
+    return workspace;
+}
+
 // This code is commented out because we might recycle it for popping up error
 // messages on parser errors.
 #if 0
@@ -319,7 +376,7 @@ void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) {
 
 /*
  * Implementation of 'move [window|container] [to] workspace
- * next|prev|next_on_output|prev_on_output'.
+ * next|prev|next_on_output|prev_on_output|current'.
  *
  */
 void cmd_move_con_to_workspace(I3_CMD, char *which) {
@@ -327,6 +384,15 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) {
 
     DLOG("which=%s\n", which);
 
+    /* We have nothing to move:
+     *  when criteria was specified but didn't match any window or
+     *  when criteria wasn't specified and we don't have any window focused. */
+    if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
+        (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) {
+        ysuccess(false);
+        return;
+    }
+
     HANDLE_EMPTY_MATCH;
 
     /* get the workspace */
@@ -339,12 +405,46 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) {
         ws = workspace_next_on_output();
     else if (strcmp(which, "prev_on_output") == 0)
         ws = workspace_prev_on_output();
+    else if (strcmp(which, "current") == 0)
+        ws = con_get_workspace(focused);
     else {
         ELOG("BUG: called with which=%s\n", which);
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        ysuccess(false);
+        return;
+    }
+
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        DLOG("matching: %p / %s\n", current->con, current->con->name);
+        con_move_to_workspace(current->con, ws, true, false);
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    ysuccess(true);
+}
+
+/**
+ * Implementation of 'move [window|container] [to] workspace back_and_forth'.
+ *
+ */
+void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
+    owindow *current;
+    Con *ws;
+
+    ws = workspace_back_and_forth_get();
+
+    if (ws == NULL) {
+        y(map_open);
+        ystr("success");
+        y(bool, false);
+        ystr("error");
+        ystr("No workspace was previously active.");
+        y(map_close);
         return;
     }
 
+    HANDLE_EMPTY_MATCH;
+
     TAILQ_FOREACH(current, &owindows, owindows) {
         DLOG("matching: %p / %s\n", current->con, current->con->name);
         con_move_to_workspace(current->con, ws, true, false);
@@ -352,7 +452,7 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) {
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -362,16 +462,24 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) {
 void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
     if (strncasecmp(name, "__i3_", strlen("__i3_")) == 0) {
         LOG("You cannot switch to the i3 internal workspaces.\n");
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        ysuccess(false);
         return;
     }
 
     owindow *current;
 
-    /* Error out early to not create a non-existing workspace (in
-     * workspace_get()) if we are not actually able to move anything. */
+    /* We have nothing to move:
+     *  when criteria was specified but didn't match any window or
+     *  when criteria wasn't specified and we don't have any window focused. */
+    if (!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) {
+        ELOG("No windows match your criteria, cannot move.\n");
+        ysuccess(false);
+        return;
+    }
+
     if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) {
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        ELOG("No window to move, you have focused a workspace.\n");
+        ysuccess(false);
         return;
     }
 
@@ -379,6 +487,8 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
     /* get the workspace */
     Con *ws = workspace_get(name, NULL);
 
+    ws = maybe_auto_back_and_forth_workspace(ws);
+
     HANDLE_EMPTY_MATCH;
 
     TAILQ_FOREACH(current, &owindows, owindows) {
@@ -388,7 +498,66 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
+}
+
+/*
+ * Implementation of 'move [window|container] [to] workspace number <name>'.
+ *
+ */
+void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
+    owindow *current;
+
+    /* We have nothing to move:
+     *  when criteria was specified but didn't match any window or
+     *  when criteria wasn't specified and we don't have any window focused. */
+    if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
+        (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) {
+        ysuccess(false);
+        return;
+    }
+
+    LOG("should move window to workspace %s\n", which);
+    /* get the workspace */
+    Con *output, *workspace = NULL;
+
+    char *endptr = NULL;
+    long parsed_num = strtol(which, &endptr, 10);
+    if (parsed_num == LONG_MIN ||
+        parsed_num == LONG_MAX ||
+        parsed_num < 0 ||
+        endptr == which) {
+        LOG("Could not parse initial part of \"%s\" as a number.\n", which);
+        y(map_open);
+        ystr("success");
+        y(bool, false);
+        ystr("error");
+        // TODO: better error message
+        ystr("Could not parse number");
+        y(map_close);
+        return;
+    }
+
+    TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
+        GREP_FIRST(workspace, output_get_content(output),
+            child->num == parsed_num);
+
+    if (!workspace) {
+        workspace = workspace_get(which, NULL);
+    }
+
+    workspace = maybe_auto_back_and_forth_workspace(workspace);
+
+    HANDLE_EMPTY_MATCH;
+
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        DLOG("matching: %p / %s\n", current->con, current->con->name);
+        con_move_to_workspace(current->con, workspace, true, false);
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    ysuccess(true);
 }
 
 static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floating_con, int px) {
@@ -396,7 +565,7 @@ static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floatin
     if (strcmp(direction, "up") == 0) {
         floating_con->rect.y -= px;
         floating_con->rect.height += px;
-    } else if (strcmp(direction, "down") == 0) {
+    } else if (strcmp(direction, "down") == 0 || strcmp(direction, "height") == 0) {
         floating_con->rect.height += px;
     } else if (strcmp(direction, "left") == 0) {
         floating_con->rect.x -= px;
@@ -406,10 +575,12 @@ static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floatin
     }
 }
 
-static void cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int ppt) {
+static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int ppt) {
     LOG("tiling resize\n");
     /* get the appropriate current container (skip stacked/tabbed cons) */
     Con *current = focused;
+    Con *other = NULL;
+    double percentage = 0;
     while (current->parent->layout == L_STACKED ||
            current->parent->layout == L_TABBED)
         current = current->parent;
@@ -418,40 +589,50 @@ static void cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int
     orientation_t search_orientation =
         (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0 ? HORIZ : VERT);
 
-    while (current->type != CT_WORKSPACE &&
-           current->type != CT_FLOATING_CON &&
-           current->parent->orientation != search_orientation)
-        current = current->parent;
+    do {
+        if (con_orientation(current->parent) != search_orientation) {
+            current = current->parent;
+            continue;
+        }
 
-    /* get the default percentage */
-    int children = con_num_children(current->parent);
-    Con *other;
-    LOG("ins. %d children\n", children);
-    double percentage = 1.0 / children;
-    LOG("default percentage = %f\n", percentage);
+        /* get the default percentage */
+        int children = con_num_children(current->parent);
+        LOG("ins. %d children\n", children);
+        percentage = 1.0 / children;
+        LOG("default percentage = %f\n", percentage);
+
+        orientation_t orientation = con_orientation(current->parent);
+
+        if ((orientation == HORIZ &&
+             (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) ||
+            (orientation == VERT &&
+             (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0))) {
+            LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
+                (orientation == HORIZ ? "horizontal" : "vertical"));
+            ysuccess(false);
+            return false;
+        }
 
-    orientation_t orientation = current->parent->orientation;
+        if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) {
+            other = TAILQ_PREV(current, nodes_head, nodes);
+        } else {
+            other = TAILQ_NEXT(current, nodes);
+        }
+        if (other == TAILQ_END(workspaces)) {
+            LOG("No other container in this direction found, trying to look further up in the tree...\n");
+            current = current->parent;
+            continue;
+        }
+        break;
+    } while (current->type != CT_WORKSPACE &&
+             current->type != CT_FLOATING_CON);
 
-    if ((orientation == HORIZ &&
-         (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) ||
-        (orientation == VERT &&
-         (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0))) {
-        LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
-            (orientation == HORIZ ? "horizontal" : "vertical"));
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
-        return;
+    if (other == NULL) {
+        LOG("No other container in this direction found, trying to look further up in the tree...\n");
+        ysuccess(false);
+        return false;
     }
 
-    if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) {
-        other = TAILQ_PREV(current, nodes_head, nodes);
-    } else {
-        other = TAILQ_NEXT(current, nodes);
-    }
-    if (other == TAILQ_END(workspaces)) {
-        LOG("No other container in this direction found, cannot resize.\n");
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
-        return;
-    }
     LOG("other->percent = %f\n", other->percent);
     LOG("current->percent before = %f\n", current->percent);
     if (current->percent == 0.0)
@@ -473,9 +654,11 @@ static void cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int
     } else {
         LOG("Not resizing, already at minimum size\n");
     }
+
+    return true;
 }
 
-static void cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, int ppt) {
+static bool cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, int ppt) {
     LOG("width/height resize\n");
     /* get the appropriate current container (skip stacked/tabbed cons) */
     Con *current = focused;
@@ -489,7 +672,7 @@ static void cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, i
 
     while (current->type != CT_WORKSPACE &&
            current->type != CT_FLOATING_CON &&
-           current->parent->orientation != search_orientation)
+           con_orientation(current->parent) != search_orientation)
         current = current->parent;
 
     /* get the default percentage */
@@ -498,7 +681,7 @@ static void cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, i
     double percentage = 1.0 / children;
     LOG("default percentage = %f\n", percentage);
 
-    orientation_t orientation = current->parent->orientation;
+    orientation_t orientation = con_orientation(current->parent);
 
     if ((orientation == HORIZ &&
          strcmp(direction, "height") == 0) ||
@@ -506,14 +689,14 @@ static void cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, i
          strcmp(direction, "width") == 0)) {
         LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
             (orientation == HORIZ ? "horizontal" : "vertical"));
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
-        return;
+        ysuccess(false);
+        return false;
     }
 
     if (children == 1) {
         LOG("This is the only container, cannot resize.\n");
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
-        return;
+        ysuccess(false);
+        return false;
     }
 
     /* Ensure all the other children have a percentage set. */
@@ -535,14 +718,14 @@ static void cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, i
             continue;
         if (!definitelyGreaterThan(child->percent - subtract_percent, 0.05, DBL_EPSILON)) {
             LOG("Not resizing, already at minimum size (child %p would end up with a size of %.f\n", child, child->percent - subtract_percent);
-            cmd_output->json_output = sstrdup("{\"sucess\": false}");
-            return;
+            ysuccess(false);
+            return false;
         }
     }
     if (!definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON)) {
         LOG("Not resizing, already at minimum size\n");
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
-        return;
+        ysuccess(false);
+        return false;
     }
 
     current->percent += ((double)ppt / 100.0);
@@ -554,6 +737,8 @@ static void cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, i
         child->percent -= subtract_percent;
         LOG("child->percent after (%p) = %f\n", child, child->percent);
     }
+
+    return true;
 }
 
 /*
@@ -576,14 +761,18 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz
         cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, px);
     } else {
         if (strcmp(direction, "width") == 0 ||
-            strcmp(direction, "height") == 0)
-            cmd_resize_tiling_width_height(current_match, cmd_output, way, direction, ppt);
-        else cmd_resize_tiling_direction(current_match, cmd_output, way, direction, ppt);
+            strcmp(direction, "height") == 0) {
+            if (!cmd_resize_tiling_width_height(current_match, cmd_output, way, direction, ppt))
+                return;
+        } else {
+            if (!cmd_resize_tiling_direction(current_match, cmd_output, way, direction, ppt))
+                return;
+        }
     }
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -611,7 +800,7 @@ void cmd_border(I3_CMD, char *border_style_str) {
                 border_style = BS_1PIXEL;
             else {
                 ELOG("BUG: called with border_style=%s\n", border_style_str);
-                cmd_output->json_output = sstrdup("{\"sucess\": false}");
+                ysuccess(false);
                 return;
             }
         }
@@ -620,7 +809,7 @@ void cmd_border(I3_CMD, char *border_style_str) {
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -643,7 +832,7 @@ void cmd_append_layout(I3_CMD, char *path) {
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -665,7 +854,7 @@ void cmd_workspace(I3_CMD, char *which) {
         ws = workspace_prev_on_output();
     else {
         ELOG("BUG: called with which=%s\n", which);
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        ysuccess(false);
         return;
     }
 
@@ -673,25 +862,31 @@ void cmd_workspace(I3_CMD, char *which) {
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
- * Implementation of 'workspace number <number>'
+ * Implementation of 'workspace number <name>'
  *
  */
 void cmd_workspace_number(I3_CMD, char *which) {
-    Con *output, *workspace;
+    Con *output, *workspace = NULL;
 
     char *endptr = NULL;
     long parsed_num = strtol(which, &endptr, 10);
     if (parsed_num == LONG_MIN ||
         parsed_num == LONG_MAX ||
         parsed_num < 0 ||
-        *endptr != '\0') {
-        LOG("Could not parse \"%s\" as a number.\n", which);
-        cmd_output->json_output = sstrdup("{\"success\": false, "
-                "\"error\": \"Could not parse number\"}");
+        endptr == which) {
+        LOG("Could not parse initial part of \"%s\" as a number.\n", which);
+        y(map_open);
+        ystr("success");
+        y(bool, false);
+        ystr("error");
+        // TODO: better error message
+        ystr("Could not parse number");
+        y(map_close);
+
         return;
     }
 
@@ -700,17 +895,19 @@ void cmd_workspace_number(I3_CMD, char *which) {
             child->num == parsed_num);
 
     if (!workspace) {
-        LOG("There is no workspace with number %d.\n", parsed_num);
-        cmd_output->json_output = sstrdup("{\"success\": false, "
-                "\"error\": \"No such workspace\"}");
+        LOG("There is no workspace with number %ld, creating a new one.\n", parsed_num);
+        ysuccess(true);
+        workspace_show_by_name(which);
+        cmd_output->needs_tree_render = true;
         return;
     }
-
+    if (maybe_back_and_forth(cmd_output, workspace->name))
+        return;
     workspace_show(workspace);
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -722,7 +919,7 @@ void cmd_workspace_back_and_forth(I3_CMD) {
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -732,30 +929,18 @@ void cmd_workspace_back_and_forth(I3_CMD) {
 void cmd_workspace_name(I3_CMD, char *name) {
     if (strncasecmp(name, "__i3_", strlen("__i3_")) == 0) {
         LOG("You cannot switch to the i3 internal workspaces.\n");
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        ysuccess(false);
         return;
     }
 
     DLOG("should switch to workspace %s\n", name);
-
-    Con *ws = con_get_workspace(focused);
-
-    /* Check if the command wants to switch to the current workspace */
-    if (strcmp(ws->name, name) == 0) {
-        DLOG("This workspace is already focused.\n");
-        if (config.workspace_auto_back_and_forth) {
-            workspace_back_and_forth();
-            tree_render();
-        }
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
-        return;
-    }
-
+    if (maybe_back_and_forth(cmd_output, name))
+       return;
     workspace_show_by_name(name);
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -783,7 +968,7 @@ void cmd_mark(I3_CMD, char *mark) {
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -795,7 +980,7 @@ void cmd_mode(I3_CMD, char *mode) {
     switch_mode(mode);
 
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -821,19 +1006,19 @@ void cmd_move_con_to_output(I3_CMD, char *name) {
 
     // TODO: clean this up with commands.spec as soon as we switched away from the lex/yacc command parser
     if (strcasecmp(name, "up") == 0)
-        output = get_output_next(D_UP, current_output);
+        output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT);
     else if (strcasecmp(name, "down") == 0)
-        output = get_output_next(D_DOWN, current_output);
+        output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT);
     else if (strcasecmp(name, "left") == 0)
-        output = get_output_next(D_LEFT, current_output);
+        output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT);
     else if (strcasecmp(name, "right") == 0)
-        output = get_output_next(D_RIGHT, current_output);
+        output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT);
     else
         output = get_output_by_name(name);
 
     if (!output) {
         LOG("No such output found.\n");
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        ysuccess(false);
         return;
     }
 
@@ -841,7 +1026,7 @@ void cmd_move_con_to_output(I3_CMD, char *name) {
     Con *ws = NULL;
     GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
     if (!ws) {
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        ysuccess(false);
         return;
     }
 
@@ -852,7 +1037,7 @@ void cmd_move_con_to_output(I3_CMD, char *name) {
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -883,7 +1068,7 @@ void cmd_floating(I3_CMD, char *floating_mode) {
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -899,18 +1084,27 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
     TAILQ_FOREACH(current, &owindows, owindows) {
         Output *current_output = get_output_containing(current->con->rect.x,
                                                        current->con->rect.y);
+        if (!current_output) {
+            ELOG("Cannot get current output. This is a bug in i3.\n");
+            ysuccess(false);
+            return;
+        }
         Output *output = get_output_from_string(current_output, name);
         if (!output) {
-            LOG("No such output\n");
-            cmd_output->json_output = sstrdup("{\"sucess\": false}");
+            ELOG("Could not get output from string \"%s\"\n", name);
+            ysuccess(false);
             return;
         }
 
         Con *content = output_get_content(output->con);
         LOG("got output %p with content %p\n", output, content);
 
+        Con *previously_visible_ws = TAILQ_FIRST(&(content->nodes_head));
+        LOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name);
+
         Con *ws = con_get_workspace(current->con);
         LOG("should move workspace %p / %s\n", ws, ws->name);
+        bool workspace_was_visible = workspace_is_visible(ws);
 
         if (con_num_children(ws->parent) == 1) {
             LOG("Creating a new workspace to replace \"%s\" (last on its output).\n", ws->name);
@@ -947,7 +1141,6 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
         }
 
         /* detach from the old output and attach to the new output */
-        bool workspace_was_visible = workspace_is_visible(ws);
         Con *old_content = ws->parent;
         con_detach(ws);
         if (workspace_was_visible) {
@@ -969,11 +1162,16 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
             /* Focus the moved workspace on the destination output. */
             workspace_show(ws);
         }
+
+        /* Call the on_remove_child callback of the workspace which previously
+         * was visible on the destination output. Since it is no longer
+         * visible, it might need to get cleaned up. */
+        CALL(previously_visible_ws, on_remove_child);
     }
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -981,13 +1179,21 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
  *
  */
 void cmd_split(I3_CMD, char *direction) {
+    owindow *current;
     /* TODO: use matches */
     LOG("splitting in direction %c\n", direction[0]);
-    tree_split(focused, (direction[0] == 'v' ? VERT : HORIZ));
+    if (match_is_empty(current_match))
+        tree_split(focused, (direction[0] == 'v' ? VERT : HORIZ));
+    else {
+        TAILQ_FOREACH(current, &owindows, owindows) {
+            DLOG("matching: %p / %s\n", current->con, current->con->name);
+            tree_split(current->con, (direction[0] == 'v' ? VERT : HORIZ));
+        }
+    }
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -1008,7 +1214,7 @@ void cmd_kill(I3_CMD, char *kill_mode_str) {
         kill_mode = KILL_CLIENT;
     else {
         ELOG("BUG: called with kill_mode=%s\n", kill_mode_str);
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        ysuccess(false);
         return;
     }
 
@@ -1024,7 +1230,7 @@ void cmd_kill(I3_CMD, char *kill_mode_str) {
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -1038,7 +1244,7 @@ void cmd_exec(I3_CMD, char *nosn, char *command) {
     start_application(command, no_startup_id);
 
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -1050,7 +1256,7 @@ void cmd_focus_direction(I3_CMD, char *direction) {
         focused->type != CT_WORKSPACE &&
         focused->fullscreen_mode != CF_NONE) {
         LOG("Cannot change focus while in fullscreen mode.\n");
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        ysuccess(false);
         return;
     }
 
@@ -1066,13 +1272,13 @@ void cmd_focus_direction(I3_CMD, char *direction) {
         tree_next('n', VERT);
     else {
         ELOG("Invalid focus direction (%s)\n", direction);
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        ysuccess(false);
         return;
     }
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -1084,7 +1290,7 @@ void cmd_focus_window_mode(I3_CMD, char *window_mode) {
         focused->type != CT_WORKSPACE &&
         focused->fullscreen_mode != CF_NONE) {
         LOG("Cannot change focus while in fullscreen mode.\n");
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        ysuccess(false);
         return;
     }
 
@@ -1111,7 +1317,7 @@ void cmd_focus_window_mode(I3_CMD, char *window_mode) {
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -1119,23 +1325,26 @@ void cmd_focus_window_mode(I3_CMD, char *window_mode) {
  *
  */
 void cmd_focus_level(I3_CMD, char *level) {
-    if (focused &&
-        focused->type != CT_WORKSPACE &&
-        focused->fullscreen_mode != CF_NONE) {
-        LOG("Cannot change focus while in fullscreen mode.\n");
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
-        return;
-    }
-
     DLOG("level = %s\n", level);
+    bool success = false;
+
+    /* Focusing the parent can only be allowed if the newly
+     * focused container won't escape the fullscreen container. */
+    if (strcmp(level, "parent") == 0) {
+        if (focused && focused->parent) {
+            if (con_fullscreen_permits_focusing(focused->parent))
+                success = level_up();
+            else
+                ELOG("'focus parent': Currently in fullscreen, not going up\n");
+        }
+    }
 
-    if (strcmp(level, "parent") == 0)
-        level_up();
-    else level_down();
+    /* Focusing a child should always be allowed. */
+    else success = level_down();
 
-    cmd_output->needs_tree_render = true;
+    cmd_output->needs_tree_render = success;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(success);
 }
 
 /*
@@ -1144,27 +1353,23 @@ void cmd_focus_level(I3_CMD, char *level) {
  */
 void cmd_focus(I3_CMD) {
     DLOG("current_match = %p\n", current_match);
-    if (focused &&
-        focused->type != CT_WORKSPACE &&
-        focused->fullscreen_mode != CF_NONE) {
-        LOG("Cannot change focus while in fullscreen mode.\n");
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
-        return;
-    }
-
-    owindow *current;
 
     if (match_is_empty(current_match)) {
         ELOG("You have to specify which window/container should be focused.\n");
         ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
 
-        sasprintf(&(cmd_output->json_output),
-                  "{\"success\":false, \"error\":\"You have to "
-                  "specify which window/container should be focused\"}");
+        y(map_open);
+        ystr("success");
+        y(bool, false);
+        ystr("error");
+        ystr("You have to specify which window/container should be focused");
+        y(map_close);
+
         return;
     }
 
     int count = 0;
+    owindow *current;
     TAILQ_FOREACH(current, &owindows, owindows) {
         Con *ws = con_get_workspace(current->con);
         /* If no workspace could be found, this was a dock window.
@@ -1172,6 +1377,13 @@ void cmd_focus(I3_CMD) {
         if (!ws)
             continue;
 
+        /* Check the fullscreen focus constraints. */
+        if (!con_fullscreen_permits_focusing(current->con)) {
+            LOG("Cannot change focus while in fullscreen mode (fullscreen rules).\n");
+            ysuccess(false);
+            return;
+        }
+
         /* If the container is not on the current workspace,
          * workspace_show() will switch to a different workspace and (if
          * enabled) trigger a mouse pointer warp to the currently focused
@@ -1201,7 +1413,7 @@ void cmd_focus(I3_CMD) {
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -1223,7 +1435,7 @@ void cmd_fullscreen(I3_CMD, char *fullscreen_mode) {
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -1258,25 +1470,39 @@ void cmd_move_direction(I3_CMD, char *direction, char *move_px) {
     }
 
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
- * Implementation of 'layout default|stacked|stacking|tabbed'.
+ * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'.
  *
  */
 void cmd_layout(I3_CMD, char *layout_str) {
     if (strcmp(layout_str, "stacking") == 0)
         layout_str = "stacked";
-    DLOG("changing layout to %s\n", layout_str);
     owindow *current;
-    int layout = (strcmp(layout_str, "default") == 0 ? L_DEFAULT :
-                  (strcmp(layout_str, "stacked") == 0 ? L_STACKED :
-                   L_TABBED));
+    int layout;
+    /* default is a special case which will be handled in con_set_layout(). */
+    if (strcmp(layout_str, "default") == 0)
+        layout = L_DEFAULT;
+    else if (strcmp(layout_str, "stacked") == 0)
+        layout = L_STACKED;
+    else if (strcmp(layout_str, "tabbed") == 0)
+        layout = L_TABBED;
+    else if (strcmp(layout_str, "splitv") == 0)
+        layout = L_SPLITV;
+    else if (strcmp(layout_str, "splith") == 0)
+        layout = L_SPLITH;
+    else {
+        ELOG("Unknown layout \"%s\", this is a mismatch between code and parser spec.\n", layout_str);
+        return;
+    }
+
+    DLOG("changing layout to %s (%d)\n", layout_str, layout);
 
     /* check if the match is empty, not if the result is empty */
     if (match_is_empty(current_match))
-        con_set_layout(focused->parent, layout);
+        con_set_layout(focused, layout);
     else {
         TAILQ_FOREACH(current, &owindows, owindows) {
             DLOG("matching: %p / %s\n", current->con, current->con->name);
@@ -1286,7 +1512,34 @@ void cmd_layout(I3_CMD, char *layout_str) {
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
+}
+
+/*
+ * Implementation of 'layout toggle [all|split]'.
+ *
+ */
+void cmd_layout_toggle(I3_CMD, char *toggle_mode) {
+    owindow *current;
+
+    if (toggle_mode == NULL)
+        toggle_mode = "default";
+
+    DLOG("toggling layout (mode = %s)\n", toggle_mode);
+
+    /* check if the match is empty, not if the result is empty */
+    if (match_is_empty(current_match))
+        con_toggle_layout(focused, toggle_mode);
+    else {
+        TAILQ_FOREACH(current, &owindows, owindows) {
+            DLOG("matching: %p / %s\n", current->con, current->con->name);
+            con_toggle_layout(current->con, toggle_mode);
+        }
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    ysuccess(true);
 }
 
 /*
@@ -1295,6 +1548,7 @@ void cmd_layout(I3_CMD, char *layout_str) {
  */
 void cmd_exit(I3_CMD) {
     LOG("Exiting due to user command.\n");
+    xcb_disconnect(conn);
     exit(0);
 
     /* unreached */
@@ -1307,13 +1561,14 @@ void cmd_exit(I3_CMD) {
 void cmd_reload(I3_CMD) {
     LOG("reloading\n");
     kill_configerror_nagbar(false);
+    kill_commanderror_nagbar(false);
     load_configuration(conn, NULL, true);
     x_set_i3_atoms();
     /* Send an IPC event just in case the ws names have changed */
     ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}");
 
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -1325,7 +1580,7 @@ void cmd_restart(I3_CMD) {
     i3_restart(false);
 
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -1335,9 +1590,15 @@ void cmd_restart(I3_CMD) {
 void cmd_open(I3_CMD) {
     LOG("opening new container\n");
     Con *con = tree_open_con(NULL, NULL);
+    con->layout = L_SPLITH;
     con_focus(con);
-    sasprintf(&(cmd_output->json_output),
-              "{\"success\":true, \"id\":%ld}", (long int)con);
+
+    y(map_open);
+    ystr("success");
+    y(bool, true);
+    ystr("id");
+    y(integer, (long int)con);
+    y(map_close);
 
     cmd_output->needs_tree_render = true;
 }
@@ -1365,7 +1626,7 @@ void cmd_focus_output(I3_CMD, char *name) {
 
     if (!output) {
         LOG("No such output found.\n");
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        ysuccess(false);
         return;
     }
 
@@ -1373,7 +1634,7 @@ void cmd_focus_output(I3_CMD, char *name) {
     Con *ws = NULL;
     GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
     if (!ws) {
-        cmd_output->json_output = sstrdup("{\"sucess\": false}");
+        ysuccess(false);
         return;
     }
 
@@ -1381,7 +1642,7 @@ void cmd_focus_output(I3_CMD, char *name) {
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -1395,9 +1656,12 @@ void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) {
 
     if (!con_is_floating(focused)) {
         ELOG("Cannot change position. The window/container is not floating\n");
-        sasprintf(&(cmd_output->json_output),
-                  "{\"success\":false, \"error\":\"Cannot change position. "
-                  "The window/container is not floating.\"}");
+        y(map_open);
+        ystr("success");
+        y(bool, false);
+        ystr("error");
+        ystr("Cannot change position. The window/container is not floating.");
+        y(map_close);
         return;
     }
 
@@ -1421,7 +1685,7 @@ void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) {
     }
 
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -1432,9 +1696,12 @@ void cmd_move_window_to_center(I3_CMD, char *method) {
 
     if (!con_is_floating(focused)) {
         ELOG("Cannot change position. The window/container is not floating\n");
-        sasprintf(&(cmd_output->json_output),
-                  "{\"success\":false, \"error\":\"Cannot change position. "
-                  "The window/container is not floating.\"}");
+        y(map_open);
+        ystr("success");
+        y(bool, false);
+        ystr("error");
+        ystr("Cannot change position. The window/container is not floating.");
+        y(map_close);
     }
 
     if (strcmp(method, "absolute") == 0) {
@@ -1460,7 +1727,7 @@ void cmd_move_window_to_center(I3_CMD, char *method) {
     }
 
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -1480,7 +1747,7 @@ void cmd_move_scratchpad(I3_CMD) {
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
 }
 
 /*
@@ -1502,5 +1769,75 @@ void cmd_scratchpad_show(I3_CMD) {
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
-    cmd_output->json_output = sstrdup("{\"success\": true}");
+    ysuccess(true);
+}
+
+/*
+ * Implementation of 'rename workspace <name> to <name>'
+ *
+ */
+void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
+    LOG("Renaming workspace \"%s\" to \"%s\"\n", old_name, new_name);
+
+    Con *output, *workspace = NULL;
+    TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
+        GREP_FIRST(workspace, output_get_content(output),
+            !strcasecmp(child->name, old_name));
+
+    if (!workspace) {
+        // TODO: we should include the old workspace name here and use yajl for
+        // generating the reply.
+        y(map_open);
+        ystr("success");
+        y(bool, false);
+        ystr("error");
+        // TODO: better error message
+        ystr("Old workspace not found");
+        y(map_close);
+        return;
+    }
+
+    Con *check_dest = NULL;
+    TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
+        GREP_FIRST(check_dest, output_get_content(output),
+            !strcasecmp(child->name, new_name));
+
+    if (check_dest != NULL) {
+        // TODO: we should include the new workspace name here and use yajl for
+        // generating the reply.
+        y(map_open);
+        ystr("success");
+        y(bool, false);
+        ystr("error");
+        // TODO: better error message
+        ystr("New workspace already exists");
+        y(map_close);
+        return;
+    }
+
+    /* Change the name and try to parse it as a number. */
+    FREE(workspace->name);
+    workspace->name = sstrdup(new_name);
+    char *endptr = NULL;
+    long parsed_num = strtol(new_name, &endptr, 10);
+    if (parsed_num == LONG_MIN ||
+        parsed_num == LONG_MAX ||
+        parsed_num < 0 ||
+        endptr == new_name)
+        workspace->num = -1;
+    else workspace->num = parsed_num;
+    LOG("num = %d\n", workspace->num);
+
+    /* By re-attaching, the sort order will be correct afterwards. */
+    Con *previously_focused = focused;
+    Con *parent = workspace->parent;
+    con_detach(workspace);
+    con_attach(workspace, parent, false);
+    /* Restore the previous focus since con_attach messes with the focus. */
+    con_focus(previously_focused);
+
+    cmd_output->needs_tree_render = true;
+    ysuccess(true);
+
+    ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"rename\"}");
 }