X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fcommands.c;h=9631923d8b4e7fd5f2c1db51fed9cdb6089d824b;hb=dbec5eb90585bc22752331f51d8a6bc90d21889c;hp=abde85d866cc8d80b78aea9a1fefba1078926c4b;hpb=fbff593f1e84f44a1ee08be4b25f0f12e40b847c;p=i3%2Fi3 diff --git a/src/commands.c b/src/commands.c index abde85d8..9631923d 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,6 +13,7 @@ #include #include "all.h" +#include "shmlog.h" // Macros to make the YAJL API a bit easier to use. #define y(x, ...) yajl_gen_ ## x (cmd_output->json_gen, ##__VA_ARGS__) @@ -21,9 +24,17 @@ y(bool, success); \ y(map_close); \ } while (0) +#define yerror(message) do { \ + y(map_open); \ + ystr("success"); \ + y(bool, false); \ + ystr("error"); \ + ystr(message); \ + 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. */ @@ -36,7 +47,6 @@ } \ } while (0) -static owindows_head owindows; /* * Returns true if a is definitely greater than b (using the given epsilon) @@ -54,23 +64,15 @@ static bool definitelyGreaterThan(float a, float b, float epsilon) { static Output *get_output_from_string(Output *current_output, const char *output_str) { Output *output; - if (strcasecmp(output_str, "left") == 0) { - output = get_output_next(D_LEFT, current_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); - 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); - 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); - if (!output) - output = get_output_most(D_UP, current_output); - } else output = get_output_by_name(output_str); + if (strcasecmp(output_str, "left") == 0) + output = get_output_next_wrap(D_LEFT, current_output); + else if (strcasecmp(output_str, "right") == 0) + output = get_output_next_wrap(D_RIGHT, current_output); + else if (strcasecmp(output_str, "up") == 0) + output = get_output_next_wrap(D_UP, current_output); + else if (strcasecmp(output_str, "down") == 0) + output = get_output_next_wrap(D_DOWN, current_output); + else output = get_output_by_name(output_str); return output; } @@ -97,6 +99,29 @@ static bool maybe_back_and_forth(struct CommandResult *cmd_output, char *name) { 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 @@ -197,6 +222,20 @@ void cmd_MIGRATION_start_nagbar(void) { * Criteria functions. ******************************************************************************/ +/* + * Helper data structure for an operation window (window on which the operation + * will be performed). Used to build the TAILQ owindows. + * + */ +typedef struct owindow { + Con *con; + TAILQ_ENTRY(owindow) owindows; +} owindow; + +typedef TAILQ_HEAD(owindows_head, owindow) owindows_head; + +static owindows_head owindows; + /* * Initializes the specified 'Match' data structure and the initial state of * commands.c for matching target windows of a command. @@ -351,7 +390,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) { @@ -359,6 +398,16 @@ 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 && + !con_has_children(focused))) { + ysuccess(false); + return; + } + HANDLE_EMPTY_MATCH; /* get the workspace */ @@ -371,6 +420,8 @@ 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); ysuccess(false); @@ -387,6 +438,33 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) { 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) { + yerror("No workspace was previously active."); + 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); + } + + 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 '. * @@ -400,9 +478,16 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) { 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) { + /* 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; + } + else if (match_is_empty(current_match) && focused->type == CT_WORKSPACE && + !con_has_children(focused)) { ysuccess(false); return; } @@ -411,6 +496,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) { @@ -424,20 +511,23 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) { } /* - * Implementation of 'move [window|container] [to] workspace number '. + * Implementation of 'move [window|container] [to] workspace number '. * */ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { 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) { + /* 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 && + !con_has_children(focused))) { ysuccess(false); return; } - LOG("should move window to workspace with number %d\n", which); + LOG("should move window to workspace %s\n", which); /* get the workspace */ Con *output, *workspace = NULL; @@ -446,15 +536,10 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { 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"); + endptr == which) { + LOG("Could not parse initial part of \"%s\" as a number.\n", which); // TODO: better error message - ystr("Could not parse number"); - y(map_close); + yerror("Could not parse number"); return; } @@ -463,16 +548,11 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { 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; + workspace = workspace_get(which, NULL); } + workspace = maybe_auto_back_and_forth_workspace(workspace); + HANDLE_EMPTY_MATCH; TAILQ_FOREACH(current, &owindows, owindows) { @@ -487,83 +567,99 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floating_con, int px) { LOG("floating resize\n"); + Rect old_rect = floating_con->rect; + Con *focused_con = con_descend_focused(floating_con); + + /* ensure that resize will take place even if pixel increment is smaller than + * height increment or width increment. + * fixes #1011 */ + if (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0 || + strcmp(direction, "height") == 0) { + if (px < 0) + px = (-px < focused_con->height_increment) ? -focused_con->height_increment : px; + else + px = (px < focused_con->height_increment) ? focused_con->height_increment : px; + } else if (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0) { + if (px < 0) + px = (-px < focused_con->width_increment) ? -focused_con->width_increment : px; + else + px = (px < focused_con->width_increment) ? focused_con->width_increment : px; + } + 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; 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; - while (current->parent->layout == L_STACKED || - current->parent->layout == L_TABBED) - current = current->parent; + floating_check_size(floating_con); - /* 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); + /* Did we actually resize anything or did the size constraints prevent us? + * If we could not resize, exit now to not move the window. */ + if (memcmp(&old_rect, &(floating_con->rect), sizeof(Rect)) == 0) + return; - while (current->type != CT_WORKSPACE && - current->type != CT_FLOATING_CON && - current->parent->orientation != search_orientation) - current = current->parent; + if (strcmp(direction, "up") == 0) { + floating_con->rect.y -= (floating_con->rect.height - old_rect.height); + } else if (strcmp(direction, "left") == 0) { + floating_con->rect.x -= (floating_con->rect.width - old_rect.width); + } - /* 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); + /* If this is a scratchpad window, don't auto center it from now on. */ + if (floating_con->scratchpad_state == SCRATCHPAD_FRESH) + floating_con->scratchpad_state = SCRATCHPAD_CHANGED; +} - orientation_t orientation = current->parent->orientation; +static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *direction, int ppt) { + LOG("tiling resize\n"); + Con *second = NULL; + Con *first = current; + direction_t search_direction; + if (!strcmp(direction, "left")) + search_direction = D_LEFT; + else if (!strcmp(direction, "right")) + search_direction = D_RIGHT; + else if (!strcmp(direction, "up")) + search_direction = D_UP; + else + search_direction = D_DOWN; - 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")); + bool res = resize_find_tiling_participants(&first, &second, search_direction); + if (!res) { + LOG("No second container in this direction found.\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"); - 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); + /* get the default percentage */ + int children = con_num_children(first->parent); + LOG("ins. %d children\n", children); + double percentage = 1.0 / children; + LOG("default percentage = %f\n", percentage); + + /* resize */ + LOG("second->percent = %f\n", second->percent); + LOG("first->percent before = %f\n", first->percent); + if (first->percent == 0.0) + first->percent = percentage; + if (second->percent == 0.0) + second->percent = percentage; + double new_first_percent = first->percent + ((double)ppt / 100.0); + double new_second_percent = second->percent - ((double)ppt / 100.0); + LOG("new_first_percent = %f\n", new_first_percent); + LOG("new_second_percent = %f\n", new_second_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); + if (definitelyGreaterThan(new_first_percent, 0.05, DBL_EPSILON) && + definitelyGreaterThan(new_second_percent, 0.05, DBL_EPSILON)) { + first->percent += ((double)ppt / 100.0); + second->percent -= ((double)ppt / 100.0); + LOG("first->percent after = %f\n", first->percent); + LOG("second->percent after = %f\n", second->percent); } else { LOG("Not resizing, already at minimum size\n"); } @@ -571,10 +667,9 @@ static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int return true; } -static bool cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, int ppt) { +static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, 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; @@ -585,7 +680,7 @@ static bool 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 */ @@ -594,7 +689,7 @@ static bool 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) || @@ -669,17 +764,22 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz 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; + HANDLE_EMPTY_MATCH; + + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + Con *floating_con; + if ((floating_con = con_inside_floating(current->con))) { + cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, px); } else { - if (!cmd_resize_tiling_direction(current_match, cmd_output, way, direction, ppt)) - return; + if (strcmp(direction, "width") == 0 || + strcmp(direction, "height") == 0) { + if (!cmd_resize_tiling_width_height(current_match, cmd_output, current->con, way, direction, ppt)) + return; + } else { + if (!cmd_resize_tiling_direction(current_match, cmd_output, current->con, way, direction, ppt)) + return; + } } } @@ -689,11 +789,11 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz } /* - * Implementation of 'border normal|none|1pixel|toggle'. + * Implementation of 'border normal|none|1pixel|toggle|pixel'. * */ -void cmd_border(I3_CMD, char *border_style_str) { - DLOG("border style should be changed to %s\n", border_style_str); +void cmd_border(I3_CMD, char *border_style_str, char *border_width ) { + DLOG("border style should be changed to %s with border width %s\n", border_style_str, border_width); owindow *current; HANDLE_EMPTY_MATCH; @@ -701,23 +801,39 @@ void cmd_border(I3_CMD, char *border_style_str) { TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); int border_style = current->con->border_style; + char *end; + int tmp_border_width = -1; + tmp_border_width = strtol(border_width, &end, 10); + if (end == border_width) { + /* no valid digits found */ + tmp_border_width = -1; + } if (strcmp(border_style_str, "toggle") == 0) { border_style++; border_style %= 3; + if (border_style == BS_NORMAL) + tmp_border_width = 2; + else if (border_style == BS_NONE) + tmp_border_width = 0; + else if (border_style == BS_PIXEL) + tmp_border_width = 1; } else { if (strcmp(border_style_str, "normal") == 0) border_style = BS_NORMAL; - else if (strcmp(border_style_str, "none") == 0) + else if (strcmp(border_style_str, "pixel") == 0) + border_style = BS_PIXEL; + else if (strcmp(border_style_str, "1pixel") == 0){ + border_style = BS_PIXEL; + tmp_border_width = 1; + } else if (strcmp(border_style_str, "none") == 0) border_style = BS_NONE; - else if (strcmp(border_style_str, "1pixel") == 0) - border_style = BS_1PIXEL; else { ELOG("BUG: called with border_style=%s\n", border_style_str); ysuccess(false); return; } } - con_set_border_style(current->con, border_style); + con_set_border_style(current->con, border_style, tmp_border_width); } cmd_output->needs_tree_render = true; @@ -779,7 +895,7 @@ void cmd_workspace(I3_CMD, char *which) { } /* - * Implementation of 'workspace number ' + * Implementation of 'workspace number ' * */ void cmd_workspace_number(I3_CMD, char *which) { @@ -790,15 +906,10 @@ void cmd_workspace_number(I3_CMD, char *which) { 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"); + endptr == which) { + LOG("Could not parse initial part of \"%s\" as a number.\n", which); // TODO: better error message - ystr("Could not parse number"); - y(map_close); + yerror("Could not parse number"); return; } @@ -808,17 +919,13 @@ void cmd_workspace_number(I3_CMD, char *which) { child->num == parsed_num); if (!workspace) { - LOG("There is no workspace with number %d, creating a new one.\n", parsed_num); + 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)) + if (maybe_back_and_forth(cmd_output, workspace->name)) return; workspace_show(workspace); @@ -888,6 +995,31 @@ void cmd_mark(I3_CMD, char *mark) { ysuccess(true); } +/* + * Implementation of 'unmark [mark]' + * + */ +void cmd_unmark(I3_CMD, char *mark) { + if (mark == NULL) { + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) { + FREE(con->mark); + } + DLOG("removed all window marks"); + } else { + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) { + if (con->mark && strcmp(con->mark, mark) == 0) + FREE(con->mark); + } + DLOG("removed window mark %s\n", mark); + } + + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + ysuccess(true); +} + /* * Implementation of 'mode '. * @@ -923,13 +1055,13 @@ 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_wrap(D_UP, current_output); else if (strcasecmp(name, "down") == 0) - output = get_output_next(D_DOWN, current_output); + output = get_output_next_wrap(D_DOWN, current_output); else if (strcasecmp(name, "left") == 0) - output = get_output_next(D_LEFT, current_output); + output = get_output_next_wrap(D_LEFT, current_output); else if (strcasecmp(name, "right") == 0) - output = get_output_next(D_RIGHT, current_output); + output = get_output_next_wrap(D_RIGHT, current_output); else output = get_output_by_name(name); @@ -1001,9 +1133,14 @@ 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"); + ELOG("Could not get output from string \"%s\"\n", name); ysuccess(false); return; } @@ -1011,8 +1148,12 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { 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); @@ -1047,9 +1188,9 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { /* notify the IPC listeners */ ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); } + DLOG("Detaching\n"); /* 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) { @@ -1071,6 +1212,22 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { /* Focus the moved workspace on the destination output. */ workspace_show(ws); } + + /* NB: We cannot simply work with previously_visible_ws since it might + * have been cleaned up by workspace_show() already, depending on the + * focus order/number of other workspaces on the output. + * Instead, we loop through the available workspaces and only work with + * previously_visible_ws if we still find it. */ + TAILQ_FOREACH(ws, &(content->nodes_head), nodes) { + if (ws != previously_visible_ws) + continue; + + /* 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); + break; + } } cmd_output->needs_tree_render = true; @@ -1083,9 +1240,17 @@ 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 @@ -1093,7 +1258,7 @@ void cmd_split(I3_CMD, char *direction) { } /* - * Implementaiton of 'kill [window|client]'. + * Implementation of 'kill [window|client]'. * */ void cmd_kill(I3_CMD, char *kill_mode_str) { @@ -1148,14 +1313,6 @@ void cmd_exec(I3_CMD, char *nosn, char *command) { * */ 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"); - ysuccess(false); - return; - } - DLOG("direction = *%s*\n", direction); if (strcmp(direction, "left") == 0) @@ -1182,14 +1339,6 @@ void cmd_focus_direction(I3_CMD, char *direction) { * */ 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"); - ysuccess(false); - return; - } - DLOG("window_mode = %s\n", window_mode); Con *ws = con_get_workspace(focused); @@ -1221,23 +1370,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"); - ysuccess(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 - ysuccess(true); + ysuccess(success); } /* @@ -1251,16 +1403,12 @@ void cmd_focus(I3_CMD) { ELOG("You have to specify which window/container should be focused.\n"); ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n"); - y(map_open); - ystr("success"); - y(bool, false); - ystr("error"); - ystr("You have to specify which window/container should be focused"); - y(map_close); + yerror("You have to specify which window/container should be focused"); return; } + Con *__i3_scratch = workspace_get("__i3_scratch", NULL); int count = 0; owindow *current; TAILQ_FOREACH(current, &owindows, owindows) { @@ -1270,17 +1418,23 @@ void cmd_focus(I3_CMD) { if (!ws) continue; - /* Don't allow the focus switch if the focused and current - * containers are in the same workspace. */ - if (focused && - focused->type != CT_WORKSPACE && - focused->fullscreen_mode != CF_NONE && - con_get_workspace(focused) == ws) { - LOG("Cannot change focus while in fullscreen mode (same workspace).\n"); + /* 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; } + /* In case this is a scratchpad window, call scratchpad_show(). */ + if (ws == __i3_scratch) { + scratchpad_show(current->con); + count++; + /* While for the normal focus case we can change focus multiple + * times and only a single window ends up focused, we could show + * multiple scratchpad windows. So, rather break here. */ + break; + } + /* 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 @@ -1326,7 +1480,7 @@ void cmd_fullscreen(I3_CMD, char *fullscreen_mode) { HANDLE_EMPTY_MATCH; TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); + DLOG("matching: %p / %s\n", current->con, current->con->name); con_toggle_fullscreen(current->con, (strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT)); } @@ -1371,21 +1525,35 @@ void cmd_move_direction(I3_CMD, char *direction, char *move_px) { } /* - * 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)); + layout_t 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); @@ -1399,34 +1567,65 @@ void cmd_layout(I3_CMD, char *layout_str) { } /* - * Implementaiton of 'exit'. + * 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); +} + +/* + * Implementation of 'exit'. * */ void cmd_exit(I3_CMD) { LOG("Exiting due to user command.\n"); + xcb_disconnect(conn); exit(0); /* unreached */ } /* - * Implementaiton of 'reload'. + * Implementation of 'reload'. * */ void cmd_reload(I3_CMD) { LOG("reloading\n"); - kill_configerror_nagbar(false); + kill_nagbar(&config_error_nagbar_pid, false); + kill_nagbar(&command_error_nagbar_pid, 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\"}"); + /* Send an update event for the barconfig just in case it has changed */ + update_barconfig(); // XXX: default reply for now, make this a better reply ysuccess(true); } /* - * Implementaiton of 'restart'. + * Implementation of 'restart'. * */ void cmd_restart(I3_CMD) { @@ -1438,12 +1637,13 @@ void cmd_restart(I3_CMD) { } /* - * Implementaiton of 'open'. + * Implementation 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); y(map_open); @@ -1509,12 +1709,7 @@ 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"); - y(map_open); - ystr("success"); - y(bool, false); - ystr("error"); - ystr("Cannot change position. The window/container is not floating."); - y(map_close); + yerror("Cannot change position. The window/container is not floating."); return; } @@ -1549,12 +1744,8 @@ 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); + yerror("Cannot change position. The window/container is not floating."); + return; } if (strcmp(method, "absolute") == 0) { @@ -1626,27 +1817,30 @@ void cmd_scratchpad_show(I3_CMD) { } /* - * Implementation of 'rename workspace to ' + * 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); + if (old_name) { + LOG("Renaming workspace \"%s\" to \"%s\"\n", old_name, new_name); + } else { + LOG("Renaming current workspace to \"%s\"\n", 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 (old_name) { + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) + GREP_FIRST(workspace, output_get_content(output), + !strcasecmp(child->name, old_name)); + } else { + workspace = con_get_workspace(focused); + } 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); + yerror("Old workspace not found"); return; } @@ -1658,13 +1852,8 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *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); + yerror("New workspace already exists"); return; } @@ -1694,3 +1883,164 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) { ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"rename\"}"); } + +/* + * Implementation of 'bar mode dock|hide|invisible|toggle []' + * + */ +bool cmd_bar_mode(char *bar_mode, char *bar_id) { + int mode = M_DOCK; + bool toggle = false; + if (strcmp(bar_mode, "dock") == 0) + mode = M_DOCK; + else if (strcmp(bar_mode, "hide") == 0) + mode = M_HIDE; + else if (strcmp(bar_mode, "invisible") == 0) + mode = M_INVISIBLE; + else if (strcmp(bar_mode, "toggle") == 0) + toggle = true; + else { + ELOG("Unknown bar mode \"%s\", this is a mismatch between code and parser spec.\n", bar_mode); + return false; + } + + bool changed_sth = false; + Barconfig *current = NULL; + TAILQ_FOREACH(current, &barconfigs, configs) { + if (bar_id && strcmp(current->id, bar_id) != 0) + continue; + + if (toggle) + mode = (current->mode + 1) % 2; + + DLOG("Changing bar mode of bar_id '%s' to '%s (%d)'\n", current->id, bar_mode, mode); + current->mode = mode; + changed_sth = true; + + if (bar_id) + break; + } + + if (bar_id && !changed_sth) { + DLOG("Changing bar mode of bar_id %s failed, bar_id not found.\n", bar_id); + return false; + } + + return true; +} + +/* + * Implementation of 'bar hidden_state hide|show|toggle []' + * + */ +bool cmd_bar_hidden_state(char *bar_hidden_state, char *bar_id) { + int hidden_state = S_SHOW; + bool toggle = false; + if (strcmp(bar_hidden_state, "hide") == 0) + hidden_state = S_HIDE; + else if (strcmp(bar_hidden_state, "show") == 0) + hidden_state = S_SHOW; + else if (strcmp(bar_hidden_state, "toggle") == 0) + toggle = true; + else { + ELOG("Unknown bar state \"%s\", this is a mismatch between code and parser spec.\n", bar_hidden_state); + return false; + } + + bool changed_sth = false; + Barconfig *current = NULL; + TAILQ_FOREACH(current, &barconfigs, configs) { + if (bar_id && strcmp(current->id, bar_id) != 0) + continue; + + if (toggle) + hidden_state = (current->hidden_state + 1) % 2; + + DLOG("Changing bar hidden_state of bar_id '%s' to '%s (%d)'\n", current->id, bar_hidden_state, hidden_state); + current->hidden_state = hidden_state; + changed_sth = true; + + if (bar_id) + break; + } + + if (bar_id && !changed_sth) { + DLOG("Changing bar hidden_state of bar_id %s failed, bar_id not found.\n", bar_id); + return false; + } + + return true; +} + +/* + * Implementation of 'bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) []' + * + */ +void cmd_bar(I3_CMD, char *bar_type, char *bar_value, char *bar_id) { + bool ret; + if (strcmp(bar_type, "mode") == 0) + ret = cmd_bar_mode(bar_value, bar_id); + else if (strcmp(bar_type, "hidden_state") == 0) + ret = cmd_bar_hidden_state(bar_value, bar_id); + else { + ELOG("Unknown bar option type \"%s\", this is a mismatch between code and parser spec.\n", bar_type); + ret = false; + } + + ysuccess(ret); + if (!ret) + return; + + update_barconfig(); +} + +/* + * Implementation of 'shmlog |toggle|on|off' + * + */ +void cmd_shmlog(I3_CMD, char *argument) { + if (!strcmp(argument,"toggle")) + /* Toggle shm log, if size is not 0. If it is 0, set it to default. */ + shmlog_size = shmlog_size ? -shmlog_size : default_shmlog_size; + else if (!strcmp(argument, "on")) + shmlog_size = default_shmlog_size; + else if (!strcmp(argument, "off")) + shmlog_size = 0; + else { + /* If shm logging now, restart logging with the new size. */ + if (shmlog_size > 0) { + shmlog_size = 0; + LOG("Restarting shm logging...\n"); + init_logging(); + } + shmlog_size = atoi(argument); + /* Make a weakly attempt at ensuring the argument is valid. */ + if (shmlog_size <= 0) + shmlog_size = default_shmlog_size; + } + LOG("%s shm logging\n", shmlog_size > 0 ? "Enabling" : "Disabling"); + init_logging(); + update_shmlog_atom(); + // XXX: default reply for now, make this a better reply + ysuccess(true); +} + +/* + * Implementation of 'debuglog toggle|on|off' + * + */ +void cmd_debuglog(I3_CMD, char *argument) { + bool logging = get_debug_logging(); + if (!strcmp(argument,"toggle")) { + LOG("%s debug logging\n", logging ? "Disabling" : "Enabling"); + set_debug_logging(!logging); + } else if (!strcmp(argument, "on") && !logging) { + LOG("Enabling debug logging\n"); + set_debug_logging(true); + } else if (!strcmp(argument, "off") && logging) { + LOG("Disabling debug logging\n"); + set_debug_logging(false); + } + // XXX: default reply for now, make this a better reply + ysuccess(true); +}