X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fcommands.c;h=3263dd0394b0d73216151f454e31bbb214f6bb22;hb=67ec2333ee659e4b8ce90e75b94a518a2dee81c0;hp=6d8db6ff6a515c8a156890780d804fd2a46f8b53;hpb=45fa4b7d232ee23ad25802fe3f7c9c3a75fca77e;p=i3%2Fi3 diff --git a/src/commands.c b/src/commands.c index 6d8db6ff..3263dd03 100644 --- a/src/commands.c +++ b/src/commands.c @@ -4,7 +4,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * commands.c: all command functions (see commands_parser.c) * @@ -16,65 +16,53 @@ #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__) -#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) -#define yerror(message) do { \ - y(map_open); \ - ystr("success"); \ - y(bool, false); \ - ystr("error"); \ - ystr(message); \ - y(map_close); \ -} while (0) +#define y(x, ...) (cmd_output->json_gen != NULL ? yajl_gen_##x(cmd_output->json_gen, ##__VA_ARGS__) : 0) +#define ystr(str) (cmd_output->json_gen != NULL ? yajl_gen_string(cmd_output->json_gen, (unsigned char *)str, strlen(str)) : 0) +#define ysuccess(success) \ + do { \ + if (cmd_output->json_gen != NULL) { \ + y(map_open); \ + ystr("success"); \ + y(bool, success); \ + y(map_close); \ + } \ + } while (0) +#define yerror(format, ...) \ + do { \ + if (cmd_output->json_gen != NULL) { \ + char *message; \ + sasprintf(&message, format, ##__VA_ARGS__); \ + y(map_open); \ + ystr("success"); \ + y(bool, false); \ + ystr("error"); \ + ystr(message); \ + y(map_close); \ + free(message); \ + } \ + } while (0) /** When the command did not include match criteria (!), we use the currently * 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. */ -#define HANDLE_EMPTY_MATCH do { \ - if (match_is_empty(current_match)) { \ - owindow *ow = smalloc(sizeof(owindow)); \ - ow->con = focused; \ - TAILQ_INIT(&owindows); \ - TAILQ_INSERT_TAIL(&owindows, ow, owindows); \ - } \ -} while (0) - +#define HANDLE_EMPTY_MATCH \ + do { \ + if (match_is_empty(current_match)) { \ + owindow *ow = smalloc(sizeof(owindow)); \ + ow->con = focused; \ + TAILQ_INIT(&owindows); \ + TAILQ_INSERT_TAIL(&owindows, ow, owindows); \ + } \ + } while (0) /* * Returns true if a is definitely greater than b (using the given epsilon) * */ 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; - - 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; + return (a - b) > ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon); } /* @@ -354,8 +342,8 @@ void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) { (end && *end != '\0')) { ELOG("Could not parse con id \"%s\"\n", cvalue); } else { - current_match->con_id = (Con*)parsed; - printf("id as int = %p\n", current_match->con_id); + current_match->con_id = (Con *)parsed; + DLOG("id as int = %p\n", current_match->con_id); } return; } @@ -370,11 +358,36 @@ void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) { ELOG("Could not parse window id \"%s\"\n", cvalue); } else { current_match->id = parsed; - printf("window id as int = %d\n", current_match->id); + DLOG("window id as int = %d\n", current_match->id); } return; } + if (strcmp(ctype, "window_type") == 0) { + if (strcasecmp(cvalue, "normal") == 0) + current_match->window_type = A__NET_WM_WINDOW_TYPE_NORMAL; + else if (strcasecmp(cvalue, "dialog") == 0) + current_match->window_type = A__NET_WM_WINDOW_TYPE_DIALOG; + else if (strcasecmp(cvalue, "utility") == 0) + current_match->window_type = A__NET_WM_WINDOW_TYPE_UTILITY; + else if (strcasecmp(cvalue, "toolbar") == 0) + current_match->window_type = A__NET_WM_WINDOW_TYPE_TOOLBAR; + else if (strcasecmp(cvalue, "splash") == 0) + current_match->window_type = A__NET_WM_WINDOW_TYPE_SPLASH; + else if (strcasecmp(cvalue, "menu") == 0) + current_match->window_type = A__NET_WM_WINDOW_TYPE_MENU; + else if (strcasecmp(cvalue, "dropdown_menu") == 0) + current_match->window_type = A__NET_WM_WINDOW_TYPE_DROPDOWN_MENU; + else if (strcasecmp(cvalue, "popup_menu") == 0) + current_match->window_type = A__NET_WM_WINDOW_TYPE_POPUP_MENU; + else if (strcasecmp(cvalue, "tooltip") == 0) + current_match->window_type = A__NET_WM_WINDOW_TYPE_TOOLTIP; + else + ELOG("unknown window_type value \"%s\"\n", cvalue); + + return; + } + if (strcmp(ctype, "con_mark") == 0) { current_match->mark = regex_new(cvalue); return; @@ -416,7 +429,7 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) { * 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))) { + !con_has_children(focused))) { ysuccess(false); return; } @@ -498,9 +511,8 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) { 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)) { + } else if (match_is_empty(current_match) && focused->type == CT_WORKSPACE && + !con_has_children(focused)) { ysuccess(false); return; } @@ -535,7 +547,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { * 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))) { + !con_has_children(focused))) { ysuccess(false); return; } @@ -544,21 +556,17 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { /* get the workspace */ Con *output, *workspace = NULL; - char *endptr = NULL; - long parsed_num = strtol(which, &endptr, 10); - if (parsed_num == LONG_MIN || - parsed_num == LONG_MAX || - parsed_num < 0 || - endptr == which) { + long parsed_num = ws_name_to_number(which); + + if (parsed_num == -1) { LOG("Could not parse initial part of \"%s\" as a number.\n", which); - // TODO: better error message - yerror("Could not parse number"); + yerror("Could not parse number \"%s\"", which); return; } TAILQ_FOREACH(output, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(output), - child->num == parsed_num); + GREP_FIRST(workspace, output_get_content(output), + child->num == parsed_num); if (!workspace) { workspace = workspace_get(which, NULL); @@ -811,7 +819,7 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz * Implementation of 'border normal|none|1pixel|toggle|pixel'. * */ -void cmd_border(I3_CMD, char *border_style_str, char *border_width ) { +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; @@ -841,7 +849,7 @@ void cmd_border(I3_CMD, char *border_style_str, char *border_width ) { border_style = BS_NORMAL; else if (strcmp(border_style_str, "pixel") == 0) border_style = BS_PIXEL; - else if (strcmp(border_style_str, "1pixel") == 0){ + else if (strcmp(border_style_str, "1pixel") == 0) { border_style = BS_PIXEL; tmp_border_width = 1; } else if (strcmp(border_style_str, "none") == 0) @@ -876,15 +884,31 @@ void cmd_nop(I3_CMD, char *comment) { */ void cmd_append_layout(I3_CMD, char *path) { LOG("Appending layout \"%s\"\n", path); + + /* Make sure we allow paths like '~/.i3/layout.json' */ + path = resolve_tilde(path); + + json_content_t content = json_determine_content(path); + LOG("JSON content = %d\n", content); + if (content == JSON_CONTENT_UNKNOWN) { + ELOG("Could not determine the contents of \"%s\", not loading.\n", path); + yerror("Could not determine the contents of \"%s\".", path); + free(path); + return; + } + Con *parent = focused; - /* We need to append the layout to a split container, since a leaf - * container must not have any children (by definition). - * Note that we explicitly check for workspaces, since they are okay for - * this purpose, but con_accepts_window() returns false for workspaces. */ - while (parent->type != CT_WORKSPACE && !con_accepts_window(parent)) - parent = parent->parent; - DLOG("Appending to parent=%p instead of focused=%p\n", - parent, focused); + if (content == JSON_CONTENT_WORKSPACE) { + parent = output_get_content(con_get_output(parent)); + } else { + /* We need to append the layout to a split container, since a leaf + * container must not have any children (by definition). + * Note that we explicitly check for workspaces, since they are okay for + * this purpose, but con_accepts_window() returns false for workspaces. */ + while (parent->type != CT_WORKSPACE && !con_accepts_window(parent)) + parent = parent->parent; + } + DLOG("Appending to parent=%p instead of focused=%p\n", parent, focused); char *errormsg = NULL; tree_append_json(parent, path, &errormsg); if (errormsg != NULL) { @@ -908,6 +932,10 @@ void cmd_append_layout(I3_CMD, char *path) { restore_open_placeholder_windows(parent); + if (content == JSON_CONTENT_WORKSPACE) + ipc_send_workspace_event("restored", parent, NULL); + + free(path); cmd_output->needs_tree_render = true; } @@ -920,6 +948,12 @@ void cmd_workspace(I3_CMD, char *which) { DLOG("which=%s\n", which); + if (con_get_fullscreen_con(croot, CF_GLOBAL)) { + LOG("Cannot switch workspace while in global fullscreen\n"); + ysuccess(false); + return; + } + if (strcmp(which, "next") == 0) ws = workspace_next(); else if (strcmp(which, "prev") == 0) @@ -948,22 +982,23 @@ void cmd_workspace(I3_CMD, char *which) { 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 == which) { - LOG("Could not parse initial part of \"%s\" as a number.\n", which); - // TODO: better error message - yerror("Could not parse number"); + if (con_get_fullscreen_con(croot, CF_GLOBAL)) { + LOG("Cannot switch workspace while in global fullscreen\n"); + ysuccess(false); + return; + } + long parsed_num = ws_name_to_number(which); + + if (parsed_num == -1) { + LOG("Could not parse initial part of \"%s\" as a number.\n", which); + yerror("Could not parse number \"%s\"", which); return; } TAILQ_FOREACH(output, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(output), - child->num == parsed_num); + 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); @@ -986,6 +1021,12 @@ void cmd_workspace_number(I3_CMD, char *which) { * */ void cmd_workspace_back_and_forth(I3_CMD) { + if (con_get_fullscreen_con(croot, CF_GLOBAL)) { + LOG("Cannot switch workspace while in global fullscreen\n"); + ysuccess(false); + return; + } + workspace_back_and_forth(); cmd_output->needs_tree_render = true; @@ -1004,9 +1045,15 @@ void cmd_workspace_name(I3_CMD, char *name) { return; } + if (con_get_fullscreen_con(croot, CF_GLOBAL)) { + LOG("Cannot switch workspace while in global fullscreen\n"); + ysuccess(false); + return; + } + DLOG("should switch to workspace %s\n", name); if (maybe_back_and_forth(cmd_output, name)) - return; + return; workspace_show_by_name(name); cmd_output->needs_tree_render = true; @@ -1015,28 +1062,48 @@ void cmd_workspace_name(I3_CMD, char *name) { } /* - * Implementation of 'mark ' + * Implementation of 'mark [--toggle] ' * */ -void cmd_mark(I3_CMD, char *mark) { - DLOG("Clearing all windows which have that mark first\n"); +void cmd_mark(I3_CMD, char *mark, char *toggle) { + HANDLE_EMPTY_MATCH; - Con *con; - TAILQ_FOREACH(con, &all_cons, all_cons) { - if (con->mark && strcmp(con->mark, mark) == 0) - FREE(con->mark); + owindow *current = TAILQ_FIRST(&owindows); + if (current == NULL) { + ysuccess(false); + return; } - DLOG("marking window with str %s\n", mark); - owindow *current; - - HANDLE_EMPTY_MATCH; + /* Marks must be unique, i.e., no two windows must have the same mark. */ + if (current != TAILQ_LAST(&owindows, owindows_head)) { + yerror("A mark must not be put onto more than one window"); + return; + } - TAILQ_FOREACH(current, &owindows, owindows) { - DLOG("matching: %p / %s\n", current->con, current->con->name); + DLOG("matching: %p / %s\n", current->con, current->con->name); + current->con->mark_changed = true; + if (toggle != NULL && current->con->mark && strcmp(current->con->mark, mark) == 0) { + DLOG("removing window mark %s\n", mark); + FREE(current->con->mark); + } else { + DLOG("marking window with str %s\n", mark); + FREE(current->con->mark); current->con->mark = sstrdup(mark); } + DLOG("Clearing all non-matched windows with this mark\n"); + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) { + /* Skip matched window, we took care of it already. */ + if (current->con == con) + continue; + + if (con->mark && strcmp(con->mark, mark) == 0) { + FREE(con->mark); + con->mark_changed = true; + } + } + cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply ysuccess(true); @@ -1047,19 +1114,23 @@ void cmd_mark(I3_CMD, char *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); + if (mark == NULL) { + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) { + if (con->mark == NULL) + continue; + + FREE(con->mark); + con->mark_changed = true; + } + DLOG("removed all window marks"); + } else { + Con *con = con_by_mark(mark); + if (con != NULL) { + FREE(con->mark); + con->mark_changed = true; + } + DLOG("removed window mark %s\n", mark); } cmd_output->needs_tree_render = true; @@ -1090,28 +1161,13 @@ void cmd_move_con_to_output(I3_CMD, char *name) { HANDLE_EMPTY_MATCH; - /* get the output */ Output *current_output = NULL; - Output *output; - // TODO: fix the handling of criteria TAILQ_FOREACH(current, &owindows, owindows) - current_output = get_output_of_con(current->con); - + current_output = get_output_of_con(current->con); assert(current_output != NULL); - // 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_wrap(D_UP, current_output); - else if (strcasecmp(name, "down") == 0) - output = get_output_next_wrap(D_DOWN, current_output); - else if (strcasecmp(name, "left") == 0) - output = get_output_next_wrap(D_LEFT, current_output); - else if (strcasecmp(name, "right") == 0) - output = get_output_next_wrap(D_RIGHT, current_output); - else - output = get_output_by_name(name); - + Output *output = get_output_from_string(current_output, name); if (!output) { LOG("No such output found.\n"); ysuccess(false); @@ -1136,6 +1192,26 @@ void cmd_move_con_to_output(I3_CMD, char *name) { ysuccess(true); } +/* + * Implementation of 'move [container|window] [to] mark '. + * + */ +void cmd_move_con_to_mark(I3_CMD, char *mark) { + DLOG("moving window to mark \"%s\"\n", mark); + + HANDLE_EMPTY_MATCH; + + bool result = true; + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("moving matched window %p / %s to mark \"%s\"\n", current->con, current->con->name, mark); + result &= con_move_to_mark(current->con, mark); + } + + cmd_output->needs_tree_render = true; + ysuccess(result); +} + /* * Implementation of 'floating enable|disable|toggle' * @@ -1178,102 +1254,13 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { owindow *current; TAILQ_FOREACH(current, &owindows, owindows) { - Output *current_output = get_output_of_con(current->con); - 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) { - ELOG("Could not get output from string \"%s\"\n", name); + Con *ws = con_get_workspace(current->con); + bool success = workspace_move_to_output(ws, name); + if (!success) { + ELOG("Failed to move workspace to output.\n"); ysuccess(false); return; } - - Con *content = output_get_content(output->con); - LOG("got output %p with content %p\n", output, content); - - Con *previously_visible_ws = TAILQ_FIRST(&(content->nodes_head)); - LOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name); - - Con *ws = con_get_workspace(current->con); - LOG("should move workspace %p / %s\n", ws, ws->name); - bool workspace_was_visible = workspace_is_visible(ws); - - if (con_num_children(ws->parent) == 1) { - LOG("Creating a new workspace to replace \"%s\" (last on its output).\n", ws->name); - - /* 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\"}"); - } - DLOG("Detaching\n"); - - /* detach from the old output and attach to the new output */ - Con *old_content = ws->parent; - con_detach(ws); - if (workspace_was_visible) { - /* The workspace which we just detached was visible, so focus - * the next one in the focus-stack. */ - Con *focus_ws = TAILQ_FIRST(&(old_content->focus_head)); - LOG("workspace was visible, focusing %p / %s now\n", focus_ws, focus_ws->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. */ - 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; @@ -1394,7 +1381,8 @@ void cmd_focus_window_mode(I3_CMD, char *window_mode) { current = TAILQ_FIRST(&(ws->focus_head)); if (current != NULL && current->type == CT_FLOATING_CON) window_mode = "tiling"; - else window_mode = "floating"; + else + window_mode = "floating"; } TAILQ_FOREACH(current, &(ws->focus_head), focused) { if ((strcmp(window_mode, "floating") == 0 && current->type != CT_FLOATING_CON) || @@ -1431,7 +1419,8 @@ void cmd_focus_level(I3_CMD, char *level) { } /* Focusing a child should always be allowed. */ - else success = level_down(); + else + success = level_down(); cmd_output->needs_tree_render = success; // XXX: default reply for now, make this a better reply @@ -1506,7 +1495,8 @@ void cmd_focus(I3_CMD) { if (count > 1) LOG("WARNING: Your criteria for the focus command matches %d containers, " - "while only exactly one container can be focused at a time.\n", count); + "while only exactly one container can be focused at a time.\n", + count); cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply @@ -1514,20 +1504,26 @@ void cmd_focus(I3_CMD) { } /* - * Implementation of 'fullscreen [global]'. + * Implementation of 'fullscreen enable|toggle [global]' and + * 'fullscreen disable' * */ -void cmd_fullscreen(I3_CMD, char *fullscreen_mode) { - if (fullscreen_mode == NULL) - fullscreen_mode = "output"; - DLOG("toggling fullscreen, mode = %s\n", fullscreen_mode); +void cmd_fullscreen(I3_CMD, char *action, char *fullscreen_mode) { + fullscreen_mode_t mode = strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT; + DLOG("%s fullscreen, mode = %s\n", action, fullscreen_mode); owindow *current; HANDLE_EMPTY_MATCH; TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); - con_toggle_fullscreen(current->con, (strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT)); + if (strcmp(action, "toggle") == 0) { + con_toggle_fullscreen(current->con, mode); + } else if (strcmp(action, "enable") == 0) { + con_enable_fullscreen(current->con, mode); + } else if (strcmp(action, "disable") == 0) { + con_disable_fullscreen(current->con); + } } cmd_output->needs_tree_render = true; @@ -1543,29 +1539,36 @@ 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); - /* TODO: make 'move' work with criteria. */ - DLOG("moving in direction %s, px %s\n", direction, move_px); - if (con_is_floating(focused)) { - DLOG("floating move with %d pixels\n", px); - Rect newrect = focused->parent->rect; - if (strcmp(direction, "left") == 0) { - newrect.x -= px; - } else if (strcmp(direction, "right") == 0) { - newrect.x += px; - } else if (strcmp(direction, "up") == 0) { - newrect.y -= px; - } else if (strcmp(direction, "down") == 0) { - newrect.y += px; + owindow *current; + HANDLE_EMPTY_MATCH; + + Con *initially_focused = focused; + + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("moving in direction %s, px %s\n", direction, move_px); + if (con_is_floating(current->con)) { + DLOG("floating move with %d pixels\n", px); + Rect newrect = current->con->parent->rect; + if (strcmp(direction, "left") == 0) { + newrect.x -= px; + } else if (strcmp(direction, "right") == 0) { + newrect.x += px; + } else if (strcmp(direction, "up") == 0) { + newrect.y -= px; + } else if (strcmp(direction, "down") == 0) { + newrect.y += px; + } + floating_reposition(current->con->parent, newrect); + } else { + tree_move(current->con, (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; } - floating_reposition(focused->parent, newrect); - } else { - 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; } + /* the move command should not disturb focus */ + if (focused != initially_focused) + con_focus(initially_focused); + // XXX: default reply for now, make this a better reply ysuccess(true); } @@ -1645,6 +1648,8 @@ void cmd_layout_toggle(I3_CMD, char *toggle_mode) { */ void cmd_exit(I3_CMD) { LOG("Exiting due to user command.\n"); + ipc_shutdown(); + unlink(config.ipc_socket_path); xcb_disconnect(conn); exit(0); @@ -1662,7 +1667,7 @@ void cmd_reload(I3_CMD) { 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\"}"); + ipc_send_workspace_event("reload", NULL, NULL); /* Send an update event for the barconfig just in case it has changed */ update_barconfig(); @@ -1676,6 +1681,11 @@ void cmd_reload(I3_CMD) { */ void cmd_restart(I3_CMD) { LOG("restarting i3\n"); + ipc_shutdown(); + unlink(config.ipc_socket_path); + /* We need to call this manually since atexit handlers don’t get called + * when exec()ing */ + purge_zerobyte_logfile(); i3_restart(false); // XXX: default reply for now, make this a better reply @@ -1718,7 +1728,7 @@ void cmd_focus_output(I3_CMD, char *name) { Output *output; TAILQ_FOREACH(current, &owindows, owindows) - current_output = get_output_of_con(current->con); + current_output = get_output_of_con(current->con); assert(current_output != NULL); output = get_output_from_string(current_output, name); @@ -1749,37 +1759,48 @@ void cmd_focus_output(I3_CMD, char *name) { * */ void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) { - int x = atoi(cx); int y = atoi(cy); + bool has_error = false; - if (!con_is_floating(focused)) { - ELOG("Cannot change position. The window/container is not floating\n"); - yerror("Cannot change position. The window/container is not floating."); - return; - } + owindow *current; + HANDLE_EMPTY_MATCH; - if (strcmp(method, "absolute") == 0) { - focused->parent->rect.x = x; - focused->parent->rect.y = y; + TAILQ_FOREACH(current, &owindows, owindows) { + if (!con_is_floating(current->con)) { + ELOG("Cannot change position. The window/container is not floating\n"); - DLOG("moving to absolute position %d %d\n", x, y); - floating_maybe_reassign_ws(focused->parent); - cmd_output->needs_tree_render = true; - } + if (!has_error) { + yerror("Cannot change position of a window/container because it is not floating."); + has_error = true; + } - if (strcmp(method, "position") == 0) { - Rect newrect = focused->parent->rect; + continue; + } + + if (strcmp(method, "absolute") == 0) { + current->con->parent->rect.x = x; + current->con->parent->rect.y = y; + + DLOG("moving to absolute position %d %d\n", x, y); + floating_maybe_reassign_ws(current->con->parent); + cmd_output->needs_tree_render = true; + } + + if (strcmp(method, "position") == 0) { + Rect newrect = current->con->parent->rect; - DLOG("moving to position %d %d\n", x, y); - newrect.x = x; - newrect.y = y; + DLOG("moving to position %d %d\n", x, y); + newrect.x = x; + newrect.y = y; - floating_reposition(focused->parent, newrect); + floating_reposition(current->con->parent, newrect); + } } // XXX: default reply for now, make this a better reply - ysuccess(true); + if (!has_error) + ysuccess(true); } /* @@ -1787,7 +1808,6 @@ void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) { * */ 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"); yerror("Cannot change position. The window/container is not floating."); @@ -1795,25 +1815,18 @@ void cmd_move_window_to_center(I3_CMD, char *method) { } 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_center(focused->parent, croot->rect); 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_center(focused->parent, con_get_workspace(focused)->rect); - floating_reposition(focused->parent, newrect); + cmd_output->needs_tree_render = true; } // XXX: default reply for now, make this a better reply @@ -1881,44 +1894,33 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) { Con *output, *workspace = NULL; if (old_name) { TAILQ_FOREACH(output, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(output), - !strcasecmp(child->name, old_name)); + GREP_FIRST(workspace, output_get_content(output), + !strcasecmp(child->name, old_name)); } else { workspace = con_get_workspace(focused); + old_name = workspace->name; } if (!workspace) { - // TODO: we should include the old workspace name here and use yajl for - // generating the reply. - // TODO: better error message - yerror("Old workspace not found"); + yerror("Old workspace \"%s\" not found", old_name); 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)); + 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. - // TODO: better error message - yerror("New workspace already exists"); + yerror("New workspace \"%s\" already exists", new_name); 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; + + workspace->num = ws_name_to_number(new_name); LOG("num = %d\n", workspace->num); /* By re-attaching, the sort order will be correct afterwards. */ @@ -1926,13 +1928,36 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) { Con *parent = workspace->parent; con_detach(workspace); con_attach(workspace, parent, false); + + /* Move the workspace to the correct output if it has an assignment */ + struct Workspace_Assignment *assignment = NULL; + TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { + if (assignment->output == NULL) + continue; + if (strcmp(assignment->name, workspace->name) != 0 && (!name_is_digits(assignment->name) || ws_name_to_number(assignment->name) != workspace->num)) { + continue; + } + + workspace_move_to_output(workspace, assignment->output); + + if (previously_focused) + workspace_show(con_get_workspace(previously_focused)); + + break; + } + /* 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\"}"); + ipc_send_workspace_event("rename", workspace, NULL); + ewmh_update_desktop_names(); + ewmh_update_desktop_viewport(); + ewmh_update_current_desktop(); + + startup_sequence_rename_workspace(old_name, new_name); } /* @@ -1969,7 +1994,7 @@ bool cmd_bar_mode(char *bar_mode, char *bar_id) { changed_sth = true; if (bar_id) - break; + break; } if (bar_id && !changed_sth) { @@ -2012,7 +2037,7 @@ bool cmd_bar_hidden_state(char *bar_hidden_state, char *bar_id) { changed_sth = true; if (bar_id) - break; + break; } if (bar_id && !changed_sth) { @@ -2050,7 +2075,7 @@ void cmd_bar(I3_CMD, char *bar_type, char *bar_value, char *bar_id) { * */ void cmd_shmlog(I3_CMD, char *argument) { - if (!strcmp(argument,"toggle")) + 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")) @@ -2082,7 +2107,7 @@ void cmd_shmlog(I3_CMD, char *argument) { */ void cmd_debuglog(I3_CMD, char *argument) { bool logging = get_debug_logging(); - if (!strcmp(argument,"toggle")) { + if (!strcmp(argument, "toggle")) { LOG("%s debug logging\n", logging ? "Disabling" : "Enabling"); set_debug_logging(!logging); } else if (!strcmp(argument, "on") && !logging) {