X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fcommands.c;h=9830478f7eeaa94ad97d761cce0727b65bd3e95f;hb=4622cde7b785b522b29d66c7d9bdcc9aa1eeb452;hp=4071097f18bb1e849612a3da91ede95bfc52b1dd;hpb=fc27c19761b47fa449514395f7ea2a21654f515d;p=i3%2Fi3 diff --git a/src/commands.c b/src/commands.c index 4071097f..9830478f 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "commands.c" /* * vim:ts=4:sw=4:expandtab * @@ -11,10 +13,19 @@ #include #include "all.h" -#include "cmdparse.tab.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. */ @@ -37,6 +48,11 @@ static bool definitelyGreaterThan(float a, float b, float epsilon) { return (a - b) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon); } +/* + * Returns an 'output' corresponding to one of left/right/down/up or a specific + * output name. + * + */ static Output *get_output_from_string(Output *current_output, const char *output_str) { Output *output; @@ -61,113 +77,31 @@ static Output *get_output_from_string(Output *current_output, const char *output return output; } -/******************************************************************************* - * Helper functions for the migration testing. We let the new parser call every - * function here and save the stack (current_match plus all parameters. Then we - * let the old parser call every function and actually execute the code. When - * there are differences between the first and the second invocation (or if - * there has not been a first invocation at all), we generate an error. - ******************************************************************************/ - -static bool migration_test = false; -typedef struct stackframe { - Match match; - int n_args; - char *args[10]; - TAILQ_ENTRY(stackframe) stackframes; -} stackframe; -static TAILQ_HEAD(stackframes_head, stackframe) old_stackframes = - TAILQ_HEAD_INITIALIZER(old_stackframes); -static struct stackframes_head new_stackframes = - TAILQ_HEAD_INITIALIZER(new_stackframes); -/* We use this char* to uniquely terminate the list of parameters to save. */ -static char *last_parameter = "0"; - -void cmd_MIGRATION_enable() { - migration_test = true; - /* clear the current stack */ - while (!TAILQ_EMPTY(&old_stackframes)) { - stackframe *current = TAILQ_FIRST(&old_stackframes); - for (int c = 0; c < current->n_args; c++) - if (current->args[c]) - free(current->args[c]); - TAILQ_REMOVE(&old_stackframes, current, stackframes); - free(current); - } - while (!TAILQ_EMPTY(&new_stackframes)) { - stackframe *current = TAILQ_FIRST(&new_stackframes); - for (int c = 0; c < current->n_args; c++) - if (current->args[c]) - free(current->args[c]); - TAILQ_REMOVE(&new_stackframes, current, stackframes); - free(current); - } -} - -void cmd_MIGRATION_disable() { - migration_test = false; -} - -void cmd_MIGRATION_save_new_parameters(Match *current_match, ...) { - va_list args; - - DLOG("saving parameters.\n"); - stackframe *frame = scalloc(sizeof(stackframe)); - match_copy(&(frame->match), current_match); - - /* All parameters are char*s */ - va_start(args, current_match); - while (true) { - char *parameter = va_arg(args, char*); - if (parameter == last_parameter) - break; - DLOG("parameter = %s\n", parameter); - if (parameter) - frame->args[frame->n_args] = sstrdup(parameter); - frame->n_args++; - } - va_end(args); - - TAILQ_INSERT_TAIL(&new_stackframes, frame, stackframes); -} - -void cmd_MIGRATION_save_old_parameters(Match *current_match, ...) { - va_list args; +/* + * 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); - DLOG("saving new parameters.\n"); - stackframe *frame = scalloc(sizeof(stackframe)); - match_copy(&(frame->match), current_match); + /* If we switched to a different workspace, do nothing */ + if (strcmp(ws->name, name) != 0) + return false; - /* All parameters are char*s */ - va_start(args, current_match); - while (true) { - char *parameter = va_arg(args, char*); - if (parameter == last_parameter) - break; - DLOG("parameter = %s\n", parameter); - if (parameter) - frame->args[frame->n_args] = sstrdup(parameter); - frame->n_args++; + DLOG("This workspace is already focused.\n"); + if (config.workspace_auto_back_and_forth) { + workspace_back_and_forth(); + cmd_output->needs_tree_render = true; } - va_end(args); - - TAILQ_INSERT_TAIL(&old_stackframes, frame, stackframes); -} - -static bool re_differ(struct regex *new, struct regex *old) { - return ((new == NULL && old != NULL) || - (new != NULL && old == NULL) || - (new != NULL && old != NULL && - strcmp(new->pattern, old->pattern) != 0)); -} - -static bool str_differ(char *new, char *old) { - return ((new == NULL && old != NULL) || - (new != NULL && old == NULL) || - (new != NULL && old != NULL && - strcmp(new, old) != 0)); + return true; } +// This code is commented out because we might recycle it for popping up error +// messages on parser errors. +#if 0 static pid_t migration_pid = -1; /* @@ -208,7 +142,7 @@ static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) { } #endif -void cmd_MIGRATION_start_nagbar() { +void cmd_MIGRATION_start_nagbar(void) { if (migration_pid != -1) { fprintf(stderr, "i3-nagbar already running.\n"); return; @@ -259,100 +193,45 @@ void cmd_MIGRATION_start_nagbar() { #endif } -void cmd_MIGRATION_validate() { - DLOG("validating the different stacks now\n"); - int old_count = 0; - int new_count = 0; - stackframe *current; - TAILQ_FOREACH(current, &new_stackframes, stackframes) - new_count++; - TAILQ_FOREACH(current, &old_stackframes, stackframes) - old_count++; - if (new_count != old_count) { - ELOG("FAILED, new_count == %d != old_count == %d\n", new_count, old_count); - cmd_MIGRATION_start_nagbar(); - return; - } - DLOG("parameter count matching, comparing one by one...\n"); - - stackframe *new_frame = TAILQ_FIRST(&new_stackframes), - *old_frame = TAILQ_FIRST(&old_stackframes); - for (int i = 0; i < new_count; i++) { - if (new_frame->match.dock != old_frame->match.dock || - new_frame->match.id != old_frame->match.id || - new_frame->match.con_id != old_frame->match.con_id || - new_frame->match.floating != old_frame->match.floating || - new_frame->match.insert_where != old_frame->match.insert_where || - re_differ(new_frame->match.title, old_frame->match.title) || - re_differ(new_frame->match.application, old_frame->match.application) || - re_differ(new_frame->match.class, old_frame->match.class) || - re_differ(new_frame->match.instance, old_frame->match.instance) || - re_differ(new_frame->match.mark, old_frame->match.mark) || - re_differ(new_frame->match.role, old_frame->match.role) ) { - ELOG("FAILED, new_frame->match != old_frame->match (frame %d)\n", i); - cmd_MIGRATION_start_nagbar(); - return; - } - if (new_frame->n_args != old_frame->n_args) { - ELOG("FAILED, new_frame->n_args == %d != old_frame->n_args == %d (frame %d)\n", - new_frame->n_args, old_frame->n_args, i); - cmd_MIGRATION_start_nagbar(); - return; - } - for (int j = 0; j < new_frame->n_args; j++) { - if (str_differ(new_frame->args[j], old_frame->args[j])) { - ELOG("FAILED, new_frame->args[%d] == %s != old_frame->args[%d] == %s (frame %d)\n", - j, new_frame->args[j], j, old_frame->args[j], i); - cmd_MIGRATION_start_nagbar(); - return; - } - } - new_frame = TAILQ_NEXT(new_frame, stackframes); - old_frame = TAILQ_NEXT(old_frame, stackframes); - } - DLOG("OK\n"); -} - -#define MIGRATION_init(x, ...) do { \ - if (migration_test) { \ - cmd_MIGRATION_save_new_parameters(current_match, __FUNCTION__, ##__VA_ARGS__ , last_parameter); \ - return NULL; \ - } else { \ - cmd_MIGRATION_save_old_parameters(current_match, __FUNCTION__, ##__VA_ARGS__ , last_parameter); \ - } \ -} while (0) - +#endif /******************************************************************************* * Criteria functions. ******************************************************************************/ -char *cmd_criteria_init(Match *current_match) { +/* + * Initializes the specified 'Match' data structure and the initial state of + * commands.c for matching target windows of a command. + * + */ +void cmd_criteria_init(I3_CMD) { + Con *con; + owindow *ow; + DLOG("Initializing criteria, current_match = %p\n", current_match); match_init(current_match); + while (!TAILQ_EMPTY(&owindows)) { + ow = TAILQ_FIRST(&owindows); + TAILQ_REMOVE(&owindows, ow, owindows); + free(ow); + } TAILQ_INIT(&owindows); /* copy all_cons */ - Con *con; TAILQ_FOREACH(con, &all_cons, all_cons) { - owindow *ow = smalloc(sizeof(owindow)); + ow = smalloc(sizeof(owindow)); ow->con = con; TAILQ_INSERT_TAIL(&owindows, ow, owindows); } - - /* This command is internal and does not generate a JSON reply. */ - return NULL; } -char *cmd_criteria_match_windows(Match *current_match) { +/* + * A match specification just finished (the closing square bracket was found), + * so we filter the list of owindows. + * + */ +void cmd_criteria_match_windows(I3_CMD) { owindow *next, *current; - /* The same as MIGRATION_init, but doesn’t return */ - if (migration_test) { - cmd_MIGRATION_save_new_parameters(current_match, __FUNCTION__, last_parameter); - } else { - cmd_MIGRATION_save_old_parameters(current_match, __FUNCTION__, last_parameter); - } - DLOG("match specification finished, matching...\n"); /* copy the old list head to iterate through it and start with a fresh * list which will contain only matching windows */ @@ -391,34 +270,29 @@ char *cmd_criteria_match_windows(Match *current_match) { TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); } - - /* This command is internal and does not generate a JSON reply. */ - return NULL; } -char *cmd_criteria_add(Match *current_match, char *ctype, char *cvalue) { - /* The same as MIGRATION_init, but doesn’t return */ - if (migration_test) { - cmd_MIGRATION_save_new_parameters(current_match, __FUNCTION__, last_parameter); - } else { - cmd_MIGRATION_save_old_parameters(current_match, __FUNCTION__, last_parameter); - } - +/* + * Interprets a ctype=cvalue pair and adds it to the current match + * specification. + * + */ +void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) { DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue); if (strcmp(ctype, "class") == 0) { current_match->class = regex_new(cvalue); - return NULL; + return; } if (strcmp(ctype, "instance") == 0) { current_match->instance = regex_new(cvalue); - return NULL; + return; } if (strcmp(ctype, "window_role") == 0) { current_match->role = regex_new(cvalue); - return NULL; + return; } if (strcmp(ctype, "con_id") == 0) { @@ -433,7 +307,7 @@ char *cmd_criteria_add(Match *current_match, char *ctype, char *cvalue) { current_match->con_id = (Con*)parsed; printf("id as int = %p\n", current_match->con_id); } - return NULL; + return; } if (strcmp(ctype, "id") == 0) { @@ -448,32 +322,54 @@ char *cmd_criteria_add(Match *current_match, char *ctype, char *cvalue) { current_match->id = parsed; printf("window id as int = %d\n", current_match->id); } - return NULL; + return; } if (strcmp(ctype, "con_mark") == 0) { current_match->mark = regex_new(cvalue); - return NULL; + return; } if (strcmp(ctype, "title") == 0) { current_match->title = regex_new(cvalue); - return NULL; + return; } - ELOG("Unknown criterion: %s\n", ctype); + if (strcmp(ctype, "urgent") == 0) { + if (strcasecmp(cvalue, "latest") == 0 || + strcasecmp(cvalue, "newest") == 0 || + strcasecmp(cvalue, "recent") == 0 || + strcasecmp(cvalue, "last") == 0) { + current_match->urgent = U_LATEST; + } else if (strcasecmp(cvalue, "oldest") == 0 || + strcasecmp(cvalue, "first") == 0) { + current_match->urgent = U_OLDEST; + } + return; + } - /* This command is internal and does not generate a JSON reply. */ - return NULL; + ELOG("Unknown criterion: %s\n", ctype); } -char *cmd_move_con_to_workspace(Match *current_match, char *which) { +/* + * Implementation of 'move [window|container] [to] workspace + * next|prev|next_on_output|prev_on_output|current'. + * + */ +void cmd_move_con_to_workspace(I3_CMD, char *which) { owindow *current; - MIGRATION_init(x, 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 */ @@ -486,9 +382,12 @@ char *cmd_move_con_to_workspace(Match *current_match, 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); - return sstrdup("{\"sucess\": false}"); + ysuccess(false); + return; } TAILQ_FOREACH(current, &owindows, owindows) { @@ -496,26 +395,38 @@ char *cmd_move_con_to_workspace(Match *current_match, char *which) { con_move_to_workspace(current->con, ws, true, false); } - tree_render(); - + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_move_con_to_workspace_name(Match *current_match, char *name) { - MIGRATION_init(x, name); - +/* + * Implementation of 'move [window|container] [to] workspace '. + * + */ +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"); - return 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. */ - if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) - return sstrdup("{\"sucess\": false}"); + /* 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) { + ELOG("No window to move, you have focused a workspace.\n"); + ysuccess(false); + return; + } LOG("should move window to workspace %s\n", name); /* get the workspace */ @@ -528,63 +439,117 @@ char *cmd_move_con_to_workspace_name(Match *current_match, char *name) { con_move_to_workspace(current->con, ws, true, false); } - tree_render(); - + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_resize(Match *current_match, char *way, char *direction, char *resize_px, char *resize_ppt) { - MIGRATION_init(x, way, direction, resize_px, resize_ppt); - /* resize [ px] [or ppt] */ - DLOG("resizing in way %s, direction %s, px %s or ppt %s\n", way, direction, resize_px, resize_ppt); - // TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking - int px = atoi(resize_px); - int ppt = atoi(resize_ppt); - if (strcmp(way, "shrink") == 0) { - px *= -1; - ppt *= -1; +/* + * Implementation of 'move [window|container] [to] workspace number '. + * + */ +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; } - Con *floating_con; - if ((floating_con = con_inside_floating(focused))) { - printf("floating resize\n"); - if (strcmp(direction, "up") == 0) { - floating_con->rect.y -= px; - floating_con->rect.height += px; - } else if (strcmp(direction, "down") == 0) { - floating_con->rect.height += px; - } else if (strcmp(direction, "left") == 0) { - floating_con->rect.x -= px; - floating_con->rect.width += px; - } else { - floating_con->rect.width += px; - } - } else { - LOG("tiling resize\n"); - /* get the appropriate current container (skip stacked/tabbed cons) */ - Con *current = focused; - while (current->parent->layout == L_STACKED || - current->parent->layout == L_TABBED) - current = current->parent; + 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 != '\0') { + LOG("Could not parse \"%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; + } - /* Then further go up until we find one with the matching orientation. */ - orientation_t search_orientation = - (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0 ? HORIZ : VERT); + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) + GREP_FIRST(workspace, output_get_content(output), + child->num == parsed_num); + + if (!workspace) { + y(map_open); + ystr("success"); + y(bool, false); + ystr("error"); + // TODO: better error message + ystr("No such workspace"); + y(map_close); + return; + } + + HANDLE_EMPTY_MATCH; - while (current->type != CT_WORKSPACE && - current->type != CT_FLOATING_CON && - current->parent->orientation != search_orientation) + 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) { + LOG("floating resize\n"); + if (strcmp(direction, "up") == 0) { + floating_con->rect.y -= px; + floating_con->rect.height += px; + } 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; + floating_con->rect.width += px; + } else { + floating_con->rect.width += px; + } +} + +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; + + /* Then further go up until we find one with the matching orientation. */ + orientation_t search_orientation = + (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0 ? HORIZ : VERT); + + 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; + 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, "up") == 0 || strcmp(direction, "down") == 0)) || @@ -592,7 +557,8 @@ char *cmd_resize(Match *current_match, char *way, char *direction, char *resize_ (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")); - return sstrdup("{\"sucess\": false}"); + ysuccess(false); + return false; } if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) { @@ -601,40 +567,167 @@ char *cmd_resize(Match *current_match, char *way, char *direction, char *resize_ other = TAILQ_NEXT(current, nodes); } if (other == TAILQ_END(workspaces)) { - LOG("No other container in this direction found, cannot resize.\n"); - return sstrdup("{\"sucess\": false}"); + LOG("No other container in this direction found, trying to look further up in the tree...\n"); + current = current->parent; + continue; } - LOG("other->percent = %f\n", other->percent); - LOG("current->percent before = %f\n", current->percent); - if (current->percent == 0.0) - current->percent = percentage; - if (other->percent == 0.0) - other->percent = percentage; - double new_current_percent = current->percent + ((double)ppt / 100.0); - double new_other_percent = other->percent - ((double)ppt / 100.0); - LOG("new_current_percent = %f\n", new_current_percent); - LOG("new_other_percent = %f\n", new_other_percent); - /* Ensure that the new percentages are positive and greater than - * 0.05 to have a reasonable minimum size. */ - if (definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON) && - definitelyGreaterThan(new_other_percent, 0.05, DBL_EPSILON)) { - current->percent += ((double)ppt / 100.0); - other->percent -= ((double)ppt / 100.0); - LOG("current->percent after = %f\n", current->percent); - LOG("other->percent after = %f\n", other->percent); - } else { - LOG("Not resizing, already at minimum size\n"); + break; + } while (current->type != CT_WORKSPACE && + current->type != CT_FLOATING_CON); + + if (other == NULL) { + LOG("No other container in this direction found, trying to look further up in the tree...\n"); + ysuccess(false); + return false; + } + + LOG("other->percent = %f\n", other->percent); + LOG("current->percent before = %f\n", current->percent); + if (current->percent == 0.0) + current->percent = percentage; + if (other->percent == 0.0) + other->percent = percentage; + double new_current_percent = current->percent + ((double)ppt / 100.0); + double new_other_percent = other->percent - ((double)ppt / 100.0); + LOG("new_current_percent = %f\n", new_current_percent); + LOG("new_other_percent = %f\n", new_other_percent); + /* Ensure that the new percentages are positive and greater than + * 0.05 to have a reasonable minimum size. */ + if (definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON) && + definitelyGreaterThan(new_other_percent, 0.05, DBL_EPSILON)) { + current->percent += ((double)ppt / 100.0); + other->percent -= ((double)ppt / 100.0); + LOG("current->percent after = %f\n", current->percent); + LOG("other->percent after = %f\n", other->percent); + } else { + LOG("Not resizing, already at minimum size\n"); + } + + return true; +} + +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; + while (current->parent->layout == L_STACKED || + current->parent->layout == L_TABBED) + current = current->parent; + + /* Then further go up until we find one with the matching orientation. */ + orientation_t search_orientation = + (strcmp(direction, "width") == 0 ? HORIZ : VERT); + + while (current->type != CT_WORKSPACE && + current->type != CT_FLOATING_CON && + con_orientation(current->parent) != search_orientation) + current = current->parent; + + /* get the default percentage */ + int children = con_num_children(current->parent); + LOG("ins. %d children\n", children); + double percentage = 1.0 / children; + LOG("default percentage = %f\n", percentage); + + orientation_t orientation = con_orientation(current->parent); + + if ((orientation == HORIZ && + strcmp(direction, "height") == 0) || + (orientation == VERT && + 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")); + ysuccess(false); + return false; + } + + if (children == 1) { + LOG("This is the only container, cannot resize.\n"); + ysuccess(false); + return false; + } + + /* Ensure all the other children have a percentage set. */ + Con *child; + TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) { + LOG("child->percent = %f (child %p)\n", child->percent, child); + if (child->percent == 0.0) + child->percent = percentage; + } + + double new_current_percent = current->percent + ((double)ppt / 100.0); + double subtract_percent = ((double)ppt / 100.0) / (children - 1); + LOG("new_current_percent = %f\n", new_current_percent); + LOG("subtract_percent = %f\n", subtract_percent); + /* Ensure that the new percentages are positive and greater than + * 0.05 to have a reasonable minimum size. */ + TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) { + if (child == current) + 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); + ysuccess(false); + return false; } } + if (!definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON)) { + LOG("Not resizing, already at minimum size\n"); + ysuccess(false); + return false; + } - tree_render(); + current->percent += ((double)ppt / 100.0); + LOG("current->percent after = %f\n", current->percent); + TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) { + if (child == current) + continue; + child->percent -= subtract_percent; + LOG("child->percent after (%p) = %f\n", child, child->percent); + } + + return true; +} + +/* + * Implementation of 'resize grow|shrink [ px] [or ppt]'. + * + */ +void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resize_ppt) { + /* resize [ px] [or ppt] */ + DLOG("resizing in way %s, direction %s, px %s or ppt %s\n", way, direction, resize_px, resize_ppt); + // TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking + int px = atoi(resize_px); + int ppt = atoi(resize_ppt); + if (strcmp(way, "shrink") == 0) { + px *= -1; + ppt *= -1; + } + + Con *floating_con; + if ((floating_con = con_inside_floating(focused))) { + cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, px); + } else { + if (strcmp(direction, "width") == 0 || + 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 - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_border(Match *current_match, char *border_style_str) { - MIGRATION_init(x, border_style_str); +/* + * Implementation of 'border normal|none|1pixel|toggle'. + * + */ +void cmd_border(I3_CMD, char *border_style_str) { DLOG("border style should be changed to %s\n", border_style_str); owindow *current; @@ -655,39 +748,46 @@ char *cmd_border(Match *current_match, char *border_style_str) { border_style = BS_1PIXEL; else { ELOG("BUG: called with border_style=%s\n", border_style_str); - return sstrdup("{\"sucess\": false}"); + ysuccess(false); + return; } } con_set_border_style(current->con, border_style); } - tree_render(); - + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_nop(Match *current_match, char *comment) { - MIGRATION_init(x, comment); +/* + * Implementation of 'nop '. + * + */ +void cmd_nop(I3_CMD, char *comment) { LOG("-------------------------------------------------\n"); LOG(" NOP: %s\n", comment); LOG("-------------------------------------------------\n"); - - return NULL; } -char *cmd_append_layout(Match *current_match, char *path) { - MIGRATION_init(x, path); +/* + * Implementation of 'append_layout '. + * + */ +void cmd_append_layout(I3_CMD, char *path) { LOG("Appending layout \"%s\"\n", path); tree_append_json(path); - tree_render(); + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_workspace(Match *current_match, char *which) { - MIGRATION_init(x, which); +/* + * Implementation of 'workspace next|prev|next_on_output|prev_on_output'. + * + */ +void cmd_workspace(I3_CMD, char *which) { Con *ws; DLOG("which=%s\n", which); @@ -702,56 +802,104 @@ char *cmd_workspace(Match *current_match, char *which) { ws = workspace_prev_on_output(); else { ELOG("BUG: called with which=%s\n", which); - return sstrdup("{\"sucess\": false}"); + ysuccess(false); + return; } workspace_show(ws); - tree_render(); + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_workspace_back_and_forth(Match *current_match) { - MIGRATION_init(x); +/* + * Implementation of 'workspace number ' + * + */ +void cmd_workspace_number(I3_CMD, char *which) { + 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); + 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) { + LOG("There is no workspace with number %ld, creating a new one.\n", parsed_num); + ysuccess(true); + /* terminate the which string after the endposition of the number */ + *endptr = '\0'; + if (maybe_back_and_forth(cmd_output, which)) + return; + workspace_show_by_name(which); + cmd_output->needs_tree_render = true; + return; + } + if (maybe_back_and_forth(cmd_output, which)) + return; + workspace_show(workspace); + + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + ysuccess(true); +} + +/* + * Implementation of 'workspace back_and_forth'. + * + */ +void cmd_workspace_back_and_forth(I3_CMD) { workspace_back_and_forth(); - tree_render(); + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_workspace_name(Match *current_match, char *name) { - MIGRATION_init(x, name); +/* + * Implementation of 'workspace ' + * + */ +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"); - return 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(); - } - return sstrdup("{\"sucess\": false}"); - } - + if (maybe_back_and_forth(cmd_output, name)) + return; workspace_show_by_name(name); - tree_render(); - + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_mark(Match *current_match, char *mark) { - MIGRATION_init(x, mark); +/* + * Implementation of 'mark ' + * + */ +void cmd_mark(I3_CMD, char *mark) { DLOG("Clearing all windows which have that mark first\n"); Con *con; @@ -770,23 +918,28 @@ char *cmd_mark(Match *current_match, char *mark) { current->con->mark = sstrdup(mark); } - tree_render(); - + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_mode(Match *current_match, char *mode) { - MIGRATION_init(x, mode); +/* + * Implementation of 'mode '. + * + */ +void cmd_mode(I3_CMD, char *mode) { DLOG("mode=%s\n", mode); switch_mode(mode); // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_move_con_to_output(Match *current_match, char *name) { - MIGRATION_init(x, name); +/* + * Implementation of 'move [window|container] [to] output '. + * + */ +void cmd_move_con_to_output(I3_CMD, char *name) { owindow *current; DLOG("should move window to output %s\n", name); @@ -817,28 +970,33 @@ char *cmd_move_con_to_output(Match *current_match, char *name) { if (!output) { LOG("No such output found.\n"); - return sstrdup("{\"sucess\": false}"); + ysuccess(false); + return; } /* get visible workspace on output */ Con *ws = NULL; GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child)); - if (!ws) - return sstrdup("{\"sucess\": false}"); + if (!ws) { + 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); } - tree_render(); - + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_floating(Match *current_match, char *floating_mode) { - MIGRATION_init(x, floating_mode); +/* + * Implementation of 'floating enable|disable|toggle' + * + */ +void cmd_floating(I3_CMD, char *floating_mode) { owindow *current; DLOG("floating_mode=%s\n", floating_mode); @@ -860,14 +1018,16 @@ char *cmd_floating(Match *current_match, char *floating_mode) { } } - tree_render(); - + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_move_workspace_to_output(Match *current_match, char *name) { - MIGRATION_init(x, name); +/* + * Implementation of 'move workspace to [output] '. + * + */ +void cmd_move_workspace_to_output(I3_CMD, char *name) { DLOG("should move workspace to output %s\n", name); HANDLE_EMPTY_MATCH; @@ -876,10 +1036,16 @@ char *cmd_move_workspace_to_output(Match *current_match, 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"); - return sstrdup("{\"sucess\": false}"); + ELOG("Could not get output from string \"%s\"\n", name); + ysuccess(false); + return; } Con *content = output_get_content(output->con); @@ -887,10 +1053,42 @@ char *cmd_move_workspace_to_output(Match *current_match, char *name) { Con *ws = con_get_workspace(current->con); LOG("should move workspace %p / %s\n", ws, ws->name); + if (con_num_children(ws->parent) == 1) { - LOG("Not moving workspace \"%s\", it is the only workspace on its output.\n", ws->name); - continue; + LOG("Creating a new workspace to replace \"%s\" (last on its output).\n", ws->name); + + /* check if we can find a workspace assigned to this output */ + bool used_assignment = false; + struct Workspace_Assignment *assignment; + TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { + if (strcmp(assignment->output, current_output->name) != 0) + continue; + + /* check if this workspace is already attached to the tree */ + Con *workspace = NULL, *out; + TAILQ_FOREACH(out, &(croot->nodes_head), nodes) + GREP_FIRST(workspace, output_get_content(out), + !strcasecmp(child->name, assignment->name)); + if (workspace != NULL) + continue; + + /* so create the workspace referenced to by this assignment */ + LOG("Creating workspace from assignment %s.\n", assignment->name); + workspace_get(assignment->name, NULL); + used_assignment = true; + break; + } + + /* if we couldn't create the workspace using an assignment, create + * it on the output */ + if (!used_assignment) + create_workspace_on_output(current_output, ws->parent); + + /* notify the IPC listeners */ + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); } + + /* 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); @@ -902,6 +1100,12 @@ char *cmd_move_workspace_to_output(Match *current_match, char *name) { workspace_show(focus_ws); } con_attach(ws, content, false); + + /* fix the coordinates of the floating containers */ + Con *floating_con; + TAILQ_FOREACH(floating_con, &(ws->floating_head), floating_windows) + floating_fix_coordinates(floating_con, &(old_content->rect), &(content->rect)); + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"move\"}"); if (workspace_was_visible) { /* Focus the moved workspace on the destination output. */ @@ -909,28 +1113,32 @@ char *cmd_move_workspace_to_output(Match *current_match, char *name) { } } - tree_render(); - + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_split(Match *current_match, char *direction) { - MIGRATION_init(x, direction); +/* + * Implementation of 'split v|h|vertical|horizontal'. + * + */ +void cmd_split(I3_CMD, char *direction) { /* TODO: use matches */ LOG("splitting in direction %c\n", direction[0]); tree_split(focused, (direction[0] == 'v' ? VERT : HORIZ)); - tree_render(); - + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_kill(Match *current_match, char *kill_mode_str) { +/* + * Implementaiton of 'kill [window|client]'. + * + */ +void cmd_kill(I3_CMD, char *kill_mode_str) { if (kill_mode_str == NULL) kill_mode_str = "window"; - MIGRATION_init(x, kill_mode_str); owindow *current; DLOG("kill_mode=%s\n", kill_mode_str); @@ -942,7 +1150,8 @@ char *cmd_kill(Match *current_match, char *kill_mode_str) { kill_mode = KILL_CLIENT; else { ELOG("BUG: called with kill_mode=%s\n", kill_mode_str); - return sstrdup("{\"sucess\": false}"); + ysuccess(false); + return; } /* check if the match is empty, not if the result is empty */ @@ -955,30 +1164,36 @@ char *cmd_kill(Match *current_match, char *kill_mode_str) { } } - tree_render(); - + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_exec(Match *current_match, char *nosn, char *command) { - MIGRATION_init(x, nosn, command); +/* + * Implementation of 'exec [--no-startup-id] '. + * + */ +void cmd_exec(I3_CMD, char *nosn, char *command) { bool no_startup_id = (nosn != NULL); DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id); start_application(command, no_startup_id); // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_focus_direction(Match *current_match, char *direction) { - MIGRATION_init(x, direction); +/* + * Implementation of 'focus left|right|up|down'. + * + */ +void cmd_focus_direction(I3_CMD, char *direction) { if (focused && focused->type != CT_WORKSPACE && focused->fullscreen_mode != CF_NONE) { LOG("Cannot change focus while in fullscreen mode.\n"); - return sstrdup("{\"sucess\": false}"); + ysuccess(false); + return; } DLOG("direction = *%s*\n", direction); @@ -993,22 +1208,26 @@ char *cmd_focus_direction(Match *current_match, char *direction) { tree_next('n', VERT); else { ELOG("Invalid focus direction (%s)\n", direction); - return sstrdup("{\"sucess\": false}"); + ysuccess(false); + return; } - tree_render(); - + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_focus_window_mode(Match *current_match, char *window_mode) { - MIGRATION_init(x, window_mode); +/* + * Implementation of 'focus tiling|floating|mode_toggle'. + * + */ +void cmd_focus_window_mode(I3_CMD, char *window_mode) { if (focused && focused->type != CT_WORKSPACE && focused->fullscreen_mode != CF_NONE) { LOG("Cannot change focus while in fullscreen mode.\n"); - return sstrdup("{\"sucess\": false}"); + ysuccess(false); + return; } DLOG("window_mode = %s\n", window_mode); @@ -1032,56 +1251,61 @@ char *cmd_focus_window_mode(Match *current_match, char *window_mode) { } } - tree_render(); - + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_focus_level(Match *current_match, char *level) { - MIGRATION_init(x, level); - if (focused && - focused->type != CT_WORKSPACE && - focused->fullscreen_mode != CF_NONE) { - LOG("Cannot change focus while in fullscreen mode.\n"); - return sstrdup("{\"sucess\": false}"); - } - +/* + * Implementation of 'focus parent|child'. + * + */ +void cmd_focus_level(I3_CMD, char *level) { 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(); - - tree_render(); + /* Focusing a child should always be allowed. */ + else success = level_down(); + cmd_output->needs_tree_render = success; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(success); } -char *cmd_focus(Match *current_match) { - MIGRATION_init(x); +/* + * Implementation of 'focus'. + * + */ +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"); - return sstrdup("{\"sucess\": false}"); - } - - 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"); - char *json_output; - sasprintf(&json_output, "{\"success\":false, \"error\":\"You have to " - "specify which window/container should be focused\"}"); - return json_output; + 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. @@ -1089,6 +1313,13 @@ char *cmd_focus(Match *current_match) { 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 @@ -1116,16 +1347,18 @@ char *cmd_focus(Match *current_match) { LOG("WARNING: Your criteria for the focus command matches %d containers, " "while only exactly one container can be focused at a time.\n", count); - tree_render(); - + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_fullscreen(Match *current_match, char *fullscreen_mode) { +/* + * Implementation of 'fullscreen [global]'. + * + */ +void cmd_fullscreen(I3_CMD, char *fullscreen_mode) { if (fullscreen_mode == NULL) fullscreen_mode = "output"; - MIGRATION_init(x, fullscreen_mode); DLOG("toggling fullscreen, mode = %s\n", fullscreen_mode); owindow *current; @@ -1136,14 +1369,16 @@ char *cmd_fullscreen(Match *current_match, char *fullscreen_mode) { con_toggle_fullscreen(current->con, (strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT)); } - tree_render(); - + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_move_direction(Match *current_match, char *direction, char *move_px) { - MIGRATION_init(x, direction, move_px); +/* + * Implementation of 'move [ [px]]'. + * + */ +void cmd_move_direction(I3_CMD, char *direction, char *move_px) { // TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking int px = atoi(move_px); @@ -1163,27 +1398,43 @@ char *cmd_move_direction(Match *current_match, char *direction, char *move_px) { } floating_reposition(focused->parent, newrect); } else { - tree_move((strcmp(direction, "right") == 0 ? TOK_RIGHT : - (strcmp(direction, "left") == 0 ? TOK_LEFT : - (strcmp(direction, "up") == 0 ? TOK_UP : - TOK_DOWN)))); - tree_render(); + tree_move((strcmp(direction, "right") == 0 ? D_RIGHT : + (strcmp(direction, "left") == 0 ? D_LEFT : + (strcmp(direction, "up") == 0 ? D_UP : + D_DOWN)))); + cmd_output->needs_tree_render = true; } - // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_layout(Match *current_match, char *layout_str) { +/* + * 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"; - MIGRATION_init(x, layout_str); - 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)) @@ -1195,57 +1446,104 @@ char *cmd_layout(Match *current_match, char *layout_str) { } } - tree_render(); + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + 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->parent, 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 - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_exit(Match *current_match) { - MIGRATION_init(x); +/* + * Implementaiton of 'exit'. + * + */ +void cmd_exit(I3_CMD) { LOG("Exiting due to user command.\n"); + xcb_disconnect(conn); exit(0); /* unreached */ } -char *cmd_reload(Match *current_match) { - MIGRATION_init(x); +/* + * Implementaiton of 'reload'. + * + */ +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 - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_restart(Match *current_match) { - MIGRATION_init(x); +/* + * Implementaiton of 'restart'. + * + */ +void cmd_restart(I3_CMD) { LOG("restarting i3\n"); i3_restart(false); // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_open(Match *current_match) { - MIGRATION_init(x); +/* + * Implementaiton of 'open'. + * + */ +void cmd_open(I3_CMD) { LOG("opening new container\n"); Con *con = tree_open_con(NULL, NULL); + con->layout = L_SPLITH; con_focus(con); - char *json_output; - sasprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con); - tree_render(); + y(map_open); + ystr("success"); + y(bool, true); + ystr("id"); + y(integer, (long int)con); + y(map_close); - return json_output; + cmd_output->needs_tree_render = true; } -char *cmd_focus_output(Match *current_match, char *name) { - MIGRATION_init(x, name); +/* + * Implementation of 'focus output '. + * + */ +void cmd_focus_output(I3_CMD, char *name) { owindow *current; DLOG("name = %s\n", name); @@ -1264,24 +1562,115 @@ char *cmd_focus_output(Match *current_match, char *name) { if (!output) { LOG("No such output found.\n"); - return sstrdup("{\"sucess\": false}"); + ysuccess(false); + return; } /* get visible workspace on output */ Con *ws = NULL; GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child)); - if (!ws) - return sstrdup("{\"sucess\": false}"); + if (!ws) { + ysuccess(false); + return; + } workspace_show(ws); - tree_render(); + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_move_scratchpad(Match *current_match) { - MIGRATION_init(x); +/* + * Implementation of 'move [window|container] [to] [absolute] position [px] [px] + * + */ +void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) { + + int x = atoi(cx); + int y = atoi(cy); + + if (!con_is_floating(focused)) { + ELOG("Cannot change position. The window/container is not floating\n"); + y(map_open); + ystr("success"); + y(bool, false); + ystr("error"); + ystr("Cannot change position. The window/container is not floating."); + y(map_close); + return; + } + + if (strcmp(method, "absolute") == 0) { + focused->parent->rect.x = x; + focused->parent->rect.y = y; + + DLOG("moving to absolute position %d %d\n", x, y); + floating_maybe_reassign_ws(focused->parent); + cmd_output->needs_tree_render = true; + } + + if (strcmp(method, "position") == 0) { + Rect newrect = focused->parent->rect; + + DLOG("moving to position %d %d\n", x, y); + newrect.x = x; + newrect.y = y; + + floating_reposition(focused->parent, newrect); + } + + // XXX: default reply for now, make this a better reply + ysuccess(true); +} + +/* + * Implementation of 'move [window|container] [to] [absolute] position center + * + */ +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"); + 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) { + Rect *rect = &focused->parent->rect; + + DLOG("moving to absolute center\n"); + rect->x = croot->rect.width/2 - rect->width/2; + rect->y = croot->rect.height/2 - rect->height/2; + + floating_maybe_reassign_ws(focused->parent); + cmd_output->needs_tree_render = true; + } + + if (strcmp(method, "position") == 0) { + Rect *wsrect = &con_get_workspace(focused)->rect; + Rect newrect = focused->parent->rect; + + DLOG("moving to center\n"); + newrect.x = wsrect->width/2 - newrect.width/2; + newrect.y = wsrect->height/2 - newrect.height/2; + + floating_reposition(focused->parent, newrect); + } + + // XXX: default reply for now, make this a better reply + ysuccess(true); +} + +/* + * Implementation of 'move scratchpad'. + * + */ +void cmd_move_scratchpad(I3_CMD) { DLOG("should move window to scratchpad\n"); owindow *current; @@ -1292,14 +1681,16 @@ char *cmd_move_scratchpad(Match *current_match) { scratchpad_move(current->con); } - tree_render(); - + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); } -char *cmd_scratchpad_show(Match *current_match) { - MIGRATION_init(x); +/* + * Implementation of 'scratchpad show'. + * + */ +void cmd_scratchpad_show(I3_CMD) { DLOG("should show scratchpad window\n"); owindow *current; @@ -1312,8 +1703,77 @@ char *cmd_scratchpad_show(Match *current_match) { } } - tree_render(); - + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply - return sstrdup("{\"success\": true}"); + ysuccess(true); +} + +/* + * Implementation of 'rename workspace to ' + * + */ +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\"}"); }