X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fcommands.c;h=8fc80a199c3b0c11efbe58583982d5006f8fc99b;hb=e114b3dba2485606cf5a3d044c0de8430c9f80b4;hp=56f1ab27cb567f4cc967c5a32b8846b2b0fe8ea3;hpb=d55e4ece83f10f9a947018fa8cc17a549cf30019;p=i3%2Fi3 diff --git a/src/commands.c b/src/commands.c index 56f1ab27..8fc80a19 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1,1230 +1,1298 @@ /* - * vim:ts=8:expandtab + * 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 file LICENSE for license information. + * commands.c: all command functions (see commands_parser.c) * */ -#include -#include -#include -#include -#include -#include - -#include - -#include "util.h" -#include "data.h" -#include "table.h" -#include "layout.h" -#include "i3.h" -#include "xinerama.h" -#include "client.h" -#include "floating.h" -#include "xcb.h" -#include "config.h" -#include "workspace.h" -#include "commands.h" -#include "resize.h" - -bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { - /* If this container is empty, we’re done */ - if (container->currently_focused == NULL) - return false; - - /* Get the previous/next client or wrap around */ - Client *candidate = NULL; - if (direction == D_UP) { - if ((candidate = CIRCLEQ_PREV_OR_NULL(&(container->clients), container->currently_focused, clients)) == NULL) - candidate = CIRCLEQ_LAST(&(container->clients)); - } - else if (direction == D_DOWN) { - if ((candidate = CIRCLEQ_NEXT_OR_NULL(&(container->clients), container->currently_focused, clients)) == NULL) - candidate = CIRCLEQ_FIRST(&(container->clients)); - } else LOG("Direction not implemented!\n"); +#include +#include - /* If we could not switch, the container contains exactly one client. We return false */ - if (candidate == container->currently_focused) - return false; +#include "all.h" - /* Set focus */ - set_focus(conn, candidate, true); - - return true; -} +/** 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) -typedef enum { THING_WINDOW, THING_CONTAINER, THING_SCREEN } thing_t; +static owindows_head owindows; -static void jump_to_mark(xcb_connection_t *conn, const char *mark) { - Client *current; - LOG("Jumping to \"%s\"\n", mark); +/* + * 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); +} - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) - SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) { - if (current->mark == NULL || strcmp(current->mark, mark) != 0) - continue; +/* + * 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(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; +} - workspace_show(conn, current->workspace->num + 1); - set_focus(conn, current, true); - return; - } +// This code is commented out because we might recycle it for popping up error +// messages on parser errors. +#if 0 +static pid_t migration_pid = -1; - LOG("No window with this mark found\n"); +/* + * Handler which will be called when we get a SIGCHLD for the nagbar, meaning + * it exited (or could not be started, depending on the exit code). + * + */ +static void nagbar_exited(EV_P_ ev_child *watcher, int revents) { + ev_child_stop(EV_A_ watcher); + if (!WIFEXITED(watcher->rstatus)) { + fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n"); + return; + } + + int exitcode = WEXITSTATUS(watcher->rstatus); + printf("i3-nagbar process exited with status %d\n", exitcode); + if (exitcode == 2) { + fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n"); + } + + migration_pid = -1; } -static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) { - LOG("focusing direction %d\n", direction); +/* We need ev >= 4 for the following code. Since it is not *that* important (it + * only makes sure that there are no i3-nagbar instances left behind) we still + * support old systems with libev 3. */ +#if EV_VERSION_MAJOR >= 4 +/* + * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal + * SIGKILL (9) to make sure there are no left-over i3-nagbar processes. + * + */ +static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) { + if (migration_pid != -1) { + LOG("Sending SIGKILL (9) to i3-nagbar with PID %d\n", migration_pid); + kill(migration_pid, SIGKILL); + } +} +#endif + +void cmd_MIGRATION_start_nagbar() { + if (migration_pid != -1) { + fprintf(stderr, "i3-nagbar already running.\n"); + return; + } + fprintf(stderr, "Starting i3-nagbar, command parsing differs from expected output.\n"); + ELOG("Please report this on IRC or in the bugtracker. Make sure to include the full debug level logfile:\n"); + ELOG("i3-dump-log | gzip -9c > /tmp/i3.log.gz\n"); + ELOG("FYI: Your i3 version is " I3_VERSION "\n"); + migration_pid = fork(); + if (migration_pid == -1) { + warn("Could not fork()"); + return; + } + + /* child */ + if (migration_pid == 0) { + char *pageraction; + sasprintf(&pageraction, "i3-sensible-terminal -e i3-sensible-pager \"%s\"", errorfilename); + char *argv[] = { + NULL, /* will be replaced by the executable path */ + "-t", + "error", + "-m", + "You found a parsing error. Please, please, please, report it!", + "-b", + "show errors", + pageraction, + NULL + }; + exec_i3_utility("i3-nagbar", argv); + } + + /* parent */ + /* install a child watcher */ + ev_child *child = smalloc(sizeof(ev_child)); + ev_child_init(child, &nagbar_exited, migration_pid, 0); + ev_child_start(main_loop, child); + +/* We need ev >= 4 for the following code. Since it is not *that* important (it + * only makes sure that there are no i3-nagbar instances left behind) we still + * support old systems with libev 3. */ +#if EV_VERSION_MAJOR >= 4 + /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is + * still running) */ + ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup)); + ev_cleanup_init(cleanup, nagbar_cleanup); + ev_cleanup_start(main_loop, cleanup); +#endif +} - int new_row = current_row, - new_col = current_col; - Container *container = CUR_CELL; - Workspace *t_ws = c_ws; +#endif - /* Makes sure new_col and new_row are within bounds of the new workspace */ - void check_colrow_boundaries() { - if (new_col >= t_ws->cols) - new_col = (t_ws->cols - 1); - if (new_row >= t_ws->rows) - new_row = (t_ws->rows - 1); - } +/******************************************************************************* + * Criteria functions. + ******************************************************************************/ - /* There always is a container. If not, current_col or current_row is wrong */ - assert(container != NULL); +/* + * Initializes the specified 'Match' data structure and the initial state of + * commands.c for matching target windows of a command. + * + */ +void cmd_criteria_init(I3_CMD) { + Con *con; + owindow *ow; + + DLOG("Initializing criteria, current_match = %p\n", current_match); + match_init(current_match); + while (!TAILQ_EMPTY(&owindows)) { + ow = TAILQ_FIRST(&owindows); + TAILQ_REMOVE(&owindows, ow, owindows); + free(ow); + } + TAILQ_INIT(&owindows); + /* copy all_cons */ + TAILQ_FOREACH(con, &all_cons, all_cons) { + ow = smalloc(sizeof(owindow)); + ow->con = con; + TAILQ_INSERT_TAIL(&owindows, ow, owindows); + } +} - if (container->workspace->fullscreen_client != NULL) { - LOG("You're in fullscreen mode. Won't switch focus\n"); - return; +/* + * A match specification just finished (the closing square bracket was found), + * so we filter the list of owindows. + * + */ +void cmd_criteria_match_windows(I3_CMD) { + owindow *next, *current; + + 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); + } } + } - /* For focusing screens, situation is different: we get the rect - * of the current screen, then get the screen which is on its - * right/left/bottom/top and just switch to the workspace on - * the target screen. */ - if (thing == THING_SCREEN) { - i3Screen *cs = c_ws->screen; - assert(cs != NULL); - Rect bounds = cs->rect; - - if (direction == D_RIGHT) - bounds.x += bounds.width; - else if (direction == D_LEFT) - bounds.x -= bounds.width; - else if (direction == D_UP) - bounds.y -= bounds.height; - else bounds.y += bounds.height; - - i3Screen *target = get_screen_containing(bounds.x, bounds.y); - if (target == NULL) { - LOG("Target screen NULL\n"); - /* Wrap around if the target screen is out of bounds */ - if (direction == D_RIGHT) - target = get_screen_most(D_LEFT, cs); - else if (direction == D_LEFT) - target = get_screen_most(D_RIGHT, cs); - else if (direction == D_UP) - target = get_screen_most(D_DOWN, cs); - else target = get_screen_most(D_UP, cs); - } - - LOG("Switching to ws %d\n", target->current_workspace + 1); - workspace_show(conn, target->current_workspace->num + 1); - return; - } + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("matching: %p / %s\n", current->con, current->con->name); + } +} - /* TODO: for horizontal default layout, this has to be expanded to LEFT/RIGHT */ - if (direction == D_UP || direction == D_DOWN) { - if (thing == THING_WINDOW) - /* Let’s see if we can perform up/down focus in the current container */ - if (focus_window_in_container(conn, container, direction)) - return; - - if (direction == D_DOWN && cell_exists(t_ws, current_col, current_row+1)) - new_row = current_row + t_ws->table[current_col][current_row]->rowspan; - else if (direction == D_UP && cell_exists(c_ws, current_col, current_row-1)) { - /* Set new_row as a sane default, but it may get overwritten in a second */ - new_row--; - - /* Search from the top to correctly handle rowspanned containers */ - for (int rows = 0; rows < current_row; rows += t_ws->table[current_col][rows]->rowspan) { - if (new_row > (rows + (t_ws->table[current_col][rows]->rowspan - 1))) - continue; - - new_row = rows; - break; - } - } else { - /* Let’s see if there is a screen down/up there to which we can switch */ - LOG("container is at %d with height %d\n", container->y, container->height); - i3Screen *screen; - int destination_y = (direction == D_UP ? (container->y - 1) : (container->y + container->height + 1)); - if ((screen = get_screen_containing(container->x, destination_y)) == NULL) { - LOG("Wrapping screen around vertically\n"); - /* No screen found? Then wrap */ - screen = get_screen_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->screen); - } - t_ws = screen->current_workspace; - new_row = (direction == D_UP ? (t_ws->rows - 1) : 0); - } - - check_colrow_boundaries(); - - LOG("new_col = %d, new_row = %d\n", new_col, new_row); - if (t_ws->table[new_col][new_row]->currently_focused == NULL) { - LOG("Cell empty, checking for colspanned client above...\n"); - for (int cols = 0; cols < new_col; cols += t_ws->table[cols][new_row]->colspan) { - if (new_col > (cols + (t_ws->table[cols][new_row]->colspan - 1))) - continue; - - new_col = cols; - break; - } - LOG("Fixed it to new col %d\n", new_col); - } - } else if (direction == D_LEFT || direction == D_RIGHT) { - if (direction == D_RIGHT && cell_exists(t_ws, current_col+1, current_row)) - new_col = current_col + t_ws->table[current_col][current_row]->colspan; - else if (direction == D_LEFT && cell_exists(t_ws, current_col-1, current_row)) { - /* Set new_col as a sane default, but it may get overwritten in a second */ - new_col--; - - /* Search from the left to correctly handle colspanned containers */ - for (int cols = 0; cols < current_col; cols += t_ws->table[cols][current_row]->colspan) { - if (new_col > (cols + (t_ws->table[cols][current_row]->colspan - 1))) - continue; - - new_col = cols; - break; - } - } else { - /* Let’s see if there is a screen left/right here to which we can switch */ - LOG("container is at %d with width %d\n", container->x, container->width); - i3Screen *screen; - int destination_x = (direction == D_LEFT ? (container->x - 1) : (container->x + container->width + 1)); - if ((screen = get_screen_containing(destination_x, container->y)) == NULL) { - LOG("Wrapping screen around horizontally\n"); - screen = get_screen_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->screen); - } - t_ws = screen->current_workspace; - new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0); - } - - check_colrow_boundaries(); - - LOG("new_col = %d, new_row = %d\n", new_col, new_row); - if (t_ws->table[new_col][new_row]->currently_focused == NULL) { - LOG("Cell empty, checking for rowspanned client above...\n"); - for (int rows = 0; rows < new_row; rows += t_ws->table[new_col][rows]->rowspan) { - if (new_row > (rows + (t_ws->table[new_col][rows]->rowspan - 1))) - continue; - - new_row = rows; - break; - } - LOG("Fixed it to new row %d\n", new_row); - } +/* + * Interprets a ctype=cvalue pair and adds it to the current match + * specification. + * + */ +void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) { + DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue); + + if (strcmp(ctype, "class") == 0) { + current_match->class = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "instance") == 0) { + current_match->instance = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "window_role") == 0) { + current_match->role = regex_new(cvalue); + return; + } + + 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 { - LOG("direction unhandled\n"); - return; + current_match->con_id = (Con*)parsed; + printf("id as int = %p\n", current_match->con_id); } + return; + } + + 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; + } + + if (strcmp(ctype, "con_mark") == 0) { + current_match->mark = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "title") == 0) { + current_match->title = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "urgent") == 0) { + if (strcasecmp(cvalue, "latest") == 0 || + strcasecmp(cvalue, "newest") == 0 || + strcasecmp(cvalue, "recent") == 0 || + strcasecmp(cvalue, "last") == 0) { + current_match->urgent = U_LATEST; + } else if (strcasecmp(cvalue, "oldest") == 0 || + strcasecmp(cvalue, "first") == 0) { + current_match->urgent = U_OLDEST; + } + return; + } - check_colrow_boundaries(); - - if (t_ws->table[new_col][new_row]->currently_focused != NULL) - set_focus(conn, t_ws->table[new_col][new_row]->currently_focused, true); + ELOG("Unknown criterion: %s\n", ctype); } /* - * Tries to move the window inside its current container. - * - * Returns true if the window could be moved, false otherwise. + * Implementation of 'move [window|container] [to] workspace + * next|prev|next_on_output|prev_on_output'. * */ -static bool move_current_window_in_container(xcb_connection_t *conn, Client *client, - direction_t direction) { - assert(client->container != NULL); - - Client *other = (direction == D_UP ? CIRCLEQ_PREV(client, clients) : - CIRCLEQ_NEXT(client, clients)); - - if (other == CIRCLEQ_END(&(client->container->clients))) - return false; - - LOG("i can do that\n"); - /* We can move the client inside its current container */ - CIRCLEQ_REMOVE(&(client->container->clients), client, clients); - if (direction == D_UP) - CIRCLEQ_INSERT_BEFORE(&(client->container->clients), other, client, clients); - else CIRCLEQ_INSERT_AFTER(&(client->container->clients), other, client, clients); - render_layout(conn); - return true; +void cmd_move_con_to_workspace(I3_CMD, 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); + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } + + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("matching: %p / %s\n", current->con, current->con->name); + con_move_to_workspace(current->con, ws, true, false); + } + + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); } /* - * Moves the current window or whole container to the given direction, creating a column/row if - * necessary. + * Implementation of 'move [window|container] [to] workspace '. * */ -static void move_current_window(xcb_connection_t *conn, direction_t direction) { - LOG("moving window to direction %s\n", (direction == D_UP ? "up" : (direction == D_DOWN ? "down" : - (direction == D_LEFT ? "left" : "right")))); - /* Get current window */ - Container *container = CUR_CELL, - *new = NULL; - - /* There has to be a container, see focus_window() */ - assert(container != NULL); - - /* If there is no window or the dock window is focused, we’re done */ - if (container->currently_focused == NULL || - container->currently_focused->dock) - return; - - /* As soon as the client is moved away, the last focused client in the old - * container needs to get focus, if any. Therefore, we save it here. */ - Client *current_client = container->currently_focused; - Client *to_focus = get_last_focused_client(conn, container, current_client); +void cmd_move_con_to_workspace_name(I3_CMD, char *name) { + if (strncasecmp(name, "__i3_", strlen("__i3_")) == 0) { + LOG("You cannot switch to the i3 internal workspaces.\n"); + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } + + owindow *current; + + /* Error out early to not create a non-existing workspace (in + * workspace_get()) if we are not actually able to move anything. */ + if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) { + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } + + 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); + } + + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); +} - if (to_focus == NULL) { - to_focus = CIRCLEQ_NEXT_OR_NULL(&(container->clients), current_client, clients); - if (to_focus == NULL) - to_focus = CIRCLEQ_PREV_OR_NULL(&(container->clients), current_client, clients); +/* + * Implementation of 'resize grow|shrink [ px] [or ppt]'. + * + */ +void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resize_ppt) { + /* resize [ px] [or ppt] */ + DLOG("resizing in way %s, direction %s, px %s or ppt %s\n", way, direction, resize_px, resize_ppt); + // TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking + int px = atoi(resize_px); + int ppt = atoi(resize_ppt); + if (strcmp(way, "shrink") == 0) { + px *= -1; + ppt *= -1; + } + + Con *floating_con; + if ((floating_con = con_inside_floating(focused))) { + 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")); + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; } - switch (direction) { - case D_LEFT: - /* If we’re at the left-most position, move the rest of the table right */ - if (current_col == 0) { - expand_table_cols_at_head(c_ws); - new = CUR_CELL; - } else - new = CUR_TABLE[--current_col][current_row]; - break; - case D_RIGHT: - if (current_col == (c_ws->cols-1)) - expand_table_cols(c_ws); - - new = CUR_TABLE[++current_col][current_row]; - break; - case D_UP: - if (move_current_window_in_container(conn, current_client, D_UP)) - return; - - /* if we’re at the up-most position, move the rest of the table down */ - if (current_row == 0) { - expand_table_rows_at_head(c_ws); - new = CUR_CELL; - } else - new = CUR_TABLE[current_col][--current_row]; - break; - case D_DOWN: - if (move_current_window_in_container(conn, current_client, D_DOWN)) - return; - - if (current_row == (c_ws->rows-1)) - expand_table_rows(c_ws); - - new = CUR_TABLE[current_col][++current_row]; - break; - /* To make static analyzers happy: */ - default: - return; + if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) { + other = TAILQ_PREV(current, nodes_head, nodes); + } else { + other = TAILQ_NEXT(current, nodes); } + if (other == TAILQ_END(workspaces)) { + LOG("No other container in this direction found, cannot resize.\n"); + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } + LOG("other->percent = %f\n", other->percent); + LOG("current->percent before = %f\n", current->percent); + if (current->percent == 0.0) + 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"); + } + } - /* Remove it from the old container and put it into the new one */ - client_remove_from_container(conn, current_client, container, true); + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); +} - if (new->currently_focused != NULL) - CIRCLEQ_INSERT_AFTER(&(new->clients), new->currently_focused, current_client, clients); - else CIRCLEQ_INSERT_TAIL(&(new->clients), current_client, clients); - SLIST_INSERT_HEAD(&(new->workspace->focus_stack), current_client, focus_clients); +/* + * Implementation of 'border normal|none|1pixel|toggle'. + * + */ +void cmd_border(I3_CMD, char *border_style_str) { + DLOG("border style should be changed to %s\n", border_style_str); + owindow *current; + + 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); + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } + } + con_set_border_style(current->con, border_style); + } - /* Update data structures */ - current_client->container = new; - current_client->workspace = new->workspace; - container->currently_focused = to_focus; - new->currently_focused = current_client; + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); +} - Workspace *workspace = container->workspace; +/* + * Implementation of 'nop '. + * + */ +void cmd_nop(I3_CMD, char *comment) { + LOG("-------------------------------------------------\n"); + LOG(" NOP: %s\n", comment); + LOG("-------------------------------------------------\n"); +} - /* delete all empty columns/rows */ - cleanup_table(conn, workspace); +/* + * Implementation of 'append_layout '. + * + */ +void cmd_append_layout(I3_CMD, char *path) { + LOG("Appending layout \"%s\"\n", path); + tree_append_json(path); + + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); +} - /* Fix colspan/rowspan if it’d overlap */ - fix_colrowspan(conn, workspace); +/* + * Implementation of 'workspace next|prev|next_on_output|prev_on_output'. + * + */ +void cmd_workspace(I3_CMD, char *which) { + Con *ws; + + DLOG("which=%s\n", which); + + 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); + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } + + workspace_show(ws); + + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); +} - render_workspace(conn, workspace->screen, workspace); - xcb_flush(conn); +/* + * Implementation of 'workspace back_and_forth'. + * + */ +void cmd_workspace_back_and_forth(I3_CMD) { + workspace_back_and_forth(); - set_focus(conn, current_client, true); + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); } -static void move_current_container(xcb_connection_t *conn, direction_t direction) { - LOG("moving container to direction %s\n", (direction == D_UP ? "up" : (direction == D_DOWN ? "down" : - (direction == D_LEFT ? "left" : "right")))); - /* Get current window */ - Container *container = CUR_CELL, - *new = NULL; - - Container **old = &CUR_CELL; - - /* There has to be a container, see focus_window() */ - assert(container != NULL); - - switch (direction) { - case D_LEFT: - /* If we’re at the left-most position, move the rest of the table right */ - if (current_col == 0) { - expand_table_cols_at_head(c_ws); - new = CUR_CELL; - old = &CUR_TABLE[current_col+1][current_row]; - } else - new = CUR_TABLE[--current_col][current_row]; - break; - case D_RIGHT: - if (current_col == (c_ws->cols-1)) - expand_table_cols(c_ws); - - new = CUR_TABLE[++current_col][current_row]; - break; - case D_UP: - /* if we’re at the up-most position, move the rest of the table down */ - if (current_row == 0) { - expand_table_rows_at_head(c_ws); - new = CUR_CELL; - old = &CUR_TABLE[current_col][current_row+1]; - } else - new = CUR_TABLE[current_col][--current_row]; - break; - case D_DOWN: - if (current_row == (c_ws->rows-1)) - expand_table_rows(c_ws); - - new = CUR_TABLE[current_col][++current_row]; - break; - /* To make static analyzers happy: */ - default: - return; +/* + * Implementation of 'workspace ' + * + */ +void cmd_workspace_name(I3_CMD, char *name) { + if (strncasecmp(name, "__i3_", strlen("__i3_")) == 0) { + LOG("You cannot switch to the i3 internal workspaces.\n"); + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } + + DLOG("should switch to workspace %s\n", name); + + Con *ws = con_get_workspace(focused); + + /* Check if the command wants to switch to the current workspace */ + if (strcmp(ws->name, name) == 0) { + DLOG("This workspace is already focused.\n"); + if (config.workspace_auto_back_and_forth) { + workspace_back_and_forth(); + tree_render(); } + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } - LOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row); + workspace_show_by_name(name); - /* Swap the containers */ - int col = new->col; - int row = new->row; + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); +} - *old = new; - new->col = container->col; - new->row = container->row; +/* + * Implementation of 'mark ' + * + */ +void cmd_mark(I3_CMD, char *mark) { + DLOG("Clearing all windows which have that mark first\n"); - CUR_CELL = container; - container->col = col; - container->row = row; + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) { + if (con->mark && strcmp(con->mark, mark) == 0) + FREE(con->mark); + } - Workspace *workspace = container->workspace; + DLOG("marking window with str %s\n", mark); + owindow *current; - /* delete all empty columns/rows */ - cleanup_table(conn, workspace); + HANDLE_EMPTY_MATCH; - /* Fix colspan/rowspan if it’d overlap */ - fix_colrowspan(conn, workspace); + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("matching: %p / %s\n", current->con, current->con->name); + current->con->mark = sstrdup(mark); + } - render_layout(conn); + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); } /* - * "Snaps" the current container (not possible for windows, because it works at table base) - * to the given direction, that is, adjusts cellspan/rowspan + * Implementation of 'mode '. * */ -static void snap_current_container(xcb_connection_t *conn, direction_t direction) { - LOG("snapping container to direction %d\n", direction); - - Container *container = CUR_CELL; - - assert(container != NULL); - - switch (direction) { - case D_LEFT: - /* Snap to the left is actually a move to the left and then a snap right */ - if (!cell_exists(container->workspace, container->col - 1, container->row) || - CUR_TABLE[container->col-1][container->row]->currently_focused != NULL) { - LOG("cannot snap to left - the cell is already used\n"); - return; - } - - move_current_window(conn, D_LEFT); - snap_current_container(conn, D_RIGHT); - return; - case D_RIGHT: { - /* Check if the cell is used */ - int new_col = container->col + container->colspan; - for (int i = 0; i < container->rowspan; i++) - if (!cell_exists(container->workspace, new_col, container->row + i) || - CUR_TABLE[new_col][container->row + i]->currently_focused != NULL) { - LOG("cannot snap to right - the cell is already used\n"); - return; - } - - /* Check if there are other cells with rowspan, which are in our way. - * If so, reduce their rowspan. */ - for (int i = container->row-1; i >= 0; i--) { - LOG("we got cell %d, %d with rowspan %d\n", - new_col, i, CUR_TABLE[new_col][i]->rowspan); - while ((CUR_TABLE[new_col][i]->rowspan-1) >= (container->row - i)) - CUR_TABLE[new_col][i]->rowspan--; - LOG("new rowspan = %d\n", CUR_TABLE[new_col][i]->rowspan); - } - - container->colspan++; - break; - } - case D_UP: - if (!cell_exists(container->workspace, container->col, container->row - 1) || - CUR_TABLE[container->col][container->row-1]->currently_focused != NULL) { - LOG("cannot snap to top - the cell is already used\n"); - return; - } - - move_current_window(conn, D_UP); - snap_current_container(conn, D_DOWN); - return; - case D_DOWN: { - LOG("snapping down\n"); - int new_row = container->row + container->rowspan; - for (int i = 0; i < container->colspan; i++) - if (!cell_exists(container->workspace, container->col + i, new_row) || - CUR_TABLE[container->col + i][new_row]->currently_focused != NULL) { - LOG("cannot snap down - the cell is already used\n"); - return; - } - - for (int i = container->col-1; i >= 0; i--) { - LOG("we got cell %d, %d with colspan %d\n", - i, new_row, CUR_TABLE[i][new_row]->colspan); - while ((CUR_TABLE[i][new_row]->colspan-1) >= (container->col - i)) - CUR_TABLE[i][new_row]->colspan--; - LOG("new colspan = %d\n", CUR_TABLE[i][new_row]->colspan); - - } - - container->rowspan++; - break; - } - /* To make static analyzers happy: */ - default: - return; - } +void cmd_mode(I3_CMD, char *mode) { + DLOG("mode=%s\n", mode); + switch_mode(mode); - render_layout(conn); + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); } -static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *client, int workspace) { - /* t_ws (to workspace) is just a container pointer to the workspace we’re switching to */ - Workspace *t_ws = workspace_get(workspace-1), - *old_ws = client->workspace; - - LOG("moving floating\n"); - - workspace_initialize(t_ws, c_ws->screen); +/* + * Implementation of 'move [window|container] [to] output '. + * + */ +void cmd_move_con_to_output(I3_CMD, char *name) { + owindow *current; + + DLOG("should move window to output %s\n", name); + + 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"); + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } + + /* get visible workspace on output */ + Con *ws = NULL; + GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child)); + if (!ws) { + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } + + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("matching: %p / %s\n", current->con, current->con->name); + con_move_to_workspace(current->con, ws, true, false); + } + + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); +} - /* Check if there is already a fullscreen client on the destination workspace and - * stop moving if so. */ - if (client->fullscreen && (t_ws->fullscreen_client != NULL)) { - LOG("Not moving: Fullscreen client already existing on destination workspace.\n"); - return; - } +/* + * Implementation of 'floating enable|disable|toggle' + * + */ +void cmd_floating(I3_CMD, char *floating_mode) { + owindow *current; - floating_assign_to_workspace(client, t_ws); + DLOG("floating_mode=%s\n", floating_mode); - /* If we’re moving it to an invisible screen, we need to unmap it */ - if (!workspace_is_visible(t_ws)) { - LOG("This workspace is not visible, unmapping\n"); - client_unmap(conn, client); - } else { - /* If this is not the case, we move the window to a workspace - * which is on another screen, so we also need to adjust its - * coordinates. */ - LOG("before x = %d, y = %d\n", client->rect.x, client->rect.y); - uint32_t relative_x = client->rect.x - old_ws->rect.x, - relative_y = client->rect.y - old_ws->rect.y; - LOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y); - client->rect.x = t_ws->rect.x + relative_x; - client->rect.y = t_ws->rect.y + relative_y; - LOG("after x = %d, y = %d\n", client->rect.x, client->rect.y); - reposition_client(conn, client); - xcb_flush(conn); - } + HANDLE_EMPTY_MATCH; - /* Configure the window above all tiling windows (or below a fullscreen - * window, if any) */ - if (t_ws->fullscreen_client != NULL) { - uint32_t values[] = { t_ws->fullscreen_client->frame, XCB_STACK_MODE_BELOW }; - xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); + 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 { - Client *last_tiling; - SLIST_FOREACH(last_tiling, &(t_ws->focus_stack), focus_clients) - if (!client_is_floating(last_tiling)) - break; - if (last_tiling != SLIST_END(&(t_ws->focus_stack))) { - uint32_t values[] = { last_tiling->frame, XCB_STACK_MODE_ABOVE }; - xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); - } + 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); + } } + } - LOG("done\n"); - - render_layout(conn); - - if (workspace_is_visible(t_ws)) - set_focus(conn, client, true); + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); } /* - * Moves the currently selected window to the given workspace + * Implementation of 'move workspace to [output] '. * */ -static void move_current_window_to_workspace(xcb_connection_t *conn, int workspace) { - LOG("Moving current window to workspace %d\n", workspace); - - Container *container = CUR_CELL; - - assert(container != NULL); - - /* t_ws (to workspace) is just a container pointer to the workspace we’re switching to */ - Workspace *t_ws = workspace_get(workspace-1); - - Client *current_client = container->currently_focused; - if (current_client == NULL) { - LOG("No currently focused client in current container.\n"); - return; - } - Client *to_focus = CIRCLEQ_NEXT_OR_NULL(&(container->clients), current_client, clients); - if (to_focus == NULL) - to_focus = CIRCLEQ_PREV_OR_NULL(&(container->clients), current_client, clients); - - workspace_initialize(t_ws, container->workspace->screen); - /* Check if there is already a fullscreen client on the destination workspace and - * stop moving if so. */ - if (current_client->fullscreen && (t_ws->fullscreen_client != NULL)) { - LOG("Not moving: Fullscreen client already existing on destination workspace.\n"); - return; +void cmd_move_workspace_to_output(I3_CMD, 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"); + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; } - Container *to_container = t_ws->table[t_ws->current_col][t_ws->current_row]; - - assert(to_container != NULL); - - client_remove_from_container(conn, current_client, container, true); - if (container->workspace->fullscreen_client == current_client) - container->workspace->fullscreen_client = NULL; - - /* TODO: insert it to the correct position */ - CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients); - - SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients); - LOG("Moved.\n"); - - current_client->container = to_container; - current_client->workspace = to_container->workspace; - container->currently_focused = to_focus; - to_container->currently_focused = current_client; + 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("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\"}"); + } - /* If we’re moving it to an invisible screen, we need to unmap it */ - if (!workspace_is_visible(to_container->workspace)) { - LOG("This workspace is not visible, unmapping\n"); - client_unmap(conn, current_client); - } else { - if (current_client->fullscreen) { - LOG("Calling client_enter_fullscreen again\n"); - client_enter_fullscreen(conn, current_client); - } + /* detach from the old output and attach to the new output */ + bool workspace_was_visible = workspace_is_visible(ws); + Con *old_content = ws->parent; + con_detach(ws); + if (workspace_was_visible) { + /* 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); - /* delete all empty columns/rows */ - cleanup_table(conn, container->workspace); + /* 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)); - render_layout(conn); + 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); + } + } - if (workspace_is_visible(to_container->workspace)) - set_focus(conn, current_client, true); + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); } /* - * Jumps to the given window class / title. - * Title is matched using strstr, that is, matches if it appears anywhere - * in the string. Regular expressions seem to be a bit overkill here. However, - * if we need them for something else somewhen, we may introduce them here, too. + * Implementation of 'split v|h|vertical|horizontal'. * */ -static void jump_to_window(xcb_connection_t *conn, const char *arguments) { - char *classtitle; - Client *client; - - /* The first character is a quote, this was checked before */ - classtitle = sstrdup(arguments+1); - /* The last character is a quote, we just set it to NULL */ - classtitle[strlen(classtitle)-1] = '\0'; - - if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) { - free(classtitle); - LOG("No matching client found.\n"); - return; - } - - free(classtitle); - set_focus(conn, client, true); +void cmd_split(I3_CMD, char *direction) { + /* TODO: use matches */ + LOG("splitting in direction %c\n", direction[0]); + tree_split(focused, (direction[0] == 'v' ? VERT : HORIZ)); + + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); } /* - * Jump directly to the specified workspace, row and col. - * Great for reaching windows that you always keep in the same spot (hello irssi, I'm looking at you) + * Implementaiton of 'kill [window|client]'. * */ -static void jump_to_container(xcb_connection_t *conn, const char *arguments) { - int ws, row, col; - int result; - - result = sscanf(arguments, "%d %d %d", &ws, &col, &row); - LOG("Jump called with %d parameters (\"%s\")\n", result, arguments); - - /* No match? Either no arguments were specified, or no numbers */ - if (result < 1) { - LOG("At least one valid argument required\n"); - return; +void cmd_kill(I3_CMD, char *kill_mode_str) { + if (kill_mode_str == NULL) + kill_mode_str = "window"; + owindow *current; + + DLOG("kill_mode=%s\n", kill_mode_str); + + int kill_mode; + if (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); + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } + + /* 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); } + } - /* Move to the target workspace */ - workspace_show(conn, ws); - - if (result < 3) - return; - - LOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows); - - /* Move to row/col */ - if (row >= c_ws->rows) - row = c_ws->rows - 1; - if (col >= c_ws->cols) - col = c_ws->cols - 1; - - LOG("Jumping to col %d, row %d\n", col, row); - if (c_ws->table[col][row]->currently_focused != NULL) - set_focus(conn, c_ws->table[col][row]->currently_focused, true); + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); } /* - * Travels the focus stack by the given number of times (or once, if no argument - * was specified). That is, selects the window you were in before you focused - * the current window. - * - * The special values 'floating' (select the next floating window), 'tiling' - * (select the next tiling window), 'ft' (if the current window is floating, - * select the next tiling window and vice-versa) are also valid + * Implementation of 'exec [--no-startup-id] '. * */ -static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) { - /* Start count at -1 to always skip the first element */ - int times, count = -1; - Client *current; - bool floating_criteria; - - /* Either it’s one of the special values… */ - if (strcasecmp(arguments, "floating") == 0) { - floating_criteria = true; - } else if (strcasecmp(arguments, "tiling") == 0) { - floating_criteria = false; - } else if (strcasecmp(arguments, "ft") == 0) { - Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); - if (last_focused == SLIST_END(&(c_ws->focus_stack))) { - LOG("Cannot select the next floating/tiling client because there is no client at all\n"); - return; - } - - floating_criteria = !client_is_floating(last_focused); - } else { - /* …or a number was specified */ - if (sscanf(arguments, "%u", ×) != 1) { - LOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments); - times = 1; - } - - SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) { - if (++count < times) { - LOG("Skipping\n"); - continue; - } - - LOG("Focussing\n"); - set_focus(conn, current, true); - break; - } - return; - } +void cmd_exec(I3_CMD, char *nosn, char *command) { + bool no_startup_id = (nosn != NULL); + + DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id); + start_application(command, no_startup_id); - /* Select the next client matching the criteria parsed above */ - SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) - if (client_is_floating(current) == floating_criteria) { - set_focus(conn, current, true); - break; - } + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); } /* - * Goes through the list of arguments (for exec()) and checks if the given argument - * is present. If not, it copies the arguments (because we cannot realloc it) and - * appends the given argument. + * Implementation of 'focus left|right|up|down'. * */ -static char **append_argument(char **original, char *argument) { - int num_args; - for (num_args = 0; original[num_args] != NULL; num_args++) { - LOG("original argument: \"%s\"\n", original[num_args]); - /* If the argument is already present we return the original pointer */ - if (strcmp(original[num_args], argument) == 0) - return original; - } - /* Copy the original array */ - char **result = smalloc((num_args+2) * sizeof(char*)); - memcpy(result, original, num_args * sizeof(char*)); - result[num_args] = argument; - result[num_args+1] = NULL; - - return result; +void cmd_focus_direction(I3_CMD, char *direction) { + if (focused && + focused->type != CT_WORKSPACE && + focused->fullscreen_mode != CF_NONE) { + LOG("Cannot change focus while in fullscreen mode.\n"); + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } + + 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); + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } + + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); } -/* - * Switch to next or previous existing workspace +/* + * Implementation of 'focus tiling|floating|mode_toggle'. * */ -static void next_previous_workspace(xcb_connection_t *conn, int direction) { - Workspace *ws = c_ws; - - if (direction == 'n') { - while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) { - if (ws->screen == NULL) - continue; - - workspace_show(conn, ws->num + 1); - return; - } - } else if (direction == 'p') { - while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) { - if (ws->screen == NULL) - continue; - - workspace_show(conn, ws->num + 1); - return; - } +void cmd_focus_window_mode(I3_CMD, char *window_mode) { + if (focused && + focused->type != CT_WORKSPACE && + focused->fullscreen_mode != CF_NONE) { + LOG("Cannot change focus while in fullscreen mode.\n"); + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } + + 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; -static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, const char *command) { - int first, second; - resize_orientation_t orientation = O_VERTICAL; - Container *con = last_focused->container; - Workspace *ws = con->workspace; - - if (STARTS_WITH(command, "left")) { - if (con->col == 0) - return; - first = con->col - 1; - second = con->col; - command += strlen("left"); - } else if (STARTS_WITH(command, "right")) { - first = con->col + (con->colspan - 1); - LOG("column %d\n", first); - - if (!cell_exists(ws, first, con->row) || - (first == (ws->cols-1))) - return; - - second = first + 1; - command += strlen("right"); - } else if (STARTS_WITH(command, "top")) { - if (con->row == 0) - return; - first = con->row - 1; - second = con->row; - orientation = O_HORIZONTAL; - command += strlen("top"); - } else if (STARTS_WITH(command, "bottom")) { - first = con->row + (con->rowspan - 1); - if (!cell_exists(ws, con->col, first) || - (first == (ws->rows-1))) - return; - - second = first + 1; - orientation = O_HORIZONTAL; - command += strlen("bottom"); - } else { - LOG("Syntax: resize [+|-]\n"); - return; + con_focus(con_descend_focused(current)); + break; } + } - int pixels = atoi(command); - if (pixels == 0) - return; - - resize_container(conn, ws, first, second, orientation, pixels); + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); } /* - * Parses a command, see file CMDMODE for more information + * Implementation of 'focus parent|child'. * */ -void parse_command(xcb_connection_t *conn, const char *command) { - LOG("--- parsing command \"%s\" ---\n", command); - /* Get the first client from focus stack because floating clients are not - * in any container, therefore CUR_CELL is not appropriate. */ - Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); - if (last_focused == SLIST_END(&(c_ws->focus_stack))) - last_focused = NULL; - - /* Hmm, just to be sure */ - if (command[0] == '\0') - return; - - /* Is it an ? Then execute the given command. */ - if (STARTS_WITH(command, "exec ")) { - LOG("starting \"%s\"\n", command + strlen("exec ")); - start_application(command+strlen("exec ")); - return; - } - - if (STARTS_WITH(command, "mark")) { - if (last_focused == NULL) { - LOG("There is no window to mark\n"); - return; - } - const char *rest = command + strlen("mark"); - while (*rest == ' ') - rest++; - if (*rest == '\0') { - LOG("interactive mark starting\n"); - start_application("i3-input -p 'mark ' -l 1 -P 'Mark: '"); - } else { - LOG("mark with \"%s\"\n", rest); - client_mark(conn, last_focused, rest); - } - return; - } - - if (STARTS_WITH(command, "goto")) { - const char *rest = command + strlen("goto"); - while (*rest == ' ') - rest++; - if (*rest == '\0') { - LOG("interactive go to mark starting\n"); - start_application("i3-input -p 'goto ' -l 1 -P 'Goto: '"); - } else { - LOG("go to \"%s\"\n", rest); - jump_to_mark(conn, rest); - } - return; - } - - if (STARTS_WITH(command, "stack-limit ")) { - if (last_focused == NULL || client_is_floating(last_focused)) { - LOG("No container focused\n"); - return; - } - const char *rest = command + strlen("stack-limit "); - if (strncmp(rest, "rows ", strlen("rows ")) == 0) { - last_focused->container->stack_limit = STACK_LIMIT_ROWS; - rest += strlen("rows "); - } else if (strncmp(rest, "cols ", strlen("cols ")) == 0) { - last_focused->container->stack_limit = STACK_LIMIT_COLS; - rest += strlen("cols "); - } else { - LOG("Syntax: stack-limit \n"); - return; - } - - last_focused->container->stack_limit_value = atoi(rest); - if (last_focused->container->stack_limit_value == 0) - last_focused->container->stack_limit = STACK_LIMIT_NONE; - - return; - } - - if (STARTS_WITH(command, "resize ")) { - if (last_focused == NULL) - return; - const char *rest = command + strlen("resize "); - parse_resize_command(conn, last_focused, rest); - return; - } - - if (STARTS_WITH(command, "mode ")) { - const char *rest = command + strlen("mode "); - switch_mode(conn, rest); - return; - } - - /* Is it an ? */ - if (STARTS_WITH(command, "exit")) { - LOG("User issued exit-command, exiting without error.\n"); - exit(EXIT_SUCCESS); - } - - /* Is it a ? */ - if (STARTS_WITH(command, "reload")) { - load_configuration(conn, NULL, true); - return; - } - - /* Is it ? Then restart in place. */ - if (STARTS_WITH(command, "restart")) { - LOG("restarting \"%s\"...\n", start_argv[0]); - /* make sure -a is in the argument list or append it */ - start_argv = append_argument(start_argv, "-a"); +void cmd_focus_level(I3_CMD, char *level) { + if (focused && + focused->type != CT_WORKSPACE && + focused->fullscreen_mode != CF_NONE) { + LOG("Cannot change focus while in fullscreen mode.\n"); + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } + + DLOG("level = %s\n", level); + + if (strcmp(level, "parent") == 0) + level_up(); + else level_down(); + + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); +} - execvp(start_argv[0], start_argv); - /* not reached */ - } +/* + * Implementation of 'focus'. + * + */ +void cmd_focus(I3_CMD) { + DLOG("current_match = %p\n", current_match); + if (focused && + focused->type != CT_WORKSPACE && + focused->fullscreen_mode != CF_NONE) { + LOG("Cannot change focus while in fullscreen mode.\n"); + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } + + owindow *current; + + if (match_is_empty(current_match)) { + ELOG("You have to specify which window/container should be focused.\n"); + ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n"); + + sasprintf(&(cmd_output->json_output), + "{\"success\":false, \"error\":\"You have to " + "specify which window/container should be focused\"}"); + return; + } + + 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); + + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); +} - if (STARTS_WITH(command, "kill")) { - if (last_focused == NULL) { - LOG("There is no window to kill\n"); - return; - } +/* + * Implementation of 'fullscreen [global]'. + * + */ +void cmd_fullscreen(I3_CMD, char *fullscreen_mode) { + if (fullscreen_mode == NULL) + fullscreen_mode = "output"; + 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, (strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT)); + } + + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); +} - LOG("Killing current window\n"); - client_kill(conn, last_focused); - return; +/* + * Implementation of 'move [ [px]]'. + * + */ +void cmd_move_direction(I3_CMD, char *direction, char *move_px) { + // TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking + int px = atoi(move_px); + + /* 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 ? D_RIGHT : + (strcmp(direction, "left") == 0 ? D_LEFT : + (strcmp(direction, "up") == 0 ? D_UP : + D_DOWN)))); + cmd_output->needs_tree_render = true; + } + + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); +} - /* Is it a jump to a specified workspace, row, col? */ - if (STARTS_WITH(command, "jump ")) { - const char *arguments = command + strlen("jump "); - if (arguments[0] == '"') - jump_to_window(conn, arguments); - else jump_to_container(conn, arguments); - return; +/* + * Implementation of 'layout default|stacked|stacking|tabbed'. + * + */ +void cmd_layout(I3_CMD, char *layout_str) { + if (strcmp(layout_str, "stacking") == 0) + layout_str = "stacked"; + DLOG("changing layout to %s\n", layout_str); + owindow *current; + int layout = (strcmp(layout_str, "default") == 0 ? L_DEFAULT : + (strcmp(layout_str, "stacked") == 0 ? L_STACKED : + L_TABBED)); + + /* 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); } + } - /* Should we travel the focus stack? */ - if (STARTS_WITH(command, "focus")) { - const char *arguments = command + strlen("focus "); - travel_focus_stack(conn, arguments); - return; - } + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); +} - /* Is it 'f' for fullscreen? */ - if (command[0] == 'f') { - if (last_focused == NULL) - return; - client_toggle_fullscreen(conn, last_focused); - return; - } +/* + * Implementaiton of 'exit'. + * + */ +void cmd_exit(I3_CMD) { + LOG("Exiting due to user command.\n"); + exit(0); - /* Is it just 's' for stacking or 'd' for default? */ - if ((command[0] == 's' || command[0] == 'd' || command[0] == 'T') && (command[1] == '\0')) { - if (last_focused != NULL && client_is_floating(last_focused)) { - LOG("not switching, this is a floating client\n"); - return; - } - LOG("Switching mode for current container\n"); - int new_mode = MODE_DEFAULT; - if (command[0] == 's') - new_mode = MODE_STACK; - if (command[0] == 'T') - new_mode = MODE_TABBED; - switch_layout_mode(conn, CUR_CELL, new_mode); - return; - } + /* unreached */ +} - /* Is it 'bn' (border normal), 'bp' (border 1pixel) or 'bb' (border borderless)? */ - /* or even 'bt' (toggle border: 'bp' -> 'bb' -> 'bn' ) */ - if (command[0] == 'b') { - if (last_focused == NULL) { - LOG("No window focused, cannot change border type\n"); - return; - } - - char com = command[1]; - if (command[1] == 't') { - if (last_focused->titlebar_position == TITLEBAR_TOP && - !last_focused->borderless) - com = 'p'; - else if (last_focused->titlebar_position == TITLEBAR_OFF && - !last_focused->borderless) - com = 'b'; - else com = 'n'; - } - - client_change_border(conn, last_focused, com); - return; - } +/* + * Implementaiton of 'reload'. + * + */ +void cmd_reload(I3_CMD) { + 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 + cmd_output->json_output = sstrdup("{\"success\": true}"); +} - if (command[0] == 'H') { - LOG("Hiding all floating windows\n"); - floating_toggle_hide(conn, c_ws); - return; - } +/* + * Implementaiton of 'restart'. + * + */ +void cmd_restart(I3_CMD) { + LOG("restarting i3\n"); + i3_restart(false); - enum { WITH_WINDOW, WITH_CONTAINER, WITH_WORKSPACE, WITH_SCREEN } with = WITH_WINDOW; - - /* Is it a ? */ - if (command[0] == 'w') { - command++; - /* TODO: implement */ - if (command[0] == 'c') { - with = WITH_CONTAINER; - command++; - } else if (command[0] == 'w') { - with = WITH_WORKSPACE; - command++; - } else if (command[0] == 's') { - with = WITH_SCREEN; - command++; - } else { - LOG("not yet implemented.\n"); - return; - } - } + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); +} - /* Is it 't' for toggle tiling/floating? */ - if (command[0] == 't') { - if (with == WITH_WORKSPACE) { - c_ws->auto_float = !c_ws->auto_float; - LOG("autofloat is now %d\n", c_ws->auto_float); - return; - } - if (last_focused == NULL) { - LOG("Cannot toggle tiling/floating: workspace empty\n"); - return; - } +/* + * Implementaiton of 'open'. + * + */ +void cmd_open(I3_CMD) { + LOG("opening new container\n"); + Con *con = tree_open_con(NULL, NULL); + con_focus(con); + sasprintf(&(cmd_output->json_output), + "{\"success\":true, \"id\":%ld}", (long int)con); + + cmd_output->needs_tree_render = true; +} - Workspace *ws = last_focused->workspace; +/* + * Implementation of 'focus output '. + * + */ +void cmd_focus_output(I3_CMD, char *name) { + owindow *current; - toggle_floating_mode(conn, last_focused, false); - /* delete all empty columns/rows */ - cleanup_table(conn, ws); + DLOG("name = %s\n", name); - /* Fix colspan/rowspan if it’d overlap */ - fix_colrowspan(conn, ws); + HANDLE_EMPTY_MATCH; - render_workspace(conn, ws->screen, ws); + /* get the output */ + Output *current_output = NULL; + Output *output; - /* Re-focus the client because cleanup_table sets the focus to the last - * focused client inside a container only. */ - set_focus(conn, last_focused, true); + TAILQ_FOREACH(current, &owindows, owindows) + current_output = get_output_containing(current->con->rect.x, current->con->rect.y); + assert(current_output != NULL); - return; - } + output = get_output_from_string(current_output, name); - /* Is it 'n' or 'p' for next/previous workspace? (nw) */ - if ((command[0] == 'n' || command[0] == 'p') && command[1] == 'w') { - next_previous_workspace(conn, command[0]); - return; - } + if (!output) { + LOG("No such output found.\n"); + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } - /* It’s a normal */ - char *rest = NULL; - enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS; - direction_t direction; - int times = strtol(command, &rest, 10); - if (rest == NULL) { - LOG("Invalid command (\"%s\")\n", command); - return; - } + /* get visible workspace on output */ + Con *ws = NULL; + GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child)); + if (!ws) { + cmd_output->json_output = sstrdup("{\"sucess\": false}"); + return; + } - if (*rest == '\0') { - /* No rest? This was a workspace number, not a times specification */ - workspace_show(conn, times); - return; - } + workspace_show(ws); - if (*rest == 'm' || *rest == 's') { - action = (*rest == 'm' ? ACTION_MOVE : ACTION_SNAP); - rest++; - } + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); +} - int workspace = strtol(rest, &rest, 10); +/* + * Implementation of 'move scratchpad'. + * + */ +void cmd_move_scratchpad(I3_CMD) { + DLOG("should move window to scratchpad\n"); + owindow *current; - if (rest == NULL) { - LOG("Invalid command (\"%s\")\n", command); - return; - } + HANDLE_EMPTY_MATCH; - if (*rest == '\0') { - if (last_focused != NULL && client_is_floating(last_focused)) - move_floating_window_to_workspace(conn, last_focused, workspace); - else move_current_window_to_workspace(conn, workspace); - return; - } + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("matching: %p / %s\n", current->con, current->con->name); + scratchpad_move(current->con); + } - if (last_focused == NULL) { - LOG("Not performing (no window found)\n"); - return; - } + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); +} - if (client_is_floating(last_focused) && - (action != ACTION_FOCUS && action != ACTION_MOVE)) { - LOG("Not performing (floating)\n"); - return; +/* + * Implementation of 'scratchpad show'. + * + */ +void cmd_scratchpad_show(I3_CMD) { + 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); } + } - /* Now perform action to */ - while (*rest != '\0') { - if (*rest == 'h') - direction = D_LEFT; - else if (*rest == 'j') - direction = D_DOWN; - else if (*rest == 'k') - direction = D_UP; - else if (*rest == 'l') - direction = D_RIGHT; - else { - LOG("unknown direction: %c\n", *rest); - return; - } - rest++; - - if (action == ACTION_FOCUS) { - if (with == WITH_SCREEN) { - focus_thing(conn, direction, THING_SCREEN); - continue; - } - if (client_is_floating(last_focused)) { - floating_focus_direction(conn, last_focused, direction); - continue; - } - focus_thing(conn, direction, (with == WITH_WINDOW ? THING_WINDOW : THING_CONTAINER)); - continue; - } - - if (action == ACTION_MOVE) { - if (with == WITH_SCREEN) { - /* TODO: this should swap the screen’s contents - * (e.g. all workspaces) with the next/previous/… - * screen */ - LOG("Not yet implemented\n"); - continue; - } - if (client_is_floating(last_focused)) { - floating_move(conn, last_focused, direction); - continue; - } - if (with == WITH_WINDOW) - move_current_window(conn, direction); - else move_current_container(conn, direction); - continue; - } - - if (action == ACTION_SNAP) { - if (with == WITH_SCREEN) { - LOG("You cannot snap a screen (it makes no sense).\n"); - continue; - } - snap_current_container(conn, direction); - continue; - } - } + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + cmd_output->json_output = sstrdup("{\"success\": true}"); }