i3
|
00001 /* 00002 * vim:ts=8:expandtab 00003 * 00004 * i3 - an improved dynamic tiling window manager 00005 * 00006 * © 2009-2010 Michael Stapelberg and contributors 00007 * 00008 * See file LICENSE for license information. 00009 * 00010 */ 00011 #include <stdbool.h> 00012 #include <stdio.h> 00013 #include <stdlib.h> 00014 #include <assert.h> 00015 #include <unistd.h> 00016 #include <string.h> 00017 00018 #include <xcb/xcb.h> 00019 00020 #include "util.h" 00021 #include "data.h" 00022 #include "table.h" 00023 #include "layout.h" 00024 #include "i3.h" 00025 #include "randr.h" 00026 #include "client.h" 00027 #include "floating.h" 00028 #include "xcb.h" 00029 #include "config.h" 00030 #include "workspace.h" 00031 #include "commands.h" 00032 #include "resize.h" 00033 #include "log.h" 00034 #include "sighandler.h" 00035 #include "manage.h" 00036 #include "ipc.h" 00037 00038 bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { 00039 /* If this container is empty, we’re done */ 00040 if (container->currently_focused == NULL) 00041 return false; 00042 00043 /* Get the previous/next client or wrap around */ 00044 Client *candidate = NULL; 00045 if (direction == D_UP) { 00046 if ((candidate = CIRCLEQ_PREV_OR_NULL(&(container->clients), container->currently_focused, clients)) == NULL) 00047 candidate = CIRCLEQ_LAST(&(container->clients)); 00048 } 00049 else if (direction == D_DOWN) { 00050 if ((candidate = CIRCLEQ_NEXT_OR_NULL(&(container->clients), container->currently_focused, clients)) == NULL) 00051 candidate = CIRCLEQ_FIRST(&(container->clients)); 00052 } else ELOG("Direction not implemented!\n"); 00053 00054 /* If we could not switch, the container contains exactly one client. We return false */ 00055 if (candidate == container->currently_focused) 00056 return false; 00057 00058 /* Set focus */ 00059 set_focus(conn, candidate, true); 00060 00061 return true; 00062 } 00063 00064 typedef enum { THING_WINDOW, THING_CONTAINER, THING_SCREEN } thing_t; 00065 00066 static void jump_to_mark(xcb_connection_t *conn, const char *mark) { 00067 Client *current; 00068 LOG("Jumping to \"%s\"\n", mark); 00069 00070 Workspace *ws; 00071 TAILQ_FOREACH(ws, workspaces, workspaces) 00072 SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) { 00073 if (current->mark == NULL || strcmp(current->mark, mark) != 0) 00074 continue; 00075 00076 set_focus(conn, current, true); 00077 workspace_show(conn, current->workspace->num + 1); 00078 return; 00079 } 00080 00081 ELOG("No window with this mark found\n"); 00082 } 00083 00084 static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) { 00085 DLOG("focusing direction %d\n", direction); 00086 00087 int new_row = current_row, 00088 new_col = current_col; 00089 Container *container = CUR_CELL; 00090 Workspace *t_ws = c_ws; 00091 00092 /* Makes sure new_col and new_row are within bounds of the new workspace */ 00093 #define CHECK_COLROW_BOUNDARIES \ 00094 do { \ 00095 if (new_col >= t_ws->cols) \ 00096 new_col = (t_ws->cols - 1); \ 00097 if (new_row >= t_ws->rows) \ 00098 new_row = (t_ws->rows - 1); \ 00099 } while (0) 00100 00101 /* There always is a container. If not, current_col or current_row is wrong */ 00102 assert(container != NULL); 00103 00104 if (container->workspace->fullscreen_client != NULL) { 00105 LOG("You're in fullscreen mode. Forcing focus to operate on whole screens\n"); 00106 thing = THING_SCREEN; 00107 } 00108 00109 /* For focusing screens, situation is different: we get the rect 00110 * of the current screen, then get the screen which is on its 00111 * right/left/bottom/top and just switch to the workspace on 00112 * the target screen. */ 00113 if (thing == THING_SCREEN) { 00114 Output *cs = c_ws->output; 00115 assert(cs != NULL); 00116 Rect bounds = cs->rect; 00117 00118 if (direction == D_RIGHT) 00119 bounds.x += bounds.width; 00120 else if (direction == D_LEFT) 00121 bounds.x -= bounds.width; 00122 else if (direction == D_UP) 00123 bounds.y -= bounds.height; 00124 else bounds.y += bounds.height; 00125 00126 Output *target = get_output_containing(bounds.x, bounds.y); 00127 if (target == NULL) { 00128 DLOG("Target output NULL\n"); 00129 /* Wrap around if the target screen is out of bounds */ 00130 if (direction == D_RIGHT) 00131 target = get_output_most(D_LEFT, cs); 00132 else if (direction == D_LEFT) 00133 target = get_output_most(D_RIGHT, cs); 00134 else if (direction == D_UP) 00135 target = get_output_most(D_DOWN, cs); 00136 else target = get_output_most(D_UP, cs); 00137 } 00138 00139 DLOG("Switching to ws %d\n", target->current_workspace + 1); 00140 workspace_show(conn, target->current_workspace->num + 1); 00141 return; 00142 } 00143 00144 /* TODO: for horizontal default layout, this has to be expanded to LEFT/RIGHT */ 00145 if (direction == D_UP || direction == D_DOWN) { 00146 if (thing == THING_WINDOW) 00147 /* Let’s see if we can perform up/down focus in the current container */ 00148 if (focus_window_in_container(conn, container, direction)) 00149 return; 00150 00151 if (direction == D_DOWN && cell_exists(t_ws, current_col, current_row+1)) 00152 new_row = current_row + t_ws->table[current_col][current_row]->rowspan; 00153 else if (direction == D_UP && cell_exists(c_ws, current_col, current_row-1)) { 00154 /* Set new_row as a sane default, but it may get overwritten in a second */ 00155 new_row--; 00156 00157 /* Search from the top to correctly handle rowspanned containers */ 00158 for (int rows = 0; rows < current_row; rows += t_ws->table[current_col][rows]->rowspan) { 00159 if (new_row > (rows + (t_ws->table[current_col][rows]->rowspan - 1))) 00160 continue; 00161 00162 new_row = rows; 00163 break; 00164 } 00165 } else { 00166 /* Let’s see if there is a screen down/up there to which we can switch */ 00167 DLOG("container is at %d with height %d\n", container->y, container->height); 00168 Output *output; 00169 int destination_y = (direction == D_UP ? (container->y - 1) : (container->y + container->height + 1)); 00170 if ((output = get_output_containing(container->x, destination_y)) == NULL) { 00171 DLOG("Wrapping screen around vertically\n"); 00172 /* No screen found? Then wrap */ 00173 output = get_output_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->output); 00174 } 00175 t_ws = output->current_workspace; 00176 new_row = (direction == D_UP ? (t_ws->rows - 1) : 0); 00177 } 00178 00179 CHECK_COLROW_BOUNDARIES; 00180 00181 DLOG("new_col = %d, new_row = %d\n", new_col, new_row); 00182 if (t_ws->table[new_col][new_row]->currently_focused == NULL) { 00183 DLOG("Cell empty, checking for colspanned client above...\n"); 00184 for (int cols = 0; cols < new_col; cols += t_ws->table[cols][new_row]->colspan) { 00185 if (new_col > (cols + (t_ws->table[cols][new_row]->colspan - 1))) 00186 continue; 00187 00188 new_col = cols; 00189 DLOG("Fixed it to new col %d\n", new_col); 00190 break; 00191 } 00192 } 00193 00194 if (t_ws->table[new_col][new_row]->currently_focused == NULL) { 00195 DLOG("Cell still empty, checking for full cols above spanned width...\n"); 00196 DLOG("new_col = %d\n", new_col); 00197 DLOG("colspan = %d\n", container->colspan); 00198 for (int cols = new_col; 00199 cols < container->col + container->colspan; 00200 cols += t_ws->table[cols][new_row]->colspan) { 00201 DLOG("candidate: new_row = %d, cols = %d\n", new_row, cols); 00202 if (t_ws->table[cols][new_row]->currently_focused == NULL) 00203 continue; 00204 00205 new_col = cols; 00206 DLOG("Fixed it to new col %d\n", new_col); 00207 break; 00208 } 00209 } 00210 } else if (direction == D_LEFT || direction == D_RIGHT) { 00211 if (direction == D_RIGHT && cell_exists(t_ws, current_col+1, current_row)) 00212 new_col = current_col + t_ws->table[current_col][current_row]->colspan; 00213 else if (direction == D_LEFT && cell_exists(t_ws, current_col-1, current_row)) { 00214 /* Set new_col as a sane default, but it may get overwritten in a second */ 00215 new_col--; 00216 00217 /* Search from the left to correctly handle colspanned containers */ 00218 for (int cols = 0; cols < current_col; cols += t_ws->table[cols][current_row]->colspan) { 00219 if (new_col > (cols + (t_ws->table[cols][current_row]->colspan - 1))) 00220 continue; 00221 00222 new_col = cols; 00223 break; 00224 } 00225 } else { 00226 /* Let’s see if there is a screen left/right here to which we can switch */ 00227 DLOG("container is at %d with width %d\n", container->x, container->width); 00228 Output *output; 00229 int destination_x = (direction == D_LEFT ? (container->x - 1) : (container->x + container->width + 1)); 00230 if ((output = get_output_containing(destination_x, container->y)) == NULL) { 00231 DLOG("Wrapping screen around horizontally\n"); 00232 output = get_output_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->output); 00233 } 00234 t_ws = output->current_workspace; 00235 new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0); 00236 } 00237 00238 CHECK_COLROW_BOUNDARIES; 00239 00240 DLOG("new_col = %d, new_row = %d\n", new_col, new_row); 00241 if (t_ws->table[new_col][new_row]->currently_focused == NULL) { 00242 DLOG("Cell empty, checking for rowspanned client above...\n"); 00243 for (int rows = 0; rows < new_row; rows += t_ws->table[new_col][rows]->rowspan) { 00244 if (new_row > (rows + (t_ws->table[new_col][rows]->rowspan - 1))) 00245 continue; 00246 00247 new_row = rows; 00248 DLOG("Fixed it to new row %d\n", new_row); 00249 break; 00250 } 00251 } 00252 00253 if (t_ws->table[new_col][new_row]->currently_focused == NULL) { 00254 DLOG("Cell still empty, checking for full cols near full spanned height...\n"); 00255 DLOG("new_row = %d\n", new_row); 00256 DLOG("rowspan = %d\n", container->rowspan); 00257 for (int rows = new_row; 00258 rows < container->row + container->rowspan; 00259 rows += t_ws->table[new_col][rows]->rowspan) { 00260 DLOG("candidate: new_col = %d, rows = %d\n", new_col, rows); 00261 if (t_ws->table[new_col][rows]->currently_focused == NULL) 00262 continue; 00263 00264 new_row = rows; 00265 DLOG("Fixed it to new col %d\n", new_row); 00266 break; 00267 } 00268 } 00269 00270 } else { 00271 ELOG("direction unhandled\n"); 00272 return; 00273 } 00274 00275 CHECK_COLROW_BOUNDARIES; 00276 00277 if (t_ws->table[new_col][new_row]->currently_focused != NULL) 00278 set_focus(conn, t_ws->table[new_col][new_row]->currently_focused, true); 00279 } 00280 00281 /* 00282 * Tries to move the window inside its current container. 00283 * 00284 * Returns true if the window could be moved, false otherwise. 00285 * 00286 */ 00287 static bool move_current_window_in_container(xcb_connection_t *conn, Client *client, 00288 direction_t direction) { 00289 assert(client->container != NULL); 00290 00291 Client *other = (direction == D_UP ? CIRCLEQ_PREV(client, clients) : 00292 CIRCLEQ_NEXT(client, clients)); 00293 00294 if (other == CIRCLEQ_END(&(client->container->clients))) 00295 return false; 00296 00297 DLOG("i can do that\n"); 00298 /* We can move the client inside its current container */ 00299 CIRCLEQ_REMOVE(&(client->container->clients), client, clients); 00300 if (direction == D_UP) 00301 CIRCLEQ_INSERT_BEFORE(&(client->container->clients), other, client, clients); 00302 else CIRCLEQ_INSERT_AFTER(&(client->container->clients), other, client, clients); 00303 render_layout(conn); 00304 return true; 00305 } 00306 00307 /* 00308 * Moves the current window or whole container to the given direction, creating a column/row if 00309 * necessary. 00310 * 00311 */ 00312 static void move_current_window(xcb_connection_t *conn, direction_t direction) { 00313 LOG("moving window to direction %s\n", (direction == D_UP ? "up" : (direction == D_DOWN ? "down" : 00314 (direction == D_LEFT ? "left" : "right")))); 00315 /* Get current window */ 00316 Container *container = CUR_CELL, 00317 *new = NULL; 00318 00319 /* There has to be a container, see focus_window() */ 00320 assert(container != NULL); 00321 00322 /* If there is no window or the dock window is focused, we’re done */ 00323 if (container->currently_focused == NULL || 00324 container->currently_focused->dock) 00325 return; 00326 00327 /* As soon as the client is moved away, the last focused client in the old 00328 * container needs to get focus, if any. Therefore, we save it here. */ 00329 Client *current_client = container->currently_focused; 00330 Client *to_focus = get_last_focused_client(conn, container, current_client); 00331 00332 if (to_focus == NULL) { 00333 to_focus = CIRCLEQ_NEXT_OR_NULL(&(container->clients), current_client, clients); 00334 if (to_focus == NULL) 00335 to_focus = CIRCLEQ_PREV_OR_NULL(&(container->clients), current_client, clients); 00336 } 00337 00338 switch (direction) { 00339 case D_LEFT: 00340 /* If we’re at the left-most position, move the rest of the table right */ 00341 if (current_col == 0) { 00342 expand_table_cols_at_head(c_ws); 00343 new = CUR_CELL; 00344 } else 00345 new = CUR_TABLE[--current_col][current_row]; 00346 break; 00347 case D_RIGHT: 00348 if (current_col == (c_ws->cols-1)) 00349 expand_table_cols(c_ws); 00350 00351 new = CUR_TABLE[++current_col][current_row]; 00352 break; 00353 case D_UP: 00354 if (move_current_window_in_container(conn, current_client, D_UP)) 00355 return; 00356 00357 /* if we’re at the up-most position, move the rest of the table down */ 00358 if (current_row == 0) { 00359 expand_table_rows_at_head(c_ws); 00360 new = CUR_CELL; 00361 } else 00362 new = CUR_TABLE[current_col][--current_row]; 00363 break; 00364 case D_DOWN: 00365 if (move_current_window_in_container(conn, current_client, D_DOWN)) 00366 return; 00367 00368 if (current_row == (c_ws->rows-1)) 00369 expand_table_rows(c_ws); 00370 00371 new = CUR_TABLE[current_col][++current_row]; 00372 break; 00373 /* To make static analyzers happy: */ 00374 default: 00375 return; 00376 } 00377 00378 /* Remove it from the old container and put it into the new one */ 00379 client_remove_from_container(conn, current_client, container, true); 00380 00381 if (new->currently_focused != NULL) 00382 CIRCLEQ_INSERT_AFTER(&(new->clients), new->currently_focused, current_client, clients); 00383 else CIRCLEQ_INSERT_TAIL(&(new->clients), current_client, clients); 00384 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), current_client, focus_clients); 00385 00386 /* Update data structures */ 00387 current_client->container = new; 00388 current_client->workspace = new->workspace; 00389 container->currently_focused = to_focus; 00390 new->currently_focused = current_client; 00391 00392 Workspace *workspace = container->workspace; 00393 00394 /* delete all empty columns/rows */ 00395 cleanup_table(conn, workspace); 00396 00397 /* Fix colspan/rowspan if it’d overlap */ 00398 fix_colrowspan(conn, workspace); 00399 00400 render_workspace(conn, workspace->output, workspace); 00401 xcb_flush(conn); 00402 00403 set_focus(conn, current_client, true); 00404 } 00405 00406 static void move_current_container(xcb_connection_t *conn, direction_t direction) { 00407 LOG("moving container to direction %s\n", (direction == D_UP ? "up" : (direction == D_DOWN ? "down" : 00408 (direction == D_LEFT ? "left" : "right")))); 00409 /* Get current window */ 00410 Container *container = CUR_CELL, 00411 *new = NULL; 00412 00413 Container **old = &CUR_CELL; 00414 00415 /* There has to be a container, see focus_window() */ 00416 assert(container != NULL); 00417 00418 switch (direction) { 00419 case D_LEFT: 00420 /* If we’re at the left-most position, move the rest of the table right */ 00421 if (current_col == 0) { 00422 expand_table_cols_at_head(c_ws); 00423 new = CUR_CELL; 00424 old = &CUR_TABLE[current_col+1][current_row]; 00425 } else 00426 new = CUR_TABLE[--current_col][current_row]; 00427 break; 00428 case D_RIGHT: 00429 if (current_col == (c_ws->cols-1)) 00430 expand_table_cols(c_ws); 00431 00432 new = CUR_TABLE[++current_col][current_row]; 00433 break; 00434 case D_UP: 00435 /* if we’re at the up-most position, move the rest of the table down */ 00436 if (current_row == 0) { 00437 expand_table_rows_at_head(c_ws); 00438 new = CUR_CELL; 00439 old = &CUR_TABLE[current_col][current_row+1]; 00440 } else 00441 new = CUR_TABLE[current_col][--current_row]; 00442 break; 00443 case D_DOWN: 00444 if (current_row == (c_ws->rows-1)) 00445 expand_table_rows(c_ws); 00446 00447 new = CUR_TABLE[current_col][++current_row]; 00448 break; 00449 /* To make static analyzers happy: */ 00450 default: 00451 return; 00452 } 00453 00454 DLOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row); 00455 00456 /* Swap the containers */ 00457 int col = new->col; 00458 int row = new->row; 00459 00460 *old = new; 00461 new->col = container->col; 00462 new->row = container->row; 00463 00464 CUR_CELL = container; 00465 container->col = col; 00466 container->row = row; 00467 00468 Workspace *workspace = container->workspace; 00469 00470 /* delete all empty columns/rows */ 00471 cleanup_table(conn, workspace); 00472 00473 /* Fix colspan/rowspan if it’d overlap */ 00474 fix_colrowspan(conn, workspace); 00475 00476 render_layout(conn); 00477 } 00478 00479 /* 00480 * "Snaps" the current container (not possible for windows, because it works at table base) 00481 * to the given direction, that is, adjusts cellspan/rowspan 00482 * 00483 */ 00484 static void snap_current_container(xcb_connection_t *conn, direction_t direction) { 00485 LOG("snapping container to direction %d\n", direction); 00486 00487 Container *container = CUR_CELL; 00488 00489 assert(container != NULL); 00490 00491 switch (direction) { 00492 case D_LEFT: 00493 /* Snap to the left is actually a move to the left and then a snap right */ 00494 if (!cell_exists(container->workspace, container->col - 1, container->row) || 00495 CUR_TABLE[container->col-1][container->row]->currently_focused != NULL) { 00496 ELOG("cannot snap to left - the cell is already used\n"); 00497 return; 00498 } 00499 00500 move_current_window(conn, D_LEFT); 00501 snap_current_container(conn, D_RIGHT); 00502 return; 00503 case D_RIGHT: { 00504 /* Check if the cell is used */ 00505 int new_col = container->col + container->colspan; 00506 for (int i = 0; i < container->rowspan; i++) 00507 if (!cell_exists(container->workspace, new_col, container->row + i) || 00508 CUR_TABLE[new_col][container->row + i]->currently_focused != NULL) { 00509 ELOG("cannot snap to right - the cell is already used\n"); 00510 return; 00511 } 00512 00513 /* Check if there are other cells with rowspan, which are in our way. 00514 * If so, reduce their rowspan. */ 00515 for (int i = container->row-1; i >= 0; i--) { 00516 DLOG("we got cell %d, %d with rowspan %d\n", 00517 new_col, i, CUR_TABLE[new_col][i]->rowspan); 00518 while ((CUR_TABLE[new_col][i]->rowspan-1) >= (container->row - i)) 00519 CUR_TABLE[new_col][i]->rowspan--; 00520 DLOG("new rowspan = %d\n", CUR_TABLE[new_col][i]->rowspan); 00521 } 00522 00523 container->colspan++; 00524 break; 00525 } 00526 case D_UP: 00527 if (!cell_exists(container->workspace, container->col, container->row - 1) || 00528 CUR_TABLE[container->col][container->row-1]->currently_focused != NULL) { 00529 ELOG("cannot snap to top - the cell is already used\n"); 00530 return; 00531 } 00532 00533 move_current_window(conn, D_UP); 00534 snap_current_container(conn, D_DOWN); 00535 return; 00536 case D_DOWN: { 00537 DLOG("snapping down\n"); 00538 int new_row = container->row + container->rowspan; 00539 for (int i = 0; i < container->colspan; i++) 00540 if (!cell_exists(container->workspace, container->col + i, new_row) || 00541 CUR_TABLE[container->col + i][new_row]->currently_focused != NULL) { 00542 ELOG("cannot snap down - the cell is already used\n"); 00543 return; 00544 } 00545 00546 for (int i = container->col-1; i >= 0; i--) { 00547 DLOG("we got cell %d, %d with colspan %d\n", 00548 i, new_row, CUR_TABLE[i][new_row]->colspan); 00549 while ((CUR_TABLE[i][new_row]->colspan-1) >= (container->col - i)) 00550 CUR_TABLE[i][new_row]->colspan--; 00551 DLOG("new colspan = %d\n", CUR_TABLE[i][new_row]->colspan); 00552 00553 } 00554 00555 container->rowspan++; 00556 break; 00557 } 00558 /* To make static analyzers happy: */ 00559 default: 00560 return; 00561 } 00562 00563 render_layout(conn); 00564 } 00565 00566 static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *client, int workspace) { 00567 /* t_ws (to workspace) is just a container pointer to the workspace we’re switching to */ 00568 Workspace *t_ws = workspace_get(workspace-1), 00569 *old_ws = client->workspace; 00570 00571 LOG("moving floating\n"); 00572 00573 workspace_initialize(t_ws, c_ws->output, false); 00574 00575 /* Check if there is already a fullscreen client on the destination workspace and 00576 * stop moving if so. */ 00577 if (client->fullscreen && (t_ws->fullscreen_client != NULL)) { 00578 ELOG("Not moving: Fullscreen client already existing on destination workspace.\n"); 00579 return; 00580 } 00581 00582 floating_assign_to_workspace(client, t_ws); 00583 00584 /* If we’re moving it to an invisible screen, we need to unmap it */ 00585 if (!workspace_is_visible(t_ws)) { 00586 DLOG("This workspace is not visible, unmapping\n"); 00587 client_unmap(conn, client); 00588 } else { 00589 /* If this is not the case, we move the window to a workspace 00590 * which is on another screen, so we also need to adjust its 00591 * coordinates. */ 00592 DLOG("before x = %d, y = %d\n", client->rect.x, client->rect.y); 00593 uint32_t relative_x = client->rect.x - old_ws->rect.x, 00594 relative_y = client->rect.y - old_ws->rect.y; 00595 DLOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y); 00596 if (client->fullscreen) { 00597 client_enter_fullscreen(conn, client, false); 00598 memcpy(&(client->rect), &(t_ws->rect), sizeof(Rect)); 00599 } else { 00600 client->rect.x = t_ws->rect.x + relative_x; 00601 client->rect.y = t_ws->rect.y + relative_y; 00602 DLOG("after x = %d, y = %d\n", client->rect.x, client->rect.y); 00603 reposition_client(conn, client); 00604 xcb_flush(conn); 00605 } 00606 } 00607 00608 /* Configure the window above all tiling windows (or below a fullscreen 00609 * window, if any) */ 00610 if (t_ws->fullscreen_client != NULL) { 00611 uint32_t values[] = { t_ws->fullscreen_client->frame, XCB_STACK_MODE_BELOW }; 00612 xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); 00613 } else { 00614 Client *last_tiling; 00615 SLIST_FOREACH(last_tiling, &(t_ws->focus_stack), focus_clients) 00616 if (!client_is_floating(last_tiling)) 00617 break; 00618 if (last_tiling != SLIST_END(&(t_ws->focus_stack))) { 00619 uint32_t values[] = { last_tiling->frame, XCB_STACK_MODE_ABOVE }; 00620 xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); 00621 } 00622 } 00623 00624 DLOG("done\n"); 00625 00626 render_layout(conn); 00627 00628 if (workspace_is_visible(t_ws)) { 00629 client_warp_pointer_into(conn, client); 00630 set_focus(conn, client, true); 00631 } 00632 } 00633 00634 /* 00635 * Moves the currently selected window to the given workspace 00636 * 00637 */ 00638 static void move_current_window_to_workspace(xcb_connection_t *conn, int workspace) { 00639 LOG("Moving current window to workspace %d\n", workspace); 00640 00641 Container *container = CUR_CELL; 00642 00643 assert(container != NULL); 00644 00645 /* t_ws (to workspace) is just a container pointer to the workspace we’re switching to */ 00646 Workspace *t_ws = workspace_get(workspace-1); 00647 00648 Client *current_client = container->currently_focused; 00649 if (current_client == NULL) { 00650 ELOG("No currently focused client in current container.\n"); 00651 return; 00652 } 00653 Client *to_focus = CIRCLEQ_NEXT_OR_NULL(&(container->clients), current_client, clients); 00654 if (to_focus == NULL) 00655 to_focus = CIRCLEQ_PREV_OR_NULL(&(container->clients), current_client, clients); 00656 00657 workspace_initialize(t_ws, container->workspace->output, false); 00658 /* Check if there is already a fullscreen client on the destination workspace and 00659 * stop moving if so. */ 00660 if (current_client->fullscreen && (t_ws->fullscreen_client != NULL)) { 00661 ELOG("Not moving: Fullscreen client already existing on destination workspace.\n"); 00662 return; 00663 } 00664 00665 Container *to_container = t_ws->table[t_ws->current_col][t_ws->current_row]; 00666 00667 assert(to_container != NULL); 00668 00669 client_remove_from_container(conn, current_client, container, true); 00670 if (container->workspace->fullscreen_client == current_client) 00671 container->workspace->fullscreen_client = NULL; 00672 00673 /* TODO: insert it to the correct position */ 00674 CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients); 00675 00676 SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients); 00677 DLOG("Moved.\n"); 00678 00679 current_client->container = to_container; 00680 current_client->workspace = to_container->workspace; 00681 container->currently_focused = to_focus; 00682 to_container->currently_focused = current_client; 00683 00684 /* If we’re moving it to an invisible screen, we need to unmap it */ 00685 if (!workspace_is_visible(to_container->workspace)) { 00686 DLOG("This workspace is not visible, unmapping\n"); 00687 client_unmap(conn, current_client); 00688 } else { 00689 if (current_client->fullscreen) { 00690 DLOG("Calling client_enter_fullscreen again\n"); 00691 client_enter_fullscreen(conn, current_client, false); 00692 } 00693 } 00694 00695 /* delete all empty columns/rows */ 00696 cleanup_table(conn, container->workspace); 00697 00698 render_layout(conn); 00699 00700 if (workspace_is_visible(to_container->workspace)) { 00701 client_warp_pointer_into(conn, current_client); 00702 set_focus(conn, current_client, true); 00703 } 00704 } 00705 00706 /* 00707 * Jumps to the given window class / title. 00708 * Title is matched using strstr, that is, matches if it appears anywhere 00709 * in the string. Regular expressions seem to be a bit overkill here. However, 00710 * if we need them for something else somewhen, we may introduce them here, too. 00711 * 00712 */ 00713 static void jump_to_window(xcb_connection_t *conn, const char *arguments) { 00714 char *classtitle; 00715 Client *client; 00716 00717 /* The first character is a quote, this was checked before */ 00718 classtitle = sstrdup(arguments+1); 00719 /* The last character is a quote, we just set it to NULL */ 00720 classtitle[strlen(classtitle)-1] = '\0'; 00721 00722 if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) { 00723 free(classtitle); 00724 ELOG("No matching client found.\n"); 00725 return; 00726 } 00727 00728 free(classtitle); 00729 workspace_show(conn, client->workspace->num + 1); 00730 set_focus(conn, client, true); 00731 } 00732 00733 /* 00734 * Jump directly to the specified workspace, row and col. 00735 * Great for reaching windows that you always keep in the same spot (hello irssi, I'm looking at you) 00736 * 00737 */ 00738 static void jump_to_container(xcb_connection_t *conn, const char *arguments) { 00739 int ws, row, col; 00740 int result; 00741 00742 result = sscanf(arguments, "%d %d %d", &ws, &col, &row); 00743 LOG("Jump called with %d parameters (\"%s\")\n", result, arguments); 00744 00745 /* No match? Either no arguments were specified, or no numbers */ 00746 if (result < 1) { 00747 ELOG("At least one valid argument required\n"); 00748 return; 00749 } 00750 00751 /* Move to the target workspace */ 00752 workspace_show(conn, ws); 00753 00754 if (result < 3) 00755 return; 00756 00757 DLOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows); 00758 00759 /* Move to row/col */ 00760 if (row >= c_ws->rows) 00761 row = c_ws->rows - 1; 00762 if (col >= c_ws->cols) 00763 col = c_ws->cols - 1; 00764 00765 DLOG("Jumping to col %d, row %d\n", col, row); 00766 if (c_ws->table[col][row]->currently_focused != NULL) 00767 set_focus(conn, c_ws->table[col][row]->currently_focused, true); 00768 } 00769 00770 /* 00771 * Travels the focus stack by the given number of times (or once, if no argument 00772 * was specified). That is, selects the window you were in before you focused 00773 * the current window. 00774 * 00775 * The special values 'floating' (select the next floating window), 'tiling' 00776 * (select the next tiling window), 'ft' (if the current window is floating, 00777 * select the next tiling window and vice-versa) are also valid 00778 * 00779 */ 00780 static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) { 00781 /* Start count at -1 to always skip the first element */ 00782 int times, count = -1; 00783 Client *current; 00784 bool floating_criteria; 00785 00786 /* Either it’s one of the special values… */ 00787 if (strcasecmp(arguments, "floating") == 0) { 00788 floating_criteria = true; 00789 } else if (strcasecmp(arguments, "tiling") == 0) { 00790 floating_criteria = false; 00791 } else if (strcasecmp(arguments, "ft") == 0) { 00792 Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); 00793 if (last_focused == SLIST_END(&(c_ws->focus_stack))) { 00794 ELOG("Cannot select the next floating/tiling client because there is no client at all\n"); 00795 return; 00796 } 00797 00798 floating_criteria = !client_is_floating(last_focused); 00799 } else { 00800 /* …or a number was specified */ 00801 if (sscanf(arguments, "%u", ×) != 1) { 00802 ELOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments); 00803 times = 1; 00804 } 00805 00806 SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) { 00807 if (++count < times) { 00808 DLOG("Skipping\n"); 00809 continue; 00810 } 00811 00812 DLOG("Focussing\n"); 00813 set_focus(conn, current, true); 00814 break; 00815 } 00816 return; 00817 } 00818 00819 /* Select the next client matching the criteria parsed above */ 00820 SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) 00821 if (client_is_floating(current) == floating_criteria) { 00822 set_focus(conn, current, true); 00823 break; 00824 } 00825 } 00826 00827 /* 00828 * Switch to next or previous existing workspace 00829 * 00830 */ 00831 static void next_previous_workspace(xcb_connection_t *conn, int direction) { 00832 Workspace *ws = c_ws; 00833 00834 if (direction == 'n') { 00835 while (1) { 00836 ws = TAILQ_NEXT(ws, workspaces); 00837 00838 if (ws == TAILQ_END(workspaces)) 00839 ws = TAILQ_FIRST(workspaces); 00840 00841 if (ws == c_ws) 00842 return; 00843 00844 if (ws->output == NULL) 00845 continue; 00846 00847 workspace_show(conn, ws->num + 1); 00848 return; 00849 } 00850 } else if (direction == 'p') { 00851 while (1) { 00852 ws = TAILQ_PREV(ws, workspaces_head, workspaces); 00853 00854 if (ws == TAILQ_END(workspaces)) 00855 ws = TAILQ_LAST(workspaces, workspaces_head); 00856 00857 if (ws == c_ws) 00858 return; 00859 00860 if (ws->output == NULL) 00861 continue; 00862 00863 workspace_show(conn, ws->num + 1); 00864 return; 00865 } 00866 } 00867 } 00868 00869 static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, const char *command) { 00870 int first, second; 00871 resize_orientation_t orientation = O_VERTICAL; 00872 Container *con = last_focused->container; 00873 Workspace *ws = last_focused->workspace; 00874 00875 if (client_is_floating(last_focused)) { 00876 DLOG("Resizing a floating client\n"); 00877 if (STARTS_WITH(command, "left")) { 00878 command += strlen("left"); 00879 last_focused->rect.width -= atoi(command); 00880 last_focused->rect.x += atoi(command); 00881 } else if (STARTS_WITH(command, "right")) { 00882 command += strlen("right"); 00883 last_focused->rect.width += atoi(command); 00884 } else if (STARTS_WITH(command, "top")) { 00885 command += strlen("top"); 00886 last_focused->rect.height -= atoi(command); 00887 last_focused->rect.y += atoi(command); 00888 } else if (STARTS_WITH(command, "bottom")) { 00889 command += strlen("bottom"); 00890 last_focused->rect.height += atoi(command); 00891 } else { 00892 ELOG("Syntax: resize <left|right|top|bottom> [+|-]<pixels>\n"); 00893 return; 00894 } 00895 00896 /* resize_client flushes */ 00897 resize_client(conn, last_focused); 00898 00899 return; 00900 } 00901 00902 if (STARTS_WITH(command, "left")) { 00903 if (con->col == 0) 00904 return; 00905 first = con->col - 1; 00906 second = con->col; 00907 command += strlen("left"); 00908 } else if (STARTS_WITH(command, "right")) { 00909 first = con->col + (con->colspan - 1); 00910 DLOG("column %d\n", first); 00911 00912 if (!cell_exists(ws, first, con->row) || 00913 (first == (ws->cols-1))) 00914 return; 00915 00916 second = first + 1; 00917 command += strlen("right"); 00918 } else if (STARTS_WITH(command, "top")) { 00919 if (con->row == 0) 00920 return; 00921 first = con->row - 1; 00922 second = con->row; 00923 orientation = O_HORIZONTAL; 00924 command += strlen("top"); 00925 } else if (STARTS_WITH(command, "bottom")) { 00926 first = con->row + (con->rowspan - 1); 00927 if (!cell_exists(ws, con->col, first) || 00928 (first == (ws->rows-1))) 00929 return; 00930 00931 second = first + 1; 00932 orientation = O_HORIZONTAL; 00933 command += strlen("bottom"); 00934 } else { 00935 ELOG("Syntax: resize <left|right|top|bottom> [+|-]<pixels>\n"); 00936 return; 00937 } 00938 00939 int pixels = atoi(command); 00940 if (pixels == 0) 00941 return; 00942 00943 resize_container(conn, ws, first, second, orientation, pixels); 00944 } 00945 00946 /* 00947 * Parses a command, see file CMDMODE for more information 00948 * 00949 */ 00950 void parse_command(xcb_connection_t *conn, const char *command) { 00951 LOG("--- parsing command \"%s\" ---\n", command); 00952 /* Get the first client from focus stack because floating clients are not 00953 * in any container, therefore CUR_CELL is not appropriate. */ 00954 Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); 00955 if (last_focused == SLIST_END(&(c_ws->focus_stack))) 00956 last_focused = NULL; 00957 00958 /* Hmm, just to be sure */ 00959 if (command[0] == '\0') 00960 return; 00961 00962 /* Is it an <exec>? Then execute the given command. */ 00963 if (STARTS_WITH(command, "exec ")) { 00964 LOG("starting \"%s\"\n", command + strlen("exec ")); 00965 start_application(command+strlen("exec ")); 00966 return; 00967 } 00968 00969 if (STARTS_WITH(command, "mark")) { 00970 if (last_focused == NULL) { 00971 ELOG("There is no window to mark\n"); 00972 return; 00973 } 00974 const char *rest = command + strlen("mark"); 00975 while (*rest == ' ') 00976 rest++; 00977 if (*rest == '\0') { 00978 DLOG("interactive mark starting\n"); 00979 start_application("i3-input -p 'mark ' -l 1 -P 'Mark: '"); 00980 } else { 00981 LOG("mark with \"%s\"\n", rest); 00982 client_mark(conn, last_focused, rest); 00983 } 00984 return; 00985 } 00986 00987 if (STARTS_WITH(command, "goto")) { 00988 const char *rest = command + strlen("goto"); 00989 while (*rest == ' ') 00990 rest++; 00991 if (*rest == '\0') { 00992 DLOG("interactive go to mark starting\n"); 00993 start_application("i3-input -p 'goto ' -l 1 -P 'Goto: '"); 00994 } else { 00995 LOG("go to \"%s\"\n", rest); 00996 jump_to_mark(conn, rest); 00997 } 00998 return; 00999 } 01000 01001 if (STARTS_WITH(command, "stack-limit ")) { 01002 if (last_focused == NULL || client_is_floating(last_focused)) { 01003 ELOG("No container focused\n"); 01004 return; 01005 } 01006 const char *rest = command + strlen("stack-limit "); 01007 if (strncmp(rest, "rows ", strlen("rows ")) == 0) { 01008 last_focused->container->stack_limit = STACK_LIMIT_ROWS; 01009 rest += strlen("rows "); 01010 } else if (strncmp(rest, "cols ", strlen("cols ")) == 0) { 01011 last_focused->container->stack_limit = STACK_LIMIT_COLS; 01012 rest += strlen("cols "); 01013 } else { 01014 ELOG("Syntax: stack-limit <cols|rows> <limit>\n"); 01015 return; 01016 } 01017 01018 last_focused->container->stack_limit_value = atoi(rest); 01019 if (last_focused->container->stack_limit_value == 0) 01020 last_focused->container->stack_limit = STACK_LIMIT_NONE; 01021 01022 return; 01023 } 01024 01025 if (STARTS_WITH(command, "resize ")) { 01026 if (last_focused == NULL) 01027 return; 01028 const char *rest = command + strlen("resize "); 01029 parse_resize_command(conn, last_focused, rest); 01030 return; 01031 } 01032 01033 if (STARTS_WITH(command, "mode ")) { 01034 const char *rest = command + strlen("mode "); 01035 switch_mode(conn, rest); 01036 return; 01037 } 01038 01039 /* Is it an <exit>? */ 01040 if (STARTS_WITH(command, "exit")) { 01041 LOG("User issued exit-command, exiting without error.\n"); 01042 restore_geometry(global_conn); 01043 ipc_shutdown(); 01044 exit(EXIT_SUCCESS); 01045 } 01046 01047 /* Is it a <reload>? */ 01048 if (STARTS_WITH(command, "reload")) { 01049 load_configuration(conn, NULL, true); 01050 render_layout(conn); 01051 /* Send an IPC event just in case the ws names have changed */ 01052 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}"); 01053 return; 01054 } 01055 01056 /* Is it <restart>? Then restart in place. */ 01057 if (STARTS_WITH(command, "restart")) { 01058 i3_restart(); 01059 } 01060 01061 if (STARTS_WITH(command, "kill")) { 01062 if (last_focused == NULL) { 01063 ELOG("There is no window to kill\n"); 01064 return; 01065 } 01066 01067 LOG("Killing current window\n"); 01068 client_kill(conn, last_focused); 01069 return; 01070 } 01071 01072 /* Is it a jump to a specified workspace, row, col? */ 01073 if (STARTS_WITH(command, "jump ")) { 01074 const char *arguments = command + strlen("jump "); 01075 if (arguments[0] == '"') 01076 jump_to_window(conn, arguments); 01077 else jump_to_container(conn, arguments); 01078 return; 01079 } 01080 01081 /* Should we travel the focus stack? */ 01082 if (STARTS_WITH(command, "focus")) { 01083 const char *arguments = command + strlen("focus "); 01084 travel_focus_stack(conn, arguments); 01085 return; 01086 } 01087 01088 /* Is it 'f' for fullscreen, or 'fg' for fullscreen_global? */ 01089 if (command[0] == 'f') { 01090 if (last_focused == NULL) 01091 return; 01092 if (command[1] == 'g') 01093 client_toggle_fullscreen_global(conn, last_focused); 01094 else 01095 client_toggle_fullscreen(conn, last_focused); 01096 return; 01097 } 01098 01099 /* Is it just 's' for stacking or 'd' for default? */ 01100 if ((command[0] == 's' || command[0] == 'd' || command[0] == 'T') && (command[1] == '\0')) { 01101 if (last_focused != NULL && client_is_floating(last_focused)) { 01102 ELOG("not switching, this is a floating client\n"); 01103 return; 01104 } 01105 LOG("Switching mode for current container\n"); 01106 int new_mode = MODE_DEFAULT; 01107 if (command[0] == 's' && CUR_CELL->mode != MODE_STACK) 01108 new_mode = MODE_STACK; 01109 if (command[0] == 'T' && CUR_CELL->mode != MODE_TABBED) 01110 new_mode = MODE_TABBED; 01111 switch_layout_mode(conn, CUR_CELL, new_mode); 01112 return; 01113 } 01114 01115 /* Is it 'bn' (border normal), 'bp' (border 1pixel) or 'bb' (border borderless)? */ 01116 /* or even 'bt' (toggle border: 'bp' -> 'bb' -> 'bn' ) */ 01117 if (command[0] == 'b') { 01118 if (last_focused == NULL) { 01119 ELOG("No window focused, cannot change border type\n"); 01120 return; 01121 } 01122 01123 char com = command[1]; 01124 if (command[1] == 't') { 01125 if (last_focused->titlebar_position == TITLEBAR_TOP && 01126 !last_focused->borderless) 01127 com = 'p'; 01128 else if (last_focused->titlebar_position == TITLEBAR_OFF && 01129 !last_focused->borderless) 01130 com = 'b'; 01131 else com = 'n'; 01132 } 01133 01134 client_change_border(conn, last_focused, com); 01135 return; 01136 } 01137 01138 if (command[0] == 'H') { 01139 LOG("Hiding all floating windows\n"); 01140 floating_toggle_hide(conn, c_ws); 01141 return; 01142 } 01143 01144 enum { WITH_WINDOW, WITH_CONTAINER, WITH_WORKSPACE, WITH_SCREEN } with = WITH_WINDOW; 01145 01146 /* Is it a <with>? */ 01147 if (command[0] == 'w') { 01148 command++; 01149 /* TODO: implement */ 01150 if (command[0] == 'c') { 01151 with = WITH_CONTAINER; 01152 command++; 01153 } else if (command[0] == 'w') { 01154 with = WITH_WORKSPACE; 01155 command++; 01156 } else if (command[0] == 's') { 01157 with = WITH_SCREEN; 01158 command++; 01159 } else { 01160 ELOG("not yet implemented.\n"); 01161 return; 01162 } 01163 } 01164 01165 /* Is it 't' for toggle tiling/floating? */ 01166 if (command[0] == 't') { 01167 if (with == WITH_WORKSPACE) { 01168 c_ws->auto_float = !c_ws->auto_float; 01169 LOG("autofloat is now %d\n", c_ws->auto_float); 01170 return; 01171 } 01172 if (last_focused == NULL) { 01173 ELOG("Cannot toggle tiling/floating: workspace empty\n"); 01174 return; 01175 } 01176 01177 Workspace *ws = last_focused->workspace; 01178 01179 if (last_focused->fullscreen) 01180 client_leave_fullscreen(conn, last_focused); 01181 01182 toggle_floating_mode(conn, last_focused, false); 01183 /* delete all empty columns/rows */ 01184 cleanup_table(conn, ws); 01185 01186 /* Fix colspan/rowspan if it’d overlap */ 01187 fix_colrowspan(conn, ws); 01188 01189 render_workspace(conn, ws->output, ws); 01190 01191 /* Re-focus the client because cleanup_table sets the focus to the last 01192 * focused client inside a container only. */ 01193 set_focus(conn, last_focused, true); 01194 01195 return; 01196 } 01197 01198 /* Is it 'n' or 'p' for next/previous workspace? (nw) */ 01199 if ((command[0] == 'n' || command[0] == 'p') && command[1] == 'w') { 01200 next_previous_workspace(conn, command[0]); 01201 return; 01202 } 01203 01204 /* It’s a normal <cmd> */ 01205 char *rest = NULL; 01206 enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS; 01207 direction_t direction; 01208 int times = strtol(command, &rest, 10); 01209 if (rest == NULL) { 01210 ELOG("Invalid command (\"%s\")\n", command); 01211 return; 01212 } 01213 01214 if (*rest == '\0') { 01215 /* No rest? This was a workspace number, not a times specification */ 01216 workspace_show(conn, times); 01217 return; 01218 } 01219 01220 if (*rest == 'm' || *rest == 's') { 01221 action = (*rest == 'm' ? ACTION_MOVE : ACTION_SNAP); 01222 rest++; 01223 } 01224 01225 int workspace = strtol(rest, &rest, 10); 01226 01227 if (rest == NULL) { 01228 ELOG("Invalid command (\"%s\")\n", command); 01229 return; 01230 } 01231 01232 if (*rest == '\0') { 01233 if (last_focused != NULL && client_is_floating(last_focused)) 01234 move_floating_window_to_workspace(conn, last_focused, workspace); 01235 else move_current_window_to_workspace(conn, workspace); 01236 return; 01237 } 01238 01239 if (last_focused == NULL) { 01240 ELOG("Not performing (no window found)\n"); 01241 return; 01242 } 01243 01244 if (client_is_floating(last_focused) && 01245 (action != ACTION_FOCUS && action != ACTION_MOVE)) { 01246 ELOG("Not performing (floating)\n"); 01247 return; 01248 } 01249 01250 /* Now perform action to <where> */ 01251 while (*rest != '\0') { 01252 if (*rest == 'h') 01253 direction = D_LEFT; 01254 else if (*rest == 'j') 01255 direction = D_DOWN; 01256 else if (*rest == 'k') 01257 direction = D_UP; 01258 else if (*rest == 'l') 01259 direction = D_RIGHT; 01260 else { 01261 ELOG("unknown direction: %c\n", *rest); 01262 return; 01263 } 01264 rest++; 01265 01266 if (action == ACTION_FOCUS) { 01267 if (with == WITH_SCREEN) { 01268 focus_thing(conn, direction, THING_SCREEN); 01269 continue; 01270 } 01271 if (client_is_floating(last_focused)) { 01272 floating_focus_direction(conn, last_focused, direction); 01273 continue; 01274 } 01275 focus_thing(conn, direction, (with == WITH_WINDOW ? THING_WINDOW : THING_CONTAINER)); 01276 continue; 01277 } 01278 01279 if (action == ACTION_MOVE) { 01280 if (with == WITH_SCREEN) { 01281 /* TODO: this should swap the screen’s contents 01282 * (e.g. all workspaces) with the next/previous/… 01283 * screen */ 01284 ELOG("Not yet implemented\n"); 01285 continue; 01286 } 01287 if (client_is_floating(last_focused)) { 01288 floating_move(conn, last_focused, direction); 01289 continue; 01290 } 01291 if (with == WITH_WINDOW) 01292 move_current_window(conn, direction); 01293 else move_current_container(conn, direction); 01294 continue; 01295 } 01296 01297 if (action == ACTION_SNAP) { 01298 if (with == WITH_SCREEN) { 01299 ELOG("You cannot snap a screen (it makes no sense).\n"); 01300 continue; 01301 } 01302 snap_current_container(conn, direction); 01303 continue; 01304 } 01305 } 01306 }