i3
|
00001 /* 00002 * vim:ts=4:sw=4:expandtab 00003 * 00004 * i3 - an improved dynamic tiling window manager 00005 * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) 00006 * 00007 * commands.c: all command functions (see commands_parser.c) 00008 * 00009 */ 00010 #include <float.h> 00011 #include <stdarg.h> 00012 00013 #include "all.h" 00014 00020 #define HANDLE_EMPTY_MATCH do { \ 00021 if (match_is_empty(current_match)) { \ 00022 owindow *ow = smalloc(sizeof(owindow)); \ 00023 ow->con = focused; \ 00024 TAILQ_INIT(&owindows); \ 00025 TAILQ_INSERT_TAIL(&owindows, ow, owindows); \ 00026 } \ 00027 } while (0) 00028 00029 static owindows_head owindows; 00030 00031 /* 00032 * Returns true if a is definitely greater than b (using the given epsilon) 00033 * 00034 */ 00035 static bool definitelyGreaterThan(float a, float b, float epsilon) { 00036 return (a - b) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon); 00037 } 00038 00039 /* 00040 * Returns an 'output' corresponding to one of left/right/down/up or a specific 00041 * output name. 00042 * 00043 */ 00044 static Output *get_output_from_string(Output *current_output, const char *output_str) { 00045 Output *output; 00046 00047 if (strcasecmp(output_str, "left") == 0) { 00048 output = get_output_next(D_LEFT, current_output); 00049 if (!output) 00050 output = get_output_most(D_RIGHT, current_output); 00051 } else if (strcasecmp(output_str, "right") == 0) { 00052 output = get_output_next(D_RIGHT, current_output); 00053 if (!output) 00054 output = get_output_most(D_LEFT, current_output); 00055 } else if (strcasecmp(output_str, "up") == 0) { 00056 output = get_output_next(D_UP, current_output); 00057 if (!output) 00058 output = get_output_most(D_DOWN, current_output); 00059 } else if (strcasecmp(output_str, "down") == 0) { 00060 output = get_output_next(D_DOWN, current_output); 00061 if (!output) 00062 output = get_output_most(D_UP, current_output); 00063 } else output = get_output_by_name(output_str); 00064 00065 return output; 00066 } 00067 00068 // This code is commented out because we might recycle it for popping up error 00069 // messages on parser errors. 00070 #if 0 00071 static pid_t migration_pid = -1; 00072 00073 /* 00074 * Handler which will be called when we get a SIGCHLD for the nagbar, meaning 00075 * it exited (or could not be started, depending on the exit code). 00076 * 00077 */ 00078 static void nagbar_exited(EV_P_ ev_child *watcher, int revents) { 00079 ev_child_stop(EV_A_ watcher); 00080 if (!WIFEXITED(watcher->rstatus)) { 00081 fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n"); 00082 return; 00083 } 00084 00085 int exitcode = WEXITSTATUS(watcher->rstatus); 00086 printf("i3-nagbar process exited with status %d\n", exitcode); 00087 if (exitcode == 2) { 00088 fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n"); 00089 } 00090 00091 migration_pid = -1; 00092 } 00093 00094 /* We need ev >= 4 for the following code. Since it is not *that* important (it 00095 * only makes sure that there are no i3-nagbar instances left behind) we still 00096 * support old systems with libev 3. */ 00097 #if EV_VERSION_MAJOR >= 4 00098 /* 00099 * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal 00100 * SIGKILL (9) to make sure there are no left-over i3-nagbar processes. 00101 * 00102 */ 00103 static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) { 00104 if (migration_pid != -1) { 00105 LOG("Sending SIGKILL (9) to i3-nagbar with PID %d\n", migration_pid); 00106 kill(migration_pid, SIGKILL); 00107 } 00108 } 00109 #endif 00110 00111 void cmd_MIGRATION_start_nagbar(void) { 00112 if (migration_pid != -1) { 00113 fprintf(stderr, "i3-nagbar already running.\n"); 00114 return; 00115 } 00116 fprintf(stderr, "Starting i3-nagbar, command parsing differs from expected output.\n"); 00117 ELOG("Please report this on IRC or in the bugtracker. Make sure to include the full debug level logfile:\n"); 00118 ELOG("i3-dump-log | gzip -9c > /tmp/i3.log.gz\n"); 00119 ELOG("FYI: Your i3 version is " I3_VERSION "\n"); 00120 migration_pid = fork(); 00121 if (migration_pid == -1) { 00122 warn("Could not fork()"); 00123 return; 00124 } 00125 00126 /* child */ 00127 if (migration_pid == 0) { 00128 char *pageraction; 00129 sasprintf(&pageraction, "i3-sensible-terminal -e i3-sensible-pager \"%s\"", errorfilename); 00130 char *argv[] = { 00131 NULL, /* will be replaced by the executable path */ 00132 "-t", 00133 "error", 00134 "-m", 00135 "You found a parsing error. Please, please, please, report it!", 00136 "-b", 00137 "show errors", 00138 pageraction, 00139 NULL 00140 }; 00141 exec_i3_utility("i3-nagbar", argv); 00142 } 00143 00144 /* parent */ 00145 /* install a child watcher */ 00146 ev_child *child = smalloc(sizeof(ev_child)); 00147 ev_child_init(child, &nagbar_exited, migration_pid, 0); 00148 ev_child_start(main_loop, child); 00149 00150 /* We need ev >= 4 for the following code. Since it is not *that* important (it 00151 * only makes sure that there are no i3-nagbar instances left behind) we still 00152 * support old systems with libev 3. */ 00153 #if EV_VERSION_MAJOR >= 4 00154 /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is 00155 * still running) */ 00156 ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup)); 00157 ev_cleanup_init(cleanup, nagbar_cleanup); 00158 ev_cleanup_start(main_loop, cleanup); 00159 #endif 00160 } 00161 00162 #endif 00163 00164 /******************************************************************************* 00165 * Criteria functions. 00166 ******************************************************************************/ 00167 00168 /* 00169 * Initializes the specified 'Match' data structure and the initial state of 00170 * commands.c for matching target windows of a command. 00171 * 00172 */ 00173 void cmd_criteria_init(I3_CMD) { 00174 Con *con; 00175 owindow *ow; 00176 00177 DLOG("Initializing criteria, current_match = %p\n", current_match); 00178 match_init(current_match); 00179 while (!TAILQ_EMPTY(&owindows)) { 00180 ow = TAILQ_FIRST(&owindows); 00181 TAILQ_REMOVE(&owindows, ow, owindows); 00182 free(ow); 00183 } 00184 TAILQ_INIT(&owindows); 00185 /* copy all_cons */ 00186 TAILQ_FOREACH(con, &all_cons, all_cons) { 00187 ow = smalloc(sizeof(owindow)); 00188 ow->con = con; 00189 TAILQ_INSERT_TAIL(&owindows, ow, owindows); 00190 } 00191 } 00192 00193 /* 00194 * A match specification just finished (the closing square bracket was found), 00195 * so we filter the list of owindows. 00196 * 00197 */ 00198 void cmd_criteria_match_windows(I3_CMD) { 00199 owindow *next, *current; 00200 00201 DLOG("match specification finished, matching...\n"); 00202 /* copy the old list head to iterate through it and start with a fresh 00203 * list which will contain only matching windows */ 00204 struct owindows_head old = owindows; 00205 TAILQ_INIT(&owindows); 00206 for (next = TAILQ_FIRST(&old); next != TAILQ_END(&old);) { 00207 /* make a copy of the next pointer and advance the pointer to the 00208 * next element as we are going to invalidate the element’s 00209 * next/prev pointers by calling TAILQ_INSERT_TAIL later */ 00210 current = next; 00211 next = TAILQ_NEXT(next, owindows); 00212 00213 DLOG("checking if con %p / %s matches\n", current->con, current->con->name); 00214 if (current_match->con_id != NULL) { 00215 if (current_match->con_id == current->con) { 00216 DLOG("matches container!\n"); 00217 TAILQ_INSERT_TAIL(&owindows, current, owindows); 00218 } 00219 } else if (current_match->mark != NULL && current->con->mark != NULL && 00220 regex_matches(current_match->mark, current->con->mark)) { 00221 DLOG("match by mark\n"); 00222 TAILQ_INSERT_TAIL(&owindows, current, owindows); 00223 } else { 00224 if (current->con->window == NULL) 00225 continue; 00226 if (match_matches_window(current_match, current->con->window)) { 00227 DLOG("matches window!\n"); 00228 TAILQ_INSERT_TAIL(&owindows, current, owindows); 00229 } else { 00230 DLOG("doesnt match\n"); 00231 free(current); 00232 } 00233 } 00234 } 00235 00236 TAILQ_FOREACH(current, &owindows, owindows) { 00237 DLOG("matching: %p / %s\n", current->con, current->con->name); 00238 } 00239 } 00240 00241 /* 00242 * Interprets a ctype=cvalue pair and adds it to the current match 00243 * specification. 00244 * 00245 */ 00246 void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) { 00247 DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue); 00248 00249 if (strcmp(ctype, "class") == 0) { 00250 current_match->class = regex_new(cvalue); 00251 return; 00252 } 00253 00254 if (strcmp(ctype, "instance") == 0) { 00255 current_match->instance = regex_new(cvalue); 00256 return; 00257 } 00258 00259 if (strcmp(ctype, "window_role") == 0) { 00260 current_match->role = regex_new(cvalue); 00261 return; 00262 } 00263 00264 if (strcmp(ctype, "con_id") == 0) { 00265 char *end; 00266 long parsed = strtol(cvalue, &end, 10); 00267 if (parsed == LONG_MIN || 00268 parsed == LONG_MAX || 00269 parsed < 0 || 00270 (end && *end != '\0')) { 00271 ELOG("Could not parse con id \"%s\"\n", cvalue); 00272 } else { 00273 current_match->con_id = (Con*)parsed; 00274 printf("id as int = %p\n", current_match->con_id); 00275 } 00276 return; 00277 } 00278 00279 if (strcmp(ctype, "id") == 0) { 00280 char *end; 00281 long parsed = strtol(cvalue, &end, 10); 00282 if (parsed == LONG_MIN || 00283 parsed == LONG_MAX || 00284 parsed < 0 || 00285 (end && *end != '\0')) { 00286 ELOG("Could not parse window id \"%s\"\n", cvalue); 00287 } else { 00288 current_match->id = parsed; 00289 printf("window id as int = %d\n", current_match->id); 00290 } 00291 return; 00292 } 00293 00294 if (strcmp(ctype, "con_mark") == 0) { 00295 current_match->mark = regex_new(cvalue); 00296 return; 00297 } 00298 00299 if (strcmp(ctype, "title") == 0) { 00300 current_match->title = regex_new(cvalue); 00301 return; 00302 } 00303 00304 if (strcmp(ctype, "urgent") == 0) { 00305 if (strcasecmp(cvalue, "latest") == 0 || 00306 strcasecmp(cvalue, "newest") == 0 || 00307 strcasecmp(cvalue, "recent") == 0 || 00308 strcasecmp(cvalue, "last") == 0) { 00309 current_match->urgent = U_LATEST; 00310 } else if (strcasecmp(cvalue, "oldest") == 0 || 00311 strcasecmp(cvalue, "first") == 0) { 00312 current_match->urgent = U_OLDEST; 00313 } 00314 return; 00315 } 00316 00317 ELOG("Unknown criterion: %s\n", ctype); 00318 } 00319 00320 /* 00321 * Implementation of 'move [window|container] [to] workspace 00322 * next|prev|next_on_output|prev_on_output'. 00323 * 00324 */ 00325 void cmd_move_con_to_workspace(I3_CMD, char *which) { 00326 owindow *current; 00327 00328 DLOG("which=%s\n", which); 00329 00330 HANDLE_EMPTY_MATCH; 00331 00332 /* get the workspace */ 00333 Con *ws; 00334 if (strcmp(which, "next") == 0) 00335 ws = workspace_next(); 00336 else if (strcmp(which, "prev") == 0) 00337 ws = workspace_prev(); 00338 else if (strcmp(which, "next_on_output") == 0) 00339 ws = workspace_next_on_output(); 00340 else if (strcmp(which, "prev_on_output") == 0) 00341 ws = workspace_prev_on_output(); 00342 else { 00343 ELOG("BUG: called with which=%s\n", which); 00344 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 00345 return; 00346 } 00347 00348 TAILQ_FOREACH(current, &owindows, owindows) { 00349 DLOG("matching: %p / %s\n", current->con, current->con->name); 00350 con_move_to_workspace(current->con, ws, true, false); 00351 } 00352 00353 cmd_output->needs_tree_render = true; 00354 // XXX: default reply for now, make this a better reply 00355 cmd_output->json_output = sstrdup("{\"success\": true}"); 00356 } 00357 00358 /* 00359 * Implementation of 'move [window|container] [to] workspace <name>'. 00360 * 00361 */ 00362 void cmd_move_con_to_workspace_name(I3_CMD, char *name) { 00363 if (strncasecmp(name, "__i3_", strlen("__i3_")) == 0) { 00364 LOG("You cannot switch to the i3 internal workspaces.\n"); 00365 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 00366 return; 00367 } 00368 00369 owindow *current; 00370 00371 /* Error out early to not create a non-existing workspace (in 00372 * workspace_get()) if we are not actually able to move anything. */ 00373 if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) { 00374 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 00375 return; 00376 } 00377 00378 LOG("should move window to workspace %s\n", name); 00379 /* get the workspace */ 00380 Con *ws = workspace_get(name, NULL); 00381 00382 HANDLE_EMPTY_MATCH; 00383 00384 TAILQ_FOREACH(current, &owindows, owindows) { 00385 DLOG("matching: %p / %s\n", current->con, current->con->name); 00386 con_move_to_workspace(current->con, ws, true, false); 00387 } 00388 00389 cmd_output->needs_tree_render = true; 00390 // XXX: default reply for now, make this a better reply 00391 cmd_output->json_output = sstrdup("{\"success\": true}"); 00392 } 00393 00394 /* 00395 * Implementation of 'move [window|container] [to] workspace number <number>'. 00396 * 00397 */ 00398 void cmd_move_con_to_workspace_number(I3_CMD, char *which) { 00399 owindow *current; 00400 00401 /* Error out early to not create a non-existing workspace (in 00402 * workspace_get()) if we are not actually able to move anything. */ 00403 if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) { 00404 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 00405 return; 00406 } 00407 00408 LOG("should move window to workspace with number %d\n", which); 00409 /* get the workspace */ 00410 Con *output, *workspace = NULL; 00411 00412 char *endptr = NULL; 00413 long parsed_num = strtol(which, &endptr, 10); 00414 if (parsed_num == LONG_MIN || 00415 parsed_num == LONG_MAX || 00416 parsed_num < 0 || 00417 *endptr != '\0') { 00418 LOG("Could not parse \"%s\" as a number.\n", which); 00419 cmd_output->json_output = sstrdup("{\"success\": false, " 00420 "\"error\": \"Could not parse number\"}"); 00421 return; 00422 } 00423 00424 TAILQ_FOREACH(output, &(croot->nodes_head), nodes) 00425 GREP_FIRST(workspace, output_get_content(output), 00426 child->num == parsed_num); 00427 00428 if (!workspace) { 00429 cmd_output->json_output = sstrdup("{\"success\": false, " 00430 "\"error\": \"No such workspace\"}"); 00431 return; 00432 } 00433 00434 HANDLE_EMPTY_MATCH; 00435 00436 TAILQ_FOREACH(current, &owindows, owindows) { 00437 DLOG("matching: %p / %s\n", current->con, current->con->name); 00438 con_move_to_workspace(current->con, workspace, true, false); 00439 } 00440 00441 cmd_output->needs_tree_render = true; 00442 // XXX: default reply for now, make this a better reply 00443 cmd_output->json_output = sstrdup("{\"success\": true}"); 00444 } 00445 00446 static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floating_con, int px) { 00447 LOG("floating resize\n"); 00448 if (strcmp(direction, "up") == 0) { 00449 floating_con->rect.y -= px; 00450 floating_con->rect.height += px; 00451 } else if (strcmp(direction, "down") == 0) { 00452 floating_con->rect.height += px; 00453 } else if (strcmp(direction, "left") == 0) { 00454 floating_con->rect.x -= px; 00455 floating_con->rect.width += px; 00456 } else { 00457 floating_con->rect.width += px; 00458 } 00459 } 00460 00461 static void cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int ppt) { 00462 LOG("tiling resize\n"); 00463 /* get the appropriate current container (skip stacked/tabbed cons) */ 00464 Con *current = focused; 00465 while (current->parent->layout == L_STACKED || 00466 current->parent->layout == L_TABBED) 00467 current = current->parent; 00468 00469 /* Then further go up until we find one with the matching orientation. */ 00470 orientation_t search_orientation = 00471 (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0 ? HORIZ : VERT); 00472 00473 while (current->type != CT_WORKSPACE && 00474 current->type != CT_FLOATING_CON && 00475 current->parent->orientation != search_orientation) 00476 current = current->parent; 00477 00478 /* get the default percentage */ 00479 int children = con_num_children(current->parent); 00480 Con *other; 00481 LOG("ins. %d children\n", children); 00482 double percentage = 1.0 / children; 00483 LOG("default percentage = %f\n", percentage); 00484 00485 orientation_t orientation = current->parent->orientation; 00486 00487 if ((orientation == HORIZ && 00488 (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) || 00489 (orientation == VERT && 00490 (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0))) { 00491 LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n", 00492 (orientation == HORIZ ? "horizontal" : "vertical")); 00493 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 00494 return; 00495 } 00496 00497 if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) { 00498 other = TAILQ_PREV(current, nodes_head, nodes); 00499 } else { 00500 other = TAILQ_NEXT(current, nodes); 00501 } 00502 if (other == TAILQ_END(workspaces)) { 00503 LOG("No other container in this direction found, cannot resize.\n"); 00504 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 00505 return; 00506 } 00507 LOG("other->percent = %f\n", other->percent); 00508 LOG("current->percent before = %f\n", current->percent); 00509 if (current->percent == 0.0) 00510 current->percent = percentage; 00511 if (other->percent == 0.0) 00512 other->percent = percentage; 00513 double new_current_percent = current->percent + ((double)ppt / 100.0); 00514 double new_other_percent = other->percent - ((double)ppt / 100.0); 00515 LOG("new_current_percent = %f\n", new_current_percent); 00516 LOG("new_other_percent = %f\n", new_other_percent); 00517 /* Ensure that the new percentages are positive and greater than 00518 * 0.05 to have a reasonable minimum size. */ 00519 if (definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON) && 00520 definitelyGreaterThan(new_other_percent, 0.05, DBL_EPSILON)) { 00521 current->percent += ((double)ppt / 100.0); 00522 other->percent -= ((double)ppt / 100.0); 00523 LOG("current->percent after = %f\n", current->percent); 00524 LOG("other->percent after = %f\n", other->percent); 00525 } else { 00526 LOG("Not resizing, already at minimum size\n"); 00527 } 00528 } 00529 00530 static void cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, int ppt) { 00531 LOG("width/height resize\n"); 00532 /* get the appropriate current container (skip stacked/tabbed cons) */ 00533 Con *current = focused; 00534 while (current->parent->layout == L_STACKED || 00535 current->parent->layout == L_TABBED) 00536 current = current->parent; 00537 00538 /* Then further go up until we find one with the matching orientation. */ 00539 orientation_t search_orientation = 00540 (strcmp(direction, "width") == 0 ? HORIZ : VERT); 00541 00542 while (current->type != CT_WORKSPACE && 00543 current->type != CT_FLOATING_CON && 00544 current->parent->orientation != search_orientation) 00545 current = current->parent; 00546 00547 /* get the default percentage */ 00548 int children = con_num_children(current->parent); 00549 LOG("ins. %d children\n", children); 00550 double percentage = 1.0 / children; 00551 LOG("default percentage = %f\n", percentage); 00552 00553 orientation_t orientation = current->parent->orientation; 00554 00555 if ((orientation == HORIZ && 00556 strcmp(direction, "height") == 0) || 00557 (orientation == VERT && 00558 strcmp(direction, "width") == 0)) { 00559 LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n", 00560 (orientation == HORIZ ? "horizontal" : "vertical")); 00561 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 00562 return; 00563 } 00564 00565 if (children == 1) { 00566 LOG("This is the only container, cannot resize.\n"); 00567 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 00568 return; 00569 } 00570 00571 /* Ensure all the other children have a percentage set. */ 00572 Con *child; 00573 TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) { 00574 LOG("child->percent = %f (child %p)\n", child->percent, child); 00575 if (child->percent == 0.0) 00576 child->percent = percentage; 00577 } 00578 00579 double new_current_percent = current->percent + ((double)ppt / 100.0); 00580 double subtract_percent = ((double)ppt / 100.0) / (children - 1); 00581 LOG("new_current_percent = %f\n", new_current_percent); 00582 LOG("subtract_percent = %f\n", subtract_percent); 00583 /* Ensure that the new percentages are positive and greater than 00584 * 0.05 to have a reasonable minimum size. */ 00585 TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) { 00586 if (child == current) 00587 continue; 00588 if (!definitelyGreaterThan(child->percent - subtract_percent, 0.05, DBL_EPSILON)) { 00589 LOG("Not resizing, already at minimum size (child %p would end up with a size of %.f\n", child, child->percent - subtract_percent); 00590 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 00591 return; 00592 } 00593 } 00594 if (!definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON)) { 00595 LOG("Not resizing, already at minimum size\n"); 00596 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 00597 return; 00598 } 00599 00600 current->percent += ((double)ppt / 100.0); 00601 LOG("current->percent after = %f\n", current->percent); 00602 00603 TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) { 00604 if (child == current) 00605 continue; 00606 child->percent -= subtract_percent; 00607 LOG("child->percent after (%p) = %f\n", child, child->percent); 00608 } 00609 } 00610 00611 /* 00612 * Implementation of 'resize grow|shrink <direction> [<px> px] [or <ppt> ppt]'. 00613 * 00614 */ 00615 void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resize_ppt) { 00616 /* resize <grow|shrink> <direction> [<px> px] [or <ppt> ppt] */ 00617 DLOG("resizing in way %s, direction %s, px %s or ppt %s\n", way, direction, resize_px, resize_ppt); 00618 // 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 00619 int px = atoi(resize_px); 00620 int ppt = atoi(resize_ppt); 00621 if (strcmp(way, "shrink") == 0) { 00622 px *= -1; 00623 ppt *= -1; 00624 } 00625 00626 Con *floating_con; 00627 if ((floating_con = con_inside_floating(focused))) { 00628 cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, px); 00629 } else { 00630 if (strcmp(direction, "width") == 0 || 00631 strcmp(direction, "height") == 0) 00632 cmd_resize_tiling_width_height(current_match, cmd_output, way, direction, ppt); 00633 else cmd_resize_tiling_direction(current_match, cmd_output, way, direction, ppt); 00634 } 00635 00636 cmd_output->needs_tree_render = true; 00637 // XXX: default reply for now, make this a better reply 00638 cmd_output->json_output = sstrdup("{\"success\": true}"); 00639 } 00640 00641 /* 00642 * Implementation of 'border normal|none|1pixel|toggle'. 00643 * 00644 */ 00645 void cmd_border(I3_CMD, char *border_style_str) { 00646 DLOG("border style should be changed to %s\n", border_style_str); 00647 owindow *current; 00648 00649 HANDLE_EMPTY_MATCH; 00650 00651 TAILQ_FOREACH(current, &owindows, owindows) { 00652 DLOG("matching: %p / %s\n", current->con, current->con->name); 00653 int border_style = current->con->border_style; 00654 if (strcmp(border_style_str, "toggle") == 0) { 00655 border_style++; 00656 border_style %= 3; 00657 } else { 00658 if (strcmp(border_style_str, "normal") == 0) 00659 border_style = BS_NORMAL; 00660 else if (strcmp(border_style_str, "none") == 0) 00661 border_style = BS_NONE; 00662 else if (strcmp(border_style_str, "1pixel") == 0) 00663 border_style = BS_1PIXEL; 00664 else { 00665 ELOG("BUG: called with border_style=%s\n", border_style_str); 00666 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 00667 return; 00668 } 00669 } 00670 con_set_border_style(current->con, border_style); 00671 } 00672 00673 cmd_output->needs_tree_render = true; 00674 // XXX: default reply for now, make this a better reply 00675 cmd_output->json_output = sstrdup("{\"success\": true}"); 00676 } 00677 00678 /* 00679 * Implementation of 'nop <comment>'. 00680 * 00681 */ 00682 void cmd_nop(I3_CMD, char *comment) { 00683 LOG("-------------------------------------------------\n"); 00684 LOG(" NOP: %s\n", comment); 00685 LOG("-------------------------------------------------\n"); 00686 } 00687 00688 /* 00689 * Implementation of 'append_layout <path>'. 00690 * 00691 */ 00692 void cmd_append_layout(I3_CMD, char *path) { 00693 LOG("Appending layout \"%s\"\n", path); 00694 tree_append_json(path); 00695 00696 cmd_output->needs_tree_render = true; 00697 // XXX: default reply for now, make this a better reply 00698 cmd_output->json_output = sstrdup("{\"success\": true}"); 00699 } 00700 00701 /* 00702 * Implementation of 'workspace next|prev|next_on_output|prev_on_output'. 00703 * 00704 */ 00705 void cmd_workspace(I3_CMD, char *which) { 00706 Con *ws; 00707 00708 DLOG("which=%s\n", which); 00709 00710 if (strcmp(which, "next") == 0) 00711 ws = workspace_next(); 00712 else if (strcmp(which, "prev") == 0) 00713 ws = workspace_prev(); 00714 else if (strcmp(which, "next_on_output") == 0) 00715 ws = workspace_next_on_output(); 00716 else if (strcmp(which, "prev_on_output") == 0) 00717 ws = workspace_prev_on_output(); 00718 else { 00719 ELOG("BUG: called with which=%s\n", which); 00720 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 00721 return; 00722 } 00723 00724 workspace_show(ws); 00725 00726 cmd_output->needs_tree_render = true; 00727 // XXX: default reply for now, make this a better reply 00728 cmd_output->json_output = sstrdup("{\"success\": true}"); 00729 } 00730 00731 /* 00732 * Implementation of 'workspace number <number>' 00733 * 00734 */ 00735 void cmd_workspace_number(I3_CMD, char *which) { 00736 Con *output, *workspace = NULL; 00737 00738 char *endptr = NULL; 00739 long parsed_num = strtol(which, &endptr, 10); 00740 if (parsed_num == LONG_MIN || 00741 parsed_num == LONG_MAX || 00742 parsed_num < 0 || 00743 *endptr != '\0') { 00744 LOG("Could not parse \"%s\" as a number.\n", which); 00745 cmd_output->json_output = sstrdup("{\"success\": false, " 00746 "\"error\": \"Could not parse number\"}"); 00747 return; 00748 } 00749 00750 TAILQ_FOREACH(output, &(croot->nodes_head), nodes) 00751 GREP_FIRST(workspace, output_get_content(output), 00752 child->num == parsed_num); 00753 00754 if (!workspace) { 00755 LOG("There is no workspace with number %d.\n", parsed_num); 00756 cmd_output->json_output = sstrdup("{\"success\": false, " 00757 "\"error\": \"No such workspace\"}"); 00758 return; 00759 } 00760 00761 workspace_show(workspace); 00762 00763 cmd_output->needs_tree_render = true; 00764 // XXX: default reply for now, make this a better reply 00765 cmd_output->json_output = sstrdup("{\"success\": true}"); 00766 } 00767 00768 /* 00769 * Implementation of 'workspace back_and_forth'. 00770 * 00771 */ 00772 void cmd_workspace_back_and_forth(I3_CMD) { 00773 workspace_back_and_forth(); 00774 00775 cmd_output->needs_tree_render = true; 00776 // XXX: default reply for now, make this a better reply 00777 cmd_output->json_output = sstrdup("{\"success\": true}"); 00778 } 00779 00780 /* 00781 * Implementation of 'workspace <name>' 00782 * 00783 */ 00784 void cmd_workspace_name(I3_CMD, char *name) { 00785 if (strncasecmp(name, "__i3_", strlen("__i3_")) == 0) { 00786 LOG("You cannot switch to the i3 internal workspaces.\n"); 00787 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 00788 return; 00789 } 00790 00791 DLOG("should switch to workspace %s\n", name); 00792 00793 Con *ws = con_get_workspace(focused); 00794 00795 /* Check if the command wants to switch to the current workspace */ 00796 if (strcmp(ws->name, name) == 0) { 00797 DLOG("This workspace is already focused.\n"); 00798 if (config.workspace_auto_back_and_forth) { 00799 workspace_back_and_forth(); 00800 tree_render(); 00801 } 00802 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 00803 return; 00804 } 00805 00806 workspace_show_by_name(name); 00807 00808 cmd_output->needs_tree_render = true; 00809 // XXX: default reply for now, make this a better reply 00810 cmd_output->json_output = sstrdup("{\"success\": true}"); 00811 } 00812 00813 /* 00814 * Implementation of 'mark <mark>' 00815 * 00816 */ 00817 void cmd_mark(I3_CMD, char *mark) { 00818 DLOG("Clearing all windows which have that mark first\n"); 00819 00820 Con *con; 00821 TAILQ_FOREACH(con, &all_cons, all_cons) { 00822 if (con->mark && strcmp(con->mark, mark) == 0) 00823 FREE(con->mark); 00824 } 00825 00826 DLOG("marking window with str %s\n", mark); 00827 owindow *current; 00828 00829 HANDLE_EMPTY_MATCH; 00830 00831 TAILQ_FOREACH(current, &owindows, owindows) { 00832 DLOG("matching: %p / %s\n", current->con, current->con->name); 00833 current->con->mark = sstrdup(mark); 00834 } 00835 00836 cmd_output->needs_tree_render = true; 00837 // XXX: default reply for now, make this a better reply 00838 cmd_output->json_output = sstrdup("{\"success\": true}"); 00839 } 00840 00841 /* 00842 * Implementation of 'mode <string>'. 00843 * 00844 */ 00845 void cmd_mode(I3_CMD, char *mode) { 00846 DLOG("mode=%s\n", mode); 00847 switch_mode(mode); 00848 00849 // XXX: default reply for now, make this a better reply 00850 cmd_output->json_output = sstrdup("{\"success\": true}"); 00851 } 00852 00853 /* 00854 * Implementation of 'move [window|container] [to] output <str>'. 00855 * 00856 */ 00857 void cmd_move_con_to_output(I3_CMD, char *name) { 00858 owindow *current; 00859 00860 DLOG("should move window to output %s\n", name); 00861 00862 HANDLE_EMPTY_MATCH; 00863 00864 /* get the output */ 00865 Output *current_output = NULL; 00866 Output *output; 00867 00868 // TODO: fix the handling of criteria 00869 TAILQ_FOREACH(current, &owindows, owindows) 00870 current_output = get_output_containing(current->con->rect.x, current->con->rect.y); 00871 00872 assert(current_output != NULL); 00873 00874 // TODO: clean this up with commands.spec as soon as we switched away from the lex/yacc command parser 00875 if (strcasecmp(name, "up") == 0) 00876 output = get_output_next(D_UP, current_output); 00877 else if (strcasecmp(name, "down") == 0) 00878 output = get_output_next(D_DOWN, current_output); 00879 else if (strcasecmp(name, "left") == 0) 00880 output = get_output_next(D_LEFT, current_output); 00881 else if (strcasecmp(name, "right") == 0) 00882 output = get_output_next(D_RIGHT, current_output); 00883 else 00884 output = get_output_by_name(name); 00885 00886 if (!output) { 00887 LOG("No such output found.\n"); 00888 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 00889 return; 00890 } 00891 00892 /* get visible workspace on output */ 00893 Con *ws = NULL; 00894 GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child)); 00895 if (!ws) { 00896 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 00897 return; 00898 } 00899 00900 TAILQ_FOREACH(current, &owindows, owindows) { 00901 DLOG("matching: %p / %s\n", current->con, current->con->name); 00902 con_move_to_workspace(current->con, ws, true, false); 00903 } 00904 00905 cmd_output->needs_tree_render = true; 00906 // XXX: default reply for now, make this a better reply 00907 cmd_output->json_output = sstrdup("{\"success\": true}"); 00908 } 00909 00910 /* 00911 * Implementation of 'floating enable|disable|toggle' 00912 * 00913 */ 00914 void cmd_floating(I3_CMD, char *floating_mode) { 00915 owindow *current; 00916 00917 DLOG("floating_mode=%s\n", floating_mode); 00918 00919 HANDLE_EMPTY_MATCH; 00920 00921 TAILQ_FOREACH(current, &owindows, owindows) { 00922 DLOG("matching: %p / %s\n", current->con, current->con->name); 00923 if (strcmp(floating_mode, "toggle") == 0) { 00924 DLOG("should toggle mode\n"); 00925 toggle_floating_mode(current->con, false); 00926 } else { 00927 DLOG("should switch mode to %s\n", floating_mode); 00928 if (strcmp(floating_mode, "enable") == 0) { 00929 floating_enable(current->con, false); 00930 } else { 00931 floating_disable(current->con, false); 00932 } 00933 } 00934 } 00935 00936 cmd_output->needs_tree_render = true; 00937 // XXX: default reply for now, make this a better reply 00938 cmd_output->json_output = sstrdup("{\"success\": true}"); 00939 } 00940 00941 /* 00942 * Implementation of 'move workspace to [output] <str>'. 00943 * 00944 */ 00945 void cmd_move_workspace_to_output(I3_CMD, char *name) { 00946 DLOG("should move workspace to output %s\n", name); 00947 00948 HANDLE_EMPTY_MATCH; 00949 00950 owindow *current; 00951 TAILQ_FOREACH(current, &owindows, owindows) { 00952 Output *current_output = get_output_containing(current->con->rect.x, 00953 current->con->rect.y); 00954 Output *output = get_output_from_string(current_output, name); 00955 if (!output) { 00956 LOG("No such output\n"); 00957 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 00958 return; 00959 } 00960 00961 Con *content = output_get_content(output->con); 00962 LOG("got output %p with content %p\n", output, content); 00963 00964 Con *ws = con_get_workspace(current->con); 00965 LOG("should move workspace %p / %s\n", ws, ws->name); 00966 00967 if (con_num_children(ws->parent) == 1) { 00968 LOG("Creating a new workspace to replace \"%s\" (last on its output).\n", ws->name); 00969 00970 /* check if we can find a workspace assigned to this output */ 00971 bool used_assignment = false; 00972 struct Workspace_Assignment *assignment; 00973 TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { 00974 if (strcmp(assignment->output, current_output->name) != 0) 00975 continue; 00976 00977 /* check if this workspace is already attached to the tree */ 00978 Con *workspace = NULL, *out; 00979 TAILQ_FOREACH(out, &(croot->nodes_head), nodes) 00980 GREP_FIRST(workspace, output_get_content(out), 00981 !strcasecmp(child->name, assignment->name)); 00982 if (workspace != NULL) 00983 continue; 00984 00985 /* so create the workspace referenced to by this assignment */ 00986 LOG("Creating workspace from assignment %s.\n", assignment->name); 00987 workspace_get(assignment->name, NULL); 00988 used_assignment = true; 00989 break; 00990 } 00991 00992 /* if we couldn't create the workspace using an assignment, create 00993 * it on the output */ 00994 if (!used_assignment) 00995 create_workspace_on_output(current_output, ws->parent); 00996 00997 /* notify the IPC listeners */ 00998 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); 00999 } 01000 01001 /* detach from the old output and attach to the new output */ 01002 bool workspace_was_visible = workspace_is_visible(ws); 01003 Con *old_content = ws->parent; 01004 con_detach(ws); 01005 if (workspace_was_visible) { 01006 /* The workspace which we just detached was visible, so focus 01007 * the next one in the focus-stack. */ 01008 Con *focus_ws = TAILQ_FIRST(&(old_content->focus_head)); 01009 LOG("workspace was visible, focusing %p / %s now\n", focus_ws, focus_ws->name); 01010 workspace_show(focus_ws); 01011 } 01012 con_attach(ws, content, false); 01013 01014 /* fix the coordinates of the floating containers */ 01015 Con *floating_con; 01016 TAILQ_FOREACH(floating_con, &(ws->floating_head), floating_windows) 01017 floating_fix_coordinates(floating_con, &(old_content->rect), &(content->rect)); 01018 01019 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"move\"}"); 01020 if (workspace_was_visible) { 01021 /* Focus the moved workspace on the destination output. */ 01022 workspace_show(ws); 01023 } 01024 } 01025 01026 cmd_output->needs_tree_render = true; 01027 // XXX: default reply for now, make this a better reply 01028 cmd_output->json_output = sstrdup("{\"success\": true}"); 01029 } 01030 01031 /* 01032 * Implementation of 'split v|h|vertical|horizontal'. 01033 * 01034 */ 01035 void cmd_split(I3_CMD, char *direction) { 01036 /* TODO: use matches */ 01037 LOG("splitting in direction %c\n", direction[0]); 01038 tree_split(focused, (direction[0] == 'v' ? VERT : HORIZ)); 01039 01040 cmd_output->needs_tree_render = true; 01041 // XXX: default reply for now, make this a better reply 01042 cmd_output->json_output = sstrdup("{\"success\": true}"); 01043 } 01044 01045 /* 01046 * Implementaiton of 'kill [window|client]'. 01047 * 01048 */ 01049 void cmd_kill(I3_CMD, char *kill_mode_str) { 01050 if (kill_mode_str == NULL) 01051 kill_mode_str = "window"; 01052 owindow *current; 01053 01054 DLOG("kill_mode=%s\n", kill_mode_str); 01055 01056 int kill_mode; 01057 if (strcmp(kill_mode_str, "window") == 0) 01058 kill_mode = KILL_WINDOW; 01059 else if (strcmp(kill_mode_str, "client") == 0) 01060 kill_mode = KILL_CLIENT; 01061 else { 01062 ELOG("BUG: called with kill_mode=%s\n", kill_mode_str); 01063 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 01064 return; 01065 } 01066 01067 /* check if the match is empty, not if the result is empty */ 01068 if (match_is_empty(current_match)) 01069 tree_close_con(kill_mode); 01070 else { 01071 TAILQ_FOREACH(current, &owindows, owindows) { 01072 DLOG("matching: %p / %s\n", current->con, current->con->name); 01073 tree_close(current->con, kill_mode, false, false); 01074 } 01075 } 01076 01077 cmd_output->needs_tree_render = true; 01078 // XXX: default reply for now, make this a better reply 01079 cmd_output->json_output = sstrdup("{\"success\": true}"); 01080 } 01081 01082 /* 01083 * Implementation of 'exec [--no-startup-id] <command>'. 01084 * 01085 */ 01086 void cmd_exec(I3_CMD, char *nosn, char *command) { 01087 bool no_startup_id = (nosn != NULL); 01088 01089 DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id); 01090 start_application(command, no_startup_id); 01091 01092 // XXX: default reply for now, make this a better reply 01093 cmd_output->json_output = sstrdup("{\"success\": true}"); 01094 } 01095 01096 /* 01097 * Implementation of 'focus left|right|up|down'. 01098 * 01099 */ 01100 void cmd_focus_direction(I3_CMD, char *direction) { 01101 if (focused && 01102 focused->type != CT_WORKSPACE && 01103 focused->fullscreen_mode != CF_NONE) { 01104 LOG("Cannot change focus while in fullscreen mode.\n"); 01105 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 01106 return; 01107 } 01108 01109 DLOG("direction = *%s*\n", direction); 01110 01111 if (strcmp(direction, "left") == 0) 01112 tree_next('p', HORIZ); 01113 else if (strcmp(direction, "right") == 0) 01114 tree_next('n', HORIZ); 01115 else if (strcmp(direction, "up") == 0) 01116 tree_next('p', VERT); 01117 else if (strcmp(direction, "down") == 0) 01118 tree_next('n', VERT); 01119 else { 01120 ELOG("Invalid focus direction (%s)\n", direction); 01121 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 01122 return; 01123 } 01124 01125 cmd_output->needs_tree_render = true; 01126 // XXX: default reply for now, make this a better reply 01127 cmd_output->json_output = sstrdup("{\"success\": true}"); 01128 } 01129 01130 /* 01131 * Implementation of 'focus tiling|floating|mode_toggle'. 01132 * 01133 */ 01134 void cmd_focus_window_mode(I3_CMD, char *window_mode) { 01135 if (focused && 01136 focused->type != CT_WORKSPACE && 01137 focused->fullscreen_mode != CF_NONE) { 01138 LOG("Cannot change focus while in fullscreen mode.\n"); 01139 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 01140 return; 01141 } 01142 01143 DLOG("window_mode = %s\n", window_mode); 01144 01145 Con *ws = con_get_workspace(focused); 01146 Con *current; 01147 if (ws != NULL) { 01148 if (strcmp(window_mode, "mode_toggle") == 0) { 01149 current = TAILQ_FIRST(&(ws->focus_head)); 01150 if (current != NULL && current->type == CT_FLOATING_CON) 01151 window_mode = "tiling"; 01152 else window_mode = "floating"; 01153 } 01154 TAILQ_FOREACH(current, &(ws->focus_head), focused) { 01155 if ((strcmp(window_mode, "floating") == 0 && current->type != CT_FLOATING_CON) || 01156 (strcmp(window_mode, "tiling") == 0 && current->type == CT_FLOATING_CON)) 01157 continue; 01158 01159 con_focus(con_descend_focused(current)); 01160 break; 01161 } 01162 } 01163 01164 cmd_output->needs_tree_render = true; 01165 // XXX: default reply for now, make this a better reply 01166 cmd_output->json_output = sstrdup("{\"success\": true}"); 01167 } 01168 01169 /* 01170 * Implementation of 'focus parent|child'. 01171 * 01172 */ 01173 void cmd_focus_level(I3_CMD, char *level) { 01174 if (focused && 01175 focused->type != CT_WORKSPACE && 01176 focused->fullscreen_mode != CF_NONE) { 01177 LOG("Cannot change focus while in fullscreen mode.\n"); 01178 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 01179 return; 01180 } 01181 01182 DLOG("level = %s\n", level); 01183 01184 if (strcmp(level, "parent") == 0) 01185 level_up(); 01186 else level_down(); 01187 01188 cmd_output->needs_tree_render = true; 01189 // XXX: default reply for now, make this a better reply 01190 cmd_output->json_output = sstrdup("{\"success\": true}"); 01191 } 01192 01193 /* 01194 * Implementation of 'focus'. 01195 * 01196 */ 01197 void cmd_focus(I3_CMD) { 01198 DLOG("current_match = %p\n", current_match); 01199 if (focused && 01200 focused->type != CT_WORKSPACE && 01201 focused->fullscreen_mode != CF_NONE) { 01202 LOG("Cannot change focus while in fullscreen mode.\n"); 01203 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 01204 return; 01205 } 01206 01207 owindow *current; 01208 01209 if (match_is_empty(current_match)) { 01210 ELOG("You have to specify which window/container should be focused.\n"); 01211 ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n"); 01212 01213 sasprintf(&(cmd_output->json_output), 01214 "{\"success\":false, \"error\":\"You have to " 01215 "specify which window/container should be focused\"}"); 01216 return; 01217 } 01218 01219 int count = 0; 01220 TAILQ_FOREACH(current, &owindows, owindows) { 01221 Con *ws = con_get_workspace(current->con); 01222 /* If no workspace could be found, this was a dock window. 01223 * Just skip it, you cannot focus dock windows. */ 01224 if (!ws) 01225 continue; 01226 01227 /* If the container is not on the current workspace, 01228 * workspace_show() will switch to a different workspace and (if 01229 * enabled) trigger a mouse pointer warp to the currently focused 01230 * container (!) on the target workspace. 01231 * 01232 * Therefore, before calling workspace_show(), we make sure that 01233 * 'current' will be focused on the workspace. However, we cannot 01234 * just con_focus(current) because then the pointer will not be 01235 * warped at all (the code thinks we are already there). 01236 * 01237 * So we focus 'current' to make it the currently focused window of 01238 * the target workspace, then revert focus. */ 01239 Con *currently_focused = focused; 01240 con_focus(current->con); 01241 con_focus(currently_focused); 01242 01243 /* Now switch to the workspace, then focus */ 01244 workspace_show(ws); 01245 LOG("focusing %p / %s\n", current->con, current->con->name); 01246 con_focus(current->con); 01247 count++; 01248 } 01249 01250 if (count > 1) 01251 LOG("WARNING: Your criteria for the focus command matches %d containers, " 01252 "while only exactly one container can be focused at a time.\n", count); 01253 01254 cmd_output->needs_tree_render = true; 01255 // XXX: default reply for now, make this a better reply 01256 cmd_output->json_output = sstrdup("{\"success\": true}"); 01257 } 01258 01259 /* 01260 * Implementation of 'fullscreen [global]'. 01261 * 01262 */ 01263 void cmd_fullscreen(I3_CMD, char *fullscreen_mode) { 01264 if (fullscreen_mode == NULL) 01265 fullscreen_mode = "output"; 01266 DLOG("toggling fullscreen, mode = %s\n", fullscreen_mode); 01267 owindow *current; 01268 01269 HANDLE_EMPTY_MATCH; 01270 01271 TAILQ_FOREACH(current, &owindows, owindows) { 01272 printf("matching: %p / %s\n", current->con, current->con->name); 01273 con_toggle_fullscreen(current->con, (strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT)); 01274 } 01275 01276 cmd_output->needs_tree_render = true; 01277 // XXX: default reply for now, make this a better reply 01278 cmd_output->json_output = sstrdup("{\"success\": true}"); 01279 } 01280 01281 /* 01282 * Implementation of 'move <direction> [<pixels> [px]]'. 01283 * 01284 */ 01285 void cmd_move_direction(I3_CMD, char *direction, char *move_px) { 01286 // 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 01287 int px = atoi(move_px); 01288 01289 /* TODO: make 'move' work with criteria. */ 01290 DLOG("moving in direction %s, px %s\n", direction, move_px); 01291 if (con_is_floating(focused)) { 01292 DLOG("floating move with %d pixels\n", px); 01293 Rect newrect = focused->parent->rect; 01294 if (strcmp(direction, "left") == 0) { 01295 newrect.x -= px; 01296 } else if (strcmp(direction, "right") == 0) { 01297 newrect.x += px; 01298 } else if (strcmp(direction, "up") == 0) { 01299 newrect.y -= px; 01300 } else if (strcmp(direction, "down") == 0) { 01301 newrect.y += px; 01302 } 01303 floating_reposition(focused->parent, newrect); 01304 } else { 01305 tree_move((strcmp(direction, "right") == 0 ? D_RIGHT : 01306 (strcmp(direction, "left") == 0 ? D_LEFT : 01307 (strcmp(direction, "up") == 0 ? D_UP : 01308 D_DOWN)))); 01309 cmd_output->needs_tree_render = true; 01310 } 01311 01312 // XXX: default reply for now, make this a better reply 01313 cmd_output->json_output = sstrdup("{\"success\": true}"); 01314 } 01315 01316 /* 01317 * Implementation of 'layout default|stacked|stacking|tabbed'. 01318 * 01319 */ 01320 void cmd_layout(I3_CMD, char *layout_str) { 01321 if (strcmp(layout_str, "stacking") == 0) 01322 layout_str = "stacked"; 01323 DLOG("changing layout to %s\n", layout_str); 01324 owindow *current; 01325 int layout = (strcmp(layout_str, "default") == 0 ? L_DEFAULT : 01326 (strcmp(layout_str, "stacked") == 0 ? L_STACKED : 01327 L_TABBED)); 01328 01329 /* check if the match is empty, not if the result is empty */ 01330 if (match_is_empty(current_match)) 01331 con_set_layout(focused->parent, layout); 01332 else { 01333 TAILQ_FOREACH(current, &owindows, owindows) { 01334 DLOG("matching: %p / %s\n", current->con, current->con->name); 01335 con_set_layout(current->con, layout); 01336 } 01337 } 01338 01339 cmd_output->needs_tree_render = true; 01340 // XXX: default reply for now, make this a better reply 01341 cmd_output->json_output = sstrdup("{\"success\": true}"); 01342 } 01343 01344 /* 01345 * Implementaiton of 'exit'. 01346 * 01347 */ 01348 void cmd_exit(I3_CMD) { 01349 LOG("Exiting due to user command.\n"); 01350 exit(0); 01351 01352 /* unreached */ 01353 } 01354 01355 /* 01356 * Implementaiton of 'reload'. 01357 * 01358 */ 01359 void cmd_reload(I3_CMD) { 01360 LOG("reloading\n"); 01361 kill_configerror_nagbar(false); 01362 load_configuration(conn, NULL, true); 01363 x_set_i3_atoms(); 01364 /* Send an IPC event just in case the ws names have changed */ 01365 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}"); 01366 01367 // XXX: default reply for now, make this a better reply 01368 cmd_output->json_output = sstrdup("{\"success\": true}"); 01369 } 01370 01371 /* 01372 * Implementaiton of 'restart'. 01373 * 01374 */ 01375 void cmd_restart(I3_CMD) { 01376 LOG("restarting i3\n"); 01377 i3_restart(false); 01378 01379 // XXX: default reply for now, make this a better reply 01380 cmd_output->json_output = sstrdup("{\"success\": true}"); 01381 } 01382 01383 /* 01384 * Implementaiton of 'open'. 01385 * 01386 */ 01387 void cmd_open(I3_CMD) { 01388 LOG("opening new container\n"); 01389 Con *con = tree_open_con(NULL, NULL); 01390 con_focus(con); 01391 sasprintf(&(cmd_output->json_output), 01392 "{\"success\":true, \"id\":%ld}", (long int)con); 01393 01394 cmd_output->needs_tree_render = true; 01395 } 01396 01397 /* 01398 * Implementation of 'focus output <output>'. 01399 * 01400 */ 01401 void cmd_focus_output(I3_CMD, char *name) { 01402 owindow *current; 01403 01404 DLOG("name = %s\n", name); 01405 01406 HANDLE_EMPTY_MATCH; 01407 01408 /* get the output */ 01409 Output *current_output = NULL; 01410 Output *output; 01411 01412 TAILQ_FOREACH(current, &owindows, owindows) 01413 current_output = get_output_containing(current->con->rect.x, current->con->rect.y); 01414 assert(current_output != NULL); 01415 01416 output = get_output_from_string(current_output, name); 01417 01418 if (!output) { 01419 LOG("No such output found.\n"); 01420 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 01421 return; 01422 } 01423 01424 /* get visible workspace on output */ 01425 Con *ws = NULL; 01426 GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child)); 01427 if (!ws) { 01428 cmd_output->json_output = sstrdup("{\"sucess\": false}"); 01429 return; 01430 } 01431 01432 workspace_show(ws); 01433 01434 cmd_output->needs_tree_render = true; 01435 // XXX: default reply for now, make this a better reply 01436 cmd_output->json_output = sstrdup("{\"success\": true}"); 01437 } 01438 01439 /* 01440 * Implementation of 'move [window|container] [to] [absolute] position <px> [px] <px> [px] 01441 * 01442 */ 01443 void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) { 01444 01445 int x = atoi(cx); 01446 int y = atoi(cy); 01447 01448 if (!con_is_floating(focused)) { 01449 ELOG("Cannot change position. The window/container is not floating\n"); 01450 sasprintf(&(cmd_output->json_output), 01451 "{\"success\":false, \"error\":\"Cannot change position. " 01452 "The window/container is not floating.\"}"); 01453 return; 01454 } 01455 01456 if (strcmp(method, "absolute") == 0) { 01457 focused->parent->rect.x = x; 01458 focused->parent->rect.y = y; 01459 01460 DLOG("moving to absolute position %d %d\n", x, y); 01461 floating_maybe_reassign_ws(focused->parent); 01462 cmd_output->needs_tree_render = true; 01463 } 01464 01465 if (strcmp(method, "position") == 0) { 01466 Rect newrect = focused->parent->rect; 01467 01468 DLOG("moving to position %d %d\n", x, y); 01469 newrect.x = x; 01470 newrect.y = y; 01471 01472 floating_reposition(focused->parent, newrect); 01473 } 01474 01475 // XXX: default reply for now, make this a better reply 01476 cmd_output->json_output = sstrdup("{\"success\": true}"); 01477 } 01478 01479 /* 01480 * Implementation of 'move [window|container] [to] [absolute] position center 01481 * 01482 */ 01483 void cmd_move_window_to_center(I3_CMD, char *method) { 01484 01485 if (!con_is_floating(focused)) { 01486 ELOG("Cannot change position. The window/container is not floating\n"); 01487 sasprintf(&(cmd_output->json_output), 01488 "{\"success\":false, \"error\":\"Cannot change position. " 01489 "The window/container is not floating.\"}"); 01490 } 01491 01492 if (strcmp(method, "absolute") == 0) { 01493 Rect *rect = &focused->parent->rect; 01494 01495 DLOG("moving to absolute center\n"); 01496 rect->x = croot->rect.width/2 - rect->width/2; 01497 rect->y = croot->rect.height/2 - rect->height/2; 01498 01499 floating_maybe_reassign_ws(focused->parent); 01500 cmd_output->needs_tree_render = true; 01501 } 01502 01503 if (strcmp(method, "position") == 0) { 01504 Rect *wsrect = &con_get_workspace(focused)->rect; 01505 Rect newrect = focused->parent->rect; 01506 01507 DLOG("moving to center\n"); 01508 newrect.x = wsrect->width/2 - newrect.width/2; 01509 newrect.y = wsrect->height/2 - newrect.height/2; 01510 01511 floating_reposition(focused->parent, newrect); 01512 } 01513 01514 // XXX: default reply for now, make this a better reply 01515 cmd_output->json_output = sstrdup("{\"success\": true}"); 01516 } 01517 01518 /* 01519 * Implementation of 'move scratchpad'. 01520 * 01521 */ 01522 void cmd_move_scratchpad(I3_CMD) { 01523 DLOG("should move window to scratchpad\n"); 01524 owindow *current; 01525 01526 HANDLE_EMPTY_MATCH; 01527 01528 TAILQ_FOREACH(current, &owindows, owindows) { 01529 DLOG("matching: %p / %s\n", current->con, current->con->name); 01530 scratchpad_move(current->con); 01531 } 01532 01533 cmd_output->needs_tree_render = true; 01534 // XXX: default reply for now, make this a better reply 01535 cmd_output->json_output = sstrdup("{\"success\": true}"); 01536 } 01537 01538 /* 01539 * Implementation of 'scratchpad show'. 01540 * 01541 */ 01542 void cmd_scratchpad_show(I3_CMD) { 01543 DLOG("should show scratchpad window\n"); 01544 owindow *current; 01545 01546 if (match_is_empty(current_match)) { 01547 scratchpad_show(NULL); 01548 } else { 01549 TAILQ_FOREACH(current, &owindows, owindows) { 01550 DLOG("matching: %p / %s\n", current->con, current->con->name); 01551 scratchpad_show(current->con); 01552 } 01553 } 01554 01555 cmd_output->needs_tree_render = true; 01556 // XXX: default reply for now, make this a better reply 01557 cmd_output->json_output = sstrdup("{\"success\": true}"); 01558 } 01559 01560 /* 01561 * Implementation of 'rename workspace <name> to <name>' 01562 * 01563 */ 01564 void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) { 01565 LOG("Renaming workspace \"%s\" to \"%s\"\n", old_name, new_name); 01566 01567 Con *output, *workspace = NULL; 01568 TAILQ_FOREACH(output, &(croot->nodes_head), nodes) 01569 GREP_FIRST(workspace, output_get_content(output), 01570 !strcasecmp(child->name, old_name)); 01571 01572 if (!workspace) { 01573 // TODO: we should include the old workspace name here and use yajl for 01574 // generating the reply. 01575 cmd_output->json_output = sstrdup("{\"success\": false, " 01576 "\"error\":\"Old workspace not found\"}"); 01577 return; 01578 } 01579 01580 Con *check_dest = NULL; 01581 TAILQ_FOREACH(output, &(croot->nodes_head), nodes) 01582 GREP_FIRST(check_dest, output_get_content(output), 01583 !strcasecmp(child->name, new_name)); 01584 01585 if (check_dest != NULL) { 01586 // TODO: we should include the new workspace name here and use yajl for 01587 // generating the reply. 01588 cmd_output->json_output = sstrdup("{\"success\": false, " 01589 "\"error\":\"New workspace already exists\"}"); 01590 return; 01591 } 01592 01593 /* Change the name and try to parse it as a number. */ 01594 FREE(workspace->name); 01595 workspace->name = sstrdup(new_name); 01596 char *endptr = NULL; 01597 long parsed_num = strtol(new_name, &endptr, 10); 01598 if (parsed_num == LONG_MIN || 01599 parsed_num == LONG_MAX || 01600 parsed_num < 0 || 01601 endptr == new_name) 01602 workspace->num = -1; 01603 else workspace->num = parsed_num; 01604 LOG("num = %d\n", workspace->num); 01605 01606 /* By re-attaching, the sort order will be correct afterwards. */ 01607 Con *previously_focused = focused; 01608 Con *parent = workspace->parent; 01609 con_detach(workspace); 01610 con_attach(workspace, parent, false); 01611 /* Restore the previous focus since con_attach messes with the focus. */ 01612 con_focus(previously_focused); 01613 01614 cmd_output->needs_tree_render = true; 01615 cmd_output->json_output = sstrdup("{\"success\": true}"); 01616 01617 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"rename\"}"); 01618 }