From f81c89ac2866bf1fc53900536f5ca9fe8cf7ddc9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 14 Jan 2012 15:02:30 +0000 Subject: [PATCH] Refactor the code out of src/cmdparse.y to src/commands.c This is the first step towards our new parser. --- include/all.h | 1 + include/commands.h | 61 +++ src/cmdparse.y | 866 ++++------------------------------ src/commands.c | 1002 ++++++++++++++++++++++++++++++++++++++++ testcases/t/124-move.t | 2 +- 5 files changed, 1153 insertions(+), 779 deletions(-) create mode 100644 include/commands.h create mode 100644 src/commands.c diff --git a/include/all.h b/include/all.h index 72c8c8f4..9942fed9 100644 --- a/include/all.h +++ b/include/all.h @@ -73,5 +73,6 @@ #include "libi3.h" #include "startup.h" #include "scratchpad.h" +#include "commands.h" #endif diff --git a/include/commands.h b/include/commands.h new file mode 100644 index 00000000..a28b799f --- /dev/null +++ b/include/commands.h @@ -0,0 +1,61 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) + * + * commands.c: all command functions (see commands_parser.c) + * + */ +#ifndef _COMMANDS_H +#define _COMMANDS_H + +/* + * 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; + +char *cmd_criteria_init(Match *current_match); +char *cmd_criteria_match_windows(Match *current_match); +char *cmd_criteria_add(Match *current_match, char *ctype, char *cvalue); + +char *cmd_move_con_to_workspace(Match *current_match, char *which); +char *cmd_move_con_to_workspace_name(Match *current_match, char *name); +char *cmd_resize(Match *current_match, char *way, char *direction, char *resize_px, char *resize_ppt); +char *cmd_border(Match *current_match, char *border_style_str); +char *cmd_nop(Match *current_match, char *comment); +char *cmd_append_layout(Match *current_match, char *path); +char *cmd_workspace(Match *current_match, char *which); +char *cmd_workspace_back_and_forth(Match *current_match); +char *cmd_workspace_name(Match *current_match, char *name); +char *cmd_mark(Match *current_match, char *mark); +char *cmd_mode(Match *current_match, char *mode); +char *cmd_move_con_to_output(Match *current_match, char *name); +char *cmd_floating(Match *current_match, char *floating_mode); +char *cmd_move_workspace_to_output(Match *current_match, char *name); +char *cmd_split(Match *current_match, char *direction); +char *cmd_kill(Match *current_match, char *kill_mode); +char *cmd_exec(Match *current_match, char *nosn, char *command); +char *cmd_focus_direction(Match *current_match, char *direction); +char *cmd_focus_window_mode(Match *current_match, char *window_mode); +char *cmd_focus_level(Match *current_match, char *level); +char *cmd_focus(Match *current_match); +char *cmd_fullscreen(Match *current_match, char *fullscreen_mode); +char *cmd_move_direction(Match *current_match, char *direction, char *px); +char *cmd_layout(Match *current_match, char *layout); +char *cmd_exit(Match *current_match); +char *cmd_reload(Match *current_match); +char *cmd_restart(Match *current_match); +char *cmd_open(Match *current_match); +char *cmd_focus_output(Match *current_match, char *name); +char *cmd_move_scratchpad(Match *current_match); +char *cmd_scratchpad_show(Match *current_match); + +#endif diff --git a/src/cmdparse.y b/src/cmdparse.y index 9ae17ff9..5400d765 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -16,20 +16,6 @@ #include "all.h" -/** When the command did not include match criteria (!), we use the currently - * focused command. 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(¤t_match)) { \ - owindow *ow = smalloc(sizeof(owindow)); \ - ow->con = focused; \ - TAILQ_INIT(&owindows); \ - TAILQ_INSERT_TAIL(&owindows, ow, owindows); \ - } \ -} while (0) - typedef struct yy_buffer_state *YY_BUFFER_STATE; extern int cmdyylex(struct context *context); extern int cmdyyparse(void); @@ -40,17 +26,6 @@ YY_BUFFER_STATE cmdyy_scan_string(const char *); static struct context *context; static Match current_match; -/* - * 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; -static TAILQ_HEAD(owindows_head, owindow) owindows; - /* Holds the JSON which will be returned via IPC or NULL for the default return * message */ static char *json_output; @@ -84,7 +59,7 @@ char *parse_cmd(const char *new) { LOG("COMMAND: *%s*\n", new); cmdyy_scan_string(new); - match_init(¤t_match); + cmd_criteria_init(¤t_match); context = scalloc(sizeof(struct context)); context->filename = "cmd"; if (cmdyyparse() != 0) { @@ -105,39 +80,6 @@ char *parse_cmd(const char *new) { return json_output; } -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); - - return output; -} - - -/* - * Returns true if a is definitely greater than b (using the given epsilon) - * - */ -bool definitelyGreaterThan(float a, float b, float epsilon) { - return (a - b) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon); -} - %} %error-verbose @@ -237,15 +179,7 @@ commands: commands ';' command | command { - owindow *current; - - printf("single command completely parsed, dropping state...\n"); - while (!TAILQ_EMPTY(&owindows)) { - current = TAILQ_FIRST(&owindows); - TAILQ_REMOVE(&owindows, current, owindows); - free(current); - } - match_init(¤t_match); + cmd_criteria_init(¤t_match); } ; @@ -255,72 +189,16 @@ command: match: | matchstart criteria matchend - { - printf("match parsed\n"); - } ; matchstart: '[' - { - printf("start\n"); - match_init(¤t_match); - TAILQ_INIT(&owindows); - /* copy all_cons */ - Con *con; - TAILQ_FOREACH(con, &all_cons, all_cons) { - owindow *ow = smalloc(sizeof(owindow)); - ow->con = con; - TAILQ_INSERT_TAIL(&owindows, ow, owindows); - } - } ; matchend: ']' { - owindow *next, *current; - - printf("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 */ - struct owindows_head old = owindows; - TAILQ_INIT(&owindows); - for (next = TAILQ_FIRST(&old); next != TAILQ_END(&old);) { - /* make a copy of the next pointer and advance the pointer to the - * next element as we are going to invalidate the element’s - * next/prev pointers by calling TAILQ_INSERT_TAIL later */ - current = next; - next = TAILQ_NEXT(next, owindows); - - printf("checking if con %p / %s matches\n", current->con, current->con->name); - if (current_match.con_id != NULL) { - if (current_match.con_id == current->con) { - printf("matches container!\n"); - TAILQ_INSERT_TAIL(&owindows, current, owindows); - - } - } else if (current_match.mark != NULL && current->con->mark != NULL && - regex_matches(current_match.mark, current->con->mark)) { - printf("match by mark\n"); - TAILQ_INSERT_TAIL(&owindows, current, owindows); - } else { - if (current->con->window == NULL) - continue; - if (match_matches_window(¤t_match, current->con->window)) { - printf("matches window!\n"); - TAILQ_INSERT_TAIL(&owindows, current, owindows); - } else { - printf("doesnt match\n"); - free(current); - } - } - } - - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - } - + json_output = cmd_criteria_match_windows(¤t_match); } ; @@ -332,62 +210,37 @@ criteria: criterion: TOK_CLASS '=' STR { - printf("criteria: class = %s\n", $3); - current_match.class = regex_new($3); + cmd_criteria_add(¤t_match, "class", $3); free($3); } | TOK_INSTANCE '=' STR { - printf("criteria: instance = %s\n", $3); - current_match.instance = regex_new($3); + cmd_criteria_add(¤t_match, "instance", $3); free($3); } | TOK_WINDOW_ROLE '=' STR { - printf("criteria: window_role = %s\n", $3); - current_match.role = regex_new($3); + cmd_criteria_add(¤t_match, "window_role", $3); free($3); } | TOK_CON_ID '=' STR { - printf("criteria: id = %s\n", $3); - char *end; - long parsed = strtol($3, &end, 10); - if (parsed == LONG_MIN || - parsed == LONG_MAX || - parsed < 0 || - (end && *end != '\0')) { - ELOG("Could not parse con id \"%s\"\n", $3); - } else { - current_match.con_id = (Con*)parsed; - printf("id as int = %p\n", current_match.con_id); - } + cmd_criteria_add(¤t_match, "con_id", $3); + free($3); } | TOK_ID '=' STR { - printf("criteria: window id = %s\n", $3); - char *end; - long parsed = strtol($3, &end, 10); - if (parsed == LONG_MIN || - parsed == LONG_MAX || - parsed < 0 || - (end && *end != '\0')) { - ELOG("Could not parse window id \"%s\"\n", $3); - } else { - current_match.id = parsed; - printf("window id as int = %d\n", current_match.id); - } + cmd_criteria_add(¤t_match, "id", $3); + free($3); } | TOK_MARK '=' STR { - printf("criteria: mark = %s\n", $3); - current_match.mark = regex_new($3); + cmd_criteria_add(¤t_match, "con_mark", $3); free($3); } | TOK_TITLE '=' STR { - printf("criteria: title = %s\n", $3); - current_match.title = regex_new($3); + cmd_criteria_add(¤t_match, "title", $3); free($3); } ; @@ -423,11 +276,7 @@ operation: exec: TOK_EXEC optional_no_startup_id STR { - char *command = $3; - bool no_startup_id = $2; - - printf("should execute %s, no_startup_id = %d\n", command, no_startup_id); - start_application(command, no_startup_id); + json_output = cmd_exec(¤t_match, ($2 ? "nosn" : NULL), $3); free($3); } ; @@ -440,208 +289,53 @@ optional_no_startup_id: exit: TOK_EXIT { - printf("exit, bye bye\n"); - exit(0); + json_output = cmd_exit(¤t_match); } ; reload: TOK_RELOAD { - printf("reloading\n"); - kill_configerror_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\"}"); + json_output = cmd_reload(¤t_match); } ; restart: TOK_RESTART { - printf("restarting i3\n"); - i3_restart(false); + json_output = cmd_restart(¤t_match); } ; focus: TOK_FOCUS { - if (focused && - focused->type != CT_WORKSPACE && - focused->fullscreen_mode != CF_NONE) { - LOG("Cannot change focus while in fullscreen mode.\n"); - break; - } - - owindow *current; - - if (match_is_empty(¤t_match)) { - ELOG("You have to specify which window/container should be focused.\n"); - ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n"); - - sasprintf(&json_output, "{\"success\":false, \"error\":\"You have to " - "specify which window/container should be focused\"}"); - break; - } - - int count = 0; - TAILQ_FOREACH(current, &owindows, owindows) { - Con *ws = con_get_workspace(current->con); - /* If no workspace could be found, this was a dock window. - * Just skip it, you cannot focus dock windows. */ - if (!ws) - continue; - - /* 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 - * container (!) on the target workspace. - * - * Therefore, before calling workspace_show(), we make sure that - * 'current' will be focused on the workspace. However, we cannot - * just con_focus(current) because then the pointer will not be - * warped at all (the code thinks we are already there). - * - * So we focus 'current' to make it the currently focused window of - * the target workspace, then revert focus. */ - Con *currently_focused = focused; - con_focus(current->con); - con_focus(currently_focused); - - /* Now switch to the workspace, then focus */ - workspace_show(ws); - LOG("focusing %p / %s\n", current->con, current->con->name); - con_focus(current->con); - count++; - } - - 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); - - tree_render(); + json_output = cmd_focus(¤t_match); } | TOK_FOCUS direction { - if (focused && - focused->type != CT_WORKSPACE && - focused->fullscreen_mode != CF_NONE) { - LOG("Cannot change focus while in fullscreen mode.\n"); - break; - } - - int direction = $2; - switch (direction) { - case TOK_LEFT: - LOG("Focusing left\n"); - tree_next('p', HORIZ); - break; - case TOK_RIGHT: - LOG("Focusing right\n"); - tree_next('n', HORIZ); - break; - case TOK_UP: - LOG("Focusing up\n"); - tree_next('p', VERT); - break; - case TOK_DOWN: - LOG("Focusing down\n"); - tree_next('n', VERT); - break; - default: - ELOG("Invalid focus direction (%d)\n", direction); - break; - } - - tree_render(); + json_output = cmd_focus_direction(¤t_match, + ($2 == TOK_LEFT ? "left" : + ($2 == TOK_RIGHT ? "right" : + ($2 == TOK_UP ? "up" : + "down")))); } | TOK_FOCUS TOK_OUTPUT STR { - owindow *current; - - HANDLE_EMPTY_MATCH; - - /* get the output */ - Output *current_output = NULL; - Output *output; - - TAILQ_FOREACH(current, &owindows, owindows) - current_output = get_output_containing(current->con->rect.x, current->con->rect.y); - assert(current_output != NULL); - - output = get_output_from_string(current_output, $3); - + json_output = cmd_focus_output(¤t_match, $3); free($3); - - if (!output) { - printf("No such output found.\n"); - break; - } - - /* get visible workspace on output */ - Con *ws = NULL; - GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child)); - if (!ws) - break; - - workspace_show(ws); - tree_render(); } | TOK_FOCUS window_mode { - if (focused && - focused->type != CT_WORKSPACE && - focused->fullscreen_mode != CF_NONE) { - LOG("Cannot change focus while in fullscreen mode.\n"); - break; - } - printf("should focus: "); - - if ($2 == TOK_TILING) - printf("tiling\n"); - else if ($2 == TOK_FLOATING) - printf("floating\n"); - else printf("mode toggle\n"); - - Con *ws = con_get_workspace(focused); - Con *current; - if (ws != NULL) { - int to_focus = $2; - if ($2 == TOK_MODE_TOGGLE) { - current = TAILQ_FIRST(&(ws->focus_head)); - if (current != NULL && current->type == CT_FLOATING_CON) - to_focus = TOK_TILING; - else to_focus = TOK_FLOATING; - } - TAILQ_FOREACH(current, &(ws->focus_head), focused) { - if ((to_focus == TOK_FLOATING && current->type != CT_FLOATING_CON) || - (to_focus == TOK_TILING && current->type == CT_FLOATING_CON)) - continue; - - con_focus(con_descend_focused(current)); - break; - } - } - - tree_render(); + json_output = cmd_focus_window_mode(¤t_match, + ($2 == TOK_TILING ? "tiling" : + ($2 == TOK_FLOATING ? "floating" : + "mode_toggle"))); } | TOK_FOCUS level { - if (focused && - focused->type != CT_WORKSPACE && - focused->fullscreen_mode != CF_NONE) { - LOG("Cannot change focus while in fullscreen mode.\n"); - break; - } - - if ($2 == TOK_PARENT) - level_up(); - else level_down(); - - tree_render(); + json_output = cmd_focus_level(¤t_match, ($2 == TOK_PARENT ? "parent" : "child")); } ; @@ -659,20 +353,7 @@ level: kill: TOK_KILL optional_kill_mode { - owindow *current; - - printf("killing!\n"); - /* check if the match is empty, not if the result is empty */ - if (match_is_empty(¤t_match)) - tree_close_con($2); - else { - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - tree_close(current->con, $2, false, false); - } - } - - tree_render(); + json_output = cmd_kill(¤t_match, ($2 == KILL_WINDOW ? "window" : "client")); } ; @@ -685,84 +366,42 @@ optional_kill_mode: workspace: TOK_WORKSPACE TOK_NEXT { - workspace_show(workspace_next()); - tree_render(); + json_output = cmd_workspace(¤t_match, "next"); } | TOK_WORKSPACE TOK_PREV { - workspace_show(workspace_prev()); - tree_render(); + json_output = cmd_workspace(¤t_match, "prev"); } | TOK_WORKSPACE TOK_NEXT_ON_OUTPUT { - workspace_show(workspace_next_on_output()); - tree_render(); + json_output = cmd_workspace(¤t_match, "next_on_output"); } | TOK_WORKSPACE TOK_PREV_ON_OUTPUT { - workspace_show(workspace_prev_on_output()); - tree_render(); + json_output = cmd_workspace(¤t_match, "prev_on_output"); } | TOK_WORKSPACE TOK_BACK_AND_FORTH { - workspace_back_and_forth(); - tree_render(); + json_output = cmd_workspace_back_and_forth(¤t_match); } | TOK_WORKSPACE STR { - if (strncasecmp($2, "__i3_", strlen("__i3_")) == 0) { - printf("You cannot switch to the i3 internal workspaces.\n"); - break; - } - - printf("should switch to workspace %s\n", $2); - - Con *ws = con_get_workspace(focused); - - /* Check if the command wants to switch to the current workspace */ - if (strcmp(ws->name, $2) == 0) { - printf("This workspace is already focused.\n"); - if (config.workspace_auto_back_and_forth) { - workspace_back_and_forth(); - free($2); - tree_render(); - } - break; - } - - workspace_show_by_name($2); + json_output = cmd_workspace_name(¤t_match, $2); free($2); - - tree_render(); } ; open: TOK_OPEN { - printf("opening new container\n"); - Con *con = tree_open_con(NULL, NULL); - con_focus(con); - sasprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con); - - tree_render(); + json_output = cmd_open(¤t_match); } ; fullscreen: TOK_FULLSCREEN fullscreen_mode { - printf("toggling fullscreen, mode = %s\n", ($2 == CF_OUTPUT ? "normal" : "global")); - owindow *current; - - HANDLE_EMPTY_MATCH; - - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - con_toggle_fullscreen(current->con, $2); - } - - tree_render(); + json_output = cmd_fullscreen(¤t_match, ($2 == CF_OUTPUT ? "output" : "global")); } ; @@ -774,11 +413,9 @@ fullscreen_mode: split: TOK_SPLIT split_direction { - /* TODO: use matches */ - printf("splitting in direction %c\n", $2); - tree_split(focused, ($2 == 'v' ? VERT : HORIZ)); - - tree_render(); + char buf[2] = {'\0', '\0'}; + buf[0] = $2; + json_output = cmd_split(¤t_match, buf); } ; @@ -792,25 +429,10 @@ split_direction: floating: TOK_FLOATING boolean { - HANDLE_EMPTY_MATCH; - - owindow *current; - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - if ($2 == TOK_TOGGLE) { - printf("should toggle mode\n"); - toggle_floating_mode(current->con, false); - } else { - printf("should switch mode to %s\n", ($2 == TOK_FLOATING ? "floating" : "tiling")); - if ($2 == TOK_ENABLE) { - floating_enable(current->con, false); - } else { - floating_disable(current->con, false); - } - } - } - - tree_render(); + json_output = cmd_floating(¤t_match, + ($2 == TOK_ENABLE ? "enable" : + ($2 == TOK_DISABLE ? "disable" : + "toggle"))); } ; @@ -823,22 +445,11 @@ boolean: border: TOK_BORDER border_style { - printf("border style should be changed to %d\n", $2); - owindow *current; - - HANDLE_EMPTY_MATCH; - - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - int border_style = current->con->border_style; - if ($2 == TOK_TOGGLE) { - border_style++; - border_style %= 3; - } else border_style = $2; - con_set_border_style(current->con, border_style); - } - - tree_render(); + json_output = cmd_border(¤t_match, + ($2 == BS_NORMAL ? "normal" : + ($2 == BS_NONE ? "none" : + ($2 == BS_1PIXEL ? "1pixel" : + "toggle")))); } ; @@ -852,256 +463,66 @@ border_style: move: TOK_MOVE direction resize_px { - int direction = $2; - int px = $3; - - /* TODO: make 'move' work with criteria. */ - printf("moving in direction %d\n", direction); - if (con_is_floating(focused)) { - printf("floating move with %d pixels\n", px); - Rect newrect = focused->parent->rect; - if (direction == TOK_LEFT) { - newrect.x -= px; - } else if (direction == TOK_RIGHT) { - newrect.x += px; - } else if (direction == TOK_UP) { - newrect.y -= px; - } else if (direction == TOK_DOWN) { - newrect.y += px; - } - floating_reposition(focused->parent, newrect); - } else { - tree_move(direction); - tree_render(); - } + char buffer[128]; + snprintf(buffer, sizeof(buffer), "%d", $3); + json_output = cmd_move_direction(¤t_match, + ($2 == TOK_LEFT ? "left" : + ($2 == TOK_RIGHT ? "right" : + ($2 == TOK_UP ? "up" : + "down"))), + buffer); } | TOK_MOVE TOK_WORKSPACE STR { - if (strncasecmp($3, "__i3_", strlen("__i3_")) == 0) { - printf("You cannot switch to the i3 internal workspaces.\n"); - break; - } - - 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(¤t_match) && focused->type == CT_WORKSPACE) - break; - - printf("should move window to workspace %s\n", $3); - /* get the workspace */ - Con *ws = workspace_get($3, NULL); - free($3); - - HANDLE_EMPTY_MATCH; - - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, ws, true, false); - } - - tree_render(); + json_output = cmd_move_con_to_workspace_name(¤t_match, $3); } | TOK_MOVE TOK_WORKSPACE TOK_NEXT { - owindow *current; - - /* get the workspace */ - Con *ws = workspace_next(); - - HANDLE_EMPTY_MATCH; - - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, ws, true, false); - } - - tree_render(); + json_output = cmd_move_con_to_workspace(¤t_match, "next"); } | TOK_MOVE TOK_WORKSPACE TOK_PREV { - owindow *current; - - /* get the workspace */ - Con *ws = workspace_prev(); - - HANDLE_EMPTY_MATCH; - - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, ws, true, false); - } - - tree_render(); + json_output = cmd_move_con_to_workspace(¤t_match, "prev"); } | TOK_MOVE TOK_WORKSPACE TOK_NEXT_ON_OUTPUT { - owindow *current; - - /* get the workspace */ - Con *ws = workspace_next_on_output(); - - HANDLE_EMPTY_MATCH; - - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, ws, true, false); - } - - tree_render(); + json_output = cmd_move_con_to_workspace(¤t_match, "next_on_output"); } | TOK_MOVE TOK_WORKSPACE TOK_PREV_ON_OUTPUT { - owindow *current; - - /* get the workspace */ - Con *ws = workspace_prev_on_output(); - - HANDLE_EMPTY_MATCH; - - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, ws, true, false); - } - - tree_render(); + json_output = cmd_move_con_to_workspace(¤t_match, "prev_on_output"); } | TOK_MOVE TOK_OUTPUT STR { - owindow *current; - - printf("should move window to output %s\n", $3); - - HANDLE_EMPTY_MATCH; - - /* get the output */ - Output *current_output = NULL; - Output *output; - - TAILQ_FOREACH(current, &owindows, owindows) - current_output = get_output_containing(current->con->rect.x, current->con->rect.y); - - assert(current_output != NULL); - - if (strcasecmp($3, "up") == 0) - output = get_output_next(D_UP, current_output); - else if (strcasecmp($3, "down") == 0) - output = get_output_next(D_DOWN, current_output); - else if (strcasecmp($3, "left") == 0) - output = get_output_next(D_LEFT, current_output); - else if (strcasecmp($3, "right") == 0) - output = get_output_next(D_RIGHT, current_output); - else - output = get_output_by_name($3); + json_output = cmd_move_con_to_output(¤t_match, $3); free($3); - - if (!output) { - printf("No such output found.\n"); - break; - } - - /* get visible workspace on output */ - Con *ws = NULL; - GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child)); - if (!ws) - break; - - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, ws, true, false); - } - - tree_render(); } | TOK_MOVE TOK_SCRATCHPAD { - printf("should move window to scratchpad\n"); - owindow *current; - - HANDLE_EMPTY_MATCH; - - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - scratchpad_move(current->con); - } - - tree_render(); + json_output = cmd_move_scratchpad(¤t_match); } | TOK_MOVE TOK_WORKSPACE TOK_TO TOK_OUTPUT STR { - printf("should move workspace to output %s\n", $5); - - HANDLE_EMPTY_MATCH; - - owindow *current; - TAILQ_FOREACH(current, &owindows, owindows) { - Output *current_output = get_output_containing(current->con->rect.x, - current->con->rect.y); - Output *output = get_output_from_string(current_output, $5); - if (!output) { - printf("No such output\n"); - break; - } - - Con *content = output_get_content(output->con); - LOG("got output %p with content %p\n", output, content); - - Con *ws = con_get_workspace(current->con); - printf("should move workspace %p / %s\n", ws, ws->name); - if (con_num_children(ws->parent) == 1) { - printf("Not moving workspace \"%s\", it is the only workspace on its output.\n", ws->name); - continue; - } - bool workspace_was_visible = workspace_is_visible(ws); - 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)); - printf("workspace was visible, focusing %p / %s now\n", focus_ws, focus_ws->name); - workspace_show(focus_ws); - } - con_attach(ws, content, false); - 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); - } - } - - tree_render(); + json_output = cmd_move_workspace_to_output(¤t_match, $5); + free($5); } ; append_layout: TOK_APPEND_LAYOUT STR { - printf("restoring \"%s\"\n", $2); - tree_append_json($2); + json_output = cmd_append_layout(¤t_match, $2); free($2); - tree_render(); } ; layout: TOK_LAYOUT layout_mode { - printf("changing layout to %d\n", $2); - owindow *current; - - /* check if the match is empty, not if the result is empty */ - if (match_is_empty(¤t_match)) - con_set_layout(focused->parent, $2); - else { - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - con_set_layout(current->con, $2); - } - } - - tree_render(); + json_output = cmd_layout(¤t_match, + ($2 == L_DEFAULT ? "default" : + ($2 == L_STACKED ? "stacked" : + "tabbed"))); } ; @@ -1114,34 +535,15 @@ layout_mode: mark: TOK_MARK STR { - printf("Clearing all windows which have that mark first\n"); - - Con *con; - TAILQ_FOREACH(con, &all_cons, all_cons) { - if (con->mark && strcmp(con->mark, $2) == 0) - FREE(con->mark); - } - - printf("marking window with str %s\n", $2); - owindow *current; - - HANDLE_EMPTY_MATCH; - - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - current->con->mark = $2; - } - - tree_render(); + json_output = cmd_mark(¤t_match, $2); + free($2); } ; nop: TOK_NOP STR { - printf("-------------------------------------------------\n"); - printf(" NOP: %s\n", $2); - printf("-------------------------------------------------\n"); + json_output = cmd_nop(¤t_match, $2); free($2); } ; @@ -1149,19 +551,7 @@ nop: scratchpad: TOK_SCRATCHPAD TOK_SHOW { - printf("should show scratchpad window\n"); - owindow *current; - - if (match_is_empty(¤t_match)) { - scratchpad_show(NULL); - } else { - TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); - scratchpad_show(current->con); - } - } - - tree_render(); + json_output = cmd_scratchpad_show(¤t_match); } ; @@ -1169,98 +559,17 @@ scratchpad: resize: TOK_RESIZE resize_way direction resize_px resize_tiling { - /* resize [ px] [or ppt] */ - printf("resizing in way %d, direction %d, px %d or ppt %d\n", $2, $3, $4, $5); - int direction = $3; - int px = $4; - int ppt = $5; - if ($2 == TOK_SHRINK) { - px *= -1; - ppt *= -1; - } - - Con *floating_con; - if ((floating_con = con_inside_floating(focused))) { - printf("floating resize\n"); - if (direction == TOK_UP) { - floating_con->rect.y -= px; - floating_con->rect.height += px; - } else if (direction == TOK_DOWN) { - floating_con->rect.height += px; - } else if (direction == TOK_LEFT) { - 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; - - /* Then further go up until we find one with the matching orientation. */ - orientation_t search_orientation = - (direction == TOK_LEFT || direction == TOK_RIGHT ? HORIZ : VERT); - - while (current->type != CT_WORKSPACE && - current->type != CT_FLOATING_CON && - current->parent->orientation != search_orientation) - current = current->parent; - - /* 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); - - orientation_t orientation = current->parent->orientation; - - if ((orientation == HORIZ && - (direction == TOK_UP || direction == TOK_DOWN)) || - (orientation == VERT && - (direction == TOK_LEFT || direction == TOK_RIGHT))) { - LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n", - (orientation == HORIZ ? "horizontal" : "vertical")); - break; - } - - if (direction == TOK_UP || direction == TOK_LEFT) { - 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"); - break; - } - 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"); - } - } - - tree_render(); + char buffer1[128], buffer2[128]; + snprintf(buffer1, sizeof(buffer1), "%d", $4); + snprintf(buffer2, sizeof(buffer2), "%d", $5); + json_output = cmd_resize(¤t_match, + ($2 == TOK_SHRINK ? "shrink" : "grow"), + ($3 == TOK_LEFT ? "left" : + ($3 == TOK_RIGHT ? "right" : + ($3 == TOK_DOWN ? "down" : + "up"))), + buffer1, + buffer2); } ; @@ -1301,6 +610,7 @@ direction: mode: TOK_MODE STR { - switch_mode($2); + json_output = cmd_mode(¤t_match, $2); + free($2); } ; diff --git a/src/commands.c b/src/commands.c new file mode 100644 index 00000000..7c4b9a61 --- /dev/null +++ b/src/commands.c @@ -0,0 +1,1002 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) + * + * commands.c: all command functions (see commands_parser.c) + * + */ +#include + +#include "all.h" +#include "cmdparse.tab.h" + +/** When the command did not include match criteria (!), we use the currently + * focused command. 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) + +static owindows_head owindows; + +/* + * 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); +} + +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); + + return output; +} + +char *cmd_criteria_init(Match *current_match) { + DLOG("Initializing criteria, current_match = %p\n", current_match); + match_init(current_match); + TAILQ_INIT(&owindows); + /* copy all_cons */ + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) { + 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) { + owindow *next, *current; + + 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 */ + struct owindows_head old = owindows; + TAILQ_INIT(&owindows); + for (next = TAILQ_FIRST(&old); next != TAILQ_END(&old);) { + /* make a copy of the next pointer and advance the pointer to the + * next element as we are going to invalidate the element’s + * next/prev pointers by calling TAILQ_INSERT_TAIL later */ + current = next; + next = TAILQ_NEXT(next, owindows); + + DLOG("checking if con %p / %s matches\n", current->con, current->con->name); + if (current_match->con_id != NULL) { + if (current_match->con_id == current->con) { + DLOG("matches container!\n"); + TAILQ_INSERT_TAIL(&owindows, current, owindows); + } + } else if (current_match->mark != NULL && current->con->mark != NULL && + regex_matches(current_match->mark, current->con->mark)) { + DLOG("match by mark\n"); + TAILQ_INSERT_TAIL(&owindows, current, owindows); + } else { + if (current->con->window == NULL) + continue; + if (match_matches_window(current_match, current->con->window)) { + DLOG("matches window!\n"); + TAILQ_INSERT_TAIL(&owindows, current, owindows); + } else { + DLOG("doesnt match\n"); + free(current); + } + } + } + + 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) { + DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue); + + if (strcmp(ctype, "class") == 0) { + current_match->class = regex_new(cvalue); + return NULL; + } + + if (strcmp(ctype, "instance") == 0) { + current_match->instance = regex_new(cvalue); + return NULL; + } + + if (strcmp(ctype, "window_role") == 0) { + current_match->role = regex_new(cvalue); + return NULL; + } + + if (strcmp(ctype, "con_id") == 0) { + char *end; + long parsed = strtol(cvalue, &end, 10); + if (parsed == LONG_MIN || + parsed == LONG_MAX || + parsed < 0 || + (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); + } + return NULL; + } + + if (strcmp(ctype, "id") == 0) { + char *end; + long parsed = strtol(cvalue, &end, 10); + if (parsed == LONG_MIN || + parsed == LONG_MAX || + parsed < 0 || + (end && *end != '\0')) { + ELOG("Could not parse window id \"%s\"\n", cvalue); + } else { + current_match->id = parsed; + printf("window id as int = %d\n", current_match->id); + } + return NULL; + } + + if (strcmp(ctype, "con_mark") == 0) { + current_match->mark = regex_new(cvalue); + return NULL; + } + + if (strcmp(ctype, "title") == 0) { + current_match->title = regex_new(cvalue); + return NULL; + } + + ELOG("Unknown criterion: %s\n", ctype); + + /* This command is internal and does not generate a JSON reply. */ + return NULL; +} + +char *cmd_move_con_to_workspace(Match *current_match, char *which) { + owindow *current; + + DLOG("which=%s\n", which); + + HANDLE_EMPTY_MATCH; + + /* get the workspace */ + Con *ws; + if (strcmp(which, "next") == 0) + ws = workspace_next(); + else if (strcmp(which, "prev") == 0) + ws = workspace_prev(); + else if (strcmp(which, "next_on_output") == 0) + ws = workspace_next_on_output(); + else if (strcmp(which, "prev_on_output") == 0) + ws = workspace_prev_on_output(); + else { + ELOG("BUG: called with which=%s\n", which); + return sstrdup("{\"sucess\": false}"); + } + + 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(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_move_con_to_workspace_name(Match *current_match, char *name) { + if (strncasecmp(name, "__i3_", strlen("__i3_")) == 0) { + LOG("You cannot switch to the i3 internal workspaces.\n"); + return sstrdup("{\"sucess\": false}"); + } + + 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}"); + + LOG("should move window to workspace %s\n", name); + /* get the workspace */ + Con *ws = workspace_get(name, NULL); + + 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); + } + + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_resize(Match *current_match, 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))) { + 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; + + /* 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); + + while (current->type != CT_WORKSPACE && + current->type != CT_FLOATING_CON && + current->parent->orientation != search_orientation) + current = current->parent; + + /* 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); + + orientation_t orientation = current->parent->orientation; + + 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")); + return sstrdup("{\"sucess\": 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"); + return sstrdup("{\"sucess\": 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"); + } + } + + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_border(Match *current_match, char *border_style_str) { + DLOG("border style should be changed to %s\n", border_style_str); + owindow *current; + + HANDLE_EMPTY_MATCH; + + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("matching: %p / %s\n", current->con, current->con->name); + int border_style = current->con->border_style; + if (strcmp(border_style_str, "toggle") == 0) { + border_style++; + border_style %= 3; + } else { + if (strcmp(border_style_str, "normal") == 0) + border_style = BS_NORMAL; + 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); + return sstrdup("{\"sucess\": false}"); + } + } + con_set_border_style(current->con, border_style); + } + + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_nop(Match *current_match, char *comment) { + LOG("-------------------------------------------------\n"); + LOG(" NOP: %s\n", comment); + LOG("-------------------------------------------------\n"); + + return NULL; +} + +char *cmd_append_layout(Match *current_match, char *path) { + LOG("Appending layout \"%s\"\n", path); + tree_append_json(path); + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_workspace(Match *current_match, char *which) { + Con *ws; + + DLOG("which=%s\n", which); + + if (strcmp(which, "next") == 0) + ws = workspace_next(); + else if (strcmp(which, "prev") == 0) + ws = workspace_prev(); + else if (strcmp(which, "next_on_output") == 0) + ws = workspace_next_on_output(); + else if (strcmp(which, "prev_on_output") == 0) + ws = workspace_prev_on_output(); + else { + ELOG("BUG: called with which=%s\n", which); + return sstrdup("{\"sucess\": false}"); + } + + workspace_show(ws); + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_workspace_back_and_forth(Match *current_match) { + workspace_back_and_forth(); + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_workspace_name(Match *current_match, char *name) { + if (strncasecmp(name, "__i3_", strlen("__i3_")) == 0) { + LOG("You cannot switch to the i3 internal workspaces.\n"); + return sstrdup("{\"sucess\": false}"); + } + + 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}"); + } + + workspace_show_by_name(name); + + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_mark(Match *current_match, char *mark) { + DLOG("Clearing all windows which have that mark first\n"); + + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) { + if (con->mark && strcmp(con->mark, mark) == 0) + FREE(con->mark); + } + + DLOG("marking window with str %s\n", mark); + owindow *current; + + HANDLE_EMPTY_MATCH; + + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("matching: %p / %s\n", current->con, current->con->name); + current->con->mark = sstrdup(mark); + } + + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_mode(Match *current_match, char *mode) { + DLOG("mode=%s\n", mode); + switch_mode(mode); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_move_con_to_output(Match *current_match, char *name) { + owindow *current; + + DLOG("should move window to output %s\n", 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_containing(current->con->rect.x, current->con->rect.y); + + 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(D_UP, current_output); + else if (strcasecmp(name, "down") == 0) + output = get_output_next(D_DOWN, current_output); + else if (strcasecmp(name, "left") == 0) + output = get_output_next(D_LEFT, current_output); + else if (strcasecmp(name, "right") == 0) + output = get_output_next(D_RIGHT, current_output); + else + output = get_output_by_name(name); + + if (!output) { + LOG("No such output found.\n"); + return sstrdup("{\"sucess\": false}"); + } + + /* 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}"); + + 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(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_floating(Match *current_match, char *floating_mode) { + owindow *current; + + DLOG("floating_mode=%s\n", floating_mode); + + HANDLE_EMPTY_MATCH; + + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("matching: %p / %s\n", current->con, current->con->name); + if (strcmp(floating_mode, "toggle") == 0) { + DLOG("should toggle mode\n"); + toggle_floating_mode(current->con, false); + } else { + DLOG("should switch mode to %s\n", floating_mode); + if (strcmp(floating_mode, "enable") == 0) { + floating_enable(current->con, false); + } else { + floating_disable(current->con, false); + } + } + } + + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_move_workspace_to_output(Match *current_match, char *name) { + DLOG("should move workspace to output %s\n", name); + + HANDLE_EMPTY_MATCH; + + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + Output *current_output = get_output_containing(current->con->rect.x, + current->con->rect.y); + Output *output = get_output_from_string(current_output, name); + if (!output) { + LOG("No such output\n"); + return sstrdup("{\"sucess\": false}"); + } + + Con *content = output_get_content(output->con); + LOG("got output %p with content %p\n", output, content); + + 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; + } + bool workspace_was_visible = workspace_is_visible(ws); + 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); + 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); + } + } + + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_split(Match *current_match, char *direction) { + /* TODO: use matches */ + LOG("splitting in direction %c\n", direction[0]); + tree_split(focused, (direction[0] == 'v' ? VERT : HORIZ)); + + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_kill(Match *current_match, char *kill_mode_str) { + owindow *current; + + DLOG("kill_mode=%s\n", kill_mode_str); + + int kill_mode; + if (kill_mode_str == NULL || strcmp(kill_mode_str, "window") == 0) + kill_mode = KILL_WINDOW; + else if (strcmp(kill_mode_str, "client") == 0) + kill_mode = KILL_CLIENT; + else { + ELOG("BUG: called with kill_mode=%s\n", kill_mode_str); + return sstrdup("{\"sucess\": false}"); + } + + /* check if the match is empty, not if the result is empty */ + if (match_is_empty(current_match)) + tree_close_con(kill_mode); + else { + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("matching: %p / %s\n", current->con, current->con->name); + tree_close(current->con, kill_mode, false, false); + } + } + + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_exec(Match *current_match, 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}"); +} + +char *cmd_focus_direction(Match *current_match, 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}"); + } + + DLOG("direction = *%s*\n", direction); + + if (strcmp(direction, "left") == 0) + tree_next('p', HORIZ); + else if (strcmp(direction, "right") == 0) + tree_next('n', HORIZ); + else if (strcmp(direction, "up") == 0) + tree_next('p', VERT); + else if (strcmp(direction, "down") == 0) + tree_next('n', VERT); + else { + ELOG("Invalid focus direction (%s)\n", direction); + return sstrdup("{\"sucess\": false}"); + } + + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_focus_window_mode(Match *current_match, 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}"); + } + + DLOG("window_mode = %s\n", window_mode); + + Con *ws = con_get_workspace(focused); + Con *current; + if (ws != NULL) { + if (strcmp(window_mode, "mode_toggle") == 0) { + current = TAILQ_FIRST(&(ws->focus_head)); + if (current != NULL && current->type == CT_FLOATING_CON) + window_mode = "tiling"; + else window_mode = "floating"; + } + TAILQ_FOREACH(current, &(ws->focus_head), focused) { + if ((strcmp(window_mode, "floating") == 0 && current->type != CT_FLOATING_CON) || + (strcmp(window_mode, "tiling") == 0 && current->type == CT_FLOATING_CON)) + continue; + + con_focus(con_descend_focused(current)); + break; + } + } + + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_focus_level(Match *current_match, char *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}"); + } + + DLOG("level = %s\n", level); + + if (strcmp(level, "parent") == 0) + level_up(); + else level_down(); + + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_focus(Match *current_match) { + 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"); + + // TODO: json output + char *json_output; + sasprintf(&json_output, "{\"success\":false, \"error\":\"You have to " + "specify which window/container should be focused\"}"); + return json_output; + } + + LOG("here"); + int count = 0; + TAILQ_FOREACH(current, &owindows, owindows) { + Con *ws = con_get_workspace(current->con); + /* If no workspace could be found, this was a dock window. + * Just skip it, you cannot focus dock windows. */ + if (!ws) + continue; + LOG("there"); + + /* 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 + * container (!) on the target workspace. + * + * Therefore, before calling workspace_show(), we make sure that + * 'current' will be focused on the workspace. However, we cannot + * just con_focus(current) because then the pointer will not be + * warped at all (the code thinks we are already there). + * + * So we focus 'current' to make it the currently focused window of + * the target workspace, then revert focus. */ + Con *currently_focused = focused; + con_focus(current->con); + con_focus(currently_focused); + + /* Now switch to the workspace, then focus */ + workspace_show(ws); + LOG("focusing %p / %s\n", current->con, current->con->name); + con_focus(current->con); + count++; + } + + 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); + + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_fullscreen(Match *current_match, char *fullscreen_mode) { + DLOG("toggling fullscreen, mode = %s\n", fullscreen_mode); + owindow *current; + + HANDLE_EMPTY_MATCH; + + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + con_toggle_fullscreen(current->con, (fullscreen_mode && strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT)); + } + + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_move_direction(Match *current_match, 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; + } + 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(); + } + + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_layout(Match *current_match, char *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 || strcmp(layout_str, "stacking") == 0 ? L_STACKED : + L_TABBED)); + + /* check if the match is empty, not if the result is empty */ + if (match_is_empty(current_match)) + con_set_layout(focused->parent, layout); + else { + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("matching: %p / %s\n", current->con, current->con->name); + con_set_layout(current->con, layout); + } + } + + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_exit(Match *current_match) { + LOG("Exiting due to user command.\n"); + exit(0); + + /* unreached */ +} + +char *cmd_reload(Match *current_match) { + LOG("reloading\n"); + kill_configerror_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}"); +} + +char *cmd_restart(Match *current_match) { + LOG("restarting i3\n"); + i3_restart(false); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_open(Match *current_match) { + LOG("opening new container\n"); + Con *con = tree_open_con(NULL, NULL); + con_focus(con); + char *json_output; + sasprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con); + + tree_render(); + + return json_output; +} + +char *cmd_focus_output(Match *current_match, char *name) { + owindow *current; + + DLOG("name = %s\n", name); + + HANDLE_EMPTY_MATCH; + + /* get the output */ + Output *current_output = NULL; + Output *output; + + TAILQ_FOREACH(current, &owindows, owindows) + current_output = get_output_containing(current->con->rect.x, current->con->rect.y); + assert(current_output != NULL); + + output = get_output_from_string(current_output, name); + + if (!output) { + LOG("No such output found.\n"); + return sstrdup("{\"sucess\": false}"); + } + + /* 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}"); + + workspace_show(ws); + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_move_scratchpad(Match *current_match) { + DLOG("should move window to scratchpad\n"); + owindow *current; + + HANDLE_EMPTY_MATCH; + + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("matching: %p / %s\n", current->con, current->con->name); + scratchpad_move(current->con); + } + + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} + +char *cmd_scratchpad_show(Match *current_match) { + DLOG("should show scratchpad window\n"); + owindow *current; + + if (match_is_empty(current_match)) { + scratchpad_show(NULL); + } else { + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("matching: %p / %s\n", current->con, current->con->name); + scratchpad_show(current->con); + } + } + + tree_render(); + + // XXX: default reply for now, make this a better reply + return sstrdup("{\"success\": true}"); +} diff --git a/testcases/t/124-move.t b/testcases/t/124-move.t index a078a9e8..ae989f06 100644 --- a/testcases/t/124-move.t +++ b/testcases/t/124-move.t @@ -195,7 +195,7 @@ cmd 'move left 20 px'; ($absolute, $top) = $floatwin->rect; -is($absolute->x, ($absolute_before->x - 20), 'moved 10 px to the left'); +is($absolute->x, ($absolute_before->x - 20), 'moved 20 px to the left'); is($absolute->y, $absolute_before->y, 'y not changed'); is($absolute->width, $absolute_before->width, 'width not changed'); is($absolute->height, $absolute_before->height, 'height not changed');